Skip to content

Latest commit

 

History

History
181 lines (92 loc) · 6.71 KB

to_direction_compare.md

File metadata and controls

181 lines (92 loc) · 6.71 KB

真·三十天

两个方向的对比

欢迎来到真三第三个视频

在这个视频里,我将带大家对操作系统内核做一个感性的了解,让大家知道我们即将制作的大概是个什么东西

我尝试从两个方向来说明,从用户界面到硬件经历了什么,和从硬件到用户界面经历了什么

我们先说 从用户界面向硬件的方向去 这个方向

学习操作系统实际上就是这样,最开始的时候是会使用操作系统

比如windows的窗口图形界面,linux的shell环境

然后学习编程语言,写自己的应用程序

想要继续深挖,就去学习汇编语言,到这个层面上,无论学习再如何深入,都会碰到一个分界线

就是无论是图形界面/shell命令/自己的应用程序/汇编实现的应用程序 都是作为一个用户在使用系统提供的服务

包括在显示器上打印,画图,读写文件,使用网络等等

后面会看到,在x86架构下提供服务的方式是使用int指令来调用系统调用


看另一个方向

一般再自顶向下学习的同时也会同时自底向上的学习

我们最初接触的计算机一般都是x86平台的机器,我们就很自然的从这个平台开始学习

学习计算机上电之后应该怎样写代码来控制机器

一般是一条一条的汇编指令

计算机不断的从存储器中取出指令,执行,再取出指令,执行

就是这样傻傻的串行执行命令

但是这时候和用户视角有一个很大的区别,就是我们可以直接使用out指令来操控硬件

这时的权利非常大


这两个方向在大学里的计算机专业课里面是有分布的

像 c语言 之类的语言的教学课程,属于自顶向下的方向

而像 计算机组成原理 之类的基础类的课程,属于自底向上的方向

刚开始学习的时候会感觉这两个方向说的事情毫无瓜葛,有种虚无缥缈的感觉,于是对自底向上的方向失去兴趣

只对自顶向下方向的课程感兴趣

但是如果没有自底向上方向的知识

有很多问题可能会一直困扰你,比如在main函数里的return 0和exit(0) 到底有什么区别 比如我写了一个爬虫程序,爬网页之后录入数据库,怎样去优化性能 再比如为什么所有标准输入输出错误输出的文件描述符都是0 1 2,为什么我向fd1中写数据就能写到屏幕上

这些问题对一个it从业者不是必需要回答的,但是在面对一些技术选型出现多选一的情况的时候,是有指导意义的

另外还有一点,学习操作系统有一个好处是能够将自己的技术体系有一个锚定点,不会被快速更新令人眼花缭乱的技术弄的焦躁不安

你有时候会大概猜出新技术是基于什么来的,不至于随波逐流

回到主线上

自己动手写一个操作系统正是要将两个方向都去深挖,直至挖通,建立你自己的锚定点

这一系列视频就是要帮你看清这两个方向上发生的事情的具体联系

不断让这个分界线越发的透明

随着之后的不断学习,两边对接的通路会不断变宽

很多之前解释不清的现象可以解释了,很多迷茫的解决方案有了线索


我们总结一下这两个方向的特点

首先是自底向上,从硬件的角度触发

硬件从上电开始就是根据从ram或者rom中取出的指令串行的傻傻的执行

这时我们可以操作所有硬件

然后是自顶向下,从用户的角度出发

就拿linux来说,我们可以同时运行很多程序(top)

我们的应用程序不能直接使用硬件

系统会帮我们调度硬件

我们自己写的应用程序可以使用一个互不干扰的内存空间

从这个角度来看,这些特性对于串行执行的指令流来说都是神乎其技的存在

那么接下来就让我们开始研究其中的关联吧


介绍一下推荐书籍


我们第一个demo,从一个linux下用汇编编写的hello world程序开始讲起

为什么是这个汇编程序呢,因为这是我们从用户角度来看最接近分界线的程序,它直接调用了系统的功能

这里的int 代表的是interrupt,是一个中断指令,这里的中断号是0x80

这里首先讲一下cpu处理中断的工作原理

(打开8259A资料)这里我提供一个一个关于中断的线索

为什么会有中断这个东西呢

中断本身设计是为了cpu和硬件协同工作的

cpu中一个ip指针,这个在我们之后调试程序中会经常看到,全称是Instruction Pointer

balabala。。。。


回到int指令上,中断响应不但是可以由硬件触发,也可以由cpu本身触发

我们触发了0x80这个中断号,提前把参数按照和操作系统约定好的方式放到寄存器中

于是系统就可以帮助我们开始工作,在此期间,我们作为用户是无事可做的

直到打印操作完成了,控制权又返回到我们手上,继续后续的动作

可能有人会问了,这和调用一个函数有什么区别呢

这里就涉及到x86保护模式机制的知识了

正常一个c函数调用的时候,会把参数和返回地址压到自己的栈上

但是如果调用内核功能的时候也压在自己的栈上会有什么问题呢

首先,一个用户为了搞垮内核,可以做这样的操作,在栈上压了很多数据,几乎要满了

这时内核函数在拿到控制权的时候,是有可能继续向栈上压数据的,这时很可能占空间不够了,于是内核崩溃

另外为了避免内核bug无意修改用户程序栈上的数据,内核本身是不想看到用户的栈的

就好比一个餐厅,你叫服务员点个菜,如果不切换栈的情况应该是

厨师直接到你的餐桌上去做菜,如果你的餐桌比较干净比较大,也许能做出一道菜

但是如果你的餐桌很小,可能厨师会手忙脚乱,做不成菜

所以我们需要栈切换,就好比服务员把客人点的菜的单据递给厨房里的厨师,这时单据就是寄存器的功能

厨师拿到单据,在自己的厨房里的操作台上做菜,这个操作台就是内核栈

做好菜之后,传递给服务员,放回用户的餐桌上,这里就是用户的栈

这中间,活动在用户这边的状态就是我们常说的用户态,而活动在厨房的状态就是内核态


再回到我们当前的汇编程序就很好理解了

首先定义了一个字符串,10是换行符


所以回到这期视频的主题,是要带大家简单感性的认识一下我们要做的内核是个什么东西

就是要实现 int 分界线以下的部分,也就是厨房内的东西

另外也要简单的在餐厅中摆几张桌子,做几个简单的菜来试验一个我们的厨房内部的功能,这就是shell