在 Linux 系统中,进程是程序的一次动态执行过程。程序是静态的可执行文件,而进程是程序运行时的实例,系统会为其分配内存、CPU 时间片等资源。例如,输入 ls 命令时,系统创建进程执行 ls 程序来显示文件列表。进程是资源分配的基本单位,理解进程对掌握 Linux 系统运行机制至关重要。
在 Linux 中,可使用 ps 命令查看系统中当前运行的进程。下面是一些常用的 ps 命令参数组合:
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 00:00 ? 00:00:01 /sbin/init splash
root 2 0 0 00:00 ? 00:00:00 [kthreadd]
在 Linux 中,除了 ps -ef,还可使用以下命令查看进程:
TypeScript
取消自动换行复制
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 24176 4360 ? Ss 00:00 0:01 /sbin/init splash
在 C 语言里,fork 函数是创建新进程的关键函数。它的作用是复制当前进程,生成一个子进程,原进程则成为父进程。fork 函数的原型如下:
#include
pid_t fork(void);
#include
#include
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork 失败");
} else if (pid == 0) {
// 子进程
printf("我是子进程,我的 PID 是 %d,父进程的 PID 是 %d\n", getpid(), getppid());
} else {
// 父进程
printf("我是父进程,我的 PID 是 %d,子进程的 PID 是 %d\n", getpid(), pid);
}
return 0;
}
僵尸进程的形成需要满足以下三个条件:
当子进程结束时,它会向父进程发送一个 SIGCHLD 信号,但如果父进程没有调用 wait 或 waitpid 函数来回收子进程的资源,子进程就会变成僵尸进程。
wait 函数的作用是等待任意一个子进程结束,并回收其资源。其原型如下:
#include
#include
pid_t wait(int *status);
#include
#include
#include
#include
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork 失败");
} else if (pid == 0) {
// 子进程
printf("子进程开始执行,PID 是 %d\n", getpid());
sleep(2);
printf("子进程结束\n");
} else {
// 父进程
int status;
pid_t child_pid = wait(&status);
printf("父进程回收了 PID 为 %d 的子进程\n", child_pid);
}
return 0;
}
可通过 signal 函数捕获 SIGCHLD 信号,并在信号处理函数中调用 wait 或 waitpid 函数。
#include
#include
#include
#include
#include
void sigchld_handler(int signo) {
pid_t pid;
int status;
while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
printf("回收了 PID 为 %d 的子进程\n", pid);
}
}
int main() {
signal(SIGCHLD, sigchld_handler);
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork 失败");
} else if (pid == 0) {
// 子进程
printf("子进程开始执行,PID 是 %d\n", getpid());
sleep(2);
printf("子进程结束\n");
} else {
// 父进程
printf("父进程继续执行\n");
sleep(5);
}
return 0;
}
孤儿进程的形成需要满足以下两个条件:
当父进程结束后,子进程就会变成孤儿进程,此时它会被进程 ID 为 1 的 init 进程接管。
init 进程会负责回收孤儿进程的资源,确保系统资源不会被浪费。
#include
#include
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork 失败");
} else if (pid == 0) {
// 子进程
printf("子进程开始执行,父进程 PID 是 %d\n", getppid());
sleep(5);
printf("子进程继续执行,父进程 PID 是 %d\n", getppid());
} else {
// 父进程
printf("父进程结束\n");
}
return 0;
}
在这个示例中,父进程会先结束,子进程在睡眠 5 秒后,会发现自己的父进程 ID 变成了 1。
守护进程是一种在后台持续运行的进程,通常在系统启动时就开始运行,并且不受用户登录和注销的影响。以下是创建守护进程的详细步骤:
#include
#include
#include
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork 失败");
exit(EXIT_FAILURE);
}
if (pid > 0) {
// 父进程退出
exit(EXIT_SUCCESS);
}
// 子进程继续执行
// 后续步骤...
return 0;
}
#include
#include
#include
#include
#include
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork 失败");
exit(EXIT_FAILURE);
}
if (pid > 0) {
// 父进程退出
exit(EXIT_SUCCESS);
}
// 子进程创建新会话
pid_t sid = setsid();
if (sid < 0) {
perror("setsid 失败");
exit(EXIT_FAILURE);
}
// 后续步骤...
return 0;
}
setsid 函数的作用是创建一个新的会话,使子进程成为新会话的首进程,并且脱离原有的控制终端。
#include
#include
#include
#include
#include
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork 失败");
exit(EXIT_FAILURE);
}
if (pid > 0) {
// 父进程退出
exit(EXIT_SUCCESS);
}
// 子进程创建新会话
pid_t sid = setsid();
if (sid < 0) {
perror("setsid 失败");
exit(EXIT_FAILURE);
}
// 改变工作目录
if (chdir("/") < 0) {
perror("chdir 失败");
exit(EXIT_FAILURE);
}
// 后续步骤...
return 0;
}
chdir 函数用于将工作目录切换到根目录,避免工作目录被卸载导致进程无法正常工作。
#include
#include
#include
#include
#include
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork 失败");
exit(EXIT_FAILURE);
}
if (pid > 0) {
// 父进程退出
exit(EXIT_SUCCESS);
}
// 子进程创建新会话
pid_t sid = setsid();
if (sid < 0) {
perror("setsid 失败");
exit(EXIT_FAILURE);
}
// 改变工作目录
if (chdir("/") < 0) {
perror("chdir 失败");
exit(EXIT_FAILURE);
}
// 设置文件权限掩码
umask(0);
// 后续步骤...
return 0;
}
umask 函数用于设置文件权限掩码,确保守护进程创建的文件具有预期的权限。
#include
#include
#include
#include
#include
int main() {
pid_t pid;
pid = fork();
if (pid < 0) {
perror("fork 失败");
exit(EXIT_FAILURE);
}
if (pid > 0) {
// 父进程退出
exit(EXIT_SUCCESS);
}
// 子进程创建新会话
pid_t sid = setsid();
if (sid < 0) {
perror("setsid 失败");
exit(EXIT_FAILURE);
}
// 改变工作目录
if (chdir("/") < 0) {
perror("chdir 失败");
exit(EXIT_FAILURE);
}
// 设置文件权限掩码
umask(0);
// 关闭不需要的文件描述符
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
// 守护进程的主循环
while (1) {
// 执行守护进程的任务
sleep(1);
}
return 0;
}
关闭标准输入、标准输出和标准错误输出的文件描述符,防止守护进程与控制终端交互。
通过以上步骤,你可以逐步掌握 Linux 多进程的相关知识,包括进程的创建、僵尸进程和孤儿进程的处理,以及守护进程的实现。在实际应用中,多进程编程可以提高程序的并发性能,充分利用多核 CPU 的资源。