从零开始学Linux进程控制:fork、wait、exec 详解 - 指南

news/2026/1/8 0:55:09/文章来源:https://www.cnblogs.com/gccbuaa/p/19447689

1:进程创建

1.1:fork函数初识

1.2:fork函数返回值

1.3:fork函数常规用法

1.4:fork函数调用失败原因

2:进程终止

2.1:退出码

代码1

代码2

2.2:异常

代码1

代码2

2.3:进程常见退出方法

2.3.1:main函数return

2.3.2:exit函数

2.3.3:_exit函数

2.3.4:return、exit与_exit之间的区别与联系

3:进程等待

3.1:进程等待的方法

3.1.1:wait方法

代码1

代码2

3.1.2:waitpid方法

代码1

代码2(等待指定进程退出)

代码3

3.2:获取进程status

3.3:阻塞等待与非阻塞等待

代码1(子进程正常退出)

代码2(子进程非正常退出)

3.3.1:阻塞等待

3.3.2:非阻塞等待

3.3.3:基于非阻塞接口的轮询检测方案

4:进程程序替换

代码1

替换原理

代码2

4.1:替换函数

函数解释

命名理解

4.1.1:execl函数

4.1.2:execlp函数

4.1.3:execv函数

4.1.4:execvp函数

4.1.5:execvpe函数

4.2:小结


1:进程创建

1:进程 = 内核相关的数据结构(task_struct + mm_struct + 页表) + 代码 + 数据.

2:创建子进程会经过以下步骤.

  1. 分配新的内存块和内核数据结构给子进程.
  2. 将父进程部分数据结构内容拷贝给子进程(子进程要继承于父进程).
  3. 添加子进程到系统的进程列表中
  4. 代码:子进程与父进程共享代码
  5. 数据:则通过写时拷贝的方式

如果理解进程具有独立性

  • 根本原因在于:进程 = 内核的相关管理数据结构(task_struct + mm_struct(地址空间) + 页表) + 代码 + 数据.子进程有自己的task_struct + mm_struct(地址空间) + 页表.
  • 代码虽然与父进程共享,但是与父进程之间互不影响.
  • 而数据是通过写时拷贝的方式进行复用.
  • 因此无论从内核的相关管理数据结构还是从代码以及数据,它都是独立的.

1.1:fork函数初识

  • 在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程.

进程调用fork,当控制转移到内核中的fork代码后,内核会做:

  1. 分配新的内存块和内核数据结构给子进程.
  2. 将父进程部分数据结构内容拷贝至子进程.
  3. 添加子进程到系统进程列表中.
  4. fork返回,开始调度器调度.

#include 
#include 
#include 
#include 
#include 
int main()
{pid_t pid;printf("Before: pid is %d\n", getpid());if((pid = fork()) == -1){perror("fork() fail");exit(-1);}printf("After:pid is %d, fork return %d\n", getpid(), pid);return 0;
}

这里看到了三行输出,一行before,两行after。进程114527先打印before消息,然后它有打印after。另一个after消息有114528打印的。注意到进程114528没有打印before,为什么呢?如下图所示.

  • fork之前父进程独立执行,而fork之后父子两个执行流分别执行.

PS:fork之后,父进程和子进程谁先执行完全由调度器决定.

1.2:fork函数返回值

fork函数为什么要给子进程返回0,给父进程返回子进程的PID?

一个父进程可以创建多个子进程,而一个子进程只能有一个父进程.因此,对于子进程来说,父进程是不需要被标识的;而对于父进程来说,子进程是需要被标识的,因为父进程创建子进程的目的是让其完成任务的,父进程只有知道了子进程的pid才能更加有针对性地去给子进程指定任务.

为什么fork函数有两个返回值

父进程调用fork函数后,为了创建子进程,fork函数内部将会进行一系列操作,包括创建子进程的进程控制块、创建子进程的地址空间、创建子进程对应的页表等等.子进程创建完毕后,操作系统还需要将子进程的进程控制块添加到系统进程列表中,此时子进程便创建完毕了

那么也就是说,在fork函数内部执行return语句以前,子进程就已经创建完毕了,那么之后的return语句不仅需要父进程执行,子进程也同样需要执行,这就是fork函数有两个返回值的原因.

1.3:写时拷贝

当子进程刚刚被创建时,子进程和父进程的数据和代码是共享的,即父子进程的代码和数据通过页表映射到物理内存的同一块空间。只有当父进程或子进程需要修改数据时,才将父进程的数据在内存当中拷贝一份,然后再进行修改。

这种在需要进行数据修改时再进行拷贝的技术,称为写时拷贝.

1:为什么数据要进行写时拷贝?

进程具有独立性.多个进程在运行时,需要独享各种资源,多进程运行期间互不干扰,不能让子进程的改变影响到父进程.

2:为什么不在创建子进程的时候进行写时拷贝.

子进程不一定会使用父进程的所有数据,并且在子进程不对数据进行写入的情况下,没有必要对数据进行拷贝,那么应该按需分配,在需要修改数据的时候再分配(延时分配),这样可以高效的使用内存空间,避免空间的浪费.

3:代码会不会进行写时拷贝?

90%的情况下是不会的,但这并不代表代码不能进行写时拷贝,例如在进行进程替换的时候,则需要进行代码的写时拷贝.

1.3:fork函数常规用法

  1. 一个进程希望复制自己,使子进程同时执行不同的代码段。例如父进程等待客户端请求,生成子进程来处理请求。
  2. 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。

1.4:fork函数调用失败原因

fork函数创建子进程也可能会失败,有以下两种情况:

  1. 系统中有太多的进程,内存空间不足,子进程创建失败。
  2. 实际用户的进程数超过了限制,子进程创建失败

2:进程终止

进程 = 内核相关的数据结构(task_struct + mm_struct + 页表) + 代码 + 数据.

创建进程时,会先创建进程的内核的相关管理数据结构.

那么终止是在做什么呢

  1. 释放曾经的代码和数据所占据的空间.
  2. 释放内核数据结构.

那么进程终止存在以下三种情况

  1. 代码跑完,结果正确.
  2. 代码跑完,结果不正确.
  3. 代码执行的时候,出现了异常,提前退出了.
  • 第一种情况和第二种情况可以通过进程的退出码来决定.
  • 第三种情况,得通过OS发给进程的信号来确定.

2.1:退出码

  • 我们都知道main函数是代码的入口,但实际上main函数只是用户级别代码的入口,main函数也是被其他函数调用的,例如在VS2013当中main函数就是被一个名为__tmainCRTStartup的函数所调用,而__tmainCRTStartup函数又是通过加载器被操作系统所调用的,也就是说main函数是间接性被操作系统所调用的.
  • 既然main函数是间接性被操作系统所调用的,那么当main函数调用结束后就应该给操作系统返回相应的退出信息,而这个所谓的退出信息就是以退出码的形式作为main函数的返回值返回,一般以0表示代码成功执行完毕,以非0表示代码执行过程中出现错误,这就是为什么我们都在main函数的最后返回0的原因.

代码1

#include 
#include 
#include 
int main()
{printf("I am process and My pid =%d,ppid == %d\n",getpid(),getppid());sleep(1);return 99;
}

当我们的代码运行起来就变成了进程,当进程结束后main函数的返回值实际上就是该进程的进程退出码,我们可以使用echo $?命令查看最近一次进程退出的退出码信息.

  • 退出码存在的意义就是在于:告诉关心方,我将任务完成得怎么样了.
  • 退出码为0则表示成功,为非0表示失败.(博主使用99是为了方便uu们查看).
代码2
#include 
#include 
#include 
#include 
int main()
{for (int errorcode = 0; errorcode <= 255; errorcode++){printf("errorcode == %d:%s\n",errorcode,strerror(errorcode));}return 0;
}

  • 1,2,3,4,5,6.....不同的非0值,一方面表示失败,另一方面表示失败的原因.

父进程bash为什么要得到子进程的退出码呢?

  • 要知道子进程的退出情况(成功,失败,以及失败的原因是什么)------>核心宗旨是为用户负责.

2.2:异常

异常这里后面会有专门的部分讲解,这里首先uu们简单理解下就好

  • 可以想象一个场景,编程运行的时候,突然崩溃了,这个时候的根本原因是:发生了异常,操作系统系统发现进程做了不该做的事情,从而杀死了进程.
  • 那么为什么会出现异常,原因在于:进程出现了异常,本质是因为进程收到了OS发给进程的信号.
  • 一旦出现了异常,退出码就没有任何意义了.

代码1

#include 
#include 
#include 
int main()
{while (1){printf("I am process and My pid =%d,ppid == %d\n",getpid(),getppid());sleep(1);}return 0;
}

代码2
#include 
#include 
#include 
int main()
{int * p = NULL;while (1){printf("I am a Process And My pid == %d\n",getpid());*p = 100;sleep(2);}return 0;
}

怎么判断异常.

在进程退出的时候,查看进程的退出信号是多少,就可以判断进程为什么异常了.

如何判断进程终止.

  1. 先确认是不是异常.
  2. 不是异常,就一定是代码跑完了,看退出码即可.

总结:衡量一个进程退出,我们只需要两个数字:退出码与退出信号

2.3:进程常见退出方法

进程正常终止一般有三种

  1. 从main函数return(非main函数return表示函数结束)
  2. 调用exit.
  3. _exit.

2.3.1:main函数return

#include 
#include 
#include 
int main()
{printf("I am process and My pid =%d,ppid == %d\n",getpid(),getppid());sleep(1);return 0;
}

2.3.2:exit函数

使用exit函数退出进程也是我们常用的方法,exit函数可以在代码中的任何地方退出进程,并且exit函数在退出进程前会做一系列工作:

  1. 执行用户通过atexit或on_exit定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入。
  3. 调用_exit函数终止进程。

#include 
#include 
#include 
#include 
void Test()
{printf("hello world");exit(1);
}
int main()
{Test();return 0;
}

上述代码使用exit终止进程前会将缓冲区当中的数据输出.

2.3.3:_exit函数

使用_exit函数退出进程的方法我们并不经常使用,_exit函数也可以在代码中的任何地方退出进程,但是_exit函数会直接终止进程,并不会在退出进程前会做任何收尾工作.

#include 
#include 
#include 
#include 
void Test()
{printf("hello world");_exit(1);
}
int main()
{Test();return 0;
}

使用_exit终止进程,则缓冲区当中的数据将不会被输出。

2.3.4:return、exit与_exit之间的区别与联系

  • 只有在main函数当中的return才能起到退出进程的作用,子函数当中return不能退出进程.
  • 而exit函数和_exit函数在代码中的任何地方使用都可起到退出进程的作用.
  • 使用exit函数退出进程前,exit函数会执行用户定义的清理函数,冲刷缓冲区,关闭流等操作,然后再终止进程,而_exit函数会直接终止进程,不会做任何收尾工作.

return、exit、_exit之间的联系

  • 执行return num等同于执行exit(num),因为调用main函数运行结束后,会将main函数的返回值当做exit的参数来调用exit函数。
  • 使用exit函数退出进程前,exit函数会先执行用户定义的清理函数、冲刷缓冲,关闭流等操作,然后再调用_exit函数终止进程。

3:进程等待

结论:任何子进程,在退出的前提下,一般必须要被父进程进行等待

进程在退出的时候,如果父进程不管不顾,那么此退出进程, 就会进入Z状态(僵尸进程)

为什么要有进程等待.

  1. 父进程通过等待,解决子进程退出的僵尸问题,回收系统资源(一定要考虑的因素).
  2. 获取子进程的退出信息,得知子进程是因为什么原因而退出.

进程等待的必要性.

  1. 子进程退出,父进程如果不读取子进程的退出信息,子进程就会变成僵尸进程,进程造成了内存泄漏.
  2. 进程一旦变成僵尸进程,那么就算是kill - 9命令也无法将其杀死,因为谁也无法杀死一个死去的进程.
  3. 对于一个进程来说,最关心自己的就是其父进程,因为父进程需要知道自己安排给子进程的任务完成得如何.
  4. 父进程通过进程等待的方式,回收子进程资源,获取子进程的退出信息.

3.1:进程等待的方法

3.1.1:wait方法

  • 作用:等待任意子进程.
  • 返回值:等待成功返回被等待进程的pid,等待失败返回-1.
  • 参数:输出型参数,获取子进程的退出状态,不关心可设置为NULL.
代码1
#include 
#include 
#include 
#include 
#include 
#include 
void ChildRun()
{int count = 5;while(count){printf("I am Child process and my pid == %d,ppid == %d\n",getpid(),getppid());sleep(2);count--;}
}
int main()
{printf("I am Father process and my pid == %d,ppid == %d\n",getpid(),getppid());pid_t id = fork();if(id == 0){ChildRun();printf("Child Quit\n");exit(0);}//父进程在等待pid_t rid = wait(NULL);if(rid > 0){printf("Wait Success,rid == %d\n",rid);}
}

代码2
#include 
#include 
#include 
#include 
#include 
#include 
void ChildRun()
{int count = 5;while(count){printf("I am Child process and my pid == %d,ppid == %d\n",getpid(),getppid());sleep(1);count--;}
}
int main()
{printf("I am Father process and my pid == %d,ppid == %d\n",getpid(),getppid());pid_t id = fork();if(id == 0){ChildRun();printf("Child Quit\n");exit(0);}sleep(10);//父进程在等待pid_t rid = wait(NULL);if(rid > 0){printf("Wait Success,rid == %d\n",rid);}sleep(3);printf("Father Process Quit\n");
}

3.1.2:waitpid方法

函数原型:pid_t waitpid(pid_t pid, int* status, int options);

返回值:

  1. 正常返回的时候waitpid返回收集到的子进程的进程pid
  2. 如果设置了选项WNOHANG,则调用中waitpid发现没有已退出的子进程可以收集,则返回0
  3. 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在.

参数:

        pid:

                Pid = -1,等待任何一个子进程,与wait等效.

                Pid > 0,等待其进程id与pid相等的子进程.

        status(输出型参数,获取子进程的退出状态):

                WIFEXITED(status):若为正常终止子进程返回的状态,则为真.(查看进程是否是正常退出)

                WEXITSTATUS(status):若WIFEXITED为非零,提取子进程退出码.(查看进程的退出码)

                不关心则可设置为NULL.

        options:

                WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待.若正常结束,则返回该子进程的id.

代码1
#include 
#include 
#include 
#include 
#include 
#include 
void ChildRun()
{int count = 5;while(count){printf("I am Child process and my pid == %d,ppid == %d\n",getpid(),getppid());sleep(1);count--;}
}
int main()
{printf("I am Father process and my pid == %d,ppid == %d\n",getpid(),getppid());pid_t id = fork();if(id == 0){ChildRun();printf("Child Quit\n");exit(0);}//Pid = -1,等待任何一个子进程,与wait等效.pid_t rid = waitpid(-1,NULL,0);if(rid > 0){printf("Wait Success,rid == %d\n",rid);}
}

代码2(等待指定进程退出)
#include 
#include 
#include 
#include 
#include 
#include 
void ChildRun()
{int count = 5;while(count){printf("I am Child process and my pid == %d,ppid == %d\n",getpid(),getppid());sleep(1);count--;}
}
int main()
{printf("I am Father process and my pid == %d,ppid == %d\n",getpid(),getppid());pid_t id = fork();if(id == 0){ChildRun();printf("Child Quit\n");exit(0);}//等待指定进程退出pid_t rid = waitpid(id,NULL,0);if(rid > 0){printf("Wait Success,rid == %d\n",rid);}sleep(3);return 0;
}

代码3
#include 
#include 
#include 
#include 
#include 
#include 
void ChildRun()
{int count = 5;while(count){printf("I am Child process and my pid == %d,ppid == %d\n",getpid(),getppid());sleep(1);count--;}
}
int main()
{int status = 0;printf("I am Father process and my pid == %d,ppid == %d\n",getpid(),getppid());pid_t id = fork();if(id == 0){ChildRun();printf("Child Quit\n");exit(1);}sleep(10);//等待指定进程退出,并且获取进程退出状态码pid_t rid = waitpid(id,&status,0);if(rid > 0){printf("Wait Success,rid == %d\n",rid);}sleep(3);printf("Father Process Quit and status == %d",status);return 0;
}

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息.
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞.
  • 如果不存在该子进程,则立即出错返回~

3.2:获取进程status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充.
  • 如果传递NULL,表示不关心子进程的退出状态信息.
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程.
  • statu是一个整型变量,但status不能简单地当作整型来看待,status的不同比特位所代表的信息不同,具体细节如下.

在status的低16比特位中,高8位表示进程的退出状态,即退出码.进程若是被信号所杀掉,则低7位表示终止信号,而第8位比特位是core dump标志.

 我们通过一系列位操作,就可以根据status得到进程的退出码和退出信号。

exitCode = (status >> 8) & 0xFF;//退出码
exitSignal = status & 0x7f;//退出信号

对于此,系统当中提供了两个宏来获取退出码和退出信号。

  • WIFEXITED(status):用于查看进程是否是正常退出,本质是检查是否收到信号。
  • WEXITSTATUS(status):用于获取进程的退出码。
exitNormal = WIFEXITED(status);  //是否正常退出
exitCode = WEXITSTATUS(status);  //获取退出码

需要注意的是,当一个进程非正常退出时,说明该进程是被信号所杀,那么该进程的退出码也就没有意义了.

#include 
#include 
#include 
#include 
#include 
#include 
void ChildRun()
{int count = 5;while(count){printf("I am Child process and my pid == %d,ppid == %d\n",getpid(),getppid());sleep(1);count--;}
}
int main()
{int status = 0;printf("I am Father process and my pid == %d,ppid == %d\n",getpid(),getppid());pid_t id = fork();if(id == 0){ChildRun();printf("Child Quit\n");exit(1);}sleep(10);//等待指定进程退出,并且获取进程退出状态码pid_t rid = waitpid(id,&status,0);if(rid > 0){printf("Wait Success,rid == %d\n",rid);}sleep(3);printf("Father Process Quit and status == %d,child quit code:%d,child quit signal:%d\n",status,(status >> 8) & 0xFF,status & 0x7F);return 0;
}

3.3:阻塞等待与非阻塞等待

在讲解阻塞等待与非阻塞等待前,我们先来看看子进程正常退出与非正常退出的情况

代码1(子进程正常退出)

#include 
#include 
#include 
#include 
#include 
#include 
void ChildRun()
{int count = 5;while(count){printf("I am Child process and my pid == %d,ppid == %d\n",getpid(),getppid());sleep(1);count--;}
}
int main()
{int status = 0;printf("I am Father process and my pid == %d,ppid == %d\n",getpid(),getppid());pid_t id = fork();if(id == 0){ChildRun();printf("Child Quit....\n");_exit(123);}//父进程休眠7ssleep(7);//等待指定进程退出,并且获取进程退出状态码pid_t rid = waitpid(id,&status,0);if(rid > 0){printf("Wait Success,rid == %d\n",rid);//wifexited判断子进程是否正常退出if(WIFEXITED(status)){//wexitstatus获取进程的退出码printf("Child Quit Success,Child exit Code == %d\n",WEXITSTATUS(status));}else{printf("Child Quit Unnormal\n");}}else{printf("Wait Faild\n");}sleep(3);return 0;
}

代码2(子进程非正常退出)

#include 
#include 
#include 
#include 
#include 
#include 
void ChildRun()
{int * p = NULL;int count = 5;while(count){printf("I am Child process and my pid == %d,ppid == %d\n",getpid(),getppid());sleep(1);count--;*p = 150;}
}
int main()
{int status = 0;printf("I am Father process and my pid == %d,ppid == %d\n",getpid(),getppid());pid_t id = fork();if(id == 0){ChildRun();printf("Child Quit....\n");_exit(123);}//父进程休眠7ssleep(7);//等待指定进程退出,并且获取进程退出状态码pid_t rid = waitpid(id,&status,0);if(rid > 0){printf("Wait Success,rid == %d\n",rid);//wifexited判断子进程是否正常退出if(WIFEXITED(status)){//wexitstatus获取进程的退出码printf("Child Quit Success,Child exit Code == %d\n",WEXITSTATUS(status));}else{printf("Child Quit Unnormal\n");}}else{printf("Wait Faild\n");}sleep(3);return 0;
}

3.3.1:阻塞等待

当子进程未退出时,父进程都在一直等待子进程退出,在等待期间,父进程不能做任何事情------>这种等待叫做阻塞等待.

3.3.2:非阻塞等待

父进程没有一直等待子进程退出,而是当子进程未退出时父进程可以做一些自己的事情,当子进程退出时再读取子进程的退出信息------>非阻塞等待.

因此waitpid如果是以阻塞等待的方式的调用的话,本质是在检测子进程的状态.

3.3.3:基于非阻塞接口的轮询检测方案

向waitpid函数的第三个参数potions传入WNOHANG,这样一来,等待的子进程若是没有结束,那么waitpid函数将直接返回0,不予以等待。而等待的子进程若是正常结束,则返回该子进程的pid。

例如,父进程可以隔一段时间调用一次waitpid函数,若是等待的子进程尚未退出,则父进程可以先去做一些其他事,过一段时间再调用waitpid函数读取子进程的退出信息。

#include 
#include 
#include 
#include 
#include 
#include 
typedef void(*Fun_T)();
#define N 3
Fun_T task[N] = {NULL};
void PrintLog()
{printf("PrintLog\n");
}
void DownLoad()
{printf("DownLoad\n");
}
void MySQLDataSync()
{printf("MySQLDataSync\n");
}
//加载任务
void LoadTask()
{//函数名表示地址task[0] = PrintLog;task[1] = DownLoad;task[2] = MySQLDataSync;
}
void HandleTask()
{for (size_t i = 0; i < N; i++){//拿到函数对应的地址,然后进行调用task[i]();}
}
//非阻塞轮询等待父进程做其他事情
void DoOtherThing()
{HandleTask();
}
void ChildRun()
{int count = 5;while(count){printf("I am Child process and my pid == %d,ppid == %d\n",getpid(),getppid());sleep(1);count--;}
}
int main()
{printf("I am Father process and my pid == %d,ppid ==%d\n",getpid(),getppid());pid_t id = fork();if(0 == id){ChildRun();printf("Child Quit\n");exit(123);}LoadTask();//非阻塞轮询while (1){int status = 0;//WNOHANG表示非阻塞等待pid_t Return_id = waitpid(id,&status,WNOHANG);//若pid指定的子进程没有结束,则waitpid函数返回0,不予以等待if (Return_id == 0){sleep(1);printf("Child is Running,Please Check Next Time\n");DoOtherThing();}//大于0表示等待成功else if(Return_id > 0){printf("Wait Success,rid == %d\n",Return_id);//wifexited判断子进程是否正常退出if(WIFEXITED(status)){//wexitstatus获取进程的退出码printf("Child Quit Success,Child exit Code == %d\n",WEXITSTATUS(status));}else{printf("Child Quit Unnormal\n");}break;}else{printf("Wait Failed\n");break;}}return 0;
}

运行结果就是,父进程每隔一段时间就去查看子进程是否退出,若未退出,则父进程先去忙自己的事情,过一段时间再来查看,直到子进程退出后读取子进程的退出信息。

4:进程程序替换

在讲进程程序替换前,我们先看一段代码

代码1

#include 
#include 
#include 
#include 
int main()
{printf("Test Execl Begin\n");execl("/usr/bin/ls","ls","-l","-a",NULL);printf("Test Execl End\n");return 0;
}

我们可以清晰地看到,上述代码并未执行   printf("Test Execl End\n");这段代码,那么这是为什么呢?这就牵扯到exec*系列函数的原理了

替换原理

  • 进程 = 内核数据结构 + 代码 + 数据,而进程替换的本质是将进程的代码与数据进行了替换,站在被替换进程的角度:本质就是这个程序(在上述例子是ls指令)被加载到内存中.

那么程序为什么要加载到内存当中

  • 因为冯诺依曼体系结构规定代码和数据只能够被CPU访问,CPU只能够就近访问内存,不能够访问外设,因此必须将磁盘里的代码和数据加载到内存当中.

所以exec*系列的函数,执行完毕以后,后续的代码就不见了,因为旧进程代码和数据被exec*系列函数替换掉了,但是并没有替换旧进程的内核数据结构(因此在执行exec*系列函数的过程中,没有创建新进程).

代码2

#include 
#include 
#include 
#include 
#include 
int main()
{printf("Test Execl Begin\n");pid_t id = fork();if(id == 0){sleep(3);execl("/usr/bin/ls","ls","-l","-a",NULL);exit(1);}int status = 0;//为0表示阻塞等待pid_t Rid = waitpid(id,&status,0);if (Rid > 0){printf("Father Wait Success,Child Exit Code:%d\n",WEXITSTATUS(status));}printf("Test Execl End\n");return 0;
}

我们可以看到,退出码是0而不是0,这是因为我们创建了子进程,子进程执行了execl函数,进行了程序替换,以至于没有执行execl函数后面的代码.

  • 创建子进程,目的是为了让子进程完成任务.但完成任务的方式有两种.
  1. 让子进程执行父进程代码的一部分.
  2. 让子进程执行一个全新的程序(程序替换).
  • 父子进程之间要保证各自进程的独立性,默认不修改的情况下,父子进程共享代码和数据.
  • 一旦要让子进程执行一个全新的程序,不仅仅是将数据进行了写时拷贝,然后将新程序的数据对其进行覆盖,同样也将代码进行了写时拷贝,并且将新程序的代码对其进行进行了覆盖,并且通过页表重新建立映射关系.

fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec*系列函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec*系列函数并不创建新进程,所以调用exec前后该进程的id并未改变.

当进行进程程序替换时,是否创建新的进程?

进程程序替换之后,该进程对应的PCB、进程地址空间以及页表等数据结构都没有发生改变,只是进程在物理内存当中的数据和代码发生了改变,所以并没有创建新的进程,而且进程程序替换前后该进程的pid并没有发生改变.

子进程进行进程程序替换后,是否影响父进程的代码和数据?

子进程刚被创建时,与父进程共享代码和数据,但当子进程需要进行进程程序替换时,也就意味着子进程需要对其数据和代码进行写入操作,这时便需要将父子进程共享的代码和数据进行写时拷贝,此后父子进程的代码和数据也就分离了,因此子进程进行程序替换后不会影响父进程的代码和数据。

4.1:替换函数

其实有六种以exec开头的函数,统称exec函数
#include `
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[], char *const envp[]);
//其中...表示可变参数

函数解释

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。

命名理解

这些函数原型看起来很容易混,但只要掌握了规律就很好记。
  • l(list) : 表示参数采用列表.
  • v(vector) : 参数用数组.
  • p(path) : 有p自动搜索环境变量PATH.
  • e(env) : 表示自己维护环境变量.

4.1.1:execl函数

#include 
#include 
int main()
{printf("Test_Execl Begin\n");execl("/usr/bin/ls","ls","-l","-a",NULL);printf("Test_Execl End\n");return 0;
}

4.1.2:execlp函数

4.1.3:execv函数

#include 
#include 
#include 
#include 
#include 
int main()
{printf("Test Execv Begin\n");pid_t id = fork();if(id == 0){sleep(3);//与execl函数一样,以NULL结尾char * const argv[] = {"ls","-l","-a",NULL};execv("/usr/bin/ls",argv);exit(1);}int status = 0;//为0表示阻塞等待pid_t Rid = waitpid(id,&status,0);if (Rid > 0){printf("Father Wait Success,Child Exit Code:%d\n",WEXITSTATUS(status));}printf("Test Execv End\n");return 0;
}

4.1.4:execvp函数

​
#include 
#include 
#include 
#include 
#include 
int main()
{printf("Test Execvp Begin\n");pid_t id = fork();if(id == 0){sleep(3);char * const argv[] = {"ls","-l","-a",NULL};execvp("ls",argv);exit(1);}int status = 0;//为0表示阻塞等待pid_t Rid = waitpid(id,&status,0);if (Rid > 0){printf("Father Wait Success,Child Exit Code:%d\n",WEXITSTATUS(status));}printf("Test Execvp End\n");return 0;
}
​

4.1.5:execvpe函数

#include 
#include 
#include 
#include 
#include 
int main()
{printf("Test Execvpe Begin\n");pid_t id = fork();if(id == 0){sleep(3);//与execl函数一样,以NULL结尾char * const argv[] = {"Cpp_Program.exe",NULL};char * const envp[] = {"LiuJunhao = No.1","LuoAiTao = No.2",NULL};execvpe("./Cpp_Program.exe",argv,envp);exit(1);}int status = 0;//为0表示阻塞等待pid_t Rid = waitpid(id,&status,0);if (Rid > 0){printf("Father Wait Success,Child Exit Code:%d\n",WEXITSTATUS(status));}printf("Test Execvpe End\n");return 0;
}
#include 
#include 
using namespace std;
int main(int argc,char * argv[],char* env[])
{int i = 0;//命令行参数表默认以NULL结尾for(i = 0; argv[i]; i++){printf("argv[%d]:>%s\n",i,argv[i]);}printf("-----------------------------\n");//环境变量参数表也默认以NULL结尾for(i = 0; env[i]; i++){printf("env[%d]:>%s\n",i,env[i]);}printf("-----------------------------\n");cout << "hello I am C++ Program and my pid == "<

4.2:小结

  • 事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execveman手册 第2,其它函数在man手册第3节。这些函数之间的关系如下图所示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1118826.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

RxJS操作符选型:AI推荐map与switchMap使用时机

RxJS操作符选型&#xff1a;精准判断map与switchMap的使用时机 在现代前端开发中&#xff0c;响应式编程早已不是“可选项”&#xff0c;而是构建复杂交互逻辑的基石。尤其是在 Angular、NestJS 或基于 RxJS 的状态管理方案中&#xff0c;数据流如同血液贯穿整个应用。而在这条…

企业开发者注意!不及时集成Entra ID,你的VSCode可能已存在安全隐患

第一章&#xff1a;企业开发者注意&#xff01;不及时集成Entra ID&#xff0c;你的VSCode可能已存在安全隐患为何VSCode需要身份安全加固 现代开发环境中&#xff0c;Visual Studio Code 已成为企业级应用开发的核心工具。然而&#xff0c;许多团队忽视了其身份认证机制的薄弱…

tRPC端到端类型安全:VibeThinker连接前后端共享类型

tRPC端到端类型安全&#xff1a;VibeThinker连接前后端共享类型 在构建一个面向数学竞赛题求解的智能系统时&#xff0c;最让人头疼的往往不是模型本身的能力&#xff0c;而是“为什么明明写对了逻辑&#xff0c;结果却出错了&#xff1f;”——这种问题常常源于前后端之间微妙…

Windows字体渲染革命:MacType终极配置指南

Windows字体渲染革命&#xff1a;MacType终极配置指南 【免费下载链接】mactype Better font rendering for Windows. 项目地址: https://gitcode.com/gh_mirrors/ma/mactype 还在忍受Windows系统下模糊不清的字体显示效果吗&#xff1f;MacType作为一款开源的字体渲染优…

3步搞定完整网页长截图:Chrome扩展终极指南

3步搞定完整网页长截图&#xff1a;Chrome扩展终极指南 【免费下载链接】full-page-screen-capture-chrome-extension One-click full page screen captures in Google Chrome 项目地址: https://gitcode.com/gh_mirrors/fu/full-page-screen-capture-chrome-extension …

接口自动化测试框架搭建详解

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快一、原理及特点参数放在XML文件中进行管理用httpClient简单封装一个httpUtils工具类测试用例管理使用了testNg管理&#xff0c;使用了TestNG参数化测试&#xff0c;…

揭秘VSCode中动态网页审查:90%开发者忽略的关键功能

第一章&#xff1a;VSCode中动态网页审查的认知重构在现代前端开发中&#xff0c;VSCode 已不仅是代码编辑器&#xff0c;更逐渐演变为集调试、审查与实时反馈于一体的集成开发环境。传统的网页审查多依赖浏览器开发者工具&#xff0c;但随着 Live Server、Debugger for Chrome…

城通网盘直连地址获取技术深度解析

城通网盘直连地址获取技术深度解析 【免费下载链接】ctfileGet 获取城通网盘一次性直连地址 项目地址: https://gitcode.com/gh_mirrors/ct/ctfileGet 在数字资源分享领域&#xff0c;城通网盘作为国内知名的文件存储与分享平台&#xff0c;其下载体验一直备受关注。本文…

老旧Mac系统兼容性升级完整指南:从发现问题到完美运行

老旧Mac系统兼容性升级完整指南&#xff1a;从发现问题到完美运行 【免费下载链接】OpenCore-Legacy-Patcher 体验与之前一样的macOS 项目地址: https://gitcode.com/GitHub_Trending/op/OpenCore-Legacy-Patcher 您是否正在为老旧Mac无法升级到最新macOS而苦恼&#xf…

16.仿函数

1.function-like classes,所谓仿函数 一个对象能够像函数一样被调用,关键就是重载(重定义)operator()。 比如: template struct identity { const T& operator()(const T& x) const { return x; } }; 使…

Steam创意工坊模组下载新方案:WorkshopDL深度解析

Steam创意工坊模组下载新方案&#xff1a;WorkshopDL深度解析 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 在游戏模组生态系统中&#xff0c;Steam创意工坊以其庞大的资源库…

Chrome浏览器网页完整截图终极解决方案

Chrome浏览器网页完整截图终极解决方案 【免费下载链接】full-page-screen-capture-chrome-extension One-click full page screen captures in Google Chrome 项目地址: https://gitcode.com/gh_mirrors/fu/full-page-screen-capture-chrome-extension 在日常浏览网页时…

区块链交易模拟:VibeThinker生成简易智能合约逻辑

区块链交易模拟&#xff1a;VibeThinker生成简易智能合约逻辑 在去中心化应用开发日益复杂的今天&#xff0c;一个微小的逻辑漏洞就可能导致数百万美元的资产损失。智能合约&#xff0c;作为区块链世界中的“自动执行协议”&#xff0c;其编写过程要求极高的精确性与严密性——…

揭秘VSCode 1.107多智能体配置:3步实现团队智能协同开发

第一章&#xff1a;揭秘VSCode 1.107多智能体配置的核心机制VSCode 1.107 引入了多智能体协同架构&#xff0c;标志着编辑器从单实例模式向分布式智能服务演进。该机制通过独立运行的多个语言智能体&#xff08;Agent&#xff09;实现并行语法分析、代码补全与错误诊断&#xf…

VS:Ctrl+K

在Visual Studio中&#xff0c;CtrlK 是一个‌组合键前缀‌&#xff0c;按下后需再按一个键来执行特定命令。以下是常见用法&#xff1a;‌代码片段操作‌&#xff1a;CtrlK, CtrlX&#xff1a;插入代码片段。CtrlK, X&#xff1a;同上&#xff0c;部分版本简写。‌注释控制‌&…

如何快速掌握MCEdit 2.0:新手玩家的终极地图编辑指南

如何快速掌握MCEdit 2.0&#xff1a;新手玩家的终极地图编辑指南 【免费下载链接】mcedit2 MCEdit 2.0 - World Editor for Minecraft. 项目地址: https://gitcode.com/gh_mirrors/mc/mcedit2 你是否曾经在《我的世界》中耗费数小时手动搭建建筑&#xff0c;却总是达不到…

Qt - QSplitter组件功能及用法

QSplitter 是 Qt 中的一个布局管理组件,主要用于创建可拖动的分隔条,让用户能够动态调整子控件的大小。在 Qt Designer(UI 设计界面)中,QSplitter 确实不会像按钮、标签那样直接出现在左侧的组件列表中。这是因为…

YuukiPS启动器:原神玩家的智能管家

YuukiPS启动器&#xff1a;原神玩家的智能管家 【免费下载链接】Launcher-PC 项目地址: https://gitcode.com/gh_mirrors/la/Launcher-PC 游戏启动的痛点与解决方案 每位原神玩家都曾经历过这样的困扰&#xff1a;多账号切换需要频繁退出登录&#xff0c;不同版本间的…

API网关设计模式:AI列举限流与鉴权实施方案

API网关设计模式&#xff1a;AI服务限流与鉴权的实战方案 在AI模型日益普及的今天&#xff0c;一个参数仅1.5B的小型语言模型——比如VibeThinker-1.5B-APP——已经能在手机端或边缘设备上流畅运行。这类“轻量级但可用”的推理引擎正被广泛部署于教育平台、内部工具和开发者沙…

Steam创意工坊模组下载难题的终极解决方案

Steam创意工坊模组下载难题的终极解决方案 【免费下载链接】WorkshopDL WorkshopDL - The Best Steam Workshop Downloader 项目地址: https://gitcode.com/gh_mirrors/wo/WorkshopDL 还在为无法访问Steam创意工坊的精彩模组而苦恼吗&#xff1f;WorkshopDL作为一款革命…