美文网首页
操作系统中最基本的抽象:进程

操作系统中最基本的抽象:进程

作者: Hemi_Dev | 来源:发表于2020-03-08 01:11 被阅读0次

    本文主要是介绍操作系统中最基本的抽象--进程。进程最简单的定义为:进程就是运行的程序。一个程序本身是没有生命周期的,它只是存放在磁盘上的一些指令或者静态数据。是操作系统让这些躺在硬盘中的程序"跑起来"。

    比如说,当我们用鼠标双击桌面上的"Chrome浏览器"图标,此时计算机操作系统会创建一个"浏览器"进程,并运行起来了。然后我们就能使用"浏览器"了。如果有的童鞋想一边写代码bug,一边听着歌儿呢?那么他会熟练地打开IDEA,然后再打开"网Y云音乐",此时计算机操作系统中又会新创建出两个进程。键盘敲的噼里啪啦的,一顿操作猛如虎,bug越改越多了。一看时间已经23:20,算了不加班了,也关掉了"网Y云音乐"。此时操作系统中会结束掉这个"网Y云音乐"进程。

    以上只是日常生活中一个非常简单的场景,大家有想过背后的计算机知识么?啥?你还没意识到这重要的基础知识?好吧,我们就到真实环境中来看看操作系统中的进程。

    以Mac笔记本为例,使用“活动监视器”可以看到本机中的进程信息,还有进程对内存的使用率和所使用的 CPU 时间等信息。

    然后可以用如下的Linux命令统计一下主机中进程的数量,我的笔记本中大概有 280 个进程,你的呢?

    ps aux | wc -l
    

    实际上,一个正常的系统中可能有几百个进程在"同时运行"。那么,这些进程是如何同时运行的呢?操作系统又是如何管理这些进程的呢?笔记本电脑中只有少量的物理CPU可用,但是操作系统又是如何给几百个进程提供有"无数个"CPU可用的假象呢?

    操作系统通过虚拟化CPU来提供这种假象。通过让一个进程只在 CPU 上运行一个时间片,然后切换到运行其他的进程。让进程们误以为自己都是独自占用 CPU 的,这就是时分复用技术。因为 CPU 是共享的,所以会带来一定的性能损失,每个进程会运行得慢一点。为了实现 CPU 虚拟化,操作系统需要一些机制策略。所谓的机制,就是一些方法或者协议,比如说,后面将要介绍的上下文切换,它能让操作系统停止 A 进程,然后运行 B 进程。当 B 进程运行一段时间后,再运行A进程。所有的操作系统中都采用了时分复用机制。所谓的策略,就是在操作系统内做出某种决定的算法。不如说,现在有 A, B, C, D 四个将要运行的进程,操作系统该运行哪个进程呢?于是操作系统中的调度策略会做出决定。

    进程

    好啦,前面的一段文字简单地提了一下虚拟化CPU,本系列接下来的几篇文章将会围绕这个主题展开。先不要着急,我们把最基础的进程弄明白。

    所谓的进程,可以看作是操作系统为正在运行的程序提供的一个抽象。既然是运行的程序,我们就应该可以通过某种方式知道该程序在运行的过程中做了什么事情:比如,访问或者影响了系统中的那些部分等。是的,我们只要研究好这个进程就可以了。在进程中很重要的一部分是内存。CPU执行的指令存放在内存中,程序操作(读取和写入)的数据也在内存中。于是一个重要的概念是弄明白进程可以访问那些内存,也就是我们常说的地址空间(address space)。(关于地址空间的分析将会放在后续的虚拟化内存中讨论,欢迎持续关注哈)。另一个重要的部分是寄存器。许多的指令要读取或者更新寄存器。其中有一些特殊的寄存器,如:程序计数器(Program Counter, PC) ,可以告诉我们当前程序正在执行那个指令。类似的,还有栈指针和相关的帧指针,用于存放函数参数栈、局部变量和返回地址等。最后,进程会访问一些持久存储设备,这类的I/O信息会保存在打开的文件列表中。

    创建进程

    那么,程序是如何变成进程的呢?从操作系统层面来分析,操作系统是如何启动并运行一个程序的呢?进程是如何创建的呢?首先,程序在被执行之前是以某种可执行文件存放在磁盘上,于是,操作系统要做的第一步是:将代码和所有的静态数据从磁盘加载到进程的地址空间中(如图中的红色虚线所示)。在早期的操作系统中,加载过程是在运行程序之前完成的;现代操作系统中,在程序执行期间需要用到的代码或数据片段才会加载,不会一次性全部加载完。(为啥要这么做呢?这涉及到虚拟化内存的知识,需要了解分页和交换机制。本系列会慢慢分析的,先记一个TODO 哈哈)。

    图1 从程序到进程

    将代码和静态数据加载到内存(该进程的地址空间)后,操作系统还要为该程序分配运行时栈。在学习C语言时,我们知道使用来存放局部变量、函数参数和返回地址。操作系统会分配这些内存供内存使用。操作系统也可能为程序的堆分配一些内存。一般用于显示请求动态分配的内存。例如,C语言中程序通过调用malloc()来请求分配堆内存,可以调用free()来明确地释放某个堆内存。操作系统还将执行一些初始化任务,特别是与I/O相关的的任务。默认情况下,每个进程都有三个打开的文件描述符,分别用于标准输入、标准输出和错误输出。

    通过将代码和静态数据加载到内存中,然后创建和初始化栈以及执行与I/O相关的工作,现在终于为程序搭建好了舞台,等待程序"跑"起来了。最后一项任务是:启动程序,在入口处运行。通过跳转到main()函数,操作系统将CPU的控制权交到新创建的进程中,从而程序开始执行。

    进程的运行状态

    进程的运行与操作系统中的调度策略紧密相关(这又是一个庞大的话题,暂时先不说了啊,记个TODO haha)。先聊下进程在运行中的状态转换吧,如下图所示。简而言之,进程在运行的过程可能处于以下三种状态之一:

    • 运行(running): 表示进程处于运行状态,正在处理器上运行,也意味着正在执行代码中的指令。
    • 就绪(ready): 表示进程已经做好了运行的准备。由于某种原因,操作系统还没有运行该进程。
    • 阻塞(blocked): 表示进程处于阻塞状态,该进程执行了某种操作,直到发生某种事件后,才会准备运行。比如说,该进程向磁盘发起了I/O请求,然后一直在等待I/O完成。此时该进程会被阻塞,状态变成阻塞,操作系统会调度某个就绪进程去执行。当I/O完成后,该进程的状态会变成就绪状态,等待被调度执行。
    图2 进程的状态转换

    好啦,今天就先到聊到这里吧,博客里的文章会第一时间首发在wx号中,可以微信搜索【小鲸鱼的思绪】。喜欢用手机查看的朋友可以关注下哈。(我就习惯睡觉前刷刷手机看看文章,蛮方便的)。我是一名后端开发工程师,工作内容:Kubernetes/Docker容器云平台建设,欢迎交流。

    相关文章

      网友评论

          本文标题:操作系统中最基本的抽象:进程

          本文链接:https://www.haomeiwen.com/subject/yjsldhtx.html