进程状态
- 1. 进程排队
- 2. 进程各个状态
- 3. 查看一个进程的状态
- 4. linux内核描述进程状态
- 5. 孤儿进程
1. 进程排队
进程为什么要排队呢?答案就是资源不够。需要等待某个软硬件资源,就像我们常用的scanf函数就是等待键盘资源。
之前的文章我们有个结论,进程 = PCB + 可执行程序,那么进程排队是谁在排队呢?答案是PCB在排队,就像我们找工作,是简历排队。排队就是链入到一个数据结构中,且PCB可以链入到多种数据结构,linux内核的PCB——task_struct是以双向链表的形式链接起来的。但和一般的双向链表的实现不一样,指向下一个task_struct的指针指的不是起始地址。
获取起始地址的运算:(task_struct*)(&n - ((task_struct*)0 ->n)
那如何理解可以链入到多个数据结构中呢?答案就是一个task_struct中有多个struct listnode节点。
2. 进程各个状态
看起来很复杂,但是在task_struct中,状态就是一个整型变量
那分成这么多状态有什么用呢?因为状态决定了后续动作。
- 运行状态
明确一个概念,一个CPU有一个运行队列,当task_struct被链入到这个运行队列中,就处于运行状态(R),表明随时准备好被调度。
- 堵塞状态
一个现实:当你写了一个死循环程序运行,其他的程序依然不受影响的在运行,这就表明了它被从运行队列中移除了。
现在有一个场景,你写的程序中有一个scanf函数,我们知道是在等待键盘资源,这时候该程序的状态会发生什么呢?
这时候该程序会从运行队列中移除,将自己的状态改为堵塞,链接到键盘的等待队列中,如果等待成功,就会再将自己从等待队列中移除,链接到运行队列中,将自己的状态改为运行。
所以堵塞状态就是将task_struct从运行队列中移除再链入到等待队列中。
- 挂起状态
当内存非常吃紧的时候,操作系统会将一些进程的代码和数据放入到磁盘的一个特定的区域,当缓和时,再将代码和数据放入内存。但注意task_struct不会放入磁盘。这种状态就称为挂起状态。
3. 查看一个进程的状态
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 while(1)
8 {
9 printf("I am a process,pid: %d,ppid: %d\n",getpid(),getppid());
10 //sleep(1);
11 }
12 return 0;
13 }
ps ajx | head -1 && ps ajx | grep myprocess | grep -v grep
这里可能会有疑惑,为什么该段代码一直在死循环的运行,但状态还是Sleep呢?关键在于printf这个函数大部分时间还是处于一个等待硬件资源的过程中,所以很难查到运行状态,你以为printf执行的很快,但不要以自己的想法衡量CPU的速度。
为了验证这个我们将代码中这一句注释掉
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 while(1)
8 {
9 //printf("I am a process,pid: %d,ppid: %d\n",getpid(),getppid());
10 //sleep(1);
11 }
12 return 0;
13 }
果然变成的运行状态,后面的加号是什么意思呢?有加号表示这个进程是前台进程,没有加号表示是后台进程,我们在运行一个程序的时候再后面输入一个&。该程序就会以后台程序运行,只能通过kill命令终止该进程。
4. linux内核描述进程状态
/*
* 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): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠
(interruptible sleep))。 - D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态.
S、D、T、t都可以理解成堵塞状态,它们都在等待资源。
- Z僵尸状态(zombie)
当一个进程执行完成之后,该进程的代码和数据可以释放了,但是它返回的信息不能释放(存放在struct_task),因为需要被父进程获取。僵尸状态就是进程执行完毕但是没有获取该进程的返回信息。
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5
6 int main()
7 {
8 pid_t id = fork();
9 if(id == 0)
10 {
11 printf("I am child, pid : %d, ppid : %d\n",getpid(),getppid());
12 sleep(5);
13 exit(0);
14 }
15 else
16 {
17 printf("I am father, pid : %d, ppid : %d\n",getpid(),getppid());
18 sleep(10);
19 }
20 return 0;
21 }
这会造成什么问题呢?很简单,就是内存泄漏,解决办法后面会介绍。
5. 孤儿进程
父进程比子进程先退出,那么子进程就会变成孤儿进程,并且同时变成后台程序。
会造成什么影响吗?答案是不会的因为孤儿进程会被操作系统所领养。