进程(2)---相关函数接口、消亡、exec函数族 | 嵌入式(Linux)

1、进程相关的函数接口

1.1 exit

  • 函数原型
cpp 复制代码
#include <stdlib.h>  // 头文件补充
void exit(int status);
  • 功能

    • exit() 是 C 标准库(<stdlib.h>)中的函数(用户层),作用是立即终止当前运行的程序 ,并将一个 "退出状态码" 返回给操作系统(比如 Windows、Linux),程序一旦执行到 exit() 就会停止,后续代码不会再运行。
    • 在主函数(main)中调用 exit(n)return n 效果基本等价(都会终止进程并返回退出码);但在非主函数中,return 仅返回函数调用处,而 exit 会直接终止整个进程。
  • 参数status 是一个整型参数,称为退出状态码,核心作用是告诉操作系统 "程序是正常结束还是异常结束"。

  • 退出流程

    • 执行通过 atexit()/on_exit() 注册的清理函数;
    • 刷新并关闭所有打开的标准 I/O 流(缓存区写入磁盘);
    • 删除通过 tmpfile() 创建的临时文件;
    • 最终调用 _exit() 完成内核层的进程终止。

1.2 _exit

  • 函数原型
cpp 复制代码
#include <unistd.h>  // 头文件补充
void _exit(int status);
  • 功能

    • 直接终止进程,属于系统调用(内核层),无任何用户层清理操作。
    • 不会刷新标准 I/O 缓存区、不会执行注册的清理函数、不会删除临时文件,直接释放进程资源并终止。
  • 对比示例:

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

int main() {
    printf("hello");  // 无换行,内容存于缓存区
    // exit(0);    // 执行此句:会刷新缓存,终端输出 "hello"
    _exit(0);       // 执行此句:不刷新缓存,终端无输出
    return 0;
}

1.3 wait

  • 函数原型
cpp 复制代码
#include <sys/wait.h>  // 头文件补充
pid_t wait(int *wstatus);
  • 功能:

    • 阻塞等待当前进程的任意一个子进程终止,并回收其资源(避免僵尸进程)。
    • 若当前进程无未回收的子进程,直接返回 - 1;若有子进程但未终止,父进程阻塞直到子进程结束。
  • 参数: wstatus:指向整型变量的指针,用于存储子进程的退出状态(可传 NULL,表示不关心退出状态)。

  • 返回值:

    • 成功:返回被回收子进程的 PID;
    • 失败:返回 - 1(如无可用子进程、被信号中断等),并设置errno
  • 退出状态宏(基于wststus):

功能
WIFEXITED(wstatus) 判断子进程是否正常退出(exit/_exit)
WEXITSTATUS(wstatus) 若 WIFEXITED 为真,获取子进程退出码(仅低 8 位)
WIFSIGNALED(wstatus) 判断子进程是否被信号终止
WTERMSIG(wstatus) 若 WIFSIGNALED 为真,获取终止子进程的信号编号
WCOREDUMP(wstatus) 判断子进程终止时是否产生核心转储文件(core dump)(是否段错误退出)
WIFSTOPPED(wstatus) 判断子进程是否被信号暂停(仅 waitpid 配合 WUNTRACED 时有效)
WSTOPSIG(wstatus) 若 WIFSTOPPED 为真,获取暂停子进程的信号编号
WIFCONTINUED(wstatus) 判断子进程是否被 SIGCONT 信号恢复运行(仅 waitpid 配合 WCONTINUED 时有效)

1.4 waitpid

  • 函数原型
cpp 复制代码
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *wstatus, int options);
  • 功能:

    • 回收指定的子进程,相比 wait 更灵活,支持阻塞 / 非阻塞模式。
  • 参数:

  • pid:指定回收的子进程范围

    • pid > 0:回收 PID 等于该值的子进程;
    • pid = -1:回收任意子进程(等价于 wait);
    • pid = 0:回收与当前进程同进程组的所有子进程;
    • pid < -1:回收进程组 ID 等于pid绝对值的所有子进程。
  • wstatus:同 wait,存储子进程退出状态(可传 NULL);

  • options:选项(可组合,用 | 分隔)

    • 0:默认,阻塞模式(子进程未终止则父进程阻塞);
    • WNOHANG:非阻塞模式,若没有子进程终止,直接返回 0;
    • WUNTRACED:返回被暂停的子进程状态;
    • WCONTINUED:返回被 SIGCONT 恢复运行的子进程状态。
  • 返回值:

    • 成功:返回被回收 / 状态改变的子进程 PID;
    • 失败:返回 - 1(无可用子进程、信号中断等);
    • 非阻塞模式(WNOHANG)且无子进程终止:返回 0。
  • 等价关系:

    • wait(NULL)waitpid(-1, NULL, 0)

2、进程的消亡

僵尸态进程如何产生:

  • 子进程终止后,内核会保留该进程的少量信息(PID、退出状态、资源使用统计等),等待父进程通过wait/waitpid回收;若父进程未调用wait/waitpid,且父进程未终止,则子进程会一直处于 "僵尸态"(Z 状态),占用 PID 资源。(进程代码执行结束,但是空间没有被回收)

如何避免产生僵尸进程:

  • 父进程先终止:父进程先于子进程结束,子进程成为孤儿进程,孤儿进程会被init(PID=1)进程收养,子进程结束,init会自动回收子进程空间,避免产生僵尸进程
  • 父进程主动回收 :子进程结束,父进程通过wait/waitpid(阻塞 / 非阻塞)及时回收子进程空间,避免产生僵尸进程

孤儿进程:

  • 父进程终止,子进程仍在运行,此时子进程的父进程变为 init 进程(PID=1),由 init 进程负责管理和回收,不会成为僵尸进程。

3、exec函数族

cpp 复制代码
#include <unistd.h>  // 所有exec函数的头文件
int execl(const char *path, const char *arg, ... /* (char *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char *const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

核心功能

  • 替换当前进程的代码段、数据段、堆栈等,执行新的程序(内核复用当前进程的 PID,仅替换进程内容);
  • 若 exec 函数调用成功,不会返回 (原进程后续代码被覆盖);只有调用失败时,才会返回 - 1,并设置errno

参数命名规则:

后缀 含义
l list:参数以可变参数列表形式传递,最后必须以 NULL 结尾
v vector:参数以字符串指针数组形式传递,数组最后一个元素必须为 NULL
p path:自动在环境变量PATH指定的目录中查找可执行文件(无需写绝对路径)
e environment:自定义环境变量,传入新的环境变量数组(替代原进程的 environ)

常用示例:

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

int main() {
    // 1. execl:绝对路径 + 列表参数
    // 执行/bin/ls,参数为ls -l -a
    execl("/bin/ls", "ls", "-l", "-a", NULL);

    // 2. execlp:PATH查找 + 列表参数(无需写路径)
    // execlp("ls", "ls", "-l", "-a", NULL);

    // 3. execv:绝对路径 + 数组参数
    // char *argv[] = {"ls", "-l", "-a", NULL};
    // execv("/bin/ls", argv);

    // 4. execvp:PATH查找 + 数组参数
    // char *argv[] = {"ls", "-l", "-a", NULL};
    // execvp("ls", argv);

    // 5. execle:自定义环境变量
    // char *envp[] = {"PATH=/bin:/usr/bin", "USER=test", NULL};
    // execle("/bin/ls", "ls", "-l", NULL, envp);

    // 若执行到此处,说明exec调用失败
    perror("exec error");
    exit(1);
}

exec + fork 经典用法

父进程 fork 创建子进程,子进程调用 exec 执行新程序,父进程用 wait 回收子进程:

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

int main() {
    pid_t pid = fork();
    if (pid == 0) {  // 子进程执行新程序
        execlp("ls", "ls", "-l", NULL);
        perror("execlp error");  // exec失败才会执行
        exit(1);
    } else if (pid > 0) {  // 父进程回收子进程
        wait(NULL);
        printf("子进程执行完毕\n");
    }
    return 0;
}
相关推荐
程序员一点1 小时前
第9章:软件包管理(DNF 与 RPM)
linux·运维·openeuler
@syh.1 小时前
【linux】进程间通信
linux
wdfk_prog1 小时前
EWMA、加权平均与一次低通滤波的对比与选型
linux·笔记·学习·游戏·ssh
Hello_Embed2 小时前
STM32F030CCT6 开发环境搭建
笔记·stm32·单片机·嵌入式·freertos
longxibo2 小时前
【Ubuntu datasophon1.2.1 二开之六:解决CLICKHOUSE安装问题】
大数据·linux·clickhouse·ubuntu
何中应2 小时前
Jenkins如何注册为CentOS7的一个服务
linux·运维·jenkins·开发工具
yttandb2 小时前
linux的基础命令
linux·运维·服务器
之歆2 小时前
Linux 系统安装、故障排除、sudo、加密、DNS 与 Web 服务整理
linux·运维·前端
枫叶丹42 小时前
【Qt开发】Qt界面优化(三)-> Qt样式表(QSS) 设置方式
c语言·开发语言·c++·qt·系统架构