Linux笔记---进程:进程状态

1. Linux中的进程状态

上图是操作系统学科中,对进程状态的分类。但是这样细致的划分是在操作系统的设计层面上做的,其中的很多细节,用户其实不必关心。

在Linux操作系统中,面向用户层面,对进程状态做了如下的划分:

cpp 复制代码
/*
*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 */
};
  1. "R (running)" - 运行状态(0):表示进程正在CPU上执行指令或者在就绪队列中等待CPU资源以便执行。处于这个状态的进程要么正在占用CPU进行计算,要么即将获得CPU时间片来运行。
  2. "S (sleeping)" - 睡眠状态(1):进程正在等待某个事件的发生,例如等待I/O操作完成(如读取磁盘文件、等待网络数据到达等)。在这种状态下,进程不会占用CPU资源,而是将CPU让给其他可运行的进程,直到被等待的事件发生(如数据准备好)才会被唤醒重新进入就绪状态或者直接运行。
  3. "D (disk sleep)" - 磁盘睡眠状态(2):这是一种特殊的睡眠状态,进程正在等待磁盘I/O操作完成。与普通睡眠状态不同的是,处于"D"状态的进程不能被轻易杀死(即使使用kill命令发送信号),因为这样可能会导致磁盘数据不一致。例如,当进程正在向磁盘写入重要数据时,如果被强制终止,可能会破坏文件系统的完整性。
  4. "T (stopped)" - 停止状态(4):进程接收到特定的信号(如SIGSTOP信号)而被暂停执行。这个状态下的进程不会继续运行,直到收到SIGCONT信号来恢复执行。例如,调试器可能会发送SIGSTOP信号使进程停止,以便进行调试操作,调试完成后再发送SIGCONT信号使进程继续运行。
  5. "t (tracing stop)" - 跟踪停止状态(8):当进程被调试器或跟踪工具跟踪时,可能会处于这种状态。在这种状态下,进程的执行被暂停以便进行跟踪和调试相关的操作,例如查看进程的变量值、执行路径等。与"T (stopped)"状态类似,它也需要特定的信号来恢复执行。
  6. "X (dead)" - 死亡状态(16):进程已经结束执行,并且已经释放了大部分资源,但是可能还有一些内核资源(如进程描述符的部分内容)正在被清理。这个状态是进程生命周期的最后阶段,一旦清理完成,进程就完全从系统中消失。
  7. "Z (zombie)" - 僵尸状态(32):进程已经执行完毕,但是父进程还没有调用wait或waitpid等函数来回收子进程的资源(如进程的退出状态码等)。在这种状态下,子进程的进程描述符仍然存在于内核中,占用少量的系统资源。僵尸进程是一种需要注意的情况,因为如果大量的僵尸进程存在,可能会消耗过多的系统资源。

进程处于某个状态,实际上就是其task_struct结点被加入对应的队列。

例如,准备就绪等待运行或正在运行的程序就被添加到运行队列;等待外部设备进行I/O操作的进程就被添加到对应设备的等待队列 依次进行I/O操作。

2. 查看进程状态

bash 复制代码
ps aux / ps axj 
  • a:显示一个终端所有的进程,包括其他用户的进程。
  • x:显示没有控制终端的进程,例如后台运行的守护进程。
  • j:显示进程归属的进程组ID、会话ID、父进程ID,以及与作业控制相关的信息。
  • u:以用户为中心的格式显示进程信息,提供进程的详细信息,如用户、CPU和内存使用情况等。

结合head、grep等指令,我们可以在命令行输入如下指令,以方便地查看指定进程:

bash 复制代码
ps ajx | head -1; ps ajx | grep [指定进程的关键字] | grep -v grep

其中,STAT栏描述的就是进程的状态,除了上一模块介绍到的各个状态的标识,这些标识后面通常还会带上一些其他符号以提供更加详细的信息:

  1. <:高优先级进程。
  2. N:低优先级进程。
  3. L:锁定内存页。
  4. s:会话进程组的领头进程。
  5. l:多线程,进程拥有多个线程。
  6. +:前台进程,该进程正在和用户交互。

查看进程状态还可通过top指令完成,在Linux笔记---进程:初识进程-CSDN博客中有详细介绍。

3. 向进程发送信号

在Linux系统中,向进程发送信号可以通过以下几种方式:

1. 使用 kill 命令

kill 命令是最常用的向进程发送信号的方式。它可以向指定的进程发送指定的信号。例如,要向进程ID为 1234 的进程发送 SIGTERM 信号,可以使用以下命令:

kill -s SIGTERM 1234

或者使用信号编号:

kill -15 1234

使用kill -l指令可以查看有哪些信号:

bash 复制代码
 1) SIGHUP	     2) SIGINT	     3) SIGQUIT	     4) SIGILL	     5) SIGTRAP
 6) SIGABRT	     7) SIGBUS	     8) SIGFPE	     9) SIGKILL	    10) SIGUSR1
11) SIGSEGV	    12) SIGUSR2	    13) SIGPIPE	    14) SIGALRM	    15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	    18) SIGCONT	    19) SIGSTOP	    20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	        23) SIGURG	    24) SIGXCPU	    25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	    28) SIGWINCH	29) SIGIO	    30) SIGPWR
31) SIGSYS	34) SIGRTMIN	    35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

这些信号,我们会在之后的文章中再做细致讲解。

2. 使用 pkill 命令

pkill 命令允许通过进程名来杀死一组进程。例如,要杀死所有名为 "firefox" 的进程,可以使用以下命令:

pkill firefox

3. 使用 killall 命令

killall 命令与 pkill 类似,但如果给出的进程名不完整,killall 会报错。例如:

killall -9 firefox

4. 使用系统调用

在C语言中,可以使用 kill 系统调用来向进程发送信号。例如:

cpp 复制代码
#include <sys/types.h>
#include <signal.h>
#include <stdio.h>

int main() {
    pid_t pid = 1234; // 目标进程的PID
    int sig = SIGTERM; // 要发送的信号

    int ret = kill(pid, sig);
    if (ret == 0) {
        printf("信号发送成功\n");
    } else {
        perror("kill");
    }

    return 0;
}

5. 使用键盘快捷键

在终端中,可以使用键盘快捷键向当前前台进程发送信号。例如:

  • Ctrl+C:发送 SIGINT 信号,通常用于终止正在运行的程序。
  • Ctrl+\:发送 SIGQUIT 信号,终止进程并生成核心转储文件。
  • Ctrl+Z:发送 SIGTSTP 信号,暂停进程的执行。

6. 使用函数产生信号

在C语言中,可以使用 raise 函数向当前进程发送信号,或者使用 abort 函数向当前进程发送 SIGABRT 信号,强制使程序异常终止。例如:

cpp 复制代码
#include <signal.h>
#include <stdio.h>

int main() {
    raise(SIGTERM); // 向当前进程发送SIGTERM信号

    return 0;
}

7. 由软件条件产生信号

例如,当进程执行某些系统调用(如 pause()wait())时,它们可能会因为信号而提前返回。这种机制是基于内核的同步原语和进程的状态管理。

8. 异常产生信号

当进程执行时发生硬件错误或异常,如除以零、访问非法内存地址等,CPU的异常处理机制会触发,内核会为当前进程生成相应的信号。例如,除以零错误会产生 SIGFPE 信号,非法内存访问会产生 SIGSEGV 信号。

请注意,有些信号(如 SIGKILLSIGSTOP)不能被捕获、阻塞或忽略,因为它们是用于确保系统管理员能够控制所有进程的。

4. 僵尸进程

进程执行完毕之后并不会直接消失,而是会进入僵尸状态,并等待其父进程来回收其资源(也就是所谓的"收尸"),因为它要向父进程反馈自身执行任务的信息,例如完成情况、因什么原因退出等。

所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就进入Z状态。

由于进程的状态等信息是由PCB维护的,进程处于僵尸状态时其代码数据可能会得到释放,但是其PCB不会被释放。在长期运行的项目中,僵尸进程忘记被释放就会导致内存泄露的问题。

在进程运行过程中,父进程创建子进程,子进程结束时,它的部分资源(如进程描述符等)需要被父进程回收。

如果父进程没有及时回收子进程的资源,子进程就会一直处于僵尸状态,并且会一直在等待父进程读取退出状态代码,这可能会导致系统资源的浪费,特别是在有大量子进程产生且父进程没有正确处理子进程结束情况的程序中。

cpp 复制代码
#include <stdio.h>
#include <stdlib.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;
}

5. 孤儿进程

与僵尸进程相对的就是孤儿进程,即父进程在子进程之前退出了。

这会导致什么问题呢?很明显,子进程在变成僵尸进程之后没有人为他收尸,我们称其为孤儿进程。这时候就需要有一个进程来领养这个孤儿。

谁来领养呢?1号进程会对孤儿进程进行领养,并负责为其收尸。

在Linux系统中,1号进程通常是指systemd,它是一个系统和服务管理器,负责在系统启动时初始化系统,并启动所有其他系统服务。systemd是现代Linux发行版中最常用的初始化系统,它取代了传统的init进程。

cpp 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
    pid_t id = fork();
    if (id < 0) {
        perror("fork");
        return 1;
    } 
    else if (id == 0) {//child
        printf("I am child, pid : %d\n", getpid());
        sleep(10);
    }
    else {//parent
        printf("I am parent, pid: %d\n", getpid());
        sleep(3);
        exit(0);
    } 
    return 0;
}
相关推荐
咔叽布吉13 分钟前
【前端学习笔记】ES6 新特性
前端·笔记·学习
超越✔15 分钟前
学习内容分享
笔记·学习·面试
Lostgreen28 分钟前
SQL on Hadoop
数据库·hadoop·笔记·分布式·sql·学习
坚硬果壳_1 小时前
《硬件架构的艺术》笔记(八):消抖技术
笔记·硬件架构
八年。。1 小时前
MATLAB 中有关figure图表绘制函数设计(论文中常用)
开发语言·笔记·学习·matlab
THRUSTER111112 小时前
Java学习笔记--继承的介绍,基本使用,成员变量和成员方法访问特点
java·开发语言·笔记·学习·学习方法·继承·intellij idea
DDDiccc2 小时前
JAVA题目笔记(二十)Stream流综合练习+方法引用
笔记
开心就好13145203 小时前
uniapp 开发微信小程序笔记
笔记·微信小程序·uni-app
澜世4 小时前
2024小迪安全基础入门第七课
网络·笔记·安全·网络安全