本文将使用虚拟软盘运行最小操作系统代码。
运行本章示例代码
1、windows下打开Ubuntu 18.04 LTS,使用cd
命令切换到第一章示例代码目录,x 选择a或者b。
cd chapter1/x
2、创建一个大小为1M的文件TINIX.IMG,内容全为0。
dd if=/dev/zero of=TINIX.IMG bs=512 count=2880
命令解释:dd:用指定大小的块拷贝一个文件,并在拷贝的同时进行指定的转换。
if=FILE 从FILE中读取数据,而不是默认的标准输入。
of=FILE 往FILE中写入数据,而不是默认的标准输出。
ibs=BYTES 读取数据时,一次性读出BYTES大小的块,如果不指定,默认512字节。
obs=BYTES 写入数据时,一次性写入BYTES大小的块,如果不指定,默认512字节。
bs=BYTES 统一ibs和obs的值(如果指定了ibs或者obs,则bs会将ibs和obs覆盖掉)。
count=N 总共读取N*ibs字节数的数据,当然写入的数据也是这个大小
3、将TINIX.IMG格式化为vfat(FAT32)格式的盘符。
sudo mkfs.vfat TINIX.IMG
4、将编译生成的boot.bin写入虚拟盘中。写入大小为512字节。
dd conv=notrunc if=boot.bin of=TINIX.IMG bs=512 count=1
命令解释:conv=CONVERSION,数据写入文件时的转换规则,notrunc 表示如果目的文件已经存在,那么不要截断文件内容。默认是会截断成0字节大小。一般配合oflag=append使用,不截断并且以追加方式写入数据。
5、使用qemu运行程序。
qemu-system-i386 -fda TINIX.IMG -boot a -m 64M
6、运行结果如下所示:
调试示例程序
【待添加】
计算机启动过程
1、按下电源按钮后,计算机首先读取一块ROM芯片,这块芯片里的程序是”基本输入输出系統”(Basic Input/Output System),即BIOS。
2、BIOS会检查计算机硬件是否满足运行条件,如果硬件出现问题,主板会发出不同含义的蜂鸣,启动中止。
3、硬件检查完成后,BIOS会将控制权交给下一阶段的启动程序,注意,“下一阶段的启动程序”可能存放在硬盘中,也可能存放在CD/DVD中,或者软盘中等等,可以设置BIOS选择从哪个设备启动。BIOS找到一个设备后,会读取该设备的第一个扇区,也就是读取最前面的512个字节。如果这512个字节的最后两个字节是0x55和0xAA,则BIOS认为它是一个引导扇区(Boot Sector)。表明这个设备可以用于启动;如果不是,表明设备不能用于启动,控制权于是被转交给”启动顺序”中的下一个设备。
4、BIOS找到了“下一阶段的启动程序”所在设备,会读取该设备的第一个扇区,即读取最前面的512字节,称为主引导记录。主引导记录会告诉计算机下一步到哪里去找操作系统。一旦BIOS发现了引导扇区,就会将其装载到内存的0000:7C00h处,然后跳转入0000:7C00h处将控制权交个这段引导代码。这就是为什么第1行用的是07C00h了。
5、计算机读取”主引导记录”前面446字节的机器码之后,运行事先安装的“启动管理器”bootloader,由用户选择启动哪个操作系统。如果你安装了多个操作系统,那么就要从这步做出选择了。
6、好了,选择操作系统(内核)后,会加载内核,下面就交给内核去处理了。
源代码详解
chapter1/a/boot.asm 源代码
org 07c00h ; 告诉编译器程序加载到7c00处
mov ax, cs ; ds,es都指向cs相同的段
mov ds, ax
mov es, ax
call DispStr ; 跳转到显示字符串程序
jmp $ ; 无限循环
DispStr: ;定义label:DispStr
mov ax, BootMessage ; 将字符串BootMessage的首地址传给ax寄存器
mov bp, ax ; ES:BP = 串地址
mov cx, 25 ; CX = 串长度
mov ax, 01301h ; AH = 13, AL = 01h
mov bx, 000ch ; 页号为0(BH = 0) 黑底红字(BL = 0Ch,高亮)
mov dl, 0
int 10h ; 10h 号中断
ret ;子程序返回
BootMessage: db "Hello, misonyo OS world!" ;定义字符串常量
times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节
dw 0xaa55 ; 结束标志
源代码涉及到的NASM汇编语法
- org:origin缩写,在汇编语言源程序的开始通常都用一条ORG伪指令来实现规定程序的起始地址。如果不用ORG规定则汇编得到的目标程序将从0000H开始。
- mov:
- call:这条指令实现子程序(过程、函数等)的调用及返回。call指令首先将当前执行指令地址入栈,然后无条件转移到由标签指示的指令。与其它简单的跳转指令不同,call指令保存调用之前的地址信息(当call指令结束后,返回到调用之前的地址)。
- jmp:跳转指令。’$’计算得到它本身所在源代码行的开始处的地址;’jmp $’表示无限循环。
- ret:子程序完成任务后返回指令。
- db:伪指令,定义字节类型变量。占一个字节。开辟一块内存,并后后面的参数放入内存中。
- dw:伪指令,定义字类型变量,占2个字节。
- times: 重复指令或数据。第2个参数为指令重复的次数,后面为指令。’KaTeX parse error: Can’t use function ‘$’ in math mode at position 15: ‘计算当前段开始处的地址,’$̲-’:本行距离程序开始处的相对距离。
80386寄存器简介
80386共有7种类型寄存器:
1、通用寄存器(EAX、EBX、ECX、EDX、ESP、EBP、ESI、EDI)(32位)
- EAX:Accumulator,累加器,常用于算术、逻辑运算。
- EBX:Base,基址寄存器,常用于地址索引。
- ECX:Count,计数寄存器,常用于计数及保存计算值。
- EDX:Data,数据寄存器,常用于数据传递。
- ESP:Stack Pointer,堆栈指针寄存器,指向目前的堆栈位置。
- EBP:Base Pointer,基址指针寄存器,可用作SS的一个相对基址位置。
- ESI:Source Index,源变址寄存器。
- EDI:Destinatin Index,目标变址寄存器。
2、段寄存器(CS、SS、DS、ES、FS、GS)(16位)
- CS:Code Segment,代码段寄存器
- SS:Stack Segment,堆栈段寄存器
- DS:Data Segment,数据段寄存器
- ES\FS\GS:Extra Segment,附加段寄存器。
- 这些段寄存器中存放的不再是某个段的基地址,而是某个段的选择符(Selector)。因为16 位的寄存器无法存放32 位的段基地址,段基地址只好存放在段的描述符(Descriptor)中。
3、指令指针寄存器和标志寄存器(EIP、EFLAGS)
- EIP:指令指针寄存器
- EFLAGS:标志寄存器
- 指令指针寄存器(EIP)中存放下一条将要执行指令的偏移量(offset ),这个偏移量是相对于目前正在运行的代码段寄存器(CS)而言的。偏移量加上当前代码段的基地址,就形成了下一条指令的地址。
4、系统表寄存器(GDTR、IDTR、LDTR、TR)
- GDTR:Global Descriptor Table Register,全局描述符表寄存器。48 位寄存器,用来保存全局描述符表(GDT)的32 位基地址和GDT 的大小(16位)。
- LDTR:Local Descriptor Table Register,16 位寄存器,保存局部描述符表LDT 段的选择符。
- IDTR:Interrupt Descriptor Table Register,48 位寄存器,用来保存中断描述符表(IDT)的32 位基地址和IDT的大小(16位)。
- TR:Task State Register,16 位寄存器,用于保存任务状态段TSS 段的16 位选择符。
5、控制寄存器(CR0、CR1、CR2、CR3、CR4)
6、调试寄存器(DR0、DR1、DR2、DR3、DR4、DR5、DR6、DR7)
7、测试寄存器(TR6、TR7)
今天的文章《orang’s一个操作系统的实现》第一章:动手写一个最小的操作系统学习笔记分享到此就结束了,感谢您的阅读。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/89539.html