Linux 进程创建与终止:fork 与 exit 底层逻辑解析
介绍 Linux 下进程创建与终止的核心机制。重点讲解 fork 函数的“一次调用,两次返回”特性及返回值含义,阐述写时拷贝(COW)优化原理。对比 exit 与_exit 函数的区别,分析正常终止与异常终止的场景及退出码规则。通过 C 语言代码示例演示父子进程协作、exec 替换及常见错误处理,帮助理解进程生命周期管理。

介绍 Linux 下进程创建与终止的核心机制。重点讲解 fork 函数的“一次调用,两次返回”特性及返回值含义,阐述写时拷贝(COW)优化原理。对比 exit 与_exit 函数的区别,分析正常终止与异常终止的场景及退出码规则。通过 C 语言代码示例演示父子进程协作、exec 替换及常见错误处理,帮助理解进程生命周期管理。

在 Linux 中,创建新进程的核心是 fork 函数。它能让一个已存在的进程(父进程)复制出一个全新的进程(子进程),两个进程拥有相同的代码段和数据段(初始状态),但能各自独立运行。
头文件和函数原型如下:
#include <unistd.h>
pid_t fork(void);
fork 函数的特点是一次调用,两次返回。父进程和子进程都会从 fork 函数返回,但返回值不同:
示例代码(fork_demo.c):
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void) {
pid_t pid;
printf("Before: pid is %d\n", getpid());
if ((pid = fork()) == -1) {
perror("fork() failed");
exit(1);
}
printf("After: pid is %d, fork return %d\n", getpid(), pid);
sleep(1);
return 0;
}
编译执行:
gcc fork_demo.c -o fork_demo
./fork_demo
执行结果中,'Before'只打印一次,'After'打印两次。这是因为 fork 之前只有父进程执行,fork 之后父子进程同时存在。
内核执行步骤包括:分配内存块和 PCB;拷贝数据结构;添加到进程列表;调度器开始调度。
返回值用于区分父子进程角色:
利用返回值可让父子进程执行不同代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void) {
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(1);
} else if (pid == 0) {
printf("我是子进程,PID:%d,父进程 PID:%d\n", getpid(), getppid());
sleep(3);
exit(0);
} else {
printf("我是父进程,PID:%d,创建的子进程 PID:%d\n", getpid(), pid);
sleep(5);
}
return 0;
}
Linux 采用**写时拷贝(Copy-On-Write, COW)**优化技术。父子进程初始共享内存资源,标记为'只读'。只有当一方试图修改数据时,内核才分配新内存并拷贝数据。
工作流程:
优势:提高创建效率,节省内存资源。
验证代码(cow_demo.c):
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int g_val = 10;
int main(void) {
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(1);
} else if (pid == 0) {
printf("子进程:初始 g_val = %d,地址 = %p\n", g_val, &g_val);
g_val = 20;
printf("子进程:修改后 g_val = %d,地址 = %p\n", g_val, &g_val);
} else {
sleep(1);
printf("父进程:g_val = %d,地址 = %p\n", g_val, &g_val);
}
return 0;
}
用于并发处理场景。父进程监听任务,子进程处理任务。
示例代码(server_fork_demo.c):
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#define PORT 8080
#define BACKLOG 5
void handle_client(int client_fd) {
char buf[1024] = {0};
ssize_t n = read(client_fd, buf, sizeof(buf));
if (n > 0) {
printf("子进程(PID:%d)收到客户端数据:%s\n", getpid(), buf);
write(client_fd, "收到你的消息啦!", strlen("收到你的消息啦!"));
}
close(client_fd);
exit(0);
}
int main(void) {
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {
perror("socket failed");
exit(1);
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
perror("bind failed");
exit(1);
}
if (listen(listen_fd, BACKLOG) == -1) {
perror("listen failed");
exit(1);
}
printf("父进程(PID:%d)监听端口 %d...\n", getpid(), PORT);
while (1) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd == -1) {
perror("accept failed");
continue;
}
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
close(client_fd);
continue;
} else if (pid == 0) {
close(listen_fd);
handle_client(client_fd);
} else {
close(client_fd);
}
}
close(listen_fd);
return 0;
}
结合 exec 函数族替换代码段,执行全新程序。这是 shell 命令执行的核心原理。
示例代码(exec_fork_demo.c):
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void) {
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(1);
} else if (pid == 0) {
printf("子进程(PID:%d)执行 ls 命令...\n", getpid());
char *const argv[] = {"ls", "-l", NULL};
execvp("ls", argv);
perror("execvp failed");
exit(1);
} else {
wait(NULL);
printf("父进程(PID:%d):子进程执行完成\n", getpid());
}
return 0;
}
失败时可调用 perror 打印错误信息。
exit 与_exit 区别示例:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void) {
printf("exit 函数:");
exit(0);
// printf("_exit 函数:");
// _exit(0);
}
异常终止退出码通常为 128 + 信号编号。
退出码范围 0-255。
常见退出码含义:
| 退出码 | 含义 |
|---|---|
| 0 | 成功 |
| 1 | 通用错误 |
| 127 | 未找到命令 |
| 130 | 被 SIGINT 终止 |
| 137 | 被 SIGKILL 终止 |
可通过 $? 获取上一个进程退出码。
本文介绍了 Linux 下进程创建与终止的核心机制。重点讲解了 fork 函数的特性及返回值含义,阐述了写时拷贝优化原理。对比了 exit 与_exit 函数的区别,分析了正常终止与异常终止的场景及退出码规则。通过 C 语言代码示例演示了父子进程协作、exec 替换及常见错误处理,帮助理解进程生命周期管理。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online