系统编程中的进程的概念No.2

简介: 系统编程中的进程的概念No.2

引言:

今天是个好日子,日更动态养成习惯,日更博客你我他,北京时间 2023/1/29/10:09,今天阳光明媚,但是还是很冷,起床时间8:55,可以看出又早了那么一点点,今天为什么能起的更早了呢?原因就是,我听了一首五月天的歌,导致我激情澎湃,如下视屏,我给大家分享一下这首起床必备神曲,说实话,真好听,当然好听的歌就跟有趣的灵魂一样是千千万万的,就看你自己喜欢什么风格的,当然身为Jay的00后头号粉丝,Jay的地位是不可撼动的,但是在00年代,好听的歌真的是数不胜数,如果没怎么接触过00年代的歌的同学,可以去了解一下,你会发现你发现了一个音乐宝藏,接下来我的博客更新中,也会为大家分享一些我最近发现的好听的音乐,大家一起共享啊!


正式学习进程

上篇博客我们仔仔细细的讲解了一下进程的先导知识,了解了什么是操作系统,什么是冯诺依曼体系,还了解了很多的有关硬件的知识,所以现在让我们带着这些知识来正式的学习一下什么是进程吧!


1.复习操作系统

此我们把操作系统再理解一下,操作系统就是上层(用户)和下层(硬件)链接的桥梁,对下通过各种的驱动程序的控制与各种硬件取得联系,对上通过系统调用的方式对各种软件进行管理,从而提供用户所需的各种需求。

看到上述这段话,有的同学可能有所疑问,到底什么是驱动程序,什么是系统调用呢?


简述驱动程序和系统调用

1.1.驱动程序:首先驱动程序是包含在我们的操作系统中的,它的本质就是各种硬件的接口,不同的驱动程序可能其程序中包含的相应的硬件的属性是不一样的,所以不同的驱动程序也就发挥着不同的作用,所以驱动程序就相当是操作系统和硬件之间的桥梁。

如果有对驱动程序更加感兴趣的同学,可以参考下述链接:带你分分钟搞定驱动程序和操作系统之间的关系

1.2.系统调用:首先系统调用本质上是:操作系统将自己的一些接口暴露给用户,用户通过使用相应的接口来完成相应的需求。此处还会涉及到最初的二次开发封装成库的概念,什么是库,例如我们的语言库,就是通过对操作系统的某些接口进行封装进而形成的,所以我们使用各种语言(Java还是C++),本质上都只是在调用系统接口而已,俗称系统调用。并且我们封装成库的原因也就是跟生成相应的语言库一样,目的是为了让我们的编程变的更加的简单,通过库使用系统接口,高效。

总:便与开发


2.操作系统和进程的关系

操作系统和进程的关系是什么,我觉得我们应该可以直接想到管理和被管理的关系,当我们知道了操作系统和进程的大致关系之后,可以想到,那么在我们常用的操作系统(Windows)中,进程在哪里呢?如下图,我们先简单的看看进程到底长什么样子,是人还是鬼!

69.png


此时我们可以在左上角看到两个大大的字“进程”,可以看出,此时你的电脑后台中是有很多的各种各样的进程在运行的,也就可以理解成是你的操作系统中此时有着很多的进程在运行,综合管理和被管理的关系,此时我们可以得出,我们的进程是被我们的操作系统通过某些特殊的手段给管理着的,并且这个管理是有序的,正常的,安全的,高效的。


3.操作系统如何管理进程

为什么操作系统可以有序的,正常的,安全的,高效的管理进程呢?

此时对上句话提取关键词,不会错,就是那个连钟xx都能找到的管理二字。我们从管理入手,从而找到此问题的答案。什么是管理?我相信上篇博客可以给我们的答案,管理的本质就是:先描述,再组织,只要我们弄清楚了什么是管理,那么我们就搞清楚了上述问题,我们也就初步的了解了进程在操作系统中的一系列具体情况。简而言之,也就弄清楚了操作系统是如何管理进程。所以下述我们通过一个建模过程带大家搞定此问题。


文字建模:首先我相信大家都知道磁盘的概念和可执行文件(后缀 .obj)的概念,此时我们电脑中有一个可执行文件存储在磁盘中,当我们双击运行此文件时,我们的操作系统识别到此操作,于是通过文件系统驱动找到此文件,然后把此文件就给加载到了内存之中,此时我们的可执行文件也就加载到了内存之中。这里有的同学可能会有这样的理解(我的一个可执行文件被加载到了内存中之后就形成了一个进程了吗?)这个想法显然是不完全正确的(CPU说,我都还没上场,你们就结束啦!),所以只有当我们内存中的可执行文件被CPU调用,然后进过CPU的计算和控制之后,返回到内存并最终被我们的操作系统重新从内存中识别,然后运行到我们电脑的其它硬件或软件之上,此时我们才可以说我们形成了一个进程,此时也就可以在我们的显示屏上的任务资源管理器上看到该进程了。但此时有的小伙伴就有疑问了,说,我在从磁盘到内存的过程中,我的文件系统驱动器可以从磁盘中拿到指定的文件是因为我已经把该文件给包装好了并且自己找到点击了该文件,那当CPU从内存中获取该文件的时候,为什么不需要经过我的操作也可以准确、完整、快速的拿到该文件呢?并且当我在内存中存放了很多不同的可执行文件时,我的CPU是怎么区分这些数据,从而执行我想要执行的程序呢?


认识pcb

解决上述两个疑问,我们只需要引入pcb的概念就可以解决,pcb也叫task_struct,顾名思义,就是一个结构体,一个任务结构体,该结构体用来干嘛的呢?我相信我们在鹏哥C语言的时候,我们刚学习结构体的时候,我们都学习过 struct people 这样的结构体,当时这样的结构体是用来存储我们人的信息的结构体(例如:钟xx的姓名,身高,年龄,家庭地址等!)所以我们的task_struct的作用就是用来存储和文件的相关的属性和内容的(并且此处强调,文件 = 内容 + 属性),这样我把我的每一个我想要执行的文件通过操作系统加载到内存之后,有强迫症并且能干的内存就把这些文件一个一个的放到了相应的task_struct中,加载一个文件,内存就生成一个task_struct,这样就可以把加载到内存中的数据按照和我们磁盘中的数据一样,按照一个一个文件夹的样子对文件的属性和内容就行整理了,并且我们的内存还要对这些task_struct进行管理(并且此处强调,管理 = 先描述,再组织),因为我们已经用task_struct对文件的内容和属性描述好了,所以此时就只需要进行组织,我们的内存就完成了管理各种各样的文件的任务了,此时内存只要在每一个task_struct结构体中增加一个指针(也就是以单链表的形式),将这一个一个的task_struct结构体链接在一起,我们的内存就完成了管理工作,此时无论CPU想要获取那个数据,获取那个优先级高的数据,都可以说是很简单的就可以快速,完整的从内存中获取。当然这个获取的过程还是由我们的操作系统来完成的。

如果有同学不理解为什么CPU从内存获取数据需要经过操作系统,可以参考以下链接:

深入理解操作系统对硬件的管理


上述我们就完成了进程管理的文字建模过程,为了防止不好理解,我们来一幅图,帮助我们更好的理解进程管理的建模过程。


图形化建模:


70.png


结合文字描述和图形结合,我们就可以更好的理解进程了,所以我们明白一个道理,就是在计算机中我们对进程的管理本质上还是我们在数据结构中学的有关的对链表的增删查改

总:进程就是内核关于进程的相关数据结构和当前进程的代码和数据。


当然我相信很多同学对什么是进程的相关数据结构肯定是有一定的疑问的,其实进程相关的数据结构也就是我们上述讲到的pcb,所以我们这边对pcb进行一定的解释。

pcb就是一个用来方便我们管理进程的存储相关数据的属性的结构体

pcb/task_struct中的内容分类

1684829997523.png


了解了pcb的相关内容之后,我们紧接着去了Linux上真正的认识一下进程是什么。

在Linux中看进程

如何在Linux中看进程呢?

首先第一步,我们要生成一个可执行文件

例:下图中的myprocess文件就是一个可执行文件


70.png


有了这个可执行文件,我们只需要将它运行起来就行了

指令:./ myprocess 此时的 ./ 就是表示将myprocess这个可执行文件加载到我们的内存当中。加载到了内存当中,进过操作系统的一些列处理,我们就可以在我们相应的地方看到我们的进程了。


比如:在Windows中看进程我们需要打开的是任务资源管理器,然后才可以看见我们当前执行的进程

而:在我们的Linux中,我们看进程只需要用到ps指令就可以了,并且此时Windows中存放进程的地方叫任务资源管理器,而我们的Linux中存放进程的地方就是我们根目录下的proc目录(进程目录),如下图:


71.png


所以只要当我们的myprocess文件加载到了内存之后,我们输入指令: ps ajx我们就可以看到当前在根目录下的proc目录中的一切进程了,如图:

72.png


当然这也就是相当于是Windows中的任务资源管理器中的如下图片:

73.png


当然我们发现当前电脑中的进程是有非常多的,所以我们也可以选择不查看所有的进程,而是只看我们想看的进程。指令:ps axj | grep myprocess 表达的意思就是,把我的所有进程给存到管道文件中,然后我再从管道文件中筛选出我想看的文件的进程,如下图所示:

74.png


这样我就可以看到我们当前myprocess文件的进程了。

当然也可以这样操作,输入指令:ps axj | head -1 然后再打开特点的指令,这样我就可以看见第一行的属性和我们进程相关的信息了

完整指令:ps axj | head -1 && ps axj | grep myprocess75.png


当然你也可以把myprocess进程之上的那个grep进程给去掉,去掉方法如图:因为grep也是进程,所以会一起显示出来,去掉指令:ps axj | grep myprocess | grep -v grep

76.png


并且当我们开启了两个myprocess文件进程时,进程时怎样的呢?现在让我们一起去看一看。

77.png

此时我们就看到了两个myprocess文件生成的对应的进程了。

并且此时我们从进程的属性内容中的PID来看,此时的两个进程并不是同一个进程,所以此时这边可以得出一个结论,就是每一个二进制文件加载到内存之后,都会有不同的PCB来管理这个进程。当然PID代表的就是进程的编号。


所以当我们此时知道了我们这两个进程的编号的时候,我们就可以前往我们根目录之下的,proc目录下,查看是否有此编号的进程了。

如图:


78.png

很明显此时我们就看到了我们的myprocess文件的两个进程的PID了(4731/31241)。

此时在proc目录中看见了我们的进程,我们此时就可以尝试进入该进程中看一看,这个进程中都有什么内容。如图:

其实这幅图中的内容也就是我们的PCB/task_struct结构体中存储的内容,我们的内存就是通过这个形式来管理我们的进程的。


79.png

虽然我们对大部分的内容都不认识,但是总归就是PCB中的相关的属性,并且此时我们发现我们还是可以看出exe和cwd属性,这两个属性就是我们的文件目录。


从pid入手父子进程概念


80.png



此时我们运用一个系统调用的接口,#include<sus/types.h>,使用这个接口我们来使用我们的函数getpid(),这样我们就可以直接获得该代码文件变成进程之后的pid了。

运行如图:

81.png


此时我们从图中基于上述得出的结论,我们可以得出我们的代码运行起来的进程和代码文件运行起来的进程是同一个进程。并且发现,同一文件运行多次,得到的进程并不相同


深入父子进程(bash)

我们按照上述原理,通过getppid()的方式来获得此进程的一个父进程,并且此时通过对进程的执行和停止,我们可以发现,不管我们进程本身的pid怎么变,但是该进程的父进程是不会变的。

如图:

82.png


所以此时我们很好奇我们的父进程为什么不会改变呢?我们通过指令,搜索一下pid为31548的进程到底是什么,如图:

83.png


此时我们可以发现,pid为31548的时候一个叫bash的东西,相信大家都知道shell外壳的概念,shell外壳就是我们和Linux操作系统沟通的桥梁,然而bash就是我们CentOS系统中的shell外壳,所以我们的命令行就是我们的bash,我们把我们的指令传送给bash,bash再传给Linux内核,这就是我们使用Linux操作系统的原理,所以我们的任何进程的父进程都是我们的bash。但是此时好奇的同学就会问了,为什么所有进程的父进程都是bash呢?

因为此时我们想要使用Linux内核,就必须依靠bash,但是bash怕我们的程序或者指令是有问题的,一但有问题,容易传送错误的信息给Linux内核,容易导致系统崩溃,所以我们的bash每次在执行指令的时候,都会创建一个子进程去执行这个指令,通过这个子进程来保证不会出错,从而使我们的Linux操作系统变的更加的安全,稳定了。这也就是为什么所有进程的父进程都是bash的原因。所以还可以说,除了bash进程,别的进程都是子进程。特殊除外。


总:bash就是我们的命令行解释器,本质上也是一个进程,并且是所有进程的父进程。


自我创建子进程

从上述的知识中,我们可以发现,我们只要把可执行文件写入内存形成进程之后,这个进程就是一个子进程了,然后有的同学就会问,那这个进程有什么用呢?我们并不能控制这个进程啊。但是其实上我们是可以控制子进程的,例如:我们可以通过系统调用fork函数来自我创建一个子进程。


我们通过相应的代码来实现


84.png


从上图我们可以看出因为有了fork函数的调用,我们此时打印的时候多打印了一行A字符,从这个现象,此时我们可以进行一个简单的猜想,就是我的程序在执行完打印O之后,此时的myprocess文件形成的进程,它因为fork函数的原因,又生成了一个子进程,此时就有了两个子进程在执行该代码,所以导致A被打印了两次。所以我们为了证明如上的猜想,我们引入pid的概念来进一步的了解,如图:

85.png


此时从我们右图的输出内容我们可以发现,我们的pid之前的关系,我们自身创建的进程,成为了此文件进程的子进程,也就是bash的孙进程的意思。

总:不仅只有bash可以创建子进程,我们自己也可以创建子进程。


fork函数的深度理解和认识虚拟地址

86.png

通过这幅图,我们可以理解到,我们不仅看到了两条打印,我们还看到了两个返回值,一个是0,一个是pid,并且我们还看到了两个一模一样的地址。

所以此时我么就有了两个问题,为什么会有两个返回值?为什么有两个地址,并且这地址是同一块地址?

所以我们可以猜想问题一,因为有两个进程,所以有两个返回值,并且因为一个有实际pid,一个没有实际pid。猜想问题二,就是两个数据公用同一块内存。

所以问题的答案到底是什么呢?

我们先来解决第一个问题,如下代码:


87.png

根据图片,从我们的代码和我们的打印,发现,我们的if语句和我们的else if语句是可以同时执行的,并且我们的两个while语句也是可以同时执行的,这是为什么呢?

原因还是因为,我们此时有两个进程,所以我们有两个执行流,有两个执行流,我们就可以执行两个相背的理论了,所以我们的猜想一是正确的,有两个执行流就会有两个返回值。


所以这里我们通过上述现象,我们可以总结一下fork函数:


fork之后,执行流会变成两个执行流
fork之后,谁先执行由调度器决定
fork之后,代码是共享的,通常我们就是通过if和else if来进行执行流的分流


解决第一个问题之后,第二个问题就涉及到了我们的虚拟地址的理解,此时这个虚拟地址我们目前认识一下有这个东西存在就行,详细的我们以后在学。


父进程如何创建子进程

如图:

88.png

并且我们根据自己的使用电脑的常识,我们可以知道我们的进程是具有独立性的。并且我们通过我们的代码也可以证明我们的进程是具有独立性的,例如:父进程改变一个值,子进程不变(与写时拷贝有关)。

清楚进程指令:kill -9 pid

总:进程具有独立性

Linux中有关注释的相关知识

批量化注释:在命令模式下,Ctrl+v进入V-BLOCK模式,然后按住j(向下移动),选中区域,切换大写,输入i,在输入//,最后esc退出,就注释好了。

批量化取消注释:在命令模式下,Ctrl+v进入V-BLOCD模式,然后按住l(向上移动),选中区域,输入d。

取消注释:切换成小写,然后输入u。


总结:温故而知新,一定要反复理解现在学的知识和以前的知识。

相关文章
|
8月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
5月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
109 20
|
4月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
84 0
|
4月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
83 0
|
7月前
|
弹性计算 运维 监控
基于进程热点分析与系统资源优化的智能运维实践
智能服务器管理平台提供直观的可视化界面,助力高效操作系统管理。核心功能包括运维监控、智能助手和扩展插件管理,支持系统健康监控、故障诊断等,确保集群稳定运行。首次使用需激活服务并安装管控组件。平台还提供进程热点追踪、性能观测与优化建议,帮助开发人员快速识别和解决性能瓶颈。定期分析和多维度监控可提前预警潜在问题,保障系统长期稳定运行。
251 17
|
7月前
|
存储 Linux 调度
【Linux】进程概念和进程状态
本文详细介绍了Linux系统中进程的核心概念与管理机制。从进程的定义出发,阐述了其作为操作系统资源管理的基本单位的重要性,并深入解析了task_struct结构体的内容及其在进程管理中的作用。同时,文章讲解了进程的基本操作(如获取PID、查看进程信息等)、父进程与子进程的关系(重点分析fork函数)、以及进程的三种主要状态(运行、阻塞、挂起)。此外,还探讨了Linux特有的进程状态表示和孤儿进程的处理方式。通过学习这些内容,读者可以更好地理解Linux进程的运行原理并优化系统性能。
241 4
|
7月前
|
存储 算法 数据处理
进程基础:概念、状态与生命周期
进程是操作系统进行资源分配和调度的基本单位,由程序段、数据段和进程控制块(PCB)组成。线程是进程中更小的执行单元,能独立运行且共享进程资源,具有轻量级和并发性特点。进程状态包括就绪、运行和阻塞,其生命周期分为创建、就绪、运行、阻塞和终止阶段。
368 2
|
9月前
|
监控 搜索推荐 开发工具
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
697 2
2025年1月9日更新Windows操作系统个人使用-禁用掉一下一些不必要的服务-关闭占用资源的进程-禁用服务提升系统运行速度-让电脑不再卡顿-优雅草央千澈-长期更新
|
10月前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
420 13
|
7月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。