目录
Linux中的进程状态
R运行状态&S休眠状态
T/t停止状态stopped(tracing stop)
D磁盘休眠状态Disk sleep
X死亡状态dead&Z僵尸状态zombie
僵尸进程
僵尸进程的理解
演示僵尸进程
僵尸进程的危害
孤儿进程
孤儿进程的理解
演示孤儿进程
进程状态的查看
本篇主要介绍task_struct中进程的状态,具体介绍Linux中进程状态,一个状态一个状态细谈。
Linux中的进程状态
- 为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。
- 每个进程都有自己的进程状态
- 进程状态在task_struct内部的一个属性 int status
- OS更改一个进程状态,只是更改进程的task_struct 的状态属性,只是一个标志位用来表明进程的状态,经此而已。
Linux操作系统本身的对状态的定义有以下:
- R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S睡眠状态(sleeping) 意味着进程在等待事件完成(这里的睡眠有时候叫做可中断睡眠(interruptible sleep))。
- D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
- X死亡状态(dead):这个状态只是个返回状态,你不会在任务列表里看到这个状态。
下面的状态在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运行状态&S休眠状态
R运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S睡眠状态(sleeping) 意味着进程在等待事件完成(这里的睡眠有时候叫做可中断睡眠(interruptible sleep))。
运行下列程序,我们发现,为什么printf打印显示的时候存在休眠状态,既有S+也有R+这个状态,这是怎么一回事呢❓
我们把下面需要显示器打印的代码全部注释掉,只保留循环,全是S+,这又是为什么呢❓
//既有S+/R+
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\n",getpid());
10 }
11 return 0;
12 }
//只有R+
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\n",getpid());
10 }
11 return 0;
12 }
解释:
- printf的本质是向显示器终端打印。
- 根据冯诺依曼体系结构,打印的代码需要先写到内存再刷新到外设。
- 程序是从CPU写入内存,由内存刷新到外设,CPU的运算速度比外设快很多,外设设备是不能保证每次都准备就绪。
- 综上所述,进程就存在等待外设准备就绪,此刻进程的状态就是浅度睡眠的状态S。
- 如果外设设备准备好了,进程可以向显示器打印数据,此刻进程的状态就是运行R。
- 所以,当打印代码被注销,不需要访问外设设备,此刻进程的状态一直都是R。
进程被调度的时候需要访问显示器资源,进程的信息往显示器上打印的时间 相较于 CPU运行慢很多,所以进程大部分时间都在等待外设资源准备就绪,此时进程的状态就是S。只有真正执行打印打代码的时候,进程的状态此时R。
- S在前台,S+在后台跑(前台/后台,后面谈)
- S状态,休眠状态,处于进程等待的状态。
- S是可中断睡眠:进程处于休眠状态,但是依旧可以随时被外部信息打断。
T/t停止状态stopped(tracing stop)
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
T/t:让进程暂停,等待进一步被唤醒。
关于进程中的信号(先应用,后面理解)
- 用一个kill进程去控制目标进程的是否暂停状态
- 查看信息kill -l
- kill命令可以向指定进程发信号
- kill -9 进程pid杀死进程
- kill -19 pid暂停进程
- kill -18 pid唤醒进程
- 注意:暂停一个进程并且再次唤醒它,它自动被切换到后台。
- Linux中Ctrl+c是中断进程,kill是终止进程。(ctrl+C和kill❓❓)
- 进程是T/T+表示纯粹暂停状态
- gdb调试代码打断点的时候t/t+表示进程被追踪,进程暂停的状态
- 两种进程的相互切换时因为遇到了断点,进程就被暂停了
【kill -9】
【kill -19 & kill -18】
【调试gdb】
D磁盘休眠状态Disk sleep
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
- LinuxOS存在一种特有的权力:当内存严重不足的时候,杀掉进程来释放空间
- 以下情况的发生根本原因是:OS杀死进程是毫无类型的杀死,哪怕是正在写入关键数据的进程。正在写入关键数据的进程是不能被杀死的。
- 防止下面情况:凡是进程都需要数据IO
- 若进程在等待【磁盘】资源,需要将自己的状态设置为D状态。(不可中断睡眠)
- D状态时系统里特有的一种进程状态。是OS对磁盘的单独状态。
- D状态是不可被杀,不可中断,深度睡眠。
- D状态的存在的目的:保证进程IO的时候,等待磁盘资源时不能被杀。
- S状态时浅度睡眠。
- 消除D状态有2中方法
- 进程自己醒来(磁盘写入完成)
- 重启断电
- 数据IO&进程IO(后面讲)
X死亡状态dead&Z僵尸状态zombie
X死亡状态(dead):这个状态只是个返回状态,你不会在任务列表里看到这个状态。
Z僵尸状态(zombie ):和下面的僵尸进程有关。OS很难查到所以就不做演示了。
👇👇和僵尸进程一起理解
僵尸进程
- 僵死状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程(使用wait()系统调用,后面讲)没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
- 僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。
- 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态
- ptrace系统调用追踪进程运行Ptrace 详解 - tangr206 - 博客园 (cnblogs.com)
僵尸进程的理解
- 子进程退出不是默默无闻退出的,而是将自己的退出信息保存在自己的PCB中。
- 进程退出之后,进程的代码和数据会被释放,但是进程的PCB一直不会释放。
- 若有人读取了进程的退出代码(有人对进程进行了"等待",后面谈),则进程的PCB才会被释放。由Z☞X
- 若一直进程的PCB的退出信息没有被读取则一直不会被释放,一直处于"僵尸状态"Z
- 父进程读取/等待子进程PCB的退出信息
- 释放PCB还是由OS来实现(一瞬间的事情)
所以综上所述:进程退出时不会立即全部被释放(数据/代码会 PCB内核数据结构不会)处于Z僵尸状态。子进程已经运行完毕,但仍需要维持自己的退出信息,在自己的进程的task_struct会记录自己的退出信息,未来让父进程去读取/等待。(❓怎么读取 等待接口 后面讲)
演示僵尸进程
- 父进程持续循环
- 子进程循环几次结束
- 此时子进程就是僵尸进程,处于僵尸状态Z
12151 12152 12151 13529 pts/1 12151 Z+ 1000 0:00 [testStatus] <defunct>
//僵尸进程
1 #include<stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 pid_t id=fork();//创建父子进程
8 if(id == 0)
9 {
10 //child
11 int cnt=7;
12 while(cnt--)
13 {
14 printf("I am child process,cnt:%d,pid:%d\n",cnt,getpid());
15 sleep(1);
16 }
17 }
18 else//id>0
19 {
20 //parent
21 while(1)
22 {
23 printf("I am parent process,pid:%d\n",getpid());
24 sleep(1);
}
}
27 return 0;
28 }
僵尸进程的危害
- 进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态?是的!
- 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存task_struct(PCB)中,换句话说,Z状态一直不退出,PCB一直都要维护?是的!
- 一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费?是的!
- 因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!内存泄漏?是的!
- 如何避免?后面讲
孤儿进程
- 父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
- 父进程先退出,子进程就称之为“孤儿进程”
- 孤儿进程被1号init进程领养,当然要有init进程回收喽。
孤儿进程的理解
演示孤儿进程
进程状态的查看
ps aux / ps axj 命令
🙂感谢大家的阅读,若有错误和不足,欢迎指正。