计算机操作系统——进程控制(Linux)

devtools/2024/11/27 22:02:44/
cle class="baidu_pl">
cle_content" class="article_content clearfix">
content_views" class="markdown_views prism-atom-one-dark">cap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);">

class="toc">

进程控制

  • 进程创建
    • fork()函数
    • fork() 的基本功能
    • fork() 的基本语法
    • fork() 的工作原理
    • fork() 的典型使用示例
    • fork() 的常见问题
    • fork() 和 exec() 结合使用
    • 总结
  • 进程终止与$
    • 进程终止的本质
    • 进程终止的情况
      • 正常退出(Exit)
      • 由于信号终止
      • 非正常退出(错误或崩溃)
      • 进程被父进程终止
    • 进程退出的方法
      • exit() 函数
      • _exit() 系统调用
      • abort() 函数
      • 接收到信号
    • 退出码详解
      • 正常退出码
      • 非零退出码
      • 父进程获取退出码
      • 退出码的构成
    • 总结
  • 进程等待
    • 进程等待的必要性
    • 等待方法
      • wait()
      • waitpid()
      • waitid()
    • 阻塞和非阻塞等待详解
      • 阻塞等待
      • 非阻塞等待
    • 获取子进程的状态
    • 总结
  • 进程程序替换
    • 进程替换的基本概念
    • 替换函数
    • 替换函数解释
    • 总结

进程创建

fork()函数

fork() 是 Linux 和类 Unix 系统中用于创建新进程的系统调用。它是进程创建的核心机制之一。当一个进程调用 fork() 时࿰c;它会创建一个几乎完全相同的新进程࿰c;称为子进程

fork() 的基本功能

创建子进程:调用 fork() 时࿰c;操作系统会复制调用进程(父进程)的地址空间、堆栈、文件描述符等信息࿰c;创建一个新的子进程。子进程几乎是父进程的一个副本࿰c;但有两个关键区别

  • 子进程拥有一个新的进程ID(PID)。
  • 子进程和父进程的返回值不同࿰c;fork() 会返回两次:一次在父进程中࿰c;返回子进程的PID;另一次在子进程中࿰c;返回0。

父子进程的区分:通过 fork() 返回的值࿰c;父进程和子进程可以根据不同的返回值执行不同的代码逻辑:

  • 在父进程中࿰c;fork() 返回子进程的 PID(大于 0 的值)。
  • 在子进程中࿰c;fork() 返回 0。
  • 进程表:每个进程在操作系统内都有一个进程表项࿰c;包括进程ID(PID)、父进程ID(PPID)等信息。父进程的 PID 就是子进程的 PPID。

fork() 的基本语法

<code class="prism language-c">class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><unistd.h>
class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><sys/types.h>class="token class-name">pid_t class="token function">forkclass="token punctuation">(class="token keyword">voidclass="token punctuation">)class="token punctuation">;
code>

返回值

  • 如果 fork() 成功࿰c;返回值在父进程中是子进程的 PID࿰c;在子进程中是 0。
  • 如果 fork() 失败࿰c;返回 -1࿰c;并设置 errno。

fork() 的工作原理

复制父进程:fork() 会复制父进程的所有资源࿰c;如内存、文件描述符、环境变量等。这里需要注意的是࿰c;现代操作系统通常会使用 写时复制(Copy on Write࿰c;COW) 技术来延迟复制内存࿰c;直到父子进程尝试修改数据时࿰c;才会进行真正的内存复制࿰c;从而提高效率。

c="https://i-blog.csdnimg.cn/direct/4621500b274346179550799b2e27232b.png" alt="在这里插入图片描述" />
c="https://i-blog.csdnimg.cn/direct/7dde06f32e474120a321ce25b026439f.png" alt="在这里插入图片描述" />

进程调⽤fork࿰c;当控制转移到内核中的fork代码后࿰c;内核做:

  • 分配新的内存块和内核数据结构给⼦进程
  • 将⽗进程部分数据结构内容拷⻉⾄⼦进程
  • 添加⼦进程到系统进程列表当中
  • fork返回࿰c;开始调度器调度

资源共享:父进程和子进程共享某些资源(例如文件描述符、信号等)࿰c;但它们有独立的内存空间。

返回值

父进程:返回子进程的 PID。
子进程:返回 0。
c="https://i-blog.csdnimg.cn/direct/62b2f7b7440544d092a01855da8c044d.png" alt="进程创建" />

fork() 的典型使用示例

<code class="prism language-c">class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><stdio.h>
class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><unistd.h>class="token keyword">int class="token function">mainclass="token punctuation">(class="token punctuation">) class="token punctuation">{class="token class-name">pid_t pid class="token operator">= class="token function">forkclass="token punctuation">(class="token punctuation">)class="token punctuation">;  class="token comment">// 创建子进程class="token keyword">if class="token punctuation">(pid class="token operator">== class="token operator">-class="token number">1class="token punctuation">) class="token punctuation">{class="token comment">// fork失败class="token function">perrorclass="token punctuation">(class="token string">"fork failed"class="token punctuation">)class="token punctuation">;class="token keyword">return class="token number">1class="token punctuation">;class="token punctuation">} class="token keyword">else class="token keyword">if class="token punctuation">(pid class="token operator">> class="token number">0class="token punctuation">) class="token punctuation">{class="token comment">// 父进程class="token function">printfclass="token punctuation">(class="token string">"This is the parent process, child PID is %d\n"class="token punctuation">, pidclass="token punctuation">)class="token punctuation">;class="token punctuation">} class="token keyword">else class="token punctuation">{class="token comment">// 子进程class="token function">printfclass="token punctuation">(class="token string">"This is the child process\n"class="token punctuation">)class="token punctuation">;class="token punctuation">}class="token keyword">return class="token number">0class="token punctuation">;
class="token punctuation">}
code>

输出:

This is the parent process, child PID is 12345
This is the child process

注意:fork() 调用时࿰c;父进程和子进程的输出顺序不一定相同࿰c;具体执行顺序由操作系统调度器决定。

fork() 的常见问题

子进程不会继承父进程的锁:fork() 会复制父进程的资源࿰c;包括文件描述符࿰c;但不会复制父进程持有的锁(如互斥锁、读写锁)。如果父进程在 fork() 后还持有某些锁࿰c;可能会导致子进程死锁。

资源消耗:每次 fork() 都会创建一个新的进程࿰c;这会消耗一定的内存和系统资源࿰c;尤其是在创建多个进程时࿰c;可能会影响系统性能。因此࿰c;要注意避免频繁调用 fork()。

进程僵尸:当子进程退出后࿰c;父进程应该通过调用 wait() 或 waitpid() 等函数来回收子进程的资源。如果父进程没有处理࿰c;子进程会变成 “僵尸进程”(Zombie Process)࿰c;占用系统资源直到父进程清理它。

进程ID的分配:每个进程都有唯一的进程ID(PID)。父进程的 PID 和子进程的 PID 是不同的。

c__93">fork() 和 exec() 结合使用

在许多情况下࿰c;fork() 和 exec() 被结合使用来启动新的程序。fork() 用于创建一个子进程࿰c;而 exec() 系列函数则用于加载不同的程序。

<code class="prism language-c">class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><stdio.h>
class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><unistd.h>class="token keyword">int class="token function">mainclass="token punctuation">(class="token punctuation">) class="token punctuation">{class="token class-name">pid_t pid class="token operator">= class="token function">forkclass="token punctuation">(class="token punctuation">)class="token punctuation">;class="token keyword">if class="token punctuation">(pid class="token operator">== class="token number">0class="token punctuation">) class="token punctuation">{class="token comment">// 子进程执行新程序class="token function">execlpclass="token punctuation">(class="token string">"/bin/ls"class="token punctuation">, class="token string">"ls"class="token punctuation">, class="token string">"-l"class="token punctuation">, class="token punctuation">(class="token keyword">char class="token operator">*class="token punctuation">) class="token constant">NULLclass="token punctuation">)class="token punctuation">;class="token comment">// 如果 execlp 失败࿰c;输出错误class="token function">perrorclass="token punctuation">(class="token string">"execlp failed"class="token punctuation">)class="token punctuation">;class="token punctuation">} class="token keyword">else class="token keyword">if class="token punctuation">(pid class="token operator">> class="token number">0class="token punctuation">) class="token punctuation">{class="token comment">// 父进程等待子进程结束class="token function">waitclass="token punctuation">(class="token constant">NULLclass="token punctuation">)class="token punctuation">;class="token function">printfclass="token punctuation">(class="token string">"Child process finished\n"class="token punctuation">)class="token punctuation">;class="token punctuation">}class="token keyword">return class="token number">0class="token punctuation">;
class="token punctuation">}
code>

在这个示例中࿰c;子进程会执行 ls -l 命令࿰c;而父进程则等待子进程结束。

总结

  • fork() 是 Linux 中用于创建新进程的系统调用࿰c;返回两次:一次在父进程中返回子进程的 PID࿰c;一次在子进程中返回 0。
  • 它复制父进程的资源࿰c;并使用写时复制技术优化性能。
  • 子进程和父进程是相互独立的࿰c;但它们共享某些资源࿰c;如文件描述符。
  • 父进程应该使用 wait() 等函数来回收子进程的资源࿰c;避免僵尸进程。

进程终止与$

进程终止的本质

进程终止指的是操作系统内的进程在完成其任务或遇到异常时࿰c;从系统中注销࿰c;释放其占用的资源࿰c;并通过某种方式通知父进程(如果存在)其退出状态。进程的终止意味着其执行的生命周期结束࿰c;操作系统会清理进程占用的内存、文件描述符、子进程等资源。

color="red">进程的终止通常由以下几种情况引起:

正常终止:进程完成任务并主动退出。
异常终止:进程遇到错误或异常情况而被操作系统强制终止。
被信号中断:进程接收到外部信号(如 SIGKILL 或 SIGTERM)而终止。

进程终止的情况

进程终止可以发生在不同的情况下࿰c;具体有以下几种情况:

正常退出(Exit)

进程执行完其所有任务后࿰c;可以主动退出࿰c;通常通过调用 exit() 系统调用来实现。进程结束时࿰c;操作系统会回收进程的资源࿰c;并通知其父进程。父进程通过 wait() 或 waitpid() 等系统调用来获取子进程的退出状态。

由于信号终止

进程在运行时可以接收到来自外部或内部的信号࿰c;这些信号可能导致进程中止。例如:

  • SIGKILL:无条件终止进程࿰c;无法被捕捉或忽略。
  • SIGTERM:请求进程终止࿰c;允许进程清理资源。
  • SIGSEGV:段错误࿰c;通常表示进程访问了非法内存。
  • SIGABRT:进程由于调用 abort() 函数而终止࿰c;通常是由于程序发生了严重错误。

非正常退出(错误或崩溃)

进程可能由于错误(如非法内存访问、除零错误、系统资源不足等)导致崩溃退出。在这种情况下࿰c;操作系统会发送错误信号给进程࿰c;通常会生成核心转储文件(core dump)以帮助调试。

进程被父进程终止

父进程可以通过发送信号来终止子进程。例如࿰c;父进程可以使用 kill() 函数发送一个 SIGKILL 或 SIGTERM 信号来中止子进程的执行。

进程退出的方法

进程可以通过多种方式退出:

exit() 函数

exit() 是标准库函数࿰c;用于正常终止进程。它会做以下几件事情:

  • 关闭所有已打开的文件描述符。
  • 清理由进程分配的内存。
  • 调用终止处理程序(如果有)。
  • 通知操作系统进程已终止。
<code class="prism language-c">class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><stdlib.h>class="token keyword">int class="token function">mainclass="token punctuation">(class="token punctuation">) class="token punctuation">{class="token comment">// 进行某些操作class="token function">exitclass="token punctuation">(class="token number">0class="token punctuation">)class="token punctuation">;  class="token comment">// 正常退出࿰c;返回退出码0
class="token punctuation">}
code>

_exit() 系统调用

_exit() 是系统调用࿰c;用于立即退出进程࿰c;而不执行任何清理操作(如关闭文件描述符、调用终止处理程序等)。它主要用于子进程退出࿰c;避免清理父进程设置的资源。

<code class="prism language-c">class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><unistd.h>class="token keyword">int class="token function">mainclass="token punctuation">(class="token punctuation">) class="token punctuation">{class="token comment">// 执行操作class="token function">_exitclass="token punctuation">(class="token number">0class="token punctuation">)class="token punctuation">;  class="token comment">// 立即退出࿰c;不进行标准库的清理操作
class="token punctuation">}
code>

abort() 函数

abort() 函数用于非正常退出࿰c;它通常在程序遇到严重错误时调用。它会立即终止进程并生成核心转储文件࿰c;方便调试。

<code class="prism language-c">class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><stdlib.h>class="token keyword">int class="token function">mainclass="token punctuation">(class="token punctuation">) class="token punctuation">{class="token comment">// 出现严重错误࿰c;退出程序class="token function">abortclass="token punctuation">(class="token punctuation">)class="token punctuation">;  class="token comment">// 终止进程并生成核心文件
class="token punctuation">}
code>

接收到信号

进程也可能通过接收到某些信号而被终止。操作系统通过信号机制通知进程发生了某些事件࿰c;如 SIGKILL(强制终止)或 SIGSEGV(段错误)。进程可以捕获某些信号并执行自定义的信号处理程序࿰c;或者直接退出。

<code class="prism language-c">class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><signal.h>class="token keyword">void class="token function">signal_handlerclass="token punctuation">(class="token keyword">int sigclass="token punctuation">) class="token punctuation">{class="token keyword">if class="token punctuation">(sig class="token operator">== SIGSEGVclass="token punctuation">) class="token punctuation">{class="token comment">// 处理段错误class="token function">exitclass="token punctuation">(class="token number">1class="token punctuation">)class="token punctuation">;class="token punctuation">}
class="token punctuation">}class="token keyword">int class="token function">mainclass="token punctuation">(class="token punctuation">) class="token punctuation">{class="token function">signalclass="token punctuation">(SIGSEGVclass="token punctuation">, signal_handlerclass="token punctuation">)class="token punctuation">;  class="token comment">// 捕获段错误信号class="token comment">// 代码中可能发生段错误class="token keyword">return class="token number">0class="token punctuation">;
class="token punctuation">}
code>

退出码详解

退出码(Exit Status)是一个用于标识进程退出状态的整数。退出码是进程结束时向操作系统报告的一个数字࿰c;通常用来告诉父进程进程是如何退出的。

正常退出码

  • 0:表示进程成功执行完毕并正常退出࿰c;常见的退出码。

非零退出码

非零值:表示进程遇到错误或异常而退出。操作系统和父进程通常会根据非零值来判断错误类型。常见的非零退出码有:

  • 1:一般性错误。
  • 2:命令行参数错误。
  • 126:命令不能执行(权限问题)。
  • 127:命令未找到。
  • 128:进程由于信号导致的退出࿰c;退出码为 128 + 信号编号(例如࿰c;SIGKILL 信号编号为 9࿰c;退出码为 137)。

c="https://i-blog.csdnimg.cn/direct/5635f720644945bc8c7379e2f5694060.png" alt="在这里插入图片描述" />

父进程获取退出码

父进程可以通过调用 wait() 或 waitpid() 等系统调用来获取子进程的退出码。退出码的高字节和低字节分别表示不同的信息:

高字节:表示子进程是否因信号退出。
低字节:表示进程正常退出时的退出码。
例如࿰c;WEXITSTATUS(status) 宏可以用来获取正常退出的退出码࿰c;WIFSIGNALED(status) 用来判断进程是否因信号退出。
在 Linux 中࿰c;程序的退出码(Exit Code)是一个 8 位无符号整数࿰c;通常表示程序的退出状态。它由两个部分组成:低位(低 7 位)和 高位(高 1 位)。这两个部分分别代表不同的含义。

退出码的构成

退出码的范围从 0 到 255(即 0x00 到 0xFF)。通常情况下࿰c;退出码的构成可以分为两个部分:

低 7 位(0-127):用于表示程序的执行结果࿰c;通常用来表示 成功或错误 的不同状态。
高 1 位(128-255):表示程序是否是因为某个 信号终止 而退出。

color="red">低位(0-127)

0:表示程序正常退出࿰c;执行成功。通常࿰c;任何程序正常执行完毕后࿰c;都会返回退出码 0。

1-127:表示程序执行过程中出现了错误。不同的程序可能会定义不同的退出码来表示不同的错误类型。比如:

  • 1 通常表示常见的错误。
  • 2 可能表示使用错误的命令行参数。
  • 126 可能表示权限问题(命令无法执行)。
  • 127 通常表示找不到命令。

这部分退出码由程序开发者或操作系统约定࿰c;用于标识程序执行的错误类型。

color="red"> 高位(128-255)

当程序因为 信号 被终止时࿰c;退出码会反映这种信号的编号。退出码的计算公式是:

<code>退出码 = 128 + 信号编号
code>

例如࿰c;程序因为 SIGKILL 信号(信号编号 9)而被强制终止时࿰c;退出码将是 137(128 + 9)。

如果程序由于 SIGSEGV 信号(信号编号 11࿰c;即段错误)崩溃退出࿰c;则退出码将是 139(128 + 11)。

这种退出码范围从 128 到 255࿰c;表示程序因接收到特定的信号而终止。

<code class="prism language-c">class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><stdio.h>
class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><sys/wait.h>
class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><unistd.h>class="token keyword">int class="token function">mainclass="token punctuation">(class="token punctuation">) class="token punctuation">{class="token class-name">pid_t pid class="token operator">= class="token function">forkclass="token punctuation">(class="token punctuation">)class="token punctuation">;class="token keyword">if class="token punctuation">(pid class="token operator">== class="token number">0class="token punctuation">) class="token punctuation">{class="token comment">// 子进程正常退出class="token function">_exitclass="token punctuation">(class="token number">42class="token punctuation">)class="token punctuation">;  class="token comment">// 退出码为42class="token punctuation">} class="token keyword">else class="token punctuation">{class="token keyword">int statusclass="token punctuation">;class="token function">waitclass="token punctuation">(class="token operator">&statusclass="token punctuation">)class="token punctuation">;class="token keyword">if class="token punctuation">(class="token function">WIFEXITEDclass="token punctuation">(statusclass="token punctuation">)class="token punctuation">) class="token punctuation">{class="token function">printfclass="token punctuation">(class="token string">"Child exited with code %d\n"class="token punctuation">, class="token function">WEXITSTATUSclass="token punctuation">(statusclass="token punctuation">)class="token punctuation">)class="token punctuation">;class="token punctuation">} class="token keyword">else class="token keyword">if class="token punctuation">(class="token function">WIFSIGNALEDclass="token punctuation">(statusclass="token punctuation">)class="token punctuation">) class="token punctuation">{class="token function">printfclass="token punctuation">(class="token string">"Child terminated by signal %d\n"class="token punctuation">, class="token function">WTERMSIGclass="token punctuation">(statusclass="token punctuation">)class="token punctuation">)class="token punctuation">;class="token punctuation">}class="token punctuation">}class="token keyword">return class="token number">0class="token punctuation">;
class="token punctuation">}
code>

总结

  • 进程终止的本质:进程终止是操作系统回收进程资源、清理内存并通知父进程的过程。
  • 终止原因:可以是正常结束、错误崩溃、外部信号等。
  • 退出方法:包括 exit()、_exit()、abort()、信号等方式。
  • 退出码:是进程退出时向操作系统报告的状态࿰c;通常 0 表示成功࿰c;非零值表示异常退出࿰c;父进程可以通过 wait() 获取子进程的退出状态。

进程等待

在操作系统中࿰c;进程的等待通常是指父进程等待子进程完成执行后获取其退出状态(exit status)。这个机制对于进程管理和资源回收非常重要࿰c;能够避免僵尸进程的出现࿰c;并确保父进程能够得到子进程的执行结果。

进程等待的必要性

当一个进程创建了子进程时࿰c;父进程需要等到子进程完成执行࿰c;并获取子进程的退出状态。否则࿰c;子进程的资源将无法及时回收c;导致僵尸进程的产生。僵尸进程指的是子进程已经终止࿰c;但其退出状态尚未被父进程获取的进程。僵尸进程会占用系统资源࿰c;如进程表项࿰c;影响系统性能,<code>并且无法被kill -9杀死code>。

<code>为什么需要等待?code>

避免僵尸进程:子进程终止后࿰c;操作系统不会立即销毁子进程的进程表项࿰c;父进程需要通过wait()或waitpid()获取子进程的退出状态࿰c;从而清理相关资源。
获取子进程的执行结果:父进程需要知道子进程是否正常完成工作、是否发生了错误等信息。这些信息通过退出码传递。
同步进程间的操作:父进程可以在等待子进程时࿰c;确保子进程已经完成特定任务࿰c;避免在子进程还没完成时继续执行某些操作。

等待方法

在 Linux 系统中࿰c;父进程获取子进程状态的方法主要有以下几种:

wait()

wait() 是最基本的等待方法࿰c;它会阻塞父进程࿰c;直到任一子进程终止。父进程调用该系统调用时࿰c;会挂起࿰c;直到一个子进程结束。

语法

<code class="prism language-c">class="token class-name">pid_t class="token function">waitclass="token punctuation">(class="token keyword">int class="token operator">*statusclass="token punctuation">)class="token punctuation">;
code>

status:如果不为 NULL࿰c;该参数会返回子进程的退出状态。
返回值:成功时返回终止的子进程的进程ID(PID)࿰c;失败时返回 -1。
示例

<code class="prism language-c">class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><stdio.h>
class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><stdlib.h>
class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><sys/wait.h>
class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><unistd.h>class="token keyword">int class="token function">mainclass="token punctuation">(class="token punctuation">) class="token punctuation">{class="token class-name">pid_t pid class="token operator">= class="token function">forkclass="token punctuation">(class="token punctuation">)class="token punctuation">;class="token keyword">if class="token punctuation">(pid class="token operator">== class="token number">0class="token punctuation">) class="token punctuation">{class="token comment">// 子进程class="token function">printfclass="token punctuation">(class="token string">"Child process\n"class="token punctuation">)class="token punctuation">;class="token function">exitclass="token punctuation">(class="token number">0class="token punctuation">)class="token punctuation">;class="token punctuation">} class="token keyword">else class="token punctuation">{class="token comment">// 父进程class="token keyword">int statusclass="token punctuation">;class="token function">waitclass="token punctuation">(class="token operator">&statusclass="token punctuation">)class="token punctuation">;  class="token comment">// 等待子进程退出class="token keyword">if class="token punctuation">(class="token function">WIFEXITEDclass="token punctuation">(statusclass="token punctuation">)class="token punctuation">) class="token punctuation">{class="token function">printfclass="token punctuation">(class="token string">"Child exited with status %d\n"class="token punctuation">, class="token function">WEXITSTATUSclass="token punctuation">(statusclass="token punctuation">)class="token punctuation">)class="token punctuation">;class="token punctuation">}class="token punctuation">}class="token keyword">return class="token number">0class="token punctuation">;
class="token punctuation">}
code>

在这个例子中࿰c;父进程等待子进程退出࿰c;并获取子进程的退出状态。

waitpid()

waitpid() 是 wait() 的增强版本࿰c;父进程可以选择等待特定的子进程࿰c;或者控制是否阻塞。

语法

<code class="prism language-c">class="token class-name">pid_t class="token function">waitpidclass="token punctuation">(class="token class-name">pid_t pidclass="token punctuation">, class="token keyword">int class="token operator">*statusclass="token punctuation">, class="token keyword">int optionsclass="token punctuation">)class="token punctuation">;
code>

(1)pid:可以指定等待特定的子进程(通过其 PID)࿰c;或者等待所有子进程。

  • pid > 0:等待具有指定 PID 的子进程。
  • pid == -1:等待任何子进程(类似 wait() 的行为)。
  • pid == 0:等待与当前进程属于同一进程组的子进程。
  • pid < -1:等待属于某个进程组的子进程。
    status:与 wait() 相同࿰c;用于存储子进程的退出状态。

(2)options:控制等待的选项࿰c;如:

  • WNOHANG:非阻塞方式࿰c;如果没有子进程退出࿰c;立即返回。
  • WUNTRACED:如果子进程停止(例如通过SIGSTOP信号)࿰c;返回。
  • WCONTINUED:如果子进程恢复运行࿰c;返回。

示例

<code class="prism language-c">class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><stdio.h>
class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><stdlib.h>
class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><sys/wait.h>
class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><unistd.h>class="token keyword">int class="token function">mainclass="token punctuation">(class="token punctuation">) class="token punctuation">{class="token class-name">pid_t pid class="token operator">= class="token function">forkclass="token punctuation">(class="token punctuation">)class="token punctuation">;class="token keyword">if class="token punctuation">(pid class="token operator">== class="token number">0class="token punctuation">) class="token punctuation">{class="token comment">// 子进程class="token function">printfclass="token punctuation">(class="token string">"Child process\n"class="token punctuation">)class="token punctuation">;class="token function">sleepclass="token punctuation">(class="token number">2class="token punctuation">)class="token punctuation">;  class="token comment">// 模拟子进程运行class="token function">exitclass="token punctuation">(class="token number">0class="token punctuation">)class="token punctuation">;class="token punctuation">} class="token keyword">else class="token punctuation">{class="token comment">// 父进程class="token keyword">int statusclass="token punctuation">;class="token class-name">pid_t result class="token operator">= class="token function">waitpidclass="token punctuation">(pidclass="token punctuation">, class="token operator">&statusclass="token punctuation">, WNOHANGclass="token punctuation">)class="token punctuation">;  class="token comment">// 非阻塞等待class="token keyword">if class="token punctuation">(result class="token operator">== class="token number">0class="token punctuation">) class="token punctuation">{class="token function">printfclass="token punctuation">(class="token string">"Child is still running\n"class="token punctuation">)class="token punctuation">;class="token punctuation">} class="token keyword">else class="token keyword">if class="token punctuation">(result class="token operator">== pidclass="token punctuation">) class="token punctuation">{class="token keyword">if class="token punctuation">(class="token function">WIFEXITEDclass="token punctuation">(statusclass="token punctuation">)class="token punctuation">) class="token punctuation">{class="token function">printfclass="token punctuation">(class="token string">"Child exited with status %d\n"class="token punctuation">, class="token function">WEXITSTATUSclass="token punctuation">(statusclass="token punctuation">)class="token punctuation">)class="token punctuation">;class="token punctuation">}class="token punctuation">}class="token punctuation">}class="token keyword">return class="token number">0class="token punctuation">;
class="token punctuation">}
code>

在这个例子中࿰c;父进程使用非阻塞的 waitpid() 来检查子进程的状态࿰c;如果子进程还未退出࿰c;则返回 0。

waitid()

waitid() 是 waitpid() 的另一种变体࿰c;提供了更细力度的控制࿰c;允许父进程获取更多的状态信息࿰c;如进程是否由于信号终止等。

语法

<code class="prism language-c">class="token keyword">int class="token function">waitidclass="token punctuation">(class="token class-name">idtype_t idtypeclass="token punctuation">, class="token class-name">id_t idclass="token punctuation">, class="token class-name">siginfo_t class="token operator">*infopclass="token punctuation">, class="token keyword">int optionsclass="token punctuation">)class="token punctuation">;
code>
  • idtype:指定要等待的进程类别(如 P_PID 等)。
  • id:指定要等待的进程 ID。
  • infop:存储关于终止进程的详细信息。
  • options:与 waitpid() 类似࿰c;控制等待行为。

阻塞和非阻塞等待详解

阻塞等待

默认的 wait() 和 waitpid() 都是阻塞的࿰c;父进程会等待子进程的终止࿰c;直到获取子进程的退出状态。如果没有子进程可等待࿰c;父进程将挂起直到有子进程退出。

非阻塞等待

通过在 waitpid() 中传递 WNOHANG 选项࿰c;可以使父进程进行非阻塞等待。如果没有子进程退出࿰c;父进程会立即返回࿰c;程序可以继续执行其他任务。

<code class="prism language-c">class="token class-name">pid_t class="token function">waitpidclass="token punctuation">(class="token class-name">pid_t pidclass="token punctuation">, class="token keyword">int class="token operator">*statusclass="token punctuation">, class="token keyword">int optionsclass="token punctuation">)class="token punctuation">;
code>

其中࿰c;options 可以指定 WNOHANG࿰c;使得调用非阻塞。

  • WNOHANG:如果没有子进程退出࿰c;waitpid() 会立即返回 0࿰c;而不是阻塞等待。
  • WUNTRACED:如果子进程已停止࿰c;waitpid() 会返回࿰c;即使没有退出。

获取子进程的状态

通过 status 变量可以获取子进程的退出状态。父进程可以使用如下宏来分析 status:

  • WIFEXITED(status):检查子进程是否正常退出。
  • WIFSIGNALED(status):检查子进程是否由于信号导致终止。
  • WIFSTOPPED(status):检查子进程是否由于信号停止。
  • WEXITSTATUS(status):获取子进程的退出状态码(如果正常退出)。
  • WTERMSIG(status):获取导致子进程终止的信号编号。

总结

父进程等待子进程的必要性:防止僵尸进程、获取子进程的退出状态和同步操作。

等待方法:wait()、waitpid() 和 waitid() 提供不同的等待机制。

阻塞与非阻塞等待:wait() 和 waitpid() 默认阻塞࿰c;waitpid() 可以通过 WNOHANG 实现非阻塞。

获取子进程状态:通过 status 获取退出状态࿰c;并使用宏分析退出原因。

进程程序替换

进程替换是操作系统中用于管理进程执行的一种机制࿰c;特别是在多任务操作系统中。它的主要目的是通过上下文切换(Context Switch)实现多个进程的并发执行。操作系统通常通过进程调度来决定哪个进程在某一时刻占用 CPU࿰c;这个过程涉及到进程的保存和恢复状态࿰c;通常称为“进程替换”。

进程替换的基本概念

进程替换(或进程切换)是指操作系统从一个进程切换到另一个进程的过程。在进程替换的过程中࿰c;操作系统需要保存当前进程的执行状态࿰c;并恢复下一个进程的执行状态。它通常发生在以下几种情况:

  • 时间片到期:如果采用时间片轮转调度算法࿰c;当一个进程的时间片用完时࿰c;操作系统会选择另一个进程来占用 CPU。
  • 进程阻塞:如果进程因等待 I/O 操作、等待资源等原因进入阻塞状态࿰c;操作系统会选择其他进程来执行。
  • 进程终止:当一个进程执行完成或被终止时࿰c;操作系统会调度另一个进程来继续执行。
  • 高优先级进程到来:如果一个高优先级的进程被创建࿰c;操作系统可能会中断当前进程࿰c;切换到高优先级进程。

替换函数

<code class="prism language-c"> class="token macro property">class="token directive-hash">#class="token directive keyword">include class="token string"><unistd.h>class="token keyword">int class="token function">execlclass="token punctuation">(class="token keyword">const class="token keyword">char class="token operator">*pathclass="token punctuation">, class="token keyword">const class="token keyword">char class="token operator">*argclass="token punctuation">, class="token punctuation">.class="token punctuation">.class="token punctuation">.class="token punctuation">)class="token punctuation">;class="token keyword">int class="token function">execlpclass="token punctuation">(class="token keyword">const class="token keyword">char class="token operator">*fileclass="token punctuation">, class="token keyword">const class="token keyword">char class="token operator">*argclass="token punctuation">, class="token punctuation">.class="token punctuation">.class="token punctuation">.class="token punctuation">)class="token punctuation">;class="token keyword">int class="token function">execleclass="token punctuation">(class="token keyword">const class="token keyword">char class="token operator">*pathclass="token punctuation">, class="token keyword">const class="token keyword">char class="token operator">*argclass="token punctuation">, class="token punctuation">.class="token punctuation">.class="token punctuation">.class="token punctuation">,class="token keyword">char class="token operator">*class="token keyword">const envpclass="token punctuation">[class="token punctuation">]class="token punctuation">)class="token punctuation">;class="token keyword">int class="token function">execvclass="token punctuation">(class="token keyword">const class="token keyword">char class="token operator">*pathclass="token punctuation">, class="token keyword">char class="token operator">*class="token keyword">const argvclass="token punctuation">[class="token punctuation">]class="token punctuation">)class="token punctuation">;class="token keyword">int class="token function">execvpclass="token punctuation">(class="token keyword">const class="token keyword">char class="token operator">*fileclass="token punctuation">, class="token keyword">char class="token operator">*class="token keyword">const argvclass="token punctuation">[class="token punctuation">]class="token punctuation">)class="token punctuation">;class="token keyword">int class="token function">execveclass="token punctuation">(class="token keyword">const class="token keyword">char class="token operator">*pathclass="token punctuation">, class="token keyword">char class="token operator">*class="token keyword">const argvclass="token punctuation">[class="token punctuation">]class="token punctuation">, class="token keyword">char class="token operator">*class="token keyword">const envpclass="token punctuation">[class="token punctuation">]class="token punctuation">)class="token punctuation">;
code>

替换函数解释

c="https://i-blog.csdnimg.cn/direct/77fce5f02e4140f7a0182794f41238b7.png" alt="在这里插入图片描述" />

exec 系列函数是 Linux 系统中的一组用于进程替换的系统调用。这些函数会用新的程序替换当前进程的映像(即加载另一个可执行文件)࿰c;并且会将当前进程的控制转移到新程序中。进程替换后࿰c;原有的进程的代码、数据和堆栈等都会被新的程序所替换࿰c;但新程序会继承旧进程的进程标识符(PID)࿰c;文件描述符等。

这些函数有不同的变种࿰c;主要的区别在于传递参数和环境变量的方式。下面是各个 exec 系列函数的详细解释。

color="green">execl(const char *path, const char *arg, …)

原型

<code class="prism language-c">class="token keyword">int class="token function">execlclass="token punctuation">(class="token keyword">const class="token keyword">char class="token operator">*pathclass="token punctuation">, class="token keyword">const class="token keyword">char class="token operator">*argclass="token punctuation">, class="token punctuation">.class="token punctuation">.class="token punctuation">.class="token punctuation">, class="token punctuation">(class="token keyword">char class="token operator">*class="token punctuation">)class="token constant">NULLclass="token punctuation">)class="token punctuation">;
code>

解释

  • execl 是 “exec with a list of arguments” 的缩写。
  • path 是要执行的可执行文件的路径。
  • arg 是传递给新程序的第一个参数࿰c;后面可以跟任意数量的额外参数。
  • 参数列表必须以 NULL 结束࿰c;这与 C 风格的字符串数组类似。
  • execl 的特点是࿰c;参数是以可变参数的形式提供的(即通过 …)。
  • 该函数调用成功时࿰c;不会返回࿰c;当前进程将被新程序替换;如果发生错误࿰c;返回 -1࿰c;并设置 errno。

示例

<code class="prism language-c">class="token function">execlclass="token punctuation">(class="token string">"/bin/ls"class="token punctuation">, class="token string">"ls"class="token punctuation">, class="token string">"-l"class="token punctuation">, class="token punctuation">(class="token keyword">char class="token operator">*class="token punctuation">)class="token constant">NULLclass="token punctuation">)class="token punctuation">;
code>

这个例子会用 /bin/ls 程序替换当前进程࿰c;并传递 “ls” 和 “-l” 作为参数。

color="green"> execlp(const char *file, const char *arg, …)

原型

<code class="prism language-c">class="token keyword">int class="token function">execlpclass="token punctuation">(class="token keyword">const class="token keyword">char class="token operator">*fileclass="token punctuation">, class="token keyword">const class="token keyword">char class="token operator">*argclass="token punctuation">, class="token punctuation">.class="token punctuation">.class="token punctuation">.class="token punctuation">, class="token punctuation">(class="token keyword">char class="token operator">*class="token punctuation">)class="token constant">NULLclass="token punctuation">)class="token punctuation">;
code>

解释

  • execlp 类似于 execl࿰c;唯一的区别是它会在系统的 PATH 环境变量指定的路径中查找 file࿰c;从而不需要提供完整的路径。
  • file 是可执行文件的名称࿰c;而不是文件的完整路径。
  • 参数列表以 NULL 结尾。
  • 如果 execlp 成功࿰c;它将用新程序替换当前进程;否则࿰c;返回 -1。

示例

<code class="prism language-c">class="token function">execlpclass="token punctuation">(class="token string">"ls"class="token punctuation">, class="token string">"ls"class="token punctuation">, class="token string">"-l"class="token punctuation">, class="token punctuation">(class="token keyword">char class="token operator">*class="token punctuation">)class="token constant">NULLclass="token punctuation">)class="token punctuation">;
code>

这将会在 PATH 环境变量指定的路径中查找 ls 可执行文件࿰c;并将其作为新程序执行。

color="green"> execle(const char *path, const char *arg, …, char *const envp[])

原型

<code class="prism language-c">class="token keyword">int class="token function">execleclass="token punctuation">(class="token keyword">const class="token keyword">char class="token operator">*pathclass="token punctuation">, class="token keyword">const class="token keyword">char class="token operator">*argclass="token punctuation">, class="token punctuation">.class="token punctuation">.class="token punctuation">.class="token punctuation">, class="token keyword">char class="token operator">*class="token keyword">const envpclass="token punctuation">[class="token punctuation">]class="token punctuation">)class="token punctuation">;
code>

解释

  • execle 与 execl 类似࿰c;但它允许指定新的环境变量。环境变量是通过 envp[] 数组传递的。
  • path 是要执行的程序的完整路径。
  • 参数列表以 NULL 结束。
  • envp[] 是一个字符指针数组࿰c;其中每个元素是一个环境变量࿰c;格式通常是 key=value。
  • 调用成功时࿰c;当前进程将被新程序替换࿰c;失败时返回 -1。

示例

<code class="prism language-c">class="token keyword">char class="token operator">*envclass="token punctuation">[class="token punctuation">] class="token operator">= class="token punctuation">{ class="token string">"HOME=/home/user"class="token punctuation">, class="token string">"PATH=/bin"class="token punctuation">, class="token punctuation">(class="token keyword">char class="token operator">*class="token punctuation">)class="token constant">NULL class="token punctuation">}class="token punctuation">;
class="token function">execleclass="token punctuation">(class="token string">"/bin/ls"class="token punctuation">, class="token string">"ls"class="token punctuation">, class="token string">"-l"class="token punctuation">, class="token punctuation">(class="token keyword">char class="token operator">*class="token punctuation">)class="token constant">NULLclass="token punctuation">, envclass="token punctuation">)class="token punctuation">;
code>

这个例子将用 /bin/ls 程序替换当前进程࿰c;传递 “ls” 和 “-l” 作为参数࿰c;并且设置新的环境变量 HOME=/home/user 和 PATH=/bin。

color="green">execv(const char *path, char *const argv[])
原型

<code class="prism language-c">class="token keyword">int class="token function">execvclass="token punctuation">(class="token keyword">const class="token keyword">char class="token operator">*pathclass="token punctuation">, class="token keyword">char class="token operator">*class="token keyword">const argvclass="token punctuation">[class="token punctuation">]class="token punctuation">)class="token punctuation">;
code>

解释

  • execv 是 “exec with an array of arguments” 的缩写。
  • path 是要执行的程序的完整路径。
  • argv[] 是一个指向字符串数组的指针࿰c;数组的每个元素是程序的一个参数࿰c;第一个元素通常是程序的名称࿰c;最后一个元素必须是 NULL。
  • execv 不像 execl 那样使用可变参数࿰c;而是使用一个数组来传递所有参数。
  • 调用成功时࿰c;当前进程被新程序替换࿰c;失败时返回 -1。

示例

<code class="prism language-c">class="token keyword">char class="token operator">*argsclass="token punctuation">[class="token punctuation">] class="token operator">= class="token punctuation">{ class="token string">"ls"class="token punctuation">, class="token string">"-l"class="token punctuation">, class="token constant">NULL class="token punctuation">}class="token punctuation">;
class="token function">execvclass="token punctuation">(class="token string">"/bin/ls"class="token punctuation">, argsclass="token punctuation">)class="token punctuation">;
code>

这个例子用 /bin/ls 程序替换当前进程࿰c;并将 “ls” 和 “-l” 作为参数传递给新程序。

color="green"> execvp(const char *file, char *const argv[])
原型

<code class="prism language-c">class="token keyword">int class="token function">execvpclass="token punctuation">(class="token keyword">const class="token keyword">char class="token operator">*fileclass="token punctuation">, class="token keyword">char class="token operator">*class="token keyword">const argvclass="token punctuation">[class="token punctuation">]class="token punctuation">)class="token punctuation">;
code>

解释

  • execvp 类似于 execv࿰c;但它会在系统的 PATH 环境变量指定的路径中查找 file࿰c;而不需要提供完整路径。
  • file 是可执行文件的名称࿰c;argv[] 是一个参数数组࿰c;和 execv 一样。
  • 如果成功࿰c;它将用新程序替换当前进程;如果失败࿰c;返回 -1。

示例

<code class="prism language-c">class="token keyword">char class="token operator">*argsclass="token punctuation">[class="token punctuation">] class="token operator">= class="token punctuation">{ class="token string">"ls"class="token punctuation">, class="token string">"-l"class="token punctuation">, class="token constant">NULL class="token punctuation">}class="token punctuation">;
class="token function">execvpclass="token punctuation">(class="token string">"ls"class="token punctuation">, argsclass="token punctuation">)class="token punctuation">;
code>

这个例子会在 PATH 环境变量中查找 ls 程序࿰c;并将 “ls” 和 “-l” 作为参数传递给新程序。

color="green"> execve(const char *path, char *const argv[], char *const envp[])

原型

<code class="prism language-c">class="token keyword">int class="token function">execveclass="token punctuation">(class="token keyword">const class="token keyword">char class="token operator">*pathclass="token punctuation">, class="token keyword">char class="token operator">*class="token keyword">const argvclass="token punctuation">[class="token punctuation">]class="token punctuation">, class="token keyword">char class="token operator">*class="token keyword">const envpclass="token punctuation">[class="token punctuation">]class="token punctuation">)class="token punctuation">;
code>

解释

  • execve 是所有 exec 系列函数的基础。它直接调用内核࿰c;执行一个新的程序。
  • path 是要执行的程序的完整路径。
  • argv[] 是一个指向字符串数组的指针࿰c;数组的每个元素是程序的一个参数࿰c;第一个元素通常是程序的名称࿰c;最后一个元素必须是 NULL。
  • envp[] 是一个指向环境变量的指针数组࿰c;格式通常是 key=value。
  • execve 是唯一一个可以直接指定环境变量的 exec 函数࿰c;其他的函数(如 execlp、execvp)都是在内部调用 execve 并传递环境变量。

示例

<code class="prism language-c">class="token keyword">char class="token operator">*argsclass="token punctuation">[class="token punctuation">] class="token operator">= class="token punctuation">{ class="token string">"ls"class="token punctuation">, class="token string">"-l"class="token punctuation">, class="token constant">NULL class="token punctuation">}class="token punctuation">;
class="token keyword">char class="token operator">*envclass="token punctuation">[class="token punctuation">] class="token operator">= class="token punctuation">{ class="token string">"HOME=/home/user"class="token punctuation">, class="token string">"PATH=/bin"class="token punctuation">, class="token constant">NULL class="token punctuation">}class="token punctuation">;
class="token function">execveclass="token punctuation">(class="token string">"/bin/ls"class="token punctuation">, argsclass="token punctuation">, envclass="token punctuation">)class="token punctuation">;
code>

这个例子用 /bin/ls 程序替换当前进程࿰c;传递 “ls” 和 “-l” 作为参数࿰c;并设置新的环境变量 HOME=/home/user 和 PATH=/bin。

总结

  • execl 传递参数以可变参数列表形式࿰c;路径由完整路径指定
  • execlp 传递参数以可变参数列表形式࿰c;路径由 PATH 环境变量查找
  • execle 传递参数以可变参数列表形式࿰c;并允许设置新的环境变量
  • execv 传递参数通过字符串数组࿰c;路径由完整路径指定
  • execvp 传递参数通过字符串数组࿰c;路径由 PATH 环境变量查找
  • execve 传递参数通过字符串数组࿰c;允许设置新的环境变量

color="green">这些 exec 系列函数都能替换当前进程࿰c;并且只会在失败时返回 -1࿰c;成功时不会返回。


http://www.ppmy.cn/devtools/137495.html

相关文章

docker创建vue镜像

1.确保你已经安装了 Node.js 和 Vue CLI。 2.创建一个 Vue.js 项目&#xff08;如果你还没有一个&#xff09; vue create my-vue-app 3.进入目录 cd my-vue-app 4.构建vue.js npm run build 5.创建一个 Dockerfile 来构建 Vue 应用的 Docker 镜像&#xff1a; # 基于 Node 官方…

多商户系统推动旅游业数字化升级与创新,定制化旅游促进市场多元化发展

国内旅游市场一直保持着强劲的发展势头。随着国内居民收入水平的提高、消费观念的转变以及交通条件的极大改善&#xff0c;国内旅游人数持续攀升。无论是传统的热门旅游城市&#xff0c;如北京、上海、杭州等&#xff0c;还是新兴的旅游目的地&#xff0c;如成都、重庆、西安等…

Vue2+el-table实现表格行上下滚动,表格单元格内容溢出左右滚动 TextScroll、TableWrapper

Vue2el-table实现表格行上下滚动&#xff0c;表格单元格内容溢出左右滚动 TextScroll、TableWrapper TextScroll 文本左右滚动容器组件 <template><div ref"wrapper" class"scroll-wrapper" mouseenter"mouseenter" mouseleave"…

【Mybatis】动态SQL详解

文章目录 动态SQLifsetifwhereiftrimforeachchoosesql 动态SQL <if> 用于条件判断&#xff0c;决定是否包含某个SQL片段。 ifset <set> 用于动态生成 SET 子句&#xff0c;自动处理多余的逗号。 <!-- 更新用户信息 --> <update id"edit" &g…

自定义 RouterLink 组件 v-slot custom

高级自定义&#xff1a;通过 v-slot 使用插槽 API 如果你需要更复杂的自定义逻辑或更加灵活的渲染&#xff0c;可以结合 v-slot 来实现&#xff1a; <template><router-link :to"to" custom v-slot"{ navigate }"><div click"navigat…

【git】取消一个已提交的文件或路径的追踪

在 Git 中&#xff0c;如果想取消对一个已提交的文件或路径的追踪&#xff0c;有几种方法可以实现这一点&#xff0c;具体取决于实际场景。以下是几种常见的方法&#xff1a; 1. 从索引中移除文件&#xff08;暂存区&#xff09; 如果只是希望取消对某个文件的追踪&#xff0…

极狐GitLab 17.6 正式发布几十项与 DevSecOps 相关的功能【一】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…

VirtIO实现原理之数据结构与数据传输演示(2)

接前一篇文章:VirtIO实现原理之数据结构与数据传输演示(1) 本文内容参考: VirtIO实现原理——vring数据结构-CSDN博客 VirtIO实现原理——数据传输演示-CSDN博客 特此致谢! 一、数据结构总览 2. 相关数据结构 上一回读了《Virtual I/O Device (VIRTIO) Version 1.3》…