手机网站备案费用,服装网站建设公司有哪些,公司的网站如何做,网站推广计划机构【Linux】--- 进程的概念 一、进程概念二、PCB1.什么是PCB2.什么是task_struct#xff08;重点#xff01;#xff09;3.task_struct包含内容 三、task_struct内容详解1.查看进程#xff08;1#xff09;通过系统目录查看#xff08;2#xff09;通过ps命令查看#xf… 【Linux】--- 进程的概念 一、进程概念二、PCB1.什么是PCB2.什么是task_struct重点3.task_struct包含内容 三、task_struct内容详解1.查看进程1通过系统目录查看2通过ps命令查看3通过top命令查看4通过系统调用获取进程PID和父进程PPID① 获取进程ID函数getpid和getppid② 获取当前进程ID③ 获取父进程ID 2.状态3.优先级4.上下文数据重点 四、通过系统调用创建进程1.使用fork创建子进程2.理解fork创建子进程重点3.fork后的数据修改 (重点)4.fork的返回值重点1fork返回值含义2根据fork返回值让父子进程执行不同的功能 五、进程状态1.进程状态定义2.进程状态分类1R-运行状态2S-浅睡眠状态3D-深睡眠状态4T-停止状态5Z-僵尸状态6X-死亡状态 3.僵尸进程危害 六、孤儿进程七、关于进程状态的补充1、kill2、僵尸进程 和 孤儿进程3、进程的状态运行挂起阻塞 八、进程优先级1.概念2.为什么要有进程优先级3. 查看系统进程4.PRI和NI5.使用top命令更改进程优先级1更改NI值2NI的取值范围3NI取值范围较小的原因 九、环境变量1.概念2.常见环境变量3.如何查看环境变量4.和环境变量相关的命令5.环境变量的全局属性6.本地变量 十、程序地址空间1.程序地址空间分布2.程序地址空间是虚拟地址3.虚拟地址1页表2如何理解地址空间2为什么要存在地址空间 4.写时拷贝 一、进程概念
课本概念言简意赅的说就是一个正在执行的程序 内核观点进程是承担系统资源CPU、内存的实体
当我们写完代码之后编译连接就形成一个可执行程序.exe本质是二进制文件在磁盘上存放着。双击这个.exe文件把程序运行起来就是把程序从磁盘加载到内存然后CPU才能执行其代码语句。当把exe文件加载到内存后此时这个exe文件形成的这程序就叫做进程。所有启动程序的过程本质都是在系统上创建进程双击.exe文件也不例外 二、PCB
1.什么是PCB
操作系统描述进程的时候就是PCBPCBprocess control block
2.什么是task_struct重点
在Linux中的PCB叫做task_structtask_struct就是PCB的一种 创建进程不仅仅是把代码和数据加载到内存里还要为进程创建task_struct 所以进程 task_struct 代码和数据
根据“先描述再组织” ①描述task_struct在Linux内核中就是一种结构体里面包含着进程的相关信息 ②组织系统里的进程都以task_struct 链表 的形式储存在内核里 3.task_struct包含内容
标示符: 描述本进程的唯一标示符用来区别其他进程。PID 状态: 任务状态退出代码退出信号等。 优先级: 相对于其他进程的优先级。 程序计数器: 程序中即将被执行的下一条指令的地址。 内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针。 上下文数据: 进程执行时处理器的寄存器中的数据。 I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。 记账信息: 可能包括处理器时间总和使用的时钟数总和时间限制记账号等。
三、task_struct内容详解
1.查看进程
1通过系统目录查看
proc是一个系统文件夹在根目录下通过ls可以看到该文件夹 可以通过
ls /proc命令查看进程的信息数字是PID
如果想查看进程信息比如查看PID为989的进程信息使用命令
ls /proc/PID2通过ps命令查看
使用
ps aux //查看所有进程如果结合grep可以查看某一个进程
比如想查看包含proc的进程可以使用如下命令
ps aux | head -1 ps aux | grep proc | grep -v grep3通过top命令查看
也可以通过top查看进程
top4通过系统调用获取进程PID和父进程PPID
① 获取进程ID函数getpid和getppid
获取进程ID和获取父进程ID可以通过以下方式进行获取其中pid_t是short类型变量
#include sys/types.h
#include unistd.hpid_t getpid(void);//获取当前进程ID
pid_t getppid(void);//获取当前进程的父进程ID② 获取当前进程ID
获取当前进程test_blog.c
#includesys/types.h
#includestdio.h
#includeunistd.hint main()
{while(1){printf(i am a process! :pid:%d\n,getpid();//获取当前进程IDsleep(1);}return 0;
}Makefile
test_blog:test_blog.c gcc -o $ $^ -g
.PHONY:clean
clean:rm -rf test_blog ③ 获取父进程ID
#includesys/types.h
#includestdio.h
#includeunistd.hint main()
{while(1){printf(i am a process! :pid:%d\n,my parent id is-ppid:%d\n,getpid(),getppid());//获取当前进程ID和ppidsleep(1);}return 0;
}Makefile
test_blog:test_blog.c gcc -o $ $^ -g
.PHONY:clean
clean:rm -rf test_blog2.状态
之前写代码的返回值是0 这个0是进程退出时的退出码这个退出码是要被父进程拿到的返回给系统父进程通过系统拿到。比如以下代码的退出码是0
#includestdio.h
int main()
{printf(hello linux!\n);return 0;
}那么使用
echo $?假如将退出码改为99 所以状态的作用是输出最近执行的命令的退出码。
3.优先级
权限指的是能不能而优先级指的是已经能了有权限了但是至于什么时候执行得先排队 这就像在餐馆点餐结帐出小票之后已经可以拿到餐食了但是什么时候能拿到呢需要排队在这个过程中是否出小票就代表是否有权限排队取餐就代表的是优先级。
4.上下文数据重点
当操作系统维护进程队列时由于进程代码可能不会在很短时间就能执行完毕假如操作系统也不会在执行一个进程时让其他进程一直等待直到当前进程执行完毕那可能当前进程需要执行很久才执行完毕其他进程会一直处于等待状态这不合理。那么操作系统在实际执行进程调度时按时间片分配执行时间时间片一到就切换下一个进程。多管齐下 时间片是一个进程单次运行的最长时间。 比如有4个进程在40ms之内先让第一个进程运行10ms时间一到就算没有运行完毕就把第一个进程从队列头移动到队列尾再让第二个进程运行10ms。40ms后使得用户感知到这4个进程都推进了其实本质上是通过CPU的快速切换完成的。
有可能在一个进程的生命周期内被调度成百上千次。比如CPU有5个寄存器进程A正在运行时时间片到了被切走的时候会把CPU里和进程A相关的保存到寄存器里面的临时数据带走。当进程B调度完后再次调度进程A的时候会把进程A里面保存的临时数据再恢复到CPU的寄存器当中继续上次切走时的状态继续运行因此保护上下文能够保证多个进程切换时共享CPU。
保存上下文数据实际上就是保存某一个进程在用完时间片后剩下该进程没有被执行的代码和数据
四、通过系统调用创建进程
1.使用fork创建子进程
fork用来创建子进程
#include unistd.h
pid_t fork(void);//通过复制调用进程创建一个新进程。新进程称为子进程。调用进程称为父进程。看一个奇奇怪怪的代码
#includeunistd.h
#includestdio.hint main()
{int ret fork();if(ret 0){printf(I am child\n);sleep(1);}else{printf(I am father\n);sleep(1);}return 0;
} 发现两句话都打印了也就是既执行了if又执行了else 再看代码
#includestdio.h
#includeunistd.hint main()
{int ret fork();while(1){printf(I am a process ,pid %d,ppid %d\n,getpid(),getppid());sleep(1);}return 0;
}发现有两个pid和ppid
这说明执行while死循环不只一个执行流在执行 而是两个执行流在执行每一行两个id都是父子关系。这是因为fork之后有两个执行流同时执行while循环。
2.理解fork创建子进程重点
./可执行程序、命令行、fork站在操作系统角度创建进程的方式没有差别都是系统中多了个进程。fork创建出来的子进程和父进程不一样父进程在磁盘上是有可执行程序的运行可执行程序时会把对应的代码和数据加载到内存中去运行。
但是子进程只是被创建出来的没有进程的代码和数据默认情况下子进程会继承父进程的代码和数据子进程的数据结构task_struct也会以父进程的task_struct为模板来初始化子进程的task_struct。因此子进程会执行父进程fork之后的代码来访问父进程的数据。
3.fork后的数据修改 (重点)
1代码是不可以被修改的。 那么数据呢子进程和父进程共享数据当父进程修改数据时子进程看到的数据也被修改了那么父进程就会影响子进程。那这两个进程还具有独立性吗
2当父子进程都只读不写数据时数据是共享的。
但是这两个进程中的任何一个进程要修改数据都会对对方造成影响这时候作为进程管理者操作系统就要站出来干涉了。
父子进程修改数据时操作系统会在内存中重新开辟一块空间把这部分数据拷贝过去之后再做修改而不是在原数据上做修改这叫做-写时拷贝。 3写时拷贝存在的意义维护进程的独立性提高空间利用率
对空间利用率的理解而在创建子进程时不会让子进程把父进程的所有数据全部都拷贝一份因为并不是所有情况下都可能产生数据写入/修改所以这就避免了fork时的效率降低和浪费更多空间的问题。因此只有写入数据时再开辟空间才是合理的。
4.fork的返回值重点
1fork返回值含义
fork出子进程后根据fork返回值的不同一般会让子进程和父进程去干不同的事情这时候如何区分父子进程呢fork函数的返回值如下 打印一下fork的返回值 这说明
fork准备return的时候子进程被创建出来了。这里有两个返回值由于函数的返回值是通过寄存器写入的 函数返回时把变量值写入到保存数据的空间。所以当父子执行流执行完毕以后有两次返回就有两个不同的返回值就要进行写入谁先返回谁就先写入即发生写时拷贝。给父进程返回子进程的pid的原因是一个父进程可能有多个子进程子进程必须得用pid来进行标识区分所以一般给父进程返回子进程的pid来控制子进程。子进程想知道父进程pid可以通过get_ppid( )来获取。这样就可以维护父子进程了。
2根据fork返回值让父子进程执行不同的功能
通过返回值来让父子进程分流去执行不同的功能
#includestdio.h
#includeunistd.hint main()
{pid_t ret fork();//通过if else来分流if(ret 0)//child{while(1){printf(I am child, pid %d,ppid %d\n,getpid(),getppid());sleep(1);}}else if(ret 0)//parent{while(1){printf(I am parent, pid %d,ppid %d\n,getpid(),getppid());sleep(3);}}else{}return 0;
}五、进程状态
1.进程状态定义
一个进程从创建而产生至撤销而消亡的整个生命期间有时占有处理器执行有时虽可运行但分不到处理器、有时虽有空闲处理器但因等待某个事件的发生而无法执行这说明进程和程序不相同它是活动的且有状态变化的能够体现一个进程的生命状态可以用一组状态来描述 内核源代码里面的状态定义
/*
* The task state array is a strange bitmap of
* reasons to sleep. Thus running is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] {//进程也叫做任务R (running), /* 0 */S (sleeping), /* 1 */D (disk sleep), /* 2 */T (stopped), /* 4 */t (tracing stop), /* 8 */X (dead), /* 16 */Z (zombie), /* 32 */
};使用如下两条命令都可以查看进程当前状态
ps auxps axj2.进程状态分类
1R-运行状态
R(Running)要么在运行中要么在运行队列里所以R状态并不意味着进程一定在运行中因此系统中可能同时存在多个R状态进程。 如果运行时在后面加就会在后台运行就变成R状态了 2S-浅睡眠状态
S(Sleeping) 进程正在等待某事件完成可以被唤醒也可被杀死浅睡眠状态也叫做可中断睡眠。
比如如下代码
status.c
int main()
{printf(hello linux\n);sleep(20);return 0;
}在运行后20s内查看status进程的状态发现为S执行kill命令后该进程被杀死
3D-深睡眠状态
D(Disk sleep)进程正在等待IO不能被杀死必须自动唤醒才能恢复也叫不可中断睡眠状态。
进程等待IO时比如对磁盘写入正在写入时进程处于深度睡眠状态需要等待磁盘将是否写入成功的信息返回给进程因此此时进程不会被杀掉
4T-停止状态
T(Stopped)可以通过发送 SIGSTOP 信号给进程来停止T进程。这个被暂停的进程可以通过发送SIGCONT 信号让进程继续运行。
运行起来的status进程通过SIGSTOP信号被暂停了状态由S变为T 又通过SIGCONT信号恢复了状态由T变为S kill -l命令可列出操作系统中所有信号其中18就是SIGCONT信号19就是SIGSTOP信号 因此上述kill SIGCONT 进程号 也可以用kill -18 进程号来代替kill SIGSTOP 进程号 也可以写成kill -19 进程号来代替。
5Z-僵尸状态
概念 僵死状态Zombies是一个比较特殊的状态。当进程退出并且 父进程 没有读取到 子进程 退出的返回代码时就会产生僵尸进程 僵死进程会以终止状态保持在进程表中并且会一直在等待父进程读取退出状态代码。 所以只要子进程退出父进程还在运行但父进程没有读取子进程状态子进程进入Z状态 6X-死亡状态
这个状态只是一个返回状态在任务列表里看不到这个状态。因为当进程退出时释放进程所占用的资源时一瞬间就释放完了所以死亡状态看不到。
3.僵尸进程危害
1进程的退出状态必须被维持下去因为它要把退出信息告诉父进程如果父进程一直不读取那么子进程就一直处于僵尸状态
2由于进程基本信息是保存在task_struct中的如果僵尸状态一直不退出只要父进程没有读取子进程退出信息那么PCB一直都需要维护。
3如果一个父进程创建了多个子进程并且不回收那么就要维护多个task_struct 数据结构会造成内存资源的浪费
4僵尸进程申请的资源无法进行回收那么僵尸进程越多实际可用的资源就越少也就是说僵尸进程会导致内存泄漏
六、孤儿进程
僵尸进程是子进程先退出但是父进程没有读取子进程的退出信息。
假如父进程先退出子进程后退出此时子进程处于孤儿状态没有父进程来读取它的退出信息此时子进程就称为孤儿进程。 孤儿进程一般会被1号进程回收/领养1号进程就是OS本身
七、关于进程状态的补充
1、kill
kill 命令可以向指定进程发起命令 ① kill -9 XXX 杀掉进程 ②kill -19 XXX 暂停进程 ③kill -18 XXX继续进程
2、僵尸进程 和 孤儿进程 3、进程的状态运行挂起阻塞
1一个进程的运行会在cpu内存中的一个进程队列里面只要一个进程在队列里面或者已经准备好进入进程队列他们就是R运行状态。 2并发、并行的概念 3阻塞态 ①操作系统如何对外设显示器、键盘、磁盘、网卡…进行资源管理 实际上就是对外设的数据进行管理所有的外设都自己task_struct 里面的数据进行管理即可
注意不仅只有CPU有进程队列各种外设也有自己的wait_queue ②让一个进程变为阻塞在内核视角就是把一个进程从它的运行队列剥离下来连接到外设的task_struct的wait_queue()里面此时的进程就变为了 阻塞态 注意入队列的 永远只是进程的 task_struct而不是代码和数据
4挂起态 注意但是频繁地唤入唤出会导致效率问题
八、进程优先级
1.概念
进程的优先级就是CPU资源分配的先后顺序 即进程的优先权优先权高的进程有优先执行权力。
其他概念
并发: 多个进程在一个CPU下采用进程切换的方式在一段时间之内让多个进程都得以推进称之为并发并行: 多个进程在多个CPU下分别同时进行运行这称之为并行
2.为什么要有进程优先级
因为CPU资源是有限的一个CPU只能同时运行一个进程当系统中有多个进程时就需要进程优先级来确定进程获取CPU资源的能力。
3. 查看系统进程
ps -l可以看到
UID : 代表执行者的身份表明该进程由谁启动 PID : 代表这个进程的代号 PPID 代表这个进程是由哪个进程发展衍生而来的亦即父进程的代号 PRI 代表这个进程可被执行的优先级其值越小越早被执行 NI 代表这个进程的nice值
4.PRI和NI
PRI是进程的优先级也就是就是程序被CPU执行的先后顺序此值越小进程的优先级别越高
NI就是nice值表示进程可被执行的优先级的修正数值PRI值越小越快被执行加入nice值后将会使得PRI变为PRI(new)PRI(old)nice当nice值为负值时该程序优先级值将变小即其优先级会变高则其越快被执行Linux下调整进程优先级就是调整进程nice值nice其取值范围是-20至19一共40个级别。
注意 nice值不是进程的优先级是进程优先级的修正数据会影响到进程的优先级变化。
5.使用top命令更改进程优先级
1更改NI值
先运行一个进程使用
ps -l查看进程号、优先级及NI值比如执行./forkProcess_getpid进程
可以查看到优先级为80NI值为0 在运行top命令之后输入r就会有PID to renice此时输入进程号5255再输入NI值此处设为10 然后查看进程的优先级和NI值优先级变成了90NI值变成了10 说明优先级和NI值已经被改了。由此也能验证
PRI(new) PRI(old)nicePRI(old)一般都是80这就是为什么没有修改NI值之前用ps -al命令查看到的进程的PRI都是80的原因。
2NI的取值范围
NI的取值范围为-2019一共40个级别。
3NI取值范围较小的原因
因为优先级再怎么设置也只能是一种相对的优先级不能出现绝对的优先级否则会出现很严重的进程“饥饿问题”即某个进程长时间得不到CPU资源而调度器需要较为均衡地让每个进程享受到CPU资源。
九、环境变量
1.概念
环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数如我们在编写C/C代码的时候在链接的时候从来不知道我们的所链接的动态静态库在哪里但是照样可以链接成功生成可执行程序原因就是有相关环境变量帮助编译器进行查找环境变量通常具有某些特殊用途还有在系统当中通常具有全局特性
2.常见环境变量
PATH : 指定命令的搜索路径HOME : 指定用户的主工作目录(即用户登陆到Linux系统中时,默认的目录)SHELL : 当前Shell,它的值通常是/bin/bash。
3.如何查看环境变量
echo $PATH系统通过PATH进行路径查找查找规则就是在PATH中先在第一个路径中找找不到就在第二个路径中找再找不到就在第三个路径中找……如果找到了就不往下找了直接将找到的路径下的程序运行起来这就完成了路径查找。即系统执行命令时操作系统通过环境变量PATH去搜索对应的可执行程序路径。
如何让一个可执行程序Progress执行时不带./跟执行系统命令一样有2种做法
把Progress命令拷贝到以上5种任意一个路径里不过这种做法不推荐会污染命令池把当前路径添加到PATH环境变量中
平时安装软件就是把软件拷贝到系统环境变量中特定的命令路径下就完成了安装的过程其实就是拷贝的过程。
不能直接把当前路径赋值给PATH否则上面的6种路径就全没了。可以使用export导入环境变量
export PATH$PATH:程序路径 查找到 forkProcess的路径 添加环境变量 现在在其他路径下也可以执行该可执行程序了比如在家目录下执行
4.和环境变量相关的命令
环境变量的本质是操作系统在内存/磁盘上开辟的空间用来保存系统相关的数据。在语言上定义环境变量的本质是在内存中开辟空间存放key、value值即变量名和数据。
echo显示某个环境变量值export设置一个新的环境变量env显示所有环境变量set显示本地定义的shell变量和环境变量unset清除环境变量
1、echo显示某个环境变量值 2、export设置一个新的环境变量 3、env显示所有环境变量
4、set显示本地定义的shell变量和环境变量 5、unset清除环境变量
5.环境变量的全局属性
环境变量通常具有全局属性可以被子进程继承。 如下代码
geteEnvironment.c
#includestdio.h
#includesys/types.h
#includeunistd.hint main()
{printf(pid %d,ppid %d\n,getpid(),getppid());return 0;
}发现每次运行该程序子进程的ID都不相同但是父进程的ID都相同 命令行上启动的进程父进程都是bashbash的环境变量是从系统里读的系统的环境变量就在系统配置中bash登陆时bash就把系统的配置导入到自己的上下文当中。子进程的环境变量是系统给的也就是父进程bash给的。环境变量一旦导出是可以影响子进程的
6.本地变量
与环境变量相对的还有本地变量针对当前用户的当前进程生效是一种临时变量退出本次登陆后就失效了。
如下变量value的值在没有退出登录前打印到是5ctrld退出登录后 退出重新登录后就不存在了 本地变量能被子进程继承吗用env查看发现shell的上下文中是没有的 说明本地变量是不能被继承的只能bash自己用。
十、程序地址空间
1.程序地址空间分布
C/C程序地址空间
2.程序地址空间是虚拟地址
先看一段下面的代码子进程在运行过程中修改了全局变量的值
printfFork.c
#includestdio.h
#includestring.h
#includeunistd.hint g_Value 1;int main()
{//发生写时拷贝时数据是父子进程各自私有一份if(fork() 0)//子进程{int count 5;while(count){printf(child,times:%d,g_Value %d,g_Value %p\n,count,g_Value,g_Value);count--;sleep(1);if(count 3){printf(############child开始更改数据############\n);g_Value 5;printf(############child数据更改完成############\n);}}}else//父进程{while(1){printf(father:g_Value %d,g_Value %p\n,g_Value,g_Value);sleep(1);}}return 0;
}但是打印时却发现同一个地址g_Value值却不一样 如果写时拷贝访问的是同一个物理地址的话为什么得到的g_Value是不一样的值呢所以程序地址空间使用的不是物理地址而是虚拟地址。
3.虚拟地址
1页表
页表是一种数据结构记录页面和页框的对应关系本质是映射表增加了权限管理隔离了地址空间能够将虚拟地址转换成物理地址。操作系统为每个进程维护一张页表。
2如何理解地址空间
地址空间本质是内核的一个struct结构体包含的都是各个区域的start和end
2为什么要存在地址空间
①地址空间保证了每个进程以统一的视角有序的区域划分进行管理从而实现了进程的独立性。 ②可以对进程管理模块 和 物理管理模块 进行解耦 具体来说操作系统可以在加载进程时确定映射关系之后物理内存的分配和进程的管理就可以各自独立进行互不影响。 ③保护数据安全拦截非法请求 例如当某个进程试图访问其未被授予权限的内存区域时操作系统能够检测到这种非法访问并采取相应的措施如终止该进程以防止数据泄露或损坏。
4.写时拷贝
深度理解写时拷贝 写车拷贝针对于父进程和子进程或者多个子进程之间指向同一块物理内存其数据也相同当某一个进程想要修改数据或者写入的时候操作系统就会在物理内存里面重新开辟一块空间然后会把原来的数据原模原样地拷贝到新开物理内存空间中然后再经过页表对物理地址的重新进行了映射然后将你想要修改或者写入的数据通过页表新映射关系找到那块开的物理内存空间然后对数据进行修改或者写入
注意1、写时拷贝是因为当你想要入的数据被按下了暂停键因为操作系统需要在物理内存中新开一块空间修改页表的映射关系所以叫做写时拷贝 2、你写入或者修改的数据不是直接进了新开物理内存而是操作系统在新开这块物理内存的时候是将原来的数据拷贝到新开物理空间之后才会修改或者写入 3、写时拷贝只会在你100%确实要写入或者修改数据的时候才会发生如果你不写是不会发生的