当前位置: 首页 > news >正文

Linux进程管理:创建,终止,等待

目录

进程创建

fork函数初识

写时拷贝

fork常规用法

fork调用失败的原因

进程终止

进程退出场景

exit 

_exit

我们所谈的缓冲区在哪里?

进程等待

为什么要等待

是什么?

​编辑

怎么做到的


进程创建

fork函数初识

在Linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void);
返回值:⾃进程中返回0,⽗进程返回⼦进程id,出错返回-1

程调用fork,当控制转移到内核中的fork代码后,内核做

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

  • 将父进程部分数据结构内容拷贝至子进程

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

  • fork返回,开始调度器调度

当一个进程调用 fork 之后,就有两个二进制代码相同的进程,而且它们都运行到相同的地方,但每个进程都将可以开始它们自己的旅程。

这里看到了三行输出,一行before,两行after。进程1233707先打印before消息,然后它又打印after。另一个after消息由1233708印的。注意到进程1233708没有打印before,为什么呢?如下图所示 

所以,fork 之前父进程独立执行,fork 之后,父子两个执行流分别执行。

注意,fork 之后,谁先执行完全由调度器决定。

fork函数返回值

子进程返回 0,父进程返回的是子进程的 pid。

写时拷贝

通常,父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图

因为有写时拷贝技术的存在,所以父子进程得以彻底分离!完成了进程独立性的技术保证! 写时拷贝,是一种延时申请技术,可以提高整机内存的使用率。 

fork常规用法

通常我们创建子进程,是为了让子进程为我们干事的,不然我们创建子进程干嘛,所以一般我们创建子进程有两种,第一种我们创建子进程,想让子进程帮我执行我自己写的代码的一部分,通过if else分流,父子进程各自执行代码的一部分

第二种,这种场景是我们创建一个子进程,但我期望我的子进程帮我执行全新的程序,比如说我们在命令行中执行我们对应的命令,我们已经验证过了,我们所输入的命令本质上就是一个进程,而一旦它变成一个进程后,它的父进程就是bash,相当于我们输入命令时,是bash创建了子进程,但是这个子进程不是为了执行bash的,而是我们新启动的命令或者程序的。

fork调用失败的原因

系统中有太多的进程 ,内存不足

实际用户的进程数超过了限制


进程终止

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

进程退出场景

• 代码运行完毕,结果正确

• 代码运行完毕,结果不正确

• 代码异常终止 

子进程也是进程,子进程是由父进程创建的,子进程执行后退出,退出的情况应该要返回给父进程

 

main函数的返回值,一般都是返回给父进程的,如何去查看程序的退出数字?

用 echo $? 命令

 打印最近一个程序(进程)的退出码,进程退出码

 

为什么我刚刚echo $? 打印的还是1,再次执行该命令,打印的却是0呢?

原因很简单,echo $?查的是最近一次的程序(进程)的退出码,第二次查的是echo命令的退出码

我们得到了退出码,应该怎样获取其退出码的含义呢,系统已经给我们了一份退出码和退出码的描述,就叫做strerror

可是我们又不知道错误码有多少,那么我们就用代码来看一下 

 我们看到系统一共提供了133个错误码和错误码信息的描述

而在C语言中,系统提供了errno对象,它可以直接根据错误设置错误码,当函数运行出错,会将错误码赋值给errno变量

所以我们以后写代码,如果想把错误信息反馈出来,我们就可以直接在返回值return errno。

进程执行结果对还是不对,由进程的退出码决定

那么如果程序异常呢?

main函数结束,表示进程结束,其他函数,只表示自己的函数调用完成,返回


exit 

表示引起进程终止

其参数status,表示状态,其实就是进程的退出码

可以看到,在代码中任何地方调用exit,都会导致进程结束,函数后续代码不执行,不返回,将exit的状态值返回给父进程bash,子进程的退出码!!

进程退出的具体做法

1.return

2.exit

_exit

除了exit,还有一个_exit,终止调用我的进程,即谁调用我谁就退出

我们做了解即可

我们下面来区分exit和_exit的区别

exit,是C语言提供的

_exit,是系统提供的

我们通过对缓冲区的测试可以看到二者的差异

进程如果exit退出的时候,exit(),会进行缓冲区刷新

进程如果_exit退出的时候,_exit(),不会进行缓冲区刷新 

exit是库函数,_exit是系统调用,库函数和系统调用的关系是上下层关系,库函数会调用相关的系统调用来完成某些任务

终止进程,你用C语言上的exit就能把进程终止了吗?不能

这个世界上的能真正杀掉进程的,只有操作系统,因为操作系统是进程的管理者

换句话说,我们看起来语言上的调用的是exit,但实际上语言上的exit也会调用系统中的_exit,因为库函数没有终止进程的能力,它只有调用系统给它的接口,才能完成终止进程


我们所谈的缓冲区在哪里?

或者一定不在哪里?

一定不是操作系统内部的缓冲区,如果是内部的缓冲区,exit和_exit都应该刷新,但是_exit并没有刷新,因此缓冲区肯定不是操作系统内部的缓冲区

它是库缓冲区,C语言提供的


进程等待

之前讲过,子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏,另外,进程一旦变成僵⼫状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息。

为什么要等待

为了解决内存泄漏问题

为了获取子进程的退出信息


是什么?

父进程调用接口wait或者waitpid等待子进程,就叫进程等待

 我们先写一个僵尸进程的代码

然后我们用wait函数来解决僵尸进程的问题

waitpid 

wait只剩waitpid的婴儿版,我们再来学习waitpid

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

我们来看一下函数接口,waitpid和wait的返回值一样,都是返回子进程的pid,wstatus是输出型参数,option一会再来谈,是用来作阻塞控制的

pid参数,>0表示只等待pid对应的子进程,

=-1,表示等待任意子进程,就是wait的作用

 wait和waitpid结果是一样的

那wait和waitpid什么时候会等待失败?

wait失败就是父进程没有子进程

而waitpid失败就是pid传错了

如图所示,就是失败的情况,我们把pid+1

statue

statue是输出型参数,当父进程交给子进程一个任务,子进程完成后,父进程怎么评估它的完成情况呢?

通过进程退出码,即main函数的返回值,来判定子进程的返回结果是否正确

那么我们用statue对其进行回收,我们用代码来看一下

我们发现,最后打印出来的statue是256,但是我们故意设的子进程exit的返回值是1,为什么不等于1呢?

所以statue一定不是只有退出码,它不只是我们所想的拿到退出码

因为除了代码正常跑完,返回对应值的情况,还有一种异常情况,也需要statue来考虑

 当我们知道statue的组成后,就可以手动进行更改

在Linux系统中会存在许多信号,查看Linux所有的信号,命令叫做kill -l

 这么多信号,我们发现左侧是信号的值,右侧是信号的名称,其实这些都是宏

可是我们发现这里并没有0号信号,一旦一个进程异常终止,那么这个父进程的statue,低七比特位会保存异常时对应的信号编号

如果没有异常

1.低七个比特位,是0

2.一旦低七比特网不为0,那它就是异常退出的,此时退出码无意义

也就是说,如果未来,低七比特位为0,我们才看次八位比特位 

当我们在程序中添加野指针,即让程序异常,看一下运行结果

sigsegv表示程序段错误

在Linux利用statue的15个比特位就能够准确把一个子进程的相关的三种运行情况,都能确定清楚了。


怎么做到的

 1.子进程退出信息存在哪里?

我们之前说过,当一个子进程退出,进入僵尸状态,会把代码数据,页表删掉,但是PCB不能释放,那么子进程的退出信息,只能放在僵尸进程的task_struct

我们来看一下task_struct中的子进程退出信息 


虽然我们可以通过位运算来转换获取退出码信息,但是其实系统给我们直接定义了宏,供我们直接使用

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是
否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的
退出码)


option

pid返回值

>0:等待结束

=0:调用结束,但是子进程没有退出

<0:失败

我们主要了解一下WNOHANG,如果我们不带该选项,当子进程不退,我们会阻塞在这里,但是带上WNOHANG选项,它的作用是如果子进程没有退出,我们就立即返回,即非阻塞调用

举个例子,当我叫一个人出去玩,到他家楼下了,给它打电话,但是他才刚起床,还要洗漱穿衣服,于是我就把电话挂了,但是我是个急性子,就一分打一次电话,问他好了没,这个过程,我就是用户,这个人就是操作系统,打电话就是一次调用,这个过程叫做非阻塞轮询(NON BLOCK)

所有的轮询都是通过循环完成的

非阻塞调用,在询问的间歇,用户依旧可以干自己的事情,可以让等待方,和操作系统并发的做事情,效率更高,单位时间做更多的事情。

第二次我来找这个人,我打了电话,但是不挂断,让他一直接着电话准备,什么时候好了,直接给我说一声,我再挂电话,于是这个人不挂电话,直到把所有的准备工作都做完,这个过程,一直打着电话不挂断的过程,叫做阻塞调用

如上是我们非阻塞轮询的代码,我们可以看到运行结果中,父进程会不断的去询问子进程是否结束,直到子进程结束,成功等待到了子进程的信息。 

如何让父进程做其他的事情?

我们写一份代码,重点让我们理解非阻塞调用,可以解放父进程,可以让父进程在轮询时,干自己的事情。

#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<errno.h>
#include <stdio.h>
#include<string.h>
#include<stdlib.h>
//函数指针
typedef void (*func_t)();
#define NUM 5
func_t handlers[NUM];
void Download()
{printf("下载任务\n");
}void Fflush()
{printf("刷新任务\n");
}void Log()
{printf("日志任务\n");
}
void registerHandler(func_t h[],func_t f)
{int i = 0;for(;i<NUM;i++){if(h[i] == NULL) break;}if(i == NUM)return;h[i] = f;h[i+1] = NULL;
}
int main()
{registerHandler(handlers,Download);registerHandler(handlers,Fflush);registerHandler(handlers,Log);pid_t id = fork();if(id == 0){//子进程while(1){printf("我是一个子进程,pid :%d,ppid:%d\n",getpid(),getppid());sleep(1);}exit(10);}//父进程while(1){int statue = 0;pid_t rid = waitpid(id,&statue,WNOHANG);if(rid>0){printf("wait success,rid:%d,exit code:%d,exit signal:%d\n",rid,(statue>>8)&0xFF,statue&0x7F);break;}else if(rid == 0){//函数指针进行回调处理int i = 0;for(;handlers[i];i++){handlers[i]();}printf(" 本轮调用结束,子进程没有退出\n");sleep(1);}else{printf("等待失败\n");break;}}return 0;}

我们就可以让父进程在阻塞时,去干其他的事情 

相关文章:

  • Linux611 libvirtb ;FTP vsftpd.conf部分配置文件
  • C#简单线程启动的几种方法总结
  • npm包 本地测试流程
  • 为 Nginx 配置 HTTPS(以 n8n 为例)完整教程【CentOS 7】
  • 时序数据库IoTDB数据模型建模实例详解
  • Java使用Selenium反爬虫优化方案
  • Nuxt3 中使用 pnpm 安装的 NuxtImg 使用会提示找不到图片
  • Linux(Centos 7.6)命令详解:whoami
  • 时序数据库Influxdb3 core安装
  • 【指针】(适合考研、专升本)
  • 基础篇:5. HTTP/2 协议深度解析
  • 递归,回溯,DFS,Floodfill,记忆化搜索
  • 【编译工具】(自动化)AI 赋能的自动化测试工具:如何让测试效率提升 500% 并实现智能质检?
  • Flutter布局系统全面解析:从基础组件到复杂界面构建
  • 一台电脑最多能接多少个硬盘
  • livetalking实时数字人多并发
  • 计算机体系结构中的MPU是什么?
  • LangGraph基础知识(MemorySaver/SqliteSaver )(三)
  • web程序设计期末复习-填空题
  • uni-app 自定义路由封装模块详解(附源码逐行解读)
  • 外包网站开发价格/百度指数查询工具
  • 游戏开发公司哪家好/如何做一个网站的seo
  • 招投标网站销售怎么做/seo排名优化公司价格
  • 提升网站权重/怎样做自己的网站
  • 网站301做下/正规代运营公司排名
  • 优化方案物理必修三电子版/郑州厉害的seo顾问