冯诺依曼体系和操作系统和进程初识
冯诺依曼体系
我们常见的计算机,如笔记本。我们不常见的计算机,如服务器,大部分都遵守冯诺依曼体系。
输入设备:就是键盘,鼠标,扫描仪,摄像头......
输出深:显示器,打印机,音响......
中央处理器(CPU):内含运算器和控制器!
存储器:这里指的是内存!
内存具有掉电易失的特性,就是断点后就会失去数据!
内存想对的是具有永久性存储能力的设备那就叫外存!一般我们普遍认识的就是磁盘!
对于存储器我们可能会认为是外存 + 内存!但是存储器这个概念在这里是指内存!
外存——外部存储器既是输入设备也是输出设备!磁盘是外部设备的一种!**磁盘不属于存储器!**磁盘和网卡都是典型的即使输入也是输出!
下面我们将对 存储器和CPU这两个部分的功能进行详细的解释!
在整个冯诺依曼体系中个部分的速度也是有快有慢!
可以看出来相比CPU和寄存器,外设的速度其实是很慢的!
CPU比外设的速度快了几百万倍!
接下来我们来简单的看一下cpu功能和内部
就像是我们在读书前还得学会识字,cpu也是一样执行代码前也必须先认识代码!我们学习的识字的过程中,也相当于在大脑里面写入指令集!我们是通过这些指令集来读书的!
cpu的数据来源!
可以将内存看成是CPU和磁盘之间的一个巨大的缓存,用来适配CPU和磁盘之间巨大的速度差!
所以这样子其实就变成了CPU在读取数据和写入数据只==和内存(存储器)进行数据交流!==!而不会直接和外设进行数据交流!——==这样子可以更好的提高整机的效率!==
计算机本质是一个硬件和软件的完美结合!
总结
- CPU不和外设直接进行数据交流!只和内存进行数据交流!
- 所有的外设,有数据要进行载入,只能载入到内存中!内存写出,也一定是写到外设中去!
有了以上的结论我们就可以对平常的一些现象进行解释了!
我们都知道程序运行必须加载到内存!那为什么一定要加载到内存中呢?——因为CPU运行程序需要代码!但是CPU不能直接的去读取磁盘中的数据!CPU只会从内存中拿数据!所以我们运行的时候就一定要把数据加载到内存中!
我们接下来看一下数据是如何通过冯诺依曼体系来在两个设备之间进行传输的!
我与我的朋友在qq上进行聊天,我发了一句“你好!”,请问这一句”你好!“在电脑中是如何流动的!?(这里我们不考虑网络的传输,只考虑在冯诺依曼体系中的数据流通!)
操作系统
操作系统是一个软硬件资源==管理==的软件!
为什么要有操作系统?
这就是为什么要存在操作系统!操作系统的存在可以为我们管理好软硬件资源!为我们(上层用户)提供一个良好的执行环境!
什么叫管理?
操作系统是如何对资源进行管理的?这是一个看上去很抽象的概念!
我们可以举一个例子来帮助理解这个抽象的概念!
当我们在上学的时候,我们也是受到管理的一方!而学校最后都是由校长来进行管理的!所以其实我们也是受到校长的管理的!可是其实我们其实很少或者说根本没有跟校长直接的进行接触!校长也不会真的来我们身边详细的询问我们的成绩,指导我们的学习,跟我们说我们该怎么做!
==所以其实管理者根本不需要和被管理者直接的进行交互!也是可以管理被管理者的!==
我们也要明白和我们直接进行交互的人不一定是我们的管理者!像是我们上学时候的班长!班长是我们的管理者吗?不是!班长是监管我们的人!但是班长不是管理我们的人!班长负责将管理的信息向下进行传递!
==管理者必须具备对重大的事宜具有决策权!==
所以老师也不是一个管理者!因为老师不具备重大事情的决策权!老师不能随意的想要将我们开除学籍,更换班级!==老师和班长都是做执行的人!==
那么校长是如何做出决策的?
那校长是如何对我们进行做出决策进行管理的?——答案是==数据==!**校长根据我们的数据来管理我们!**我们的数据上有我们的学习成绩,有我们的个人信息,有我们的实践经历,获奖经历.....有我们的各种信息!像是我们迟到,我们早退,我们作弊这些都会化作数据记录起来!这样子校长就可以通过这些数据来进行管理!进行给学生颁奖!通报学生!劝退学生......各种操作!
所以虽然我们不会直接和校长打交道!但是我们的数据早就被校方拿走了!==而且一直在更新==!
==所以管理的本质就是对数据进行管理!==
数据哪里来?
如果校长要对数据进行管理!那么么数据是哪里来的?管理者不会与被管理者直接的进行接触!**所以必须存在第三者用来获取数据!那就是执行者!**从老师哪里来的!==所以和我们直接进行交互的老师其目的不是为了管理我们而是为了从身上获得必要的数据!且及时的对数据进行更新!==初次之外执行者还有==执行管理者下达命令的职能==
这样子流程就变成了 老师收集学生的数据——学习成绩,课堂表现..... 然后老师将数据给校长,校长做决策来决定给学生的待遇!——发奖学金,处分......然后老师在根据下达的命令来执行相应的行为!
例如校长看到一班的成绩不错,但是出勤率很差!于是告诉老师要提高出勤率!于是老师根据这个决策来开始天天到班级点名!提高学生的出勤率!
接下里我们将上面所说的角色和操作系统进行一一的对应!
我们可以认为操作系统就是校长是管理者!硬件就是学生是被管理者!那么他们中间一定存在一个执行者!那就是驱动层!每一个硬件都存在驱动!操作系统就是通过管理驱动来管理硬件的!也是通过驱动来控制硬件的!
但是这还衍生出来一个问题!学生少的时候还好!一旦学生多了数据量大了!我们应该 怎么处理?数据量一旦大起来!数据的管理也是一种巨大的负担!
所以如何更好的去管理数据呢?校长总不能天天盯着这些excel的数据天天吧?恰好校长是一名程序员!校长发现==学生信息虽然很多!但是种类都是一样的!==可以按种类进行管理,所以校长就通过==对学生的信息进行抽象和提炼==最后定义了一个==结构体==!用来专门管理这些信息!
**最后对于学生的管理便转化成了对这一个个的变量进行管理!**但是万一数据量还是太多了!变量管理起来也不方便!要找也很麻烦!校长作为一个优秀的程序员于是他在结构体里面又加了一个指针!
==这样子对于学生信息的管理就变成了对于链表的管理和维护!==对于学生的管理就变成了对于链表的增删查改!
有新的学生!那就添加一个节点!学生被退学了那就是删除一个节点!学生的信息更新了那就对学生对应的节点进行修改!校长想要找到成绩最好的学生!也只要设计一个遍历的算法对整个链表进行遍历就可以找到了!
==这就是对被管理的对象进行建模的过程!==
管理的两个重要的阶段 ——先描述,在组织!
描述就是将要管理的对象进行抽象变成一个结构体!
组织就是根据结构体设计出来一个特定的数据结构!
这样就将对对象的管理转换为了对某种结构的管理!
==这也是我们操作系统的管理思想!==所有管理的本质逻辑也都是:先描述,在组织!
这也是所有软件的设计思路!
像是我们写一个通讯录!
我们首先就要先构建对应的结构体!——struct person——描述
然后以链表或者数组的形式进行增删查改!——组织
所以我们就可以进一步的去了解操作系统究竟是如何的去管理硬件的了!
其实也是通过描述后组织的方式来管理硬件的!
操作系统就是通过这个链表来管理硬件的!当操作系统遍历链表的时候发现有个设备的status是error 那么就将其从链表移除!
对于软件做管理!
操作系统对于硬件做管理是先描述再组织!那么对于软件呢?自然也是一样的!软件不仅能管理硬件也管理软件!——这也是很好理解的,人不仅可以管理物品,人也能管理人!是一样的!所以接下来我们再举个例子用来说明这一点!也用这个例子来进一步的完善整个计算机体系!
下面这是一个银行!
'
从上面的例子我们可以显然的看出来!这个银行的内部结构和操作系统也具有相似性!而银行还具有承接用户存钱的业务!
那么问题来了!在传统的银行中是否允许用户直接的操作银行的电脑!直接自己存钱?——答案是否定的!一般我们都是将钱给业务人物让他们来替我们存钱!而柜台的前面往往还有很厚的玻璃那么为什么要怎么做?——答案自然是这样子能保护银行!
**银行系统不相信任何人!而且自己很容易受到伤害!银行必须保护自己!**但是银行也承担着为客户服务的责任! ——所以银行既要保护自己,又要对外提供服务!
==而操作系统也是一样!操作系统相信任何人和任何程序!但是也必须给上层的用户提供各种服务!==
**为了保护操作系统不允许任意用户对操作系统内做任意的修改!所以操作系统不允许用户直接访问操作系统里面的各种程序!但是又必须提供服务,所以操作系统提供了一个接口!(就像银行不允许用户直接往仓库存钱一样!操作系统也不允许用户直接往磁盘写入数据!) **
**操作系统接口既可以用来满足用户的请求,又可以拒绝用户的非法请求!**这样子就可以最大程度的保护操作系统!
**系统调用接口是由操作系统来提供的!**在linux下因为linux都是有c语言写成的!所以linux的系统调用接口其实都是c式的接口!
系统调用接口是很重要的接口!从编程的角度上来看实际上我们本质都是在调用系统接口!
所以我们以后对于操作系统的任意访问都必须通过系统调用接口来进行访问!
随着银行的发展,银行来的各种各样的人越来越多!有的人还不识字!来了柜台还没法办事!那么怎么呢?答案是银行也有了一个大厅经理!专门来帮组人来办事!就是说来银行的人甚至都不用去柜台了**!只要把需求告诉大厅经理!大厅经理就拿着你的材料和带着你的需求帮你去柜台办事!**
用系统调用接口要求我们用户对于操作系统要有一定的了解!但是这样也太麻烦了!所以在操作系统之上还多了一个软件层!用来解决这个问题!
例如shell/各种各样的库......这些都是软件层甚至界面也是属于软件层!我们只需要输入各种的指令!软件层就会根据这些指令去调用各种各样的系统接口!
当我们开始编程的第一行代码就是“hello world” ,这个代码的执行就是调用c标准库里面的函数接口!然后c标准库再去调用系统接口!最后由操作系统来完成输出到显示器上的!
上面的所有结合起来就是计算机的软硬件体系结构!就是下面的这张图!
总结
- 管理的本质:对于数据做管理!
- 管理的方法:先描述,再组织!
- 我们对于系统的任意访问都必须经过系统接口!
- 我们的指令和编程的本质也是调用系统接口!
进程初识
基本概念
- 课本概念:程序的一个执行实例,正在执行的程序等
什么叫做运行起来呢?——答案是加载到内存中
但是其实很多人看到这个定义其实很难理解这到底是什么意思!为什么程序加载道内存里面就变成了进程?把程序换个位置就变成了进程?
或许有人看听过另一句话==进程和程序相比就有动态的属性!==
那这句话就又要么理解呢?
我们用c/c++/java.....各种语言编写出来的程序本质上是什么呢?
程序的本质——就是文件!而文件放在磁盘上!
然后呢如果我们要执行这个软件根据冯诺依曼体系我们可以知道文件要放在内存中!
然后CPU就去内存中==直接拿到==这个文件然后执行文件里面的代码吗?不是的!
因为不止一个程序在内存中跑!一般我们内存中至少有几十个程序在执行!这就衍生出了很多的问题!每一个程序内存该分配多少?在哪里分配内存?程序的代码执行到了哪里?有没有被调度过?CPU应该先执行哪一个程序,后执行哪一个程序?程序执行完毕了又该怎么办?.......
这些问题的本质就是操作系统该如何去==管理==这些==加载进内存的程序==呢?因为操作系统本身就是用来管理软硬件资源的软件!所以进程的问题本质也属于操作系统的管理问题!
那操作系统肯定要对这些==加载进内存的程序==进行管理!我们上面说了管理的本质是对数据进行管理!管理的两个步骤就是先描述后组织!——==描述就是用结构体进行描述,组织就是用数据结构进行组织!==
所以操作系统为了对进程进行描述!就有了一个叫==PCB的概念!==
task_struct内容
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
- 其他信息
==这些进程属性在当初的可执行程序里面有吗?——没有!==
可执行程序里面只有代码和执行逻辑!其他什么都没有!
==每一个可执行程序进入内存里面,操作系统都会自动的生成一个相对应的pcb!==
==然后这些pcb以链表的方式链接起来!完成再组织!==
这样子如果我们要CPU去调度一个优先级最高的进程那么流程是怎么样的呢?
首先操作系统会去遍历这个pcb链表!(不是遍历可执行程序!没有任何的意义!)因为pcb有进程的所有信息!所以操作系统可以找到优先级最高的进程!然后把这个进程对应的代码交到CPU里面!CPU就可以执行这个进程的代码了!
进程退出也是同理——操作系统遍历链表,发现有一个pcb里面的状态被标记为死亡,那么操作系统就将这个pcb从链表删除,同时释放进程在内存中对应的代码和数据那么进程就被退出了!
==这样对所谓的对进程进行管理就变成了对进程对应的PCB进行管理!==
对进程的管理 ——> 转换为了对链表的增删查改!
这就是PCB存在的意义!所以PCB存在也是一个必然的!
==linux内核的源码下的PCB==
所以我们终于可以对进程下达一个准确的定义!
==进程 = 内核数据结构(task_struct) + 进程对应的磁盘代码!==
进程在linux与Windows下的表现!
==Windows下的进程==
==linux下的进程==
这就是linux下面的进程(右边)首行有这个进程的各种属性!
PPID 该进程的父进程id
PID 该进程自己的id
PGID 该进程的主id
SID 该进程的会话id
TTY 该进程对应的终端!
STAT 该进程的状态!
UID 用户的id
COMMAND 对应的是哪一个进程
有两个进程一个是我们左边执行的mypro,一个是我们用来过滤的grep!
进程在调度运行的时候!进程就具有了其动态属性!
linux下面的进程查看指令
ps aux / ps axj 命令
可以配合管道与其他指令一起使用!上面的右图便是使用ajx来查看系统进程!
进程有关的系统调用!
我们上面的冯诺依曼体系已经说过了!我们编程的本质就是在调用系统调用接口!然后让系统帮我们完成剩下工作!
我们一般同软件层来使用系统调用!但是我们也可以自己去调用系统调用!
getpid/getppid
(2)意味着就是系统调用接口!
我们可以看出getpid的功能就是返回调用它的进程的id
getppid的功能就是返回调用它的进程的ppid
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
while(1)
{
printf("这是一个进程! pid :%d ,ppid:%d\n",getpid(),getppid());
sleep(1);
}
return 0;
}
接下来我们可以使用这个接口看到一种现象
那就是每次重新将程序加载到内存的时候!该进程的pid都会改变!这是很正常的一个现象!
因为每次加载到内存中,操作系统都要为这个进程重新创建相应的pcb! 重新分配pid!
而且我们也可以发现另一个现象那就是ppid都不会改变!这是因为我们现在所有的子进程都是在bash这个父进程下面运行!所以无论子进程如何改变都不会影响到父进程的!
而且bash这个进程也是shell外壳(父bash)下面的一个子进程!这是因为shell是内核和用户进行交互的唯一窗口如果这个窗口坏了那就无法进行交互了!所以shell外壳都会以子进程的方式运行而不会本身自己去运行!这样子无论子进程如何,都不会影响到shell外壳本身!
如果我们把bash杀掉了!也会shell外壳重新生成一个新的子进程!
我们可以看到此时ppid就发生了改变!
==一般来说命令行上启动的进程它的父进程的都是bash!==
无论子进程如何错误!顶多是子进程崩溃!父进程不会有任何影响!
我们可以看到我们在进程里面写入一个 1/0最后进程是崩溃了!
但是父进程是没有任何的影响
另一种查看进程的方式
ls /proc/
/proc 是一个系统文件!
我们可以发现其实linux下面的进程也是以文件的形式存在的!
所以我们可以用我们pid 来查找该文件下我们的进程
我们可以从下面的这个文件里面看到这个进程对应的可执行程序!
当我们使用kill -9 杀死该进程的时候!下面的文件就消失了!
接下来我们做个实验?那就是当我们在进程执行的时候删掉这个进程指向的二进制文件!这个进程会怎么样?因为我们上面可以看到这个进程的文件是会指向这个进程的二进制文件的!
我们可以发现进程仍然在执行!虽然进程下面的文件警告我们我们的可执行文件已经被删除了!
==从这里我们也可以看出来!一个进程对应的程序加载到内存中之后!理论上来说已经和这个进程对应的可执行程序没有任何关系了!==
进程的常见调用——fork
用于创建子进程的系统调用接口!
这是一个函数函数执行前只有一个父进程,函数执行后有一个父进程一个子进程
我们可以看到一个行代码打印了两次!
30047就是子进程30038的父进程!
那fork的实际用途是什么呢?
我们可以看看fork的返回值!
会返回给父进程子进程的pid
会返回给子进程一个0!
这个意思是不是说fork有两个返回值呢?——不是的!但是现在因为牵扯到内存地址空间!所以无法进行解释!但是fork并不是有两个返回值!
我们先来了解fork的用法!
#include<stdio.h> #include <unistd.h> int main() { pid_t id = fork(); if(id < 0 ) { perror("fork"); return 1; } else if(id == 0) { while(1) { printf("我是子进程 ,pid = %d ,ppid = %d\n",getpid(),getppid()); sleep(1); } //child } else { //parent while(1) { printf("我是父进程 ,pid = %d ,ppid = %d\n",getpid(),getppid()); sleep(3); } } return 0; }
我们神奇的发现本应该只执行一个的if else语句竟然两个都开始执行了!
这是因为fork之后,会有连哥哥进程在执行代码!
fork后续的代码!会被父子进程共享!
通过返回值的不同来让父子进程执行后续共享代码的一部分!
从上面的代码中我们可以看出操作系统不仅对硬件做管理!
操作系统也对进程做管理!而进程在没有进入内存之前就是磁盘上的一个个的二进制程序
然后我们把它加载到内存中 就要先描述在组织,对应的设计出进程的管理结构(就是PCB)
PCB里面就含有各种信息例如(pid,ppid这些都是进程里面的信息)
而对进程做管理就是对软件做管理!
而无论对硬件还是软件做管理里面的数据是要被用户给拿到的!