温州做网站报价,门户网站前台页面,网络营销培训学院,网页做什么主题好目录
预备知识
冯诺依曼和现代计算机结构
操作系统的理解
进程和PCB的概念
PCB中的信息
查看进程信息的指令 - ps
pid
进程状态 预备知识
在学习操作系统之前我们需要先了解一下如下的预备知识。
冯诺依曼和现代计算机结构
美籍匈牙利科学家冯诺依曼最先提出“程序存…目录
预备知识
冯诺依曼和现代计算机结构
操作系统的理解
进程和PCB的概念
PCB中的信息
查看进程信息的指令 - ps
pid
进程状态 预备知识
在学习操作系统之前我们需要先了解一下如下的预备知识。
冯诺依曼和现代计算机结构
美籍匈牙利科学家冯·诺依曼最先提出“程序存储”的思想并成功将其运用在计算机的设计之中根据这一原理制造的计算机被称为冯·诺依曼结构计算机。由于他对现代计算机技术的突出贡献因此冯·诺依曼又被称为“现代计算机之父”。程序存储是指指令以代码的形式事先输入到计算机的主存储器中然后按其在存储器中的首地址执行程序的第一条指令以后就按该程序的规定顺序执行其他指令直至程序执行结束。结构示意图如下 早期的冯诺依曼模型是以运算器为核心由输入设备将数据和相关的指令传递给运算器之后先将数据放到存储器然后运算器将数据处理完成之后再传递给输出设备。而这整个过程的协调和分配就是由控制器来控制的。 但是这样会有一个很严重的缺陷就是运算器的速率会被输入输出设备大大的影响。在微处理器问世之前运算器与控制器分离。而且存储器容量小因此设计成以运算器为中心的结构其他部件都通过运算器完成信息的传递也就是上图的样子。 而随着微电子技术的进步同时计算机需要处理的信息也越来越多大量I/O设备的速度和CPU的速度差距悬殊因此需要更新换代计算机的组织结构以适应新的需求。计算机发展为了以存储器为中心使I/O设备尽可能的绕过CPU直接在I/O设备与存储器之间完成操作以提高整体效率。由此便产生了现代主流的计算机结构结构示意图如下
可以看到当代的计算机结构是以存储器为核心的输入设备将信息传递给存储器然后由存储器将信息传递给运算器运算器处理完数据之后再传回存储器最后由存储器将数据传递到输出设备。其中控制器和运算器共同组成我们现在耳熟能详的CPU。 可以看到现代的计算机结构是以存储器为核心的这样就实现了运算器只与存储器之间进行交互大大提高了运算器的效率。
我们可以把上面的结构抽象为下面的简化图这样CPU和主存储器也就是我们的内存就组成了我们专业术语上的主机而其它的任何输入输出设备都叫做外设。 其次我们还需要介绍一下存储相关的知识的首先是存储设备的层级结构 下面的内容选自《深入理解计算机系统第三版》 实际上每个计算机系统中的存储设备都被组织成了一个存储器层次结构如图下图所示。在这个层次结构中从上至下设备的访问速度越来越慢、容量越来越大、离CPU越来越远并且每字节的造价也越来越便宜。寄存器文件在层次结构中位于最顶部也就是第0级或记为L0。这里我们展示的是三层高速缓存L1到L3占据存储器层次结构的第1层到第3层。主存在第4层以此类推。 特别的主存储器主要由RAM和ROM组成而RAM主要有DRAM和SRAM两种。其中内存条就是DRAM集成的也就是俗称的运行内存。而像SRAM、CACHE等高速缓存和寄存器一般是被集成在CPU中的。至于ROM一般被嵌入在主板中存储了BIOS等一些很重要的程序。关于RAM和ROM的一些区别感兴趣的可以参考这篇文章RAM和ROM的区别转 - 知乎 (zhihu.com)
操作系统的理解
我曾在 Linux初探 - 概念上的理解和常见指令的使用 博客中简单介绍了一下操作系统的概念这里我们将细说一下操作系统的上下级也就是操作系统所处的层级结构。 可以看到操作系统是处于一个“承上启下”的位置用于处理用户和硬件程序之间的交互功能。
首先操作系统的本质其实就是一款软件它是我们的计算机启动时第一个启动的软件。如果细究的话操作系统其实是由主板上ROM存储中的或是其它硬件中的一些程序启动的。感兴趣的话可以试着阅读这篇文章操作系统是如何启动起来的 - 知乎 (zhihu.com)
由于操作系统的本质其实就是一款很大的软件所以是无法直接在操作系统上操作键盘、鼠标、音响甚至是主存储器和CPU等硬件的。那么为了实现硬件与操作系统这款软件之间的交互操作于是便有了”驱动程序“这个中介驱动程序将硬件的信息的方法等组织封装起来然后打包提供给操作系统这些一般是嵌入式的活。正是由于驱动程序的介入使得操作系统可以像处理文件那样与我们的硬件进行交互同时这也应证了”Linux下一切皆文件“的这句经典名言。通常情况下很多人认为主存储器和CPU与操作系统之间没有驱动实际上并不是没有驱动而是由于安全和便捷等因素的考虑一般把CPU和主存储器的驱动程序内嵌在主板和操作系统中这也就是为什么有些CPU或内存条在某些主板上不适配的原因之一。所以有了驱动程序这个中介操作系统就可以按照我们的需求与硬件之间进行交互了。
那么操作系统又是怎么和用户或者应用软件之间进行交互的呢这里就不得不提一下“系统调用”了。虽然操作系统能够以它能理解的方式操作硬件了但是为了防止用户的一些危险操作并且使得用户/程序员能够以一种方便、易于理解的方式来操作这些硬件设备最终操作系统会提供给我们一系列的操作接口这些接口就是所谓的“系统调用”。系统调用给了上层程序一个清晰的接口隐藏了内核的复杂结构这些接口使得我们能够以一种简单并且安全的方式访问我们的硬件程序。特别的我们没有能够与CPU之间进行交互的系统调用只有与内存之间进行交互的系统调用这是因为CPU是严格按照一些既定的规则来处理日常的这些指令、操作的些指令基本上都来源于内存或者寄存器所以我们根本就不能而且完全没必要与CPU之间进行交互。可以理解为CPU就是一个计算机的大脑他有自己的想法不需要我们去教它做事。
不过由于系统调用非常的基础所以有时使用起来也是很繁杂的比如一个简单的给变量分配内存空间的操作就需要动用多个系统调用。所以在系统调用的基础上又有了库函数和外壳程序等工具。操作系统会将一些编程中常用操作所对应的系统调用封装成对应的库函数以供开发者的使用这大大的提高了编程效率和学习成本比如C语言中的malloc函数看似只是一个函数实际上却调用了大量的系统调用的接口。(实际上一个操作系统只要称得上是Linux系统必须要拥有一些库函数比如ISO C标准库POSIX标准库等)。至于外壳程序shell如果我们在使用操作系统的时候每执行一个简单的操作都需要我们手动的去调用大量的系统调用的话那么即使是一些很专业的开发者也会不堪重负。因此便有了Windows下的图形化界面和Linux下像bash这样的命令行解释器每当我们在Windows下点击鼠标或者是在Linux下输入以下指令其背后都是调用了大量的系统调用的。其实shell外壳程序可以看作是一种特殊的软件它为我们封装了大量的系统调用为我们提供了一种高效、稳定、安全的操作环境。
我们日常生活中说的操作系统是包含了系统内核、系统调用、shell外壳这些内容的。而严格意义上讲操作系统的内核不包含所谓的系统接口、库函数等内容如下图所示。也就是说我们日常在Windows下看到的那个图形化界面并不属于操作系统的内核他只是一种套在系统内核上让用户可以快速上手的外壳程序。正是因为有了这个图形化界面于是便有了一系列的可以运行在这图形化界面环境下的应用程序诸如我们日常使用的QQ、bilibili、Steam等。我们可以在外壳程序的基础之上快速上手使用这些应用程序而不是打开一个软件之前还需要我们手动的去调用大量系统接口。 至此我们知道了操作系统对下是通过一系列的驱动程序来调度硬件设备的对上提供一系列安全、稳定的系统调用。人们又将这些系统调用封装为一些库函数或者便于使用的shell外壳而在外壳程序之上又衍生了一大批的应用程序。而操作系统的一个很重要的任务就是管理和调度这些上下层以及操作系统本身的软硬件资源。我们人为地将操作系统的管理功能进行分类其中较为常见的有内存管理、进程管理、文件管理和驱动管理这么几类。下面我就就先针对进程管理进行介绍。
进程和PCB的概念
可执行程序的运行首先是需要加载或者说是拷贝到内存中的然后由CPU去读取和处理内存中对应区域的二进制指令。广义上讲进程就是可执行程序加载到内存。但严格意义上讲并没有这么简单一个简单的描述是进程可执行程序PCB。那么何为PCB呢下面是一段对PCB的介绍摘自百度百科 为了描述控制进程的运行系统中存放进程的管理和控制信息的数据结构称为进程控制块PCB Process Control Block它是进程实体的一部分是操作系统中最重要的记录性数据结构。它是进程管理和控制的最重要的数据结构每一个进程均有一个PCB在创建进程时建立PCB伴随进程运行的全过程直到进程撤消而撤消。 那么为什么要引入这个PCB呢简单来说就是为了便于对进程进行统一的管理和描述。因为可执行程序的具体实现五花八门操作系统无法对每一个进程做到做监控而操作系统又需要对进程进行统一的管理于是PCB便诞生了。PCB就相当于是一个信息收集表表中是和进程相关的一些数据如进程标识符、进程状态等信息。这样操作系统就不需要对每一个进程做到实时监控而是只需要通过分析处理进程所对应的PCB中的信息即可例如下图所示。这就像学校为了管理学生的体质情况会进行体测最终的体测结果填入统一的项目表中最终学校只需要分析和处理我们对应的体测表信息就可以了而不是每次想要查看某个同学的体测结果时都要去调监控。 而PCB是进程控制块的一个统称不是具体的一个实例。我们知道Linux是用C语言写的所以在Linux下PCB其实就是一个名为task_struct的结构体。而PCB与task_struct的关系就和shell与bash的关系一样是一类东西的名称和具体实例的关系。
PCB中的信息
查看进程信息的指令 - ps
在Linux下我们通常使用ps指令来获取一个进程的各种信息具体使用细节见ps命令 – 显示进程状态 – Linux命令大全(手册) (linuxcool.com)https://www.linuxcool.com/ps通常使用组合就是 ps ajx 或者ps aux 配合管道用grep检索使用例如
ps aux | grep test # 列出所有进程并筛选含有test条目的信息
pid
进程标识符又名pid、进程号等是当前OS中每个进程唯一的标识符。PID是一个正整数取值范围一般是 2—32768 /proc/sys/kernel/pid_max 文件中的内容就是最大可支持的进程个数。 我们知道Linux是用C语言写的所以在Linux下我们可以在C语言编程时使用对应的getpid这个库函数来获取一个进程的pid。 其中 sys/types.h 是 pid_t 这个类型声明的头文件而getpid()函数是在 unistd.h 头文件中的。getpid函数直接返回一个pid_t类型的当前进程号这个pid_t就是某个整型类型的声明因为这是封装好的库函数所以用法上就和C语言中普通的函数调用一样。 在Linux中内存管理的一系列进程的PCB信息都是统一存放在 ./proc 目录中的例如 其中这些数字形式的目录就是对应的进程而且这些目录的中的内容格式都是一致的例如下面是目录内容的部分截图 值得一提的是cwd和exe这两个链接文件分别指向自己所在的工作目录和具体的路径位置这也就是为什么我们可以采取相对路径的方式输入指令因为指令创建的进程所在的地址是可以通过对应的链接符直接找到的。其中可以使用chdir库函数实现改变工作目录具体用法就不再过多赘述感兴趣的可以自行查阅相关资料。
暂且可以认为这些目录中的信息就是进程所对应的PCB信息。与一般目录不同的是 /proc 目录下的信息内容是动态的当有进程被创建时就会向其中增加对应的目录信息文件名一般就是其对应的PID。而有进程结束时 /proc 目录下就会随之删除对应的信息。
进程状态
想要描述一个进程一个必不可少的信息就是进程状态我们需要知道进程当前是在运行、还是被终止了亦或是其它状态所以PCB中一个必不可少的信息就是进程的状态。那么以Linux为例Linux中对上述描述的具体实现就是 task_struct 结构体中的 state 变量如下图所示task_struct中的第一个就是state。 参考 Linux 2.6.39 版本 而PCB对进程各种状态的描述就是state对应了不同的变量例如 参考 Linux 5.2 版本 也就是说仅从PCB的角度来看待进程的话那么所谓的进程状态其实就是一些提前定义好的数据进程处在哪个状态对应的state变量就切换成对应的值。进程状态切换时PCB中其实就只是修改了state变量的值而已。
那么从操作系统的角度来看进程状态又是怎么一回事呢以操作系统的视角来看进程大致有如下新建、就绪、运行、阻塞、挂起、结束等几个状态。 下面我们就简单的从概念层面上介绍这几个进程状态
新建状态新建状态也常被称作创建状态顾名思义新建状态就是执行进程创建相关的工作。此时操作系统会执行但不限于如下操作为新进程申请一个空白PCB、为新进程分配其运行所需的资源、初始化PCB中的相关信息例如PCB的state变量。就绪状态当进程的创建操作完成之后并不是马上将其PCB放到运行队列中而是先把它放到就绪队列中并修改PCB的state变量等待操作系统的调度分配具体是如何调度的与进程的优先级和系统的调度算法等有关暂时不展开讨论此时进程的状态就是就绪状态。运行状态一般情况下进入到就绪队列中的PCB基本上都会被调度到CPU的运行队列中并修改PCB的state变量此时CPU会逐条执行处于运行队列中PCB所对应可执行程序中的二进制代码。那么此时进程所处的状态就是运行状态。值得注意的是一个CPU只有一个运行队列多个CPU可以有多个运行队列。阻塞状态一般来说如果进程当前要获取的资源软硬件资源没有就绪或者说进程当前无法获取到目标资源。那么此时这个进程的PCB就会被转移到对应资源的等待队列中对应的state变量也会被修改。此时CPU的运行队列就没有了这个进程的PCB那么也就无法去调度执行这个进程。那么此时的进程状态就叫做阻塞状态。挂起状态当系统资源紧张的时候操作系统会对在内存中的资源进行更合理的安排这时就会将将某些优先级不高的进程设为挂起状态并将其移到内存外面一段时间内不对其进行任何操作当条件允许的时候会被操作系统再次调回内存重新进入等待被执行的状态即就绪态。结束状态顾名思义进程执行结束操作系统为其释放对应的系统资源和执行一些清理工作的过程就叫做结束状态。 下面是与进程状态一些相关的内容有点杂乱不想看可以直接跳过
操作系统在管理硬件资源时也有对应的硬件信息队列只是提一嘴不细究这些硬件资源的管理和PCB的运行队列类似虽然实际情况肯定不是这样的但却是以这种形式为基础的所以可以这样描述当硬件资源就绪时进程就可以正常访问、获取进程想要的资源而如果硬件资源没有就绪时那么前来访问资源的进程就会被安排在对应资源的等待队列中当资源就绪时进程获取资源对应的PCB再回到CPU的运行队列就又可以被CPU重新调度。其中每一个硬件都有其对应的等待队列所以并不是只有一个等待队列。所以当我们运行了多个进程这些进程有时可能会访问一些共同的资源时就可能会出现我们感受到的卡顿现象。而且操作系统中存在着各种各样的队列不止CPU的运行队列和各种硬件的等待队列。挂起状态的实例 —— 阻塞挂起阻塞挂起的操作是将内存中的代码和数据先暂存到外存设备的某些特定区域如硬盘的swap区只留一个很小的PCB放在内存中当然也会修改对应的state变量当进程被唤醒时再次重新被加载到内存。这样做肯定会造成速度降低但有时却不得不这样做。例如当所剩的内存资源严重不足时操作系统就不得不暂时挂起一些阻塞队列中的进程来缓解内存的压力。阻塞和挂起的区别参考一文助你理解进程七态及挂起与阻塞 - 掘金 1、挂起是一个行为而阻塞是进程的一种状态。 2、进程的位置不同挂起是将进程移到外存中而处于阻塞状态的进程还是在内存的对应资3、源的等待队列中。 4、产生的原因不同挂起一般是由于可分配资源不足迫使操作系统对一些进程采取挂起操作抑或是用户指定某个进程挂起。而阻塞是进程正在等待某一事件发生一般是等待资源或者响应等而暂时停止运行的状态。 5、挂起是被动的行为进程被迫从内存中移至外存中。而阻塞可以看成是一个主动的行为主动的进入到对应资源的等待队列。 至此我们简单的从概念的层面认识了进程的几种状态那么接下来我们就以Linux为例介绍一下Linux下的一些具体的进程状态。
Rrunning or runnable (on run queue)
Sinterruptible sleep (waiting for an event to complete)
Duninterruptible sleep (usually IO)
Tstopped by job control signal
tstopped by debugger during the tracing
Wpaging (not valid since the 2.6.xx kernel)
Xdead (should never be seen)
Zdefunct (zombie) process, terminated but not - reaped by its parent 内容参考Linux系统之进程状态-腾讯云开发者社区-腾讯云
1、R (TASK_RUNNING)运行或可执行状态
TASK_RUNNING运行态和就绪态的合并表示进程正在运行或准备运行因为CPU的运行速度非常快所以运行状态和就绪状态之间的空隙时间很短所以就统一用 R 状态表示了。 补充很多教科书将正在CPU上执行的进程定义为RUNNING状态、而将可执行但是尚未被调度执行的进程定义为READY状态这两种状态在linux下统一为 TASK_RUNNING状态 2、S (TASK_INTERRUPTIBLE)可中断睡眠状态
TASK_INTERRUPTIBLE此时进程被阻塞当等待的资源到来时即可唤醒。或者也可以通过其他进程信号或时钟中断唤醒进入运行队列。这种睡眠状态是可中断的即可以被kill掉的。 补充通过ps命令会看到一般情况下进程列表中的绝大多数进程都处于 S 状态除非机器的负载很高。毕竟CPU就这么几个进程动辄几十上百个如果不是绝大多数进程都在睡眠CPU又怎么响应得过来。 3、D (TASK_UNINTERRUPTIBLE)不可中断的睡眠状态
与TASK_INTERRUPTIBLE状态类似进程处于睡眠状态但是此刻进程是不可中断的。不可中断也就是说处于这种睡眠状态的进程哪怕是用户强制kill都不能让其强行结束。 补充不可中断睡眠状态存在的意义就在于内核的某些处理流程是不能被打断的。如果响应异步信号程序的执行流程中就会被插入一段用于处理异步信号的流程这个插入的流程可能只存在于内核态也可能延伸到用户态于是原有的流程就被中断了。 例如进程在对某些硬件进行操作时比如进程调用read系统调用对某个设备文件进行读操作而read系统调用最终执行到对应设备驱动的代码并与对应的物理设备进行交互可能需要使用TASK_UNINTERRUPTIBLE状态对进程进行保护以避免进程与设备交互的过程被打断造成设备陷入不可控的状态。这种情况下的TASK_UNINTERRUPTIBLE状态总是非常短暂的通过ps命令基本上不可能捕捉到。 4、T (TASK_STOPPED)暂停状态
TASK_STOPPED进程暂停执行处于挂起状态而不是阻塞状态。 通过向进程发送一个SIGSTOP信号kill -19它就会因响应该信号而进入暂停状态除非该进程本身处于 D 状态而不响应信号。5、t (TASK_TRACED)跟踪状态
TASK_TRACED此时进程被跟踪“正在被跟踪” 指的是进程暂停下来等待跟踪它的进程对它进行操作。比如在gdb调试中对进程打一个断点进程在断点处停下来的时候就处于跟踪状态。而在其他时候进程就还是处于正常情况不是跟踪状态。 补充对于进程本身来说暂停状态和跟踪状态很类似都是表示进程暂停下来。只不过跟踪状态相当于在暂停状态之上多了一层保护处于跟踪状态的进程不能响应SIGCONT信号而被唤醒。只能等到调试进程通过一些系统调用执行对应的操作操作或是调试进程退出被调试的进程才能恢复到其它状态。 6、Z (TASK_DEAD - EXIT_ZOMBIE)退出状态 - 进程成为僵尸进程
进程在退出的过程中是处于TASK_DEAD状态的。在这个退出过程中除了task_struct(PCB)之外操作系统会将进程占有的所有资源进行回收。那么最后进程就只剩下task_struct这个空壳所以被称为僵尸进程。
之所以保留task_struct是因为task_struct里面保存了进程的退出码、以及一些统计信息。而其父进程很可能会关心这些信息。例如父进程可以通过wait系列的系统调用如wait4、waitid等来等待某个或某些子进程的退出并获取它的退出信息而这个退出信息就是被保存在task_struct中的之后wait系列的系统调用会顺便将子进程的尸体task_struct也释放掉。 当父、子进程在不同时间点退出时就可能会出现Z的细分状态僵尸状态和孤儿状态。这个在后面会谈到这里就不展开来说了。 6、X (TASK_DEAD - EXIT_DEAD)死亡状态 - 进程即将被销毁
一个进程即将退出PCB随之也会销毁。所以死亡状态是非常短暂的几乎不可能通过ps命令捕捉到这里仅作为概念了解日常生活中很少遇到。 案例分析当编写如下的C语言的代码并监视对应的进程时会发现大多数情况捕获到的都是S状态。是因为CPU的执行速度非常快而访问硬件资源或是文件等速度相对会很慢例如代码中的printf函数所以实际上处于R状态的时间是微乎其微的。当我们把printf和sleep都注释掉之后再去捕获进程状态就会发现此时都是R状态了。
int main()
{while(1){printf(我是一个进程我的pid是: %d\n, getpid());sleep(1);}return 0;
}内容补充前台进程和后台进程 不难发现有时进程状态后面还会跟一个加号 这表示的是当前的进程是一个前台进程前台进程占用着前台资源所以此时是无法进行输入指令等操作的。而后台进程状态信息之后是没有那个加号的后台进程不影响前台操作所以还是可以进行输入指令等操作的。在Linux下我们只需要在指令后加一个 那么对应的进程就是后台进程。 未完待续……