在讲父子进程之前,我们接着上面那篇继续讲
1.查看进程
mycode.c
makefile
我们在zs_108直接编译mycode.c,直接运行,然后我们转换另一个账号来查看这个进程
我们可以通过ps指令来查看进程
我们就会好奇了,第二行是什么?我们查的是第一行的啊
那个是指令的ps的进程
PID有什么用呢?
一个PID只对应一个进程
这个非常有用,比如说我们可以让一个进程强制停止下来
我们执行下面这个就能让这个进程停止
程序就被强制停止了
2.通过系统调用获取进程标示符
2.1.PID——子进程
我们怎么获得自己的PID呢?
用系统调用——getpid函数
我们用man查一下
我们来使用一下就知道了
mycode.c
makefile
在执行之前我们先执行这个
注意test是我的可执行程序的名字
我们再运行我们编译好的可执行程序
发现右边的也开始出现数据了
发现跟我们查的PID是一样的啊
我们可以多跑几次,发现PID变了,但是两边的PID都是一样的
2.2.PPID——父进程
PPID是通过函数getppid来实现的
我们还是用man手册查询一下
我们去修改代码
我们重复上面PID的步骤
我们再执行我们的test
完全对的上
我们终止一下我们的程序,我们再打开试试看
我们发现我们的PID变化了,但是父进程PPID没变
那么这个PPID是谁啊
bash进程???
我们每次登录时,系统都会为我们创建一个bash进程
3.通过系统调用创建进程-fork初识
我们可以用man查看fork一下
他的作用是创建一个子进程
它的用法有的奇怪,我来展示一下
我们运行一下这段代码
我们再修改一下代码
再执行一下
怎么还打印到命令行里面来了???
我们再修改一下代码
before是一条,after是两条,为什么呢?
这个是因为fork()的原因
按照手册的说法,fork的返回值既大于0又等于0,这可能吗?????
我们来验证一下
我们编译运行一下
两个死循环在跑???
这个在c语言是基本不可能的,但是这个在操作系统是可以的
有没有发现children的PPID和parent的PID是一样的,所以他们是纯正的父子关系
在执行fork之前只有一个进程,执行到fork后就创建子进程,父进程就是原来的那个进程
创建进程的方式
- ./可执行程序
- fork()函数
接下来我们好好来好好研究一下fork函数
程序开始时按照从上往下的执行顺序创建一个执行流,执行到fork时创建子进程,子进程的返回值等于0,父进程的返回值大于0 ,就变成了两个执行流,父进程是旧的那个执行流,子进程是新创建的进程
3.1 为什么要创建子进程?
父进程创建子进程通常是希望子进程来协助父进程来完成一些工作,这些工作是单进程无法实现的。
例如:用户在下载某一款软件时,同时存在播放该款软件的官方宣传视频的需求。这就需要子进程来协助父进程来达到边下载边播放的目的。即通过一定手段(通常时fork()的返回值),让父子进程分别执行不同的代码!
3.2 fork()究竟干了些什么?
- fork()创建进程后,OS中会多一个进程(子进程)。
- 在创建过程中,Linux操作系统会为子进程创建对应的PCB,并用父进程的大部分属性来初始化子进程的相关属性(如子进程的pid、ppid、所在路径等属性信息则是单独实现)。
- 最后将该进程链入到运行队列中,等待CPU的调度!!
而进程 = 代码 + 内核数据结构和数据,并且进程间时是相互独立的。而进程中的代码和数据等是操作系统在创建该进程时,从磁盘上加载拷贝到内存中的。
但创建的子进程是直接在内存中创建的,子进程并没有相应的代码和数据,那要怎么解决这个问题呢?
- 实际上,代码只是用于读取的。
- 所以Linux中fork()创建的子进程和父进程共用同一段代码。
- 但对于数据来说,父/子进程间必然会存在差异(比如pid、ppid等)。
- 同时为了保证父/子进程间的独立性,必须在父/子进程中各自独立私有一份。
- 而在Linux中采用了写时拷贝的方式来解决这个问题。
下一个问题就是为啥我们在前面的展示中,fork()创建出的子进程只执行fork()后的代码?在fork()前的代码子进程能否“看见”呢?
- 答案是子进程能看见fork()前的所有代码!
- 至于为啥子进程只执行fork()后的代码,这是由于代码运行过程中,存在诸如epi、pc等寄存器。
- 这些寄存器中会保存当前指令要执行的下一条指令的地址。
- 而fork()创建子进程过程中,父进程中pc、epi等寄存器的结果也“继承”给了子进程。
- 所以才出现子进程只运行执行fork()函数后的代码!
3.3 fork为什么会存在两个返回值?
fork()是一个函数,其存在返回值。fork()在执行是,操作系统内核做了如下工作:分配新的内存块和数据结构给子进程、将父进程的部分内核数据结构拷贝给子进程、添加子进程到系统进程列表中,调度器开始调度。
fork()函数执行完后,已经完成了创建子进程、将子进程添加到调度队列中等工作。
当父进程和子进程在调度队列中被调度时,两个进程都需要执行return语句,都需要返回一个值!所以fork存在两个返回值!(操作系统通过寄存器来实现返回值返回两次)(真正原因其实在于地址空间的实现)
3.4 为何fork函数中父进程返回子进程的pid、子进程返回0?
对于一个进程,其父进程是唯一确定的,但其子进程可能存在多个。就像我们生活中,一个孩子的爹是唯一确定的;但对于一个父亲,其可能存在多个孩子。
而父进程和子进程之间是管理和被管理的关系。父进程为了更好的管理好子进程,所以fork函数在创建子进程后返回子进程pid;对于子进程来说,其只需管理好自身即可,所以返回0。
3.5 父进程和子进程谁先运行?
我们已经看到了fork()函数会创建一个子进程。创建完子进程后,子进程会被加载链接到系统进程列表中等待CPU调度运行。
至于父进程和子进程谁先被CPU调度是不确定的。进程被调度的先后顺序由各自的PCB中的调度信息(时间片 + 优先级等)+ 调度器算法确定。换句话说,父进程和子进程谁先运行是由操作系统决定的!
3.6 为何同一个变量接收两个返回值
我们前面已经提到过了进程是相互独立的,为例保存fork()创建出的子进程和父进程之间的独立性,我们所采用的解决办法是:父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:
我们来深入了解一下
- 操作系统在创建子进程时,会先将父进程页表中的访问权限大部分改为只读权限。
- 然后以父进程为模板,将数据拷贝给子进程。
- 当子进程或父进程对数据试图修改数据,由于页表中的访问权限为只读。此时该行为会被操作系统识别到。并分为以下两种情况:
- 如果代码和数据所处区域本身就是只读的(如:代码区、字符常量区),此时OS认为该行为非法被拦截。
- 原本数据可读可写,但操作系统将对应页表中的访问权限设置为只读。当操作系统识别到该信息时,操作系统并不会将行为视为错误,而是作为重新申请内存拷贝内容的一种策略机制。此时操作系统会重新申请开辟空间,拷贝和修改数据,并修改页表中的映射关系。
3.7.fork创建子进程失败原因
在Linux中,fork()创建子进程失败通常由有以下两种原因:
- 系统中有太多的进程。操作系统内存不足。
- 实际上,用户使用fork()创建子进程是有次数限制的。当超过该范围时,fork创建子进程失败!
标签:fork,创建,代码,PID,进程,PPID,我们 From: https://blog.csdn.net/2301_80224556/article/details/1395436663.8.bash如何创建子进程?
我们看看程序最开始的那个printf,PPID是45106,那么45106是谁呢?
很明显了,就是我们自己的PID
这个bash创建子进程的过程肯定也调用了fork()