进程的创建、终止

news2024/9/28 15:18:45

目录

  • 前言
  • 1. 进程创建
  • 2. 进程终止
  • 3. exit && _exit 的异同
    • 3.1 相同点
    • 3.2 不同点

前言

紧接着进程地址空间之后,我们这篇文章开始谈论进程控制相关的内容,其中包括进程是如何创建的,进程终止的几种情况,以及进程异常终止的本质,还有 C 语言库中的 strerror 以及 errno 全局变量的相关内容,最后对比系统调用 _exit 与 C 库的 exit 的异同点。


1. 进程创建

在之前文章 进程概念(三)----- fork 初识,我们初始了 fork(),大致了解了一个进程是怎么被创建出来的,不管是我们的程序运行起来后,操作系统为我们自动创建的,还是代码层面我们手动创建的进程,都是通过 fork() 实现的,也回答了与 fork 相关的几个内容,为什么要有两个返回值(为了让父子进程分流工作);如何做到返回两次的(在return之前,子进程就已经被创建完成,因此父子进程都执行了一遍return语句)以及 一个变量如何做到两个不同的内容的(写诗拷贝实现的)。

有了之前的 fork 初始,以及进程地址空间,页表等铺垫,我们现在就足以理解一个进程是如何被创建的了。

创建进程,最终都离不开 fork() 函数,当一个进程调用 fork 之后,操作系统就会做以下几件事:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程(包括进程地址空间,页表都会拷贝一份给子进程)
  • 添加子进程到系统进程列表当中(本质就是讲子进程的 PCB 链入到 cpu 的运行队列中)
  • fork返回,开始调度器调度

所以 fork 之后,父子进程就开始共享代码,但数据却不一定是共享的,也就是可能发生写诗拷贝。
在这里插入图片描述
关于写诗拷贝,再谈一个细节。在创建子进程时,当子进程开始要拷贝父进程的进程地址空间,页表的时候,父进程会把页表中的所有读写权限的字段暂时设置为只读(原本只读的字段保持不变),然后再让子进程开始继承其进程地址空间和页表。而后续当其中一个进程开始对这些只读字段的地址进行写入时(操作系统能够识别到原本属于读写权限的),操作系统不做异常处理,它会重新开辟一块内存空间,将要写入的数据拷贝一份到新地址中,然后修改这个进程的页表中相应字段的物理地址,再把这个字段恢复为读写权限,这就完成了所谓的 写时拷贝

而既然有写时拷贝,那就注定在技术层面上可以不要写时拷贝,在创建子进程的时候,直接一次性的将父进程的全部数据拷贝一份给子进程不就完了吗,还省事!

其实这样做反而 “不省事” ! ------>

  • 可能导致大量重复的数据在内存中,造成资源浪费
  • 拷贝量可能很大,导致整机性能下降(虽然之后写时拷贝的时候也要拷贝,但拷贝量注定不会很大,效率是不会降低的)

而在而来 fork 可能会出现调用失败的原因:1. 系统中有太多的进程; 2. 实际用户的进程数超过了限制

代码层面上想要创建多个子进程,可以借助循环创建。demo用例:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define N 5

void runChild()
{
    int cnt = 10;
    while(cnt)
    {
     	printf("I am child: %d, ppid:%d\n", getpid(), getppid());
        sleep(1);
        cnt--;
    }
}

int main()
{
    int i = 0;
    for(; i < N; i++)
    {
     	pid_t id = fork();
        if(id == 0)
        {
            runChild();
            exit(0);
        } 
    }
    sleep(100);
    return 0;
}

关于父子进程到底谁先运行这件事,是具体某一款操作系统的调度器怎么设计决定的,并没有唯一的标准,而一定要说哪个进程先运行,因为创建出来的子进程的优先级默认都是一样的,只能是谁先被调度器调度,放在 cpu 的运行队列中,谁就先被运行。


2. 进程终止

一个进程的终止无非就是以下这三种情况:

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常中止

而我们应该关注的是后面两种情况,代码运行完毕且结果正确我们一般不关心(就好比考试完毕且成绩达到预期,我们也不会去关心),但是第二种情况,代码运行完毕且结果不正确,我们肯定要关心吧,总得知道为什么不正确吧,包括代码异常终止。

int main()
{
    printf("this is a test!\n");
    return 0; 	// return 0 的 0 就是进程的退出码,表征进程的运行结果是否正确. 0->success
}

在这里插入图片描述

echo $? 其中的 $? 代表保存最近一次进程退出时的退出码,而上面我们执行了 test 这个可执行程序,该进程执行完毕后返回了一个 0,代表这这个进程执行完毕,并且结果正确。而如果我们代码中不是 retrun 0,而是 return 1/2/3,那么echo $? 查出来的结果也不一样的。换言之,可以用 return 不同的返回值数字,表征不同的出错原因,这些数字就称为退出码。 而 main 函数返回值的本质就是:进程运行完成时是否是正确的结果,如果不是,可以用不同的退出码表示的出错原因。

那进程中,谁会关心我这个进程的运行结果呢?? ----- 该进程的父进程

在这里插入图片描述

当我们 main 函数中 return 2 ,那我们 echo $? 查出来的退出码就是 2,但是之后我继续执行 echo $? 就不再是 2 了,而是 0,这是因为第一条 echo 命令执行完是正确的,没有发生错误,所以接下来的 echo $? 打印的就是最近一次进程退出时的退出码(echo执行起来时,本身就是一个进程)。

但是,退出码都是些 0 1 2 3 4 的纯数字,理解起来也太抽象了吧,所以退出码只是给计算机自己看的,在用户层,就需要将这些特定含义的数字转换成对应错误原因描述的字符串信息,方便我们观看使用。

而在 C 预言中,就有一个查看错误码的库函数 strerror,并且我们可以把这个函数的信息打印出来看一下。

在这里插入图片描述
在这里插入图片描述

当我们 ls 查看目录中不存在的文件名,它的错误信息 与 上面我们打印出来的 strerror 函数中的内容是匹配的上的!No such file or directory 这条错误信息的退出码正好是 2 。换言之,系统提供的错误码和错误码描述是有对应关系的。

但为什么父进程会关心子进程的执行结果呢??其实父进程它只是一个跑腿的,可以理解为是因为它有义务要收集子进程的执行结果,并且向上反馈,而不是它真的想关心子进程的执行结果。换言之,真正关心一个进程的执行结果的人一定是用户。就好比我们上面 ls 查看当前目录不存在的文件,执行失败了,用户才会根据执行失败的错误信息,调整自己执行程序的方式!

那为什么要创建子进程?因为有需求,不就是因为用户要创建子进程出来干活吗。所以将来这个子进程执行某个任务的结果,我作为用户,我如何得知!? ------ 通过父进程将进程的退出信息转交给用户,让用户根据错误信息做下一阶段的执行决策

接下来,我们可以来看看退出码的应用场景。在 C 库中提供了 errno 全局变量,在 C 语言中,我们可能会调用一些库函数,比如 malloc,fopen,这些库函数在调用时,内部都是有可能会执行错误的,而这个 errno 就是记录最后一次执行的错误码。要记录错误码的就是因为只要执行错误,那用户就有需求要了解错误信息是什么。

int main()
{
    int ret = 0;
    char* p = (char*)malloc(1000*1000*1000*4);
    if(p == NULL)
    {
     	printf("malloc error, %d: %s\n", errno, strerror(errno));
        ret = errno;
    }
    else
    {
     	printf("malloc success, %d: %s\n", errno, strerror(errno));
    }
    return ret;
}

在这里插入图片描述

当我们 malloc 申请很大的内存时,极大困难就是 12 的错误码:Cannot allocate memory。而这个错误码是 C 语言提供的 errno 这样的全局变量记录下来的,结合 strerror 根据错误码查询具体错误信息得来的!

截止现在,我们还没有讲进程终止的第三种情况:代码异常中止, 现在的问题是,代码异常中止了,main 函数的退出码还有意义吗??

前面两种进程终止的情况都好说,因为起码代码执行完了,我们可以通过退出码来判定是否执行正确。

  • 而代码异常,是不是可以理解为代码没跑完,进程就已经退出了,根本就没有执行 main 函数的 return 语句。
  • 但是会不会也有可能,执行完 main 函数的 return 语句之后,才发生的异常中止的呢?好像也有可能?毕竟 main 函数也是函数,它也会被调用。return 之后,在语言层面上认为这个程序结束了,但在系统层面上,这个进程不一定退出了!

所以现在的关键是,作为用户的我,我该怎么知道,这个程序到底有没有执行最后的 return 语句呢!?就是因为我们对代码异常在哪个位置的不确定性,假设今天真的是 return 之后才异常的,退出码给返回到了父进程,进而转交给用户,作为用户的你,你真的敢用吗?换言之,你敢信吗??凭什么你就敢确定这个程序真的执行了 return 语句呢?

作为用户的我们,不能百分百确定确定在哪一行代码发生的异常,所以即便真的有退出码,这个退出码照样没有意义!(因为我们不敢相信。就好比考试的时候,你作弊了,但是你跟老师解释说是考试即将结束作弊的,你只抄了最后一道题,老师会信吗??换言之,只要你作弊了,你何时作弊,对于老师来说已经不重要了,没有意义了!你的话也不再可信,他只知道你作弊了!再者,难道你只抄了最后一题,最后100分的试卷考了95分,去掉最后的5分,你也还有90分,是年级百强选手,难道学校管理层还要把你加入百优表彰宣传单里面吗?然后同时再贴出一张违纪通知书:上面写着你的名字??你觉得这件事合理吗??换言之,成绩 与 作弊 这两件事,只能有一件事存在!同时存在就毫无意义!!同理,代码异常中止了也如此,只要中止了,那么退出码将毫无意义!!)


所以当进程异常终止退出时,我们也就不关心退出码了,但是我们应该要关心进程为什么异常了,以及进程发生了什么异常。

当我们对空指针进行解引用访问时,会出现 Segmentation fault (core dumped) 这样的错误信息;当我们进行除 0 运算时,会有 Floating point exception (core dumped) 。而类似这样的代码异常中止的情况,本质就是进程收到了对应的信号!

[outlier@localhost process3]$ kill -l
 1) SIGHUP	 2) SIGINT	 3) SIGQUIT	 4) SIGILL	 5) SIGTRAP
 6) SIGABRT	 7) SIGBUS	 8) SIGFPE	 9) SIGKILL	10) SIGUSR1
11) SIGSEGV	12) SIGUSR2	13) SIGPIPE	14) SIGALRM	15) SIGTERM
16) SIGSTKFLT	17) SIGCHLD	18) SIGCONT	19) SIGSTOP	20) SIGTSTP
21) SIGTTIN	22) SIGTTOU	23) SIGURG	24) SIGXCPU	25) SIGXFSZ
26) SIGVTALRM	27) SIGPROF	28) SIGWINCH	29) SIGIO	30) SIGPWR
31) SIGSYS	34) SIGRTMIN	35) SIGRTMIN+1	36) SIGRTMIN+2	37) SIGRTMIN+3
38) SIGRTMIN+4	39) SIGRTMIN+5	40) SIGRTMIN+6	41) SIGRTMIN+7	42) SIGRTMIN+8
43) SIGRTMIN+9	44) SIGRTMIN+10	45) SIGRTMIN+11	46) SIGRTMIN+12	47) SIGRTMIN+13
48) SIGRTMIN+14	49) SIGRTMIN+15	50) SIGRTMAX-14	51) SIGRTMAX-13	52) SIGRTMAX-12
53) SIGRTMAX-11	54) SIGRTMAX-10	55) SIGRTMAX-9	56) SIGRTMAX-8	57) SIGRTMAX-7
58) SIGRTMAX-6	59) SIGRTMAX-5	60) SIGRTMAX-4	61) SIGRTMAX-3	62) SIGRTMAX-2
63) SIGRTMAX-1	64) SIGRTMAX	

kill -l 可以查看给进程发送的信号,其中的 8 号信号,SIGFPE == Floating point exception
11 号信号就是我们常见的段错误 SIGSEGV == Segmentation fault

那么如何验证所谓的进程异常中止就是收到了某种信号呢??

当进程跑起来之后,我们尝试对进程发送 8 号信号 和 11 号信息,并观察现象

在这里插入图片描述


3. exit && _exit 的异同

  • exit 是 C 语言的库函数
  • _exit 是 linux 系统的系统调用

3.1 相同点

在不涉及缓冲区刷新的问题,exit 和 _exit 的作用都是一样的,都是用于进程退出的函数,与 return 不同的是,return 更多的代表函数的返回,当 main 函数调用一个函数,使用 return 之后,程序会回到调用处继续执行后续代码,而 exit / _eixt 则是直接进程退出了,不会再执行 exit / _eixt 之后的代码。

3.2 不同点

int main()
{

    printf("hello, linux!");
    sleep(1);
    exit(12);
}

在这里插入图片描述

int main()
{

    printf("hello, linux!");
    sleep(1);
    exit(12);
}

在这里插入图片描述

当调用的是 C 库的 exit,那么在退出进程时,会刷新缓冲区,关闭文件流等各种流,而在掉系统调用 _exit 则不会刷新缓冲区,这也是为什么在 _exit 之后,本该打印输出的信息并没有打印在屏幕上,因为 printf 一定是先把数据写入缓冲区中,达到某种界限时,在进行刷新!

在这里插入图片描述

程序中调用 _exit,直接在进程层面上终止进程; C 库当中的 exit 会先把应用层中打开的各种流关闭 以及 刷新缓冲区等操作,再调 _exit 终止进程。 可以理解为 exit 就是对 _exit 作了一定的封装!

那么我们需要知道,这个缓冲区一定不在哪里?? ---- 一定不在内核中!

为什么这么说呢,因为如果这个缓冲区在内核中,那么内核就一定需要维护这个缓冲区,只要是维护,就会有刷新,就不存在上面的现象了。

缓冲区在哪的问题,在后续关于 IO 方面的文章会介绍。


关于进程创建、进程终止等话题暂且谈论至此,后续还会讲进程控制中的进程等待。

如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!

感谢各位观看!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2066872.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于vue框架的比赛门票出售的系统12lh6(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。

系统程序文件列表 项目功能&#xff1a;用户,赛事类型,赛事信息,比赛队伍,比赛结果 开题报告内容 基于Vue框架的比赛门票出售系统开题报告 一、研究背景与意义 随着体育产业的蓬勃发展&#xff0c;各类体育赛事如雨后春笋般涌现&#xff0c;吸引了全球亿万观众的关注与参与…

域控ntdsutil修改架构、域命名、PDC、RID、结构主机

#笔记记录# 五大操作主机角色 架构主机&#xff08;schema master&#xff09;域命名主机&#xff08;naming master&#xff09;RID主机&#xff08;RID master&#xff09;基础架构主机&#xff08;infrastructure master&#xff09;PDC仿真器主机&#xff08;PDC&#xf…

算法力扣刷题记录 八十九【332.重新安排行程】

前言 回溯章节第13篇。 记录 八十九【332.重新安排行程】 一、题目阅读 给你一份航线列表 tickets &#xff0c;其中 tickets[i] [fromi, toi] 表示飞机出发和降落的机场地点。请你对该行程进行重新规划排序。 所有这些机票都属于一个从 JFK&#xff08;肯尼迪国际机场&am…

全网最全的Fiddler抓包的详细教学-直接收藏!

一、Fiddler与其他抓包工具的区别 c 2、Wireshark是通用的抓包工具&#xff0c;能获取HTTP&#xff0c;也能获取HTTPS&#xff0c;但是不能解密HTTPS&#xff0c;所以wireshark看不懂HTTPS中的内容&#xff0c;但如果是TCP、UDP协议可以用wireshark&#xff1b; 3、Httpwatch也…

豆包大模型迎来多维升级:综合性能提升20.3% 零售、汽车等行业落地提速

2024年无疑是科技行业公认的“AI大模型落地元年”&#xff0c;大模型应用的供需关系正在经历着前所未有的变化。 与过去AI厂商单方面探索应用场景的做法不同&#xff0c;今年我们见证了更多的重要行业和头部企业&#xff0c;在降本提效、以技术驱动新发展和新升级的需求之下&a…

AI开发者大赛 | 6道算法赛题上新

2024 AI开发者大赛正在如火如荼地进行着&#xff0c;本届比赛吸引了全球范围内的众多优秀开发者参与其中&#xff0c;用代码书写未来&#xff0c;用算法改变世界。 如今&#xff0c;算法赛新一批赛题上线&#xff0c;无论是初学者还是资深开发者&#xff0c;你都可以在2024 AI…

vue3实现系统tab标签页面切换

功能&#xff1a; 支持刷新当前、关闭其他、关闭全部、关闭当前支持打开多个相同path不同路由参数的页面&#xff0c;将fullPath作为路由页面唯一值 UI组件&#xff1a; 使用的是element-plus中的el-tab组件&#xff0c;结构目录如下 代码实现&#xff1a; 下面是 TagsView…

MATLAB水果分级系统

课题介绍 现在商业行为中&#xff0c;在水果出厂前都需要进行质量检测&#xff0c;需要将不同等级的水果进行分级包装&#xff0c;以保证商业利益最大化。可是传统方法都是依靠人工进行检测&#xff0c;效率低下&#xff0c;主观成分大&#xff0c;并不能很好客观地评价出货质…

适用于 Windows 10/11 的 2 大文件恢复软件

我很遗憾我在 Windows 10 中删除了 PC 中的数据并再次移动了它们。当我检查时&#xff0c;什么都没有。是否有任何Windows数据恢复软件&#xff0c;或者是否可以想象&#xff1f;我都会看到任何援助的价值。 文档、图像、音频等数据文件可能会因不良或危险行为而丢失&#xff…

Ollama 部署大模型

由于每次调用 OpenAI 等大模型都会产生费用&#xff0c;这个成本问题可以在开发环节可以通过私有化部署 LLM 的方式来避免。 Ollama 简介 Ollama 是一个开源的大型语言模型服务工具&#xff0c;专注于在本地运行大型语言模型。用户可以通过简单的安装指令在本地运行开源大型语…

关于Br的bean

笔者高烧了5天没有更新&#xff0c;今天终于感到热了&#xff0c;来继续更新。 JSON to Dart使用生成模型&#xff0c;首先要继承Br BR点进去把重复的内容删掉 然后去List里rename一下就好了。 然后再去

TypeError: Cannot read properties of undefined (reading ‘ciphertext‘)

ciphertext 是密文的意思&#xff0c;可能是使用插件进行解密的时候&#xff0c;密文的内容是 null 空的&#xff0c;假如密文是 null 时我们可以把密文改成空字符串就好了 例如 使用了 CryptoJS 进行加解密&#xff0c;关于 CryptoJS 的介绍可以看这篇文章 【CryptoJS】使…

基于STM32开发的智能水族箱控制系统

目录 引言环境准备工作 硬件准备软件安装与配置系统设计 系统架构硬件连接代码实现 系统初始化水温监测与调节水质监控与自动换水照明控制与状态指示Wi-Fi通信与远程控制应用场景 家庭水族箱的智能管理公共水族馆的水质监控常见问题及解决方案 常见问题解决方案结论 1. 引言 …

mp4转m4v怎么转?5种方法快速完成转换

在这个多媒体内容爆炸的时代&#xff0c;视频格式转换成为了我们日常生活中不可或缺的一部分。尤其是从MP4转换为M4V&#xff0c;这种转换不仅关乎视频播放的兼容性&#xff0c;还影响着视频质量。下面就来给大家分享5种高效转换方法&#xff0c;一起来看看吧。 方法一&#xf…

开学季数码好物分享!推荐适合学生党好用又实惠的平替电容笔!

​开学季总是伴随着满满的期待与新鲜感&#xff0c;好多小伙伴都会在这个时候规划自己的学习新篇章&#xff0c;寻找那些能够助力学习、提升效率的好帮手。在数字化时代&#xff0c;电容笔作为无纸化学习的重要工具之一&#xff0c;其重要性不言而喻。它不仅能让学习笔记更加便…

MinIO在Windows中部署,并注册服务

文章目录 一、下载二、安装1. 打开命令提示符或PowerShell(需用命令提示符窗口运行)&#xff1a;2. 切换到 D:\MinIO 目录&#xff1a; 使用 cd 命令导航到 D:\MinIO 目录3. 运行 minio.exe&#xff1a; 输入以下命令并按 Enter&#xff1a;.\minio.exe4. 退出命令行&#xff1…

⼆⼿⻋交易系统架构分析

二手车交易系统架构分析涉及多个层面&#xff0c;包括技术选型、系统模块、数据库设计、用户界面及安全性等。以下是对二手车交易系统架构的综合分析&#xff1a; 技术选型&#xff1a;系统通常采用B/S架构模式&#xff0c;前后端分离&#xff0c;前端使用微信小程序开发工具&…

⼆⼿⻋交易系统小程序功能分析

二手车交易系统小程序的功能分析主要聚焦于如何利用移动互联网技术提升用户体验和交易效率。以下是一些关键功能的分析&#xff1a; 用户注册与登录&#xff1a;提供用户注册和登录功能&#xff0c;确保用户信息安全&#xff0c;可能包括手机号验证、邮箱验证或第三方平台&…

ROG NUC 助力金猴 冲破天命!

ROG NUC -畅玩黑神话性能指南来了&#xff01; 黑神话悟空已经发布两天了&#xff0c;三百万大圣齐齐讨贼&#xff0c;感觉大头怪都快不够用了&#xff01;而ROG NUC作为目前最强的桌面性能独显主机&#xff0c;到底作为英特尔基于INTEL 4的7nm工艺开发的最新一代CPU酷睿Ultra…

【深度生成模型】Diffusion model-公式推导

&#xff08;前提&#xff1a;数学原理很多&#xff09; 第一件事&#xff0c;前向过程&#xff1a;不断往输入数据中加噪声&#xff0c;变成纯噪声 每一步加入的噪声是不一样的&#xff0c;希望加噪的过程不断越来越多&#xff0c;理解为噪声的权重越来越大。 任意时刻的xt的…