Linux应用:进程的接纳

打印 上一主题 下一主题

主题 984|帖子 984|积分 2952

进程的诞生和灭亡

程的诞生通常是通过系统调用(如fork、exec等)来创建新进程。当一个进程完成其任务或者出现错误时,它会进入灭亡阶段。进程可以通过exit函数主动结束自身,也大概由于操作系统的调理策略(如资源耗尽、进程异常终止等)而被强制终止。进程结束后,操作系统会接纳其占用的资源,如内存、文件形貌符等。
进程的诞生

进程0

在操作系统启动过程中,进程 0 是最早被创建的进程,它是整个进程体系的始祖。进程 0 通常由操作系统内核直接初始化,负担着至关重要的任务,例如初始化系统关键数据布局、创建进程 1 等后续进程的基础环境搭建。它运行在内核态,拥有最高的权限,可以直接访问系统硬件资源,对系统的稳定运行起着决定性作用。在类 Unix 系统中,进程 0 也被称为 “swapper” 进程,其重要职责之一是负责进程调理的初始化,为后续进程能够在合适的时机获得 CPU 资源执行奠基基础。
进程1


进程 1 是进程 0 创建的第一个用户态进程,也被称作 “init” 进程。它是全部用户进程的祖先,在系统启动流程中,当内核完成自身初始化以及进程 0 的相干设置后,就会通过特定机制创建进程 1。进程 1 会读取系统配置文件,启动各种系统服务,好比网络服务、文件系统服务等。它负责管理系统中全部用户进程的生命周期,当某个用户进程异常终止时,进程 1 会接纳其资源,克制资源泄漏。并且,进程 1 始终存在于系统中,直到系统关闭,是维持系统正常运行的核心进程之一。
fork

fork是 Unix 和类 Unix 系统中用于创建新进程的系统调用。当一个进程调用fork时,内核会为新进程分配独立的进程控制块(PCB),并复制调用进程(父进程)的大部门资源,包括内存空间(数据段、代码段、堆栈段等)、打开的文件形貌符、信号处理函数等。新创建的进程(子进程)几乎与父进程如出一辙,fork调用会在父进程和子进程中分别返回,在父进程中返回子进程的进程 ID(PID),而在子进程中返回 0。通过这种返回值的差异,程序可以区分当前是在父进程还是子进程中执行差别的逻辑。例如,父进程大概继承执行一些管理任务,而子进程可以执行特定的计算任务或启动新的程序。不外,由于资源复制操作,fork在创建进程时开销相对较大。
父进程可以有多个子进程。在操作系统中,父进程可以通过多次调用 fork 函数来创建多个子进程。每个子进程都是父进程的一个副本,它们可以独立执行差别的任务,并且父进程可以通过各种方式管理和与这些子进程进行交互。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <unistd.h>
  5. int main() {
  6.     pid_t pid1, pid2;
  7.     // 创建第一个子进程
  8.     pid1 = fork();
  9.     if (pid1 < 0) {
  10.         // 处理fork失败的情况
  11.         perror("fork1");
  12.         return 1;
  13.     } else if (pid1 == 0) {
  14.         // 第一个子进程的代码
  15.         printf("我是第一个子进程,我的PID是 %d,父进程的PID是 %d\n", getpid(), getppid());
  16.         // 子进程执行一些任务,这里简单地休眠2秒
  17.         sleep(2);
  18.         // 子进程退出
  19.         exit(0);
  20.     } else {
  21.         // 父进程继续执行,创建第二个子进程
  22.         pid2 = fork();
  23.         if (pid2 < 0) {
  24.             // 处理fork失败的情况
  25.             perror("fork2");
  26.             return 1;
  27.         } else if (pid2 == 0) {
  28.             // 第二个子进程的代码
  29.             printf("我是第二个子进程,我的PID是 %d,父进程的PID是 %d\n", getpid(), getppid());
  30.             // 子进程执行一些任务,这里简单地休眠3秒
  31.             sleep(3);
  32.             // 子进程退出
  33.             exit(0);
  34.         } else {
  35.             // 父进程的代码
  36.             printf("我是父进程,我的PID是 %d\n", getpid());
  37.             // 父进程可以等待子进程结束,或者执行其他任务
  38.             // 这里简单地等待一段时间
  39.             sleep(5);
  40.             printf("两个子进程都已结束,父进程继续执行其他任务...\n");
  41.         }
  42.     }
  43.     return 0;
  44. }
复制代码

vfork

vfork也是用于创建新进程的系统调用,与fork有相似之处,但也存在明显差异。vfork创建子进程时,子进程与父进程共享内存空间,而不是像fork那样复制内存。这使得vfork创建进程的速率更快,开销更小。在vfork调用后,子进程会先运行,父进程会被阻塞,直到子进程调用exec系列函数(用于执行新的程序)或者exit退出。这种机制确保了子进程在使用共享内存时不会与父进程产生冲突。通常在必要快速创建进程并立即执行新程序的场景中,vfork会比fork更合适。例如,当一个进程必要频繁启动其他程序时,使用vfork可以减少资源开销,进步系统效率。​
  1. #include <iostream>
  2. #include <unistd.h>
  3. #include <sys/types.h>
  4. #include <sys/wait.h>
  5. #include <cstdlib>
  6. int main() {
  7.     pid_t pid;
  8.     // 使用 vfork() 创建新进程
  9.     pid = vfork();
  10.     if (pid < 0) {
  11.         // vfork() 失败
  12.         std::cerr << "vfork() failed!" << std::endl;
  13.         return 1;
  14.     } else if (pid == 0) {
  15.         // 子进程
  16.         std::cout << "This is the child process. PID: " << getpid() << std::endl;
  17.         // 子进程可以执行一些操作,这里简单地调用 exit()
  18.         exit(0);
  19.     } else {
  20.         // 父进程
  21.         int status;
  22.         // 等待子进程结束
  23.         waitpid(pid, &status, 0);
  24.         std::cout << "This is the parent process. PID: " << getpid() << std::endl;
  25.         std::cout << "Child process exited with status: " << WEXITSTATUS(status) << std::endl;
  26.     }
  27.     return 0;
  28. }
复制代码
包含必要的头文件:
:用于输入输出操作。
<unistd.h>:包含 vfork()、getpid() 等系统调用。
<sys/types.h>:包含一些基本的系统数据范例。
<sys/wait.h>:包含 waitpid() 函数。
:包含 exit() 函数。
使用 vfork() 创建新进程:
pid = vfork();:调用 vfork() 函数创建新进程。如果乐成,父进程会返回子进程的 PID(大于 0),子进程会返回 0;如果失败,返回 -1。
错误处理:
if (pid < 0):如果 vfork() 失败,输堕落误信息并返回 1。
子进程处理:
else if (pid == 0):子进程执行的代码。输出子进程的 PID,然后调用 exit(0) 结束子进程。
父进程处理:
else:父进程执行的代码。使用 waitpid() 等候子进程结束,并获取子进程的退出状态。输出父进程的 PID 和子进程的退出状态。

僵尸进程

当子进程先于父进程结束,且父进程没有及时调用wait系列函数来获取子进程的退出状态时,子进程就会成为僵尸进程。僵尸进程虽然已经结束运行,但它在系统中仍旧占据一定的资源(如进程表项),直到父进程调用wait函数接纳其资源。过多的僵尸进程大概会导致系统资源浪费,影响系统性能。
SIGCHILD 信号讲解


  • 信号概述
    在 Unix 或类 Unix 系统中,信号是一种软件中断机制,用于通知进程发生了某个特定事件。SIGCHILD 信号就是其中之一,它是由内核在以下几种情况下发送给父进程的:
    子进程终止(正常退出或被信号终止)。
    子进程克制(例如通过 SIGSTOP 信号)。
    克制的子进程恢复执行(例如通过 SIGCONT 信号)。
  • 信号处理的意义
    父进程接收到 SIGCHILD 信号后,通常必要对其进行处理,以克制产生僵尸进程。僵尸进程是指子进程已经终止,但父进程尚未接纳其资源(重要是进程表项)的进程。通过处理 SIGCHILD 信号,父进程可以及时接纳子进程的资源,释放系统资源。
  • 信号处理方式
    父进程可以通过以下几种方式处理 SIGCHILD 信号:
    忽略信号:将信号处理函数设置为 SIG_IGN,如许内核会自动接纳子进程的资源,克制产生僵尸进程。但这种方式在某些情况下大概会导致一些问题,例如在多线程程序中。
    自定义信号处理函数:父进程可以定义自己的信号处理函数,在函数中调用 wait 或 waitpid 函数来接纳子进程的资源。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <signal.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <sys/wait.h>
  7. int main() {
  8.     // 忽略 SIGCHILD 信号
  9.     signal(SIGCHLD, SIG_IGN);
  10.     pid_t pid = fork();
  11.     if (pid < 0) {
  12.         perror("fork");
  13.         return 1;
  14.     } else if (pid == 0) {
  15.         // 子进程
  16.         printf("子进程开始执行,PID: %d\n", getpid());
  17.         sleep(2);
  18.         printf("子进程结束\n");
  19.         exit(0);
  20.     } else {
  21.         // 父进程
  22.         printf("父进程继续执行,子进程 PID: %d\n", pid);
  23.         sleep(5);
  24.         printf("父进程结束\n");
  25.     }
  26.     return 0;
  27. }
复制代码
signal(SIGCHLD, SIG_IGN);:将 SIGCHILD 信号的处理方式设置为忽略。如许,当子进程终止时,内核会自动接纳其资源,不会产生僵尸进程。
子进程打印信息,就寝 2 秒退却出。
父进程打印信息,就寝 5 秒后结束。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <signal.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <sys/wait.h>
  7. // 自定义信号处理函数
  8. void sigchld_handler(int signo) {
  9.     pid_t pid;
  10.     int status;
  11.     // 使用 waitpid 以非阻塞模式回收所有终止的子进程
  12.     while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
  13.         if (WIFEXITED(status)) {
  14.             printf("子进程 %d 正常结束,退出状态码: %d\n", pid, WEXITSTATUS(status));
  15.         } else if (WIFSIGNALED(status)) {
  16.             printf("子进程 %d 被信号 %d 终止\n", pid, WTERMSIG(status));
  17.         }
  18.     }
  19. }
  20. int main() {
  21.     // 注册信号处理函数
  22.     signal(SIGCHLD, sigchld_handler);
  23.     pid_t pid = fork();
  24.     if (pid < 0) {
  25.         perror("fork");
  26.         return 1;
  27.     } else if (pid == 0) {
  28.         // 子进程
  29.         printf("子进程开始执行,PID: %d\n", getpid());
  30.         sleep(2);
  31.         printf("子进程结束\n");
  32.         exit(10);
  33.     } else {
  34.         // 父进程
  35.         printf("父进程继续执行,子进程 PID: %d\n", pid);
  36.         sleep(5);
  37.         printf("父进程结束\n");
  38.     }
  39.     return 0;
  40. }
复制代码
sigchld_handler 函数是自定义的信号处理函数,当父进程接收到 SIGCHILD 信号时会调用该函数。
在 sigchld_handler 函数中,使用 waitpid(-1, &status, WNOHANG) 以非阻塞模式接纳全部终止的子进程,并根据子进程的退出状态进行相应的输出。
signal(SIGCHLD, sigchld_handler);:将 SIGCHILD 信号的处理方式设置为自定义的信号处理函数。
子进程打印信息,就寝 2 秒后以状态码 10 退出。
父进程打印信息,就寝 5 秒后结束。
父进程wait回

收子进程
wait函数是父进程用于等候子进程结束并获取其退出状态的系统调用。当父进程调用wait时,它会阻塞自己,直到有一个子进程结束。wait函数返回子进程的 PID,并通过参数获取子进程的退出状态,如许父进程就可以对结束的子进程进行善后处理,克制产生僵尸进程。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <sys/wait.h>
  5. #include <unistd.h>
  6. int main() {
  7.     pid_t pid;
  8.     int status;
  9.     // 创建子进程
  10.     pid = fork();
  11.     if (pid < 0) {
  12.         // 处理 fork 失败的情况
  13.         perror("fork");
  14.         return 1;
  15.     } else if (pid == 0) {
  16.         // 子进程代码
  17.         printf("子进程开始执行,PID: %d\n", getpid());
  18.         sleep(2);  // 模拟子进程执行一段时间
  19.         printf("子进程结束\n");
  20.         exit(10);  // 子进程退出,返回状态码 10
  21.     } else {
  22.         // 父进程代码
  23.         printf("父进程等待子进程结束,子进程 PID: %d\n", pid);
  24.         // 调用 wait 函数等待子进程结束
  25.         pid_t terminated_pid = wait(&status);
  26.         if (terminated_pid > 0) {
  27.             if (WIFEXITED(status)) {
  28.                 // 子进程正常退出
  29.                 printf("子进程 %d 正常结束,退出状态码: %d\n", terminated_pid, WEXITSTATUS(status));
  30.             } else if (WIFSIGNALED(status)) {
  31.                 // 子进程被信号终止
  32.                 printf("子进程 %d 被信号 %d 终止\n", terminated_pid, WTERMSIG(status));
  33.             }
  34.         }
  35.     }
  36.     return 0;
  37. }
复制代码
wait 函数示例
创建子进程:使用 fork 函数创建一个子进程。
子进程:打印信息,就寝 2 秒,然后以状态码 10 退出。
父进程:调用 wait 函数等候子进程结束。wait 函数会阻塞父进程,直到有一个子进程结束。获取子进程的退出状态后,根据状态判断子进程是正常退出还是被信号终止

WIFEXITED(status):这个宏用于判断子进程是否是正常终止的。所谓正常终止,指的是子进程通过 return 语句、exit 函数或者 _exit 函数退出。如果子进程是正常终止的,该宏返回非零值;否则返回 0。
WIFSIGNALED(status):此宏用于判断子进程是否是非正常终止的,也就是子进程是否是被某个信号所终止的。如果子进程是被信号终止的,该宏返回非零值;否则返回 0。
WEXITSTATUS(status):当子进程正常终止时,使用这个宏可以获取子进程的返回值。必要注意的是,只有在 WIFEXITED(status) 返回非零值时,使用这个宏才有意义。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <sys/wait.h>
  5. #include <unistd.h>
  6. #include <signal.h>
  7. int main() {
  8.     pid_t pid;
  9.     int status;
  10.     // 创建子进程
  11.     pid = fork();
  12.     if (pid < 0) {
  13.         // 处理 fork 失败的情况
  14.         perror("fork");
  15.         return 1;
  16.     } else if (pid == 0) {
  17.         // 子进程代码
  18.         printf("子进程开始执行,PID: %d\n", getpid());
  19.         // 模拟子进程执行一段时间
  20.         sleep(2);
  21.         // 子进程正常退出,返回状态码 42
  22.         exit(42);
  23.     } else {
  24.         // 父进程代码
  25.         printf("父进程等待子进程结束,子进程 PID: %d\n", pid);
  26.         // 调用 wait 函数等待子进程结束
  27.         pid_t terminated_pid = wait(&status);
  28.         if (terminated_pid > 0) {
  29.             if (WIFEXITED(status)) {
  30.                 // 子进程正常退出
  31.                 printf("子进程 %d 正常结束,退出状态码: %d\n", terminated_pid, WEXITSTATUS(status));
  32.             } else if (WIFSIGNALED(status)) {
  33.                 // 子进程被信号终止
  34.                 printf("子进程 %d 被信号 %d 终止\n", terminated_pid, WTERMSIG(status));
  35.             }
  36.         }
  37.     }
  38.     return 0;
  39. }
复制代码
创建子进程:
使用 fork() 函数创建一个子进程。fork() 函数会返回两次,在父进程中返回子进程的 PID,在子进程中返回 0,如果创建失败则返回 -1。
子进程部门:
子进程打印自身的 PID 信息,然后调用 sleep(2) 模拟执行一段时间。
末了调用 exit(42) 正常退出,并返回状态码 42。
父进程部门:
父进程打印等候子进程结束的信息,然后调用 wait(&status) 函数等候子进程结束。wait 函数会阻塞父进程,直到有一个子进程结束,并将子进程的退出状态存储在 status 变量中。
父进程通过 WIFEXITED(status) 宏判断子进程是否正常退出。由于子进程是通过 exit(42) 正常退出的,所以该宏返回非零值。
在 WIFEXITED(status) 为真的情况下,使用 WEXITSTATUS(status) 宏获取子进程的退出状态码,并将其打印输出。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <sys/wait.h>
  5. #include <unistd.h>
  6. #include <signal.h>
  7. int main() {
  8.     pid_t pid;
  9.     int status;
  10.     // 创建子进程
  11.     pid = fork();
  12.     if (pid < 0) {
  13.         // 处理 fork 失败的情况
  14.         perror("fork");
  15.         return 1;
  16.     } else if (pid == 0) {
  17.         // 子进程代码
  18.         printf("子进程开始执行,PID: %d\n", getpid());
  19.         // 模拟子进程执行一段时间
  20.         sleep(2);
  21.         // 子进程给自己发送 SIGTERM 信号,模拟被信号终止
  22.         kill(getpid(), SIGTERM);
  23.     } else {
  24.         // 父进程代码
  25.         printf("父进程等待子进程结束,子进程 PID: %d\n", pid);
  26.         // 调用 wait 函数等待子进程结束
  27.         pid_t terminated_pid = wait(&status);
  28.         if (terminated_pid > 0) {
  29.             if (WIFEXITED(status)) {
  30.                 // 子进程正常退出
  31.                 printf("子进程 %d 正常结束,退出状态码: %d\n", terminated_pid, WEXITSTATUS(status));
  32.             } else if (WIFSIGNALED(status)) {
  33.                 // 子进程被信号终止
  34.                 printf("子进程 %d 被信号 %d 终止\n", terminated_pid, WTERMSIG(status));
  35.             }
  36.         }
  37.     }
  38.     return 0;
  39. }
复制代码
子进程在执行一段时间后,调用 kill(getpid(), SIGTERM) 给自己发送 SIGTERM 信号,模拟被信号终止的情况。父进程在子进程结束后,通过 WIFSIGNALED(status) 宏判断子进程是否是被信号终止的,如果是,则使用 WTERMSIG(status) 宏获取终止子进程的信号编号并打印输出。

waitpid介绍

waitpid函数也是用于等候子进程结束的系统调用,它比wait函数更加机动。waitpid可以指定等候特定 PID 的子进程,也可以设置非阻塞等候模式或者阻塞模式。通过waitpid,父进程可以更正确地控制对子进程的等候和资源接纳操作,例如可以在不阻塞父进程的情况下,定期查抄子进程是否结束。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <sys/wait.h>
  5. #include <unistd.h>
  6. int main() {
  7.     pid_t pid;
  8.     int status;
  9.     // 创建子进程
  10.     pid = fork();
  11.     if (pid < 0) {
  12.         // 处理 fork 失败的情况
  13.         perror("fork");
  14.         return 1;
  15.     } else if (pid == 0) {
  16.         // 子进程代码
  17.         printf("子进程开始执行,PID: %d\n", getpid());
  18.         sleep(2);  // 模拟子进程执行一段时间
  19.         printf("子进程结束\n");
  20.         exit(20);  // 子进程退出,返回状态码 20
  21.     } else {
  22.         // 父进程代码
  23.         printf("父进程等待子进程结束,子进程 PID: %d\n", pid);
  24.         pid_t terminated_pid;
  25.         // 使用 waitpid 函数以非阻塞模式等待子进程结束
  26.         while ((terminated_pid = waitpid(pid, &status, WNOHANG)) == 0) {
  27.             printf("子进程还在运行,父进程继续执行其他任务...\n");
  28.             sleep(1);
  29.         }
  30.         if (terminated_pid > 0) {
  31.             if (WIFEXITED(status)) {
  32.                 // 子进程正常退出
  33.                 printf("子进程 %d 正常结束,退出状态码: %d\n", terminated_pid, WEXITSTATUS(status));
  34.             } else if (WIFSIGNALED(status)) {
  35.                 // 子进程被信号终止
  36.                 printf("子进程 %d 被信号 %d 终止\n", terminated_pid, WTERMSIG(status));
  37.             }
  38.         } else if (terminated_pid == -1) {
  39.             perror("waitpid");
  40.         }
  41.     }
  42.     return 0;
  43. }
复制代码
创建子进程:同样使用 fork 函数创建一个子进程。
子进程:打印信息,就寝 2 秒,然后以状态码 20 退出。
父进程:使用 waitpid 函数以非阻塞模式(WNOHANG)等候子进程结束。在子进程未结束时,父进程可以继承执行其他任务。当子进程结束后,获取其退出状态并进行相应处理。

竟态开端引入

竞态条件是指当多个进程或线程并发访问共享资源时,由于它们的执行次序不确定,导致终极效果依赖于它们实际执行次序的一种现象。在进程编程中,如果多个进程同时对共享资源(如共享内存、文件等)进行读写操作,而没有采取适当的同步机制,就大概出现竞态条件,导致程序运行效果错误。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

南飓风

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表