详解操作系统是如何管理计算机软硬件资源的,以及Linux中进程状态的观察与解释
目录
前言
一、操作系统(Operator System)
1.操作系统是什么
2.设计操作系统的目的是什么
3.操作系统是如何管理计算机的
1)对上:操作系统是如何为用户提供安全可靠的服务
2)对下:操作系统是如何管理软硬件资源
①对软件的管理
进程的概念
进程的属性
②对硬件资源的管理
二、进程状态
1.见一见Linux中的进程
2.程序的pid
什么是父进程,什么是子进程
通过代码演示getpid和getppid的使用方式
3.进程的状态
1)首先为什么会有进程状态这一概念
2)介绍三个基本的进程状态概念
3)介绍Linux中的一些进程状态
R(running)状态观察与解释:
S(Sleepping)状态观察与解释:
T(Topped)状态观察与解释:
后台程序
t(tracing stop)状态观察与解释
Z(Zombie)状态观察与解释
一些难以直接观察的进程状态介绍
D(disk sleep)状态简单介绍:
X(Dead)状态
什么是孤儿进程
总结
前言
现代计算机鼻祖——冯诺依曼体系结构
该结构可大致分为:IO(输入输出设备)、存储器、中央处理器cpu(运算器、控制器)
注意:
①这里的存储器指的是内存;
②在不考虑缓存的情况下,默认cpu只与内存交换数据,各种IO外设也只与内存交互
现在以两个好友a和b之间的微信聊天中数据的流动过程为例,加深对冯诺依曼体系的理解:
发送信息:
1)好友a通过IO设备将信息数据传入内存;
2)内存将数据传入cpu加密打包;
3)cpu再将数据传入内存;
4)内存把的数据传输到网卡发送
接受信息:
1)好友b通过网卡收到数据,并将数据传入内存;
2)内存将数据传入cpu解密;
3)cpu再将信息传入内存;
4)内存把数据再传入显示器(输入输出设备)。
一、操作系统(Operator System)
1.操作系统是什么
①操作系统是计算机上一个最基本程序的集合,是一个负责协调管理整个计算机的软件,他的内部十分复杂却又非常高效——截至目前,Linux内核的源代码总量已突破 4000万行。Windows 11的代码规模可能已经达到 数亿行。
②操作系统处于用户层与硬件层之间,是计算机的核心,灵魂所在。
2.设计操作系统的目的是什么
对上:方便用户使用,为用户提供稳定的、高效的、安全的使用环境。
对下:管理好计算机繁杂的软硬件资源;
3.操作系统是如何管理计算机的
1)对上:操作系统是如何为用户提供安全可靠的服务
操作系统是通过系统预留的接口为用户提供服务的。
举个生活中的栗子:食堂买饭。
①为了保护饮食安全,食堂禁止非内部员工进入,所你只能在一个窗口等着;
②你的所有操作都只能通过那个窗口实现,要什么菜不要什么菜都只能跟打饭阿姨说,你不能自己操作;
③打个饭对买饭人而言是件很简单容易的事情,而食堂要做的事情就多了,如食材的购买、加工、烹饪、定价等等。
再回到问题上:操作系统就是那个食堂,用户就是买饭的人,而那个卖饭窗口也有个专有名词“系统调用接口”——OS通过C语言函数提供的函数调用。
为了保护饮食安全,食堂不允许外人进入。同样为了保证操作系统内的井然有序,操作系统是不允许用户或者外部程序访问操作系统的核心的。
(难以理解保护食堂安全的重要性?那将食堂换成银行再想想)
总结上述:操作系统为了实现保护自身的安全与为用户提供服务这两个要求,从而折中出了为用户提供接口的方式,这个接口有个专业用语叫做“系统调用”。
补充:库是什么?
操作系统的系统调用在使用上比较单一基础,且对用户有一定要求,于是有大佬们对系统调用进行了二次封装,从而形成了库,便于上层用户使用或者基于库的再开发。
2)对下:操作系统是如何管理软硬件资源
包括用户的程序在内,当诸多程序调入内存时,各个程序需要使用的硬件资源又不尽相同,操作系统如何管理?
解决办法是“面向对象”这一管理方法。
①对软件的管理
进程的概念
课本上称呼进程为一个正在执行的程序,由PCB(也被称为进程控制块)、代码和相应数据构成,在Linux系统中这个PCB是:task_struct。
注意:程序本质上是磁盘上存放着的文件,与进程是不同的概念=> 程序 != 进程
进程的属性
①竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高 效完成任务,更合理竞争相关资源,便具有了优先级
②独立性: 多进程运行,需要独享各种资源,多进程运行期间互不干扰
有了上述有关进程的理解后,接下来为方便读者了解操作系统管理软硬件资源的方式,笔者整理抽象出一句话:先整理,再管理。
整理:操作系统通过创建结构体的方式,将每一个调入内存的程序创建一个结构体对象,该结构体中的成员包括——该进程的属性和该程序调入内存中的代码的地址等信息。
管理:而这些结构体对象,被某种数据结构存储管理着,当有新的进程创建、删除或更改时,便利用该数据结构的增删查改等功能实现。
不知道是什么是数据结构?——数据结构(计算机存储、组织数据方式)_百度百科
这里用单链表帮助读者在脑海中构筑起一个简单的操作系统管理进程task_struct(PCB)的模型
整理:通过统计每个进程的属性等信息,为他们创建结构体对象PCB(Linux中称task_struct);
管理:再利用数据结构管理这些结构体对象。
于是,操作系统便从管理繁杂软件变成了管理这些结构体对象。
由于PCB的概念有些难以理解,这里来一个小总结:
②对硬件资源的管理
对于管理硬件,由于硬件资源有限,故操作系统为每个硬件外设创建结构体并使用队列这种数据结构的方式来管理到来的进程。
请看下方例子加以理解:
当多个程序从硬盘调入内存并创建PCB后,便进入到了cpu的队列当中排队(通过queue中的head指针一个个连起来,同时又通过task_cpu时刻监视着cpu的资源情况),当轮到某进程后,操作系统便通过他的PCB执行相关代码,如果执行过程中该进程又需要访问A硬件,那么操作系统又会将它放入A硬件的队列中排队,当该进程得到A的使用权后就会被立马调回cpu队列继续执行之后的代码。(操作系统怎么会记得上次执行到哪?PCB中有记录的)
注意:进入硬件的队列中排队的是进程的PCB!不是他们的可执行代码!在PCB中有这些可执行代码的地址!
笔者这里画了一个虚拟的操作系统管理诸多进程的假想图以供读者参考
如上,计算机的软硬件资源便被有条不紊的管理起来,而接下来的进程状态,便是建立在这种管理机制下某一时刻进程的状态。
二、进程状态
1.见一见Linux中的进程
笔者创建了一个简单myproc.c的C语言程序,以方便读者观察进程在linux环境下的存在方式。
为方便读者观察,笔者再将第一行的表头打出:
为什么会有两个进程被查询到?
答:除了我们编写的myproc在执行外,由于指令其本身也是一种可执行程序,故此时有myproc进程和grep进程同时打印出来。
注意:程序需一直处于执行的状态,上述观察进程的指令才会生效,这也笔者使用while循环的原因!(CTRL + C可以终止程序)
这里再补充一个终止进程的方法:kill -9 +进程id
2.程序的pid
观察上图
1)一旦进程被创建,操作系统会为其自动生成一个ID以便于管理该进程(这个id也在task_struct中存储着)
2)getpid:系统调用接口,作用是查看该进程的ID,无参数,返回值为该进程的id,使用需要包含两个头文件:"unistd.h"
3)getppid:说明如getpid,但返回的是该进程的父进程,一般bash。
什么是父进程,什么是子进程
父进程是创建新进程的原始进程。当一个进程决定通过系统调用生成一个新进程时,它就成为这个新进程的父进程。
子进程是由父进程通过复制自身的PCB以及代码和数据创建的进程。它是父进程的一个副本或衍生。
在Linux中的的初始进程是 init
(或 systemd
),所有其他进程都直接或间接由它派生,而进程又可以派生子进程,最后形成树状结构。
而当我们在命令行运行指令的时候父进程是 Shell(如 Bash),而非操作系统内核。
补充:系统调用与编程中的函数调用的区别:
在Linux中的系统调用并不是直接以函数的形式给开发者使用的,而是通过C标准库的封装函数提供给用户使用的,与C++类中成员函数的概念有些相似,但与类在底层机制和权限层上不同。
通过代码演示getpid和getppid的使用方式
在Linux“万物皆文件的”设计哲学思想下,我们也可以看到进程运行中所创建的文件的。
这里补充另一种可见到进程文件的方法:
这些蓝色的数字就是系统中正在运行的进程,还可通过ls / proc / 你要查询进程的id
一旦你将程序终止运行,这个目录下就没有你的进程id文件了。
3.进程的状态
1)首先为什么会有进程状态这一概念
设置进程状态的目的,一是为了更方便管理进程;二是以满足进程在不同的场景运行。
2)介绍三个基本的进程状态概念
运行、阻塞、挂起三状态。
值得注意的是,这三种状态其实是一种精准的抽象,实际各不同的操作系统中的进程名或有不同,但他所描述的该进程在某时刻所处的状态所表示的含义是不变的。
比如:运行状态在Linux中就被称为R(running)状态
--------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------
注意:
①运行状态是只要进程的PCB进入了cpu的运行队列中,那该进程就处于运行状态,而不是只有在真正的到cpu资源的时候才叫做运行状态。
②所谓不同的进程状态,本质是进程在不同硬件的等待队列中。
③在理解了三种基本状态后便可明白所谓“阻塞不一定挂起,但挂起一定阻塞”的含义。
3)介绍Linux中的一些进程状态
在Linux中,进程状态主要有运行(R)、可中断睡眠(S)、停止(T)、跟着(t)、僵尸(Z),不可中断睡眠(D)、死亡(X)。
R(running)状态观察与解释:
笔者将myproc.c程序稍做修改,如上图,再通过指令观察stat列:R状态,可以发现上述笔者框住的部分真是此时myproc的进程状态R,不过后面跟了个‘+’,下文会见到解释。
很明显,Linux系统下的R状态就是上文提到的三种基本状态之一的运行状态。
S(Sleepping)状态观察与解释:
这里程序分明是运行状态,可为什么系统显示该进程处于S睡眠状态?
这里是站在cpu的视角去看的,cpu运行速度太快了,显示器即IO外设打印跟不上cpu的处理速度,所以大部分时间cpu处于睡眠状态,以等待外设响应。
S状态,便是Linux环境下的的阻塞状态。
T(Topped)状态观察与解释:
想要观察到T(Top)状态需要使用一个指令——kill -19 pid,
当使用kill指令后发现打印立即停止,此时再查看进程状态此时就变成了T状态
如何回复呢?再通过kill -18 即可回复
在 Linux,T(Stopped)状态是一种特殊的暂停状态,既不属于传统意义上的阻塞态,也不等同于挂起状态。其本质是进程被 主动暂停执行,但仍在内存中等待恢复信号。
可以发现时该程序继续打印,通过观察进程状态发现与之前不同的是‘+’没有了!
后台程序
那么一个进程的状态带不带‘+’有什么区别呢?
带 +:即意味着该程序是Linux目前的前台程序,此时命令行输入指令系统不会有反应,但可以被CTRL+C终止掉;
不带+:即意味着该程序是Linux目前的后台程序,虽然此时输入指令系统会响应,但后台依旧执行着该程序,且该程序无法被CTRL+C终止,如下图所示:
那此时的程序一个如何终止呢?kill - 9 杀掉它!
小结:
t(tracing stop)状态观察与解释
需要指出的是t(tracing stop)与上述提到的T(Topping)并不是同一种状态,小t状态表示进程因调试而暂停,比如在断点处停止。
通过gdb调试,我们可以观察到t状态:
在Linux系统中,大写T与小写t表示的进程状态不同,这是值得注意的。
若对gdb,及其调试有疑惑的读者可查看笔者的另一文章:Linux常见工具如yum、vim、gcc、gdb的基本使用,以及编译过程和动静态链接的区别-CSDN博客
Z(Zombie)状态观察与解释
Zombie,意为僵尸,该进程状态也常被称为僵尸状态。
为了观察Z状态这里需要引入另一个系统调用:fork()调用,在头文件“unistd.h”中,无参数,其作用是创建子进程,会返回两个返回值——若创建子进程成功,为父进程返回子进程的id,为子进程返回0。fork()调用之后的代码父进程与子进程共用。
笔者第一次见到fork()调用的时相当震惊,因为这是笔者第一次接触到有两个返回值的“函数”调用!!具体原理需要结合“进程中的虚拟地址”解释,这里读者暂时知道fork调用会有两个返回值即可。
若对进程地址空间以及虚拟地址感兴趣的朋友可以翻看笔者另一篇文章:Linux环境变量的作用以及虚拟地址原理-CSDN博客
1 #include<stdio.h>2 #include<stdlib.h>3 #include<unistd.h>4 5 int main()6 {7 pid_t id =fork();8 9 if(id<0)10 {11 printf("创建子进程失败!\n");12 exit(1);13 }14 else if (id > 0)15 {16 //父进程17 while(1)18 {19 printf("我是父进程,我的id是:%d,我的父进程id是:%d\n",getpid(),getppid());20 sleep(1);21 }22 }23 else 24 { 25 //子进程26 printf("我是子进程,我的id是:%d,我的父进程id是:%d\n",getpid(),getppid());27 }28 return 0;29 }
代码解释:这里pid_t 数据类型可以理解为是int的封装,在第7行创建了子进程后,id变量会有两个值(是的,你没看错,具体原理也需要结合“进程的虚拟地址讲解”),大于0的会进入父进程中的一个循环——每隔1s打印一次pid和ppid,小于0的进入子进程后打印一次pid和ppid后结束。
一般情况下,当进程结束后会释放其所占据的资源,但会保留PCB(task_struct),目的是要等待父进程的检查,此时该进程的PCB中记录进程的执行情况,只有当父进程检查完执行结果后,再由父进程结束掉该子进程的PCB。而等待父进程检查的子进程的进程状态就是Z(僵尸)状态(死了,但没完全死)。
这里举个例子:可以将者父进程想象为一个老板,下面的子进程都是他的员工,当员工完成任务后老板会来检查任务完成的怎么样。
当进程退出后,父进程没有收到到子进程退出的返回值时就会产生僵尸进程,僵尸进程会一直等待父进程读取退出状态代码。 所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态。
注意:
一些难以直接观察的进程状态介绍
D(disk sleep)状态简单介绍:
X(Dead)状态
在 Linux 进程管理中,X(Dead)状态是一个极其短暂且特殊的进程状态,表示进程已完全终止且其资源已被操作系统回收。X 状态是进程生命周期中的最后一个状态,持续时间极短(通常仅几毫秒)。
X状态和Z状态的进程不能被OS终止(杀死),原因是该进程已经死亡。
总结上述不能被OS终止的进程:D、X、Z。
什么是孤儿进程
在上述介绍僵尸进程状态的代码下进行上述更改,让子进程也进入循环,且每2秒打印一次。
查看进程状态:
通过kill指令终止父进程后,父进程变为操作系统:
此时的进程状态:
孤儿进程默认是后台进程。
总结
本篇文章先是介绍了冯诺依曼体系结构;其次介绍了操作系统的概念、目的以及操作系统是如何做到在计算机中承上启下充当核心角色的;最后介绍了进程的状态,包括什么是进程,见识进程在Linux中是什么样的,什么是父子进程,如何理解后台进程,以及介绍了Linux中常见的几种进程状态,最后还介绍了什么是孤儿进程。
笔者水平浅薄,难免有疏忽大意的地方,若有错误还请读者多多指出。
文章读完可否给笔者一个免费的赞呢,一个免费的点赞就能让笔者高兴很久哦。