进程管理
一、进程基础
pid_t pid = getpid();
pid_t ppid = getppid();
uid_t uid = getuid(), euid = geteuid();
gid_t gid = getgid(), egid = getegid();
进程状态(D 不可中断睡眠 / R 运行 / S 可中断睡眠 / T 停止 / Z 僵尸 / X 死亡 / I 空闲)
查看:ps -ef、top、/proc/<pid>/status
二、fork / exec / wait
1. fork
#include <sys/wait.h>
#include <unistd.h>
pid_t pid = fork();
if (pid < 0) { perror("fork"); }
else if (pid == 0) {
// 子进程
printf("child pid=%d\n", getpid());
execlp("ls", "ls", "-l", NULL); // 替换为 ls
_exit(127);
} else {
// 父进程
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status)) {
printf("exit code = %d\n", WEXITSTATUS(status));
} else if (WIFSIGNALED(status)) {
printf("killed by sig %d\n", WTERMSIG(status));
}
}
2. exec 系列
execl(path, arg0, arg1, ..., NULL) // 列表
execv(path, argv) // 数组
execlp(file, arg0, ..., NULL) // 找 PATH
execvp(file, argv) // 找 PATH
execve(path, argv, envp) // 系统调用封装
execle(path, arg0, ..., NULL, envp)
注意:exec 成功则不返回;失败返回 -1。
3. vfork(极少用)
- 与父进程共享地址空间
- 子进程必须立刻 exec 或 _exit
- 历史用法,现代用
clone(CLONE_VM)替代
4. wait / waitpid
pid_t wait(int *status); // 等任意子进程
pid_t waitpid(pid_t pid, int *status, int options); // 指定 pid
// options: WNOHANG 不阻塞、WUNTRACED 等停止态、WCONTINUED 等继续态
5. SIGCHLD
- 子进程退出时给父进程发 SIGCHLD
- 默认忽略,但僵尸进程要靠 wait 回收
- 推荐忽略 SIGCHLD 并用 SIGCHLD handler 内 waitpid(-1, ..., WNOHANG)
三、退出
exit(0); // 退出前调用 atexit 注册函数、刷新 stdio、调用 _exit
_exit(0); // 立即退出(fork 后子进程常用)
atexit(handler); // 注册退出处理
四、守护进程
1. 经典五步
1. fork + 父退出 // 让 shell 以为任务完成
2. setsid() // 创建新会话,脱离控制终端
3. 再次 fork(可选) // 防止重新获得控制终端
4. chdir("/") // 防止占用挂载点
5. umask(0)
6. close(0,1,2) → 重定向到 /dev/null / 日志文件
2. systemd 时代
- 现代:写
.serviceunit 文件,由 systemd 管理 - 见
09-daemon.md
五、进程资源
1. 优先级
- nice 值:-20 ~ 19(越低优先级越高,root 才能给负值)
nice()/setpriority()- 实时优先级(root):
sched_setscheduler(SCHED_FIFO/SCHED_RR)
2. 资源限制
struct rlimit lim = { .rlim_cur = 64*1024*1024, .rlim_max = RLIM_INFINITY };
setrlimit(RLIMIT_AS, &lim);
| 资源 | 含义 |
|---|---|
| RLIMIT_CPU | CPU 秒 |
| RLIMIT_FSIZE | 文件大小 |
| RLIMIT_NOFILE | fd 数量 |
| RLIMIT_AS | 虚拟内存 |
| RLIMIT_NPROC | 进程数 |
| RLIMIT_CORE | core 文件大小 |
3. Cgroup(嵌入式关注)
- 内核特性,资源控制组
- 用于限制 CPU、内存、IO
- systemd、docker 都依赖 cgroup
六、进程间关系
- 进程组:用
setpgid,常用于作业控制 - 会话:用
setsid,包含多个进程组 - 控制终端:会话首领关联
tcsetpgrp(fd, pgrp)切换前台进程组
七、代码模板
1. 安全的 fork + exec
static int run_cmd(char *const argv[]) {
pid_t pid = fork();
if (pid < 0) return -1;
if (pid == 0) {
execvp(argv[0], argv);
_exit(127);
}
int status;
waitpid(pid, &status, 0);
return WIFEXITED(status) ? WEXITSTATUS(status) : -1;
}
2. 多客户端服务(fork 模型)
while (1) {
int cfd = accept(lfd, NULL, NULL);
if (fork() == 0) {
close(lfd);
handle(cfd);
close(cfd);
_exit(0);
}
close(cfd);
}
注意:fork 模型资源开销大、信号复杂,现代多用单进程多线程 / epoll。