您可以捐助,支持我们的公益事业。

1元 10元 50元





认证码:  验证码,看不清楚?请点击刷新验证码 必填



  求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Model Center   Code  
会员   
   
 
     
   
 
 订阅
进程调度:应用为什么能并行执行?
作者 :牛犁heart
   次浏览      
 2022-12-30
 
编辑推荐:
本文主要介绍了进程调度:应用为什么能并行执行?希望对您的学习有所帮助。
本文来自博客园,由火龙果软件Linda编辑、推荐。

最简单的shell

为什么要从shell开始了解呢?因为熟悉它,才能知道Linux上怎么运行一个应用程序,才能明白Linux内部怎么表示一个正在运行的应用程序。

通常情况下,在Linux上运行程序,都是在终端下输入一个命令,这个命令其实大部分都是Linux系统里相应应用程序的文件名。

而终端也是Linux系统上一个普通的应用程序,从 UNIX 开始它就叫 shell,但是 shell 只是一个别名,在你的系统上,它的文件名可能是 sh,也可能是 bash。shell 实现的功能有别于其它应用,它的功能是接受用户输入,然后查找用户输入的应用程序,最后加载运行这个应用程序。

shell的机制里只用到两个系统调用 -- fork和execl,下图展示了其中的逻辑:

可发现shell应用首先调用了fork,通过写时复制,创建了一个自己的副本,暂且称为shell子应用。

然后,shell子应用中调用了execl,该函数会通过文件内容重载应用的地址空间,它会读取应用程序文件中的代码段、数据段、bss段和调用进程的栈,覆盖掉原有的应用程序地址空间中的对应部分。而且execl函数执行成功不会返回,而是会调用CPU的PC寄存器,从而执行心的init段和text段,从此,一个新的应用就产生并开始运行了。

可以看到,上述代码 shell_run 函数中循环读取用户输入,然后调用 run 函数,在 run 函数中 fork 建立子进程。如果子进程建立成功,子进程最初和父进程执行相同的代码,当子进程进入执行时会调用 execl 系统调用,加载我们输入的应用程序并覆盖原有的进程数据,这就是一个新进程诞生的过程。

为了证明我们这个 shell 是正确的,我们要在当前工程目录下建立一个子目录 app,在 app 目录写个 main.c 文件作为新应用,并在其中写下后面这段代码。

在终端中切换到该工程目录下,执行 make 就编译好了。然后,就可以借助这个终端(shell)加载我们的 shell,如下图所示:

首先执行 make 编译,然后加载我们的 shell。第一次我们使用系统中的 date 命令进行测试,该命令会输出当前日期和时间。我们看到,上图中显示的是正确的;第二次我们使用自己的应用测试,如上图中的输出,正是我们应用程序运行之后的结果。

对一个应用的运行过程有了初步了解:由 fork 建立起应用的生命载体,也就是接下来要讲的进程;由 execl 来建筑应用程序的血肉,也就是用新的应用程序的数据覆盖进程的地址空间。

什么是进程

从应用程序、资源管理和代码实现这三个角度分别来探讨

应用程序

从前面实现的极简shell的过程来看,进程像极了操作系统制作的一个盒子。这个盒子能装下一个应用程序的指令和数据,而应用程序只能在这个盒子里做一些简单的工作,盒子之外的工作就需要请示操作系统介入,并代替应用去完成相应的工作。

这种盒子操作系统可以制作很多,不同应用各自使用自己的盒子,也可以让操作系统内部使用多个盒子装入同一个应用。其逻辑结构如下图所示:

应用程序由源代码编译而成,没有运行之前它是一个文件,直到它被装入内存中运行、操作数据执行相应的计算,完成相应的输入输出。从这个层面来看,进程不仅仅类似一个盒子或者容器,更像是一个具有独立功能的程序,是关于某个数据集合的一次运行活动。也就是说,运行状态的进程是动态的,是一个活动的实体。

理论上,操作系统能制造无数个叫作进程的盒子,装入无数道应用程序运行。然而理想是美好的,现实是骨干的,制造进程需要消耗系统资源,比如内存。下面我们就从资源角度继续看看进程是什么。

资源管理

在计算机中,CPU、内存、网络、各种输入、输出设备甚至文件数据都可以看成是资源,操作系统就是这些资源的管理者。

应用程序要想使用这些“资本”,就要向操作系统申请。比方说,应用程序占用了多少内存,使用了多少网络链接和通信端口,打开多少文件等,这些使用的资源通通要记录在案。记录在哪里比较合适呢?当然是代表一个应用程序的活动实体——进程之中最为稳妥。

由此,我们推导出一个运行中的应用程序包含了它分配到的内存,通常包括虚拟内存的一个区域。

在这个区域里放的内容有:

可运行代码

保存该应用程序运行时中途产生的数据,比如输入、输出、调用栈、堆等

分配给该应用程序的文件描述表、数据源和数据终端

安全特性,即操作系统允许该应用程序进行的操作,比如应用程序拥有者、应用程序的权限集合

处理寄存器、MMU页表内容;

......

结合上图发现,进程可以看作操作系统用来管理应用程序资源的容器,通过进程就能控制和保护应用程序。

一个进程记录了一个应用运行过程中需要用到的所有资源,那操作系统到底是通过什么样的机制来实现这一点呢?

代码实现角度

在计算机的世界中,不管实现何种功能或者逻辑,首先都要把功能或者逻辑进行数理化,变成一组特定意义的数据,然后把这组数据结构化、实例化,这是实现功能和逻辑的手段和方法。

回到进程的主题上,如果让你实现进程这一功能,你该怎么做呢?

首先会想到,进程包含了什么。刚刚资源管理角度分析过,进程包含进程 id(用于标识)、进程状态、地址空间(用于装载应用程序的代码和数据),还有堆和栈、CPU 上下文(用于记录进程的执行过程)、文件描述表(用于记录进程使用了哪些资源,记住资源也可以抽象为文件)、权限、安全等信息。

现在,需要把这些信息汇总,变成一个数据结构中的各种字段,或者子数据结构。这个数据结构和许多子数据结构组合在一起,就可以代表一个进程了。

Linux 的进程数据结构,如下所示:

代码中 struct 开头的结构都属于进程的子数据结构。task_struct 数据结构非常巨大,这里省略了进程的权限、安全、性能统计等相关内容,有近 500 行代码。

只需要记住:在内存中,一个task_struct结构体的实例变量就代表一个Linux进程。

接下来,来看下Linux里表示进程内存空间的数据结构,也就是struct mm_struct *mm;的mm所指向的数据结构:

不难发现,mm_struct 结构中包含了应用程序的代码区、数据区、堆区、栈区等各区段的地址和大小,其中的 vm_area_struct 结构,是用来描述一段虚拟地址空间的。mm_struct 结构中也包含了 MMU 页表相关的信息。

多个进程

进程的提出就是为了实现多进程,让一个进程在等待某一个资源时,CPU 去执行另外的进程。

可以看到,每个进程都有自己的 CPU 上下文,来保护进程的执行状态和轨迹。我们选择一个进程,对其 CPU 上下文保存和加载,这样就实现了进程的调度,进而演化出各种进程调度算法

进程调度涉及到给进程设置相应的状态,我们看看通常进程有哪些状态。人有生老病死,进程也是一样。一个进程从建立到运行,还可能因为资源问题不得不暂停运行,最后进程还要退出系统。这些过程,我们称为进程的生命周期。

在系统实现中,通常用进程的状态来表示进程的生命周期。进程通常有五种状态,分别是运行状态、睡眠状态、等待状态、新建状态、僵死状态。其中进程僵死状态,表示进程将要退出系统,不再进行调度。

进程的各种状态之间是如何进行转换呢?

上图中已经为你展示了,从进程建立到进程退出过程里,系统各状态之间的转换关系和需要满足的条件。

因为切换的速度很快,而且 CPU 运行速度远高于其他设备的速度,才会造成多个应用同时运行的假象。单 CPU 多进程的前提下,一个进程不得不停下来,等待 CPU 执行完其他进程,再处理自己的请求,实际上同一时刻还是只有一个进程在运行。

多核多进程

时至今日,市面上的软件数以百万计,用户常用的软件也有成十上百了,能同时运行,高效率完成各种工作。可是当系统中可运行的进程越来越多,CPU 又只有一个,这时 CPU 开始吃不消了,CPU 只好在各种可运行进程间来回切换,累得满头大汗。哪怕加大风扇,机器依然持续发热,但我们仍然感觉电脑很慢,有的程序失去响应,甚至开始卡顿

对称多处理器系统

这时硬件工程师们也意识到了问题,并着手解决,他们开始提升单颗 CPU 的频率,但收益不大,而且频率还有上限,不能无限提升。

于是,工程师开始聚焦在并行计算上,让多个进程能真正并行运行起来,不是像从前那样一个进程运行一小段时间,轮流着来。他们不再琢磨怎样提升频率,而是开始拼装 CPU,把多颗相同的 CPU 封装在一起,形成多核 CPU,这就是著名的 SMP,即对称多处理器系统。

SMP 是一种应用十分广泛的并行技术,它在一个计算机上汇集了一组处理器(多 CPU),各 CPU 之间共享内存以及总线结构。SMP 系统的逻辑结构和实施结构如下图所示:

画的是典型的 4 核 8 线程 CPU 结构,请注意上图中的实施结构,更接近于真实的情况。一个芯片上封装了四个 CPU 内核,每个 CPU 内核都有具有相同功能的执行单元;在一个执行单元上实现了两个硬件线程,这里硬件线程就是一套独立的寄存器,由执行单元调度,就像是操作系统调度进程。只是这里由硬件完成,软件开发人员不知道,操作系统开发者只感觉到这是一颗逻辑 CPU,可以用来执行程序。

多核心调度

SMP 系统的出现,对应用软件没有任何影响,因为应用软件始终看到是一颗 CPU,然而这却给操作系统带来了麻烦,操作系统必须使每个 CPU 都正确地执行进程。

来看下操作系统都需要解决哪些问题?

首先,操作系统要开发更先进的同步机制,解决数据竞争问题。

之前同一时刻下只有一个 CPU 能运行进程,对系统中的全局数据的读写,没有任何竞争问题,现在不同了,同一时间下有多个 CPU 能执行进程。比如说,CPU1 执行的进程读写全局数据 A 时,同时 CPU2 执行进程也在读写全局数据 A,这就是读写竞争问题,会导致数据 A 状态不一致,进而引发更为致命的错误。

为解决这样的问题,操作系统开发出了原子变量、自旋锁、信号量等高级的同步机制。用这些锁对全局的数据进行保护,确保同一时刻只有一个进程读写数据。

解决了数据竞争问题,还得解决进程调度问题。

这就需要使得多个 CPU 尽量忙起来,否则多核还是等同于单核。让 CPU 忙起来的方法很简单,就是让它们不停地运行进程,要让每个 CPU 都有“吃不消”的感觉。

为此,操作系统需要对进程调度模块进行改造。 单核 CPU 一般使用全局进程队列,系统所有进程都挂载到这个队列上,进程调度器每次从该队列中获取进程让 CPU 执行。多核下如果使用全局队列需要同步,会导致性能下降,所以多核下一般是每个 CPU 核心一个队列, 如下图所示:

多核心系统下,每个 CPU 一个进程队列,虽然提升了进程调度的性能,但同时又引发了另一个问题——每个 CPU 的压力各不相同。这是因为进程暂停或者退出,会导致各队列上进程数量不均衡,有的队列上很少或者没有进程,有的队列上进程数量则很多,间接地出现一部分 CPU 太忙吃不消,而其他 CPU 太闲(处于饥饿空闲状态)的情况。

怎么解决呢?这就需要操作系统时时查看各 CPU 进程队列上的进程数量,做出动态调整,把进程多的队列上的进程迁移到较少进程的队列上,使各大进程队列上的进程数量尽量相等,使 CPU 之间能为彼此分担压力。这就叫负载均衡,这种机制能提升系统的整体性能

概括一下“进程”到底是什么:

进程是操作系统用于刻画应用程序的运行动态,是操作系统用于管理和分配资源的单元,是操作系统的调度实体。

进程具备四大特性

动态特性

进程的本质是程序在操作系统中的一次执行过程,进程是动态建立、动态消亡的,有自己的状态和生命周期

并行特性

任何进程都可以同其他进程一起在操作系统中并行执行,尽管在单 CPU 上是伪并行

独立特性

进程是操作系统分配和管理资源的独立单元,同时进程也是一个被操作系统独立调度和执行的基本实体;

异步特性

由于进程需要操作系统的资源而被制约,使进程具有执行的间断性,即进程之间按各自独立的、不可预知的速度向前推进执行。

   
次浏览       
相关文章

一文了解汽车嵌入式AUTOSAR架构
嵌入式Linux系统移植的四大步骤
嵌入式中设计模式的艺术
嵌入式软件架构设计 模块化 & 分层设计
相关文档

企点嵌入式PHP的探索实践
ARM与STM简介
ARM架构详解
华为鸿蒙深度研究
相关课程

嵌入式C高质量编程
嵌入式操作系统组件及BSP裁剪与测试
基于VxWorks的嵌入式开发、调试与测试
嵌入式单元测试最佳实践

最新活动计划
C++高级编程 12-25 [线上]
白盒测试技术与工具实践 12-24[线上]
LLM大模型应用与项目构建 12-26[特惠]
需求分析最佳实践与沙盘演练 1-6[线上]
SysML建模专家 1-16[北京]
UAF架构体系与实践 1-22[北京]
 
 
最新文章
基于FPGA的异构计算在多媒体中的应用
深入Linux内核架构——简介与概述
Linux内核系统架构介绍
浅析嵌入式C优化技巧
进程间通信(IPC)介绍
最新课程
嵌入式Linux驱动开发
代码整洁之道-态度、技艺与习惯
嵌入式软件测试
嵌入式C高质量编程
嵌入式软件可靠性设计
成功案例
某军工所 嵌入式软件架构
中航工业某研究所 嵌入式软件开发指南
某轨道交通 嵌入式软件高级设计实践
深圳 嵌入式软件架构设计—高级实践
某企业 基于IPD的嵌入式软件开发
更多...