上一篇文章我们解决了为什么要有操作系统以及操作系统是什么的问题。本篇文章我们来回答操作系统是如何管理软硬件的问题以及讲清楚什么是进程。

如何管理?

Linux-进程(2)_数据

如何管理?下面我们以一个例子来说明:

对于管理,我们都很陌生。但是对于被管理者,这一概念我们深有体会,我从小就开始收到各种管控。你要上小学,上初中,按时完成作业,回家不可以玩游戏,只有到了大学,我们才感受到自由一点。

在大学里,有校长,辅导员,学生这三个重要的角色。我们学生是最典型的被管理者,校长是学校的管理者,辅导员是执行者。问大家一个问题,你有没有见过你们学校的校长?有多少人真正见过校长?很多人都没见过校长,可校长却能把学生管理好。这是为什么?校长和辅导员说,小张,你们院成绩比较差的同学有谁呀?辅导员的办事效率很高,第二天,就把院里成绩差的同学资料放到校长办公桌上了。校长,拿起桌上的资料看了看,一下子就知道这些同学的学习情况了。打电话给辅导员说:小张呀!你去了解一下这些同学的情况,把需要退学的告诉我。辅导员的办事效率确实很快,很快就了解完了需要退学的同学,把名单资料一整理,就拿到校长办公室去了。校长,一看,没什么问题,签字。自此,校长就完成了对成绩差的同学的退学管理。在这个管理学生的过程中,校长自始至终都没有与学生见面,而只是根据辅导员提供的数据做出了决策,谁需要退学。由此,我们可以得到一个结论,管理者和被管理者不需要见面,管理者只需要得到被管理者的信息,就可以进行决策管理。管理的本质,是通过对数据的管理,来达到对人的管理。那数据我们怎么拿到呢?通过执行者(辅导员)。

单看成绩差的同学是没法了解整个学校的总体情况的。于是,校长要来了各个院的学生资料。看到资料的那一刻,校长陷入了沉思,学生的资料共有一万多份呀!曾经作为一名程序员的校长,很快就想出了解决方案。每个学生都有姓名,学号,性别,电话,地址等属性,我可以用C语言创建一个student结构体,来描述学生信息。

Linux-进程(2)_数据_02

学校的每一个学生的信息,都可以用一个struct student结构来存储,刚好,每个结构体都有一个指针变量,我们可以用指针变量将结构体相互联系起来,构成一个链式的数据结构。如果校长想给予成绩最好的同学奖励,只需要找到链表中成绩最高的结构体。自此,对学生的管理,就转换成了对链式数据结构的增删查改。

我们将把学生描述成结构体的过程,称之为描述的过程。每个学生结构体都是一个独立的个体,我们将,把这一个个结构体连接起来构成某种数据结构的过程,称之为组织的过程

在计算机中,操作系统相当于学校里的校长,驱动程序相当于之执行者,软硬件资源相当于学生。未来操作系统要对软硬件资源进行管理,怎么管理?管理资源实际上是在管理数据,数据怎么来?通过驱动程序获取,软硬件资源的数据很多怎么办?没关系,硬件之间一定会存在很多的共性,比如说:工作状态,硬件名称等。我们可以创建一个个的结构体来表示这些硬件,再将它们组织成某种数据结构。自此,操作系统对软硬件资源的管理就变成了对数据结构的管里。操作系统是用C语言实现的,操作系统内部只能使用struct结构体来描述被管理对象。所以,操作系统内部的管理思路和我们刚刚讲的校长管理学生的思路是一样的。我们把这种思路称之为先描述,再组织在操作系统中,管理任何对象,最终都可以转换成为对某种数据结构的增删查改

我们将,先描述,再组织的过程,称之为建模。现在,我们回过头来回答如何管理的问题?其实就六个字,先描述,再组织。操作系统的核心工作是作各种中管理,这就注定了,在操作系统中,一定存在大量的数据结构

Linux-进程(2)_数据结构_03

Linux-进程(2)_加载_04

在使用C语言编程的时,我们常常会用到库函数printf。库函数和系统调用之间是什么关系呢?printf函数的功能是向显示器打印内容,它能不能直接使用器呢?不能,显示器由操作系统来进行管理,而操作系统不相信任何人。printf函数的底层必须封装系统调用,来进行对显示器进行操作。库函数和系统调用是上下层和被调用关系。库函数是上层,操作系统是下层。库函数对硬件的使用,必须贯穿操作系统,通过系统调用来进行使用。

Linux-进程(2)_数据结构_05

有了前面这些准备知识储备。我虽然现在还不知道进程是什么东西?但操作系统是如何管理进程的,我必须知道。首先,进程得有一个结构体进行描述,一个结构体对象相当于一个进程,一个个的结构体又被连接起来构成某种数据结构。于是,操作系统对进程的管理,就变成了对某种数据结构的管理。

进程

一个被加载到内存中的程序,叫做进程。很多教材也把进程称之为任务。

在Linux中,有很多的进程,我们可以使用ps axj指令来查看进程信息。

Linux-进程(2)_数据结构_06

也可以使用top指令来查看当前正在运行的进程的信息。

Linux-进程(2)_进程_07

我们可以简单的写一个小程序。测试:

Linux-进程(2)_进程_08

Linux-进程(2)_数据结构_09

当一个源文件编译运行起来后,就变成了一个进程。

Linux-进程(2)_进程_10

我们可以是使用ps axj指令来查看

Linux-进程(2)_加载_11

图中的command是指这个进程是由什么指令生成什么的。

还有一些教材,会将正在运行的程序,叫做进程。

我们刚刚创建的可执行程序,本质是一个文件,存储在磁盘中。而我们的程序一旦跑起来,是由CPU来执行的。在冯诺依曼体系结构中,CPU只与内存打交道,这就注定了程序要运行必须先加载到内存里。换而言之,进程被执行,就必须有一部分被加载到内存里。操作系统,在不执行你的进程的时候,可能在定期检查我们计算机的状态,释放计算机各种各样的资源,内部的数据定期刷新到磁盘等等这些日常的管理工作。所以,这就注定了操作系统是要在内存当中的。我们平时开机,计算机在做什么?从冯诺依曼体系结构的视角来看,是将操作系统由磁盘搬到内存中。

Linux-进程(2)_进程_12

一个操作系统中不仅仅能运行一个进程,可以同时运行多个进程。那么多的进程,操作系统必须将它们管理起来。如何管理?先描述,再组织。任何一个进程,在加载进程的时候,形成真正的进程时,操作系统会先创建描述进程的结构体对象--PCB(process ctrl block),进程控制块。

我们平时是如何认识一个事物或对象的。你对苹果的认识是什么?红的,圆的。你对你朋友的认识是什么?帅气,1.7m,阳光。这不就是一个个的属性吗?一个个属性构成的集合就是我们要描述的对象。那我们如何去描述进程?同样的,用一个个的属性。那我们用什么属性呢?每个学生都他自己的编号,同样的进程也有自己的编号,叫进程编号。在操作系统中,有的进程刚来,有的快执行完了,有的已经执行完了。每个进程都处于不同的工作状态,笼统的称为,进程状态。未来,进程还会被调度,谁先被执行。这个由它们的优先级来决定。进程编号,进程状态,优先级等等,这些描述进程的属性值的集合就相当于进程。操作系统是用C语言来写的,描述一个对象只能用struct结构体。所以,我们也可以这样理解,进程就是一个struct结构体,结构体里包含了各种的属性。

Linux-进程(2)_进程_13

我们写的代码本质是一个文件,经过编译后,就得到了一个二进制文件,也就是可执行程序(你的代码和数据)。执行一个程序变成进程,需要做两件核心工作,第一件,把你的代码和数据加载到内存中。第二件,由操作系统给你的程序创建一个PCB对象。此时,这个PCB+你的代码和数据才叫进程。你的代码和数据不能称之为进程。举个例子:怎么才能算这个学校的学生?高考结束后,你被心仪的学校录取了,录取你的学校,需要到你所在的高中,拿到你的学籍,等你开学的时候,来报道,你就是这个学校的学生了。你就相当于你的代码和数据,学籍就相当于PCB。你们学校的保安算不算学生,当然不算啦,他没有对应的学籍。进程也是一样的。我们可以将进程理解为进程=内核PCB数据结构对象+你的代码和数据。学校怎么管理你?它需不需要关心你,不需要。管理的本质是管理数据,学校只需要对你的数据做管理,就完成了对你的管理。同样的,操作系统怎么管理进程?它关不关心你的代码和数据,不关心,它只关心你的PCB。于是,操作系统对进程的管理,就变成了对PCB对象的管理。那它怎么知道你的代码和数据在哪?PCB里包了描述进程的所有属性,你的代码和数据也是属性,所以,PCB里会存在一个相关的指针,告诉操作系统你的代码和数据在那。

Linux-进程(2)_数据结构_14

操作系统中,不止有一个进程。当PCB存在一个指针struct PCB* next时,我们就可以把所有进程连接成一个单链表。此时,在操作系统中,对进程进行管理,变成了对单链表进行增删查改。

Linux-进程(2)_加载_15

以上这些是理解进程的一般思路。PCB是操作系统的概念。不管是在Linux上,Windowns上,还是手机上的进程控制块,我们都称之为PCB。

Linux-进程(2)_进程_16

任何一个操作系统的实现思路,都是我们上面所讲的思路。在Linux中,用来描述进程的PCB是task_struct。进程有任务的叫法,翻译成应为就是task,所以,叫task_struct。它和PCB是什么关系呢?task_struct是PCB的其中一种。

Linux-进程(2)_进程_17

task_struct中包含了那些属性,请看下图。

Linux-进程(2)_数据结构_18

这些属性,我们讲讲什么程序计数器以及记账信息。一个程序在执行的时候,不是一下就执行完的,而是这个程序执行一段时间,另一个程序执行一段时间。当我们在返回第一个程序的时候,怎么知道它执行到哪了。在CPU中会有一个寄存器,我们称它为程序计数器,用来记录程序当前执行到哪了,当再次运行这个程序时,我们就从这里开始执行。这个有点像我们读一本书的过程,我们大多数情况不是一下就读完一本书,而是今天读几页,过几天在读几页。那过几天后,我们怎么知道我们读到那一页了,是不是需要一个书签来标记我们读到了那一页,下一次,我们再读这本书,就从书签标记的那一页开始读。

记账信息,是用来记录进程在CPU累计运行的时间。未来,CPU会运行很多的进程,那我们怎么去保证调度进程的公平性呢?是不是要确保一段时间里,各个进程调度运行的时间大致相同呀!

往后对于进程的学习,我们更多的是去了解进程的属性,而不会是进程的概念。

在Linux内核中,采用双向链表来组织进程的PCB--task_struct。

Linux-进程(2)_加载_19

查看进程

我们查看有哪些进程可以使用ps axj指令。我们另外打开两个窗口后,运行同一个程序。使用ps指令查看,打印进程信息。信息属性中有一列叫PID,用来标识不同的进程。我们可以发现,虽然我们运行的是同一个程序,可它们的PID却不同。在系统层面开来,这是两个不同的进程。

Linux-进程(2)_进程_20

我们在过滤进程信息的时候,为什么会多出第三行的信息?

Linux-进程(2)_进程_21

这是因为执行grep指令前,我们得先把grep指令加载到内存中,变成一个进程。当grep指令对所有进程信息进行过滤时,很尴尬的是它自己本省也带有过滤信息的关键字。所以,grep指令也被过滤出来了。

查看进程,我们还可以使用一个比较偏门的方式,使用ls指令,查看目录/proc,进而查看系统中的所有进程。

Linux-进程(2)_加载_22

proce是一个十分重要的目录结构,在你的关机后,里面的数据全没了。在你再次开机的时候,操作系统会自动的帮我们创建好对应的目录和文件。为什么会这样呢?这是因为Linux操作系统用文件系统的方式把内存当中的文件包括进程信息帮我们可视化出来了。

我们可以简单的证明一下。在proc这个目录中,蓝色的字体的文件都是目录文件,它们的文件名是由进程的PID来命名的。我们刚刚不是创建了一个PID为12648的进程吗?我们可以试着打开这个文件,看是否存在?很显然,它是存在的,并且里面包含了很多文件,这些文件里包含了相应进程的所以属性信息。

Linux-进程(2)_进程_23

当我们将程序终止后,这个目录会自动将相应进程的目录文件清除。我们可以简单的验证一下。

Linux-进程(2)_进程_24

通过验证,我们便了解了,proc会动态保存运行进程信息的目录。当我们在次启动运行myprocess这个程序,它的PID大概率不会和之前的相同,这就像我们离开了所在的学校,在重新考进这个学校,我们将会得到一个新的学号,而不是之前的学号。

Linux-进程(2)_数据结构_25

此时,我们到proc目录下可以找到一个与这个进程PID相对应的目录文件。

Linux-进程(2)_数据结构_26

我们查看该目录下中的文件,可以看到如下两个蓝色字体的目录文件。

Linux-进程(2)_进程_27

exe后面的箭头跟着的路劲,不就是我们可执行程序的路径吗?而cwd后面跟着的路径,是我们可执行程序所存放的路径。我们把cwd称之为当前进程的工作路径。也就是说改指令在什么路径下调用运行的。还记不记得,我们在学C语言的时候,使用的fopen指令,它为什么会把文件创建在可执行程序所在的目录文件中?我们明明只告诉了它文件叫什么名字呀!这是因为操作系统会默认的把cwd加到文件名的前面。

Linux-进程(2)_加载_28

好了,到这里,我们本次的分享就到此结束了,不知道我有没有说明白,给予你一点点收获。关于更多和Linux相关的知识,会在后面的文章更新。如果你有所收获,别忘了给我点个赞,这是对我最好的回馈,当然你也可以在评论发表一下你的收获和心得,亦或者指出我的不足之处。如果喜欢我的分享,别忘了给我点关注噢。