进程是操作系统资源分配的基本单位。本文将详细介绍 Linux 下进程的创建、终止、管理以及进程间通信方式,帮助你掌握多进程编程技能。
进程基础概念
每个进程都有唯一的 PID(进程 ID),进程之间相互独立,通过内核调度执行。进程的主要状态包括:
- 运行态 (R) - 正在 CPU 上执行或就绪
- 睡眠态 (S/D) - 等待事件或资源
- 停止态 (T) - 暂停执行
- 僵死态 (Z) - 进程已终止但资源未回收
fork 创建进程
fork 是创建新进程的主要方式,调用一次返回两次:
fork_demo.c
#include#include #include #include int main(void) { pid_t pid = fork(); if (pid < 0) { perror("fork failed"); exit(1); } if (pid == 0) { /* 子进程 */ printf("Child: PID=%d, Parent PID=%d\n", getpid(), getppid()); sleep(2); exit(42); /* 子进程退出码 */ } else { /* 父进程 */ printf("Parent: forked child PID=%d\n", pid); int status; pid_t terminated_pid = wait(&status); /* 等待子进程 */ if (WIFEXITED(status)) { printf("Child %d exited with code %d\n", terminated_pid, WEXITSTATUS(status)); } } return 0; }
vfork 与 fork 的区别
vfork 在早期用于更高效的进程创建,但有一些特殊行为:
- 子进程共享父进程的内存地址空间(直到 exec)
- 子进程先运行,父进程等待子进程调用 exec 或 exit
- 现代系统推荐使用 fork + copy-on-write
vfork_demo.c
#include#include #include int main(void) { int x = 10; pid_t pid = vfork(); if (pid == 0) { x = 20; /* 修改会影响父进程 */ printf("Child: x=%d\n", x); _exit(0); /* 必须用 _exit */ } printf("Parent: x=%d\n", x); return 0; }
exec 系列函数
exec 用新程序替换当前进程的映像:
exec_demo.c
#include#include #include int main(void) { pid_t pid = fork(); if (pid == 0) { /* 子进程执行新程序 */ execlp("ls", "ls", "-l", NULL); /* 如果 execlp 返回,说明出错 */ perror("execlp failed"); exit(1); } else { wait(NULL); /* 等待子进程 */ printf("Child finished\n"); } return 0; }
wait 系列函数
父进程需要等待子进程结束以回收资源:
wait_demo.c
#include#include #include #include int main(void) { for (int i = 0; i < 3; i++) { if (fork() == 0) { printf("Child %d (PID=%d) exiting\n", i, getpid()); exit(i); } } /* 等待所有子进程 */ int count = 0; while (wait(NULL) > 0) { count++; } printf("Waited for %d children\n", count); return 0; }
进程间通信 (IPC)
常见进程间通信方式:
- 管道 (Pipe) - 血缘进程间通信
- FIFO (命名管道) - 非血缘进程间通信
- 消息队列 - 消息传递
- 共享内存 - 最高效的 IPC
- 信号量 - 进程同步
pipe_demo.c
#include#include #include int main(void) { int pipefd[2]; pipe(pipefd); /* 创建管道 */ if (fork() == 0) { close(pipefd[0]); /* 关闭读端 */ char msg[] = "Hello from child!"; write(pipefd[1], msg, sizeof(msg)); close(pipefd[1]); exit(0); } else { close(pipefd[1]); /* 关闭写端 */ char buf[100]; int n = read(pipefd[0], buf, sizeof(buf)); write(STDOUT_FILENO, buf, n); close(pipefd[0]); wait(NULL); } return 0; }
进程管理最佳实践
- 避免僵尸进程 - 父进程必须调用 wait/waitpid 回收子进程
- 避免孤儿进程 - 父进程退出时确保有 init 收养子进程
- 正确使用 exit vs _exit - _exit 不刷新缓冲区
- 信号处理 - 处理 SIGCHLD 避免过多僵尸
- 资源限制 - 使用 setrlimit 控制子进程资源
总结
进程是 Linux 系统的核心概念。fork 用于创建进程,exec 用于执行新程序,wait 用于回收子进程。掌握这些基础 API,才能完成多进程程序的开发。