fork创建子进程,OS做了什么
进程 = 内核数据结构 + 代码和数据
创建进程首先要创建私有的内核数据结构,并将程序的代码和数据加载到内存中。
因为代码的只读属性,所以可以与父进程共享。但是数据是可修改的,所以必须分离。同时为了保证高效使用内存,所以只读数据父子进程共享一份,可修改的数据只有在被修改时才会开辟独立的变量空间(写时拷贝)来保证进程的独立性。
子进程能看到fork之前的代码,为什么只能在fork之后的语句开始执行
进程随时可能会被中断,下次回来时需要从之前的位置继续向下执行,所以CPU会记录下一条指令的地址 (EIP)。fork之后的子进程是可以看到所有的代码的,但是因为子进程的EIP起始值就是fork之后的代码,所以只能从fork之后的代码开始执行。
进程终止
进程终止OS做了什么
释放进程申请的相关内核数据结构和对应的数据和代码,本质就是释放系统资源
进程终止的常见方式
- 代码跑完,结果正确
- 代码跑完,结构不正确
- 代码没有跑完,程序崩溃
main函数的返回值:
main函数的返回值就是程序的退出码,返回给上级进程,用来评判进程的运行结果(可以忽略)。
echo $? # 查看在最近一次进程的退出码
进程的退出码有很多种,其中0表示成功,非0表示失败。每个退出码对应这不同的失败原因。可以通过strerror
来查看对应错误码的描述
但是程序崩溃时,进程退出码没有意义,因为main
函数的return
语句没有被执行。
如何终止进程
-
main
函数返回 -
exit或_exit
函数终止进程
C语言函数:
系统调用接口:
两者的区别:
可以说明缓冲区是语言层面给我们维护的,如果是OS维护的,那么系统调用接口_exit
也会刷新缓冲区。
进程等待
为什么要进行进程等待
- 防止僵尸进程,造成内存泄漏
子进程退出,父进程不管子进程,子进程就要处于僵尸状态。
- 父进程需要回收子进程退出信息,回收子进程资源
如何进程等待
wait(NULL) = waitpid(-1, NULL, 0)
其中status
并不是整体使用,而是通过比特位的方式进行划分,我们只需要关心低16位:
中间的core dump标志位表示是否发生了核心转储:
其中次低8位表示子进程的退出码,所以可以通过位运算来获取子进程的退出码:
WEXITSTATUS(status) # 获取子进程的退出码,如果被信号所杀值为0
status >> 8 & 0xFF # WEXITSTATUS的原理
低7位表示子进程收到的信号,同样可以通过位运算来获取(可以用来判断子进程是否正常退出):
WIFEXIT(status) # 判断子进程是否正常退出,如果子进程正常退出为0
status & 0x7F # WIFEXIT()的工作原理
为什么不直接使用全局变量获取子进程的退出信息?
因为进程具有独立性,写入退出信息时会发生写时拷贝,父进程无法拿到,信号更是如此
进程具有独立性,wait和waitpid如何拿到子进程的退出信息
僵尸进程还是会保留进程的
PCB
信息,task_struct
中保留了子进程退出时的退出结果信息,进程等待的本质就是读取子进程的task_struct
结构。
因为这两个函数是系统调用,所以有权利访问task_struct
进程替换
概念
进程替换:通过特定的接口,加载磁盘上一个全新的程序(代码和数据)到调用进程的地址空间中,替换调用进程
为什么要进程替换
在多执行流时,有时候需要子进程执行新的程序。
怎么做到的(原理)
PCB和虚拟地址基本不变,将新程序的代码和数据加载到内存中,并和当前进程的页表重新建立映射
但是并没有创建新的进程,因为PCB结构体没有发生变化,只是页表的映射关系发生了变化。
子进程进行替换时,代码和数据的替换也是一种写入,所以会发生写时拷贝,将代码与数据跟父进程彻底分离。
如何进程替换
通过系统调用exec函数实现:
这些函数如果调用成功会将当前进程的所有代码和数据进行替换,后续的代码也就不可执行。如果调用出错则返回-1,所以这些函数只有出错的返回值,没有成功的返回值
命名解释:
示例:
# execl:命令行参数使用列表的形式传参
execl("/usr/bin/ls", "ls", "--color=auto", "-l", NULL);
# execv:命令行参数使用数组的形式传参
char *const argv[NUM] = { "ls", "--color=auto", "-l", NULL};
execv("/usr/bin/ls", argv);
# execlp:自动搜索PATH变量,命令行参数使用列表的形式传参
execlp("ls", "ls", "--color=auto", "-l", NULL);
# execv:自动搜索PATH变量,命令行参数使用数组的形式传参
char *const argv[NUM] = { "ls", "--color=auto", "-l", NULL};
execv("ls", argv);
# execle:命令行参数使用列表的形式传参,同时可以传递环境变量
execle("/usr/bin/ls", "ls", "--color=auto", "-l", NULL, env);
但是这些函数都是C语言的库函数,在底层都是调用系统调用接口execve
:
使用方法与上面的库函数类似,只是参数的形式不同文章来源:https://www.toymoban.com/news/detail-555915.html
文章来源地址https://www.toymoban.com/news/detail-555915.html
到了这里,关于Linux进程控制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!