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

文章目录

  • 一.进程创建
    • 1.fork()
    • 2.fork的工作内容
    • 3.用户控件&内核空间
    • 4.写时拷贝
    • 5. fork的用法
  • 二.进程终止
    • 1.进程终止的场景
    • 2.exit和_Exit的区别
    • 3.自定义清理函数
    • 4.缓冲区
  • 三.进程等待
    • 1.必要性
    • 2.wait函数
      • 2.1 函数原型
      • 2.2 参数的含义
    • 3.waitpid函数
      • 3.1 函数原型
      • 3.2 参数的含义
  • 四.进程替换
    • 1.原理
    • 2.exec函数簇
      • 2.1 execl
      • 2.2 execlp
      • 2.3 execle
      • 2.4 execv
      • 2.5 execvp
      • 2.6 execve
    • 3. 函数之间的区别

一.进程创建

1.fork()

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

2.fork的工作内容

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

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

3.用户控件&内核空间

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

4.写时拷贝

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

5. fork的用法

守护进程:

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

二.进程终止

1.进程终止的场景

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

正常终止:
从main函数的return返回 只有main函数的return才能结束程序
调用exit函数(库函数)#include
作用:谁调用终止谁
Linux进程控制(代码+图解)_第2张图片
其中status为程序退出的退出码( 可以用echo $?查看进程退出码)
Linux进程控制(代码+图解)_第3张图片

调用_Exit函数(系统调用函数)#include
作用:谁调用终止谁
Linux进程控制(代码+图解)_第4张图片

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

2.exit和_Exit的区别

Linux进程控制(代码+图解)_第5张图片
exit函数比_exit函数多执行两个步骤
1.执行用户自定义的清理函数
2.冲刷缓冲区,关闭流等
3.调用进程(_Exit);(exit函数的实现也调用了_Exit函数)

3.自定义清理函数

回调函数:在代码中注册一个函数,在特定的时候进行执行
Linux进程控制(代码+图解)_第6张图片

#include
#include
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不会刷新缓冲区

Linux进程控制(代码+图解)_第7张图片

三.进程等待

1.必要性

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

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

2.wait函数

2.1 函数原型

函数原型:
#include
#include
pid_t wait(int*status);//等待任一子进程

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

2.2 参数的含义

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

只用到低位的两个字节
子进程正常退出,如果传递status,会获取到子进程的退出状态
子进程异常退出,如果传递status,会获取到coredump标志位+退出信号
Linux进程控制(代码+图解)_第8张图片
正常退出的时候,退出码会被设置,coredump标志位为0,不会有退出信号
Linux进程控制(代码+图解)_第9张图片

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

Linux进程控制(代码+图解)_第10张图片
如何判断子进程是正常退出还是异常退出

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

Linux进程控制(代码+图解)_第11张图片
大于0并且退出信号被设置(有值):异常退出
先设置下核心转储文件的大小为unlimited,也就是不限制
在这里插入图片描述
Linux进程控制(代码+图解)_第12张图片

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。

Linux进程控制(代码+图解)_第13张图片
设置为非阻塞状态后,子进程并没有被父进程等待到
解决方法:waitpid函数调用要搭配循环使用
Linux进程控制(代码+图解)_第14张图片

四.进程替换

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

1.原理

替换进程的代码段和数据段,更新堆栈
Linux进程控制(代码+图解)_第15张图片

2.exec函数簇

#include `
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) : 表示自己维护环境变量
Linux进程控制(代码+图解)_第16张图片

2.1 execl

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

2.2 execlp

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

arg:传递给可执行程序的命令行参数;第一个参数为可执行程序本身,如果需要传递多个参数,则用“,”进行间隔,末尾以NULL结尾。
Linux进程控制(代码+图解)_第18张图片

2.3 execle

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

2.4 execv

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

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是系统调用函数,其他都是库函数
Linux进程控制(代码+图解)_第21张图片

你可能感兴趣的:(linux,linux,运维,服务器)