Linux进程控制(代码+图解)

一.进程创建

1.fork()

让正在运行的程序创建出来一个子进程,它从已存在的进程中创建一个新进程,新进程为子进程,而原进程为父进程。
返回值:pid_t
创建失败:返回-1;
创建成功:
大于0:返回给父进程
等于0:返回给子进程

2.fork的工作内容

创建子进程,子进程拷贝父进程的PCB

1):分配新的内存块和内核数据结构(task_struct)给子进程
2):将父进程部分数据结构内容拷贝至子进程
3):添加子进程到系统进程列表当中,添加到双向链表中
4):fork返回,开始调度器(操作系统开始调度)调度

3.用户控件&内核空间

内核空间:Linux操作系统和驱动程序运行在内核空间。即系统调用函数都是在内核空间运行的,因为是操作系统提供的函数。
用户空间:应用程序都是运行在用户空间的。即自己写的代码都是运行在用户空间的。
但是,当程序员的代码调用了系统调用函数,则会切换到内核空间执行。执行完毕之后,再返回到用户空间继续执行用户代码。

4.写时拷贝

在父进程创建出子进程后,子进程的PCB是拷贝父进程的,页表也是拷贝父进程的,在刚开始,同一变量的虚拟地址和物理地址的映射关系是一样的,操作系统并没有给子进程的变量在物理内存中分配空间,子进程的变量还是原来父进程中物理地址中的内容。
当数据进行改变时:以写时拷贝的方式进行拷贝一份,此时父子进程通过各自的页表,指向不同的物理地址。
当数据不发生改变:父子进程共享同一个数据。
在这里插入图片描述

5. fork的用法

守护进程:

父进程创建子进程,让子进程执行真正的业务(进程程序替换),父进程负责守护子进程。
当子进程在执行业务时出现问题,父进程负责重新启动子进程,让子进程继续提供服务。

二.进程终止

1.进程终止的场景

进程终止可分为三种
代码运行结束,结果正确;
代码运行结束,结果不正确;
代码异常终止;

正常终止:
从main函数的return返回 只有main函数的return才能结束程序
调用exit函数(库函数)#include<stdlib.h>
作用:谁调用终止谁
在这里插入图片描述
其中status为程序退出的退出码( 可以用echo $?查看进程退出码)
在这里插入图片描述

调用_Exit函数(系统调用函数)#include<stdlib.h>
作用:谁调用终止谁
在这里插入图片描述

异常终止: 异常终止即程序崩溃
可能导致其的原因有:ctrl+c,内存访问越界,访问空指针

2.exit和_Exit的区别

在这里插入图片描述
exit函数比_exit函数多执行两个步骤
1.执行用户自定义的清理函数
2.冲刷缓冲区,关闭流等
3.调用进程(_Exit);(exit函数的实现也调用了_Exit函数)

3.自定义清理函数

回调函数:在代码中注册一个函数,在特定的时候进行执行
在这里插入图片描述

#include<stdlib.h>
#include<stdio.h>
void atexit_callback(void)
{
    printf("i am atexit_callback\n");
}

int main()
{
    //atexit函数是注册函数,并没有在注册时候使用,所有此时不会调用
    atexit(atexit_callback);
    printf("end\n");
    exit(0);
    return 0;
}

在这里插入图片描述
可以看到,函数的调用是在exit之后执行的
而如果将exit改为_Exit,则不会调用函数
在这里插入图片描述

4.缓冲区

缓冲区:缓冲区是C标准库定义的,不属于操作系统内核。建立缓冲区可以减少IO次数,当触发冲刷缓冲区的条件后,缓冲区的内容才会进行IO操作(比如打印到屏幕上,写入文件内)

刷新缓冲区的方法

1.exit
2.main函数的return
3.fflush
4.\n
注意:_exit不会刷新缓冲区

在这里插入图片描述

三.进程等待

1.必要性

僵尸进程:子进程先于父进程退出,父进程没有回收子进程的退出状态,则子进程会变成僵尸进程

为了防止僵尸进程的发生,父进程可以进行进程等待,回收子进程的退出状态信息,防止子进程变成僵尸进程
(僵尸进程的最合理解决方案:进程等待)

2.wait函数

2.1 函数原型

函数原型:
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);//等待任一子进程

作用:等待子进程退出(回收退出的子进程的退出状态)
返回值:
成功:返回被等待进程的pid;
失败:返回-1;

2.2 参数的含义

参数:输出型参数,获取子进程退出状态,不关心则设置为NULL;
int * status:
wait函数的调用者,想要获取子进程的退出状态信息,但wait的返回值以及有含义了,所以wait获取到的子进程的退出状态信息是通过参数进行传递的。

只用到低位的两个字节
子进程正常退出,如果传递status,会获取到子进程的退出状态
子进程异常退出,如果传递status,会获取到coredump标志位+退出信号
在这里插入图片描述
正常退出的时候,退出码会被设置,coredump标志位为0,不会有退出信号
在这里插入图片描述

异常退出的时候,退出码不会被设置,coredump标志位为1,退出信号被设置

在这里插入图片描述
如何判断子进程是正常退出还是异常退出

wait返回值:
大于0并且退出信号没有被设置(==0):正常退出

在这里插入图片描述
大于0并且退出信号被设置(有值):异常退出
先设置下核心转储文件的大小为unlimited,也就是不限制
在这里插入图片描述
在这里插入图片描述

3.waitpid函数

3.1 函数原型

pid_ t waitpid(pid_t pid, int *status, int options);

返回值:
1.正常返回时waitpid返回收集到的子进程的进程ID ;
2.若设置WNOHANG(非阻塞),而调用中waitpid发现没有已退出的子进程可收集,则返回0;
3.若调用出错,返回-1,这时errno会被设置成相应的值以指示错误所在;

3.2 参数的含义

pid_t pid:
-1:等待任一子进程与wait等效
大于0:等待其进程与pid相等的子进程。
int * status:
子进程退出信息(同wait)
int options:
WNOHANG:设置waitpid为非阻塞状态
若pid指定的子进程没有结束,则waitpid的函数返回0,不予以等待。若正常结束,返回该子进程pid。

在这里插入图片描述
设置为非阻塞状态后,子进程并没有被父进程等待到
解决方法:waitpid函数调用要搭配循环使用
在这里插入图片描述

四.进程替换

为什么要进行进程程序替换:
父进程创建出来的子进程和父进程拥有相同的代码段,所有,子进程看到的代码和父进程是一样的
当我们想要子进程执行不同的程序时,就需要子进程调用进程程序替换的接口,从而让子进程执行不同的代码

1.原理

替换进程的代码段和数据段,更新堆栈
在这里插入图片描述

2.exec函数簇

#include <unistd.h>`
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *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[]);

这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
如果调用出错则返回-1
所以exec函数只有出错的返回值而没有成功的返回值。

l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量
在这里插入图片描述

2.1 execl

int execl(const char *path, const char *arg, …);
参数:
pash:带路径的可执行程序
arg:传递给可执行程序的命令行参数;第一个参数为可执行程序本身,如果需要传递多个参数,则用“,”进行间隔,末尾以NULL结尾。
返回值:调用成功则加载新的程序从启动代码开始执行,不再返回,调用失败返回-1;
在这里插入图片描述
没有打印出end,即程序替换成功了

2.2 execlp

int execlp(const char *file, const char *arg, …);
file:可执行程序,可以不用带路径,也可以带路径。
execlp会自动搜索PATH这个环境变量去看能不能找到;找到:正常替换;没找到:报错返回,替换失败返回-1;

arg:传递给可执行程序的命令行参数;第一个参数为可执行程序本身,如果需要传递多个参数,则用“,”进行间隔,末尾以NULL结尾。
在这里插入图片描述

2.3 execle

int execle(const char *path, const char *arg, …,char *const envp[]);
path:带路径的可执行程序(需要路径)
arg:传递给可执行程序的命令行参数;第一个参数为可执行程序本身,如果需要传递多个参数,则用“,”进行间隔,末尾以NULL结尾。
envp :在调用的时候,需要自己组织环境变量传递给函数
返回值:成功则替换;失败返回-1;
在这里插入图片描述

2.4 execv

int execv(const char *path, char *const argv[]);
path:带路径的可执行程序
argv:传递给可执行程序的命令行参数,以指针数组的方式进行传递
第一个参数为可执行程序本身,多个参数都放在数组当中,末尾以NULL结尾
返回值:成功则替换;失败返回-1;
在这里插入图片描述

2.5 execvp

int execvp(const char*file,char *const argv[])
file:可执行程序,可以不用带路径,也可以带路径。
argv:传递给可执行程序的命令行参数,以指针数组的方式进行传递
第一个参数为可执行程序本身,多个参数都放在数组当中,末尾以NULL结尾

返回值:成功则替换;失败返回-1;

2.6 execve

int execve(const char *path, char *const argv[],,char *const envp[])😉;
path:带路径的可执行程序
argv:传递给可执行程序的命令行参数,以指针数组的方式进行传递
第一个参数为可执行程序本身,多个参数都放在数组当中,末尾以NULL结尾
返回值:成功则替换;失败返回-1;
envp :在调用的时候,需要自己组织环境变量传递给函数
返回值:成功则替换;失败返回-1;

3. 函数之间的区别

execve是系统调用函数,其他都是库函数
在这里插入图片描述

  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

星星21211

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值