进程的基础接口

最底下速查表

进程基础接口


进程的创建

核心接口:fork()

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

主要功能

  • 复制当前进程,生成父、子两个独立进程。
  • 两个进程从fork()的下一逻辑语句开始并发执行,无固定先后顺序。

接口关键解析

  • 代码、变量会完整复制两份(父子进程各自拥有独立副本)。
  • 返回值特性:父进程返回子进程PID(非0整数),子进程返回0,失败返回-1。
  • 执行顺序控制:需依赖信号量、互斥锁、条件变量等同步机制。

示例代码

#include <stdio.h>
#include <unistd.h>

int main()
{
    printf("[]fork之前\n");

    pid_t pid = fork();
    // 以上函数执行成功后
    // 父子进程都将从下面的语句开始执行,不分先后

    // 以下语句会被执行两遍
    // 在父进程中,pid将是子进程的PID
    // 在子进程中,pid将是0
    printf("[%d]: pid=%d\n", getpid(), pid);
}

输出结果说明

  • fork()前的代码(第6行)仅父进程执行1次。
  • fork()后代码(第15行)执行2次,父子进程分别输出自身PID。
  • getpid():获取当前进程的PID,父进程PID为5140时,子进程PID为5141。
  • 父进程输出的pid值是子进程PID(5141),子进程输出的pid值为0。
  • bash提示符提前输出原因:bash是父进程的父进程(祖孙关系),父进程退出后bash立即输出提示符,不等待子进程。

进程关系层级

bash(终端shell进程)
└── a.out(程序父进程,bash的子进程)
    └── a.out(程序子进程,bash的孙子进程)

知识:fork()的写时复制(Copy-On-Write)

  • 原理解析:fork()创建子进程时,并非立即复制所有内存数据,而是父子进程共享同一份内存页。
  • 触发复制:当任一进程修改内存数据(如变量赋值)时,内核才为修改的内存页创建独立副本。
  • 优势:减少fork()执行时间和内存开销,提高效率。
图示

进程的回收

核心接口

#include <sys/types.h>
#include <sys/wait.h>

// 等待任意子进程退出(阻塞)
pid_t wait(int *wstatus);

// 可指定子进程、设置等待方式(灵活)
pid_t waitpid(pid_t pid, int *wstatus, int options);

主要功能

  • 阻塞当前进程,等待子进程退出。
  • 回收子进程的系统资源,避免产生僵尸进程。

接口关键解析

  • 如果当前进程没有子进程,则该函数立即返回。
  • 如果当前进程有不止1个子进程,则该函数会回收第一个变成僵尸态的子进程的系统资源。
  • 子进程的退出状态(包括退出值、终止信号等)将被放入wstatus所指示的内存中,若wstatus指针为NULL,则代表当前进程放弃其子进程的退出状态。

退出状态判断宏

功能
WIFEXITED(status) 判断子进程是否正常退出
WEXITSTATUS(status) 获取正常退出子进程的退出值
WIFSIGNALED(status) 判断子进程是否被信号杀死
WTERMSIG(status) 获取杀死子进程的信号值

示例代码

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main(int argc, char const *argv[])
{
    int pid = fork();
    if (pid > 0)
    {
        // 父进程等待子进程退出
        int Stat;
        if (wait(&Stat) < 0)
        {
            perror("wait error");
        }
        else{
            printf("[%d]成功等待子进程退出...\n", Stat);

            // 判断当前进程的退出状态
            if (WIFEXITED(Stat))  // 判断是否正常退出
            {
                printf("子进程寿终正寝 【 %d 】\n", WEXITSTATUS(Stat));
            }
            else if (WIFSIGNALED(Stat))  // 判断是否被信号杀死
            {
                printf("子进程被信号杀死 【 %d 】\n", WTERMSIG(Stat));
            }
        }
    }
    else if (pid == 0)
    {
        // 子进程
        printf("我是【%d】你来砍我呀...\n", getpid());
        while(1);  // 无限循环,需手动发送信号终止
        printf("子进程任务结束..\n");
        exit(123); // 退出,返回值123
    }

    return 0;
}

waitpid() 进阶

#include <sys/types.h>
#include <sys/wait.h>

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

与wait()的核心区别

  • 可指定回收的子进程(通过pid参数)。
  • 可设置非阻塞等待(通过options参数)。

参数取值说明

pid取值 作用 options取值 作用
< -1 等待进程组ID为pid绝对值的任意子进程 0 阻塞等待(默认,等价于wait())
-1 等待任意子进程(等价于wait()) WNOHANG 非阻塞,无僵尸子进程则立即返回
0 等待同进程组的任意子进程 WUNTRACED 子进程暂停时返回
> 0 等待指定PID的子进程 WCONTINUED 子进程收到SIGCONT信号继续运行时返回

注意: options的取值,可以是0,也可以是上表中各个不同的宏的位或运算取值。

示例伪代码

waitpid( -123 , .... ) ; // 等待处于123进程组中的任意一个子进程退出
waitpid( -1 , .... ) ; // 等待任意一个子进程的退出 等价于wait() ;
waitpid( 0 , .... ) ;  // 等待与本进程处于同一个进程组中的任意一个子进程退出
waitpid( 123 , .... ) ; // 等待进程号为123的子进程退出

waitpid( ... , ... , 0 ) ; // 阻塞等待【默认状态】 等价于 wait() ;
waitpid( ... , ... , WNOHANG ) ;  // 非阻塞(不等待)
waitpid( ... , ... , WUNTRACED ) ; // 当子进程被暂停时返回
waitpid( ... , ... , WCONTINUED ) ;// 当子进程被继续运行时返回

进程组相关辅助接口

#include <sys/types.h>

pid_t setsid(void);        // 创建会话(脱离原进程组)
int setpgid(pid_t pid, pid_t pgid);  // 创建或加入进程组
pid_t getpgid(pid_t pid);  // 获取指定进程的进程组ID
pid_t setpgrp(void);       // 创建并进入新进程组

僵尸进程

定义

僵尸进程是处于僵尸态(Z) 的进程,无法被调度,但占用的系统资源(如PID、PCB)未被释放。

  • 僵尸态是进程生命周期的必经阶段,无法避免。
  • 核心危害:占用PID资源,PID耗尽后无法创建新进程,需及时清理。
图示

产生原因

  • 子进程退出后,需向父进程汇报退出状态(执行结果、死亡原因)。
  • 子进程退出后无法被调度,只能等待父进程主动获取状态,此阶段即为僵尸态。
  • 若父进程未处理子进程的退出状态(未调用wait/waitpid),子进程将长期处于僵尸态。

僵尸进程示例代码(zombie.c)

#include <stdio.h>
#include <unistd.h>

int main()
{
    // 子进程创建后立即退出,变成僵尸进程
    if (fork() == 0)
        return 0;

    // 父进程不退出、不处理子进程退出状态,子进程长期僵尸
    pause();  // 父进程阻塞,等待信号(此处用于保持进程运行)
    return 0;
}

僵尸进程的释放方法

方法一:父进程主动调用wait()/waitpid()

  • 核心逻辑:父进程通过wait/waitpid获取子进程退出状态,同时释放其系统资源。
  • 功能:释放资源、获取退出状态、可选阻塞父进程。
  • 适用场景:父进程已知子进程退出时机,或可阻塞等待。

方法二:父进程退出,子进程被孤儿进程组收养

  • 核心逻辑:父进程退出后,子进程成为孤儿进程,被系统的孤儿进程组(init进程或systemd,PID=1)接管。
  • 系统会自动回收孤儿进程的僵尸态资源,无需手动处理。
  • 适用场景:父进程无需持续运行,可随子进程退出而退出。

方法三:子进程通过SIGCHLD信号通知父进程(推荐)

  • 核心机制:子进程退出时,内核会向父进程发送SIGCHLD信号(默认处理方式为忽略)。
  • 实现步骤:在父进程中注册SIGCHLD信号的自定义处理函数,触发时调用wait/waitpid回收资源。
  • 优势:无需父进程阻塞,子进程退出后主动通知,实时回收,效率最高。

信号处理示例代码

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

// SIGCHLD信号的处理函数:回收僵尸子进程
void sigchld_handler(int sig_num)
{
    // 调用wait(NULL)回收任意僵尸子进程,忽略退出状态
    pid_t zombie_pid = wait(NULL);
    printf("成功回收僵尸子进程[PID: %d]\n", zombie_pid);
}

int main()
{
    pid_t pid = fork();
    if (pid == 0)
    {
        // 子进程睡眠2秒后退出,触发SIGCHLD信号
        sleep(2);
        printf("子进程[PID: %d]退出\n", getpid());
        exit(0);
    }
    else if (pid > 0)
    {
        // 注册SIGCHLD信号的处理函数,替换默认忽略行为
        signal(SIGCHLD, sigchld_handler);

        // 父进程持续运行,模拟业务逻辑
        while (1)
        {
            printf("父进程[PID: %d]运行中...\n", getpid());
            sleep(1);
        }
    }

    return 0;
}

关键说明

  • signal(SIGCHLD, 处理函数):用于修改SIGCHLD信号的默认处理方式。
  • 处理函数中必须调用wait/waitpid,否则信号触发后仍无法回收资源。
  • 适用于父进程需要持续运行(如服务端程序),无法阻塞等待子进程的场景。

加载并执行指定程序

核心接口:exec函数簇

函数名 函数原型 参数说明
execl int execl(const char *pathname, const char *arg, ... /*, (char *) NULL */); - pathname:待执行程序的完整路径(如"/bin/ls"
- arg, ...:可变参数列表,依次传入命令行参数(首个通常为程序名),** 末尾必须以(char *) NULL结束 **
execlp int execlp(const char *file, const char *arg, ... /*, (char *) NULL */); - file:待执行程序的文件名(无需完整路径,自动搜索环境变量PATH,如"ls"
- arg, ...:同execl,末尾需(char *) NULL
execle int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */); - pathname:待执行程序的完整路径
- arg, ...:可变参数列表,末尾先加(char *) NULL,再跟自定义环境变量数组envp(数组元素为"KEY=VALUE",末尾需NULL
execv int execv(const char *pathname, char *const argv[]); - pathname:待执行程序的完整路径
- argv:命令行参数数组(元素依次为程序名、参数,** 末尾元素必须为NULL**)
execvp int execvp(const char *file, char *const argv[]); - file:待执行程序的文件名(自动搜索PATH
- argv:同execv,参数数组末尾需NULL
execvpe int execvpe(const char *file, char *const argv[], char *const envp[]); - file:待执行程序的文件名(自动搜索PATH
- argv:同execv,参数数组末尾需NULL
- envp:自定义环境变量数组(末尾需NULL

主要功能

  • 加载指定程序,覆盖当前进程的内存空间(代码、数据替换)。
  • 执行成功后,原进程代码不再执行(内存已被新程序覆盖)。

接口关键解析

  • 环境变量:默认继承原进程环境变量,带e后缀的函数可自定义环境变量。
  • 后缀字母含义:
    • l(list):以可变参数列表形式传参,末尾需加NULL标识结束。
    • v(vector):以字符串数组形式传参,数组末尾需加NULL。
    • p(PATH):自动搜索环境变量PATH中的路径,无需指定完整路径。
    • e(environment):自定义环境变量,需传入环境变量数组。
  • 进程属性:仅替换内存中的代码和数据,进程PID、进程组ID等属性保持不变。

知识:exec函数簇对比表

函数名 传参方式 路径搜索 自定义环境变量 适用场景
execl 列表 已知程序完整路径,参数少
execlp 列表 未知完整路径(依赖PATH)
execle 列表 需自定义环境变量
execv 数组 参数多,需动态构造参数
execvp 数组 未知路径+动态参数
execvpe 数组 自定义环境+动态参数+路径搜索

环境变量操作接口

#include <stdlib.h>

char *getenv(const char *name);        // 获取指定环境变量的值
int setenv(const char *name, const char *value, int overwrite);  // 设置环境变量
char *secure_getenv(const char *name); // 安全获取环境变量(避免空指针)
int unsetenv(const char *name);        // 删除指定环境变量

其他加载操作

system() 函数

#include <stdlib.h>
int system(const char *command);

主要功能

  • 自动创建子进程,在子进程中执行指定命令。
  • 父进程阻塞等待子进程执行完毕后返回。

示例代码

// 执行ls -l -a命令
system("ls -l -a");

特点

  • 简化fork+exec的组合操作,无需手动回收子进程。
  • 命令执行结果无法直接获取,仅返回执行成功与否。

popen() 与 pclose()

#include <stdio.h>

FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
参数分析:
    command --> 需要执行的指定可执行文件
    type --> 可以设置管道的读写方向(只能是单向)

主要功能

  • 创建管道,fork子进程执行命令,通过文件流与子进程通信。
  • pclose()关闭文件流,等待子进程退出并回收资源。

参数解析

  • command:需要执行的命令(如"ls -l")。
  • type:管道方向,"r"(读取子进程输出)或"w"(向子进程输入),仅支持单向通信。

示例代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    FILE *fp = popen("ls -l", "r");

    char msg[128] = {0};
    int count = 0;
    while (1)
    {
        bzero(msg, sizeof(msg));
        char *ret = fgets(msg, sizeof(msg), fp);
        if (ret == NULL)
        {
            break;
        }
        printf("[%s]\n", msg);
        count++;
    }

    printf("有:%d个文件\n", count - 1);

    pclose(fp); // 必须调用,回收子进程资源
    return 0;
}

特点

  • 支持获取命令执行结果(通过文件流读取)。
  • 必须调用pclose(),否则会产生僵尸进程。

拓展知识:system() 与 popen() 区别

特性 system() popen()
通信能力 无(仅执行命令) 支持单向管道通信
结果获取 仅返回执行状态 可读取/写入数据
资源回收 自动回收 需手动调用pclose()
适用场景 简单命令执行 需要获取命令输出/输入

核心知识拓展:进程状态转换

进程常见状态

  • 运行态(R):正在CPU上执行或就绪等待CPU。
  • 阻塞态(S/D):等待资源(如IO、信号),无法执行。
  • 僵尸态(Z):子进程退出,父进程未回收资源。
  • 暂停态(T):收到SIGSTOP信号,暂停执行。

状态转换触发条件

  1. 运行态 → 阻塞态:调用wait()、等待IO、sleep()等。
  2. 阻塞态 → 就绪态:等待的资源就绪(如IO完成、信号到达)。
  3. 运行态 → 僵尸态:进程退出,资源未回收。
  4. 僵尸态 → 终止态:父进程调用wait/waitpid回收资源。

进程接口核心函数速查表

一、进程创建类

函数名 函数原型 参数说明 典型用法
fork() #include <sys/types.h><br>#include <unistd.h><br>pid_t fork(void); 无参数
返回值
- 父进程:返回子进程PID(非0整数)
- 子进程:返回0
- 失败:返回-1(并设置errno)
c// 创建子进程,区分父子逻辑pid_t pid = fork();if (pid > 0) { // 父进程逻辑:如等待子进程} else if (pid == 0) { // 子进程逻辑:如执行新程序} else { perror("fork error");}

二、进程回收类

1. wait()

函数名 函数原型 参数说明 典型用法
wait() #include <sys/types.h><br>#include <sys/wait.h><br>pid_t wait(int *wstatus); wstatus
- 非NULL:存储子进程退出状态(需配合状态宏解析)
- NULL:不存储退出状态
返回值
- 成功:返回被回收子进程的PID
- 失败:返回-1(无子女或出错)
c// 父进程阻塞等待子进程,不关心退出状态wait(NULL);<br>// 关心退出状态,解析正常退出/信号终止int stat;if (wait(&stat) > 0) { if (WIFEXITED(stat)) { printf("子进程正常退出,退出值:%d\n", WEXITSTATUS(stat)); } else if (WIFSIGNALED(stat)) { printf("子进程被信号杀死,信号值:%d\n", WTERMSIG(stat)); }}

2. waitpid()

函数名 函数原型 参数说明 典型用法
waitpid() #include <sys/types.h><br>#include <sys/wait.h><br>pid_t waitpid(pid_t pid, int *wstatus, int options); 1. pid:指定回收的子进程
- < -1:回收进程组ID为pid的任意子进程
- -1:回收任意子进程(等价于wait())
- 0:回收同进程组的任意子进程
- > 0:回收PID为pid的指定子进程
2. wstatus:同wait()
3. options:等待方式
- 0:阻塞等待
- WNOHANG:非阻塞等待(无僵尸子进程则返回0)
- WUNTRACED:子进程暂停时返回
- WCONTINUED:子进程被SIGCONT唤醒时返回
返回值
- 成功:返回被回收子进程PID(阻塞)或0(非阻塞无僵尸)
- 失败:返回-1
c// 1. 非阻塞等待任意子进程int stat;pid_t ret;while ((ret = waitpid(-1, &stat, WNOHANG)) == 0) { // 无僵尸子进程,可做其他逻辑 sleep(1);}if (ret > 0) { /* 解析退出状态 */ }<br>// 2. 回收指定PID(1234)的子进程waitpid(1234, &stat, 0);<br>// 3. 回收进程组123的任意子进程waitpid(-123, &stat, 0);

三、程序加载类(exec函数簇,更新版)

函数名 函数原型 参数说明 典型用法
execl() #include <unistd.h><br>int execl(const char *pathname, const char *arg, ...<br> /*, (char *) NULL */); 1. pathname:待执行程序的完整路径(如"/bin/ls"
2. arg, ...:可变参数列表,依次传入命令行参数(首个通常为程序名),** 末尾必须以(char *) NULL结束
返回值**:成功无返回,失败返回-1
c// 执行ls -l,指定完整路径execl("/bin/ls", "ls", "-l", (char *) NULL);perror("execl error"); // 仅失败时执行
execlp() #include <unistd.h><br>int execlp(const char *file, const char *arg, ...<br> /*, (char *) NULL */); 1. file:待执行程序的文件名(自动搜索PATH路径,如"ls"
2. arg, ...:同execl(),末尾需(char *) NULL
返回值:同execl()
c// 执行ls -a,无需完整路径(依赖PATH)execlp("ls", "ls", "-a", (char *) NULL);
execle() #include <unistd.h><br>int execle(const char *pathname, const char *arg, ...<br> /*, (char *) NULL, char *const envp[] */); 1. pathname:待执行程序的完整路径
2. arg, ...:可变参数列表,末尾先加(char *) NULL,再跟自定义环境变量数组envp(数组元素为"KEY=VALUE",末尾需NULL
返回值:同execl()
c// 自定义环境变量,执行echochar *envp[] = {"MY_ENV=test", NULL};execle("/bin/echo", "echo", "$MY_ENV", (char *) NULL, envp);
execv() #include <unistd.h><br>int execv(const char *pathname, char *const argv[]); 1. pathname:待执行程序的完整路径
2. argv:命令行参数数组(元素依次为程序名、参数,** 末尾元素必须为NULL
返回值**:同execl()
c// 数组传参执行ls -lchar *argv[] = {"ls", "-l", (char *) NULL};execv("/bin/ls", argv);
execvp() #include <unistd.h><br>int execvp(const char *file, char *const argv[]); 1. file:待执行程序的文件名(自动搜索PATH)
2. argv:同execv(),参数数组末尾需NULL
返回值:同execl()
c// 数组传参,自动搜索PATH执行pschar *argv[] = {"ps", "aux", (char *) NULL};execvp("ps", argv);
execvpe() #include <unistd.h><br>int execvpe(const char *file, char *const argv[], char *const envp[]); 1. file:待执行程序的文件名(自动搜索PATH)
2. argv:同execv(),参数数组末尾需NULL
3. envp:自定义环境变量数组(末尾需NULL
返回值:同execl()
c// 自定义环境变量,数组传参执行echochar *argv[] = {"echo", "$MY_ENV", (char *) NULL};char *envp[] = {"MY_ENV=test", NULL};execvpe("echo", argv, envp);

四、其他加载操作类

1. system()

函数名 函数原型 参数说明 典型用法
system() #include <stdlib.h><br>int system(const char *command); command:shell命令字符串(如"ls -l"
返回值
- 成功:返回命令退出状态(需用WEXITSTATUS解析)
- 失败:返回-1(创建子进程或执行shell出错)
c// 执行shell命令,打印结果system("ls -l /home");<br>// 解析命令退出状态int ret = system("echo hello");if (WIFEXITED(ret)) { printf("命令退出值:%d\n", WEXITSTATUS(ret));}

2. popen() & pclose()

函数名 函数原型 参数说明 典型用法
popen() #include <stdio.h><br>FILE *popen(const char *command, const char *type); 1. command:shell命令(如"ls -l"
2. type:管道方向
- "r":读取子进程输出(子进程stdout→管道→父进程)
- "w":向子进程输入(父进程→管道→子进程stdin)
返回值
- 成功:返回文件流指针(FILE*)
- 失败:返回NULL
c// 读取ls -l的输出FILE *fp = popen("ls -l", "r");if (fp == NULL) { perror("popen error"); }char buf[128];while (fgets(buf, sizeof(buf), fp) != NULL) { printf("输出:%s", buf); // 打印子进程输出}
pclose() #include <stdio.h><br>int pclose(FILE *stream); stream:popen()返回的文件流指针
返回值
- 成功:返回子进程退出状态(同system())
- 失败:返回-1
c// 配合popen()使用,避免僵尸进程FILE *fp = popen("ls -l", "r");// 读取操作...int ret = pclose(fp); // 必须调用!if (WIFEXITED(ret)) { printf("子进程退出值:%d\n", WEXITSTATUS(ret));}

五、辅助工具类(环境变量/进程组)

函数名 函数原型 参数说明 典型用法
getenv() #include <stdlib.h><br>char *getenv(const char *name); name:环境变量名(如"PATH"
返回值
- 成功:返回环境变量值的字符串指针
- 失败:返回NULL(变量不存在)
c// 获取PATH环境变量char *path = getenv("PATH");printf("PATH: %s\n", path);
setenv() #include <stdlib.h><br>int setenv(const char *name, const char *value, int overwrite); 1. name:环境变量名
2. value:环境变量值
3. overwrite
- 1:覆盖已存在的变量
- 0:不覆盖(变量存在则不修改)
返回值:成功0,失败-1
c// 设置MY_ENV变量为"hello"setenv("MY_ENV", "hello", 1);printf("MY_ENV: %s\n", getenv("MY_ENV"));
getpgid() #include <sys/types.h><br>pid_t getpgid(pid_t pid); pid:进程PID(0表示当前进程)
返回值
- 成功:返回进程组ID(PGID)
- 失败:返回-1
c// 获取当前进程的进程组IDpid_t pgid = getpgid(0);printf("当前进程组ID:%d\n", pgid);
posted @ 2025-11-21 08:17  Jaklin  阅读(48)  评论(0)    收藏  举报