Linux之进程控制专讲

162 阅读6分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。


前面博客中讲解了进程控制中的一小部分:进程创建

下面开始其他部分内容总结:


进程终止(进程退出)

exit()

库函数(3号man手册)

_exit()

系统调用函数(2号man手册)

  • 所以实际是exit调用了_exit系统函数

区别

  1. main函数中 return 或者 exit 终止进程,会刷新缓冲区,并释放资源。

  2. _exit终止进程会直接释放资源

进程返回值:

  1. 程序正常退出,结果符合预期,返回0
  2. 程序正常退出,结果不符合预期,返回错误码
  3. 程序异常退出:[ 崩溃 ]

通过指令$? : 查看上一程序退出码。

程序的退出码使用一个字节来保存,最大为255


进程等待

进程等待:等待子进程状态改变(一般为终止),一般用于循环中,轮询判断

  • 为什么要等待子进程退出? 避免产生僵尸进程。

最好是在子进程退出的时候等待,但是父进程根本就不知道子进程何时退出~ 因此只能在子进程创建之后调用wait,进行进程等待。 调用wait就是一直在等待子进程的退出。

wait: 系统调用接口,man手册2号。

wait接口是一个阻塞函数,功能是等待子进程退出。

如果没有任何一个子进程退出,则一直等待,直到有任一子进程退出。

阻塞:为了完成某个功能发起调用,如果当前不具备完成条件,一直等待,直到完成后返回。

非阻塞:为了完成某个功能发起调用,如果当前不具备完成条件,直接报错返回。

wait()

pid_t wait(int *status);
  • 功能:等待任意一个子进程退出
  • status:用于返回退出值
  • 返回值:如果返回退出子进程的pid。 如果出错:返回-1

用法

int status;
wait(&status);

waitpid()

pid_t waitpid(pid_t pid,int *status,int options);	
  1. pid指定的进程id<= -1:等待任一子进程 >= 0 :等待指定子进程

  2. status: 用于获取返回值

  3. option:选项,默认为0 WNOHANG:将waitpid设置为非阻塞

  4. 函数返回值:

    < 0 :出错

    == 0 :没有子进程退出

    > 0 :退出子进程的pid

用法

while(waitpid(pid,&status,0) == 0){
	perror("waitpid error");
}

waitpid函数的参数status值中:

  • 16位没有使用~
  • 16位存放:
    • 8位:返回码,例如255
    • 8位:
      • 1位:coredump标志(核心转储) :保存异常退出时,程序的运行信息。

      • 7位:异常退出原因,异常事件的信号值。

获取子进程返回值: 我们获取返回值其实只是通过按位右移就可获取:

(status >> 8) & 0xff
  • 正常终止: 0xff:一个字节,相当于char*。一个字节二进制位上全为1,按位与&上一个数字,则剩下的就是低八位,其中存放的是程序的返回值。

  • 异常终止

7位中保存异常信号值,出错一定大于 0,否则正常退出低7位为0

通过低7位是否为0,判断程序是否异常退出。

status & 0x7f 

7位如果是0表示正常退出。

已经封装好的宏接口

  • WIFEXITED(status);:如果为真,正常退出
  • WEXITSTATUS(status);

上面获取返回值的操作相当于:

if(WIFEXITED(status)){
	printf("exit code :%d\n",WEXITSTATUS(status));
}

程序替换

程序替换:替换进程所运行的程序,重新初始化虚拟地址空间,更新页表信息

替换后重新从替换进程的main函数起始处开始运行。就是将进程的运行虚拟地址空间所映射在物理内存的区域进行改变,改变成另一个程序在内存中的位置,更改内存指针,更新页表信息,重新初始化虚拟空间。

  • 为何替换? 常用于替换子进程程序,让子进程完成其他功能。

  • 如何替换? exec函数族 (调用的都是execve系统调用接口)

exec函数族

不定参函数:

execl	(const char *path,const char *arg,...);
execlp	(const char *file,const char *arg,...);//如果没有给定路径,默认去PATH环境变量中查找,找不着则报错
execle	(const char *path,const char *arg,...,char *const envp[]);
		
execv	(const char *path,char *const argv[]);
execvp	(const char *file,char *const argv[]);
execve	//(man 2号手册)	【最终调用函数】
  1. execlexecv区别:参数的赋予是以指针数组形式还是以不定参形式

  2. p与无p的区别:第一个参数:程序名称是否需要给定路径,未给定就从PATH中找。

  3. e与无e的区别:环境变量是否由用户自己设置,自己设置环境变量就会覆盖掉原有的全局变量。

shell处理流程: while(1){

  1. 获取标准输入

  2. 对输入字符串进行解析:获取程序名称+参数

  3. 创建子进程 程序替换 -- 程序名称 目的:(保证主程序的稳定性) 父进程自己并不完成功能,而是使子进程去完成, 如果发生错误,shell就崩溃了! 所以这个“危险的”操作让子进程去处理。

  4. 进程等待 }


虚拟地址空间

Linux中为一个结构体:内存描述符mm_struct

作用

  1. 保持进程独立性。
  2. 通过页表映射物理地址,充分利用物理内存。
  3. 增加内存访问控制。

内存页:页号|页内偏移

一页中有4k,一个地址的高20位就是页号,低12位位页内偏移。

虚拟地址通过虚拟页号通过页表得到物理页号,再复制一份页内偏移,虚拟地址与物理地址的页内偏移都是一样的。物理页号结合页内偏移就是物理地址,完成访问控制。


例题两则

  1. 下面程序会创建多少个进程?
fork();
if(fork() && fork() || fork())
	fork();

答案:16个。

0号)
fork();(1号)
if(fork()(2号) && fork()(3号)|| fork()(4号))
	fork()(5号);

0号与1号进程经历完全相同。 创建过程: 零号:0-1-2(4-5)-3(4-5)-5 ①:2(4-5)-3(4-5)-5 ②:4-5 ③:4-5 4号自己返回0,失败,无法进入if语句 14+2=16个

图解

	0
	|-1
	  |-2
		|-4
		|-5
	  |-3
		|-4
		|-5
	  |-5
	|-2
	  |-4
	  |-5
	|-3
	  |-4
	  |-5
	|-5
(共16)
  1. 共打印多少个横杠?
for(int i = 0;i < 2;i++){
	fork();
	printf("-");
}

答案:共打印8个。 因为没有刷新缓冲区,复制了父进程缓冲区中未打印的横杠。

那如果变成了以下形式,多了\n换行符,会打印多少个?

for(int i = 0;i < 2;i++){
	fork();
	printf("-\n");
}

答案:共打印6个。 因为\n刷新缓冲区,没有复制父进程缓冲区中的横杠。


【注】监控while [true] ;do clear;ps aux |grep wait; sleep 0.5;done