进程控制(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常规用法
- 父进程希望复制自己,使父子进程同时执行不同的代码段
- 父进程希望子进程执行另一个不同的程序
fork调用失败的原因
- 系统中有太多的进程
- 实际用户的进程数超过限制
进程终止
进程退出场景
- 代码运行完毕,结果正确
- 代码运行完毕,结果错误
- 代码异常运行终止
进程常见退出方法
先介绍退出码,到目前为止,所写的代码中在最后都会加上 return 0
;为什么一定是数字0,而不是其他数字呢???
进程退出时,会返回对应的退出码,标识进程执行的结果是否中正确;一般而言退出码都必须有对应的文字描述
退出码:0表示程序正常运行;!0具体的数字表示不同的错误
程序正常终止
可以通过 echo $
查看最近一次的进程退出码
-
return 返回
-
调用exit
程序并没有立刻打印hello world
,而是两秒之后再打印的;而且退出码也与程序中exit(1)
所设置的一致 -
调用_exit
程序压根就没有打印hello world
,退出码仍是一致的
原因如下:
exit终止进程,主动刷新缓冲区
_eixt终止进程,不会刷新缓冲区
两者区别:exit是库函数;_exit是系统调用
return函数
return
是较为常见的退出进程的方式,执行return n
等同于执行exit(n)
进程等待
进程等待必要性
- 如果子进程退出,父进程一直不能获取其状态,就会造成子进程变成僵尸状态,进而造成内存泄漏
- 如果进程变成僵尸状态,操作系统也无能为力
- 父进程创建子进程的目的便是为了完成任务,所以需要知道子进程运行是否完成,结果正确与否,是否正常退出
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
获取子进程status
进程等待的方法
wait/waitpid
,都有一个status
参数,该参数是一个输出型参数,由操作系统填充- 如果传递NULL,表示不关心子进程的退出状态信息(子进程阻塞)
- 操作系统会根据该参数,将子进程的退出信息反馈给父进程
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
status
:WIFEXITED
:若正常终止子进程,则为真,查看进程是否正常退出;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时代表阻塞状态; option
为WNOHANG
时代表非阻塞
阻塞状态上面已经展示过,接下来展示非阻塞获取子进程资源
waitpid
返回值:等于零,调用成功,但是子进程并没有退出;大于零,调用成功并且子进程也退出;小于零,调用失败
- 如果子进程已经退出,调用
wait/waitpid
时,会立刻返回,并释放资源,获取子进程退出信息 - 如果在任意时刻调用
wait/waitpid
,子进程存在且正常运行,则进程可能会阻塞 - 如果不存在该子进程,则立刻出错返回
进程程序替换
替换原理
通过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,可以自动找到程序所在路径,只需要输入待执行的程序即可
第一个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
- 替换函数,只有失败的返回值,没有成功的返回值