一、进程状态
进程有六种状态:
R运行 S睡眠 D深度睡眠 T暂停 Z僵尸 X死亡
R:
表示当前进程可以被调度,但是调还是没调不知道。操作系统会将所有R状态的进程的task_struct以链表的形式链接起来,在调度时先来先调度。
S:
该进程挂起或阻塞,一般为等待某件事发生,可以随时被唤醒调用,也可以随时被杀死。
D:
该状态的创建是为了避免当内存向磁盘写数据的时候被杀掉而导致数据输入失败,操作系统不能直接杀死该进程,该状态通常会等待IO结束。
T:
kill -SIGSTOP pid可以将某个进程变为暂停状态,杀死进程后还可以看到它,直到将其唤醒才能真正地删除它。
Z:
当进程要退出的时候,在系统层面,曾经申请的资源并不是被立即释放,而是暂存一段时间,供父进程进行读取,此时该进程处于僵尸状态。
X:
与僵尸状态有联系,当操作系统或父进程读取了子进程的数据,子进程就从僵尸状态变为死亡状态被释放掉。
时间片:
操作系统分配给每个正在运行的进程微观上的一段CPU时间。同一个CPU不可能真正同时运行多个任务,进程看起来像是同时运行,实则是轮流穿插运行。时间片由操作系统调度程序分配给每个进程。首先,内核会给每个进程分配相等的初始时间片,然后每个进程轮番指向相应的时间,当所有进程处于时间片耗尽状态时,内核会重新为每个进程计算并分配时间片。
二、僵尸进程
fork函数创建子进程但子进程比父进程先退出时,子进程就是僵尸进程。若父进程先退出,子进程自动被 init 进程(编号为1)收养,init 进程会自动调用 wait() 处理,不会产生僵尸进程。
父进程 wait() 处理(即父进程调用wait/waitpid方法来处理),则僵尸进程会被父进程清理,但父进程会阻塞。
如果父进程不用 wait() 处理,则僵尸进程会在父进程退出之前一直存在。对于处理网络请求的服务器进程来说,父进程可能会一直存在,子进程处理完任务就退出,这种情况下会产生很多僵尸进程,这种场景就需要对僵尸进程的处理提高警惕了。
避免僵尸进程的方法:
1、fork twice
子进程再创建一个子进程实现业务逻辑,并让自己尽快退出,将二级子进程交给init进程处理。
2、signal(SIGCHLD, SIG_IGN)
并不是所有系统都兼容。通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收,即可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源
或者:
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sa.sa_flags = SA_NOCLDWAIT;
sigemptyset(&sa.sa_mask);
sigaction(SIGCHLD, &sa, NULL);
3、在signal handler函数中调用 waitpid
建立信号处理程序并从该处理程序调用wait不足以防止僵尸。 问题是如果在执行信号处理程序之前生成五个信号,则信号处理程序只执行一次,因为Unix信号通常不排队(这种情况下只会产生一次“SIGCHLD”信号)。在这种情况下,信号处理程序执行一次,留下四个僵尸。故正确的解决方案是调用waitpid而不是wait,并且我们必须指定WOHOANGoption:这告诉waitpid不要阻止是否有正在运行的子节点还没有终止。
void handle_sigchld(int sig)
{
while(waitpid(-1,NULL,WNOHANG) > 0)
;
//pid = -1 等待任何子进程,相当于 wait()
//WNOHANG 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若结束,则返回该子进程的ID
}
int main(void)
{
signal(SIGCHLD,handle_sigchld)
//使用waitpid函数实现,主动处理fork出来的会变成僵尸进程的方法
......
}