目录
硬件
冯诺依曼体系结构
- 存储器:内存。
- 输入设备:键盘、摄像头、话筒、磁盘、网卡……。
- 输出设备:显示器、音响、磁盘、网卡……。
-
CPU(*处理器):
- 运算器:算术运算、逻辑运算。
- 控制器:CPU是可以响应外部事件,协调外部就绪事件,比如,拷贝数据到内存。
- 这里的存储器指的是内存。
- 不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)。
- 外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。
- 一句话,所有设备都只能直接和内存打交道。
冯诺依曼体系结构推导
#问:为什么会有一个存储器?
在我们看来:
- 输入设备:是产生数据的。
- 输出设备:是保存 / 显示数据的。
那么体系结构为:下图,不是也可以吗?
1. 存储的效率:
CUP && 寄存器 > 内存 > 磁盘 / SSD > 光盘 > 磁盘。(并且还是数量级的上升)
2. 木桶原理(木桶原理又称短板理论,木桶短板管理理论):
最直接的话来说:一个木桶能够接多少水,是由木桶的最短的木板所决定的。换而言之就是,是由多个木板构成一个木桶,相当于由多个元素构成一个系统,同样的道理,一个系统的运行速度不是由最快的元素决定的,而是最慢的元素所决定。所以在计算机设计的角度来说,如果以:-> 输入设备 -> CUP -> 输出设备 -> 。来设计,这个时候输入设备和输出设备都会大大的拖慢CPU的运行速度,导致整个系统运行速度降低。(更直接的来说就是:CPU运行速度太快了,对比起磁盘等外设的速度,磁盘们慢的离谱,CUP"等急了")
于是我们在磁盘到CPU的间隔中,插入了一个内存。(更直接的来说就是:内存还行,比磁盘快不了太多,又比CPU慢不了太多,CPU"这个等待可以接受")利用存储器的存在,让我们对于速率上,以软件上做文章。
通过将外设的数据预先加载到存储器当中,此时CPU读取数据时就不去外设而是去存储器读取数据。即:因为存储器的存在,以此通过软件层的策略(比如:操作系统),提升效率。以此达到以后的木桶短板就不是外设,而是内存了。例如:缓冲区满了才将数据打印到屏幕上,使用fflush函数 / 显示器的行刷新策略,将缓冲区当中的数据,都是将内存当中的数据直接拿到输出设备当中进行显示输出。
比如:开机的时候操作系统将我们可能要访问的数据,预先从外设,读取到内存当中。
重点概念
结论:
- CPU读取数据(数据 + 代码),都是要从内存中读取数据。—— 站在数据的角度,我们认为CPU不和外设直接交互。
- CPU要处理数据,需要先将外设中的数据,加载到内存。—— 站在数据的角度,外设直接只和内存打交道。
所以在语言级的学习中的一局话,我们就可以理解了:程序(在文件、磁盘中)要运行,必须先加载到内存中(因为体系结构决定)。
网络数据流向
我们利用QQ和远在其他地方上大学的朋友,进行聊天,并且使用的是由冯•诺依曼体系结构搭建的电脑。
我们在与朋友聊天的时候,我们通过键盘,输入自己想说的话,于是键盘输入的信息会加载到内存,而我们也需要知道我们输入的是什么,所以信息还会回显屏幕上。而关键在于对方收到我们的信息,所以CPU需要将我们输入的内容,经过网络的包装、打包、网络IP等,然后打包完的内容写回到内存当中。然后通过网卡进行网络上的数据传输,然后对方通过网卡接受到我们所发出的信息,由于体系结构,就会将内容加载到内存当中,然后CPU进行读取分析,将分析完的数据写回到内存,然后将内容写到显示器上。
路线:
冯诺依曼体系结构,能够帮助我们理解对用的日常生活中的软件行为的,是硬件规定了我们的软件应该怎么做。
软件
操作系统(Operator System - OS)
概念
- 内核(进程管理,内存管理,文件管理,驱动管理)。
- 其他程序(例如函数库,shell程序等等)。
定位
- 对上:提供良好的使用环境 —— 是目的
- 对下:通过管理好软硬件资源,保证系统的稳定性 —— 是手段
可以说:给用户提供一个稳定、安全、简单的执行环境。
进程内核数据结构PCB(task_struct)
- 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。
- 课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct
task_ struct内容分类
- 标示符:描述本进程的唯一标示符,用来区别其他进程。
- 状态:任务状态,退出代码,退出信号等。
- 优先级:相对于其他进程的优先级。
- 程序计数器:程序中即将被执行的下一条指令的地址。
- 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据:进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
#问:为什么要存在PCB(task_struct)?
重点理解操作系统的作用,以及操作系统如何做管理的。要管理必须要先描述再组织,先描述就是面向对象,对于被管理的对象先抽象成类,然后再组织,就是根据类来定义对象。然后将对象,根据某种链入式的结构组织起来。
通过系统调用创建进程-fork初始
fork基本用法
fork之后,代码是父子共享的,但是如果父子多做的事情都以一样的,那我们费这么劲作用不大,需要的是分出来做不同的事情,这样可以明显的提高效率。所以通常用法,使用fork是利用父子进程执行不同的代码。
使用if进行分流
简单来说就是,父子进程都能看见fork后面的代码,但是只能执行自己的。
int main()
{
pid_t id = fork(); //pid_t相当于无符号整数
if(id<0){
//创建失败
perror("fork");//打印出fork失败的原因(由C语言提供)
return 1;
}
else if(id == 0)
{
//child process(task)
while(1)
{
peintf("I am child, pid %d, ppid %d\n", getpid(), getppid());
sleep(1);
}
}
else
{
//parent process
while(1)
{
peintf("I am father, pid %d, ppid %d\n", getpid(), getppid());
sleep(1);
}
}
printf("you can see me!\n");
sleep(1);
return 0;
}
利用id在父进程里面是子进程的pid,在子进程里面是0。让else if与else里面的语句同时执行。主要是一个变量(id)是可以有不同的值。与语言本身无关,仅仅就是因为使用fork创建了子进程而产生的特殊现象。(不同值的原理:在进程地址空间讲解)
查看运行效果
用于可以直接清晰的查看进程。
ps xaj | head -1
就是打印一个表格的头标(表格中数据的名称)
ps axj | head -1 && ps axj | grep myproc
&&与逻辑与相同,只有左侧的执行成功才会执行右侧的,要成功答应,需要左侧与右侧都成功。
ps axj | head -1 && ps axj | grep myproc | grep -v grep
while ;: do ps axj | head -1 && ps axj | grep myproc | grep - v grep; sleep 1; done
- 为什么给子进程返回0,给父进程返回子进程的pid?(此处没有官方的讲解,仅仅是理解)
任何一个子进程,永远都只有一个父进程,任何一个父进程,可以有一到多个子进程。父进程 : 子进程 = 1: n,只要父进程调用fork之后就可以有子进程。而为了父子进程便于协同,所以,就相当于生活中:父亲不可能叫自己的孩子为,那个孩子吃饭了、那个孩子别乱摸……,更何况还会有多个孩子。所以,需要给子进程进行标识,等同于fork之后,给父进程返回子进程的pid,以此父进程来对子进程进行相关管理。
而对于子进程永远都只有一个父进程,子进程与父进程的对应关系就是相当明确的、唯一的。子进程可以很方便的找到父进程。
- 创建进程的时候,操作系统要做什么?fork为什么会有两个返回值?
创建子进程,那么我们一定要给子进程配套一个task_struct,来让操作系统来管理这个新进程。这样这个 task_struct 就可以入到系统的全局的维护进程列表的结构当中,操作系统就可以对新进程进行管理了。
对于在系统层的fork实现中,return时核心代码的执行状态:
操作系统和CPU运行某一个进程,本质从 task_struct 形成的队列中挑选一个task_struct,来执行它的代码。
进程调度,变成了在task_struct的队列中选择一个进程的过程。只要想到进程,优先想到进程对应的task_struct。
所以当准备return的时候,核心代码已经完成了。因为return,是为上层返回结果。既然已经返回结果,证明计算过程已经结束。
而通过此我们知道父与子进程的执行其实是有优先级的。
所以,才会看到会有两个返回值。不是出了fork才有的父子进程,在fork中的return的时候,进程早已经创建出来了,甚至子进程都可以进行调度了。
#问:父子进程被创建出来,哪一个进程先运行?
有可能父进程刚刚将子进程创建出来,父进程就因为某些原因,就被放到了(run_queue)队列的尾部,反而放到了子进程的后面,这个时候就是子进程先运行了。但是也有可能,系统一口气将父进程跑完了,甚至将fork之后的代码也跑完了。所以这个时候:哪一个先跑完是不可控的。
谁先运行,不一定,这个是由操作系统的调度器决定的。只有操作系统最清楚谁先调用。