1. 进程的几种状态描述
进程是动态的活动的实体,指的是进程会有很多种运行状态,一会运行、一会睡眠、一会暂停、一会又继续执行,下图展示了Linux进程从创建到被回收的全部状态,以及这些状态发生转化的条件
- “蛋生”:从“蛋生”可以看到,一个进程的诞生是其父进程调用fork()开始的
- TASK_RUNNING:进程刚被创建出来时,处于TASK_RUNNING状态,从上图可以看到处于该状态的进程可以是正在进程队列中等待排队,也可以占用CPU正在运行,我们习惯上称前者为就绪态,后者为执行态。当进程状态为TASK_RUNNING且占用CPU运行(执行态)时才是真正的运行
- 就绪态:刚被创建的进程处于就绪态,等待系统调用,内核中的shed()称为调度器,它会根据各种参数选择一个处于就绪态的进程来执行,进程占用CPU之后就可以真正的运行了,但是运行的时间有个限制,比如20ms,这段时间称为时间片,时间片耗完之后,如果此时进程还没结束,那么该进程重新进入就绪态。另外,处于执行态的进程即使占用的时间片没有耗完,也可能被更高级别的进程抢占CPU(进程抢占),该进程被迫进入就绪态
- 睡眠态/挂起态:进程处于执行态时,可能会由于某些IO资源的不可获取而被置为**“睡眠态/挂起态”,比如进程要读取一个管道文件,而管道文件数据为空,或者进程要获取一个资源锁而不可得,或者进程自己调用睡眠函数sleep()来强制自己挂起,这些都会使得进程装填变为TASK_INTERRUPIBLE或TASK_UNINTERRUPIBLE,它们的区别是后者跟某些硬件设置相关,在睡眠期间不可以响应信号,因此TASK_UNINTERRUPIBLE被称为深度睡眠**,而TASK_UNINTERRUPIBLE是可以响应信号的。当进程所等待的资源变得可获取时,进程状态会被重置为TASK_RUNNING或者就绪态
- 暂停态:当进程收到SIGSTOP或者SIGTSTP中的一种信号时,状态会被置为TASK_STOPPED,此时称为“暂停态”,该状态下的进程不参与调度,但系统资源不释放,直到收到SIGCONT信号时,状态置为就绪态
- 僵尸态:进程死亡的状态有许多种,可以是寿终正寝,也可以是被异常杀死。比如图中,main函数调用return或者调用exit,包括在线程中调用pthread_exit等都是正常退出,而收到信号被异常杀死属于异常退出。不管进程怎么死亡,最终都会调用内核的do_exit()函数来使得进程变成僵尸态(EXIT_ZOMBIE,进程已经死亡,但是占用的资源没有释放),这里的“僵尸”指的是进程的PCB(进程控制块)。为什么进程都结束了,还留下僵尸呢?因此父进程可以从“尸体”中得到子进程的死亡信息(比如,退出码等)
- 死亡态:父进程调用wait()或者waitpid()来查看子进程的“死亡信息”,顺便将子进程的状态由僵尸态(EXIT_ZOMBIE)变为死亡态(EXIT_DEAD),处于死亡态的进程,PCB才能被系统回收
2. 进程状态的代码示例
以下示例代码,综合展示了如何正确使用fork()/exec()函数簇、exit()/_exit()和wait()/waitpid()。程序的功能是:父进程产生一个子进程,让子进程去执行程序child_elf,并且等待它的退出(可以用wait()阻塞等待,也可以用waitpid()非阻塞等待),子进程退出(可以正常退出,也可以异常退出)后,父进程获取子进程的退出状态后打印出来
子进程执行的程序child_elf.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char**argv){
printf("I am child_elf...\n");
#ifdef ABORT
abort(); //自己给自己发送一个致命信号SIGABRT
#else
exit(7); //正常退出,且退出值为7
#endif
}
父进程程序wait.c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <fcntl.h>
int main(int argc, char**argv){
pid_t pid = fork(); //创建子进程
if(0 == pid){ //子进程,执行程序child_elf
execl("./child_elf", "child_elf", NULL);
}
if(pid > 0){ //父进程,使用wait()阻塞等待子进程的退出
int status;
wait(&status);
if(WIFEXITED(status)){ //判断子进程是否正常退出
printf("Child exit normally, exit value is %d\n", WEXITSTATUS(status));
}
if(WIFSIGNALED(status)){ //判断子进程是否被信号杀死
printf("Child killed by signal, signal is %d\n", WTERMSIG(status));
}
}
return 0;
}
代码执行结果