目录
一、冯诺依曼体系结构
二、操作系统
1.关于下三层的理解
2.关于上三层的理解
三、进程
1.进程(也叫做任务)对应的标识符---pid
2.fork---用代码创建进程(系统接口)
1)初步认识一下fork
2)fork函数的返回值
3)fork的原理
问题1: fork具体干了什么?
问题2: 为什么fork会有两个返回值?
问题3:为什么fork的两个返回值,给父进程返回子进程的pid,给子进程返回0?
问题4:fork之后,父子进程谁先运行?
问题5:如何理解同一个变量id,会有不同的值?
一、冯诺依曼体系结构
计算机都是由一个个的硬件组件组成
- 输入设备:包括键盘、鼠标、扫描仪等
- 中央处理器(CPU):含有运算器和控制器等
- 输出设备:显示器、打印机等
注意:
- 这里的存储器指的是内存
- 不考虑缓存的情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入输出设备)
- 外设要输入或者输出数据,也只能写入内存或者从内存中读取
总结:所有的设备都只能直接和内存打交道
上图是一个表示设备存储效率的金字塔图,根据木桶效应,我们会让效率相差不大的设备直接相连来保证相对高效的运行效率和相对较低的成本。
1.程序在被运行之前,得先加载到内存,为什么?程序(代码+数据)被运行,本质就是让CPU去执行,而CPU只和内存进行直接的数据交互,并且我们一般运行的程序都是exe文件,被存储在磁盘中,所以程序要被先加载到内存(体制结构决定)
2.如何理解网络信息之间的数据流动过程?(拿qq好友聊天举例)
二、操作系统
1.是什么?是一款软件,进行软硬件资源管理的软件 (电脑开机先要打开操作系统)
2.为什么要有操作系统?操作系统将软硬件资源管理好(手段),给用户提供良好的(稳定,高效,安全)使用环境(目的)
3.怎么办?即如何实现
1.关于下三层的理解
- 硬件部分是冯诺依曼体系结构。
- 驱动程序---这个大家可能没怎么听过,但是我们都应该见过当插入U盘时,电脑会弹出一个U盘驱动程序启动类似的弹窗,那就是在运行驱动程序(不同的硬件有不同的驱动程序)。
- 操作系统就是通过驱动程序来访问硬件资源的。
那么操作系统如何对软硬件资源进行管理?
这个其实可以类比现在学校的教务系统,学校对学生的管理本质是对学生的相关信息的管理,比方说,学校的校长并不了解每一个学生,但是他能对所有学生的学习情况了如指掌,为什么?因为他有你们每次考试的成绩数据,通过对这些数据的进行排序筛选等操作,他就能知道每个学生的学习情况,所以管理的本质是对数据进行管理
而如何记录学生相关数据,在计算机领域,显然就是用struct/class存放学生的基本信息等等,而对它们的管理,就是用相应的数据结构将数据连接组织起来,进行相应的增删查改的操作,所以总结一下就是六个字:先描述,在组织。
操作系统对硬件的管理也是如此,每个硬件都会有一个结构体对象用来描述该硬件的相关属性信息,再将它们用链表组织起来,这样对硬件的管理就变成了对链表的管理(当然也可能是其他的数据结构),所以一个硬件是否被管理,不在于它是否和电脑进行了物理连接,而在于操作系统中是否有一个结构体对象用来描述管理该硬件
2.关于上三层的理解
- 用户---一般来讲指所有人,但是这里我们以开发者的视角来看待问题,因为操作系统如果对开发者都不提供服务,那么就没有所谓的软件应用给普通人使用
- 用户调用接口和系统调用接口的存在是为了保护操作系统的安全
通过下三层的讲解,我们知道操作系统能访问硬件,那么我们用户能不能直接对操作系统进行操作以此来访问外设呢?
当然可以,但是我们不能保证用户的操作不会对操作系统造成影响甚至破坏,毕竟还是有恶意用户存在的,为此,操作系统提供了system call来防范用户的不合理的请求。
那么我们能不能绕过操作系统,直接对硬件进行访问呢?
这就有点类似于那种无人看管的图书馆,任何人都能进去借书,但是不做记录,因为没人管理,这座图书馆迟早出问题,同理,硬件也需要操作系统来进行统一的管理
那么为什么有用户操作接口呢?
因为系统接口用起来比较麻烦,不是所有人都能很好的使用系统接口,所以在system call之上还有用户操作接口,来简化用户的操作
外壳程序在Linux中是shell,在windows中是图形化界面。拿windows系统举个例子,当我们双击图标打开文件的时候,本质是调用系统接口打开文件,打开不同的文件要传的参数也不同,如果让你去选,你愿意双击还是敲系统接口,显然我们都喜欢双击。
各个语言的lib(标注库)也是如此,拿C语言举例,C标准库中的函数有很大一部分都是对系统接口进行了封装实现的,比如printf打印字符串到显示器,对于硬件的访问都是要经过操作系统的,所以别看我们只写了一行打印语句,底层还是封装的系统调用接口帮助我们实现的。库的实现,提高了开发的效率
三、进程
什么是进程?进程是正在执行的程序。
我们如何理解这句话?
进程=可执行程序+内核数据结构(pcb),对于"正在执行"的理解其实就是进程会在不同的状态之间切换,比如我们打开了很多进程,但不是每个进程都在一直被运行,而是用到它的时候就让它运行,不用的时候就让它等待(当然还有其他状态),看起来进程好像是动态的,本质还是对内核数据结构的操作
Linux中的PCB是struct task_struct{}
tast_struct中有哪些内容?
- 标示符: 描述本进程的唯一标示符,用来区别其他进程。
- 状态: 任务状态,退出代码,退出信号等。
- 优先级: 相对于其他进程的优先级。
- 程序计数器: 程序中即将被执行的下一条指令的地址。
- 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针
- 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。
- I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
- 其他信息
1.进程(也叫做任务)对应的标识符---pid
可以看见mytest的进程pid为22385,当然这个pid是会变化的,每次运行都不一样,这跟我们去排队,然后中途有事离开,再回来时需要重新排队是一个道理。
(感兴趣的可以用man ps去看看ps命令的介绍和其他选项)
(重新在运行程序后ps命令的截图)
ppid是进程的父进程,mytest的父进程是bash命令行,bash命令行是一直在运行的,所以id不变
Linux中启动进程的两种方式:1.手动输入命令行启动 2.通过代码创建进程启动
进程的启动本质是创建进程,一般通过父进程创建(如上,我们通过bash命令行解释器这个父进程创建mytest这个子进程),所以进程间有一种父子关系。
这里介绍获取pid和ppid的函数
除了ps命令,我们还可以通过 /proc 这个目录来查看进程的信息。
蓝色的数字就是进程的pid,我们可以运行一下自己的进程看看
当我们运行程序时,就会再/proc目录中创建相关的目录记录该进程信息,当我们结束进程时,对应的目录就会被删除。所以 /proc 是动态的目录结构,存放所有存在的进程,目录的名称,就是以这个进程的id命名的。
下面我们来看看这个目录中存放了些什么
这些文件都是进程的相关信息,我们暂不关心,我们主要看被框里来的两个。
第二个被框起来的部分是该程序在磁盘上的位置,也就是说,一个进程能找到自己的可执行程序。
在上面的操作中,我们先运行mytest,然后将mytest从磁盘上删掉。可以从该进程的目录下看到该进程知道自己被删除,但是却还在正常运行,为什么?在之前我们就说过,进程的创建要先将程序加载到内存,所以我们运行的是内存中的mytest程序,磁盘上的mytest删除并不会影响内存中的程序运行。
cwd---当前目录,这个大家在学C语言的文件操作的时候,应该就听过这个概念
//fopen函数的第一个参数如果传的是相对路径,就会在该路径前面加上cwd //这就是为什么我们创建的文件一般都在项目所在的目录下, //一般我们默认的当前目录都是进程启动所处的路径 fopen("test.txt","w");
当然这个当前路径是可以改变的,用下面这个函数
int chdir(const char *path);
2.fork---用代码创建进程(系统接口)
启动进程,本质是在系统中创建一个进程,操作系统管理的进程多一个,而进程=可执行程序+内核数据结构(task_struct对象),所以创建进程,就是申请内存空间保存可执行程序+task_struct对象,并将tast_struct对象添加到进程队列中。
1)初步认识一下fork
fork可以用来创建进程,如下
第二个打印语句被执行了两次,且它们pid不同,那么我们来看看这两个进程的ppid分别是什么。
现象:mytest的父进程是bash,新创建的进程的父进程为mytest,所以fork函数用来创建子进程,且子进程只执行fork之后的语句,前面的语句不执行
(可以推断出bash命令行创建子进程用的就是fork函数)
2)fork函数的返回值
fork函数的返回值不同,这很难理解,但是我们根据现象可知,在父进程中返回子进程的pid,在子进程中返回0,当然如果创建进程失败,返回-1
这里要说明一下创建子进程的意义是什么,创建子进程是为了辅助父进程完成工作,而不是为了和父进程执行一样的操作,所以我们一般是这样用fork的
用if-else语句将父子进程要做的事情分流,所以我们需要有两个返回值来分辨这两个进程
3)fork的原理
在上面的演示,其实我们还有很多问题没有讲清楚
- fork具体干了什么?
- 为什么fork会有两个返回值?
- 为什么fork的两个返回值,给父进程返回子进程的pid,给子进程返回0?
- fork之后,父子进程谁先运行?
- 如何理解同一个变量id,会有不同的值?
问题1: fork具体干了什么?
问题2: 为什么fork会有两个返回值?
在之前我们就说过fork是用来创建子进程的,那么fork创建子进程实在最后一句return语句之后才彻底完成的吗?显然不是,创建子进程的工作一定在return之前就已经完成了,所以在return之前,父子进程就已经开始共享数据和代码执行相关语句,当然fork的return也在其中,所以fork会有两个返回值
问题3:为什么fork的两个返回值,给父进程返回子进程的pid,给子进程返回0?
因为一个父进程可以创建多个子进程,而一个子进程只能有一个父进程,所以父进程需要知道子进程的id来分辨子进程,而子进程不需要,因为它只有一个父进程
问题4:fork之后,父子进程谁先运行?
这个是不确定的,当创建了子进程后,父子进程都需要在运行队列中排队,哪一个进程的pcb先被选择调度,哪个进程就先运行(具体由pcb中的调度信息和调度器算法共同决定,可以理解为由操作系统决定)
问题5:如何理解同一个变量id,会有不同的值?
当我们终止子进程/父进程时,会不会影响另一个进程的运行?
所以任何一个进程都具有独立性,但是我们知道父子进程的代码和数据是共享的,它怎么保证互不影响呢?首先代码是只读的,不可修改,那么数据呢?
在父子进程运行的过程中,我们都会用到数据,如果某些数据会被两个进程同时影响会导致代码逻辑出现问题,所以操作系统会让进程的数据各自"私有",当然这个不是说就完全拷贝一份给子进程,而是通过写时拷贝(就是在写入时才会出现拷贝)实现的,这样会减少空间浪费。
而fork函数的返回值给id这个变量,本质就是数据写入,会出现写实拷贝,所以id会有两个值
但其实我们还不能完全解释这个现象
看上图,我们会发现id这个变量的地址是一样的,但是一样的地址却有两个不同的值,这不可能,所以我们能确定这个地址不是真正的物理地址!!!(具体在进程空间再讲)
未完待续……