天天看点

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

文章目录

  • 1.进程的创建
  • 2. fork函数返回值
    • 2.1 fork为什么有两个返回值
    • 2.2 为什么给子进程返回0,给父进程返回子进程的pid?
  • 3 写时拷贝
    • 3.1代码为什么要共享
    • 3.2数据为什么要私有
    • 3.3何谓写实拷贝
    • 3.4实例分析
  • 4.fork常规用法和调用失败原因
  • 5.如何理fork和子进程创建
  • 6.进程终止
    • 6.1进程退出场景
    • 6.2进程退出的常见方法
  • 7.进程等待
    • 7.1进程等待的必要性
    • 7.2wait方法
    • 7.3waitpid方法
      • 7.3.1获取子进程的status
  • 8.进程替换
    • 8.1替换原理
    • 8.2替换函数
    • 8.3函数解释
    • 8.4命名理解
    • 8.5实战操作

1.进程的创建

fork在已存在进程之中创建一个新的进程,这个被创建的进程称之为子进程,原来已知的进程称之为父进程;

进程是由可执行程序和它对应的数据和代码以及一堆数据结构组成(PCB,虚拟地址空间,页表等);

而子进程的创建是以父进程为模板的,因此调用fork之后就有两个代码相同的进程,并且两个进程都会从fork下一行代码开始执行

父进程调用fork,当控制权转移到内核中的fork代码后(fork是系统调用接口),内核会做如下事情:

1.分配新的内存块和内核数据结构给子进程

fork在内核之中,操作系统是管理软硬件资源的,因此有权限管理进程,并且给进程分配内存和数据结构

2.将父进程部分数据结构内容拷贝至子进程

子进程的创建是以父进程为模板的,将父进程的PCB、地址空间、页表映射内容拷贝给子进程

3.添加子进程到系统进程列表当中

当子进程被添加到进程列表中之后就可以被调度了

4.fork返回,开始调度器调度

当fork返回,开始调度之后就完成了一个子进程的创建

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

2. fork函数返回值

子进程返回0,父进程返回的是子进程的pid

2.1 fork为什么有两个返回值

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

2.2 为什么给子进程返回0,给父进程返回子进程的pid?

因为一个父进程或许会有多个子进程,那么它如何准确的定位寻找它的这些子进程呢?靠的就是创建时候返回来的子进程的pid,这就是父进程寻找子进程的标识,父进程通过这个标识就可以对子进程精准定位。而子进程永远只有一个父进程,因此不需要标识

3 写时拷贝

通常父子代码共享,父子不再写入时,数据也是共享的,当任意一方试图写入,便以写入时拷贝的方式各自一副副本,这种情况就叫做写时拷贝

3.1代码为什么要共享

程序=代码(逻辑)+数据;

子进程是以父进程为模板创建而来的,因此父子进程代码都是一样的,同时因为页表的映射权限问题,代码是不可以被修改的,如果各自父子进程各自私有一份,会造成空间的浪费

分流之后代码也是共享的,只是会不会去执行的问题。比如,你家里有一百辆车,但是你最常开的只有3辆,其余的车也是你的,只是没有去碰罢了

3.2数据为什么要私有

在页表中,进程数据的权限大多是可读可写的,如果共享,一个进程的数据改变就会影响另外一个进程,这样就破坏了进程之间的独立性,因此数据是私有的

3.3何谓写实拷贝

顾名思义,就是写的时候进行拷贝

一个进程的数据是很多的,不是所有的数据子都要立即使用,且不是所有的数据都需要进行拷贝。但是如果马上要独立就需要将数据全部拷贝,把本来可以在后面拷贝的,甚至不需要拷贝的数据都拷贝了,就比较浪费空间

通常父子代码共享,父子不再写入时,数据也是共享的,当任意一方试图写入,便以写入时拷贝的方式各自一副副本,这种情况就叫做写时拷贝

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

3.4实例分析

当有数据20M,需要修改其中10M的时候,发生写时拷贝,是拷贝10M还是拷贝20M?

答案是拷贝10M,因为另外的10M不一定有用,写时拷贝只拷贝当下有用的数据

从操作系统的角度出发,OS只会尽可能的节省空间

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

4.fork常规用法和调用失败原因

常规用法:

1.子进程赋值父进程,通过if、else来实现父子进程执行不同的代码段。比如父进程等待客户端请求,生成子进程来进行处理。

2.一个进程要执行一个不同的程序。比如fork后,子进程调用exec函数

调用失败原因:

1.系统中有太多的进程

进程是需要占据空间的,包括PCB,页表,代码,数据等等,都需要向系统申请空间,有可能空间不够就被系统拒绝了

2.实际用户的进程数超过了限制

有可能一个用户申请的进程数超过了操作系统允许的数量,因此申请失败

5.如何理fork和子进程创建

子进程创建本质上是系统多了一个进程,因此操作系统需要将新的进程管理起来,就需要给进程创建PCB、虚拟空间、页表、映射关系等。

而子进程是以父进程为模板的,这些数据大体上是类似的(比如pid等等就不会拷贝过来),并且在一开始是共享代码和数据的,只有当任何一方发生数据写入的时候才会发生写时拷贝,将数据独立出来;

6.进程终止

6.1进程退出场景

进程退出有如下三种场景

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

1.代码运行完毕,结果正确

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

2.代码运行完毕,结果不正确

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

3.代码异常终止

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

6.2进程退出的常见方法

通过命令 echo $? 可以查看最近一次进程的退出码

正常终止:

1.从main函数返回

在main中执行return 等同于执行exit(n),因为调用main的运行时,函数会将main的返回值当做exit的参数

2.调用exit

a.执行用户通过atexit或on_exit定义的清理函数

b.关闭所有打开的流,所有缓存数据均被写入

c.调用_exit

3.调用_exit,直接清理进程,不会刷新缓冲区

异常退出:

ctrl +c,信号终止

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

7.进程等待

是父进程通过wait等系统调用,用来等待子进程状态的一种现象,且是必须的

7.1进程等待的必要性

子进程被创建出来,谁先运行是由调度器来决定的

进程创建本质是系统多了一个进程,是多了PCB、地址空间、页表、代码数据等

一个进程终止分为僵尸和彻底释放两种情况

彻底释放:释放PCB、释放地址空间、释放页表、代码和数据被情况

僵尸状态:进程的代码和数据被释放了、甚至部分页表被释放了、但是这个进程的PCB要被保留下来、PCB中会记录进程的退出码,这个退出码和数据信息将来要被父进程通过wait/waitpid进行读取

一般而言是让子进程先退出的:

1.如果父进程先退出,子进程变成了孤儿进程,子进程被系统接管,资源也被系统回收,但是回收的时间和系统有关系,不一定是立即回收的

2.如果父进程先退出,子进程变成了僵尸进程,会导致内存泄漏

2.因为父进程很容易对子进程进行管理(进行垃圾回收)

3.创建出来的子进程是用来处理业务的,什么时候执行完是不确定的,需要父进程帮我们拿到子进程处理的结果,读取子进程状态

一般子进程是需要被父进程等待的,等待的方式有wait/waitpid

等待的目的是为了进行垃圾回收和获知子进程运行结果

7.2wait方法

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

返回值:

成功返回被等待进程的pid,失败返回-1

参数:

输出型参数,获取子进程退出状态,不关心则可以设置为NULL

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

7.3waitpid方法

当一个子进程退出变成僵尸状态时,我们的父进程通过waitpid释放它的僵尸状态,并且读取子进程的退出码和退出时所收到的信号信息

所以需要传入一个st参数,st读取的数据是在进程的PCB中保存的。当进程退出的时候,我们获取到st中的信息就可以对进程的退出状态进行分析(正常退出结果正确、正常退出结果不正确、被信号杀死),我们可以通过这些信息提示用户或加入一些信息进行判断

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换
Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

返回值:

当正常返回的时候waitpid返回收集到的子进程的进程ID

如果设置了选项WNOHANG,而调用中waitpid发现自己没有已退出的子进程可收集则返回0

如果的调用出错,则返回-1,这时errno会被设置成相应的值以指示错误所在

参数:

pid:

pid=-1,等待任意一个子进程,与wait等效

pid>0,等待其进程id与pid相等的子进程

staus:

WIFEXITED(status):若为正常终止子进程返回的状态,则为真。(查看进程是否正常退出):!(status & 0x7f)

WEXITSTATUS(status):若WIFEXITED非零,提取子进程退出码。(查看进程的退出码):(status>>8) & 0xff

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

options:

1.默认是0,表示阻塞方式,只要子进程不退出,父进程就一直在等,容易卡住,于是下面就有了对立的WNOHANG(非阻塞等待)

2.WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予等待。若正常结束则返回该子进程的id

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

7.3.1获取子进程的status

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充

如果传递NULL,表示不关心子进程的退出状态信息

否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程

status不能简单的当作整形来看待,可以当作位图来看待

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

8.进程替换

8.1替换原理

fork创建子进程的目的:

让子进程和执行父进程一部分代码:代码共享

让子进程执行和父进程完全不同的事:调用exce函数,代码也要发生写时拷贝

当进程调用另外一种exce函数时,该进程的用户空间和代码和数据将完全被新程序替换,从新程序启动的例程开始执行

调用exce函数并不是创建新进程,所以调用exce前后该进程的id并没有改变

新的代码和数据的替换过程是由操作系统来进行的,我们只需要调用操作系统提供的系统调接口函数即可,下面介绍六种exec开头的替换函数

8.2替换函数

有六种exec开头的函数,统称为exce函数

int execl(const char *path,const char *arg,…);

int execlp(const char *file,const chat *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[]);

int execve(const char *pash,char *const argv[],cahr *const envp[]);

操作系统只提供了execve一个调用接口,其余的五个函数都是堆execve的封装

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换
Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

8.3函数解释

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回

调用出错则返回-1

exce函数只有出错返回值,成功没有返回值,因为成功源程序所有数据和代码都会被覆盖

8.4命名理解

l(list):表示参数采用列表

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

v(cevtor):参数采用数组

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

p(path):有p自动搜索环境变量PATH

带p的传入系统中的程序或者命令的时候,会自动去环境变量PATH中寻找对应的指令,不需要传入文件路径

不带p的,则需要自己传入文件路径

e(env):表示自己维护环境变量

8.5实战操作

Linux——进程控制1.进程的创建2. fork函数返回值3 写时拷贝4.fork常规用法和调用失败原因5.如何理fork和子进程创建6.进程终止7.进程等待8.进程替换

继续阅读