进程的基础接口
最底下速查表
进程基础接口
进程的创建
核心接口: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信号,暂停执行。
状态转换触发条件
- 运行态 → 阻塞态:调用wait()、等待IO、sleep()等。
- 阻塞态 → 就绪态:等待的资源就绪(如IO完成、信号到达)。
- 运行态 → 僵尸态:进程退出,资源未回收。
- 僵尸态 → 终止态:父进程调用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(),参数数组末尾需 NULL3. 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); |

浙公网安备 33010602011771号