一、Linux进程状态一个进程从创建而产生至撤销而消亡的整个生命期间有时占有处理器执行有时虽然可以运行但分不到处理器有时虽然有空闲处理器但因等待某个时间的发生而无法执行这一切都说明进程和程序不相同进程是活动的且有状态变化的于是就有了进程状态这一概念。代码语言javascriptAI代码解释//linux源代码如下 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*/ };注意进程的当前状态是保存到自己的进程控制块PCB当中的在Linux操作系统当中也就是保存在task_struct当中的。在Linux操作系统当中我们可以通过 ps aux 或 ps axj 命令查看进程的状态。ps ajx在这里插入图片描述ps aux在这里插入图片描述1. Rruning— 运行状态运行状态一个进程处于运行状态并不说明他一定正在运行中。 它可能正在CPU中运行也可能在CPU对应的运行队列中准备接受调度。代码语言javascriptAI代码解释//通过一个死循环来查看运行状态 1 #includestdio.h 2 int main() 3 { 4 while(1) 5 { 6 7 } 8 9 return 0; 10 }在这里插入图片描述此时我们可以看到STATstate状态栏那一栏我们的进程对应的状态为RR是运行状态代表是前台运行没有就是后台运行。 我们的死循环程序会不断的重复进行代码的执行占用CPU的资源它也不满足访问外事进入到阻塞状态或者其他状态的情况所以它是运行状态。2. Ssleeping— 睡眠状态睡眠状态说明进程在等待某件事情的完成。 处于浅度睡眠状态的进程随时可以被唤醒也可以被杀掉这里的睡眠有时候也可叫做可中断睡眠interruptible sleep。代码语言javascriptAI代码解释#include stdio.h #include unistd.h int main() { while(1) { printf(i am a process\n); sleep(1); // 睡眠1秒 } return 0; }我们通过ps ajx | head -1 ps ajx | grep 文件名来查看它的状态在这里插入图片描述虽然从代码逻辑上看程序确实在不断地循环执行但在操作系统层面的进程调度视角下情况却有所不同。当程序执行到sleep(1)时会发生一个关键的状态转变进程会主动让出 CPU 的使用权并进入等待队列。此时操作系统内核会将这个进程标记为睡眠状态S 状态因为进程明确表示它将在接下来的1秒钟内不需要 CPU 资源。这种睡眠不是被动的等待而是进程通过系统调用主动发起的请求。在这个过程中printf函数的执行确实需要极短的 CPU 时间但相对于整整1秒的睡眠期来说这个执行时间几乎可以忽略不计。当printf完成输出后进程立即进入睡眠状态在接下来的1秒内都处于这种等待状态。操作系统的进程调度器在扫描进程状态时几乎总是捕捉到进程在睡眠而不是在运行。更重要的是sleep()函数在底层是通过设置定时器并调用等待函数来实现的。进程会挂起自己直到定时器超时才会被重新唤醒。代码语言javascriptAI代码解释#include stdio.h int main() { int a 0; scanf(%d, a); return 0; }我们的程序需要从键盘上读取数据不断等待外设键盘准备好如果我们不去敲击键盘输入数据那么键盘就会一直不准备好那么此时进程就会在键盘设备的阻塞队列中等待此时就对应操作系统学科的阻塞状态所以此时我们的进程处于休眠状态而处于浅度睡眠状态的进程是可以被杀掉的我们可以使用kill命令将该进程杀掉。在这里插入图片描述3. Ddisk sleep— 磁盘休眠状态D磁盘休眠状态disk sleep也叫做深度睡眠处于这个状态的进程通常会等待IO的结束不响应任何请求同时其也对应操作系统学科上的阻塞状态的一种特殊情况处于深度睡眠的进程不响应操作系统的任何请求也无法被 kill -9杀死 您描述的场景很好地解释了深度睡眠Uninterruptible Sleep的设计原理和必要性。在这个假设的磁盘写入困境中确实展现了一个典型的数据完整性保护机制。当进程向磁盘发起写入请求后便进入等待回应的状态。此时如果操作系统因内存资源严重不足而决定终止该进程就会引发数据一致性问题。进程被强制杀死后其内存中的代码和数据随之释放而磁盘那边由于空间不足也丢弃了待写入的数据这就造成了不可挽回的数据丢失。在这种复杂情境下操作系统为了维护系统稳定性而清理资源磁盘为了最大化利用有限空间而优先处理可完成的任务各自的行为从局部视角看都有其合理性但整体协作却导致了用户数据的损失。为了解决这种责任边界模糊但后果严重的数据丢失风险系统引入了深度睡眠状态。处于深度睡眠的进程对操作系统的终止信号不予响应就像进入了一种受保护的休眠。只有当磁盘完成操作并返回明确结果后进程才会苏醒。这种机制确保了数据操作的原子性——要么完整写入要么完全失败但绝不会停留在悬而未决的中间状态。4. Tstopped— 停止状态在Linux当中我们可以通过发送SIGSTOP信号使进程进入暂停状态stopped发送SIGCONT信号可以让处于暂停状态的进程继续运行。代码语言javascriptAI代码解释#include stdio.h #include sys/types.h #include unistd.h int main() { while(1) { printf(i am proc,my PID:%d,my PPID:%d\n,getpid(),getppid()); sleep(6); } return 0; }暂停kill -SIGSTOP PID或者kill -19 PID在这里插入图片描述继续kill -SIGCONT PID或者kill -18 PID在这里插入图片描述6. Xdead— 死亡状态注意这个状态只是一个返回状态并不会在任务列表中看到这个状态当Z僵尸状态结束后就会进入Z死亡状态二、僵尸状态进程1. 定义僵尸进程zombies子进程退出的时候如果父进程没有主动读取回收子进程的信息那么子进程会让自己一直处于Z僵尸状态即对应子进程相关资源尤其是task_struct结构体不能释放。exit系统调用接口可以终止一个进程使用exit可以保证我们的子进程或父进程被终止在这里插入图片描述代码语言javascriptAI代码解释#include stdio.h #include stdlib.h #include unistd.h int main() { printf(I am running...\n); pid_t id fork(); if(id 0){ //child int count 5; while(count){ printf(I am child...PID:%d, PPID:%d, count:%d\n, getpid(), getppid(), count); sleep(1); count--; } printf(child quit...\n); exit(1); } else if(id 0){ //father while(1){ printf(I am father...PID:%d, PPID:%d\n, getpid(), getppid()); sleep(1); } } else{ //fork error } return 0; }在这里插入图片描述程序开始运行时初始进程处于睡眠状态这是因为它等待外设屏幕输出主动让出CPU。当fork系统调用执行后创建出的子进程与父进程各自进入独立的执行流两者都因包含sleep调用而周期性处于睡眠状态。子进程在完成五次输出后通过exit正常退出此时进程的执行已经结束但由于父进程正忙于自己的循环输出而没有调用wait系列函数来回收子进程的退出状态子进程便进入了僵尸状态。僵尸状态的特点是进程的执行已终止核心资源已被释放但在进程表中仍保留着退出状态信息等待父进程读取这时使用ps命令便能观察到标记为Z的子进程。2. 危害僵尸进程的长期存在确实会带来显著的系统资源浪费问题。当一个子进程结束后其退出状态信息必须被保留在进程控制块PCB中这是为了向父进程汇报任务执行的结果。这些状态数据并非凭空存在它们需要占用实实在在的内存空间来存储。如果父进程始终不去读取子进程的退出状态那么子进程就会一直保持僵尸状态。这意味着操作系统必须持续维护这些已经终止进程的PCB结构而每个PCB都包含着进程的详细信息如同C语言中每个结构体变量都需要在内存中分配特定空间一样。设想一个父进程频繁创建大量子进程却从不进行回收的情况这会导致系统中积累越来越多的僵尸进程。每个僵尸进程的PCB都无法被释放它们占据的内存空间也就无法被重新利用。这种场景本质上就是一种内存泄漏——系统的宝贵内存资源被这些已经死亡但未被清理的进程残留信息所占据可用的内存空间逐渐减少最终可能影响系统的整体性能和稳定性。因此及时回收子进程不仅是良好的编程习惯更是保证系统健康运行的重要措施。三、孤儿进程在Linux当中的进程关系大多数是父子关系若子进程先退出而父进程没有对子进程的退出信息进行读取那么我们称该进程为僵尸进程。 但若是父进程先退出那么将来子进程进入僵尸状态时就没有父进程对其进行处理此时该子进程就称之为孤儿进程。若是一直不处理孤儿进程的退出信息那么孤儿进程就会一直占用资源此时就会造成内存泄漏。因此当出现孤儿进程的时候孤儿进程会被1号init进程领养此后当孤儿进程进入僵尸状态时就由int进程进行处理回收。代码语言javascriptAI代码解释#include stdio.h #include stdlib.h #include unistd.h int main() { printf(I am running...\n); pid_t id fork(); if(id 0){ //child int count 5; while(1){ printf(I am child...PID:%d, PPID:%d\n, getpid(), getppid(), count); sleep(1); } } else if(id 0){ //father int count 5; while(count){ printf(I am father...PID:%d, PPID:%d, count:%d\n, getpid(), getppid(), count); sleep(1); count--; } printf(father quit...\n); exit(0); } else{ //fork error } return 0; }在这里插入图片描述在这里插入图片描述当执行程序后打印字符串然后进程调用了fork会创建子进程当前进程成为父进程子进程和父进程通过if分流进入不同的执行流去执行不同的代码块使用ps查看此时子进程和父进程都是处于S睡眠状态前台进程子进程会一直死循环间隔睡眠1s打印进程的PID和PPID父进程会打印5次进程的PID和PPID后被exit系统调用终止进程那么此时子进程的父进程的进程信息被bash回收释放了所以子进程就没有父进程回收进程信息了。操作系统肯定不能让这种事情发生因为任何一个进程都要退出也要被释放那么此时操作系统收养了当前的子进程。注意此时的子进程被操作系统收养在后台运行并且写的是死循环打印那么子进程不会主动退出那么只能使用kill -9 进程标识符杀死子进程进行退出。这里的bash进程算的上是当前子进程的爷爷进程可是任何一个父进程只对子进程负责即bash使用fork创建出来的当前子进程的父进程 bash只对当前子进程的父进程负责bash的代码逻辑中没有要为当前子进程负责的逻辑所以bash不能回收当前的子进程。