建设网站需要营业执照,上海集团网站制作,专业的建站,杭州做销售去哪个网站好W...Y的主页 #x1f60a;
代码仓库分享#x1f495; 前言#xff1a;上篇文章中我们认识了进程#xff0c;可执行程序在内存中加载运行被称作进程#xff0c;而操作系统是通过给每一个可执行程序创建一个PCB来管理进程的。并且学习了一些查看进程的指令#xff0c;认识…
W...Y的主页
代码仓库分享 前言上篇文章中我们认识了进程可执行程序在内存中加载运行被称作进程而操作系统是通过给每一个可执行程序创建一个PCB来管理进程的。并且学习了一些查看进程的指令认识了进程中的PID与PPID分别代表一个进程的唯一标识ID与他的父进程的ID今天我们继续了解进程中的属性探索其中的规则。
目录
通过系统调用创建进程-fork初识
进程状态
进程排队
Linux内核源代码
运行状态
阻塞状态
挂起状态
各个状态的情况
Z(zombie)-僵尸进程
僵尸进程危害
孤儿进程 通过系统调用创建进程-fork初识
当我们执行一个可执行文件时就相当于创建了一个进程我们也可以在一个可执行程序中进行创建一个进程。通过对fork函数的理解我们就可以实现。
我们可以在Linux下使用man fork来查看其中的内容 #includestdio.h2 #includeunistd.h3 4 int main()5 {6 printf(before fork: I am a prcess, pid: %d, ppid: %d\n, getpid(), getppid());7 fork();8 printf(after fork: I am a prcess, pid: %d, ppid: %d\n, getpid(), getppid()); 9 return 0;10 }我们创建一个可执行程序后使用fork来运行我们可以看到 在fork直线printf只打印一遍而fork之后却打印了两遍。说明在fork之前只有一个执行分支而fork之后就有两个执行分支因为创建了子进程。 我们通过打印的结果看来after fork中第三行的ppid是第二行的pid说明fork创建了自己的子进程。fork之后代码共享
fork有自己的返回值pid_t 如果fork成功父进程返回子进程的pid子进程返回0。我们知道返回值后就可以操作父子进程各做各的事情了我们让其死循环打印每隔一秒打印一次
int main()14 {15 pid_t id fork();16 if(id 0)17 {18 //子进程19 while(1)20 {21 printf(child fork : I am process, pid: %d, ppid: %d\n, getpid(), getppid());22 sleep(1);23 }24 }25 else{26 //父进程27 while(1)28 {29 printf(parent fork : I am process, pid: %d, ppid: %d\n, getpid(), getppid());30 sleep(1); 31 }32 }33 return 0;34 } 以前在vs中写代码时当程序进入死循环后不可能继续出来到下一个死循环中去而在父子进程下却可以两个死循环同时进行 操作系统会在创建好的可执行程序后构建一个task_struct子进程也会创建一个task_struct但是子进程会指向父进程的代码块与数据区中共享同一块数据父进程会将自己的task_struct中的大部分属性拷贝给子进程所以子进程才能和父进程使用同一块数据与代码。
那很多人就会有疑问为什么fork函数有两个返回值一般函数不是只有一个吗那如果一个函数已经到了return的时候这个函数的整体逻辑已经完成了所以我们可以理解为在返回之前就已经有父子两个进程了所以就会有两个返回值。
父子进程代码共享数据各自开辟空间私有一份采用写时拷贝这个我们后面会讲。
我们如何创建多个进程呢
#includesys/types.h38 const int num 10;39 40 void Worker()41 {42 int cnt 12;43 while(cnt)44 {45 printf(child %d is rnning, cnt: %d\n, getpid(), cnt);46 cnt--;47 sleep(1);48 }49 } 50 51 int main() 52 {53 for(int i 0; i num ;i)54 { 55 pid_t id fork();56 if(id 0) break; 57 if(id 0){58 //子进程 59 Worker(); 60 exit(0); // C用过, 让子进程直接退出61 } 62 printf(father create child process success, child pid: %d\n, id);63 sleep(1); 64 } 65 66 //只有父进程会执行到这里67 sleep(15);68 69 return 0;70 }我们使用循环创建了10个子进程上述就是代码。
进程状态
进程排队
说到进程状态我们先来说一说进程排队的事情。进程 task_struct 可执行程序。我们再Windows中使用软件时当一个软件崩溃后不会影响其他软件说明进程是一个个独立的当我们写一个死循环程序时别的程序也可以正常运行证明进程不是一直在运行的即使在CPU中也一样。因为计算机中有个时间片的概念比如时间片规定为1ms当一个程序在CPU上已经运行了1ms后就会被剥离下来让后面的程序占用CPU这就是为什么死循环也不会影响其他程序 当我们运行这个程序时在scanf时就需要我们键盘输入但是我们不输入时就会一直等待。所以通过这个用例我们就可以知道在等待某种资源。
进程的排队其实不是程序排队而是对应的task_struct在排队。并且task_struct的管理是用数据结构进行管理的但是最奇特的是它可以插入多种数据结构。 在task_struct中存放了多个结构体结构体里面有两个指针一个指针指向下一个结构体首一个指针指向上一个结构体。这样只要task_struct中有多个结构体我们就可以使用多种数据结构进行匹配。
Linux内核源代码
为了弄明白正在运行的进程是什么意思我们需要知道进程的不同状态。一个进程可以有几个状态在Linux内核里进程有时候也叫做任务。 下面的状态在kernel源代码里定义
/*
* 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 */
};
R运行状态running: 并不意味着进程一定在运行中它表明进程要么是在运行中要么在运行队列里。 S睡眠状态sleeping): 意味着进程在等待事件完成这里的睡眠有时候也叫做可中断睡眠 interruptible sleep。 D磁盘休眠状态Disk sleep有时候也叫不可中断睡眠状态uninterruptible sleep在这个状态的进程通常会等待IO的结束。 T停止状态stopped 可以通过发送 SIGSTOP 信号给进程来停止T进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。 X死亡状态dead这个状态只是一个返回状态你不会在任务列表里看到这个状态。 我们看源码可知所谓的状态本质就是一个整形变量类似与#define定义。而这些状态就是Linux中多个进程的后续动作在进程排队时我们就需要这些状态来区分进程。 运行状态
只要这个程序万无一失准备就绪然后他们的PCB就开始在cpu进行排队我们把这种进程叫做R运行状态。 阻塞状态
什么是阻塞状态呢 当我们需要输入时就必须要键盘输入。但是这些硬件都是被操作系统管理起来的。这些硬件管理就犹如cpu管理软件时创建的PCB一样。这些软件也有对应的结构体并且将他们连接起来这样操作系统才可以更好的维护增删查改。当一个程序需要一款硬件时其就会从运行状态转变为阻塞状态并且将自己的PCB与硬件的结构体进行排队连接。
总结当我们的进程等待某种硬件资源时资源如果没有准备就绪我们的进程的task_struct只能先将自己设置为阻塞状态然后将自己的PCB连入等待的硬件资源队列中。状态的变迁引起的是PCB会被操作系统变迁到不同对列中去
挂起状态
挂起状态必须要有一个大前提就是计算机的资源已经非常小了内存不够了这时我们的某些进程比如阻塞的进程因为这些进程当前不会被cpu调用为了腾出内存就出现了挂起状态。
当资源被挂起就会将进程的代码以及数据拷贝到磁盘的swap分区中这个过程我们称作唤出。而当cpu要进行调度此进程时在从磁盘中将代码与数据拷贝到内存中这个过程叫做唤入。
各个状态的情况
当我们写入一个死循环 这时这个程序一直在循环执行这个程序但是我们通过监视可以看出这个进程的状态为S。证明这个进程为睡眠状态因为cpu的执行速度非常的快我们基本捕捉不到这个进程的运行状态并且这个程序我们是要往显示器上打印数据cpu的速度远远高于显示器所以我们看不到。但是当我们在程序中有一个空语句的循环时我们就可以看到此进程一直是R状态。
那后面的是什么呢代表的是前端进程而没有代表的是后端进程。当有时我们的显示器只能用来显示此进程但是当没有时我们既可以看到进程显示的内容而且可以执行我们的Linux命令。但是前端进程使用ctrlc是可以终止的但是后端进程只能使用kill -9 pid才可以终止
Linux默认执行程序时是前端进程若想成为后端进程 ./可执行程序 。
那我们说的S睡眠状态就是阻塞状态也是可中断睡眠状态。还有一个D状态叫做深度睡眠状态也叫不可中断睡眠就是操作系统不能将其终止。
我们可以使用kill -l指令查看我们可以对进程发送的信号 其中我们最常用的指令信号就是kill -9 PID杀死当前进程。而kill -19 PID就可以使进程状态为T状态也就是暂停状态 我们就可以将S状态转变为T状态。要想在恢复到运行的状态kill -18 PID即可。
Z(zombie)-僵尸进程
僵死状态Zombies是一个比较特殊的状态。当进程退出并且父进程没有使用wait()系统调用没有读取到子进程退出的返回代码时就会产生僵死(尸)进程僵死进程会以终止状态保持在进程表中并且会一直在等待父进程读取退出状态代码。所以只要子进程退出父进程还在运行但父进程没有读取子进程状态子进程进入Z状态。
创建一个有僵尸进程的例子 我们先创造一个父子进程然后在五秒后exit(0)直接退出子进程但是我们使用wait函数来读取子进程的信息所以子进程就会由S变为Z成为僵尸进程。
僵尸进程危害
进程的退出状态必须被维持下去因为他要告诉关心它的进程父进程你交给我的任务我办的怎么样了。可父进程如果一直不读取那子进程就一直处于Z状态。 维护退出状态本身就是要用数据维护也属于进程基本信息所以保存在task_struct(PCB)中换句话说Z状态一直不退出PCB一直都要维护。 那一个父进程创建了很多子进程就是不回收是不是就会造成内存资源的浪费。因为数据结构对象本身就要占用内存想想C中定义一个结构体变量对象是要在内存的某个位置进行开辟空间所以内存就会泄露
孤儿进程
什么是孤儿进程呢顾名思义孤儿进程就是当父进程提前退出而子进程还没退出时父进程的父亲是bash所以退出后不会担心变成僵尸进程但是子进程就没有了父进程所以就变成了孤儿进程。
那孤儿进程没有了父亲当进程结束时怎么样回收孤儿进程的各种资源呢如果没有人回收那么是不是会变成僵尸进程当孤儿进程太多时那么僵尸进程就会变多最终导致资源泄漏系统崩溃呢我们可以先来看一个程序 我们可以看出当父进程提前结束时子进程会被bash所接管子进程的父亲变成了bash所以当子进程结束时会有bash进程帮他回收资源不会出现僵尸进程 以上就是本次全部内容感谢大家观看