前言
今天这篇文章我们将一起学习进程的概念,在了解进程之前我将先来和大家回顾操作系统的概念,之后我们再引入进程的概念,由浅入深深入剖析进程的前世今生,相信这篇文章会让您对进程有一个更加深入的理解,如果对您有所帮助的话,希望留下点赞、关注加收藏,您的支持就是我创作的最大动力
操作系统
在介绍进程这个概念之前我们无法绕过的话题就是操作系统,那么我们为什么需要先了解操作系统呢?
这是因为,进程是由操作系统来管理的
那么为什么操作系统可以管理进程呢?
这是因为,操作系统是一款进行软硬件资源管理的软件
从上面的结论我们不难看出,操作系统是用来做管理的,那么操作系统是由什么组成的呢?
笼统的理解,一款操作系统分为两部分,一部分是操作系统的内核,也就是最核心的部分,有:进程管理、内存管理、文件管理、驱动管理等
另一部分是由其他程序组成,也叫外壳程序,如库函数、shell程序等
操作系统的核心既然是它的内核,那么我们为什么要有外壳程序呢?
实际上这是为了让用户更好的使用操作系统,操作系统作为一款软件,它的使用对象除了程序员还有其他普通用户,如果我们只有内核,而没有其他外壳程序,用户使用操作系的难度将会很大,因此一款操作系统是由内核+外壳程序组成,这也就是广义上的操作系统
那么我们此时不禁疑问,操作系统的内核主要有什么用呢?
从操作系统的组成来说,它的内核主要是用来做管理的,对下做好软硬件资源管理;对上给用户提供一个稳定的、安全的、高效的运行环境
从操作系统的作用我们不难看出,一款成熟的操作系统从它的架构上看应该是一个层状结构,即:操作系统是用户和计算机硬件进行交互的一个媒介,用户使用计算机是通过对系统的操作,从而让计算机的各个硬件帮助用户完成各个不同的操作,因此可以这么说,操作系统是一款做管理的软件
看到这里我知道您应该对管理这个概念十分的不理解,下面我将带你理解管理的概念
首先我们要清楚什么是管理,管理的两个实体应该是管理者和被管理者,那么什么是管理者呢?实际上就是做决策的人,那么什么是被管理者呢?实际上是对管理者做出的决策进行执行的人。
我们以一个公司为例,在一个公司当中,谁是管理者?毫无疑问是公司的董事长,因为董事长只做决策,不做执行,那么谁是被管理者,毫无疑问是除了公司董事长以外的所有人,那么此时我们可以的出结论:真正的管理者只有一个人;那么管理者是如何管理被管理者的呢?在公司当中评价一个人业绩的好坏是通过绩效、考情来看的,公司的董事长在评选最佳员工、发工资、发奖金都是通过这个员工的绩效来决定的,换一句话就是:管理者管理被管理者见面不是必要条件,只需要通过被管理者身上的各个属性就可以做出决策。
所以我们将上述例子当中的出的结论与操作系统进行类比
对操作系统来说,管理的本质是对数据的管理,管理的做法就是先描述再组织,这里的描述就是描述这个对象的各个属性。
那么我们此时就对编程语言的认识上升了一个台阶:
为什么C++、Java、python等语言要给我提供面向对象和容器技术,本质就是为了更好的描述对象,而容器技术就是为了更好的组织对象应运而生的
那么我们此时再回过头来看操作系统
操作系统作为一款做管理的软件,也应该严格遵守先描述再组织的原则,而数据结构则是用来做组织的,因此我们可以猜测一下,操作系统内部应该有许多许多的数据结构,如果我们有兴趣去查看Linux操作系统的源代码,我们可以发现,里面充满了各种数据结构,所以我们也能理解了,数据结构为什么是计算机学科的基础学科
我们此时已经了解了操作系统是一款进行软硬件资源管理的软件,那么它对下应该是计算机硬件,对上应该是用户,因此我们可以猜测,操作系统的架构应该是层状的,而操作系统处于中间起承上启下的作用,那么事实是否符合我们的猜测呢?我们来看下面的一张图片:
通过这张图我们再来理解操作系统的作用就会更加直观。
但是此时我还有一个疑问,操作系统对下是做硬件资源管理的,对上是对接用户的,但是为什么在这张图里面操作系统对下是对接驱动程序,对上是系统调用呢,为什么不直接对接相应的部位呢?
这实际上是为了让操作系统能够更好的运行
我们先来看操作系统对下管理硬件资源,前面我们知道,操作系统要做好管理应该遵循先描述再组织的原则,因为这个原则的存在我们没有办法直接对接硬件资源,而是将硬件的各个属性描述起来,然后操作系统再对属性进行组织,所以这就注定了我们需要在硬件上面加一层驱动程序,来帮助操作系统做好管理的工作
接下来我们再来分析对上对接用户,我们设想,如果操作系统直接对接用户,那么此时会发生什么?用户可以肆意使用操作系统对硬件进行操作,如果用户的操作不符合规范,那么就会导致操作系统崩溃,为了防止这种现象发生,操作系统会对外暴露自己的接口,用户只能通过这个接口对操作系统进行操作,这个接口就是系统调用,换句话说,系统调用是操作系统与生俱来的,但是系统调用对于程序员来说尚可,但是对于普通用户来说却是十分困难,因此有心的程序员将这些系统调用进行了封装,形成了C语言或C++等语言的标准库,用户只需要调用库函数就可以完成各种操作,但是这个操作只是方便了程序员或者开发者,对于真正的使用者来说还是存在困难,于是开发者通过这些库函数开发出来了各种程序,它们有的内置于操作系统发内,有的需要用户进行安装,通过一层层封装保证了操作系统的稳定性、安全性、便捷性
进程的引入
有了前面对操作系统的认识,我们再来介绍进程的概念。
什么是进程
我们翻开任何一本教材,都会告诉你,加载到内存中的程序就是进程,这对于我们来说是无法理解的,接下来我将从另一个视角带你了解进程的概念。
首先我们要知道什么是程序?
程序员通过代码编译形成的磁盘二进制文件就是程序
但是这个程序仅仅存在于磁盘当中,因此我们不能称之为进程,我们需要将这个程序运行起来才能称之为进程,但是我们应该怎么加载到内存里呢?
在磁盘当中保存的是程序的二进制代码和数据,一个程序想要运行起来首先需要操作系统对其进行管理,如果操作系统不对其进行管理,那么这个程序我们就不知道它什么时候开始运行什么时候结束运行抑或是程序的运行结构,所以我们在运行一个程序之前首先要对进程进行管理
那么一个程序如何运行起来的问题就转变成了操作系统如何管理一个程序?
根据前面的铺垫,我们应该严格按照先描述再组织的原则,操作系统首先应该创建该程序的各个属性,将这些属性保存在一个结构体当中,然后操作系统只需要对这个结构体进行管理就可以知道一个程序的运行状态,而这个结构体我们称之为PCB(process control block)程序控制块。我们只需要通过各种数据结构对这个PCB结构体进行管理就达到了进程管理的目的
那么将程序加载到内存当中就转化成了将程序程的PCB加载到内存当中,换句话说:我们只需要将PCB加载到内存当中就可以称之为进程。
但是我们知道,一个计算机当中在同一时间会有多个进程同时运行,那么这就涉及进程的排队问题,进程的排队本质就是让进程的PCB进行排队
此时我们就得出一个结论:
进程=PCB(内核数据结构)+代码和数据
那么这个结构体包含什么东西呢?
我们以Linux系统为例,在Linux系统当中,PCB结构体称为task_struct,里面包含许多不同的属性,我们列举如下:
标识符:用来标识本进程的唯一字符,通常是一个无符号整数,分为PID和PPID
状态:任务状态(运行、阻塞、挂起)、退出代码、退出信号等
优先级:进程加载的顺序
程序计数器:PC指针,用来记录程序中即将被执行的下一条指令的地址
内存指针:包括程序代码和进程相关数据的指针,还有和其它进程共享内存块的指针
上下文数据:进程执行时处理器寄存器中的数据
I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
记账信息:可能包括处理器的时间总和,使用的时钟总和,时间限制,记帐号等
其他信息
那么进程在系统当中是如何组织的呢?
任何进程都以结构体双链表的形式存在于内核当中
如何查看进程
在Linux系统当中,查看进程的方式有很多,我们介绍两种方法:
方法一:我们可以通过/proc系统文件查看
我们可以看到,当我们查看该系统文件时就会出现许多的数字,这些数字就是一个进程的PID,当我们随便查看一个进程,如下图所示:
我们可以看到,里面有cwd和exe两个以红色表示的,其中exe是进程记录的自己对应的磁盘二进制文件;cwd是当前的工作路径
方法二:我们可以通过指令来查看进程
我们可以看到,我们使用ps加管道过滤指令就可以达到查看进程的目的
但是我们发现,此时进程有PID和PPID,其中PID是进程的标识符,那么PPID是什么呢?实际上PPID就是父进程的ID信息
那么为什么要有PPID呢?我们从进程的创建方法来说明
进程创建的方法
我们直接输出结论,在Linux系统中,增多进程是通过父进程创建子进程的方式让系统中的进程增多的,其中父进程是bash,由bash的子进程来执行代码
那么bash是通过什么方法来创建子进程的呢?
实际上是通过系统调用的方式来创建子进程的,这个系统调用是fork函数,我们以下面的一个例子来说明:
我们运行上述代码,看一看会发生什么?
可以看到,fork之后的代码运行了两次,这是为什么呢?其中fork之后第一个进程的PID与最开始的PID相等,都是3780,但是第二个进程的pid变成了3781,它的ppid是3780,与上一个进程的pid相等,由于pid具有唯一标识的特性,那么我们可以看到,fork之后我们创建了一个新的进程,这个进程的ppid等于上一个进程的pid,那么我们此时可以得出结论,父进程3780创建了子进程3781
那么这个结论类比到操作系统来说,就可以说明,操作系统通过调用fork函数,创建子进程,然后让子进程去执行相应的代码,对于操作系统创建的所有子进程,它们都有一个共同的父进程,那就是bash,换句话说,操作系统创建进程的方式是通过bash调用fork函数创建子进程,然后子进程去执行相应的代码。
此时我们来改写代码,我们看看会发生什么现象:
注:fork的返回值是pid_t是一个无符号整数,当其等于0时,返回的是子进程,当其大于0时返回的是父进程的pid
那么我们此时不禁疑问,我们上述的代码是通过if else判断了的,怎么可能同时执行两个代码?换句话说,怎么可能fork的返回值同时有两个呢?
实际上这个问题就要我们来讨论fork的工作原理了,在linux系统中,当我们创建一个新进程的时候,子进程的task_struct也要被初始化,其初始化是要以父进程为模板;当fork创建子进程时,该子进程默认为没有自己要执行的代码和数据,即:子进程共享fork之后的代码和数据,换句话说就是,fork之后的子进程创建了一份新的代码,这份新的代码是通过父进程进行拷贝过来的,它们分别执行自己的代码,从而导致返回值有两个。
但是我们此时又有一个疑问,为什么要给父进程返回子进程的pid而子进程要返回0呢?
实际上,fork函数是系统提供的,在操作系统内部完成子进程的创建等内核操作,当函数执行到return时主体功能已经完成了,我们需要给操作系统一个创建成功的标识符,这个标识符就是pid,所以当我们创建子进程成功时需要给操作系统返回子进程的pid,而子进程执行结束我们也需要返回一个pid,这个pid毫无疑问就是0
综上所述,在操作系统当中,创建进程的方法是通过bash调用fork函数创建子进程的。
小结
本篇文章我们深入理解了操作系统的概念,通过操作系统的概念,我们平滑的过度到了进程的概念当中,相信通过我的讲解,您对操作系统以及进程的概念会有一个全新的认识。
以上就是本篇博客的主要内容,受限于博主的知识水平,可能一些方面还存在着些许纰漏,欢迎大家指正,最后,如果本篇文章对您有所帮助的话希望留下点赞、关注加收藏,您的支持就是我创作的最大动力