目录
一、冯诺依曼体系结构
二、操作系统
1.概念
2.理解操作系统的管理
硬件和管理
为什么要有操作系统
三、进程的概念
PCB:
进程的删除和子进程的创建
删除
创建子进程
四、进程的状态
七种状态:
实验查看部分状态:
R:
S:
D:
T/t:
Z/X:
运行状态
阻塞状态和阻塞挂起状态
一、冯诺依曼体系结构
我们在使用计算机本质上都是对数据进行处理,将数据输入到计算机,计算机在输出到一些设备上让我们看到。计算机在处理这些数据上都基本遵循着冯诺依曼体系。
输入设备:键盘、鼠标、磁盘、网卡、话筒...
存储器:内存
中央处理器:cpu
输出设备:显示器、声卡、磁盘、网卡...
这个过程就是对数据的流动加工,先将数据输入到输入设备中,输入设备再拷贝到存储器上,存储器将数据给运算器来进行基本运算处理数据,再把加工后的数据返给存储器,由存储器给输出设备。整个过程都是由控制器来控制的。我们都知道cpu是非常快的,但它容量很小,它也是计算机最重要的部分。
‘
上图总结一下就是距离cpu越近,成本越高,容量越小。
存储器的作用:
体系中存储器起到的作用就像一个巨大的缓存。如果没有它,处理数据时就是外设(输入输出设备)把数据给cpu,但外设的速度又是特别慢的,cpu是很快的,这就导致数据的流动是一点一点流,cpu一点一点的处理数据,整体效率就很低。有了存储器,内存就会先将这些数据存起来,等达到一定要求时再给cpu让其运算,返给内存后,等到输出设备需要时再给它。这时的运算效率就成了内存到cpu,比外设到cpu快很多。
如果我们有钱也可以将内存和硬盘都换成cpu,不过那样的电脑就要几百万,所以内存和硬盘的存在也是计算机价格低的原因。
体会冯诺依曼体系的运行方式:
1.程序的运行
一个程序包含代码和数据,我们要通过代码运算数据就需要cpu来实现,这期间程序是一直在内存上的,我们使用输入设备将数据给内存上的程序在由cpu加工。这也像我们写代码一样,开辟的变量都是在内存上,最后通过内存由cpu去执行代码对数据进行加工得出结果,再由内存返给显示器让我们看到。
2.网上聊天
假如你和一个朋友在使用qq聊天,你给他发“在吗”,可能为了保证数据的安全需要对数据加密,就要cpu的处理,从输入设备输入到内存,cpu在处理数据。使用qq需要网络,输出设备就必须能和网路建立联系,所以要将数据流到网卡上,中间还有网路的处理这里先不管。朋友的电脑就是从输入设备(网卡)上拿到加密的“在吗”,将数据给内存,再到cpu解密返给内存,内存在给到输出设备(显示器)让朋友看到。
二、操作系统
1.概念
操作系统是一款软件,对软硬件资源管理的软件。在电脑开机时,等待一段时间显示器才会显示内容,这段时间就是在等待操作系统的启动。
广义认识:操作系统内核和操作系统外壳周边程序(对操作系统的使用方法)
狭义认识:操作系统内核。
2.理解操作系统的管理
操作系统结构示意图(层状结构):
硬件和管理
先来了解一下对硬件的管理,硬件都是有自己的属性,属性是用来描述硬件的状态及一些相关信息,就像写的通讯录存储人的信息,不过只会存储需要的信息,比如姓名、年龄、电话等等。
拿鼠标来举例,我们在更换鼠标的时,链接上新鼠标,右下角就会提示正在安装驱动程序,过一段时间新鼠标才可以使用。因为不同的鼠标都有不同的厂商,属性是略有不同。操作系统不可能有所有鼠标的属性数据,所以这些硬件的数据就要有厂商来提供接口,让操作系统拿到数据并可以修改数据。这些接口就是驱动程序,操作系统使用驱动管理来对接驱动程序对硬件进行管理。
linux操作系统是使用c来写的,对硬件属性的描述,就可以使用struct来封装。硬件的属性有相同的地方,如厂商、电流大小等,不同的有状态以及功能,只用有相同处就可以使用数据结构进行管理,假如这里是用list对硬件进行链接,那我们对硬件进行管理就是对list进行增删查改。这种方式同样的适合其他的管理,例如文件管理,我们对目录下的增删查改。所以操作系统内是有着大量的数据结构进行管理工作的。
总结一下:管理某些东西我们要先描述他们的属性,在将它们放到一起组织起来(例如数据结构)。管理 == 先描述,再组织
为什么要有操作系统
再回到鼠标,我们现在准备完cs2,但感觉鼠标太快了,查看鼠标属性发现DPI是1200。现在要将DPI改成800,我们只需要将1200的选项改为800,操作系统就会对鼠标进行修改。这个过程中操作系统和鼠标的关系是管理者和被管理者,我们和操作系统的关系是甲方和乙方。我们作为甲方是不懂如何管理被管理者,所以需要一个人(操作系统)帮我们管理被管理者,我们就只需要拿到被管理者的属性,想修改才什么告诉乙方就行。
可是乙方也知道我们不懂管理,我们要是随便修改数据,乙方后面还怎么管理?数据已经乱了,此时乙方拿了份合同让我们签了,上面有对我们的限制,不让我们直接去操作系统修改数据,必须使用操作系统的调用接口才能让安排它做事。
这个系统调用接口就是操作系统给的一些函数,让我们使用函数来使用操作系统。这对于会c语言和了解系统的人还能使用,可那些不会的人就不能了,所以就有了用户部分。用户操作系统提供了shell外壳、lib(c/c++标准库)和部分指令,用户提供了指令操作mkdir、touch、ll等,还有我们熟悉的window的图形化界面。
总结一下:我们现在在来看操作系统,它在计算机中有着承上启下的作用,向下对软硬件资源进行管理(手段),向上为用户提供了一个良好(稳定的,安全的,高效的)的运行环境(目的)。
三、进程的概念
我们在使用window打开浏览器、微信等软件,其实就是在执行一个exe的可执行文件,执行文件时就会创建一个进程来控制程序的运行。
当我们结束到文件所对应的进程,执行的文件也将会不在执行。
在Linux中也是同理,当我们运行一个可执行程序,它就会生成进程。
ps -axj:查看进程
ps -ajx查看的进程过多,可以使用 | grep [文件] 搭配查看。
上面可以看到./test.exe确实在进程里,下面的test.exe是grap的进程,之前说过指令本身也是可执行文件,只要是可执行文件在执行时都会有进程。
刚刚也看到了进程是可以同时存在多个的,操作系统自然要对进程进行管理(先描述,再组织),那是在什么时候创建的进程呢?
PCB:
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合,这个集合就是PCB。课本上称之为PCB(process control block)。在Linux中PCB被称为tesk_struct,它会被装载到RAM(内存)里并且包含着进程的信息.
我们上面已近了解了冯诺依曼体系,根据这个体系,要想运行程序,就要先将程序的数据和代码加载到内存当中再由,内存交给cpu让其计算。进程显然就是对这些程序的数据和代码进行处理,在它们加载到内存后,操作系统就要对它们进行管理,为每个程序创建一个PCB的结构体来描述,里面放着程序的所有属性,指向对应的代码和数据的指针,以及存放下一个进行的指针(假如用list来组织)。这时想让cpu运算哪个程序将它的PCB给cpu就可以了。
操作系统也是一款软件,它也要遵循冯诺一曼体系,电脑启动时就会加载到内存中,不过它是没有进程的。
总结一下:进程 == PCB+自己的代码和数据。Linux下就是tesk_struct+自己的代码和数据。
test_struct内容分类:
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/ O状态信息: 包括显示的I/O请求,分配给进程的I/ O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
进程的删除和子进程的创建
删除
写一个死循环
Ctrl+c:在用户层面终止程序
kill -9 [pid]
pid就是标识符,是tesk_struct的一员用来描述本进程的唯一标示符,区别其他进程。在ps -ajx下可以看到。
运行程序,查看他的pid,ppid是他的父进程,每个进程都是由他的父进程创建的。
kill -9 22358,删除进程。
创建子进程
fork:创建一个子进程,当前程序是子进程就返回0,是父进程就返回子进程的pid.
getpid:查看当前进程的pid
getppid:查看当前进程的父进程pid
代码:
执行结果:
这个fork创建子进程会继承父进程从fork开始下面的所有代码,从上面的代码就可以看出,因为id值的不一样父子进程分别执行了不同结果的数据。
总结一下:fork创建的子进程是有自己的tesk_struct的(他有自己的pid),代码上是继承父进程fork以下包括fork那行的所有代码,数据上因为fork的返回值不同所以和父进程输出的结果是不一样的。
四、进程的状态
七种状态:
- R:运行状态
- S:浅度睡眠状态
- D:深度睡眠状态
- T/t:暂停状态
- Z:退出状态
- X:死亡状态
实验查看部分状态:
R:
代码:
查看运行test.c时进程的信息:
while :; do ps ajx | head -1 && ps ajx | grep test.exe | grep -v grep; sleep 1; done
命令行执行上面的命令可以循环查看程序的进程,这里查看的是test.exe,修改test.exe就可以查看其他程序进程。
运行进程后cpu执行代码,它进入循环后一直在执行while,进程的PCB一直在cpu的队列中(每个cpu都有一个自己的队列,来让进程轮流调用cpu),这时这个进程就是R状态。+代表是前台运行,没有则是后台。
S:
代码:
运行时进程的信息:
这段代码使用了printf,cpu在执行printf时需要调用显示器的资源,进程在等待外设资源就绪时就是S状态。显示器是外设,外设的速度是比cpu慢许多的,所以在这进程运行的大部分时间都是在调用显示器资源,R状态只会停留很短的时间。
D:
D状态的场景是很难造的,这里就简单说明一下。
场景:一个进程在给磁盘写入1G的数据,这时操作系统发现内存严重不足,Linux操作系统有权杀掉进程来释放空间,它就将这个正在等待磁盘写入数据的进程杀掉了。磁盘写着写着数据发现进程没了,磁盘就不知道要怎么处理这些数据,可其他进程也要调用磁盘,磁盘就不会处理这些未写完的数据,这就造成了数据的丢失还暂用了空间。
操作系统为了解决这个问题就创建了D状态(深度睡眠,不可被系统杀掉),解除D状态只能等进程自己醒来(数据传完)或重启/断电。
T/t:
让进程暂停需要kill命令,kill是一个向进程发送信号的命令。
kill -l:查看所有信号
kill -19:暂停进程
kill -18:解除暂停
解除暂停后进程会成为后台进程,只能kill -9杀掉进程,Ctrl+c无法杀掉。
Z/X:
代码:
运行后杀掉子进程:
杀掉子进程发现子进程依旧存在,进入了Z状态。进程在被杀掉后会先清楚掉程序在内存中的代码和数据,test_struct不会被删除,它会生成退出信息等待父进程来读取,读取后再进入X状态清楚这个程序的test_struct。这个过程就像你让一个朋友帮你做了件事,他在做完后会告知你一声这件事完成的状态。
上图,杀掉子进程后,因为父进程在运行没有去读取子进程的退出信息,子进程就成了Z状态,这个进程也被称为僵尸进程。
僵尸进程的危害:当父进程死亡后,子进程变为僵尸进程就可能会一直存在,导致内存泄漏,因为没有父进程读取它。OS(操作系统)为了解决这个问题,就将没有父进程的子进程(也称孤儿进程)让1号进程(OS本身)领养。
在操作系统内部还细分了很多种状态:
这里只介绍运行、阻塞和阻塞挂起状态。
运行状态
运行状态就是R,只要进程进入cpu的队列中就是运行状态。上面查看S状态使用了while写了死循环,但cpu在跑进程时不可能因为遇到死循环就只跑这一个进程,所以这里的问题就是cpu是如何进行多个进程的同时调用?
时间片:进程的PCB内都有自己的调度信息,里面有个时间片,cpu会根据时间片让进程使用多长时间,时间到了重新进入queue排队。
但进程重新进入queue去调用cpu不可能让cpu从头再开始运行代码。这时就需要寄存器了,每个cpu内都有寄存器,用来存储进程调用cpu的状态,进程需要重新进入queue时,会保留上一次进入cpu结束时寄存器的内容,也就达到再次使用CPU可以延续上次使用状态的效果。(这是OS教材调度算法的一种,Linux不是这样调度的)。
CPU寄存器的信息也程为上下文数据,在进程的切换时最重要的就是保护上下文数据。要注意的是寄存器 != 寄存器内容,寄存器是硬件,寄存器内容是数据。
阻塞状态和阻塞挂起状态
阻塞:S和D状态都是等待外设的资源就绪,外设内也有自己的queue来让进程调用,进程等待外设就会将自己的task_struct放到外设的queue内,这时进程就是阻塞状态,调用完后外设再回到cpu的queue内变成运行状态。
挂起:磁盘在内有一块swap分区,当内存特别吃紧时,有的进程是阻塞状态,OS就会将这个进程的代码和数据放到分区中,结束阻塞状态时会将数据和代码拿回内存。代码和数据在分区时就是挂起状态,不过挂起通常是伴随阻塞状态一起的,被称为阻塞挂起状态。