进程控制函数整理笔记
一、 进程回收函数
wait() - 阻塞回收
#include <sys/wait.h>
pid_t wait(int *status);
功能:阻塞等待任意子进程退出并回收状态(只能父进程回收子进程)
参数:
-
status:进程退出时的状态-
不关心退出状态:
NULL -
需要回收状态:传变量地址
-
返回值:
-
成功:回收的子进程PID
-
失败:-1
状态宏使用:
int status;
pid_t pid = wait(&status);
if (WIFEXITED(status)) { // 是否正常结束
int exit_code = WEXITSTATUS(status); // 获取退出码
printf("子进程正常退出,退出码: %d\n", exit_code);
}
else if (WIFSIGNALED(status)) { // 是否被信号终止
int sig_num = WTERMSIG(status); // 获取信号编号
printf("子进程被信号终止,信号: %d\n", sig_num);
}
waitpid() - 精确控制回收
pid_t waitpid(pid_t pid, int *status, int options);
参数:
-
pid:-
>0:指定回收特定PID的子进程 -
-1:回收任意子进程(等价于wait()) -
0:回收同进程组的子进程 -
<-1:回收指定进程组ID的绝对值
-
-
status:同wait() -
options:-
0:阻塞模式(默认) -
WNOHANG:非阻塞模式
-
返回值:
-
成功:回收的子进程PID
-
0:WNOHANG模式下,子进程未退出 -
-1:错误
二、 非阻塞回收示例
方式1:循环检查
// 非阻塞回收所有子进程
int status;
pid_t pid;
while (1) {
pid = waitpid(-1, &status, WNOHANG);
if (pid > 0) {
// 成功回收一个子进程
printf("回收子进程 %d\n", pid);
if (WIFEXITED(status)) {
printf("退出码: %d\n", WEXITSTATUS(status));
}
}
else if (pid == 0) {
// 没有子进程退出,可以做其他事情
printf("没有子进程退出,继续工作...\n");
sleep(1);
}
else if (pid == -1) {
// 错误或没有子进程了
printf("所有子进程已回收完毕\n");
break;
}
}
方式2:信号驱动(SIGCHLD)
#include <signal.h>
void sigchld_handler(int sig) {
int status;
pid_t pid;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
printf("子进程 %d 已结束\n", pid);
}
}
int main() {
signal(SIGCHLD, sigchld_handler);
// ... 创建子进程
}
三、 exec函数族
内存变化
执行exec前 :原进程有自己的代码段、数据段、堆栈
执行exec后:原进程的代码段被新程序替换,PID不变,其他资源可能变化
函数对比表
| 函数 | 路径指定 | 参数传递 | PATH查找 |
|---|---|---|---|
execl() |
完整路径 | 参数列表 | 否 |
execlp() |
文件名 | 参数列表 | 是 |
execv() |
完整路径 | 参数数组 | 否 |
execvp() |
文件名 | 参数数组 | 是 |
函数详解
execl() - 参数列表形式
int execl(const char *path, const char *arg, ..., NULL);
-
path:完整路径(如"/usr/bin/ls") -
arg:参数列表,以NULL结束
execl("/usr/bin/ls", "ls", "-l", "/home", NULL);
execlp() - 自动PATH查找
int execlp(const char *file, const char *arg, ..., NULL);
file:程序名,自动在PATH中查找
execlp("ls", "ls", "-l", "/home", NULL);
execv() - 参数数组形式
int execv(const char *path, char *const argv[]);
argv:参数数组,最后一项必须是NULL
char *args[] = {"ls", "-l", "/home", NULL};
execv("/usr/bin/ls", args);
execvp() - 自动PATH查找+数组参数
int execvp(const char *file, char *const argv[]);
char *args[] = {"ls", "-l", "/home", NULL};
execvp("ls", args);
调用自己的程序
// 假设当前目录有程序 myapp
execl("./myapp", "myapp", "arg1", "arg2", NULL); // 必须带路径
execlp("./myapp", "myapp", "arg1", "arg2", NULL); // 带路径也可
execv("./myapp", args); // 必须带路径
execvp("./myapp", args); // 带路径也可
四、 fork + exec 配合使用
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程执行新程序
execlp("ls", "ls", "-l", NULL);
// 如果exec失败才会执行到这里
perror("execlp failed");
exit(1);
}
else if (pid > 0) {
// 父进程等待子进程
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("子进程退出码: %d\n", WEXITSTATUS(status));
}
}
return 0;
}
五、 system()函数
#include <stdlib.h>
int system(const char *command);
功能 :执行shell命令(内部实现:fork() + exec())
限制:
-
不能执行需要修改父进程状态的命令(如
cd,export等) -
适合执行信息输出、文件操作等命令
示例:
system("ls -l"); // 列出文件
system("echo hello"); // 输出信息
system("cp file1 file2"); // 复制文件
// ❌ 这些无效(不会影响父进程):
system("cd /tmp"); // 只在子shell中切换
system("export VAR=1"); // 只在子shell中设置
六、 工作目录函数
getcwd() - 获取当前工作目录
#include <unistd.h>
char *getcwd(char *buf, size_t size);
参数:
-
buf:存储路径的字符数组 -
size:数组最大长度
返回值:
-
成功:指向
buf的指针 -
失败:
NULL
示例:
char cwd[1024];
if (getcwd(cwd, sizeof(cwd)) != NULL) {
printf("当前目录: %s\n", cwd);
} else {
perror("getcwd失败");
}
chdir() - 改变工作目录
int chdir(const char *path);
参数 :path - 要切换的路径
返回值:
-
成功:0
-
失败:-1
示例:
if (chdir("/tmp") != 0) {
perror("chdir失败");
} else {
printf("成功切换到 /tmp\n");
}
七、 关键点总结
-
wait() vs waitpid():
-
wait():简单但只能阻塞回收 -
waitpid():功能更强,支持非阻塞和指定进程
-
-
exec函数选择:
-
知道完整路径:用
execl()或execv() -
想用PATH查找:用
execlp()或execvp() -
参数动态:用
execv()或execvp() -
参数固定:用
execl()或execlp()
-
-
非阻塞回收:
-
用
WNOHANG选项 -
返回值需要特殊处理
-
通常放在循环中
-
-
cd命令特殊性:
-
必须在当前进程执行
chdir() -
不能用
fork()+exec()实现 -
不能用
system()实现
-
-
僵尸进程避免:
-
及时用
wait()或waitpid()回收 -
对于后台进程,可以用信号处理
SIGCHLD
-