Linux学习笔记(九)--Linux进程终止与进程等待

初识fork函数:在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

基本概念:在父进程中返回子进程的 PID(进程ID),在子进程中返回 0,如果出错则返回 -1。

进程调用fork,当控制转移到内核中的fork代码后,内核做:分配新的内存块和内核数据结构给子进程,将父进程部分数据结构内容拷贝至子进程,添加子进程到系统进程列表当中 fork返回,开始调度器调度。

当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以 开始它们自己的旅程,看如下程序。

复制代码
int main( void )
 {
   pid_t pid;
 
   printf("Before: pid is %d\n", getpid());
   if ( (pid=fork()) == -1 )perror("fork()"),exit(1);
   printf("After:pid is %d, fork return %d\n", getpid(), pid);
 sleep(1);
   return 0;
 }

运行结果:
[root@localhost linux]# ./a.out
 Before: pid is 43676
 After:pid is 43676, fork return 43677
 After:pid is 43677, fork return 0

这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after 消息有43677打印的。而进程43677没有打印before,原理如下图所示:

所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器 决定。

进程终止:

进程终止有三种场景,分别是:(1)代码运行完毕,结果正确 (2)代码运行完毕,结果不正确 (3)代码异常终止

进程常见退出方法:

1.正常终止(可以通过echo $? 查看进程退出码):(1)从main返回 (2)调用exit (3_exit

复制代码
int main() {
    return 0;  // 进程正常终止
}

#include <stdlib.h>
exit(0);  // 正常终止,状态码0

#include <unistd.h>
_exit(0);  // 立即终止,不执行清理

异常终止:ctrl + c,信号终止

exit函数和_exit函数的区别

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
    printf("这行内容在缓冲区中");  // 注意没有换行符
    
    // exit(0);      // 会输出上面没有换行的内容
    _exit(0);       // 不会输出上面没有换行的内容
    
    return 0;
}
特性 exit() _exit()
头文件 <stdlib.h> <unistd.h>
是否刷新缓冲区
调用退出处理函数 是(atexit注册的)
关闭标准IO流

进程等待:父进程等待子进程终止并获取其退出状态的机制(防止子进程变成僵尸进程从而导致内存泄漏)

进程等待的方法:

wait:

复制代码
#include <sys/wait.h>

pid_t wait(int *status);

功能​​:等待任意一个子进程终止

参数​​:status:用于存储子进程退出状态(可以为NULL)

返回值​​:成功:返回终止的子进程PID / 失败:返回-1(如没有子进程)

waitpid:

复制代码
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

功能​​:等待特定子进程终止,提供更多控制选项

参数:

pid

(1)>0:等待特定PID的子进程

(2)-1:等待任意子进程(类似wait)

(3)0:等待与调用进程同组ID的任何子进程

(4)<-1:等待进程组ID等于pid绝对值的任何子进程

status

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

options

(1)WNOHANG:非阻塞,即使没有子进程退出也立即返回

(2)WUNTRACED:报告已停止的子进程

(3)WCONTINUED:报告已继续的子进程

返回值:成功:返回子进程PID,非阻塞且无子进程退出:返回0,失败:返回-1。

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。如果不存在该子进程,则立即出错返回。

wait的基本使用示例:

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

int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        printf("子进程运行,PID=%d\n", getpid());
        sleep(2);
        exit(123);
    } else {
        int status;
        printf("父进程等待子进程...\n");
        pid_t child_pid = wait(&status);
        
        if (WIFEXITED(status)) {
            printf("子进程%d正常退出,状态码=%d\n", 
                  child_pid, WEXITSTATUS(status));
        }
    }
    
    return 0;
}

原理图:

获取子进程status:在Unix/Linux系统中,父进程可以通过wait()waitpid()系统调用获取子进程的终止状态。这个状态信息存储在status变量中,需要使用特定的宏来解析。

复制代码
WIFEXITED(status)    // 子进程正常退出?
WEXITSTATUS(status)  // 获取子进程退出码(当WIFEXITED为真)
WIFSIGNALED(status)  // 子进程被信号终止?
WTERMSIG(status)     // 获取终止信号(当WIFSIGNALED为真)
WIFSTOPPED(status)   // 子进程是否停止?
WSTOPSIG(status)     // 获取停止信号(当WIFSTOPPED为真)
WIFCONTINUED(status) // 子进程是否已继续执行?

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。如果传递NULL,表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。status不能简单的当作整形来看待,可以当作位图来看待(只研究status低16比特位):

进程退出状态(低 8 位)​​:如果进程正常退出(WIFEXITED(status)为真),则低 8 位(status & 0xFF)是进程的退出状态(exit()_exit()的参数)。

如果进程被信号终止(WIFSIGNALED(status)为真),则低 8 位包含终止信号编号(WTERMSIG(status))。

进程终止原因(高 8 位)​​:第 8 位(status & 0x80)可能指示是否生成了核心转储(WCOREDUMP(status))。其他位可能用于特殊标志(如 WIFSTOPPED(status)表示进程被暂停)。

测试代码:

复制代码
#include <sys/wait.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <errno.h>
int main( void )
{
    pid_t pid;
 
    if ( (pid=fork()) == -1 )
        perror("fork"),exit(1);
 
    if ( pid == 0 ){
        sleep(20);
        exit(10);
    } else {
        int st;
        int ret = wait(&st);
        
        if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出
            printf("child exit code:%d\n", (st>>8)&0XFF);
        } else if( ret > 0 ) {  // 异常退出
            printf("sig code : %d\n", st&0X7F );
        }
    }
 }

测试结果:

(1)# ./a.out #等20秒退出

child exit code:10

(2)# ./a.out #在其他终端kill掉

sig code : 9

进程等待方式:在Unix/Linux多进程编程中,父进程等待子进程终止有两种主要方式:阻塞等待和循环(非阻塞)等待。

(1)阻塞等待:阻塞等待是指父进程调用wait()waitpid()时会暂停执行,直到有子进程状态改变。

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

int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        printf("子进程(PID=%d)开始运行\n", getpid());
        sleep(3);
        printf("子进程结束\n");
        exit(10);
    } else {
        printf("父进程(PID=%d)开始阻塞等待子进程\n", getpid());
        int status;
        pid_t child_pid = wait(&status);  // 阻塞等待
        
        if (WIFEXITED(status)) {
            printf("子进程(PID=%d)正常退出,状态码=%d\n", 
                  child_pid, WEXITSTATUS(status));
        }
    }
    
    return 0;
}

特点:父进程会暂停执行,直到子进程终止

循环等待(非阻塞等待):循环等待是指父进程定期检查子进程状态,而不是一直阻塞等待。

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

int main() {
    pid_t pid = fork();
    
    if (pid == 0) {
        printf("子进程(PID=%d)开始运行\n", getpid());
        sleep(5);
        printf("子进程结束\n");
        exit(20);
    } else {
        printf("父进程(PID=%d)开始循环等待子进程\n", getpid());        
        int status;
        pid_t child_pid;
        int wait_count = 0;
        
        while (1) {
            child_pid = waitpid(pid, &status, WNOHANG);  // 非阻塞检查
            if (child_pid == -1) {
                perror("waitpid失败");
                exit(1);
            } else if (child_pid == pid) {
                if (WIFEXITED(status)) {
                    printf("子进程(PID=%d)正常退出,状态码=%d\n", 
                          child_pid, WEXITSTATUS(status));
                }
                break;
            } else if (child_pid == 0) {
                wait_count++;
                printf("等待中(%d秒)... 父进程可以做其他工作\n", wait_count);
                sleep(1);  // 等待1秒后再次检查
            }
        }
    }
    
    return 0;
}

特点:父进程可以继续执行其他任务,需要主动定期检查子进程状态,适用于需要父进程同时处理其他任务的场景。

option:在进程等待(如使用 waitpid()系统调用)时,options参数用于控制等待的行为。默认情况下,options设置为 ​​0​​,表示​​阻塞等待​​(即父进程会挂起,直到目标子进程状态改变)。

option = 0的默认行为:

父进程会一直阻塞(挂起),直到以下情况发生:(1)目标子进程终止(正常退出或被信号杀死)。(2)目标子进程被信号暂停(如 SIGSTOP)。(3)如果没有子进程状态改变,父进程会一直等待。

返回条件:(1)waitpid()会返回​​任意一个终止或暂停的子进程​​的 PID。(2)如果没有子进程匹配指定的 pid参数,则返回 -1(错误码 ECHILD)。

其他option模式:

选项标志 说明
​​WNOHANG​​ 非阻塞模式:如果没有子进程状态改变,立即返回 0(不阻塞父进程)。
​​WUNTRACED​​ 报告被暂停的子进程状态(如 SIGSTOP)。
​​WCONTINUED​​ 报告被恢复的子进程状态(如 SIGCONT)。
相关推荐
淮北4943 小时前
立创EDA学习(一、新建项目与自定义元件)
学习
Json____4 小时前
学习springBoot框架-开发一个酒店管理系统,来熟悉springboot框架语法~
spring boot·后端·学习
wheeldown4 小时前
【Linux】Linux 进程信号核心拆解:pending/block/handler 三张表 + signal/alarm 实战
linux·运维·服务器
运维老司机4 小时前
ThinkPad 安装 Ubuntu 系统教程
linux·运维·ubuntu
云飞云共享云桌面5 小时前
替代传统电脑的共享云服务器如何实现1拖8SolidWorks设计办公
linux·运维·服务器·网络·电脑·制造
添砖java‘’10 小时前
vim高效编辑:从入门到精通
linux·编辑器·操作系统·vim
tryCbest11 小时前
CentOS部署Docker容器
linux·docker·centos
递归不收敛11 小时前
大语言模型(LLM)入门笔记:嵌入向量与位置信息
人工智能·笔记·语言模型
冷雨夜中漫步11 小时前
高级系统架构师笔记——数据库设计基础知识(5)Armstrong公理系统、无损连接和有损连接
笔记·系统架构