【Linux】--进程控制

一、进程创建

前面我们学习进程概念的时候,已经知道了,创建进程,我们可以使用fork函数进行创建。其是在当前的进程下创建一个新的进程,新进程为子进程,原进程为父进程。

其给父进程返回子进程的pid,给子进程返回0,出错返回-1。

进程调用fork后,当控制转移到内核中的fork代码后,内核做下面几个工作:

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

2、将父进程部分数据结构内容拷贝到子进程中

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

对于fork的使用和现象我们前面已经讲解的很清楚了。

二、进程终止

进程终止的本质是释放系统资源,也就是释放掉进程申请的相关内核数据和对应的代码。

那么我们的进程中终止一共有几种情况呢?

一共有三种情况:

1、代码跑完,运行结果是正确的

2、代码跑完,运行结果有误

3、代码都没跑完,运行异常

首先我们看前面两种:

我们是如何判断我们的代码跑完是正确的还是错误的呢?

我们一开始学习语言的时候,在main函数也有返回值,其返回值为int类型,然后我们在main函数的末尾都会返回一个0,然后遇到一种输入错误呀啥的,都会返回1的。

其返回0,说明我们的运行是正确的。

要是返回非0的数那么说明运行错误。

然后就是代码都没走完,就发生异常,这个是如何判断的呢?

异常退出,说明我们的代码都没有运行到return,所以退出码是没有意思的,其实我们的进程管理中,还有一个是用来记录进程异常退出的码。一个是信号,信号就是异常的时候反馈的。

运行结果状态其用两个整型变量表示:

int exit_code。int exit------signal。

那么我们如何查看我们的进程退出码呢?

1、查看进程退出码

在我们的Shell中我们可以使用echo $?查看我们的上一个进程的退出码。

可以看到我们的程序运行错误的退出码是1。

2、error

这个函数我们前面也有使用过,这个是我们对于一些异常情况进行处理的函数,其通常会返回一个特殊值,然后将这个错误编码存放在一个全局变量errno中,如果我们要查看我们拿到的错误编号,那么我们要先安装moreutils,然后执行errno -l查看。

errno是一个整数,其每一个数据就代表一种特定的错误类型

其使用要包含头文件error.h

其返回值有两种情况:

返回0,说明没有问题

返回非0,就说明发生了错误

3、exit和_exit

exit是C++标准库中提供的一个函数,其是用来正常终止一个进程的。

其功能如下:

终止当前的进程,然后将状态码返回给父进程,其函数样子为:void exit(int status)。

注意:虽然我们的status是int类型,但是其只有8位bit位给其使用。'

exit和_exit的区别:

exit是C++标准库中提供的函数,_exit是系统调用,实际上exit的底层还是调用的_exit。

exit其执行退出进程的时候会将缓冲区进行I\O,刷新I\O缓冲区,然后我们的_exit的话是不会对缓冲区进行刷新的,直接就终止我们的进程。

下面是我们常见的信号状态和对应 的编号:

我们可以使用kill -l来查看:

三、进程等待

前面我们提到,进程状态,会有僵尸状态,这是因为我们的父进程先结束,然后子进程运行结束,父进程没有进行回收,获取子进程的退出信息。

所以父进程通过进程等待的方式,回收子进程的资源,获取子进程退出信息

我们在父进程中使用wait()函数或者waitpid()函数进行阻塞等待。

1、wait()

函数作用:

阻塞等待:如果子进程不退出,那么父进程会一直卡在wait这个函数中,直到接收到子进程的退出信息。

其是等待的任意一个子进程,不能指定等待某一个子进程。

其原型如下:

wait(int *status),这个指针可以穿空,就是只需要回收到子进程即可,不关心其运行状态。

其返回值类型为:pid_t,其实就是一个整型,如果等待成功,那么就返回等待到的子进程的pid,如果等待失败,那么就返回-1。

下面我们演示一下:

2、waitpid()

这个函数的功能和wait是一样的,不过其支持我们去指定子进程回收,其使用一次也是回收一个子进程。

函数原型如下:

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

第一个参数pid

传入的是-1的话,那么父进程就会等待任意一个子进程。

传入的为>0的值的话,那么其就会等待pid为这个数的子进程。

第二个参数,我们暂时将其设置为NULL

第三个参数,我们暂时将其设置为0。

下面我们演示其效果:

下面我们来具体看看其参数:

3、status

这个参数是一个int*类型的参数,其是用来接收被回收的子进程的退出状态的。

如果我们需要知道的话,那么可以创建一个int*类型的数据,获取其退出的状态。

然后要注意的是,其返回的话,是包含了退出信号和退出码的。

我们的退出信号和退出码就是其前16bit位。

那么我们要是想看子进程的退出码,那么我们只需要将status的数据右移8个bit位。

然后终止信号就在前八个bit位置,然后如果其是被信号所杀的话,那么会有一个bit位被用来做为core dump标志。所以其是7个bit位置。

然后我们的Linux下定义了两个宏来获取我们进程退出的状态的:

4、阻塞等待和非阻塞等待

阻塞等待就是我们的父进程等待子进程回收,在这个等待的期间,我们的父进程啥都不做。

非阻塞等待就是我们的父进程在等待回收子进程的时候,我们的父进程还可以去干别的事情。

选择阻塞等待还是非阻塞等待,就是我们的waitpid函数的第三个参数。

如果我们传入的是0,那么就是阻塞等待,如果传入的是WNOHANG,那么就使用的是非阻塞等待。

如下:

运行结果如下:

可以看到在等待子进程的回收中,我们的父进程也还在运行。

四、进程替换

我们前面创建了一个子进程后,父子各自执行父进程的一部分代码,如果我们的子进程想要执行一个全新的进程呢?

那么就需要用到我们的进程替换了,进程替换是不会创建新的进程的。

可以理解其是直接在这个进程内,将其后面的代码覆盖了。

我们要使用到exec系列的函数:

其替换原理如上。

这是我们的进程替换的函数,那么其使用要包含unistd头文件。

那么我们下面来对其一一讲解:

1、execl()

函数原型:

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

第一个参数,要传入的是我们要替换的文件的绝对路径或者相对路径

第二个参数,传入我们的执行方式,命令行咋使用的,那么我们这边就怎么调用

最后一个参数,一定要传一个NULL

返回值:调用成功的话,就没有返回值,如果失败返回-1;

2、execlp()

函数原型:

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

第一个参数:文件的绝对路径或者相对路径,然后如果这个路径在我们的PATH环境变量中的话, 那么我们就可以直接写文件名。

第二个参数:和execl是一样的,命令行咋写的,这里就咋写。

最后一个参数就必须是NULL。

3、execv()

函数原型:

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

第一个参数:文件的绝对路径或者相对路径

第二个参数:参数数组,argv0一般是我们要执行的文件名字或者命令,注意的是其最后一个元 素必须是NULL。

4、execvp()

函数原型:

int execvp(const char*file,char *const argv\[\]);

第一个参数:文件的绝对路径或者相对路径,如果在环境变量PATH中有的话,那么可以只传文件 名字

第二个参数:参数数组,和上面的execv一样。

5、execle()

函数原型

int execle(const char*pathname,chonst char * arg0,.....,NULL,char *const envp\[\]);

第一个参数:必须是可执行文件的路径

第二个参数:新程序的命令行参数列表,注意最后一个参数一定要为NULL

第三个参数:新的环境变量,替换当前进程的环境变量。

相关推荐
A小辣椒14 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒18 小时前
TShark:基础知识
linux
AlfredZhao20 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪2 天前
linux 拷贝文件或目录到指定的位置
linux
大树882 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质2 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式