Linux进程编程实践——进程状态、模拟实现僵尸进程和孤儿进程
一、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): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态
- Z僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
二、僵尸进程
2.1 什么是僵尸进程?
如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,否则这个时候子进程就成为僵尸进程。之所以被称为僵死进程是因为他虽然死掉了,但依然在进程表中存在。
2.2 模拟实现僵尸进程
僵尸进程的原理是子进程先退出,而父进程没有捕获到子进程的退出状态,这是子进程就变成了一个僵尸进程,我们利用fork()来创建出一个子进程,并于10s后退出
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t ret = fork();
if(ret < 0)
{
printf("创建进程失败!\n");
}
else if(ret == 0)
{
int count = 0;
while(count++ < 5)
{
printf("我是子进程(id:%d)\n",getpid());
sleep(2);
}
exit(0);
}
else
{
while(1)
{
printf("我是父进程(id:%d)\n",getpid());
sleep(1);
}
}
sleep(1);
return 0;
}
上述代码编译后运行,可以看到如下效果
我们另开一个窗口并编写一个监测进程脚本来查看进程的状态
while :; do ps axj | head -1 && ps axj | grep 06-zombie| grep -v grep; sleep 1 ; echo "#############"; done
这样僵尸进程就被模拟出来了
Q1:为什么会有僵尸进程?
- 给进程设置僵尸状态的目的是维护子进程的信息,以便父进程在以后某个时间获取。
- 这些信息包括子进程的进程ID、终止状态以及资源利用信息(CPU时间,内存使用量等等)。
-
- 如果一个进程终止,而该进程有子进程处于僵尸状态,那么它的所有僵尸子进程的父进程ID将被重置为1(init进程)。继承这些子进程的init进程将清理它们(init进程将wait它们,从而去除僵尸状态)。
Q2:僵尸进程的危害有哪些?
- 僵尸进程存在的原因是为了维护子进程的信息,如果父进程不读取子进程的退出状态,那么子进程会一直维持僵尸状态!
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说, Z状态一直不退出, PCB一直都要维护
- 结构体本身占用内存,如果Z状态一直不退出,就会造成内存资源的浪费!
- 造成内存泄漏!
三、孤儿进程
3.1 什么是孤儿进程?
如果父进程先退出,子进程还没退出那么子进程的父进程将变为init进程。
注意:
- 任何一个进程都必须由父进程
- 如果父亲进程先结束,子进程会托孤给1号进程
3.2 模拟实现孤儿进程
僵尸进程的模拟实现是通过fork()创建子进程并先于父进程退出就可以实现,同理,孤儿进程只需要让父进程先于子进程退出即可,只需对代码逻辑稍加修改就能实现
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t ret = fork();
if(ret < 0)
{
printf("创建进程失败!\n");
}
else if(ret > 0)
{
int count = 0;
while(count++ < 5)
{
printf("我是父进程(id:%d)\n",getpid());
sleep(2);
}
exit(0);
}
else
{
while(1)
{
printf("我是子进程(id:%d)\n",getpid());
sleep(1);
}
}
sleep(1);
return 0;
}
编译运行上述代码:
我们另开一个窗口并编写一个监测进程脚本来查看进程的状态
while :; do ps axj | head -1 && ps axj | grep 07-orphan | grep -v grep; sleep 1 ; echo "#############"; done