Linux系统进程状态、僵尸进程及孤儿进程讲解
一、Linux进程状态
在我们的操作系统中,可以同时运行多个程序,而程序在内存中则是一个个的进程,在windows下我们打开任务管理器就可查看对应进程的状态。Linux下可以通过ps命令查看,Linux上进程主要有以下几种状态:
<1>运行状态 R(TASK_RUNNING)
当进程正在被CPU执行,或已经准备就绪随时可被调度执行,则称该进程为处于运行状态(running)。所谓就绪状态就是该进程已经具有执行所需的条件,随时可被调度执行,但是还在就绪队列中尚未被调度。
<2>可中断睡眠状态 S(TASK_INTERRUPTIBLE)
由于cpu同时可执行的进程有限,所以我们查看进程状态时会有大多数的进程都处于此状态。当进程处于可中断睡眠状态时,系统不会调度该进程执行。只有当系统产生一个中断或者释放了该进程正在等待的资源,或者该进程收到一个信号,就可以唤醒该进程并将状态转换到运行状态(R)。
<3>不可中断睡眠状态 D(TASK_UNINTERRUPTIBLE)
与可中断睡眠状态类似,但处于该状态的进程不响应异步信号,此状态存在的意义就在于,内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入的流程可能只存在于内核态,也可能延伸到用户态),于是原有的流程就被中断了。
<4>暂停状态 T(TASK_STOPPED)
当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。
<5>僵死状态 Z(TASK_ZOMBIE)
当子进程已停止运行,但其父进程还在运行并且没有询问其状态时,则称该子进程处于僵死状态。僵死进程会以终止状态保持在进程表中,直到等待父进程读取退出状态码。
<6>退出状态 X ( TASK_DEAD - EXIT_DEAD)
进程将被置于EXIT_DEAD退出状态,这意味着接下来的代码立即就会将该进程彻底释放。该状态是非常短暂的,几乎不可能通过ps命令捕捉到。
补充:STAT状态位常见的状态字符
D 无法中断的休眠状态(通常 IO 的进程);
R 正在运行可中在队列中可过行的;
S 处于休眠状态;
T 停止或被追踪;
W 进入内存交换 (从内核2.6开始无效);
X 死掉的进程 (基本很少見);
Z 僵尸进程;
< 优先级高的进程
N 优先级较低的进程
L 有些页被锁进内存;
s 进程的领导者(在它之下有子进程);
l 多进程的(使用 CLONE_THREAD, 类似 NPTL pthreads);
+ 位于后台的进程组;
使用ps a可查看当前所有进程:
二、僵尸进程
<1>先来构造一个僵尸进程的例子:我们在前面已经了解到一个子进程在其父进程没有调用wait()或waitpid()的情况下退出,如果其父进程还存在而一直不调用wait(),则该僵尸进程将无法回收,等到其父进程退出后该进程将被init回收。在这个过程中子进程已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,这个时候该子进程称为一个僵尸进程。这个子进程就是僵尸进程。例:
1 #include 2 #include 3 #include 4 5 6 int main() 7 { 8 pid_t id = fork(); 9 if(id < 0){ 10 perror("fork"); 11 return 1; 12 }else if(id > 0){ 13 printf("parent[%d] is sleeping\n",getpid()); 14 sleep(30); 15 }else{ 16 printf("child[%d] is begin\n",getpid()); 17 sleep(5); 18 exit(1); 19 } 20 21 return 0; 22 }
我们在shell下同时开启两个终端,一个下运行以上程序,另外一个在执行过程中使用grep抓取stat状态为zZ进程,通过结果的显示我们可以看出来在该进程执行时父进程状态为Z,成为了僵尸进程
ps -A -o stat,ppid,pid,cmd | grep -e '^[Zz]'
<2>结果分别如下:
我们通过fork()创建一个子进程,父进程执行30s,子进程执行5s后就退出,并且父进程没有使用wait或waitpid的方式来获取子进程的退出信息,此时子进程就会出现僵尸进程的现象。
<3>避免僵尸进程的方法:
⒈父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起,相关用法可以通过man命令查看。
⒉ 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后, 父进程会收到该信号,可以在handler中调用wait回收。
⒊ 如果父进程不关心子进程什么时候结束,那么可以用signal(SIGCHLD,SIG_IGN) 通知内核,自己对子进程的结束不感兴趣,那么子进程结束后,内核会回收, 并不再给父进程发送信号。
⒋ 还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收 还要自己做。
<4>僵尸进程的危害:
假设这样一个情景,我们有一个父进程在不断的创建子进程,每个子进程的存活时间都很短,父进程对子进程的终止状态都不管不顾,任由发展下去,子子孙孙,系统中就会存在许多的僵尸进程,更重要的是每一个僵尸进程都还没占着对应的进程列表,进程列表可是临界资源是有限的,时间一长内存中就没有多余的地方再让我们创建进程了。
三、孤儿进程
<1>同样通过构造一个例子来理解什么叫孤儿进程,此场景就和名字一样通俗易懂,所谓的“孤儿”,就是失去了“父亲”,也就是说在子进程还在执行时,父进程就已经退出了。看例子:
#include #include int main() { pid_t id = fork(); if(id < 0){ perror("for"); return 1; }else if(id == 0){ printf("start:im child[%d],parent is [%d]\n",getpid(),getppid()); sleep(10); printf("finish:im child[%d],parent is [%d]\n",getpid(),getppid()); }else{ printf("im father[%d]\n",getpid()); sleep(2); } return 0; }
<2>运行结果及分析:
我们可以看出本来子进程的父进程Pid为2420,但是我们让父进程sleep两秒后就结束了,此时子进程依然还在运行,等子进程sleep完成后,此时父进程已经不在了,新的父亲的pid为1,也就是系统中的init进程,也可以说该子进程被init进程所接管。