C 语言进程控制
C语言进程控制[编辑 | 编辑源代码]
进程控制是C语言系统编程中的核心概念之一,它允许程序创建、管理和终止进程。在多任务操作系统中,进程是程序执行的基本单位,掌握进程控制技术对于开发高效、稳定的系统级应用至关重要。
进程基础[编辑 | 编辑源代码]
在Unix/Linux系统中,进程(Process)是一个正在执行的程序的实例。每个进程都有:
- 独立的地址空间
- 唯一的进程ID(PID)
- 自己的资源分配(文件描述符、内存等)
进程状态[编辑 | 编辑源代码]
进程在其生命周期中会经历多种状态:
进程创建[编辑 | 编辑源代码]
C语言通过`fork()`系统调用创建新进程。
fork()系统调用[编辑 | 编辑源代码]
`fork()`会创建一个与父进程几乎完全相同的子进程,区别仅在于:
- 返回值不同
- 进程ID不同
- 父进程ID不同
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid < 0) {
// fork失败
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程代码
printf("这是子进程,PID=%d\n", getpid());
} else {
// 父进程代码
printf("这是父进程,PID=%d,子进程PID=%d\n", getpid(), pid);
}
return 0;
}
输出示例:
这是父进程,PID=1234,子进程PID=1235 这是子进程,PID=1235
注意:父进程和子进程的执行顺序是不确定的,由系统调度决定。
进程终止[编辑 | 编辑源代码]
进程可以通过以下方式终止: 1. 正常退出(`exit()`或`_exit()`) 2. 异常终止(收到信号)
exit() vs _exit()[编辑 | 编辑源代码]
| 函数 | 是否刷新缓冲区 | 是否调用atexit注册的函数 | |------------|----------------|--------------------------| | exit() | 是 | 是 | | _exit() | 否 | 否 |
#include <stdlib.h>
#include <unistd.h>
void cleanup() {
printf("执行清理函数\n");
}
int main() {
atexit(cleanup);
printf("使用exit():\n");
exit(0); // 会输出"执行清理函数"
// 以下代码不会执行
printf("使用_exit():\n");
_exit(0); // 不会输出"执行清理函数"
}
进程等待[编辑 | 编辑源代码]
父进程可以使用`wait()`或`waitpid()`等待子进程结束,并获取其退出状态。
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
printf("子进程开始\n");
sleep(2);
printf("子进程结束\n");
exit(42); // 子进程返回42
} else {
// 父进程
int status;
printf("父进程等待子进程...\n");
wait(&status);
if (WIFEXITED(status)) {
printf("子进程正常退出,返回值: %d\n", WEXITSTATUS(status));
} else {
printf("子进程异常终止\n");
}
}
return 0;
}
输出示例:
父进程等待子进程... 子进程开始 子进程结束 子进程正常退出,返回值: 42
进程替换[编辑 | 编辑源代码]
`exec`系列函数用于将当前进程替换为一个新程序:
#include <stdio.h>
#include <unistd.h>
int main() {
printf("准备执行ls命令...\n");
char *args[] = {"ls", "-l", NULL};
execvp("ls", args);
// 如果exec成功,以下代码不会执行
perror("exec失败");
return 1;
}
输出示例:
准备执行ls命令... total 24 -rwxr-xr-x 1 user group 12345 Jan 1 10:00 a.out -rw-r--r-- 1 user group 678 Jan 1 09:00 example.c
实际应用案例[编辑 | 编辑源代码]
案例1:简单的shell实现[编辑 | 编辑源代码]
以下代码展示了一个简化版shell如何利用进程控制:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#define MAX_LINE 80
int main() {
char line[MAX_LINE];
char *args[MAX_LINE/2 + 1];
while (1) {
printf("myshell> ");
fflush(stdout);
if (!fgets(line, MAX_LINE, stdin)) {
break; // 读取失败或EOF
}
// 解析命令
int i = 0;
args[i] = strtok(line, " \t\n");
while (args[i] != NULL && i < MAX_LINE/2) {
args[++i] = strtok(NULL, " \t\n");
}
if (i == 0) continue; // 空命令
// 内置命令处理
if (strcmp(args[0], "exit") == 0) {
break;
}
// 创建子进程执行命令
pid_t pid = fork();
if (pid == 0) {
// 子进程
execvp(args[0], args);
perror("exec失败");
exit(1);
} else if (pid > 0) {
// 父进程等待子进程
wait(NULL);
} else {
perror("fork失败");
}
}
return 0;
}
案例2:并行任务处理[编辑 | 编辑源代码]
利用`fork()`创建多个子进程并行处理任务:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define NUM_WORKERS 4
void worker(int id) {
printf("Worker %d (PID=%d) 开始工作\n", id, getpid());
sleep(id * 2); // 模拟工作
printf("Worker %d 完成工作\n", id);
exit(0);
}
int main() {
printf("主进程 (PID=%d) 启动\n", getpid());
for (int i = 0; i < NUM_WORKERS; i++) {
pid_t pid = fork();
if (pid == 0) {
worker(i);
exit(0); // 确保子进程不会继续循环
} else if (pid < 0) {
perror("fork失败");
exit(1);
}
}
// 等待所有子进程结束
for (int i = 0; i < NUM_WORKERS; i++) {
wait(NULL);
}
printf("所有工作完成\n");
return 0;
}
进程间通信(IPC)简介[编辑 | 编辑源代码]
进程间通信是进程控制的重要扩展,常见方法包括: 1. 管道(pipe) 2. 共享内存 3. 消息队列 4. 信号量 5. 套接字(socket)
简单管道示例[编辑 | 编辑源代码]
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
int fd[2];
if (pipe(fd) == -1) {
perror("pipe失败");
exit(1);
}
pid_t pid = fork();
if (pid == 0) {
// 子进程 - 写入数据
close(fd[0]); // 关闭读取端
char msg[] = "Hello from child!";
write(fd[1], msg, sizeof(msg));
close(fd[1]);
exit(0);
} else {
// 父进程 - 读取数据
close(fd[1]); // 关闭写入端
char buf[100];
read(fd[0], buf, sizeof(buf));
printf("父进程收到: %s\n", buf);
close(fd[0]);
wait(NULL);
}
return 0;
}
安全注意事项[编辑 | 编辑源代码]
1. 避免僵尸进程:父进程必须等待子进程或设置SIGCHLD处理 2. 防止孤儿进程:子进程未结束前父进程不应退出 3. 资源释放:确保所有文件描述符被正确关闭 4. 信号处理:正确处理可能中断系统调用的信号
总结[编辑 | 编辑源代码]
C语言的进程控制提供了强大的系统编程能力,包括:
- 使用`fork()`创建新进程
- 使用`exec`系列函数替换进程映像
- 使用`wait()`/`waitpid()`同步进程
- 使用`exit()`/_exit()`终止进程
掌握这些基础知识后,可以进一步学习更高级的进程间通信技术和多线程编程。