本文最后更新于 33 天前,其中的信息可能已经有所发展或是发生改变。

进程的基本概念
进程是程序的执行实例,是动态的、运行中的实体。程序是静态的概念,进程是动态的概念。每个进程都有一个唯一的非负整数标识符,称为PID(Process ID)。getpid()获取自身进程ID,getppid()获取父进程ID。
fork()——创建子进程
函数原型
#include <unistd.h>
#include <sys/types.h>
pid_t fork(void);
返回值
fork()创建一个新进程(子进程),子进程几乎是父进程的复制版本,获得父进程的栈、数据段、堆和执行文本段的拷贝。
- 父进程中返回子进程的PID
- 子进程中返回0
- 失败返回-1
代码案例:fork使用
/**
* fork_demo.c - 演示fork()创建子进程
* 编译:gcc fork_demo.c -o fork_demo
* 运行:./fork_demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int global_var = 100; // 全局变量
int main() {
pid_t pid;
int local_var = 50; // 局部变量
printf("Before fork: PID=%d, global=%d, local=%d\n", getpid(), global_var, local_var);
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(EXIT_FAILURE);
} else if (pid == 0) {
// 子进程
printf("\n[Child] PID=%d, PPID=%d\n", getpid(), getppid());
printf("[Child] Before change: global=%d, local=%d\n", global_var, local_var);
global_var = 200;
local_var = 100;
printf("[Child] After change: global=%d, local=%d\n", global_var, local_var);
exit(0);
} else {
// 父进程
wait(NULL); // 等待子进程结束
printf("\n[Parent] PID=%d, Child PID=%d\n", getpid(), pid);
printf("[Parent] After child terminated: global=%d, local=%d\n",
global_var, local_var);
printf("[Parent] Note: changes in child do NOT affect parent\n");
}
return 0;
}
运行结果如下:
Before fork: PID=1234, global=100, local=50
[Child] PID=1235, PPID=1234
[Child] Before change: global=100, local=50
[Child] After change: global=200, local=100
[Parent] PID=1234, Child PID=1235
[Parent] After child terminated: global=100, local=50
[Parent] Note: changes in child do NOT affect parent
- 父子进程是两个完全独立的进程
- 子进程会拷贝父进程的所有数据(全局变量、局部变量)
- 子进程修改变量,父进程完全不受影响
- 它们有不同的 PID
exec函数族——执行新程序
在当前进程里,替换成另一个程序来运行。
- 进程 PID 不变
- 原来的代码、数据全部被清空替换
- 运行新的程序
- 执行成功不返回,直接跑新程序
使用exec函数的原因:Linux 所有命令(ls、ps、ifconfig、./a.out)都是由父进程fork()后,再exec()运行的!
函数原型
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[]);
/*
l = list:参数一个一个写
v = vector:参数用数组
p = path:自动去系统 PATH 找命令
e = environment:带环境变量
*/
execlp("ls","ls","-l",NULL);
char *agrv[] = {"ls","-l",NULL};
execvp(argv[0],argv);
进程调用exec函数族执行某个程序时,进程当前内容被指定的程序替换,从而实现让父子进程执行不同的程序。
代码案例:fork+exec组合
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if (pid < 0)
{
perror("fork");
exit(EXIT_FAILURE);
}
else if (pid == 0)
{
// 子进程:执行Ping命令
printf("正在执行子进程:%d\n", getpid());
// 使用execvp(v:数组,p:寻找PATH)
char *args[] = {"ping", "-c", "10", "www.baidu.com", NULL};
execvp(args[0], args);
// 如果执行成功,以下代码不会执行
perror("exec");
exit(EXIT_FAILURE);
}
else
{
wait(NULL);
printf("子进程%d执行完毕,父进程%d退出\n", pid, getpid());
}
return 0;
}
exit()、_exit()和wait()/waitpid()
函数原型
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
#include <sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
功能说明
- exit():标准C库函数,终止进程时会刷新所有stdio缓冲区
- _exit():系统调用,立即终止进程,不刷新缓冲区
- wait():阻塞等待任意子进程终止,回收子进程资源,防止产生僵尸进程
- waitpid():可指定等待的特定子进程,提供更精细的控制
守护进程
守护进程(Daemon)是在后台运行的进程,通常脱离终端控制,独立于用户登录会话。
- 后台运行
- 脱离终端控制(关闭终端不退出)
- 父进程是 1 号进程(init/systemd)
- 日志输出到文件 /syslog
创建守护进程的方法(代码):
/**
* daemon_demo.c - 创建守护进程并定期写日志
* 编译:gcc daemon_demo.c -o daemon_demo
* 运行:./daemon_demo
* 查看:tail -f /tmp/daemon.log
* 停止:killall daemon_demo
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#define LOG_FILE "/tmp/daemon.log"
void write_log(const char *msg) {
FILE *fp;
time_t now;
struct tm *tm_info;
char timestamp[64];
fp = fopen(LOG_FILE, "a");
if (fp == NULL) return;
time(&now);
tm_info = localtime(&now);
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", tm_info);
fprintf(fp, "[%s] %s\n", timestamp, msg);
fclose(fp);
}
// 信号处理函数
void signal_handler(int sig) {
if (sig == SIGTERM) {
write_log("Daemon received SIGTERM, shutting down");
exit(0);
}
}
int main() {
pid_t pid;
// 第1步:fork,使父进程退出
pid = fork();
if (pid < 0) {
perror("fork failed");
exit(EXIT_FAILURE);
}
if (pid > 0) {
// 父进程退出
exit(0);
}
// 第2步:setsid()创建新会话,成为会话组长
if (setsid() < 0) {
perror("setsid failed");
exit(EXIT_FAILURE);
}
// 第3步:忽略SIGHUP信号
signal(SIGHUP, SIG_IGN);
// 第4步:二次fork(可选,确保不获取终端)
pid = fork();
if (pid < 0) {
perror("second fork failed");
exit(EXIT_FAILURE);
}
if (pid > 0) {
exit(0);
}
// 第5步:更改工作目录到根目录
chdir("/");
// 第6步:清空文件权限掩码
umask(0);
// 第7步:关闭不需要的文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 将标准输入/输出/错误重定向到/dev/null
open("/dev/null", O_RDWR); // stdin
dup(0); // stdout
dup(0); // stderr
// 第8步:设置信号处理
signal(SIGTERM, signal_handler);
// 记录启动日志
write_log("Daemon started");
// 守护进程主循环
int count = 0;
while (1) {
sleep(30); // 每30秒执行一次任务
count++;
char msg[128];
snprintf(msg, sizeof(msg), "Daemon heartbeat #%d", count);
write_log(msg);
}
return 0;
}
本篇文章到此结尾了,感谢阅读!



