送给正在努力前行的你一句话:要努力,但不要着急,繁花锦簇,硕果累累都需要过程!
博主主页
目录
1.进程创建
fork函数初识
fork函数返回值
写时拷贝
2.进程终止
进程退出场景
进程如何退出
3.进程等待
进程等待的必要性
进程等待的方法
4.进程程序替换
1.进程创建
fork函数初识
在Linux中fork函数是从一个已经存在的进程中创建一个新的进程,新的进程被称为子进程,而原进程称为父进程。
#include<unistd.h> pid_t id = fork()
返回值:有两个返回值,给父进程返回子进程的pid,给子进程返回0,创建失败返回-1
进程调用fork函数之后:
分配新的内存块和内核数据结构给子进程
将父进程部分数据结构内容拷贝到子进程
fork函数返回值
为什么fork函数有两个返回值?
保证进程的独立性
如何理解给父进程返回子进程的pid,给子进程返回0?
因为父进程是具有唯一性的,通过子进程能够找到父进程
如何理解同一个id值会保存两个值,让if,else同时执行?
fork函数在返回之前就已经创建好子进程分流开始调度了
写时拷贝
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。
2.进程终止
进程退出场景
#include<stdio.h> int main() { return 0; }
为什么main函数结束之后要返回0呢?
答案:0是进程退出的时候对应的退出码
退出码:0表示正常退出,非0表示进程失败
非0具体是几,表示不同的错误
查看退出码:
命令行查看进程退出码:
echo $?:返回运行记录最近一个进程在命令行中执行完毕时对应的退出码
进程如何退出
1.main函数return返回
2.任意地方调用exit(退出码)_exit(退出码)
exit和_exit的区别:
1.exit头文件:#include<stdlib.h>
_exit头文件:#include<unistd.h>
2.运行这段代码:exit
sleep两秒之后打印出“hello world”
_exit运行这段代码:
sleep两秒之后没有打印出任何结果
所以exit和_exit的区别是:
exit终止进程主动刷新缓冲区,_exit终止进程不会主动刷新缓冲区
3.exit是库函数,_exit是系统调用接口
3.进程等待
进程等待的必要性
子进程退出,如果父进程不进行回收,那么子进程就可能会成为僵尸进程,一旦成为僵尸进程操作系统也没有办法终止掉该进程,此时就有可能会造成内存泄漏的问题,同时也无法得知该进程将任务完成的如何!
因此对于上面这种问题必须通过进程等待的方式,让父进程回收子进程的资源,获取子进程的退出信息。
进程等待的方法
wait方法:
#include<sys/types.h> #include<sys/wait.h> pid_t wait(int*status);
返回值:
成功返回被等待进程的pid,失败返回-1
参数:
输出型参数,获取子进程退出状态,不关心可以设置为NULL
验证父进程回收子进程的过程:
这段代码的含义是:用fork创建子进程,然后让子进程跑10秒钟,让父进程休眠15秒,所以中间就会有5秒子进程处于僵尸状态,因为父进程没有回收子进程,5秒后wait等待成功,然后被父进程回收了
waitpid方法:
#include<sys/types.h> #include<sys/wait.h> pid_ t waitpid(pid_t pid, int *status, int options);
返回值:
等待成功返回子进程的pid
参数:
pid
pid=-1,等待任意一个进程,与wait等效
pid>0,等待子进程id与pid相同的进程
status:查看子进程退出时的状态信息
通过运行后可以观察到,等待成功之后返回子进程的pid,但是并没有通过status正确获取到子进程终止时的状态信息 ,因为在进程退出的时候我们将子进程终止时的状态信息设置为10,而status拿的是2560.这是因为我们不能将status当作一个整数来看待,因为status有自己的位图结构:
获取进程终止时的状态信息:就需要拿到status二进制中的次8位
获取方法:(status>>8) & 0xFF
获取进程终止的信号:就需要拿到status二进制中的前7位
获取方法:status &0x7F
此时我们就成功获取到了进程退出时的状态信息和终止信号;
注:当进程正常终止的时候终止信号就为0,如果进程异常被操作系统异常终止就会显示进程异常终止的对应信号
进程异常终止的信号:
当进程中存在野指针的问题时,进程异常,被操作系统终止,终止信号就为11代表段错误,而我们的进程也没有正常运行完,所以status也没有获取到次8位的值,我们默认异常终止状态信息设置为0
status获取进程退出时的状态信息第二种方式:宏
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)options:
1.设置为0表示阻塞式等待,当子进程没有退出的时候,父进程一直在等待子进程,直到等待成功,释放子进程:
2.设置为WNOHANG,表示为阻塞式等待,父进程会检测子进程是否退出的状态,如果子进程没有退出,则父进程不会继续等待子进程,而是继续执行其它的操作,但是如果非阻塞式等待第一次子进程没有退出,则可能会造成父进程先释放,子进程变成孤儿进程,所以对于非阻塞式等待一般采用轮询的方式进行等待!
为什么会存在非阻塞式等待:当父进程在轮询式等待的过程中,子进程并不会占用父进程的资源,因此父进程可以做其它的事情:
例:
4.进程程序替换
概念:
将指定的程序加载到内存,让指定的进程执行
所以进程程序替换完成包括两个步骤:1.找到要执行的程序,2.要如何执行
进程程序替换的方法:
通过函数调用来实现的:
#include <unistd.h>` 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[]);
例:通过调用execl这个函数,让我们的进程执行ls这个命令
此时就完成了我们自己写的进程执行别的程序,这就是一次程序替换
execlp函数介绍:
功能:只需要在调用函数的时候传需要执行哪个程序,就自动会在环境变量PATH中查找
execv函数介绍:
功能:可以将所有的可执行参数放入到数组中统一传递,而不用使用可变参数方案:
execvp函数介绍:
功能:不需要带路径,只需要传调用谁,就自动会在环境变量PATH中查找:
execle函数介绍:
功能:可以将环境变量传到子进程中
mybin.c:
myexec.c:
注:这五个函数都不是系统调用接口,而是基于execve这个系统调用接口进行封装的
进程程序替换的本质
验证:
运行发现,输出了start,但是没有输出end,说明在调用execl函数之后,原来进程的代码和数据被覆盖了
为什么要进行程序替换
因为进程具有独立性,因此可以通过程序替换的方式让子进程执行一个全新的程序:
进程替换的使用场景:
模拟实现一个简易的shell: