Linux进程状态
1.进程状态
状态在Kernel源码中的定义:
static const char * const task_state_array[] = {
"R (running)",
"S (sleeping)",
"D (disk sleep)",
"T (stopped)",
"t (tracing stop)",
"X (dead)",
"Z (zombie)",
};
- R-- 运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
- S-- 睡眠状态(sleeping): 意味着进程在等待事件完成(也叫做可中断睡眠)
- D-- 磁盘休眠状态(Disk sleep)也叫不可中断睡眠状态,在这个状态的进程通常会等待IO的结束
- T-- 停止状态(stopped): 可以通过发送 SIGSTOP 信号给进程来停止进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行
- X-- 死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态
- Z-- 僵尸状态 (zombie) : 已经死亡的进程, 依然占着某些资源
可以通过ps aux / ps axj 命令查看进程状态

2.运行状态R
只有该状态下的进程才有可能在CPU中执行,为什么说是可能呢?因为在Linux下,正在运行和就绪(在运行队列中, 要拿到时间片才能运行)的进程都视作运行状态。Linux 下 R 状态的进程的task_struct结构 (PCB) 被放入对应的CPU的可执行队列中(一个进程最多只能出现在一个CPU的可执行队列中)。
进程调度器的任务就是从各个CPU的可执行队列中分别选择一个进程在该CPU上行。
3.睡眠状态S(可中断睡眠)
处于这个状态的进程因为等待某个事件的发生,比如wait()阻塞,而被挂起。 这些进程的task_struct被放入对应事件的等待队列中。当等待的事件发生时(由外部中断触发、或由其他进程触发),对应等待队列中的这个睡眠状态的进程被唤醒。通过ps命令我们会看到,一般情况下,进程列表中的绝大多数进程都处于S 状态。
如下代码:子进程由于sleep进入S状态,父进程由于wait阻塞等待子进程退出,也处于S状态。
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
int main(){
pid_t pid = fork();
if(pid == 0){
sleep(10);
_exit(0);
}
wait(NULL);
return 0;
}
4.磁盘休眠状态D(不可中断睡眠)
与S状态类似,D状态进程也处于 “睡眠状态”,但是此刻的睡眠是不可中断的。不可中断指的并不是CPU不响应外部硬件的中断,而是指进程不响应异步信号。绝大多数情况下,进程处在睡眠状态时,总是应该能够响应异步信号的。否则你将惊奇的发现,kill -9竟然杀不死一个正在睡眠的进程了!
我们也很好理解,为什么ps命令看到的进程几乎不会出现D 状态,而总是S状态。而 D 状态存在的意义就在于,内核的某些处理流程是不能被打断的。 如果响应异步信号,程序的执行流程中就会被插入一段用于处理异步信号的流程(这个插入流程可能只存在于内核态,也可能延伸到用户态),于是原有的流程被中断了。在进程对某些硬件进行操作时(比如进程调用read系统调用对某个设备文件进行读操作,而read系统调用最终执行到对应设备驱动的代码,并与对应的物理设备进行交互),可能需要使用 D 状态对进程进行保护,以避免进程与设备交互的过程被打断,造成设备陷入不可控的状态。这种情况下的 D 状态总是非常短暂的,通过ps命令基本上不可能捕捉到。
5.停止状态(T) & 跟踪状态(t)
向进程发送一个SIGSTOP信号,它就会因响应信号而进入T状态(除非该进程本身处于 D 状态而不响应信号 )。SIGSTOP与SIGKILL(无条件终止)信号一样,是非强制的。不允许用户进程通过signal系统的系统调用重新设置对应的信号处理函数。向进程发送一个SIGCONT信号,可以让其从 T 状态恢复到 R 状态。
当进程正在被跟踪时,它处于 t 这个特殊的状态。“正在被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作。比如在gdb中对被跟踪的进程设置一个断点,进程在断点处停下来的时候就处于 t 状态。而在其他时候,被跟踪的进程还是处于前面提到的那些状态。对于进程本身来说,T 和 t 状态很类似,都是表示进程暂停下来。
而 t 状态相当于在 T 之上多了一层保护,处于 t 状态的进程不能响应SIGCONT信号而被唤醒。只能等到调试进程通过ptrace系统调用执行PTRACE_CONT、PTRACE_DETACH等操作 (通过ptrace系统调用的参数指定操作), 或调试进程退出,被调试的进程才能恢复 R 状态。
6.死亡状态X(退出状态)
进程即将被销毁时的状态,死亡状态是非常短暂的,几乎不可能通过ps命令捕捉到。
7.僵死状态Z
在进程退出过程中,进程所占有的所有资源都将被回收,除了task_struct结构 (以及少数资源) 不立即释放。但当一个进程退出,如果其所占用的资源没有释放回收,这个进程就变成了僵尸进程,处于僵死状态。
例如当子进程先于父进程退出时,为了保存自身的退出状态(退出码或异常信号),task_struct 以及少数资源不会立即释放,一直等待父进程接收其退出状态。这时操作系统会通知父进程获取子进程的退出状态,当父进程获取到子进程的退出状态后,将释放子进程未释放完的资源。
但若父进程没有关注到子进程退出,一直没有获取子进程退出状态。这个子进程就成为了一个只有着一个空的task_struct(只保存着退出状态以及一些统计信息)而没有"真正灵魂"的进程,此时这个子进程就处于僵死状态。
内核也可以将这些信息保存在别的地方,而将task_struct释放掉,以节省一些空间。但是使用task_struct结构更为方便,因为内核中已经建立了从PID到task_struct查找关系,还有进程间的父子关系。释放掉task_struct,则需要建立一些新的数据结构,以便让父进程找到它的子进程的退出信息。父进程可以通过wait系列的系统调用(如wait4,waitid)来等待某个或某些子进程的退出,并获取它的退出信息。然后wait系列的系统调用会顺便将子进程的task_struct也释放掉。
前面说到,子进程在退出时操作系统会通知父进程获取退出码,即内核会给其父进程发送一个信号,通知父进程来"收尸"。这个信号默认是SIGCHLD,但是在通过clone系统调用(fork和vfork就是封装了clone)创建子进程时,可以设置这个信号。
8.僵尸进程
处于僵死状态的进程称之为僵尸进程。
#include <stdio.h>
#include <stdlib.h>
#include<unistd.h>
int main() {
pid_t pid = fork();
if(pid < 0){
perror("fork");
return 1;
}
else if(pid == 0){
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
else{
printf("parent[%d] is sleeping...\n", getpid());
sleep(10);
}
return 0;
}

父进程处于S状态时,没有获取子进程退出状态,子进程就变成了僵尸进程。
僵尸进程的危害:
- 子进程先于父进程终止,而父进程又没有调用wait或waitpid,此时子进程进入僵死状态,并且会一直保持下去直到系统重启。内核只保存进程的一些必要信息在task_struct中以备父进程所需。
- 一个父进程创建了很多子进程,要是都不回收,就会造成内存资源的浪费, 因为task_struct数据结构对象本身就要占用内存,要在内存的某个位置进行开辟空间, 这样就会造成资源泄漏。
- Linux操作系统最大进程程数是有限制的,如果僵尸进程过多,会导致进程数过多创建不了新的进程。
如何避免僵尸进程:
- 让退出进程的父进程用wait()阻塞等待子进程退出并回收, 或用waitpid() 隔段时间来查询子进程是否结束并回收
- 采用信号SIGCHLD通知处理,并在信号处理程序中调用wait()函数
- 让僵尸进程变成孤儿进程,由init进程回收,就是让父进程先退出
孤儿进程
如果父进程先于子进程退出,那么子进程该怎么办呢?
当一个父进程进程退出的时候,会将它所有的子进程都托管给别的进程,使其成为别的进程的子进程。托管给谁?可能是退出进程所在进程组的下一个进程或者是1号进程(init进程)。所以每个进程每时每刻都有父进程存在,除非它是1号进程。
1号进程: pid为1的进程,又称init进程。linux系统启动后, 第一个被创建的用户态进程就是init进程。
它有两项使命:
-
1、执行系统初始化脚本,创建一系列的进程(它们都是init进程的子孙)
-
2、在一个死循环中等待其子进程的退出事件,并调用waitpid()由系统来完成资源回收操作
init进程不会被暂停,也不会被杀死(内核来保证的)。 它在等待子进程退出的过程中处于S状态,资源回收过程中则处于R状态。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
pid_t pid = fork();
if(pid < 0){
perror("fork error");
exit(-1);
}
else if(pid == 0){
printf("子进程睡一会\n");
sleep(5);
printf("子进程退出\n");
}
else{
printf("父进程先走一步\n");
exit(0);
}
return 0;
}

父进程先退出,在子进程睡眠的这段时间秒内,子进程被1号进程收养,子进程退出后由1号进程回收。

本文详细介绍了Linux进程的七种状态:运行、睡眠、磁盘休眠、停止、死亡、僵死和僵尸进程,以及各状态的特点和转换,强调了不可中断睡眠状态D的特殊性和僵尸进程对系统资源的影响。通过进程状态,可以更好地理解和调试Linux系统中的进程行为。
2345

被折叠的 条评论
为什么被折叠?



