进程控制(Linux)

进程创建

fork函数初识

fork函数的功能是在已经存在的进程中创建一个子进程

#include <unistd.h>
pid_t fork(void);

返回值:创建失败返回-1;创建成功,子进程返回0,父进程返回子进程的进程id;可以进行简单的类比:父亲可以有多个孩子,但是孩子却只有一个父亲,所以进程创建之后需要将孩子的id给父亲,而子进程只需要getpid()就可以获取自己的id

在这里插入图片描述
在这里插入图片描述

进程调用fork函数后,操作系统会分配新的内存块和内核数据结构给子进程;将父进程部分数据结构内容拷贝到子进程中;将子进程添加到进程列表中

fork函数返回值

为什么fork函数会有两个返回值呢?而且为什么返回之后,给父进程赋值子进程的id,给子进程赋值为0呢?为什么同一个返回值可以让if else同时成立呢?
接下来,深入了解该函数

首先用户使用fork函数,操作系统开始调用函数完成相应的任务

在这里插入图片描述

当函数fork()准备return pid;时,核心代码已经执行完毕,子进程也已经创建完成,并且在操作系统的运行队列中,准备被调度;所以,在执行return pid;之前,父子进程已经分流,可分别执行return pid;语句;返回的本质就是写入,子进程或父进程谁先返回,谁先写入id中,由于写时拷贝,同一个id会有两个不同的内容,根据不同的内容和判断语句if else进行匹配

fork常规用法

  1. 父进程希望复制自己,使父子进程同时执行不同的代码段
  2. 父进程希望子进程执行另一个不同的程序

fork调用失败的原因

  1. 系统中有太多的进程
  2. 实际用户的进程数超过限制

进程终止

进程退出场景

  1. 代码运行完毕,结果正确
  2. 代码运行完毕,结果错误
  3. 代码异常运行终止

进程常见退出方法

先介绍退出码,到目前为止,所写的代码中在最后都会加上 return 0;为什么一定是数字0,而不是其他数字呢???
进程退出时,会返回对应的退出码,标识进程执行的结果是否中正确;一般而言退出码都必须有对应的文字描述
退出码:0表示程序正常运行;!0具体的数字表示不同的错误

程序正常终止
可以通过 echo $查看最近一次的进程退出码
在这里插入图片描述

  1. return 返回

  2. 调用exit
    在这里插入图片描述在这里插入图片描述
    程序并没有立刻打印 hello world,而是两秒之后再打印的;而且退出码也与程序中exit(1)所设置的一致

  3. 调用_exit
    在这里插入图片描述
    在这里插入图片描述
    程序压根就没有打印hello world,退出码仍是一致的

原因如下:
exit终止进程,主动刷新缓冲区
_eixt终止进程,不会刷新缓冲区

两者区别:exit是库函数;_exit是系统调用

在这里插入图片描述

return函数

return是较为常见的退出进程的方式,执行return n等同于执行exit(n)

进程等待

进程等待必要性

  1. 如果子进程退出,父进程一直不能获取其状态,就会造成子进程变成僵尸状态,进而造成内存泄漏
  2. 如果进程变成僵尸状态,操作系统也无能为力
  3. 父进程创建子进程的目的便是为了完成任务,所以需要知道子进程运行是否完成,结果正确与否,是否正常退出
  4. 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

获取子进程status

进程等待的方法

  1. wait/waitpid,都有一个 status参数,该参数是一个输出型参数,由操作系统填充
  2. 如果传递NULL,表示不关心子进程的退出状态信息(子进程阻塞)
  3. 操作系统会根据该参数,将子进程的退出信息反馈给父进程
  4. status不能简单地作为整体来看待,位图如下:信号等于0表示正常退出,退出状态就是退出码(位图在后面的学习中再详细学习)

status&0x7F进程退出信息;status >>8)&0xFF获取进程退出状态

在这里插入图片描述

wait方法

#include<sys/types.h>
#include<sys/wait.h>

pid_t wait(int*status);

返回值:如果成功,返回被等待进程的id;如果失败,返回-1
参数:输出型参数,获取子进程退出状态,不关心可设置为NULL;

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
前十秒一直在子进程中运行,状态是S+;十秒到十五秒之间,子进程终止运行,处于僵尸进程Z+;十五秒后父进程通过wait将子进程资源回收

waitpid方法

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

返回值:如果成功,返回子进程的id;如果设置了选项WNOHANG,但是waitpid并没有已退出的子进程可以收集,则返回0;如果失败,返回-1,此时errno会被设置成相应的值以指示错误

参数:
pid:子进程id

statusWIFEXITED:若正常终止子进程,则为真,查看进程是否正常退出;WEXITSTATUS:若WIFEXIT非零,则提取进程退出码,查看进程的退出码

options
0默认为阻塞状态;WNOHANG(非阻塞):若pid指定的子进程没有结束,则waitpd函数返回0,不予等待;若进程正常结束,则返回该子进程的id

在这里插入图片描述
在这里插入图片描述

status获取子进程信息的过程:子进程退出后,将退出状态,终止信息保存至PCB中;父进程系统调用waitpid,通过status获取子进程的退出状态,终止信息

在这里插入图片描述

通过宏获取status

在这里插入图片描述
在这里插入图片描述

阻塞VS非阻塞

阻塞:当父进程等待获取子进程资源时,如果子进程还未退出,父进程就一直在等其退出;非阻塞:当父进程等待获取子进程资源时,如果子进程还未退出,父进程可以去执行其他其他程序,不需要一直等待子进程,采取轮询

pid_t waitpid(pid_t pid,int*status,int options);中, option值为0时代表阻塞状态; optionWNOHANG时代表非阻塞

阻塞状态上面已经展示过,接下来展示非阻塞获取子进程资源
在这里插入图片描述
在这里插入图片描述

waitpid返回值:等于零,调用成功,但是子进程并没有退出;大于零,调用成功并且子进程也退出;小于零,调用失败

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

进程程序替换

替换原理

通过fork创建子进程之后是为了执行程序,子进程往往需要调用一种exec函数用来执行另外一个程序,也就是与父进程不同的程序;当进程调用一种exec函数时,创建的子进程就会将指定的程序加载到内存中,并执行;调用exec并不创建新进程,所以调用exec前后该进程的id并没有变化

程序替换的本质:将指定程序的代码和数据加载到指定位置,并不、覆盖父进程的数据和代码

在这里插入图片描述

替换函数

exec开头的函数有六种,统称exec函数

int execl(const char *path, const char *arg, ...);

l:list,将参数一个个低传入 execl

首先执行程序,先要找到该程序,然后再执行包括如何执行
path表示该程序所在路径;arg表示如何执行该程序,类似与命令行参数...表示可变参数列表,最后一个参数必须是NULL

在这里插入图片描述
在这里插入图片描述

运行结果中,只打印了第一个 printf,第二个并没有打印,为什么呢?
因为 printf也是代码,并且第二个是在替换函数 execl之后的,当替换函数执行完毕后,原代码已经全部被覆盖,开始并执行新的程序代码,所以第二个 printf无法执行

int execlp(const char *file, const char *arg, ...);

p:path,可以自动找到程序所在路径,只需要输入待执行的程序即可

在这里插入图片描述
![在这里插入图片描述](https://img-blog.csdnimg.cn/7dda163e822444408419cc2419f2ecc4.png

第一个ls代表要执行的程序;第二个ls代表如何执行

int execv(const char *path, char *const argv[]);

v:vector,可以将所有可执行参数放入数组中,统一传递,代替使用可变参数

在这里插入图片描述

在这里插入图片描述

int execvp(const char *file, char *const argv[]);

直接输入待执行的程序,以及如何执行

在这里插入图片描述
在这里插入图片描述

到目前为止,一直执行的是系统命令,下面尝试执行自己所写的程序

在这里插入图片描述

自己所写的程序:

在这里插入图片描述

运行结果:
在这里插入图片描述

根据运行结果来看,调用非常地成功;所以可以使用替换函数调用任何语言地程序

int execle(const char *path, const char *arg,
                    ..., char * const envp[]);

e:自定义环境变量

先试试将不加入自定义环境变量

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上面便是简单地获取两个系统内的环境变量的方式;接下来如果想要自定义环境变量该如何操作呢???

先介绍一个函数:putenv
在这里插入图片描述
添加一个环境变量到系统中environ所指向的环境变量表中
自定义环境变量操作如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

execle可以获取环境变量,同样地上面几种替换函数也可以获取环境变量,在虚拟地址空间中存在着环境变量参数,子进程可通过地址空间获取环境变量

execle的参数列表与 mian函数的命令行参数非常的相似

在这里插入图片描述

main也是函数,也要被调用,也要被传参,而execl就是将程序加载到内存中的;所以程序执行的顺序便是:先加载,再执行

程序替换函数的本质是将程序加载到内存中,加载的工作由Linux exec*加载器来完成

函数解释

  1. 替换函数如果调用成功则加载新的程序从启动代码开始执行,不再返回
  2. 如果替换失败,返回-1
  3. 替换函数,只有失败的返回值,没有成功的返回值

猜你喜欢

转载自blog.csdn.net/qq_68006585/article/details/129845290