文章目录
写在前面
《自己动手写操作系统》让我有了手写操作系统的思路。《x86汇编语言-从实模式到保护模式》让我明白了保护模式,中断,分页机制。可看完这两本书,我有很多天都不敢下手去写操作系统。后来,我想到了一个思路:我可以先学习linux内核,之后,再回过头来做自己想做的事情。又不是只有我一个人“借鉴”,这个世界多了去了,绝大多数人只不过是跟跑。所有的资料都是我自己收集的。人工智能gpt是我的“老师”。为了手写操作系统,我学会了Makefile gcc gdb c语言 汇编(nasm汇编,as86汇编,gun as汇编)等等。
在思路上,我感觉手写操作系统已经没有什么问题了。现在就是边学边做。
额,我咋感觉我就是在学习大佬们写的东西 ????? 嗯,从小到大 。。。。。。
相关环境,源码,课程,书籍,linux0.11编译运行效果
课程实验内容:
操作系统实验内容:https://www.lanqiao.cn/courses/115
参考书籍----我查了好多资料:
《汇编语言程序设计(美)布鲁姆 著,马朝晖 等译》:linux内核涉及到的 gun as 汇编。
《LINUX内核完全剖析:基于0.12内核》: 不知道内核代码含义,可以看该书。
其他:
地址:https://gitee.com/YMQ_1314/linux_os_learn。
环境:deepin20.7操作系统
编译运行效果:
此次实验的基本内容是:
阅读《Linux 内核完全注释》的第 6 章,对计算机和 Linux 0.11 的引导过程进行初步的了解;
按照下面的要求改写 0.11 的引导程序 bootsect.s
有兴趣同学可以做做进入保护模式前的设置程序 setup.s。
改写 bootsect.s 主要完成如下功能:
bootsect.s 能在屏幕上打印一段提示信息“XXX is booting…”,其中 XXX 是你给自己的操作系统起的名字,例如 LZJos、Sunix 等(可以上论坛上秀秀谁的 OS 名字最帅,也可以显示一个特色 logo,以表示自己操作系统的与众不同。)
改写 setup.s 主要完成如下功能:
bootsect.s 能完成 setup.s 的载入,并跳转到 setup.s 开始地址执行。而 setup.s 向屏幕输出一行"Now we are in SETUP"。
setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。
setup.s 不再加载 Linux 内核,保持上述信息显示在屏幕上即可。
一、修改bootsect.s打印 Hello! LiChanghe is booting…
bios加载启动磁盘第一扇区数据到内存0x7c00 处,并跳入该地址开始执行程序。而第一扇区就是编译后的bootsect.s文件。不太了解的话,可以看《手写操作系统》。
bootsect.s主要功能就是移动自身到内存0x90000处,加载setup.s到内存0x90200处,加载system到0x10000处,最后跳入setup.s (即0x90200处)。
按照《LINUX内核完全剖析:基于0.12内核》搞懂代码逻辑。书讲得很详细,我就不在这里说了。上代码:
1、bootsect.s文件里边找到msg1
msg1: ! 开机调用BIOS中断显示的信息,共36个字符
.byte 13,10 ! 回车、换行的ACSII码
.ascii "Hello! LiChanghe is booting..."
.byte 13,10,13,10
2、显示设置
! Print some inane message
! 显示信息:“‘Loading’+回车+换行”,并显示包括回车和换行控制符在内的9个字符。
! BIOS中断0x10功能号ah = 0x03,读光标位置。
! 输入:bh = 页号
! 返回:ch = 扫描开始线; cl= 扫描结束线; dh = 行号(0x00顶端); dl = 列号(0x00最左边)。
! BIOS中断0x10功能号 ah = 0x13,显示字符串。
! 输入: al = 放置光标的方式及规定属性。 0x01-表示使用bl中的属性值,光标停在字符串结尾处。
! es:bp 此寄存器对指向要显示的字符串起始位置处。cx = 显示的字符串字符数。bh = 显示页面号;
! bl = 字符属性。 dh = 行号; dl = 列号。
mov ah,#0x03 ! read cursor pos
xor bh,bh ! 首先读光标位置。返回光标位置值在dx中
int 0x10 ! dh - 行(0--24); dl - 列(0--79)
mov cx,#36 ! 共显示36个字符
mov bx,#0x0007 ! page 0, attribute 7 (normal)
mov bp,#msg1 ! es:bp 指向要显示的字符串
mov ax,#0x1301 ! write string, move cursor
int 0x10 ! 写字符串并移动光标到串结尾处
3、修改build.c
这个文件非常熟悉,我在base目录里边的更有意思。一开始我是用自己写的工具writeToDisk.c,writeToVhd.c。 在接触build.c之后,我就更新了下。build.c最有意思的地方-----去掉文件的特殊头部信息。
如下图:
现在我要修改build.c的话,我就直接用strncmp了。
其实就是在system处理之前加个判断。
if(strncmp(argv[3],"none",4) == 0){
return(0);
}
// system处理
if ((id=open(argv[3],O_RDONLY,0))<0)
4、编译运行
编译 make BootImage
运行 cd … && ./run
二、修改setup.s输出"Now we are in SETUP"以及硬件参数
setup.s主要利用实模式下bios中断读取机器信息的特点,然后把这些数据写到0x90000处(即覆盖掉bootsect代码),接着将system移动到0x00000处,关中断,设置中断描述符表(IDT)跟全局描述符(GDT),并在gdt中设置当前内核代码段的描述和数据段的描述符,最后跳入到system里边的head.s(head.s才会去设置内存分页)代码处。
如果你想要了解idt跟gdt的话,可以看《x86汇编语言-从实模式到保护模式》。
由于当时的硬件条件的限制,system模块长度不会超过512KB。可是现在我在64位机器编译,其objcopy工具处理之后的kernel内存大小为128.3M。倘若我要让其在64位系统下编译运行,看来我需要再了解一些东西。
1、setup.s 向屏幕输出一行"Now we are in SETUP"
在未关中断、进入保护模式之前进行操作。直接将bootsect.s里边的代码复制过来,但是需要注意es地址。
如果已经关中断,进入保护模式呢? ---- 可以直接在8086显存映射的内存区域(即文本模式下,段地址0xB800, 偏移地址0x0000~0xFFFF)里边写数据。
mov ax,0xb800 ;指向文本模式的显示缓冲区
mov es,ax
;以下显示字符串"Label offset:"
mov byte [es:0x00],'L'
mov byte [es:0x01],0x07
mov byte [es:0x02],'a'
mov byte [es:0x03],0x07
mov byte [es:0x04],'b'
mov byte [es:0x05],0x07
mov byte [es:0x06],'e'
mov byte [es:0x07],0x07
mov byte [es:0x08],'l'
mov byte [es:0x09],0x07
mov byte [es:0x0a],' '
mov byte [es:0x0b],0x07
mov byte [es:0x0c],"o"
mov byte [es:0x0d],0x07
mov byte [es:0x0e],'f'
mov byte [es:0x0f],0x07
mov byte [es:0x10],'f'
mov byte [es:0x11],0x07
mov byte [es:0x12],'s'
mov byte [es:0x13],0x07
mov byte [es:0x14],'e'
mov byte [es:0x15],0x07
mov byte [es:0x16],'t'
mov byte [es:0x17],0x07
mov byte [es:0x18],':'
mov byte [es:0x19],0x07
jmp $
由于中断还是可用的,我这里就直接复制粘贴了。
打印信息:
entry start
start:
! 还没有关中断跟进入保护模式。但是程序已经加载到0x90200处,所以es需要设置成0x90200
mov ax,#SETUPSEG
mov es,ax
mov ah,#0x03 ! read cursor pos
xor bh,bh ! 首先读光标位置。返回光标位置值在dx中
int 0x10 ! dh - 行(0--24); dl - 列(0--79)
mov cx,#26 ! 共显示36个字符
mov bx,#0x0007 ! page 0, attribute 7 (normal)
mov bp,#msg ! es:bp 指向要显示的字符串
mov ax,#0x1301 ! write string, move cursor
int 0x10 ! 写字符串并移动光标到串结尾处
信息:
msg: ! 开机调用BIOS中断显示的信息
.byte 13,10 ! 回车、换行的ACSII码
.ascii "Now we are in SETUP"
.byte 13,10,13,10
效果:
2、setup.s 能获取至少一个基本的硬件参数(如内存参数、显卡参数、硬盘参数等),将其存放在内存的特定地址,并输出到屏幕上。
主要思路: 利用bios中断获取硬件信息,然后进行打印。其打印的思路 ----- 先打印单个字符,再以此为基础进行扩展(比如打印字符串,十进制打印数字,十六进制打印数字,打印回车换行等等)。
注意:字符请对照acsii码表。
核心函数:
! 该子程序使用中断0x10的OxOE功能,以电传方式在屏幕上写一个字符。光标会自动移到下一个
! 位置处。如果写完一行光标就会移动到下一行开始处。如果已经写完一屏最后-行,则整个屏幕
! 会向上滚动一行。字符0x07 (BEL)、0x08 (BS)、OxOA(LF)和OxOD (CR)被作为命令不会显示。
! 输入:AL——欲写字符;BH——显示页号;BL——前景显示色(图形方式时)。
print_char:
push ax
push cx
mov bh,#0x00 !显示页面
mov cx,#0x01
mov ah,#0x0e
int 0x10
pop cx
pop ax
ret
以上边的代码为基础进行扩展
打印字符串:
! 显示位于DS:SI处以NULL(0x00)结尾的字符串。
print_string:
lodsb
and al,al
jz fin
call print_char ! 显示al中的一个字符。
jmp print_string
fin:
ret
十六进制打印:
! 显示十六进制数字的子程序。显示值放在寄存器al中(0~255)
! idiv 有符号除
! 如果除数为8位,则被除数为16位,则结果的商存放与al中,余数存放于ah中。
! 如果除数为16位,则被除数为32位,则结果的商存放与ax中,余数存放于dx中。
! 如果除数为32位,则被除数为64位,则结果的商存放与eax中,余数存放于edx中。
print_hex:
push ax
push cx
mov ah,#0x00
mov cl,#0x10
idiv cl ! 除法 ah---余数 al---商
cmp al,#0x0f
jbe lthex ! 不大于0x0f则跳转
call print_hex
jmp skiphex
lthex:
cmp al,#0x09
jbe lthex_9
add al,#0x57
call print_char
jmp skiphex
lthex_9:
add al,#0x30
call print_char
skiphex:
mov al,ah
cmp al,#0x09
jbe skiphex_9
add al,#0x57
call print_char
jmp print_hex_end
skiphex_9:
add al,#0x30
call print_char
print_hex_end:
pop cx
pop ax
ret
获取扩展内存大小:
! Get memory size (extended mem, kB)
! 取扩展内存的大小值(KB)
! 利用BIOS中断0x15功能号 ah = 0x88 取系统所含扩展内存大小并保存在内存0x90002处。
! 返回:ax = 从0x100000(1m)处开始的扩展内存大小(KB)。若出错则CF置位,ax = 出错码。
mov ah,#0x88
int 0x15
mov [2],ax
call print_extend_memory_info
! 打印扩展内存信息 ax存放扩展内存大小
print_extend_memory_info:
push ax
push bx
push cx
push dx
push ds
push es ! 寄存器入栈
mov dx,ax ! dx存放扩展内存大小
mov ax,#SETUPSEG
mov ds,ax
mov es,ax
call print_enter_wrap ! 打印回车换行
sub si,si
lea si,extend_memeory_size_title
call print_string
sub ax,ax ! ax清零
mov al,dh
call print_oct
sub ax,ax ! ax清零
mov al,dl
call print_oct
sub si,si
lea si,unit_KB ! 打印大小单位
call print_string
call print_enter_wrap ! 打印回车换行
pop es
pop ds
pop dx
pop cx
pop bx
pop ax ! 寄存器出栈
ret
在上边的思路之上,更新之前欢迎信息:
! 还没有关中断跟进入保护模式。但是程序已经加载到0x90200处,所以es,ds需要设置成0x90200
call print_welcome
call print_lab_job_info
! 测试 print_oct
mov al,#0x61
call print_oct
call print_enter_wrap
! 测试 print_oct
mov al,#0xaf
call print_hex
call print_enter_wrap
! 打印实验信息
print_lab_job_info:
push ax
push bx
push cx
push dx
push ds
push es
mov ax,#SETUPSEG
mov es,ax
mov ah,#0x03 ! read cursor pos
xor bh,bh ! 首先读光标位置。返回光标位置值在dx中
int 0x10 ! dh - 行(0--24); dl - 列(0--79)
mov cx,#25 ! 共显示多少个字符
mov bx,#0x0007 ! page 0, attribute 7 (normal)
mov bp,#lab_job_info ! es:bp 指向要显示的字符串
mov ax,#0x1301 ! write string, move cursor
int 0x10 ! 写字符串并移动光标到串结尾处
pop es
pop ds
pop dx
pop cx
pop bx
pop ax
ret
! 打印欢迎信息
print_welcome:
push ax
push bx
push cx
push dx
push ds
push es
mov ax,#SETUPSEG
mov ds,ax
lea si,welcome_info
call print_string
pop es
pop ds
pop dx
pop cx
pop bx
pop ax
ret
! 打印光标信息
print_cursor_info:
push ax
push bx
push cx
push dx
push ds
push es
mov ax,#SETUPSEG
mov ds,ax
mov es,ax
sub si,si
lea si,cursor_info
call print_string
call print_enter_wrap
sub si,si
lea si,cursor_line_number
call print_string
sub al,al
mov al,bh
call print_oct
call print_tab
sub si,si
lea si,cursor_column_number
call print_string
sub al,al
mov al,dl
call print_oct
call print_enter_wrap
pop es
pop ds
pop dx
pop cx
pop bx
pop ax
ret
pop es
pop ds
pop dx
pop cx
pop bx
pop ax
ret
! 打印tab
print_tab:
call print_space
call print_space
call print_space
call print_space
ret
! 打印一个空格
print_space:
mov al,#0x20 ! 显示一个空格
call print_char
ret
! 打印回车换行
print_enter_wrap:
push ax
push bx
push cx
push dx
push ds
push es
mov al,#0x0a
call print_char
mov al,#0x0d
call print_char
pop es
pop ds
pop dx
pop cx
pop bx
pop ax
ret
效果:
总结
主要是提了自己学了啥,用了啥工具,看了啥书以及做了几个有趣的实验。