进程控制(进程创建、进程终止、进程等待、进程程序替换)

进程创建

fork函数

子进程是拷贝父进程的PCB的,子进程的大部分数据是来源书父进程,例如:内存指针(数据段、代码段)

父进程创建子进程成功之后,父子进程是独立的两个进程(进程的独立性),父子进程的调度取决于操作系统的内核

进程是抢占式执行的,父子进程谁先运行是不能确定的

写时拷贝

fork调用失败的原因:

系统有太多的进程
实际用户的进程数超过了限制

vfork函数

调用栈混乱的问题的解决:子进程先调用,子进程调用完毕后父进程再调用

进程终止

进程终止的场景

从 main 函数的 return 返回

能得到的结果:
	代码执行完毕,没有获得既定的结果
	代码执行完毕,获得了既定的结果

程序崩溃

程序终止的方法

从 main 函数的 return 返回

写一个程序,本身可以直接 return 结束,但是为了看的更清楚,所以打印一下进程号

使用 getpid 函数需要调用<unistd.h>头文件

在这里插入图片描述
运行后发现打印了一个进程号,即刚才的进程的进程号
在这里插入图片描述

exit函数–库函数
void exit(int status);

status会被进程等待接口的参数获取到

还是刚才的程序,这次使用 exit 函数

使用 exit 函数需要调用<stdlib.h>头文件

在这里插入图片描述
跑起来看看,确实是没有走到打印
在这里插入图片描述
此时可以通过 echo $? 来查看退出码
在这里插入图片描述
也就是刚刚在代码中写的 exit(10);

_exit函数–系统调用函数
void _exit(int status);

接下来尝试一下 _exit 函数
在这里插入图片描述
更改的地方很简单,目的也是一样,不想让程序走到打印那一步,运行看看
在这里插入图片描述
发现 _exit 也达成了希望的目标

exit 和 _exit 的区别

对刚才的代码做一些更改,首先是 exit 函数
在这里插入图片描述

此时希望的是能够打印出“oh ho”,而不打印进程号
在这里插入图片描述
完成了目标,再此修改程序,使用 _exit 函数
在这里插入图片描述
再运行代码
在这里插入图片描述
???

东西咧???

但是如果加上一个 \n 呢?

\n:想不到吧,我才是那个关键点哒
在这里插入图片描述
可能都没发现删掉了这个吧

现在是 exit 函数,运行试试
在这里插入图片描述
显而易见的成功了,那接着换成 _exit 函数
在这里插入图片描述
运行一下
在这里插入图片描述
发现这次成功了

原因在于,当我们使用 printf 函数打印时,数据是放在缓冲区中的,而使用 \n 会刷新缓冲区,当使用 _exit 函数时,由于是系统调用函数,所以不会刷新缓冲区

执行用户定义的清理函数
int atexit(void (*function)(void));

继续刚才的代码
在这里插入图片描述
那么此时出现了两种情况

①代码走到了 atexit 函数后进入 func 函数,打印后退出 func,再打印 oh ho,exit函数退出

②代码先打印 oh ho,在进行到exit函数时进入到 atexit 中打印 func 函数

所以到底是哪种情况?

还是直接运行来看看吧
在这里插入图片描述
看来是第二种情况了

再次修改代码
在这里插入图片描述
通过这次的修改,代码在走到打印 oh ho 结束后进入死循环,那此时会怎样输出?
在这里插入图片描述
看起来连输出都没有输出

atexit 函数就是在C库中注册一个函数,举个例子就像是我们订了一个闹铃,但是在到时间之前根本不会响,而 exit 函数就相当于闹铃响的时间,只有当代码运行到 exit 函数时才会使 atexit 在C库中注册的函数展现出来,即这是一个回调函数

进程等待

进程等待的作用

父进程进行进程等待,子进程在先于父进程退出之后,由于父进程在等待子进程,所以父进程会回收子进程的退出资源,从而防止子进程产生僵尸进程

进程等待的接口

wait 函数
pid_t wait(int *status);

返回值:

		成功:返回子进程的 pid 号
		失败:-1

参数:整形指针,int*,占用4个字节
在这里插入图片描述
处于高位的两个字节没有被使用,低位的两个字节中高位的一个字节用于存放进程退出码,低位字节中一个bit位用于存放coredump标志符,剩下的7bit位用于存放终止信号

coredump标志位:
	1:如果取值为1,则表示当中的进程是由coredump(核心转储文件)产生
	0:如果取值为0,则表示当前进程没有coredump产生

终止信号:当前程序是由什么终止的

参数 int* status:是一个出参,调用者准备一个 int 类型的变量,将地址传递给 wait 函数,wait 函数在自己实现的内部进行赋值,当 wait 函数返回之后,调用者就可以通过 int 变量,获取进程退出的信息

使用 wait 函数时需要包含一个名为 <sys/wait.h> 的头文件
在这里插入图片描述
让代码运行起来看看
在这里插入图片描述
可以看到已经输出了子进程和父进程的 pid 号

给父进程加一个死循环,再试着查看他们的进程
在这里插入图片描述
在这里插入图片描述
再去查查子进程的 pid,看看是不是僵尸进程
在这里插入图片描述
现在再在代码中删除 wait
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到产生了僵尸进程

如何获取进程退出码?
(status >> 8) & 0xff
如何获取终止信号?
status & 0x7f
如何获取coredump?
(status >> 7) & 0x1

在这里插入图片描述
在这里插入图片描述
拿到了进程退出符

再看看异常的情况
在这里插入图片描述
此时根本不会退出,直接就崩溃了
在这里插入图片描述
可以看到也输出了

waitpid 函数
pid_t waitpid(pid_t pid, int *status, int options);

pid:

-1:等待任一子进程,一旦等待到了,则返回
>0:等待特定的子进程,大于0的值就是子进程的 pid 号

status:出参,同 wait 函数

options:

WNOHANG:非阻塞等待方式--需要搭配循环去使用,直到完成函数功能
0:阻塞等待方式

进程等待的非阻塞模式

第一步,需要创建子进程,并模拟让子进程先于父进程退出
第二步,在父进程的逻辑中执行进程等待,同时使用非阻塞的等待方式
代码如下

 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <sys/wait.h>
  5 
  6 int main()
  7 {
  8   pid_t pid = fork();
  9   if(pid < 0)
 10   {
 11     perror("fork file");
 12     return 0;
 13   }
 14   else if(pid == 0)
 15   {
 16     //child
 17     printf("i am child, my pid is %d\n, ppid is %d\n",getpid(), getppid());
 18     sleep(5);
 19     exit(1);
 20   }
 21   else
 22   {
 23     //father
 24     //当没又等待到相应的子进程,waitpid的返回值为0;
 25     //当等待到了相应的子进程,waitpid的返回值为等待到的
 26     //进程的pid号,即大于0
 27     pid_t ret = 0;
 28     do
 29     {
 30       ret = waitpid(-1,NULL,WNOHANG);
 31       if(ret == 0)//输出child的情况
 32         {printf("child is running\n");}
 33     }
 34     while(ret == 0);                                                                                                                                                                                                                                                                                    
 35 
 36     sleep(20);
 37     printf("i am father, my pid is %d, exit...\n", getpid());
 38   }
 39 }

随后建立Makefile文件后运行,同时查看进程,发现存在两个进程
在这里插入图片描述
再看一次,发现只剩下一个进程了
在这里插入图片描述
更改一下代码,使父进程能获取子进程的pid随后输出
在这里插入图片描述
再对比一下
在这里插入图片描述

进程程序替换

作用

将正在运行的进程替换为另一个程序的代码

原理

①进程程序替换就是将进程的代码段、数据段替换成为新的程序的低吗和数据
②更新堆栈信息

应用场景

①守护进程(本质上是为了让服务不间断,但是当子进程由于代码崩溃退出后,虽然父进程会立即重新启动一个子进程,让子进程进行程序替换,我们一定要知道,导致子进程退出的原因是崩溃,而崩溃并没有被修复,是一种治标不治本的行为)
②bash(命令行解释器)

接口

exec函数簇,这是一堆的接口
execl
int execl(const char *path, const char *arg, ...);

path:并不是单纯的路径,而是 路径+可执行程序名称
可以总结为带路径的可执行程序

arg:给待要替换的可执行程序传递的参数

规定:
1、第一个参数是可执行程序本身
2、多个可执行参数,使用“,”进行间隔
3、参数的最后需要以NULL结尾

…:可变参数列表
使用which命令可以查看命令的位置
在这里插入图片描述
返回值:替换成功后,该函数是没有返回值的,也不知道返回值应返回给谁,只有替换失败之后才会有返回值,返回值 < 0
由上述返回值来看,第一个 printf 可以打印出来,但是第二个 printf 无法打印
在这里插入图片描述
建立Makefile文件后运行
在这里插入图片描述
已经显示出了我们想要的内容

进程替换成功后,进程的PID是否发生了变化?答案是没有,但是不好验证

execlp
int execlp(const char *file, const char *arg, ...);

参数:
file:只需要传递待替换的可执行程序

注意:	1、如果只传递可执行程序的名称,则该可执行程序一定要在环境变量PATH中可以找到
		2、也可以将待替换的可执行程序的路径写全
		3、函数名中带有‘p’说明当前的函数会自动搜索环境变量PATH

arg:同execl
在这里插入图片描述
在这里插入图片描述
替换也成功了

execle
int execle(const char *path, const char *arg, ..., char *const envo[]);

path:带有路径的可执行程序

arg:同 execl

envp[]:指针数组,本质是数组,数组的每一个元素都是一个char*,程序员需要自己组织环境变量,发到envp[]数组当中,最后一个元素需要放入NULL

函数名称当中带有 ‘e’ 则表示当前的exec函数需要自己组织环境变量,放到envp指针数组当中,以NULL结尾
函数名称当中带有 ‘l’ 则表示给待替换的可执行程序,传递的命令行参数为可变参数列表

在这里插入图片描述
需要引用一下
在这里插入图片描述
在这里插入图片描述
替换成功了

execv
int execv(const char *path, char *const argv[]);

path:带有路径的可执行程序

argv[]:指针数组,命令行参数的数组,存放的是传递给可执行程序的命令行参数

规则:	1、第一个参数是可执行程序本身
		2、参数的最后需要以NULL结尾

7.8更新

_exit()函数之所以不会刷新缓冲区,是因为_exit函数是内核代码,直接操作内核结束了进程,而此时不会通知上层C库,
所以,C库维护的缓冲区完全在无感知的情况下进程就终止了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值