
先打个预防针进程状态本质其实task_struct内部维护的一个int类型的整数只不过不同的整数表示不同的状态。1.操作系统的角度理解运行、阻塞、挂起状态1.1 运行状态我们已经知道PCB是由双向链表进行管理的但实际上它又属于调度队列同时属于两个数据结构这怎么理解呢我们先谈一谈内核中链表的实现。预备知识C语言中对任何类型开辟空间的时候变量的地址在数字上等于开辟空间中字节地址最小的那一串数字然后通过偏移量来确定数据。内核中链表的实现双向链表实际上是在task_struct内部维护的外部定义结点内部定义结点对象通过对象的前后指针指向串联起整个链表如图然后问题转化成了知道某个对象内部某个变量的地址怎么求对象的起始地址也就是该变量的偏移量解决了这个问题就解决了管理进程时的访问数据问题。我们先定义一个简单的结构体方便说明问题。struct obj { int a 0; int b 1; int c 2; itn d 3; } int main() { obj s; return 0; }解决办法偏移量 (long long)(((struct obj*)0) - b)起始地址 (long long)(s.b) - (long long)(((struct obj*)0) - b)解释 (strcut obj *)0假设起始地址为0的结构体;-b访问b这个变量再取它的地址强转为long long后的结果就是偏移量核心要理解的就是假设起始地址为0。问题来了为什么内核要这么做设计这么复杂答其一对内核对象进行管理更具通用性其二这样的设计保障了task_struct既可以属于双向链表又可以属于调度队列且有需求的话未来还可以属于任何数据结构什么是调度队列调度队列是操作系统内核中用于管理进程/线程执行顺序的数据结构简单来说就是操作系统的“待办清单”也就是待CPU处理的。说明只要进程在调度队列内部我们就认为是处于运行状态。1.2 阻塞状态阻塞状态简单来说就是进程等待的资源未就绪造成堵塞。比如当前运行的程序中有scanf它要访问操作系统然后访问外设从键盘读取数据此时是在调度队列中的处于运行状态但是如果迟迟没有等到键盘输入即资源始终不就位此时要出调度队列进入等待队列读取数据。等待队列此时需要与访问的外设进行链接等待资源就位如下图此时进程的管理依旧还是数据结构的增删擦查改无非就是数据结构在变化而已。所以判断当前是处于运行状态还是阻塞状态看当前所处的队列即可。1.3 挂起状态当内存资源严重不足时进程会被“请出”调度列表它的代码和数据会被换出swap out到磁盘上一个叫swap的分区并且寄存器中的值以及运行道哪一行代码也会被保留进这个swap分区此时的状态称作挂起状态当内存资源足够时再将这部分换入swap in内存那如果换出操作后内存还是严重不足呢此时系统就有可能杀进程了直接关闭程序/进程。进程运行期间挂起叫做运行挂起阻塞期间挂起叫做阻塞挂起。问题swap分区多大最为合适 一般认为是内存大小或者内存大小的一半最好因为swap分区足够大的话OS会依赖swap分区swap in 和swap out 操作频率会增加也就是IO操作增加而IO操作效率是很低的降低系统速度。所以挂起的本质就是用时间换取空间。阻塞状态和挂起状态的区分阻塞状态是进程仍在内存区域只不过是在等待某种资源而没有运行挂起状态的进程已经不在内存当中了。2. Linux下看待进程状态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 */ };2.1 RR运行状态程序不一定在运行也可能在调度队列里。还有一种情况就是当死循环打印时明明当前程序在一直运行但状态却显示的是S这是因为CPU太快了它可能已经完成了十万次输出但是外设显示屏只输出了几万次此时就是在等待外设完成输出的过程。如下图这里我们看到S的后面有一个这个 表示当前程序是在前台运行。理解一下前后台试想一下如果当前有很多程序正在执行且大部分都需要从键盘读取数据此时我们输入的数据究竟给那个进程呢显然我们只能给一个进程输入数据而能够接收这个数据的程序就叫做前台程序。所以前台程序能够从键盘当中读取数据的程序有且只有一个。后台程序也就是不能从键盘读取数据的可以存在多个。那为什么要有前后台呢那当然是为了提高效率比如下载和安装软件的时候你总不能傻傻的等着它下载完才能执行其他命令吧此时我们就可以把下载和安装放在后台运行如果有写入数据的话先向文件中写入即可。要将程序放在后台运行只需在命令的最后加一个‘’,如下前台运行时此时输入的命令被忽略bash被换到后台了不能从键盘读取数据但此时的ctrl c还是能终止程序的。后台运行时程序后台运行的时候命令可以被读取和执行但此时的ctrl c不再起作用。kill -9 PID #此时这个命令才能杀掉程序2.2 SS休眠状态这里的休眠状态也叫做可中断休眠状态意味着进程在等待事件完成例如scanf时等待从键盘获取资源或者等待从磁盘获取资源。2.3 TT需要获取资源如scanf的程序放后台此时这个程序的状态就是Tstopcode.c1 #includestdio.h 2 #includeunistd.h 3 4 int main() 5 { 6 int a; 7 while(1) 8 { 9 scanf(%d\n,a); 10 printf(我是一个进程我的pid是%d\n a :%d,getpid(),a); 11 sleep(1); 12 } 13 return 0; 14 }jobs #用于查看后台运行的程序 fg [序号] #将后台程序移至到前台kill -19 [PID] #暂停挂起一个进程 kill -18 [PID] #恢复至运行状态 这两一般搭配使用2. 4 tt进程被追踪时暂停。如gdb创建子进程进行调试打断点程序运行至断点处停下此时的状态就是t。2.5 DD磁盘休眠是一种深度睡眠不可中断睡眠OS也杀不掉只能等待进程自己醒来。这是一种保护措施。比如一个进程正在向磁盘写入1GB的数据而此时内存刚好又严重不足操作系统会开始杀程序但是不会杀该进程因为如果杀了的话这些1GB的数据怎么处理不好交代所以只能等待当前进程完成。2.6 Z和XZzombie进程退出但是PCB还需要维持一段时间直到父进程来读取这段时间就是Z僵尸进程状态Z是走向X的一个过度阶段。为什么要这么做呢因为进程是需要被管理的父进程通过getpid子进程的PID来进行管理如果一直不读取的话会造成内存泄漏这对一直运行而不退出的程序常驻程序影响会非常大。另一种情况父进程先结束的子进程被称作孤儿进程此时子进程会被PID为1就是bash的进程领养避免内存泄漏。X是一种瞬时状态被释放的一瞬间。完~