欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

Linux 进程讲解 三 进程状态 有大用

  之前在介绍PCB的时候给大家说过PCB中有一项是保存我们进程的状态,我们可以看一下Linux内核里边的源码里边的定义

可以看出这个数组里边有7种状态:

R(running)是我们的运行状态,但是这并不代表着这个程序一定正在执行,他很有可能是在我们的运行队列里边,也有很多人会把这个状态分成两个, 是在执行的程序才是我们的running状态在运行队列里边的是我们的ready状态,这里只有一个了解就可以。

S(sleeping)这里的状态是睡眠状态,我认为叫睡眠不如叫等待状态,并且这里的睡眠是我们的可中断睡眠状态,他是在等待某个事件的发生或者结束比如在服务器里边我在等待我们的socket连接

我们可以通过ps指令来查看,发现我们大部分程序都是在S状态也就是说是sleep睡眠状态,因为毕竟我们的CPU只有一个,进程几百甚至上千一个CPU怎么可能处理过来,所以S是我们最常见到的一个进程。

D(disk sleep)既然我们上边说了可中断睡眠状态,那这里要说的就是我们的不可中断睡眠状态,这里的不可中断并不是说我们的CPU不响应外部硬件的中断,而是指进程不响应异步信号绝大多数情况下,进程处在睡眠状态时,总是应该能够响应异步信号的。否则你将惊奇的发现,kill -9竟然杀不死一个正在睡眠的进程了!而D状态存在的意义就在于,内核的某些处理流程是不能被打断的。如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入的流程可能只存在于内核态,也可能延伸到用户态),于是原有的流程就被中断了。在进程对某些硬件进行操作时(比如进程调用read系统调用对某个设备文件进行读操作,而read系统调用最终执行到对应设备驱动的代码,并与对应的物理设备进行交互),可能需要使用D状态对进程进行保护,以避免进程与设备交互的过程被打断,造成设备陷入不可控的状态。这种情况下的D状态总是非常短暂的,通过ps命令基本上不可能捕捉到。我们是可以通过程序来使我们的程序进入D状态的,但是这里我并不知道咋弄,所以就不多介绍了。

T(stopped)和t(tracing stop)状态我们一起说,向进程发送一个SIGSTOP信号,它就会因响应该信号而进入T状态。(SIGSTOP与SIGKILL信号一样,是非常强制的。向进程发送一个SIGCONT信号,可以让其从T状态恢复到R状态。当进程正在被跟踪时,它处于t这个特殊的状态。“正在被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作。对于进程本身来说,T和t状态很类似,都是表示进程暂停下来。
而t状态相当于在T之上多了一层保护,处于t状态的进程不能响应SIGCONT信号而被唤醒。只能等到调试进程通过ptrace系统调用执行PTRACE_CONT、PTRACE_DETACH等操作。

X(dead)是死亡状态,这个状态是一个返回状态,所以我们通过指令来查看的时候是不会看到这个状态的。进程在退出过程中也可能不会保留它的task_struct。此时,进程将被置于X退出状态,这意味着接下来的代码立即就会将该进程彻底释放。X状态是非常短暂的,几乎不可能通过ps命令捕捉到。命好的可以去尝试一下捕捉一下。

最后一个是我们的Z(zombie)僵尸状态,这个状态是十分恐怖的一个状态,简单来说就是如果子进程死掉了,但是父进程还没有来查看子进程退出的信息,那子进程的task_struct还会继续保留,保留一些他进程退出的信息这时候这个子进程就会进入一个僵尸状态。我们可以通过一个简单的例子来理解这个僵尸进程,假如有个二十岁的程序员在编写代码的时候突然暴毙了......但是死掉之后他的尸体不能立马被处理掉,因为这时候需要他的父亲亲自来公司看看他的儿子是怎么死的,是被客户不断改需求气死的,还是被他的竞争对手下毒害死的,那他父亲来之前的这段时间,他已经死了,但是尸体没有处理掉,那他这段时间就是一个僵尸,进程在这段时间就被称为僵尸进程。

也就是说进程在退出过程中,进程占有的所有资源将被回收,除了task_struct结构(以及少数资源)以外。于是进程就只剩下task_struct这么个空壳,故称为僵尸。
之所以保留task_struct,是因为task_struct里面保存了进程的退出码、以及一些统计信息。而其父进程很可能会关心这些信息。当然,内核也可以将这些信息保存在别的地方,而将task_struct结构释放掉,以节省一些空间。但是使用task_struct结构更为方便。父进程可以通过wait系列的系统调用来等待某个或某些子进程的退出,并获取它的退出信息。然后wait系列的系统调用会顺便将子进程的尸体(task_struct)也释放掉。
  子进程在退出的过程中,内核会给其父进程发送一个信号,通知父进程来“收尸”。

僵尸状态我们是可以通过程序来查看到的,我们通过一个程序来看一下一个进程的僵尸状态。

  1. 1 #include<stdio.h>                                                                                                                                                  
  2.   2 #include<stdlib.h>
  3.   3
  4.   4 int main()
  5.   5 {
  6.   6     pid_t id=fork();
  7.   7     if(id<0)
  8.   8     {
  9.   9         printf("error\n");
  10.  10         return 1;
  11.  11     }
  12.  12     else
  13.  13     {
  14.  14         if(id>0)//这里是父进程
  15.  15         {
  16.  16             printf("this is parent id=%d\n",getpid());
  17.  17             sleep(30);
  18.  18         }
  19.  19         else
  20.  20         {
  21.  21             printf("child become zombie id=%d\n",getpid());
  22.  22             sleep(5);
  23.  23             exit(EXIT_SUCCESS);
  24.  24         }
  25.  25      }
  26.  26     
  27.  27    return 0;
  28.  28 }
  29.  29

这是我们编写的程序,我们之后查看我们的进程状态,这里我们就要打开两个终端,如果在xshell下边操控的话要复制一个ssh渠道,在另外的终端下查看,因为这个终端我们需要运行我们的程序,并且不能退出,退出的话这个进程就消失了。

这里我们可以看到子进程的进程id是2592,我们通过ps指令可以看到我们的2592的进程状态是Z也就是我们上边所说的僵尸状态,这个程序通过啥原理,就是我们fork的分流原理,我们的子程序执行的是else里边的语句,执行完之后他死掉了,但是这段时间父进程依然在执行if(ture)里边的语句,父进程还没有死这段时间并且对子进程不管不顾子进程就变成了僵尸进程。

简单来说,子进程想把自己的状态或者是死因告诉父进程但是父进程还不读取还不看,那这个子进程就成了僵尸进程,并且我们僵尸进程的PCB还是保存着,既然有PCB那就是占着空间占着内存的,所以说如果我们的程序里边有大量的僵尸进程,该退出不退出,就这么说吧,假如你打开QQ了,qq各种服务全都是僵尸进程,你看了看个人资料他没关掉,你看了看空间他没关掉,你打了个语音电话他没关掉,等会你和妹子聊天了发现进程数目满了,不能创建新的进程了

所以僵尸进程的危害还是很大的,内存泄漏是必然的,不过这和我们数组越界一样,虽然这个比喻不是很恰当,但是这些都是可以预防的,就比如说我们上边说到的wait()函数waitpid()函数。

这里我简单做了一个尝试

我在父进程里边加了一个wait函数,

就可以看到直到子进程消失了我们的子进程都没有进入僵尸状态。自己对wait函数了解并不是很多,如果有使用错误的地方大家可以提出来。


来自 https://blog.csdn.net/Hanani_Jia/article/details/81841714


普通分类: