【Linux】进程概念(下)
创始人
2025-05-31 07:38:57
0

文章目录

  • 📕 进程状态
    • 阻塞的概念
    • 挂起的概念
  • 📕 Linux 的进程状态
    • R 状态
    • S 状态
    • D 状态
    • T 状态
    • t 状态
    • Z 状态
  • 📕 孤儿进程

📕 进程状态

上一篇进程概念的文章,仔细讲解了进程的创建及其组成,进程 = 内核数据结构 + 代码和数据。 一个系统中往往由多个进程共同执行,而不是只执行一个进程。比如现在,我既打开网页,又打开qq,又打开网易云音乐、又打开PDF阅读器,后台还有一些我看不见的进程,这些进程在操作系统内被CPU调度的时候,并不是一直运行,而是一个进程被CPU执行一段时间,然后让另一个进程被CPU执行一段时间,这样不断切换,每一个进程在固定时间段内都有所推进。
但是为什么我们感觉上是进程一直在执行呢?是因为CPU运行的速率和我们人的感官能力差别太大,从我们的角度看来,所有的进程是同时在运行的。

既然进程是可以被CPU调度的,那么 CPU 调度进程的依据是什么呢?其实这取决于进程的状态。

阻塞的概念

例如,阻塞 就是进程因为等待某种条件就绪,而导致的一种不推进的状态——即进程卡住了。阻塞一定是在等待某种资源,当具体的资源被别人使用完之后,再被自己使用,这就是进程阻塞的原因。

举一个现实生活中的例子,取银行存钱,窗口工作人员告诉我必须要先要填一张表,于是我去先把表填了,而工作人员就先处理其他人的业务。这里的工作人员就相当于CPU,办理业务的人就相当于一个进程(“我”也是一个进程),进程要完成的任务就是存钱,其代码也是存钱的逻辑,而 这张表 就是存钱进程所必须的资源。 在这张表就绪之前,“我” 所处的状态就是阻塞状态,被CPU调度了也没用 。
但是,在现实中等待就是找个位置坐下来,可是对于操作系统,如何去理解进程等待资源就绪呢?诸如磁盘、网卡、显卡等等设备,其实也是被操作系统“管理”的,也是先描述、再组织。

如下图所示,设备被操作系统“先描述,再组织”,一个个描述设备的 pcb 结构体被连起来成了链表。左边的 struct dev 代表的就是描述设备的结构体,里面有一个指针 queue ,指向该设备的等待队列。例如,现在某个进程正在使用网卡这个设备,但是进程A也要使用,设备又只有一个,所以网卡的pcb 里面的 queue 指针就会指向进程A 的pcb,这就代表着 A 在等待使用网卡,A处于阻塞状态。(如果有多个进程请求使用网卡,就按照顺序链接在该队列的尾部)

所以,将进程的 pcb 链接在某个设备的等待队列的尾部,就说明该进程在等待某种资源!既然该进程的 pcb 被链入到了某个资源的等待队列,那么就无法调度它,该进程就属于阻塞状态!!!
请添加图片描述

挂起的概念

如下,CPU原本在调度某个进程A,该进程现在需要网卡设备,但是网卡设备被另一个进程占用了,所以 A 进程的 pcb(task_struct) 只能挂在网卡设备的等待队列中,此时进程A处于阻塞状态!
但是,如果内存里面的进程以及其他的数据过多,会使得内存空间不够用,这时处于阻塞状态的进程 A,其 pcb、代码和数据都是用不到的,白白占用内存空间,CPU就会把进程的代码和数据放到磁盘中,此时进程A就处于挂起状态!!

请添加图片描述
如下图所示,挂起状态的进程的 pcb依然在内存中,只是代码和数据被放到磁盘。

请添加图片描述

📕 Linux 的进程状态

上面所说的进程状态,是适用于所有的操作系统,对于 Windows,MacOS,Linux 等等都适用,但是具体到某一个操作系统,那么除了上面所说的,还有其他的一些状态,下面就以 Linux 为例。

先看一段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):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

进程的这些状态是存储在 task_struct 里面的,如下,用一个 status 来标识其状态。

struct task_struct
{int status;…………
}

R 状态

进程处于“R”状态的时候,一定是在CPU上运行吗?其实不是,进程处于“R”状态的时候,其实是进程的 pcb 在运行队列里面,CPU在调度的时候,只需要去运行队列里挑选指定的进程即可。

请添加图片描述

S 状态

S状态是休眠状态,意味着进程在等待某种资源

编译并运行下列代码,观察进程状态,如下图。

  1 #include  2   3 int main()  4 {  5   6   while(1)  7   {  8     int a;  9     scanf("%d",&a);                                        10     printf("%d\n",a);  11   }  12   return 0;  13 }  

如下,通过 COMMAND 可以看出 PID 为13272的就是测试代码的进程,查看其状态,是 “S+”,该进程在等待键盘资源
当我们从键盘中输入数字并回车,键盘设备就绪了,该进程就会被CPU调度到运行队列,就会执行代码,将数据放到变量a里面,再打印,我们就会看到打印出的数据!
请添加图片描述
S状态又被称作可中断休眠,是指处于该状态下的进程,可以通过 Ctrl+c 强制退出

D 状态

D状态又被称作不可中断休眠。这种状态正常情况下很少会遇到。

例如,现在进程A要向磁盘写100Mb的数据,写完之后才可以在CPU上跑。将数据写到磁盘是比较慢的,这段时间内,进程A无疑会处于休眠状态,假设是处于S状态。当磁盘还在拷贝数据的时候,假设操作系统发现内存严重不足,那么它就会杀掉某些进程,此时进程A由于在休眠状态,所以被杀死。过了一会,磁盘数据保存失败,但是磁盘向进程A反馈的时候,发现进程A不见了。
那么这些数据该怎么办呢?丢弃吧,万一是100Mb很重要的信息呢?比如银行转账信息。保存吧?,由于进程A已经没了,即使保存了,进程也找不到了。

从操作系统、进程、磁盘的角度来看,三者都没有错。但是这件事就是做错了,所以,D 状态应运而生,处于D状态的进程,操作系统不可以强制杀死。

T 状态

T状态是用户来控制的,可以通过 kill 指令来实现。
使用下列代码测试:

  1 #include2 #include                             3 4 int main()5 {6   int cnt=0;7   while(1)8   {9     printf("我在运行!%d\n",cnt++);10     sleep(1);11   }12   return 0;13 }

如下,使用 kill -19 PID ,该进程就成了T状态,这是由 “我” 来控制的,而非进程要等待某种资源。
请添加图片描述
当然,也可以使该进程继续运行,使用 18 号信号即可。如下图。

请添加图片描述
但是,此时却发现一个问题,kill -18 PID 之后,该进程的状态就变成了 “S” ,而不是一开始的 “S+”,并且无法通过 Ctrl+c 退出进程,进程一直在打印消息。
这其实是因为,“S+” 状态的进程,是在前台运行,“S” 状态的进程,是在后台运行。前台运行的可以用 Ctrl+c 终止,但是后台运行的进程却不可以,虽然不会影响我们使用命令行,但是总是不舒服的。

这里可以使用 kill -9 PID ,杀死后台进程。
请添加图片描述

t 状态

该状态是追踪时的暂停,其实就相当于我们在VS环境下编写代码打断点,然后程序运行会在断点处停下,此时进程就处于 t 状态。

请添加图片描述

Z 状态

Z 状态又被称作僵尸状态,是 Linux 下非常重要的一种状态。在这里和 X 状态一起分析。

首先,我们要明白为什么要创建一个进程?因为——我们要让进程帮我们办事情。那么对于这件事情的结果,我们就有两种态度——1.我关心结果。2.我不关心结果。这里主要是关心结果的情况。

用C语言写代码的时候,有的函数会设置返回值,例如 main() 函数。这个返回值其实就是进程的退出码。如下,执行 test ,实际上是 bash 创建了一个子进程来执行。可以通过 echo $? 拿到上一个进程的退出码,也就是 test 的退出码,发现是4。由此知道,result 的最终结果不是 100。

那么,我们就可以根据退出码来判断一个进程执行的结果是否正确。

请添加图片描述

  1 #include  2 #include  3   4 int main()  5 {6   int a=10,b=20;7   int ret=a*b;8   if(ret == 100) return 0;9   else return 4;                                                                                                             10 }

但是,当一个进程退出的时候,其所有信息都被操作系统清除,该进程的父进程也就无法获取它的退出码。
所以,为了让父进程得到退出码,一般而言,进程退出的时候,不会彻底退出,而是维持 Z 状态,也叫做僵尸状态,这是为了方便父进程 / 操作系统 去读取该进程的退出码。

那么为什么子进程不直接把退出码返回给父进程,再退出呢?这是因为退出码也是数据,而进程具有独立性,子进程无法把数据返回给父进程,会发生写时拷贝。

我们可以通过运行下列代码,查看僵尸状态。

  1 #include                                                                                                    2 #include                                                                                                   3                                                                                                                      4 int main()                                                                                                           5 {                                                                                                                    6     pid_t ret = fork();                                                                                              7     if(ret == 0)                                                                                                     8     {                                                                                                                9         //子进程                                                                                                     10         while(1)                                                                                                     11         {                                                                                                            12             printf("我是子进程, 我的pid是: %d, 我的父进程是: %d \n", getpid(), getppid());                           13             sleep(1);                                                                                                14         }                                                                                                            15     }                                                                                                                16     else if(ret > 0)                                                                                                 17     {                                                                                                                18         //父进程                                                                                                     19         while(1)                                                                                                     20         {                                                                                                            21             printf("我是父进程, 我的pid是: %d, 我的父进程是: %d \n", getpid(), getppid());                           22             sleep(1);                                                                                                23         }                                                                                                            24     }                                                                                                                25     return 0;                                                                                                                26 }  

如下,kill 子进程之后,子进程确实进入了僵尸状态。
但是,维持僵尸状态也是要消耗内存的,如果一直维持僵尸状态,就会造成内存泄漏。
请添加图片描述

📕 孤儿进程

僵尸进程是 子进程退出,父进程要获取一些信息。但是如果一个进程的父进程退出了,那么该进程会发生什么变化呢?

我们可以通过执行下面的代码查看。

  1 #include2 #include3 4 int main()                                                                         5 {                                                                                  6     pid_t ret = fork();                                                            7     if(ret == 0)                                                                   8     {                                                                              9         //子进程                                                                   10         while(1)                                                                   11         {                                                                          12             printf("我是子进程, 我的pid是: %d, 我的父进程是: %d \n", getpid(), getppid());13             sleep(1);                                                              14         }                                                                          15     }                                                                              16     else if(ret > 0)                                                               17     {                                                                              18         //父进程                                                                   19         int cnt=5;                                                                 20         while(1)                                                                   21         {                                                                          22             printf("我是父进程, 我的pid是: %d, 我的父进程是: %d \n", getpid(), getppid());           23             sleep(1);                                                              24             if(--cnt) break;25         }26     }27     return 0;28 }

如下图,我们可以看到:1、当父进程推出之后,父进程并没有进入僵尸状态,而是直接退出了。2、父进程退出之后,子进程的父进程就变成了 1 号进程,并且子进程由 “S+” 变成了 “S”,即从前台进程变成了后台进程

对于 1 ,很好解释,因为在僵尸状态里面的代码其实是错误的,没有 wait ,只是为了帮我们更好地看到僵尸进程这个现象。而父进程的父进程,即bash,在父进程退出之后,自动回收了该进程,所以我们没有看到僵尸状态。
对于2,子进程被 1 号进程“领养”,让1 号进程成为新的父进程(1号进程实际上就是操作系统)。这种被 1 号进程“领养”的进程,就是孤儿进程
请添加图片描述
那么,为什么子进程会被1 号进程领养呢?反过来想,如果子进程A不被领养,那么后续A退出就没有进程回收进程A了,进程A就会一直处于僵尸状态,消耗内存资源。

相关内容

热门资讯

SLF4J、Log4J、Log... SLF4J ,全称Simple Logging Facade for Java...
法律是什么: 20世纪英美法理... 作者: 刘星 出版社: 重庆出版社 出品方: 华章同人 丛书: 华章同人·现代图书馆 全书总共分为...
哈佛大学学生抗议特朗普政策:没... 哈佛大学部分学生和教职员工近日在校园内举行集会,抗议特朗普政府切断对哈佛的资助,并试图禁止哈佛招收国...
律师解读“男子遭精神病邻居砸门... 据荔枝新闻报道,吉林松原的刁先生自2022年12月以来,一家人长期受到楼下精神病邻居上门砸门咒骂,生...
戴尔电脑开机自检或显示no b... 1、情况描述   从2022年10月到2023年3月,我用了将近五年的戴尔G3...
k8s学习(三十四)飞腾200... 文章目录1、环境2、准备Kubernetes安装包3、安装docker3.1、下载docker3.2...
Handler源码分析之Mes... 问:大家知道Handler机制中发送的Message分为几种吗?答&#x...
三部门发文明确7条政策措施 优... 原标题:三部门发文明确7条政策措施(引题) 优化国企技能岗位薪酬分配(主题) 人民日报北京5月31日...
2025跨境电商专题政策法规汇... 今天分享的是:2025跨境电商专题政策法规汇编 报告共计:105页 跨境电商政策法规核心解读:红利与...
面试历程(5) 1、Time_Wait的产生和危害以及解决方案 time-wait的产生: 在TCP连接中四次挥手关...