朋友们、伙计们,我们又见面了,本期来给大家解读一下有关Linux进程创建与进程fork本质的知识点,如果看完之后对你有一定的启发,那么请留下你的三连,祝大家心想事成!
C 语 言 专 栏:C语言:从入门到精通
数据结构专栏:数据结构
个 人 主 页 :stackY、
C + + 专 栏 :C++
Linux 专 栏 :Linux
目录
前言
回顾上文,我们可以通过系统调用getpid来获取当前进程的ID,同时也可以通过ps ajx 搭配grep 来查看我们运行起来的进程:
1. 进程的创建
在Linux中创建进程的方式:
1. 命令行中直接启动进程 --- 手动启动
2. 通过代码来创建进程
1.1 getpid与getppid
启动进程的本质就是创建进程,这个新启动的进程一般是由父进程创建的!
那么进程与进程之间就注定了存在一种关系:父子关系
通过之前的代码可以发现,在Linux中,登录之后,命令行启动的进程,它的进程id一直在变,但是它的ppid(父进程)一直不变,那么这个父进程是谁呢?
可以使用pa ajx 搭配 grep来查看一下这个进程到底是什么
居然是命令行解释器!!!
所以我们命令行启动的进程都是bash的子进程;
可以使用系统调用getppid来查看当前进程的ppid;
1.2 getpid与getppid原理
那么getpid与getppid的原理是什么呢?
当进程被创建好之后,那么在操作系统内部就会为进程创建一个task_struct的内核数据结构(PCB),那么根据管理的先描述再组织,这个PCB里面肯定保存着该进程的pid与ppid,但是用户并不能直接访问操作系统得到它,需要通过上层系统调用接口来间接访问,因此使用getpid与getppid可以获取到PCB中的pid与ppid。
1.3 查看进程的第二种方式
Linux系统中的进程信息保存在/proc系统文件中,所以可以通过ls指令来查看一下这个系统文件:
可以看到有很多蓝色的数字,那么将我们自己写的程序运行起来,然后再查看系统文件
如果这样子感觉难找的话可以搭配grep指令
总结:Linxu系统中会存在一个proc系统文件,它是一个动态目录结构,用于存放所有存在的进程,目录名称就是以该进程的id来命名。
那么这个以进程id命名的目录里面保存着什么呢?
可以使用指令:ls /proc/进程id -l
里面最显眼的就是一个cwd和一个exe
① exe:可以发现这个exe正好指向的就是我们自己写的代码然后生成的可执行程序的路径。
当可执行程序能运行起来之后,我们可以在proc系统文件中找到这个可执行程序的路径,那么在程序运行期间,我们将可执行程序删掉之后,会发生什么呢?
当程序运行期间,将可执行程序删掉,可以发现:程序并没有被终止,还在正常运行,但是在系统文件中的exe被删掉了,这同样的证明了进程 != 可执行程序,那么为什么删掉可执行程序之后,运行起来的并不会收到影响?
可执行程序即使变成了进程,但是在它的PCB里面还会存储自己的可执行程序在哪个路径,当可执行程序运行之后,将可执行程序删掉,并不会影响已经运行起来的可执行程序,因为删掉的是磁盘中的数据,运行之后已经被加载拷贝到了内存中,但是系统文件proc中的exe已经被删掉了。
一个进程,可以找到自己的可执行程序。
② cwd: 当前目录(当前工作目录)
在一些文件接口中,我们通常会创建文件或者打开文件,当不给定制定路径时,打开或者创建的文件就在当前目录下,它的原理就是cwd在进程PCB中维护了一串路径,当需要在当前目录下打开或者创建文件时就会在前面自动拼接上cwd,默认情况下,进程启动时所处的路径就是当前路径。
更改当前目录的系统调用:chdir(指定路径)
每一个进程,都要有自己的工作目录
1.4 fork创建进程
如何理解启动一个进程呢?
启动一个进程,本质就是系统多了一个进程,操作系统要管理的进程也就多了一个,进程 = 可执行程序 + 内核数据结构(task_struct),创建一个进程就是要申请内存,保存当前进程的可执行程序 + task_struct对象,并将task_struct对象添加到进程的列表中。
1.4.1 初识fork
fork是创建进程的,那么也就是说,在fork之前只有一个执行流(只有父进程执行),fork之后会变成两个执行流(父进程和子进程都会执行)。
1.4.2 fork的返回值
失败返回-1,成功后会将子进程的pid返回给父进程,将0返回给子进程。
看到这里会发现很震惊的事情:一个函数居然会有两个返回值?(后面解释)
1.4.3 fork的一般写法
1. 为什么要创建子进程?
想让子进程协助父进程完成一些工作,这些工作是单进程解决不了的,例如边下载,边播放,这样做也可以提成效率和用户体验。
2. 创建子进程的目的就是为了执行和父进程不一样的代码。
3. 可以通过fork之后的返回值,让父子进程执行不同的代码片段。
4. fork之后,用if进行分流。
#include <stdio.h> #include <unistd.h> int main() { pid_t id = fork(); if(id < 0 ) return 1; else if(id == 0) { // 子进程 while(1) { printf("我是一个子进程: pid: %d, ppid: %d, fork return id: %d, 我正在执行下载任务!\n",getpid(), getppid(),id); sleep(1); } } else { // 父进程 while(1) { printf("我是一个父进程: pid: %d, ppid: %d, fork return id: %d, 我正在执行播放任务!\n", getpid(), getppid(), id); sleep(1); } } return 0; }
2. fork原理(基本理解)
1. fork都干了写什么事情?
fork创建子进程,系统中会多一个子进程,创建子进程,会以父进程的PCB为模版,为子进程创建PCB,但是子进程没有属于自己的代码和数据,它会和父进程共享代码和数据!所以fork之后,父子进程会执行一样的代码。
那么这里就存在一个问题,那么既然是共享的代码和数据,在fork之前的代码子进程也是可以看到的,那么子进程为什么不会从头到尾将代码重新执行一遍呢?
我们都知道,程序是按照从上往下按照顺序执行的,那么为什么呢?当我们的程序在运行时,OS内会有一个寄存器pc指针/eip程序计数器,来记录代码执行到了哪一步,那么在pc/eip执行fork之后,eip指向的是fork后续的代码片段,那么既然父子进程共用代码和数据,同样的pc/eip也会被子进程继承下来。
2. 为什么fork给父进程返回子进程的pid,给子进程返回0?
一个父进程不仅仅只存在一个子进程,那么父进程与子进程之间的比例是:父 : 子 = 1 : n
所以父进程为了区分这么多的子进程,就需要一个特定的唯一值来标志每一个子进程,因此每创建一个子进程都会有一个唯一的子进程的pid返回给父进程,同样的子进程只有一个父进程,不需要特定的标记,所以返回0即可。
3. fork之后父子进程谁先运行
子进程创建完成只是一个开始,系统的其他进程、父进程和子进程都是要被调度执行的,这里就需要引入一个新的概念:运行队列;
当父子进程的PCB都被创建并且在运行队列中排队的时候,哪一个进程的PCB先被CPU选择调度,哪一个进程就先运行;
所以父子进程哪一个先运行是不确定的,由各自PCB中的调度信息(时间片、优先级等)+ 调度器的算法共同决定,简单的说就是由操作系统自主决定的。
父子进程执行后续代码的本质就是被CPU调度运行
4. 为什么fork会有两个返回值?
fork是一个系统调用的接口,同时也是一个函数,那么在它的内部会完成子进程的创建工作。
fork之后代码共享,其实在创建子进程完成之后的代码就共享了,那么retutn语句也是一条代码,因此它也会被共享且执行,在创建完成子进程之前只有一个执行流执行return语句,那么在创建完成之后就有两个执行流来执行return语句了,所以子进程和父进程都会返回,因此fork就会有两个返回值了。(有助于理解)
其实真实的情况是:操作系统会通过一些寄存器的手段来做到返回值返回两次。
5. 如何理解同一个变量会有不同的值
当我们平时使用一些app,先打开QQ,再打开微信,然后再打开浏览器,此时将这些进程随便干掉一个,都是不会影响其他进程的运行情况的,同样的,在父子进程的关系中也是不会互相影响的。
![]()
所以,进程之间运行的时候是具有独立性的,无论是什么关系!
那么系统是怎么做到的呢?
首先表现在每个进程都有自己独一份的PCB;
进程之间不会互相影响彼此。
但是父子进程是共享代码和数据的,那么怎么能做到父子进程互不影响呢?
代码本身是只读的,不会修改,所以不会产生影响,但是数据是可以的!!!
比如我们的代码中存在一个全局变量ret,父进程运行的条件是ret大于0,那么如果在子进程中添加了修改ret的代码,将ret修改到小于0,那么父进程就会退出!!,所以这种情况是不被允许的,所以父子进程的代码可以共享,但是数据必须各自私有一份。
因此OS就使用写时拷贝的方法将父子进程的数据各自私有一份。
return的本质就是写入,id是父进程定义的局部变量,那么它就一份数据,既然是数据,在return时会发生写时拷贝,所以才产生了同一个变量具有不同的值。
那么既然发生了写时拷贝,会有两个值,那么这两个指的地址是不是一样的呢?我们可以使用代码来看一下:
可以发现,地址相同的两个值居然值不一样,这怎么可能呢?唯一的解释:这个地址绝对不是物理地址!!!(后面的进程地址空间再详细介绍)
朋友们、伙计们,美好的时光总是短暂的,我们本期的的分享就到此结束,欲知后事如何,请听下回分解~,最后看完别忘了留下你们弥足珍贵的三连喔,感谢大家的支持!