【Linux】进程状态&&僵尸进程和孤儿进程&&阻塞、挂起和运行 您所在的位置:网站首页 Linux进程被终止后进入什么态 【Linux】进程状态&&僵尸进程和孤儿进程&&阻塞、挂起和运行

【Linux】进程状态&&僵尸进程和孤儿进程&&阻塞、挂起和运行

2024-05-20 09:18| 来源: 网络整理| 查看: 265

1. 前言

上一篇博客中提到 【Linux】进程初步理解,这次继续来分享与进程有关的知识。

2. Linux的进程状态

Linux的进程状态就是struct task_struct内部的一个属性。 为了弄明白正在运行的进程是什么意思,我们需要知道进程的不同状态。一个进程可以有几个状态(在Linux内核里,进程有时候也叫做任务)。 下面的状态在kernel源代码里定义:

代码语言:javascript复制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):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

2.1 S状态

检测一下这些状态: 先写一个测试代码:

代码语言:javascript复制 1 #include 2 #include 3 #include 4 5 int main() 6 { 7 8 while(1) 9 { 10 printf("I am a process,pid:%d\n",getpid()); 11 } 12 return 0; 13 }

运行代码:发现进程都是出于s状态:

printf在显示器上打的时候,根据冯诺依曼特体系结构,显示器是一个外设,所以CPU在跑当前程序时,要把数据写到内存中,然后刷新到外设上,但是不能保证,每一次打印的时候显示器都是就绪的。相比较CPU来讲,大部分时间这个进程都在等待设备资源是否就绪,如果资源不就绪,当前进程就一直出于S状态。

把代码在printf之前先休眠10秒:

此时发现进程一直出于S状态,可以直接ctrl+c把处于S状态的进程终止掉:

把S的这种状态叫做可中断睡眠,就是处于睡眠状态,依旧可以被外部信息随时打断。

2.2 R状态

那么把代码里面的printf给注释了:

此时进程都是R状态:

2.3 T/t状态

在kill命令中的19号命令,让进程暂停:

直接使用:

代码语言:javascript复制kill -19 pid

此时进程就处于T状态

要想让暂停的进程继续运行起来就用18号信号

此时进程又重新运行起来:

但是此时是在后台运行的,要想终止进程,只能使用kill -9。 暂停在之间调试的时候就已经用到了。

打开Makefile加-g选项

代码语言:javascript复制 1 testStatus:testStatus.c 2 gcc -o $@ $^ -g 3 .PHONY:clean 4 clean: 5 rm -f testStatus

在10行打一个断点,然后查看一下进程的运行状态:

遇到断点,进程就暂停下来。

2.4 D状态

D状态是Linux系统比较特有的状态。

D磁盘休眠状态(Disk sleep)有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。

操作系统杀进程的时候时毫无类别的去杀,在写入关键数据的进程是不能被杀掉的,所以操作系统规定,凡是进程在就行数据IO,在等待外设,像磁盘资源时,把状态设为D状态。 D状态不可被杀,深度睡眠,不可中断睡眠,就是一种sleep状态。

消除D状态:1. 让进程自己醒来;2. 重启–断点

3. 僵尸进程和孤儿进程3.1 僵尸进程

Linux中一个进程的退出,它会将自己的退出信息保留在自己的PCB中。如果不读取PCB中的进程退出消息,那么进程就一直不释放,一般会释放掉代码和数据,但PCB的内核数据结构是一直存在的,直到将来对进程进行等待;如果不等待,那么进程就一直出于僵尸状态。如果读取了这个进程的退出信息或者等待了,那么这个进程才会变成X,进而将进程的信息全部释放。

为了测试重新写一个父子进程代码:

代码语言:javascript复制 1 #include 2 #include 3 #include 4 5 int main() 6 { 7 8 pid_t id=fork(); 9 if(id==0) 10 { 11 //child 12 int cnt=5; 13 while(cnt) 14 { 15 printf("I am a child,cnt:%d,pid:%d\n",cnt,getpid()); 16 sleep(1); 17 cnt--; 18 } 19 20 } 21 else{ 22 //parent 23 while(1) 24 { printf("I am parent,running always,pid:%d\n",getpid()); 25 sleep(1); 26 } 27 } 28 return 0; 29 }

来看一下这个状态:

Z状态:已经运行完毕,但是需要维护自己的退出信息,在自己的进程task_struct会记录自己的退出信息,未来让父进程读取。 如果没有父进程读取,僵尸进程就会一直存在。 如果对僵尸进程一直不回收,就会引起内存泄漏问题,操作系统会调用waitpid来进行进程状态的改变,变为X,再由操作系统进行释放。

一个进程已经出于僵尸了,就不能kill,无法杀掉已经死掉的进程。

3.2 孤儿进程

如果一个进程在运行的时候,它的父进程先退出了,那么这个进程就是孤儿进程。

来代码看看:

代码语言:javascript复制 1 #include 2 #include 3 #include 4 5 int main() 6 { 7 8 pid_t id=fork(); 9 if(id==0) 10 { 11 //child 12 int cnt=5; 13 while(cnt) 14 { 15 printf("I am a child,cnt:%d,pid:%d\n",cnt,getpid()); 16 sleep(1); 17 18 } 19 20 } 21 else{ 22 //parent 23 int cnt=5; 24 while(cnt) 25 { printf("I am parent,running always,pid:%d\n",getpid()); 26 sleep(1); 27 cnt--; 28 } 29 } 30 // while(1) 31 // { 32 // sleep(1); 33 // printf("I am a process,pid:%d\n",getpid()); 34 // } 35 return 0; 36 }

来看看现象:

父进程如果先退出,子进程就会变成孤儿进程, 而孤儿进程一般会被1号进程(OS本身)进行领养。 为了保证孤儿进程正常被回收,孤儿进程会被操作系统领养。

可以直接kill掉孤儿进程:

在之前在Linux上写的代码,怎么出来没有关系过僵尸呢?或者内存泄漏? 因为直接在命令行中启动的进程,它的父进程是bash,bash会自动回收新进程的Z。

4. 进程的阻塞、挂起和运行

在网上找的一张进程状态图:

终止状态就等价于Z状态和X状态。

4.1 运行

进程运行一般在CPU上运行。 进程=task_struct+进程的代码和数据

每一个进程都有task_struct,为了对当前所有的进程进行管理,用链表将它们链接起来。而每一个CPU都会有一个运行队列struct runqueue,要运行进程,就得将进程放入运行队列struct runqueue中。从此CPU要运行已经进程,就在运行队列的头部取出一个进程,然后把相关的代码和数据拿到CPU寄存器中,进而就可以调度这个进程了。 一般而言一个进程被放到CPU上这个进程状态就是R,但是大部分教材中说的是进程在运行队列中,该进程的状态就是R状态,这里意思就是进程已经准备好了,可以随时被调度。

一个进程一旦持有CPU,会一直只运行这个进程吗? 不会,进程基于时间片进行轮转调度的。(而Linux中并不是以这种方法调度的,在之后的博客中会提到,请多多关注。) 让多个进程以切换的方式进程调度,在一个时间段内同时得以推进代码,就叫做并发。

把任何时刻,都有多个进程在真的同时运行,叫做并行。

4.2 阻塞状态

在C语言中用过一个scanf,如果不往里面输入数据,会一直处于什么状态?

来看看代码:

代码语言:javascript复制 1 #include 2 #include 3 #include 4 5 int main() 6 { 7 8 int a=0; 9 10 scanf("%d",&a); 11 12 printf("a=%d\n",a);

阻塞状态就相当于S状态或者是D状态。

等待:等待键盘资源是否就绪,键盘上有没有被用户按下按键,按键数据交给进程:

操作系统是软硬件资源的管理者。 进程本身就是软件。 堆硬件的管理也是先描述在组织。

操作系统里面有一个结构体对设备就行管理,每一个结构体里面包含设备的状态,种类,还有指向其他设备的指针。

在等待说明进程并没有被调度,说明进程并不在运行队列中。

如果还有进程要等待键盘资源,就把对应的进程从运行队列放在等待队列里面。拿到对应的资源后,就回到运行队列里面,这个过程一般叫唤醒。 不是只有CPU才有运行队列,各种设备都有自己的等待队列。

阻塞和运行的状态变化,往往伴随进程PCB被连入到不同的队列中。

4.3 挂起

操作系统在运行进程的时候内存时比较吃紧的,一旦进程出于阻塞状态,那么就意味着当前进程不会被调度,这个进程的代码和数据就不会被访问,此时就会把这个进程的代码和数据唤出到磁盘上。那么曾经代码和数据占用的空间就空出来了,一旦获取到相应的资源,又会被唤起。

这个进程的PCB还在内存中,只是它的代码和数据在磁盘的swap分区,此时把这种状态叫做阻塞挂起。 这样操作系统就会更合理使用内存资源。

在用户层是感知不到的。 频繁的换入换出,会导致效率问题。这个是牺牲效率换取空间。

一般swap分区的大小不会太大,为了减少操作系统频繁的使用swap带来效率问题。

4.4 进程切换

CPU内部会有很多寄存器。 函数调用返回的临时变量,就用到了寄存器。

当一个进程被CPU调度的时候,CPU的寄存器中会保存当前进程的临时数据。如果这个进程的时间片到了,那么就会把这个进程从CPU上剥离下来,把下一个进程放上去。如果想要把这个进程恢复,就得保存上下文,然后CPU运行到这个进程又继续执行。

CPU内部的所有寄存器中的临时数据,叫做进程的上下文。

进程在切换,最重要的一件事就是:上下文数据的保护和恢复。

CPU内的寄存器:寄存器本身是硬件,具有数据的存储能力,CPU的寄存器硬件只有一套。 CPU内部的数据,可以有多套,有几个进程,就有几套和该进程对应的上下文数据。 所以寄存器!=寄存器内容

有问题请指出,大家一起进步!!!



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有