Linux 进程控制

Linux 进程控制是操作系统对进程生命周期(创建、运行、终止、资源回收)的管理机制,核心涉及进程创建、进程终止、进程等待、进程程序替换等操作,以及相关的关键概念(如僵尸进程、孤儿进程)。

一、进程创建

进程创建的核心是通过系统调用从已存在的进程(父进程)生成新进程(子进程),Linux 中最常用的是 fork() 函数,另有 vfork()(较少使用)。

1. fork () 函数

fork() 是创建新进程的主要系统调用,功能是复制当前进程(父进程)的地址空间,生成一个几乎完全相同的子进程。
  • 函数原型
    #include <unistd.h>
    pid_t fork(void);
    
  • 返回值
    • 对父进程:返回子进程的 PID(正数);
    • 对子进程:返回 0;
    • 失败:返回 -1(如内存不足、进程数达上限)。
  • 原理
    fork() 会为子进程复制父进程的 PCB(进程控制块)、地址空间(代码段、数据段、堆、栈等),但父子进程共享文件描述符(如打开的文件)。
    注意:fork() 后,父子进程并发执行,执行顺序由操作系统调度器决定,不确定。
  • 示例
    #include <stdio.h>
    #include <unistd.h>
    int main() {
        pid_t pid = fork();
        if (pid < 0) {  // 失败
            perror("fork error");
            return 1;
        } else if (pid == 0) {  // 子进程
            printf("我是子进程,PID:%d,父进程PID:%d\n", getpid(), getppid());
        } else {  // 父进程
            printf("我是父进程,PID:%d,子进程PID:%d\n", getpid(), pid);
        }
        return 0;
    }
    

2. vfork () 函数

vfork() 也用于创建子进程,但与 fork() 有本质区别:
  • 子进程共享父进程的地址空间(不复制),即父子进程操作同一块内存;
  • 子进程优先运行,直到子进程调用 exit() 或 exec 系列函数后,父进程才会恢复运行。
由于共享地址空间容易引发冲突(如子进程修改变量影响父进程),且存在安全风险,现在已很少使用,推荐用 fork()

二、进程终止

进程终止即进程生命周期结束,分为正常终止异常终止

1. 正常终止

  • main 函数返回return n 等价于 exit(n)(会触发 exit 函数)。
  • 调用 exit ():标准库函数,会先刷新缓冲区(如 printf 的输出缓冲区),再调用 _exit() 终止进程。
  • 调用 _exit ()/_Exit ():系统调用(或标准库的底层实现),直接终止进程,不刷新缓冲区。
  • exit 与 _exit 的区别
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    int main() {
        printf("Hello");  // 缓冲区未刷新(默认行缓冲,无换行符)
        exit(0);         // 会刷新缓冲区,输出 "Hello"
        // _exit(0);     // 不刷新缓冲区,无输出
    }
    

2. 异常终止

由信号(Signal)触发,如:
  • 手动输入 Ctrl+C(触发 SIGINT 信号);
  • 访问非法内存(触发 SIGSEGV 信号);
  • 除以 0(触发 SIGFPE 信号)。

三、进程等待

父进程需要等待子进程终止并回收其资源(如 PCB、内存),否则子进程会变成僵尸进程(占用系统资源)。进程等待通过 wait() 或 waitpid() 实现。

1. wait () 函数

  • 功能:阻塞等待任意一个子进程终止,回收其资源。
  • 原型
    #include <sys/wait.h>
    pid_t wait(int *status);  // status 用于接收子进程的退出状态
    
  • 返回值:成功返回终止的子进程 PID;失败返回 -1(如无子进程)。

2. waitpid () 函数

比 wait() 更灵活,可指定等待的子进程、设置非阻塞等。
  • 原型
    pid_t waitpid(pid_t pid, int *status, int options);
    
  • 参数
    • pid:指定等待的子进程(pid > 0 等待 PID 为 pid 的子进程;pid = -1 等待任意子进程,同 wait());
    • status:接收退出状态(需用宏解析,如 WIFEXITED(status) 判断是否正常终止,WEXITSTATUS(status) 获取退出码);
    • optionsWNOHANG 表示非阻塞(若子进程未终止,立即返回 0)。
  • 示例:父进程等待子进程终止并获取退出码
    #include <stdio.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <stdlib.h>
    
    int main() {
        pid_t pid = fork();
        if (pid == 0) {  // 子进程
            sleep(2);  // 模拟运行
            return 10;  // 退出码为 10
        }
        // 父进程等待
        int status;
        pid_t ret = waitpid(pid, &status, 0);  // 阻塞等待子进程 pid
        if (ret > 0) {
            if (WIFEXITED(status)) {  // 正常终止
                printf("子进程 %d 正常终止,退出码:%d\n", ret, WEXITSTATUS(status));
            }
        }
        return 0;
    }
    

四、进程程序替换

进程程序替换是用新程序的代码和数据覆盖当前进程的地址空间,实现 “运行另一个程序” 的效果(进程 PID 不变,仅替换代码、数据、堆栈等)。通过 exec 系列函数实现。

1. exec 系列函数

exec 有 6 个变体,区别在于参数传递方式、是否带路径、是否自定义环境变量:
函数名 特点(参数形式 / 路径 / 环境变量)
execl 参数为列表(const char *path, const char *arg, ...),需完整路径
execlp 同 execl,但可自动搜索 PATH 环境变量(无需完整路径)
execle 同 execl,且可自定义环境变量(最后一个参数为环境变量数组)
execv 参数为数组(const char *path, char *const argv[]),需完整路径
execvp 同 execv,可自动搜索 PATH
execvpe 同 execvp,可自定义环境变量
  • 示例:子进程替换为 ls -l 命令
    #include <stdio.h>
    #include <unistd.h>
    
    int main() {
        pid_t pid = fork();
        if (pid == 0) {  // 子进程
            printf("子进程替换为 ls -l...\n");
            // 替换为 ls -l(execlp 自动搜索 PATH,参数列表以 NULL 结尾)
            execlp("ls", "ls", "-l", NULL);  
            // 若替换失败,才会执行下面的代码
            perror("exec error");
            exit(1);
        }
        wait(NULL);  // 父进程等待子进程
        return 0;
    }
    

五、关键概念

  1. 僵尸进程
    子进程终止后,父进程未调用 wait()/waitpid() 回收资源,子进程的 PCB 仍保留在系统中(仅占少量内存)。僵尸进程过多会耗尽进程号资源,导致无法创建新进程。
    解决:父进程及时调用 wait() 回收;若父进程已退出,僵尸进程会被 init(或 systemd)收养并回收。
  2. 孤儿进程
    父进程先于子进程终止,子进程会被 init(或 systemd)进程(PID=1)收养,成为 “孤儿进程”。孤儿进程终止后会被收养者回收,不会成为僵尸进程。

总结

Linux 进程控制通过 fork() 创建进程、exit() 终止进程、wait()/waitpid() 回收资源、exec 系列函数替换程序,核心目标是管理进程生命周期,避免资源泄漏(如僵尸进程),确保系统稳定运行。
阅读剩余
THE END