.hd-box .hd-fr

Linux 0.11 第五回 | 进入保护模式前的最后一次折腾内存

2022-10-04 16:31低并发编程(闪客)14评

本文来自微信公众号:低并发编程 (ID:dibingfa),作者:闪客

本系列会以一个读小说的心态,从开机启动后的代码执行顺序,带着大家阅读和赏析 Linux 0.11 全部核心代码,了解操作系统的技术细节和设计思想。

你会跟着我一起,看着一个操作系统从啥都没有开始,一步一步最终实现它复杂又精巧的设计,读完这个系列后希望你能发出感叹,原来操作系统源码就是这破玩意。

以下是已发布文章的列表,详细了解本系列可以先从开篇词看起。

开篇词

第一回 | 最开始的两行代码

第二回 | 自己给自己挪个地儿

第三回 | 做好最最基础的准备工作

第四回 | 把自己在硬盘里的其他部分也放到内存来

本系列的 GitHub 地址如下:

https://github.com/sunym1993/flash-linux0.11-talk

------- 正文开始 -------

书接上回,上回书咱们说到,操作系统已经完成了各种从硬盘到内存的加载,以及内存到内存的复制。

至此,整个bootsect.s的使命就完成了,也是我们品读完的第一个操作系统源码文件。之后便跳转到了0x90200这个位置开始执行,这个位置处的代码就是位于setup.s的开头,我们接着来看。

start:    mov ax,#0x9000  ; this is done in bootsect already, but    mov ds,ax    mov ah,#0x03    ; read cursor pos    xor bh,bh            ; save it in known place, con_init fetches    mov [0],dx      ; it from 0x90000.

又有个int指令。

前面的文章好好看过的话,一下就能猜出它要干嘛。还记不记得之前有个int 0x13表示触发 BIOS 提供的读磁盘中断程序?这个int 0x10也是一样的,它也是触发 BIOS 提供的显示服务中断处理程序,而ah寄存器被赋值为0x03表示显示服务里具体的读取光标位置功能

具体 BIOS 提供了哪些中断服务,如何去调用和获取返回值,请大家自行寻找资料,这里只说结果。

这个 int 0x10 中断程序执行完毕并返回时,dx寄存器里的值表示光标的位置,具体说来其高八位 dh 存储了行号,低八位 dl 存储了列号。

这里说明一下:计算机在加电自检后会自动初始化到文字模式,在这种模式下,一屏幕可以显示 25 行,每行 80 个字符,也就是 80 列。

那下一步mov [0],dx就是把这个光标位置存储在 [0] 这个内存地址处。注意,前面我们说过,这个内存地址仅仅是偏移地址,还需要加上 ds 这个寄存器里存储的段基址,最终的内存地址是在0x90000处,这里存放着光标的位置,以便之后在初始化控制台的时候用到。

所以从这里也可以看出,这和我们平时调用一个方法没什么区别,只不过这里的寄存器的用法相当于入参和返回值,这里的0x10 中断号相当于方法名

这里又应了之前说的一句话,操作系统内核的最开始也处处都是 BIOS 的调包侠,有现成的就用呗。

再接下来的几行代码,都是和刚刚一样的逻辑,调用一个 BIOS 中断获取点什么信息,然后存储在内存中某个位置,我们迅速浏览一下就好咯。

比如获取内存信息。 Get memory size (extended mem, kB)    mov ah,#0x88    int 0x15    mov [2],ax获取显卡显示模式。 Get video-card data:    mov ah,#0x0f    int 0x10    mov [4],bx      ; bh = display page    mov [6],ax      ; al = video mode, ah = window width检查显示方式并取参数; check for EGA/VGA and some config parameters    mov ah,#0x12    mov bl,#0x10    int 0x10    mov [8],ax    mov [10],bx    mov [12],cx获取第一块硬盘的信息。 Get hd0 data    mov ax,#0x0000    mov ds,ax    lds si,[4*0x41]    mov ax,#INITSEG    mov es,ax    mov di,#0x0080    mov cx,#0x10    rep    movsb获取第二块硬盘的信息。 Get hd1 data    mov ax,#0x0000    mov ds,ax    lds si,[4*0x46]    mov ax,#INITSEG    mov es,ax    mov di,#0x0090    mov cx,#0x10    rep    movsb

以上原理都是一样的。

我们就没必要细琢磨了,对操作系统的理解作用不大,只需要知道最终存储在内存中的信息是什么,在什么位置,就好了,之后会用到他们的。

内存地址长度 (字节) 名称 0x900002 光标位置 0x900022

扩展内存数 0x900042 显示页面 0x900061

显示模式 0x900071 字符列数 0x900082 未知 0x9000A1

显示内存 0x9000B1

显示状态 0x9000C2 显卡特性参数 0x9000E1

屏幕行数 0x9000F1 屏幕列数 0x9008016

硬盘 1 参数表 0x9009016 硬盘 2 参数表 0x901FC2

根设备号

由于之后很快就会用 c 语言进行编程,虽然汇编和 c 语言也可以用变量的形式进行传递数据,但这需要编译器在链接时做一些额外的工作,所以这么多数据更方便的还是双方共同约定一个内存地址,我往这里存,你从这里取,就完事了。这恐怕是最最原始和直观的变量传递的方式了。

把这些信息存储好之后,操作系统又要做什么呢?我们继续往下看。

cli         ; no interrupts allowed ;

就一行 cli,表示关闭中断的意思。

因为后面我们要把原本是 BIOS 写好的中断向量表给覆盖掉,也就是给破坏掉了,写上我们自己的中断向量表,所以这个时候是不允许中断进来的。

继续看。

 first we move the system to it's rightful place    mov ax,#0x0000    cld          'direction'=0, movs moves forwarddo_move:    mov es,ax        destination segment    add ax,#0x1000    cmp ax,#0x9000    jz  end_move    mov ds,ax        source segment    sub di,di    sub si,si    mov cx,#0x8000        jmp do_move; then we load the segment descriptorsend_move:    ...

看到后面那个rep movsw熟不熟悉,一开始我们把操作系统代码从0x7c00移动到0x90000的时候就是用的这个指令,来图回忆一下。

同前面的原理一样,也是做了个内存复制操作,最终的结果是,把内存地址0x10000处开始往后一直到0x90000的内容,统统复制到内存的最开始的0位置,大概就是这么个效果。

由于之前的各种加载和复制,导致内存看起来很乱,是时候进行一波取舍和整理了,我们重新梳理一下此时的内存布局。

栈顶地址仍然是0x9FF00没有改变。

0x90000开始往上的位置,原来是bootsectsetup程序的代码,现 bootsect 的一部分代码在已经被操作系统为了记录内存、硬盘、显卡等一些临时存放的数据给覆盖了一部分。

内存最开始的00x80000这 512K 被system模块给占用了,之前讲过,这个 system 模块就是除了 bootsect 和 setup 之外的全部程序链接在一起的结果,可以理解为操作系统的全部

那么现在的内存布局就是这个样子。

好了,记住上面的图就好了,这回是不是又重新清晰起来了?之前的什么 0x7c00,已经是过去式了,赶紧忘掉它,向前看!

接下来,就要进行有点技术含量的工作了,那就是模式的转换,需要从现在的 16 位的实模式转变为之后 32 位的保护模式,这是一项大工程!也是我认为的这趟操作系统源码旅程中,第一个颇为精彩的地方,大家做好准备!

后面的世界越来越精彩,欲知后事如何,且听下回分解。

广告声明:文内含有的对外跳转链接(包括不限于超链接、二维码、口令等形式),用于传递更多信息,节省甄选时间,结果仅供参考,IT之家所有文章均包含本声明。

下载IT之家APP,分享赚金币换豪礼
相关文章
大家都在买广告
热门评论
查看更多评论