在哈工大纯C论坛论坛有找到一篇写操作系统基础知识的文章.docx

上传人:b****5 文档编号:12701667 上传时间:2023-04-21 格式:DOCX 页数:22 大小:30.74KB
下载 相关 举报
在哈工大纯C论坛论坛有找到一篇写操作系统基础知识的文章.docx_第1页
第1页 / 共22页
在哈工大纯C论坛论坛有找到一篇写操作系统基础知识的文章.docx_第2页
第2页 / 共22页
在哈工大纯C论坛论坛有找到一篇写操作系统基础知识的文章.docx_第3页
第3页 / 共22页
在哈工大纯C论坛论坛有找到一篇写操作系统基础知识的文章.docx_第4页
第4页 / 共22页
在哈工大纯C论坛论坛有找到一篇写操作系统基础知识的文章.docx_第5页
第5页 / 共22页
点击查看更多>>
下载资源
资源描述

在哈工大纯C论坛论坛有找到一篇写操作系统基础知识的文章.docx

《在哈工大纯C论坛论坛有找到一篇写操作系统基础知识的文章.docx》由会员分享,可在线阅读,更多相关《在哈工大纯C论坛论坛有找到一篇写操作系统基础知识的文章.docx(22页珍藏版)》请在冰豆网上搜索。

在哈工大纯C论坛论坛有找到一篇写操作系统基础知识的文章.docx

在哈工大纯C论坛论坛有找到一篇写操作系统基础知识的文章

操作系统是计算机的核心,没有操作系统,一切计算机应用都免谈,但现在操作系统基本上被老外垄断,Windows就不说了,就算是Linux那也是泊来品,什么时候我们才能写出有中国特色的操作系统啊?

 

在工大,我们每个人都学过操作系统,我也一样,但老师教的那真的只是理论,一个实际的系统原比老师教的要复杂上一千万倍!

然而,我们基本上没有可能实践的机会,就算是有一些实验,那也是停留在一个非常高的表层。

我非常之笨,学完之后,还是不知系统是怎样从无到有,开始工作的。

系统是怎样启动的?

曾经把一段汇编代码写进了磁盘,但无论如何没将机器启动起来(原因现在已经清楚,稍后再谈)。

想看看Linux的源码,但犹如天书!

即使是Minix也非常之庞大,晦涉难懂!

郁闷啊~~~,今天在网上无意间看见了一个老外写的E文,讲到此事,霍然开朗,在网上狂找了一堆E文后,最终将机器起启来了。

(本人E文差得没底,基本上是用金山词霸配合联通国际在线翻译系统一句一句翻译的,痛苦啊!

看来要想学好计算机E文不好还真不行:

(,很多资料中文的就是没有,只有E文的,你咋办?

)。

现特将全过程描述一下,一来留个纪念,二来希望工大能有更多的人能对此感兴趣,超级大牛们早日写出我们自己的操作系统。

Linux也是芬兰的一个大学生写的。

 

阅读本文最好有那么一丁点的汇编基础。

另外,本人水平极其有限,对操作系统也不是很熟,对于论述中不计其数的错误,望大家原谅,千万不要来砍我~~~ 

首先,我要先说明一下计算机在你按下电源按钮后,计算机都做了什么 

当你按下电源键的时候,同这个键相联的电线就会送出一个电信号给主板,主板将此电信号传给供电系统,供电系统开始工作,为整个系统供电,并送出一个电信号给BIOS,通知BIOS供电系统已经准备完毕。

随后BIOS启动一个程序,进行主机自检,主机自检主要工作是确保系统的每一个部分都得到了电源支持,内存储器,主板上的其它芯片,键盘,鼠标,磁盘控制器及一些I/O端口正常可用,此后,自检程序将控制权还给BIOS。

 

接下来,BIOS开始启动操作系统。

 

BIOS将访问启动盘的第一个扇区(0磁道,1扇区,一共是512字节),BIOS将这第一扇区中的内容调入内存的0x7c00地址处,并开始执行它。

这是启动系统的第一关,从此之后,系统就将控制权将给操作系统了,留下的事情就由你的程序来完成。

 

现在我们的任务就是写这样一个程序,系统将它称之为引导程序,用它来引导或说启动我们的计算机。

它有如下两个特点:

 

1。

大小只能是512字节,不能多一字节,也不能少一字节。

因为BIOS只能读512字节的数据到内存中,多的部份BIOS不会理采 

2。

它必须以"55 AA"结尾,即最后两字节(511,512)必须是它们。

这是引导区程序结束的标志,没有它BIOS不会将它作为引导程序看待。

(我以前的程序没有执行,就是因为没有在这里写"55 AA") 

把这一程序放在磁盘的0磁道,1扇区里,这样,此磁盘就可以用来引导系统,而且是用的你自己做的引导程序!

 

在开始制作引导程序之前,先介绍一下怎样在Windows环境下进行这样的开发。

 

首先,需要一个实验环境,你当然可以就用真实的计算机,如果你有多台计算机的话,且不觉得麻烦的话。

 

这里我们使用虚拟机来进行实验,它与使用真实的计算机是一样的,不信待会儿你可以自己实验一下。

 

我用的是 MS Virtual PC,使用非常简单,这里就不多说了,它可以用一个1.44M大小的img文件,作为模拟软盘,因此,我们就只需把我们的引导程序写到一个img文件中,就如同写在了一张磁盘上面,就可以用它来引导系统。

 

启动虚拟机后,在Floopy菜单下,选Floopy Disk Image项,然后选到我们生成的那个img文件后,就可以了。

 

下面说说img文件的创建生成方法 

要把引导程序写到这个1.44M的文件里面,我使用的是WinHex工具,它非常方便,可以直接通过拷贝完成二进制文件的写入,而且还可以创建指定大小的文件。

 

1.44M的img文件可以用WinHex来创建,点击新建按钮就行,大小输入1474560,单位字节。

 

到时后,把我们写的程序用WinHex打开,将其内容复制到生成的img文件下就行。

 

这些工具连同本文所介绍的实验程序,我已经打包,大家可以通过下面的地址下载 

ftp:

//202.118.239.46/Incoming/Other/BTC/temp/os_test/os_test.rar 

下面再介绍一个本程序用到的唯一一个BIOS中断, 

Int 0x10 

0x10 中断是BIOS的显示器中断,所有输出都需要调用此中断,在使用前你需要设置一部分寄存器的值以告诉BIOS怎样进行输出 

ah :

 0x0e 打字机模式,告诉BIOS,把字符输出到屏幕上 

bh :

 页码 

bl :

 文字属性 

al :

 欲现示的字符的ASCII码 

好了,下面我们就能创建我们的引导程序,完整的源程序如下:

 

[BITS 16] ; 告诉编译器,编译成16位的程序 

[ORG 0x7C00] ; 告诉编译器,代码将从0x7c00处开始执行 

main:

 ; 主程序 

mov ax , 0x0000 ; 以下两句设置数据段为0000 

mov ds , ax 

mov si , Message ; 设置基址指针 

call ShowMessage ; 调用显示函数 

jmp $ ; $ 代表此语句的地址,表示在此语句此进行无限循环 

ShowMessage:

 ; 显示函数 

mov ah , 0x0e ; 设置现示模式 

mov bh , 0x00 ; 设置页码 

mov bl , 0x07 ; 设置字体属性 

nextchar:

 

lodsb ; 字符载入指令 

; 它将DS数据段中SI为偏移地址的源串中的一个字符取出送AL,同时修改SI指向下一个字符 

or al , al ; 测试字符串是否为0 

jz return ; 如果为零则表明字符串结束,跳转到返回指令处返回原调用函数 

int 0x10 ; 调用BIOS 10号中断显示字符 

jmp nextchar ; 继续显示下一下字符 

return:

 

ret ; 返回原调用函数 

Message:

 

db 'Can We Write A Chinese OS ?

'; 定义显示消息 

db 13 , 10 , 0 ; 13 表示回车,10 表示换到下一行 0 表示字符串结束 

times 510 - ( $ - $$ ) db 0 ; 填充 0 以满足文件大小足够512字节 

; $ 表示当前语句的地址,$$ 表示程序的起始地址 

db 0x55 , 0xaa ; 结束标志 

用任何一个文件编辑器输入它之后,存为test.asm 

然后用一个汇编程序编译它,我用的是nasm, 

输入 nasm test.asm 

编译完成后,会生成一个 test 文件,用WinHex打开它,然后照上面讲的方法,把它写到一个img文件中,你就可以尝试一下用你自己的程序启动计算机了。

 

也可以用WinHex将它直接写到磁盘上,通过磁盘启动,这样的感觉非常爽啊~~~ 

下面是它的运行结果 

此主题相关图片如下:

 

一个操作系统非常的复杂,这里只是我的一个尝试实验的心得,它只完成了系统启动这一步,严格说来引导程序不算是操作系统,虽然它无比重要,一般来说,它需要把真正的操作系统的内核载入内存,然后用一条jmp指令跳转到真正的内核处执行。

 

上一篇谈到了怎样把计算机用我们自己的程序启动起来,然而我已经说过,那只是最最初 

始的一步,只表明了我们的确可以让计算机从一开始就按照我们的命令去执行一个任务。

 

但它并不能算是一个操作系统,当计算机用引导程序引导起来之后,我们需要让它把真正 

的操作系统内核载入内存中,然后跳转到真正的操作系统内核中运行。

 

本篇将完成一个真正意义上的操作系统引导,而不是第一篇里所描述的计算机的引导。

这 

里我们将从计算机启动时的16位的实模式转到现在通用的32位的保护模式下。

 

现在的操作系统除了最低层的部份之外,均由高级语言完成,在本篇中我们也将用高级语 

言来编写我们的内核。

本篇中实现的一个内核是用C语言编写的。

用汇编写的引导程序,把 

C语言写的内核载入并执行,这就是本篇将要完成的任务。

 

阅读本文最好有那么一丁点的汇编基础~~~ 

还是那句老话,我水平极其有限,这只是一个尝试,对于不计其数的错误,望大家海涵, 

千万不要来砍我啊~~~ 

非常感谢各位老师,大牛能指点一二!

 

现在,我们继续我们的实验。

 

首先,我想先介绍一下实模式与保护模式,对于本篇,它们是非常重要的概念!

一定要领 

会!

就算现在领会有误也要尽可能的领会!

大家都知道现在用的Intel的CPU在原先8086的 

时代是16位的,后来技术发展了,Intel的CPU也改成了32位的了。

但是为了同以前16位的 

程序兼容,Inetl在它的32位的CPU中仍然保留了16位的模式。

这样在32位的CPU中,就有两 

种工作模式,一种是16位的模式,称之为实模式;另一种是32位的模式,称之为保护模式 

在计算机刚刚启动的时候,它是工作在实模式情况下,为了让它工作在保护模式的情况 

下,我们需要向CPU中一个cr0的寄存器置位,这样,当cr0的第0位被置位后,CPU就转到32 

位模式下工作了。

因此,从16位转到32位是非常简单的,只需要对CPU的一个寄存器进行置 

位操作就行了。

但是,还是需要做一个准备工作,这里我们先来谈谈16位与32位模式下内 

存是怎样访问的。

 

学过汇编的都知道,在8086中,内存是分段的,一个内存地址由 段基址:

偏移量 构成。

这 

是因为在16位的CPU中,寄存器的大小是16位的,只能访问2^16=64K的地址,这实在是太小 

了,因此需要通过分段的机制来扩大程序的访问内存的范围。

显然,在16位的CPU中,一个 

段的大小最大只能是64K 

但是在32位的CPU中,寄存器的大小是32位的,可以访问2^32=4GB有空间,这又太大了!

一 

个程序只需要其中的能小一部分空间就足够了。

因此,在32位的CPU中,也将内存分段,为 

每个程序分配必要的空间,以免浪费。

(注,在32位的CPU中,内存管理有分段与分页两种 

模式,分页主要用于实现虚拟内存,本文只讨论分段模式)这样就需要记住每个段是属于 

哪个程序的?

是用来干啥的?

是数据段还是代码段?

可以访问还是不可以访问?

是只读的 

还是可写的?

这些需要记录的信息是在太多了,CPU就把它们组织起来放在内存中一 

个专门的地方,这块内存就称之为段描述符,它描述了这个段的属性。

每个段都有一个描 

述属性的段描述符,把它们全部组织起来放在内存的一个专门的地方,就形成了段描述符 

表。

32位CPU有全局描述符表(GDT)与局部描述符表(LDT)之分。

全局描述符表就指此表 

可被所有的程序访问。

在32位的CPU中,段寄存器并不是表示段的基址,而是表示一个指向 

描述符表的索引,用来指出此程序用到的段的描述符在描述符表的位置。

然后,访问此描 

述符表取出描述符,由此才得到有关段的属性信息。

32位CPU的段寄存器器还是16位的,不 

过,它用13位来作段描述符表的索引,因此,我们可以定义的最大段数为2^13=8192个。

由 

此可见,在32位的保护模式下,内存是通过段描述符表来访问的,因此,当我们转到32位 

模式之间,我们需要创建好段与段描述符表。

 

好了,基础知识我们已经有了,闲话少说,我们来编写我们的引导程序,完整的源代码如 

下:

注:

在上一篇已经注解过的地方,这次就不注解了,如果有不理解之处,请找上一篇看看 

[BITS 16] 

[ORG 0x7C00] 

    jmp main 

; --------------------------------------------------------------------------- 

; 数据定义 

bootdrive   db  0 

; -------------------------------------------------------------- 

; GDT 定义,此处定义段及段描述符 

gdt:

 

    gdt_null:

           ; 这是CPU要求保留的,CPU要求第一个段必须是空段,不知它 

                        ; 想用来干啥 

        dd  0 

        dd  0           ; 每个段的描述符是64位(8字节),空描述符的这64位全是0 

    gdt_code_addr equ $ - gdt   ; 求得代码段在GDT表中的位置 

    gdt_code:

 

        dw  0xffff      ; 段大小为4GB 

        dw  0           ; 基地址(24位) 

        db  0 

        db  10011010b   ; 属性描述位,指明此是代码段,可读可执行 

        db  11001111b   ; 具体的每一位是代表什么可查Intel CPU编程手册 

        db  0           ; 没有的可以去网上下,也可以找我要 

    gdt_data_addr equ $ - gdt   ; 求得数据段在GDT表中的位置 

    gdt_data:

 

        dw  0xffff 

        dw  0 

        db  0 

        db  10010010b   ; 指明此是数据段,可读可写 

        db  11001111b 

        db  0 

    gdt_end:

 

    gdt_addr:

 

        dw  gdt_end - gdt - 1   ; GDT 表的大小 

        dd  gdt                 ; GDT 表的位置 

; --------------------------------------------------------------------------- 

main:

                       ; 引导程序从此处开始执行 

    mov [bootdrive] , dl    ; 得到启动的驱动器号 

    xor ax , ax             ; 设置 DS 

    mov ds , ax 

    ; 清屏 

    ;mov ax , 3             ; 设置清屏功能号 

    ;int 0x10               ; 调用 BIOS 10 号中断清屏 

    .ResetFloppy            ; 重置磁盘,不是必须的,主要是为了安全起见 

    mov ax , 0              ; 设置重置磁盘的功能号 

    mov dl , [bootdrive]    ; 选择启动磁盘 

    int 0x13                ; 调用 BIOS 13 号中断重置磁盘 

    jc .ResetFloppy         ; 如果出错则重试 

    .ReadFloppy             ; 读内核到内存中 0000:

9000 (es:

bx)处, 

                            ; 为什么要读到9000处,这不是一定的, 

                            ; 你可以读到另外一个合适的地址 

                            ; 什么地址合适?

怎样知道一个地址合不合适?

待会再说 

    xor ax , ax             ; 设置 es 寄存器 

    mov es , ax 

    mov bx , 0x9000 

    mov ah , 2              ; 设置读磁盘功能号 

    mov dl , [bootdrive]    ; 设置欲读驱动器号 

    mov ch , 0              ; 磁头号 

    mov cl , 2              ; 起始扇区号,从第二个扇区开始读, 

                            ; 第一个扇区是引导扇区,第二个才是内核所在 

    mov al , 17             ; 需读入扇区的数量,此处读了17个扇区, 

                            ; 是怕内核较大,读少了读不完 

    int 13h                 ; 调用 BIOS 13 号中断开始读扇区, 

                            ; 此中断会将数据读到 es:

bx 处 

    jc .ReadFloppy          ; 如果出错则重试(ah中是错误号,为0则没错) 

    mov dl , [bootdrive]    ; 停止驱动器 

    mov edx , 0x3f2 

    mov al , 0x0c 

    out dx , al 

    cli                     ; 关中断 

    lgdt [gdt_addr]         ; 载入 GDT 的描述符 

    mov eax , cr0           ; 下面三句设置 cr0 的第 0 位(PE位)为1, 

                            ; 表示进入保护模式 

    or eax , 1 

    mov cr0 , eax 

    jmp gdt_code_addr:

code_32   ; 跳入32位的代码段中 

[BITS 32] 

code_32:

 

    mov ax , gdt_data_addr  ; 以下三句设置 DS,ES,SS,FS,GS 

                            ; 为数据段描述表的位置 

    mov ds , ax 

    mov es , ax 

    mov ss , ax 

    mov fs , ax 

    mov gs , ax 

    mov esp , 0xffff        ; 设置堆栈的头指针 

    jmp gdt_code_addr:

0x9000    ; 跳入内核, 

                                ; gdt_code_addr是定义的代码段的描述符所在的索引 

                                ; 由于我们先前是把内核读到了 0x9000的位置, 

                               

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 小学教育 > 语文

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1