前言
如果大家学习过操作系统,那么对于以上这张图会比较熟悉,但是不同的操作系统实际上对于进程状态的定义也是不同的。对于Linux操作系统我们可以大致分为三个状态:运行状态、阻塞状态、挂起状态。
通俗的理解,进程状态就是PCB中的一个字段,就是PCB中的一个变量,int status,我们通过改变PCB中的status变量,就相当于改变了对应的状态。后续再将PCB链入不同的队列中就相当于进程状态的变化。
请记住:进程状态变化的本质就是1、改变PCB中的status值。2、将PCB链入不同的队列中。
什么是运行状态?
先了解一个概念—每一个CPU在系统层面都会维护一个运行队列,而只要在运行队列中的进程,状态都是运行状态。运行队列中包涵着众多的属性,其中主要管理进程的实际上就是一个PCB类型的结构体链表,通过链接每一个进程的PCB以此来确定进程为运行状态。运行状态实际上就对应以及包含了传统进程状态中的创建状态、就绪状态、执行状态。
什么是阻塞状态?
操作系统管理着诸多的资源,我们一定会或多或少的访问系统中的某些资源,这些资源包括系统软件、驱动程序、硬件等等。我们可以通过先描述,在组织,来构建对应设备的结构体。接着通过在系统中用一个设备列表来将这些设备都链接起来进行管理,当然这些结构体中肯定是包含对应的状态、设备描述符、属性以及最重要的进程等待队列等等。
阻塞实际上就是进程等待某种资源就绪的过程,在这里我们就是将PCB链入等待队列中,当某个资源被占用,其它进程也要使用,那么其它进程需要链入对应资源的等待队列中,并且将对应的字段改为阻塞状态,以此等待资源。在等待到资源后,就可以将对应进程的字段改回运行状态,然后链回到运行状态的队列中。这样看来,不同的资源是不是都有不同的等待队列呢?是的,也就是说操作系统中会有很多的队列,运行队列、等待硬件的设备的等待队列等。
什么是挂起状态?
————————————————
磁盘当中存在着一个分区-swap分区(大概为内存的大小或者两倍),OS中的数据会交换到这里。当内存足够时且进程被OS调度时,被置换出去的内存和数据将会从这里被重新加载进来。如下图:
Linux中对于进程状态的详细理解
进程状态源码
static const char *task_state_array[] = { "R (running)", /* 0 */ "S (sleeping)", /* 1 */ "D (disk sleep)", /* 2 */ "T (stopped)", /* 4 */ "T (tracing stop)", /* 8 */ "Z (zombie)", /* 16 */ "X (dead)" /* 32 */ };
R运行状态(running) : 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
T停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
前台进程与后台进程:
(./可执行程序) 默认为前台进程,通常表现为状态+ 此时可以通过Ctrl+c强行终止
(./可执行程序 &)为后台进程,通常表现为状态 此时不能通过Ctrl+c强行终止,需用 kill -9 杀死进程
查询进程状态
while :; do ps ajx | head -1 && ps ajx | grep 执行程序名 | grep -v "grep"; sleep 1; echo "*******************************"; done
S睡眠状态(sleeping)
实际上也就是上面提到的阻塞状态,当然根据以上等待资源的概念,那么我们就下来的D、T、t也属于阻塞状态!在Linux叫做休眠状态,也被叫做浅度睡眠,它可以被终止。为什么叫浅度睡眠呢?因为浅度会对外部的信号做出响应。比如可以被Ctrl+c强行终止。
D磁盘休眠状态(Disk sleep)
也叫休眠状态,只不过也叫做深度睡眠。它是专门针对磁盘来设计的。在了解D状态前我们先了解一个概念—Linux在实在没办法的时候会通过杀掉进程来节省资源。那么,假设现在OS就处于这个情况,而此时有个进程正在向磁盘写入数据,如果此时如果刚刚好这个进程被OS杀掉了,那这个时候就出现问题了。因此,D状态就是针对这种情况,这是为了防止当进程向磁盘写入关键数据时而被杀掉,对于D状态的进程是无法被杀掉的。说一个题外话,如果我们查看到了D状态说明计算机离宕机不远了(#^.^#)
T停止状态(stopped)
在了解这个状态前我们先理解一下信号的概念,输入以下命令可查看信号:
kill -l
怎么理解这些信号呢?我们进程用的杀死进程的命令,kill -9 就对应了其中一个信号,对这些信号量的理解,可以理解成用宏定义的数字。对于命令可以应用对应的信号名,也可以用对应的数字。
我们要看到停止状态,可以用kill -SIGSTOP pid 或者kill -19 pid 来停止对应的进程。如下图:
那么我们为什么要暂停进程呢?因为在进程访问软件资源的时候,可能暂时不让进程进行访问,就将进程设置为stop。
t追踪停止状态(tracing stop)
debug程序的时候,追踪程序,遇到断点,进程就暂停了!此时,会显示出t状态。如下:
X死亡状态(dead)
就是字面所描述,进程死亡。这个状态也很难见到,因为进程死亡了,进程会释放资源,但是难以查看到。
Z僵尸状态(zombie)
什么是僵尸状态?
我们都知道进程=内核PCB+进程代码和数据。进程退出的核心工作之一就是将PCB和自己的代码和数据释放掉。进程在退出的时候要有一些退出信息,以表明自己把任务完成的怎么样。因此,当一个进程退出的时候,退出信息会有OS写入到当前退出进程的PCB中,可以允许代码和数据空间被释放掉,但是不能允许进程的PCB被立即释放!这是为了要让OS或者父进程读取退出进程的PCB中的退出信息,得知子进程的退出原因!如果进程退出了,但是还没被父进程或者OS读取,OS必须维护这个进程的PCB结构!
此时,这个进程状态被称为Z僵尸状态(zombie)。父进程或者OS读取后,PCB状态才会被改成X状态,才会释放。如果一个进程Z状态了,但是父进程就是不回收他,PCB会一直存在,如果我们不及时回收,会造成内存泄漏!
僵尸状态(Zombies)是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程僵死进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态 。
僵尸状态的例子
以下为一个维持30秒的僵尸状态例子:
#include <stdio.h> #include <stdlib.h> #include<sys/types.h> #include<unistd.h> int main() { pid_t id = fork(); if(id < 0){ perror("fork"); return 1; } else if(id > 0){ //parent printf("parent[%d] is sleeping...\n", getpid()); sleep(30); }else{ printf("child[%d] is begin Z...\n", getpid()); sleep(5); exit(EXIT_SUCCESS); } return 0; }
孤儿进程
父进程如果提前退出,那么子进程后退出,进入Z之后,那该如何处理呢?
父进程先退出,子进程就称之为“孤儿进程”。
孤儿进程被1号init进程领养,当然要有init进程回收喽 。
以下为一个例子:
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> int main() { pid_t id = fork(); if(id < 0){ perror("fork"); return 1; } else if(id > 0){ //parent printf("i am parent[%d]\n", getpid()); sleep(3); exit(2); }else{ printf("i am child[%d] \n", getpid()); sleep(10); } return 0; }
感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o!