【 linux 】理解进程状态


目录

1.僵尸进程与孤儿进程

[1.1 孤儿进程](#1.1 孤儿进程)

[1.2 僵尸进程(Z)](#1.2 僵尸进程(Z))

2.进程状态

3.进程退出与进程等待

[3.1 进程退出](#3.1 进程退出)

[3.2 进程等待](#3.2 进程等待)

[3.2.1 wait和waitpid对比](#3.2.1 wait和waitpid对比)

[3.3 WEXITSTATUS 和 WIFEXITED](#3.3 WEXITSTATUS 和 WIFEXITED)


1.僵尸进程与孤儿进程

1.1 孤儿进程

父进程结束了子进程还没有结束就成为了孤儿进程,linux系统所有孤儿进程会被init进程(进程号为1)自动领养,当孤儿进程执行结束时,init进程会进行回收,孤儿进程依然占据着cpu和内存。

孤儿进程示例

1.2 僵尸进程(Z)

僵尸进程是指子进程已经结束了,但是父进程没有回收子进程。子进程此时不会占据cpu资源和内存,但会占据进程号,系统的进程号是有限的,如果一直占用不回收进程号就不够用了。父进程调用wait/waitpid来回收子进程,如果父进程结束了或者被杀死了,那僵尸进程会变成孤儿进程被系统回收。

僵尸进程示例

可以输入这串命令观察进程状态:

while : ;do ps ajx | head -1&& ps ajx | grep zonbine |grep -v grep ;sleep 1 ;done

2.进程状态

常见的进程状态有运行,阻塞,暂停。

运行状态用R表示,代表程序正在运行或者在队列中等待被调度

阻塞状态分为D(可中断睡眠)和S(不可中断睡眠) S 通常为等待输入信号,可以是键盘或者调用了sleep函数。按下ctrl c或者kill进程就会立刻响应 D不能被任何信号打断,为了数据保护,大文件拷贝,数据库拷入磁盘等场景。

暂停态分为T和t T为ctrl z主动挂起 t是由调试器(gdb)触发

每一个进程都有一个PCB(进程控制块),PCB是进程存在的唯一标志。具体为一个task_struct的结构体。操作系统为了管理进程,先描述在组织。组织形式就是用红黑树,哈希,链表。一个CPU有一个调度队列,代码和数据是和进程PCB分开的,当进程执行时代码数据会从磁盘加载到内存。

抽象如下

设备也有一个设备队列,结构和调度队列类似,当进程处于阻塞状态时,就会将进程的PCB和等待响应的设备以指针的形式连接,对进程的操作本质上就是对链表的增删改查。

抽象如下

3.进程退出与进程等待

3.1 进程退出

进程结束都会有退出码 ,运行完结果正确退出码为0,运行完结果错误退出码非0,异常退出(退出码无意义)每个进程的PCB都会有退出码的信息,并把退出码返回给父进程,父进程通过wait来获得子进程的退出信息。一共有140个退出信息,可以通过steerror函数查看,echo $? 会打印最近一个进程退出时的退出码

cs 复制代码
0: Success
1: Operation not permitted
2: No such file or directory
3: No such process
9: Bad file descriptor
13: Permission denied
110: Connection timed out
...
3.2 进程等待

父进程可以通过fork创建子进程,每个进程都有自己的pid,可以通过getpid和getppid查看。

fork返回值为0代表子进程,返回值大于0(子进程pid)代表父进程。父子进程是按照时间片交替执行的。如果想让子进程执行完再去执行父进程叫做进程等待,是通过wait系统调用实现的,此时父进程就变成阻塞状态

cs 复制代码
pid_t waitpid(pid_t pid,int* stream,int options)

第二个参数会存储子进程的退出码,低7个比特位全0,如果非0代表退出码无意义

如果不想让父进程一直处于阻塞状态第三个参数可以设为WNOHANG,通常搭配循环来完成。返回值大于0,等待结束;等于0,调用结束但子进程没有退出;小于0,等待失败。父进程需要在一个循环里反复调用来检查状态

cs 复制代码
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

int main() {
    pid_t pid = fork();
    if (pid == 0) {
        printf("子进程(PID: %d)启动,运行3秒...\n", getpid());
        sleep(3); 
        printf("子进程工作完成,准备退出。\n");
        exit(42); 
    } else {
        int status;
        printf("父进程开始非阻塞轮询等待子进程...\n"); 
        while (1) 
       {
           pid_t ret = waitpid(pid, &status, WNOHANG);
            
            if (ret > 0) {
                printf("父进程成功回收子进程(PID: %d),退出码为: %d\n", 
                       ret, WEXITSTATUS(status));
                break; 
            } else if (ret == 0) {
                printf("子进程还没结束,父进程先去处理点其他任务...\n");
                sleep(1);
            } else{
                perror("waitpid error");
                break;
            }
        }
        printf("父进程所有任务处理完毕,退出。\n");
    }
    return 0;
}
3.2.1 wait和waitpid对比

wait是c语言封装的函数,底层调用的就是waitpid 。wait只需要传一个参数,其余参数都有默认值,函数原型为 pid_t wait(int *wstatus);传入NULL表示父进程不关心子进程怎么退出的,只是单纯的阻塞等待,把子资源回收,传&status搭配 WEXITSTATUS 和 WIFEXITED获取退出信息。wait只能等待任意一个子进程,不能像waitpid那样通过第一个参数pid指定等待的子进程。

3.3 WEXITSTATUS 和 WIFEXITED

这是两个宏,通常在一起组合使用来判断进程的退出情况。如果WIFEXITED为真,代表子进程正常退出,WEXITSTATUS打印退出进程的退出码

cs 复制代码
if (WIFEXITED(status)) {
    printf("正常退出,退出码: %d\n", WEXITSTATUS(status));
}

status是waitpid函数的第二个参数,使用WEXITSTATUS查看退出码就不需要右移8位了

相关推荐
雯宝1 小时前
mnt_init 函数
linux
律宏阔7 小时前
WSL Docker 端口明明空闲,但就是绑不上端口
linux·windows
律宏阔7 小时前
WSL 突然断网,无法 ping 内网或外网
linux·windows
穷人小水滴7 小时前
用容器编译 VirtualBox 虚拟机软件 (ArchLinux, podman)
linux·容器·virtualbox
乱码三千7 小时前
如何优雅地直连无公网 IP 的远程 GPU 服务器
linux·人工智能·程序员
yunwei377 小时前
使用 eBPF 跟踪 Nginx 请求
linux·后端·性能优化
yunwei377 小时前
使用 eBPF 跟踪 MySQL 查询
linux·后端·性能优化
yunwei377 小时前
eBPF 示例教程:使用 XDP 捕获 TCP 信息
linux·后端·性能优化
judezh7 小时前
api 容器一直 unhealthy?我把一个 Agent 运行时的健康检查逐条拆了,查出四个问题
运维·docker
GeW7 小时前
制造业高端化,为什么必须重估Linux和数据库?
linux