当前位置: 首页 > news >正文

【Linux系统】探索进程等待与程序替换的奥秘

在这里插入图片描述

文章目录

  • 前言
    • 一、重谈进程创建
      • 1.1 fork 函数
      • 1.2 写时拷贝
      • 1.3 fork 的常规用法
      • 1.4 fork 调用失败的原因
      • 1.5 创建一批进程
    • 二、进程终止
      • 2.1 进程退出场景
      • 2.2 strerror 函数的作用
      • 2.3 `errno` 全局变量
      • 2.4 程序异常机制
      • 2.5 进程退出方式
    • 三、进程等待
      • 3.1 进程等待必要性
      • 3.2 进程等待的方法
        • 1. `wait`方法
        • 2. `waitpid` 方法
        • 3. `wait` 和 `waitpid` 的本质
      • 3.3 非阻塞轮询等待
        • 1. 使用场景
        • 2. 关键点
    • 四、程序替换
      • 4.1 单进程版程序替换
      • 4.2 程序替换的基本原理
      • 4.3 程序替换七大接口
        • 1. execl
        • 2. execlp
        • 3. execle
        • 4. execv
        • 5. execvp
        • 6. execve
        • 7. execvpe
        • 总结:七大接口对比
      • 4.4 练习:自定义命令行参数和环境变量进行程序替换
  • 结语


前言

在 Linux 操作系统的世界里,进程是程序运行的动态实体,它们如同一个个忙碌的工作者,承载着系统中各种任务的执行。无论是系统服务的稳定运行,还是用户程序的交互响应,都离不开进程的支持。深入理解进程的生命周期,包括创建、终止、等待以及程序替换等关键环节,对于掌握 Linux 系统编程和开发高性能应用程序至关重要。


一、重谈进程创建

1.1 fork 函数

  • 在 Linux 系统中,fork 函数可从已有进程创建新进程。新进程是子进程,原进程为父进程。其函数声明为 pid_t fork(void),返回值规则为:子进程中返回 0,父进程中返回子进程的 pid,出错则返回 -1。

    #include <unistd.h>
    pid_t fork(void); 
    
  • 当进程调用 fork 函数,控制转移到内核代码后,内核会进行以下操作:

    • 为子进程分配新的内存块和内核数据结构;
    • 将父进程部分数据结构内容复制到子进程;
    • 把子进程添加到系统进程列表;
    • fork 返回并开始调度。

    实际上,完成前两步,子进程就已创建。

通过代码演示,能看到 fork 前父进程独自执行,fork 后父子进程分别执行,且谁先执行由调度器决定。

#include <stdio.h>    
#include <unistd.h>    int main()    
{    printf("befor pid:%d\n", getpid());    fork();    printf("after pid:%d\n", getpid());                                                                                                                                                                          return 0;    
}

在这里插入图片描述

1.2 写时拷贝

  • 通常,父子进程的代码是共享的,在不写入数据时,数据也共享。一旦任意一方尝试写入,就会以写时拷贝的方式再生成一份数据。

  • 操作系统通过在创建子进程时,将父子进程页表中的数据项权限设为只读来实现写时拷贝。当父子进程写入数据触发权限问题时,操作系统会识别并拷贝数据,重新映射页表项,再将权限恢复为可读可写。

  • 采用写时拷贝的原因在于避免不必要的数据拷贝。若父进程有大量数据,而子进程仅需修改少量数据,全部拷贝会浪费时间和物理内存,所以操作系统采用此策略。

1.3 fork 的常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端的请求,生成子进程来处理请求。

  • 一个进程要执行一个不同的程序。例如子进程从 fork 返回后,调用 exec 函数。

1.4 fork 调用失败的原因

  • 系统中有太多的进程。

  • 实际用户的进程数超过了限制。

1.5 创建一批进程

#include <stdio.h>      
#include <unistd.h>      
#include <stdlib.h>      #define N 5                                                                                            void func()           
{                      int cnt = 10;      while(cnt)        {                                                                       printf("I am chid, pid:%d, ppid:%d\n", getpid(), getppid());      cnt--;                                                            sleep(1);                                                         }                 return;           
}                     int main()            
{                     int i = 0;                  for(i = 0; i < N; i++)      {                           pid_t id = fork();                  if(id == 0)// 只有子进程会进去      {                                 func();                                exit(0);// 子进程走到这里就退出了      }                                        }                                            sleep(1000);      return 0;       
}

在这里插入图片描述
父进程执行的速度是很快的,由于父进程的 for 循环里没有 sleep 函数,所以五个子进程几乎是在同一时间被创建出来,创建出来的每一个子进程会去调用 func 函数,每一个子进程执行完 func 函数后会执行 exit 函数退出。父子进程谁先执行完全是由调度器来决定的。

二、进程终止

2.1 进程退出场景

进程终止意味着其生命周期结束,通常包括以下场景:

  • 正常退出:代码运行完毕,结果正确。
  • 异常退出:代码运行完毕但结果不正确,或因错误终止(如未捕获的异常)。
  • 外部干预:被用户或其他进程终止(如 kill 命令)。

main 函数里常写的 return 0 的作用,就是每个进程终止时都会返回的一个退出码(Exit Code),用于标识运行结果:

  • 0:表示成功(Success)。
  • 非 0:表示失败或异常,不同值表征不同错误。

提示:父进程最关心子进程的运行情况,main 函数返回的退出码会被父进程(如 bash)获取并保留,可通过 echo $? 查看最近进程的退出码。

int main()    
{    printf("模拟一段逻辑!\n");    return 0;                                                                                          
}

在这里插入图片描述

2.2 strerror 函数的作用

strerror 函数是一个标准 C 库函数,用于将错误码转换为可读的错误信息字符串。它非常适合程序员在调试和错误处理时,将数字形式的错误码(如 errno)转化为更直观的文本信息,方便理解和输出。

下面的代码展示了如何遍历并打印当前系统支持的所有错误码及其对应的描述信息:

#include <stdio.h>
#include <string.h>int main() {for (int i = 0; i < 200; i++) {printf("%d: %s\n", i, strerror(i));  // 输出错误码及其描述}return 0;
}

说明

  1. strerror 仅对系统支持的错误码返回有效的错误描述。
  2. 如果传入的错误码无效或系统未定义的错误,返回 "Unknown error"

在这里插入图片描述

2.3 errno 全局变量

errno 是 C 语言提供的全局变量,记录最近一次函数调用失败时的错误码。当调用 C 标准库函数失败,errno 会被赋值为特定数值,结合 strerror 函数可获取错误详细信息。例如 malloc 内存分配失败时,通过 errnostrerror 输出错误码及描述。

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <string.h>    
#include <errno.h>                                                                                   int main()    
{    int ret = 0;    char* str = (char*)malloc(1000*1000*1000*4);    if(str == NULL)    {    printf("malloc error:%d, %s\n", errno, strerror(errno));    ret = errno;    }    else    {    printf("malloc success!\n");    }    return ret;    
}

在这里插入图片描述

2.4 程序异常机制

程序异常(如除零操作、空指针解引用)会导致进程未执行到 return 语句即终止,此时退出码无参考价值。程序异常本质是进程接收到操作系统发送的信号,如空指针解引用会触发段错误信号,导致进程异常结束。Linux 系统的所有信号如下图所示:
在这里插入图片描述

2.5 进程退出方式

进程退出分为正常终止和异常终止两类:

  • 正常终止:代码完整执行完毕,包括 main 函数 return 语句返回、调用库函数 exit、调用系统函数 _exit
  • 异常终止:通过 Ctrl + C 中断,或因接收到特定信号导致进程强制结束。

三、进程等待

3.1 进程等待必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成僵尸进程的问题,进而造成内存泄漏。
  • 此外,进程一旦变成僵尸状态,那就刀枪不入,就算是"杀人不眨眼"的 kill -9 也无能为力,因为谁也没有办法杀死一个死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息。

3.2 进程等待的方法

进程等待就是在父进程的代码中,通过系统调用 wait/waitpid,来进行对子进程进行状态检测与回收的功能。
在这里插入图片描述

1. wait方法

在 Linux 系统中,wait 方法是用于等待子进程结束的系统调用或库函数。其功能是让父进程挂起,直到一个子进程终止,并返回子进程的终止状态。

1. 功能描述

  • wait() 方法会暂停父进程的执行,直到任意一个子进程结束。
  • 当子进程终止时,父进程会从子进程处获取其退出状态。
  • 如果没有子进程,wait() 会立即返回 -1,并设置错误信息为 ECHILD

2. 原型

#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *status);

3. 参数解释

  • status:
    • 一个指针,指向存储子进程退出状态的整数。
    • 父进程可以通过宏(如 WIFEXITEDWEXITSTATUS 等)解析该状态。

4. 返回值

  • 返回终止的子进程的 PID(进程 ID)。
  • 如果出错,返回 -1,并设置 errno

5. 父进程只等待一个进程(阻塞式等待)

以下是一个使用 wait() 的简单示例:

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <sys/types.h>    
#include <sys/wait.h>    int main()    
{    pid_t id = fork();    if(id < 0)    {    perror("fork");    return 1;    }    else if(id == 0)    {    // child    int cnt = 5;    while(cnt)    {    printf("I am child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);    sleep(1);    }    exit(0);    }    else    {    int cnt = 10;    // parent    while(cnt)    {    printf("I am parent, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);    sleep(1);    }    int ret = wait(NULL);    if(ret == id)    {    printf("wait success!\n, ret:%d\n", ret);    }    sleep(5);                                                             }    return 0;    
}

注意:前五秒父子进程同时运行,紧接着子进程退出变成僵尸状态,五秒钟后父进程对子进程进行了等待,成功将子进程释放掉,最后再五秒钟后父进程也退出,整个程序执行结束。
在这里插入图片描述
在这里插入图片描述

6. 父进程等待多个子进程(阻塞式等待)

一个 wait 只能等待任意一个子进程,因此父进程如果要等待多个子进程可以通过循环来多次调用 wait 实现等待多个子进程。

#include <stdio.h>    
#include <unistd.h>    
#include <stdlib.h>    
#include <sys/types.h>    
#include <sys/wait.h>    #define N 5    
// 父进程等待多个子进程    
void RunChild()    
{    int cnt = 5;    while(cnt--)    {    printf("I am child, pid:%d, ppid:%d\n", getpid(), getppid());    sleep(1);    }    return;    
}    
int main()    
{    for(int i = 0; i < N; i++)    {    pid_t id = fork();// 创建一批子进程    if(id == 0)    {    // 子进程    RunChild();    exit(0);    }    // 父进程    printf("Creat process sucess:%d\n", id);    }    sleep(10);    for(int i = 0; i < N; i++)    {    pid_t id = wait(NULL);                                                                           if(id > 0)    {    printf("Wait process:%d, success!\n", id);    }    }    sleep(5);    return 0;    
}

2. waitpid 方法

waitpidwait 的增强版本,它可以指定等待特定的子进程终止,同时支持非阻塞等待。相比 waitwaitpid 提供了更大的灵活性,适用于复杂的进程管理场景。

1. 函数原型

#include <sys/types.h>
#include <sys/wait.h>pid_t waitpid(pid_t pid, int *status, int options);

2. 参数说明

  1. pid
    • 用于指定需要等待的子进程。
    • 各种取值的含义:
      • pid > 0:等待进程 ID 为 pid 的子进程。
      • pid == 0:等待与调用进程属于同一进程组的任何子进程。
      • pid < -1:等待进程组 ID 为 |pid|(绝对值)的所有子进程。
      • pid == -1:等待任意子进程(等效于 wait())。
  2. status
    • 指向一个整数,用于存储子进程的退出状态。
    • 可以通过一系列宏来解析 status 的值(见 状态解析宏 部分)。
  3. options
    • 控制 waitpid 的行为。
    • 常见取值:
      • WNOHANG:非阻塞等待。如果没有子进程终止,立即返回 0
      • WUNTRACED:如果子进程由于信号暂停(但未终止),则返回。
      • WCONTINUED:如果子进程接收到 SIGCONT 信号恢复运行,则返回。

3. 返回值

  • 成功
    • 返回终止的子进程的 PID。
    • 如果使用 WNOHANG 且没有子进程终止,则返回 0
  • 失败
    • 返回 -1,并设置 errno。常见错误包括:
      • ECHILD:没有符合条件的子进程。
      • EINTR:调用被信号中断。

4. 状态解析宏

子进程退出状态存储在 status 参数中,可以通过以下宏解析:

  • WIFEXITED(status)
    • 返回非零值,表示子进程正常终止。
  • WEXITSTATUS(status)
    • 返回子进程的退出码(仅当 WIFEXITED 为真时)。

5. 获取子进程的退出信息(阻塞式等待)

进程有三种退出场景,父进程等待希望获得子进程退出的以下信息:子进程代码是否异常?没有异常,结果对嘛?不对是因为什么呢? 子进程这些所有的退出信息都被保存在 status 参数里面。

  • waitwaitpid 都有一个 status 参数,该参数是一个输出型参数,由操作系统填充。

  • 如果传递 NULL,表示不关心子进程的退出状态信息。

  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

  • status 不能简单的当做整型来看待,例如 int 型总共占 32 位, 则具体细节需要关注位,如下图:
    在这里插入图片描述
    注意:操作系统没有0号信号,因此,如果低七位是0说明子进程没有收到任何信号。

示例代码:

#include <stdio.h> 
#include <unistd.h>    
#include <stdlib.h> 
#include <errno.h>
#include <sys/types.h>    
#include <sys/wait.h> int main()
{pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id == 0){// childint cnt = 5, a = 10;while(cnt){printf("I am child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);sleep(1);a /= 0; // 故意制造一个异常}exit(11); // 将退出码故意设置成11}else {// parentint cnt = 10;while(cnt){printf("I am parent, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);sleep(1); }// 目前为止,进程等待是必须的!//int ret = wait(NULL);int status = 0;int ret = waitpid(id, &status, 0);if(ret == id){// 获取子进程退出状态信息的关键代码// 0111 1111:0x7F,1111 1111 0000 0000:0xFF00printf("wait success! exit signal:%d, exit code:%d!\n", status&0X7F, (status >> 8)&0XFF); }sleep(5);}return 0;
}

在这里插入图片描述
在代码运行结果中,我们可以看到以下几点:

  1. 子进程在运行过程中,执行了 a /= 0; 的除零操作,触发了 SIGFPE 信号(信号编号为 8),导致子进程异常终止。
  2. 子进程未能正常执行完 exit(11),因此我们设置的退出码 11 并没有生效。
  3. 子进程的退出码显示为 0,这是因为进程收到信号并被异常终止时,退出码是不可信的。

6. 一般的进程等待代码

int status = 0;
int ret = waitpid(id, &status, 0);
if(ret == id)
{// 0111 1111:0x7F,1111 1111 0000 0000:0xFF00//printf("wait success! exit signal:%d, exit code:%d!\n", status&0X7F, (status >> 8)&0XFF);if(WIFEXITED(status)){printf("子进程正常退出,退出码是:%d\n", WEXITSTATUS(status));}else {printf("子进程被异常终止!\n");}
}
3. waitwaitpid 的本质

waitwaitpid 的核心工作是:

  1. 检查子进程状态:
    • 操作系统会检查子进程是否已经处于 僵尸状态Z 状态)。
  2. 读取子进程的退出状态:
    • 如果子进程已经终止,操作系统会从其 PCB 中读取退出状态(包括信号和退出码)。
  3. 释放子进程的 PCB:
    • 回收子进程的 PCB 资源,将其从僵尸状态变为完全销毁的状态。
  4. 将状态信息返回给父进程:
    • 父进程通过 status 获取子进程的退出状态信息。

3.3 非阻塞轮询等待

非阻塞轮询等待的核心思想是,父进程在等待子进程的同时,继续执行其他任务,而不是一直阻塞在等待操作上。通过 waitpidWNOHANG 选项,可以实现非阻塞的轮询等待。

1. 使用场景
  • 父进程需要同时执行其他任务,并且不希望因为等待子进程而被阻塞。
  • 多任务处理时,比如监听其他事件或继续其他逻辑的执行。
2. 关键点
  • 使用 waitpidWNOHANG选项:

    • 如果有子进程已退出,则返回该子进程的 PID。

    • 如果没有子进程退出,则立即返回 0

    • 如果发生错误,则返回 -1 并设置 errno

  • 父进程通过轮询检查子进程的状态。

  • 在轮询中加入适当的延迟(如 sleep),以避免占用过多 CPU。

示例代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/wait.h>// 父进程只等待一个子进程(非阻塞轮询等待)
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id == 0){// childint cnt = 5;while(cnt){printf("I am child, pid:%d, ppid:%d, cnt:%d\n", getpid(), getppid(), cnt--);sleep(1);}exit(11);}else {// parent // 目前为止,进程等待是必须的!while(1){int status = 0;int ret = waitpid(id, &status, WNOHANG);if(ret > 0){if(WIFEXITED(status)){printf("子进程正常退出,退出码是:%d\n", WEXITSTATUS(status));}else {printf("子进程被异常终止!\n");}break;}else if(ret == 0){// 父进程的任务可以写在这里printf("child process is running...\n");}else{printf("等待出错!\n");}sleep(1);}sleep(2);}return 0;
}

注意:进程等待在一定程度上确保了父进程一定是最后一个退出的,这样可以避免子进程变为僵尸进程,进而导致内存泄露的问题。
在这里插入图片描述

四、程序替换

4.1 单进程版程序替换

#include <stdio.h>
#include <unistd.h>int main()
{printf("befor: I am a process, pid:%d, ppid:%d\n", getpid(), getppid());// exec类函数的标准写法// execl("/usr/bin/ls", "ls", "-a", "-l", NULL);execl("/usr/bin/top", "top", NULL);printf("after: I am a process, pid:%d, ppid:%d\n", getpid(), getppid());return 0;
}

输出示例

在这里插入图片描述

4.2 程序替换的基本原理

函数原型

int execl(const char *path, const char *arg0, ..., NULL);
  • execl 是一个可变参数函数,用于执行程序替换操作。
  • 它的第一个参数是可执行程序的路径(path)。
  • 后续参数是传递给该程序的命令行参数(argv),并以 NULL 结尾。

各个参数意义

  • /bin/ls:可执行程序的绝对路径,这是需要加载的新程序。
  • "ls":新程序的第一个命令行参数(argv[0]),通常用作程序名(与 shell 中运行 ls 类似)。
  • NULL:表示参数列表结束,这是可变参数函数的结束标志。

程序替换行为

  • 当前进程的代码段、数据段等被新程序(ls)替换。
  • 当前进程的 PID 保持不变,但其执行的内容变为 /bin/ls
  • 此处,ls 是 Linux 中的一个命令行工具,用于列出目录中的文件和目录。

注意点

  • 如果 execl 成功执行,新程序的代码替换当前程序,后续代码(如 printf)不会执行。
  • 如果 execl 执行失败(例如路径不存在或权限不足),execl 会返回 -1,并设置 errno。通常在失败时用 perrorstrerror(errno) 打印错误原因。

4.3 程序替换七大接口

在 Linux 中,程序替换操作(Program Replacement)是通过 exec 系列接口 实现的。这些接口将当前进程的执行内容替换为新程序,并保留原有进程的 PID。以下是 exec 系列的七个接口及其用法。


1. execl

(上文已经解释,这里不再过多赘述。)


2. execlp
int execlp(const char *file, const char *arg0, ..., NULL);
  • 描述:与 execl 类似,但会在环境变量 PATH 中搜索 file,而无需指定绝对路径。

  • 参数:

    • file: 可执行程序的名称(会从 PATH 中查找)。
    • 其他参数与 execl 一致。
  • 示例:

    execlp("ls", "ls", "-l", "-a", NULL);
    
    • 替换当前进程为 ls 程序,execlp 会自动在 /bin 等目录中查找 ls

3. execle
int execle(const char *path, const char *arg0, ..., NULL, char *const envp[]);
  • 描述:与 execl 类似,但允许显式指定新的环境变量 envp

  • 参数:

    • path: 新程序的文件路径。
    • arg0: 通常为程序名(argv[0])。
    • ..., NULL: 命令行参数。
    • envp[]: 指定的环境变量数组(如 {"KEY=VALUE", NULL})。
  • 示例:

    char *envp[] = {"MY_ENV=HelloWorld", NULL};
    execle("/usr/bin/env", "env", NULL, envp);
    
    • 替换当前进程为 env 程序,并设置环境变量 MY_ENV=HelloWorld

4. execv
int execv(const char *path, char *const argv[]);
  • 描述:与 execl 类似,但通过数组传递命令行参数。

  • 参数:

    • path: 新程序的文件路径。
    • argv[]: 参数数组,argv[0] 通常是程序名,最后一项必须为 NULL
  • 示例:

    char *argv[] = {"ls", "-l", "-a", NULL};
    execv("/bin/ls", argv);
    
    • 替换当前进程为 /bin/ls 程序,使用参数数组。

5. execvp
int execvp(const char *file, char *const argv[]);
  • 描述:与 execv 类似,但会在环境变量 PATH 中搜索程序。

  • 参数:

    • file: 程序名(会从 PATH 环境变量中查找)。
    • argv[]: 参数数组。
  • 示例:

    char *argv[] = {"ls", "-l", "-a", NULL};
    execvp("ls", argv);
    
    • 替换当前进程为 ls 程序,execvp 会从 PATH 中查找 ls

6. execve
int execve(const char *path, char *const argv[], char *const envp[]);
  • 描述:是所有 exec 系列函数的底层系统调用,直接调用内核执行程序替换。

  • 参数:

    • path: 新程序的文件路径。
    • argv[]: 参数数组。
    • envp[]: 环境变量数组。
  • 示例:

    char *argv[] = {"ls", "-l", "-a", NULL};
    char *envp[] = {"MY_ENV=HelloWorld", NULL};
    execve("/bin/ls", argv, envp);
    
    • 替换当前进程为 /bin/ls 程序,并显式传递参数和环境变量。

7. execvpe
int execvpe(const char *file, char *const argv[], char *const envp[]);
  • 描述:扩展接口,结合了 execvp 和显式的环境变量指定功能。

  • 参数:

    • file: 程序名(会从 PATH 环境变量中查找)。
    • argv[]: 参数数组。
    • envp[]: 环境变量数组。
  • 注意:此函数并非 POSIX 标准,某些系统可能不支持。

  • 示例:

    char *argv[] = {"ls", "-l", "-a", NULL};
    char *envp[] = {"MY_ENV=HelloWorld", NULL};
    execvpe("ls", argv, envp);
    

总结:七大接口对比
接口参数传递方式是否使用 PATH是否支持显式 envp
execl可变参数列表
execlp可变参数列表
execle可变参数列表 + envp
execv参数数组
execvp参数数组
execve参数数组 + envp
execvpe参数数组 + envp

注意点

  • 替换后的进程依然会继承原父进程的环境变量。

  • 在使用第三个参数 envp 的时候,要注意此时新替换的进程将会覆盖原来父进程的环境变量。

  • 七大接口均是 exec* 的形式,后缀中 l(list) 表示列表,v(vector) 表示数组,p 表示是否使用环境变量 PATHe 表示是否支持显式 envp

4.4 练习:自定义命令行参数和环境变量进行程序替换

1. mycommand.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main(int argc, char* argv[], char* env[])
{pid_t id = fork();if(id == 0){// childprintf("before: I am process, pid: %d, ppid: %d\n", getpid(), getppid());// 自定义命令行参数char *const myargv[] = {"otherExe", "-a", "-b", "-c", NULL};// 自定义环境变量char *const myenv[] = {"MY_YAL=123456","MY_NAME=lesson17",NULL};execve("./otherExe", myargv, myenv);printf("after: I am process, pid: %d, ppid: %d\n", getpid(), getppid());exit(1);}// fatherpid_t ret = waitpid(id, NULL, 0);if(ret > 0) printf("wait success, father pid: %d, ret id: %d\n", getpid(), ret);return 0;
}

2. otherExe.cpp

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
using namespace std;int main(int argc, char* argv[], char* env[])
{cout << "这是命令行参数" << endl;for(int i = 0; argv[i]; i++){cout << i << " : " << argv[i] << endl;}cout << "这是环境变量" << endl;for(int j = 0; env[j]; j++){cout << j << " : " << env[j] << endl;}return 0;
}

3. makefile

.PHONY: all
all: otherExe mycommand
mycommand: mycommand.cgcc -o $@ $^ -std=c99
otherExe: otherExe.cppg++ -o $@ $^ -std=c++11
.PHONY: clean
clean:rm -f mycommand otherExe

在这里插入图片描述
不难看出,环境变量已经被覆盖了。


结语

至此,我们完成了对 Linux 进程从创建到替换全流程的深入探讨。从fork函数的神奇复制,到进程终止时的各种场景与退出方式;从进程等待对资源回收和状态获取的重要性,到程序替换实现进程功能蜕变的原理与多样接口,每一个环节都展现了 Linux 进程管理的精妙之处。

今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,17的主页还有很多有趣的文章,欢迎小伙伴们前去点评,您的支持就是17前进的动力!

在这里插入图片描述

相关文章:

【Linux系统】探索进程等待与程序替换的奥秘

文章目录 前言一、重谈进程创建1.1 fork 函数1.2 写时拷贝1.3 fork 的常规用法1.4 fork 调用失败的原因1.5 创建一批进程 二、进程终止2.1 进程退出场景2.2 strerror 函数的作用2.3 errno 全局变量2.4 程序异常机制2.5 进程退出方式 三、进程等待3.1 进程等待必要性3.2 进程等待…...

Github 2025-05-06Python开源项目日报 Top10

根据Github Trendings的统计,今日(2025-05-06统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目10C++项目2TypeScript项目1系统设计指南 创建周期:2507 天开发语言:Python协议类型:OtherStar数量:241693 个Fork数量:42010 次…...

【愚公系列】《Manus极简入门》021-音乐创作助手:“音符魔术师”

&#x1f31f;【技术大咖愚公搬代码&#xff1a;全栈专家的成长之路&#xff0c;你关注的宝藏博主在这里&#xff01;】&#x1f31f; &#x1f4e3;开发者圈持续输出高质量干货的"愚公精神"践行者——全网百万开发者都在追更的顶级技术博主&#xff01; &#x1f…...

【Azure Redis】Redis导入备份文件(RDB)失败的原因

问题描述 在测试Azure Redis的导入/导出备份文件的功能中&#xff0c;突然发现在Redis 4.0上导入的时候&#xff0c;一直报错。 image.png 问题解答 因为门户上只是显示导入失败&#xff0c;没有任何错误消息说明。根据常理推断&#xff0c;Redis 的RDB文件格式都具有一致性。居…...

git “分离头指针”(detached HEAD) 状态。

在 Git 中&#xff0c;当你运行 git branch 命令时&#xff0c;看到如下输出&#xff1a; * (detached from 5b596b5)master 其中的&#xff1a; * (detached from 5b596b5) 表示你当前处于 “分离头指针”&#xff08;detached HEAD&#xff09; 状态。 &#x1f9e0; 什…...

Gitee的介绍

目录 1.Gitee介绍&#xff1a; 1.1 代码托管 1.2 本土化优势 1.3 企业级服务 1.4 开源生态 1.5 多形态适配 定位&#xff1a;国内开发者首选的高效代码协作平台&#xff0c;兼顾个人开源与企业级私有开发需求。 2.Gitee和GitHub区别 3.Gitee使用教程 4.Gitee相关…...

NoUniqueKey问题和Regular join介绍

问题背景 在flink任务中&#xff0c;遇到了 NoUniqueKey Join的情况&#xff0c;导致了数据膨胀&#xff0c;和下游结果与数据库数据不一致问题 那NoUniqueKey Join为什么会导致问题呢&#xff0c;下面是其中一种场景示例&#xff1a; 为什么会出现 NoUniqueKey &#xff1a;…...

TC8:SOMEIP_ETS_027-028

SOMEIP_ETS_027: echoUINT8 目的 检查method方法echoUINT8的参数及其顺序能够被顺利地发送和接收 说白了就是检查UINT8数据类型参数在SOME/IP协议层的序列化与反序列化是否正常。 UINT8相比于测试用例SOMEIP_ETS_021: echoINT8中的SINT8数据类型来说,属于无符号整数,也就是…...

小微企业SaaS ERP管理系统,SpringBoot+Vue+ElementUI+UniAPP

小微企业的SaaS ERP管理系统&#xff0c;ERP系统源码&#xff0c;ERP管理系统源代码 一款适用于小微企业的SaaS ERP管理系统, 采用SpringBootVueElementUIUniAPP技术栈开发&#xff0c;让企业简单上云。 专注于小微企业的应用需求&#xff0c;如企业基本的进销存、询价&#…...

css filter 常用方法函数和应用实例

1. blur() 模糊 filter: blur(半径);参数&#xff1a;模糊半径&#xff08;像素&#xff09;&#xff0c;值越大越模糊 示例&#xff1a;filter: blur(5px);2. brightness() 亮度 filter: brightness(百分比); 参数&#xff1a;1原始对比度&#xff0c;0全灰&#xff0c;>…...

chrome inspect 调试遇到的问题

1、oppp 手机打开webview 的时候&#xff0c; 报错这个并没有页面 Offline #V8FIG6SGLN75M7FY Pending authentication: please accept debugging session on the device. 解决方法&#xff0c;保持chrome 浏览器在显示的状态 去设置里开启usb 调试再关闭&#xff0c;反复重…...

Kotlin 中 List 和 MutableList 的区别

在 Kotlin 中&#xff0c;List 和 MutableList 是两种不同的集合接口&#xff0c;核心区别在于可变性。 Kotlin 集合框架的重要设计原则&#xff1a;通过接口分离只读&#xff08;read - only&#xff09;和可变&#xff08;mutable&#xff09;操作&#xff0c;以提高代码的安…...

openssl 生成自签名证书实现接口支持https

1.下载安装openssl Win32/Win64 OpenSSL Installer for Windows - Shining Light Productions 2.配置环境变量 将 openssl 的目录&#xff08;D:\tools\openssl\bin&#xff09;添加到 path 中 3.生成自签名证书 找一个存证书的目录打开powershell 3.1 生成私钥 openssl gen…...

React 中集成 Ant Design 组件库:提升开发效率与用户体验

React 中集成 Ant Design 组件库:提升开发效率与用户体验 一、为什么选择 Ant Design 组件库?二、基础引入方式三、按需引入(优化性能)四、Ant Design Charts无缝接入图标前面提到了利用Redux提供全局维护,但如果在开发时再自己手动封装组件,不仅效率不高,可能开发的组件…...

神经网络:节点、隐藏层与非线性学习

神经网络&#xff1a;节点、隐藏层与非线性学习 摘要&#xff1a; 神经网络是机器学习领域中一种强大的工具&#xff0c;能够通过复杂的结构学习数据中的非线性关系。本文从基础的线性模型出发&#xff0c;逐步深入探讨神经网络中节点和隐藏层的作用&#xff0c;以及它们如何…...

vue+tsc+noEmit导致打包报TS类型错误问题及解决方法

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 当我们新建vue3项目,package.json文件会自动给我添加一些配置选项,这写选项基本没有问题,但是在实际操作过程中,当项目越来越复杂就会出现问题,本文给大家分享vuetscnoEmit导致打包报TS类型错误问题及…...

Ragflow服务器上部署教程

参考官方文档进行整理 克隆相应代码 git clone https://github.com/infiniflow/ragflow.git修改vm.max_map_count sudo sysctl -w vm.max_map_count262144修改 daemon.json文件 {"registry-mirrors": ["https://docker.m.daocloud.io","https://0…...

Ubuntu 系统中解决 Firefox 中文显示乱码的完整指南

Firefox 是一款流行的网络浏览器,但在 Ubuntu 系统中有时会遇到中文显示乱码的问题。本文将为您提供一个全面的解决方案,帮助您轻松解决这个烦人的问题。 问题概述 在 Ubuntu 系统中使用 Firefox 浏览器时,有时会发现中文字符显示为乱码或方块。这通常是由于缺少合适的中文…...

JVM——垃圾回收

垃圾回收 在Java虚拟机&#xff08;JVM&#xff09;的自动内存管理中&#xff0c;垃圾回收&#xff08;Garbage Collection, GC&#xff09;是其核心组件之一。它负责回收堆内存中不再使用的对象所占用的内存空间&#xff0c;以供新对象的分配使用。下面我们将深入探讨JVM中的…...

【AI News | 20250506】每日AI进展

AI Repos 1、gitsummarize GitSummarize是一个在线工具&#xff0c;用户只需将GitHub URL中的“hub”替换为“summarize”&#xff0c;即可为任何公开或私有代码库生成交互式文档。该工具利用Gemini分析代码结构&#xff0c;自动生成系统级架构概述、目录和文件摘要、自然语言…...

LabVIEW高冲击加速度校准系统

在国防科技领域&#xff0c;高 g 值加速度传感器广泛应用于先进兵器研制&#xff0c;如深侵彻系统、精确打击弹药及钻地弹药等。其性能指标直接影响研究结果的准确性与可靠性&#xff0c;因此对该传感器进行定期校准意义重大。高冲击加速度校准系统具备多方面功能&#xff0c;适…...

优化算法 - intro

优化问题 一般形式 minimize f ( x ) f(\mathbf{x}) f(x) subject to x ∈ C \mathbf{x} \in C x∈C 目标函数 f : R n → R f: \mathbb{R}^n \rightarrow \mathbb{R} f:Rn→R限制集合例子 C { x ∣ h 1 ( x ) 0 , . . . , h m ( x ) 0 , g 1 ( x ) ≤ 0 , . . . , g r …...

从PotPlayer到专业播放器—基于 RTSP|RTMP播放器功能、架构、工程能力的全面对比分析

从PotPlayer到专业播放器SDK&#xff1a;工程项目怎么选择合适的播放方案&#xff1f; ——基于 RTSP、RTMP 播放器功能、架构、工程能力的全面对比分析 在许多音视频项目早期&#xff0c;我们都听过这句话&#xff1a; “本地测试就用 PotPlayer 播吧&#xff0c;能播就行了…...

EasyRTC嵌入式音视频通信SDK技术,助力工业制造多场景实时监控与音视频通信

一、背景 在数字化时代&#xff0c;实时监控广泛应用于安防、工业、交通等领域。但传统监控系统实时性、交互性欠佳&#xff0c;难以满足需求。EasyRTC作为先进实时通信技术&#xff0c;具有低延迟、高可靠、跨平台特性&#xff0c;能有效升级监控系统。融入EasyRTC后&#xf…...

MPay码支付系统第四方聚合收款码多款支付插件个人免签支付源码TP8框架全开源

一、源码描述 这是一套码支付源码&#xff08;MPay&#xff09;&#xff0c;基于TP8框架&#xff0c;前端layui2.9后端PearAdmin&#xff0c;专注于个人免签收款&#xff0c;通过个人的普通收款码&#xff0c;即可实现收款通知自动回调&#xff0c;支持绝大多数商城系统&#…...

wrod生成pdf。[特殊字符]改背景

import subprocess import os,time from rembg import remove, new_session from PIL import Image import io from docxtpl import DocxTemplate, InlineImage from docx.shared import Inches input_folder ‘tupian’ # 输入文件夹 kouchu_folder ‘kouchu’ # 去背景图像…...

动手学深度学习12.1. 编译器和解释器-笔记练习(PyTorch)

以下内容为结合李沐老师的课程和教材补充的学习笔记&#xff0c;以及对课后练习的一些思考&#xff0c;自留回顾&#xff0c;也供同学之人交流参考。 本节课程地址&#xff1a;无 本节教材地址&#xff1a;12.1. 编译器和解释器 — 动手学深度学习 2.0.0 documentation 本节…...

数字文明时代开源技术驱动的商业范式重构:基于开源AI大模型、AI智能名片与S2B2C商城小程序源码的协同创新研究

摘要&#xff1a;数字文明时代&#xff0c;数字技术正以指数级速度重构全球经济与社会结构。本文聚焦开源AI大模型、AI智能名片与S2B2C商城小程序源码的协同创新机制&#xff0c;从技术架构、商业逻辑、实践案例三个维度展开系统研究。基于多行业实证数据&#xff0c;揭示开源技…...

【Bootstrap V4系列】学习入门教程之 组件-轮播(Carousel)

Bootstrap V4系列 学习入门教程之 组件-轮播&#xff08;Carousel&#xff09; 轮播&#xff08;Carousel&#xff09;一、How it works二、Example2.1 Slides only 仅幻灯片2.2 With controls 带控制装置2.3 With indicators 带指示器2.4 With captions 带字幕 轮播&#xff0…...

嵌入式openharmony标准鸿蒙系统驱动开发基本原理与流程

第一:鸿蒙概述 OpenHarmony采用多内核(Linux内核或者LiteOS)设计,支持系统在不同资源容量的设备部署。当相同的硬件部署不同内核时,如何能够让设备驱动程序在不同内核间平滑迁移,消除驱动代码移植适配和维护的负担,是OpenHarmony驱动子系统需要解决的重要问题。 …...

Leetcode 刷题记录 08 —— 链表第二弹

本系列为笔者的 Leetcode 刷题记录&#xff0c;顺序为 Hot 100 题官方顺序&#xff0c;根据标签命名&#xff0c;记录笔者总结的做题思路&#xff0c;附部分代码解释和疑问解答&#xff0c;01~07为C语言&#xff0c;08及以后为Java语言。 01 合并两个有序链表 /*** Definition…...

PaddlePaddle 和PyTorch选择与对比互斥

你遇到的错误信息如下&#xff1a; RuntimeError: (PreconditionNotMet) Tensors dimension is out of bound.Tensors dimension must be equal or less than the size of its memory.But received Tensors dimension is 8, memorys size is 0.[Hint: Expected numel() * Size…...

极新月报·2025.4人工智能投融资观察

“ AI投资从‘量’向‘质’过渡 ” 4月重点关注&#xff1a; 1、四月人工智能领域投融资事件105起&#xff0c;披露金额78.63亿人民币。 2、亿级人民币以上金额的投资事件共20起 。 3、四月人工智能领域出现1起IPO事件。 4、在所有融资事件里&#xff0c;除去股权投资&…...

C++ vector 介绍与使用

目录 1.vector是什么&#xff1f; 2.vector的使用 2.1vector的构造函数 2.2vector iterator 的使用 2.3vector 空间增长问题 2.4vector的增删查改 1.vector是什么&#xff1f; 1. vector是表示可变大小数组的序列容器。 2. 就像数组一样&#xff0c;vector也 采用连续的存储…...

可以下载blender/fbx格式模型网站

glbxz.com glbxz.com可以下载blender/fbx格式模型。当然里面有免费的...

Vi/Vim 编辑器详细指南

Vi/Vim 编辑器详细指南 简介一、模式详解1. 命令模式(Normal Mode)2. 插入模式(Insert Mode)3. 可视模式(Visual Mode)4. 命令行模式(Ex Mode)二、核心操作1. 保存与退出2. 导航与移动3. 编辑与文本操作4. 搜索与替换三、高级技巧1. 多文件与窗口操作2. 宏录制3. 寄存器…...

LeetCode 热题 100 22. 括号生成

LeetCode 热题 100 | 22. 括号生成 大家好&#xff0c;今天我们来解决一道经典的算法题——括号生成。这道题在 LeetCode 上被标记为中等难度&#xff0c;要求生成所有可能的并且有效的括号组合。这是一道非常经典的回溯法题目&#xff0c;非常适合用来练习递归和回溯的技巧。…...

UE5 MetaHuman眼睛变黑

第5个材质MI_EyeOcclusion_Inst修改成透明即可...

【C语言】--指针超详解(一)

目录 一.内存和地址 1.1--内存 1.2--如何理解编址 二.指针变量和地址 2.1--取地址操作符(&) 2.2--指针变量和解引用操作符(*) 2.2.1--指针变量 2.2.2--如何理解指针类型 2.2.3--解引用操作符 2.3--指针变量的大小 三.指针变量类型的意义 3.1--从指针的解引用方…...

高频工业RFID读写器-三格电子

高频工业RFID读写器 型号&#xff1a;SG-HF40-485、SG-HF40-TCP 产品功能 高频工业读写器&#xff08;RFID&#xff09;产品用在自动化生产线,自动化分拣系统,零部件组装产线等情境下&#xff0c;在自动化节点的工位上部署RFID读写设备&#xff0c;通过与制品的交互&#xf…...

驱动开发系列57 - Linux Graphics QXL显卡驱动代码分析(四)显示区域绘制

一&#xff1a;概述 前面在介绍了显示模式设置&#xff08;分辨率&#xff0c;刷新率&#xff09;之后&#xff0c;本文继续分析下&#xff0c;显示区域的绘制&#xff0c;详细看看虚拟机的画面是如何由QXL显卡绘制出来的。 二&#xff1a;相关数据结构介绍 struct qxl_moni…...

6.5 行业特定应用:金融、医疗、制造等行业的定制化解决方案

金融、医疗和制造行业作为全球经济支柱&#xff0c;面临数据复杂性、实时性需求和严格合规性的共同挑战&#xff0c;同时各行业因业务特性衍生出独特需求。金融行业需应对市场波动、欺诈风险和多国法规&#xff0c;医疗行业聚焦精准诊断和患者数据隐私&#xff0c;制造业则强调…...

【Linux我做主】深入探讨从冯诺依曼体系到进程

从冯诺依曼体系到进程 从冯诺依曼体系到进程github地址1. 前言2. 计算机硬件2.1 冯诺依曼体系结构2.2 冯诺依曼模型的三大要点2.3 从QQ聊天认识&#xff1a;冯诺依曼体系下数据是如何流动的&#xff1f;发送方数据流动接收方数据流动 3. 计算机软件的根基——操作系统3.1 操作系…...

idea更换jdk版本操作

有时候我们有更换jdk版本的问题&#xff0c;自己电脑可能有多个版本&#xff0c;下面来介绍修改jdk版本修改修改什么地方 1 2 3 4 5 6 再修改pom即可&#xff0c;还有环境变量即可&#xff0c;希望有帮到大家&#xff01;...

npm install下载插件无法更新package.json和package-lock.json文件的解决办法

经过多番查证&#xff0c;使用npm config ls查看相关配置等方式&#xff0c;最后发现全局的.npmrc文件的配置多写了globaltrue&#xff0c;去掉就好了 如果参数很多&#xff0c;不知道是哪个参数引起的&#xff0c;先只保留registryhttp://xxx/&#xff0c;试试下载&#xff0…...

机器学习实操 第二部分 神经网路和深度学习 第13章 使用TensorFlow加载和预处理数据

机器学习实操 第二部分 神经网路和深度学习 第13章 使用TensorFlow加载和预处理数据 内容概要 第13章深入探讨了如何使用TensorFlow加载和预处理数据。本章首先介绍了tf.data API&#xff0c;它能够高效地加载和预处理大规模数据集&#xff0c;支持并行文件读取、数据打乱、批…...

WebSoket的简单使用

一、WebSocket简介 1.1、双向通信/全双工 客户端和服务器之间同时双向传输&#xff0c;全双工通信允许客户端和服务器随时互相发送消息&#xff0c;不需等一方发送请求后另一方才进行响应。 适用要低延迟/实时交互的场景&#xff0c;如在线游戏、即时通讯、股票行情等。 1.2…...

01_线性表

一、线性表的顺序存储 逻辑上相邻的数据元素&#xff0c;物理次序也相邻。占用连续存储空间&#xff0c;用“数组”实现&#xff0c;知道初始位置就可推出其他位置。 00_宏定义 // 函数结果状态代码 #define TRUE 1 #define FALSE 0 #define OK 1 #define ERROR 0 #defin…...

STL详解 - map和set

目录 一、关联式容器概述 二、键值对 三、树形结构的关联式容器 &#xff08;一&#xff09;set 1. set的介绍 2. set的使用 &#xff08;1&#xff09;模板参数列表 &#xff08;2&#xff09;构造函数 &#xff08;3&#xff09;迭代器函数 &#xff08;4&#xff…...

SpringBoot 集成滑块验证码AJ-Captcha行为验证码 Redis分布式 接口限流 防爬虫

介绍 滑块验证码比传统的字符验证码更加直观和用户友好&#xff0c;能够很好防止爬虫获取数据。 AJ-Captcha行为验证码&#xff0c;包含滑动拼图、文字点选两种方式&#xff0c;UI支持弹出和嵌入两种方式。后端提供Java实现&#xff0c;前端提供了php、angular、html、vue、u…...