哈工大操作系统学习1——计算机的启动流程
2023-12-14 04:24:20
引言
从上电开始,计算机都做了什么
- 首先我们要知道,汇编语言中特定寄存器的作用如下:
- AX,BX,CX,DX是16位数据寄存器,可以分成两个8位使用。如AX可以分为AH(AX的高8位)和AL(AX的低8位)
- CS (Code Segment):代码段寄存器
- DS (Data Segment):数据段寄存器,记录访问数据的段地址
- SS (Stack Segment):堆栈段寄存器
- ES (Extra Segment):附加段寄存器
- SP:指向堆栈顶的偏移地址
- 上电后,CPU处于实模式,会去执行cs:ip地址处的指令
- cs:ip地址即:cs寄存器值<<4 + ip寄存器值 例:0x07c0:0x0000 = 0x07c0 << 4 + 0x0000 = 0x7c00
- 接下来我们看看,计算机上电后都做了什么,首先执行的是0x7c00处的bootsect.s代码:
bootsect.s部分
- 检查硬件
- 从引导扇区读取512字节到0x7c00,并从0x7c00开始运行
bootsect.s
,代码如下。这段代码将0x7c00处的256个字(就是512字节)移动到0x90000处
;bootsect.s
BOOTSEG = 0x07c0
INITSEG = 0x9000
SETUPSEG = 0x9020
.globl begtext,begdata,begbss,endtext,enddata,endbss
;.text 等是伪操作符,告诉编译器产生文本段,.text用于标识文本段的开始
.text
begtext:
;数据段
.data
begdata:
;未初始化数据段
.bss
begbss:
;上面六条指令的意思是3个段重叠,不分段
;关键字entry告诉链接器“程序入口”,此条语句的地址就是0x7c00
entry start
start:
mov ax, #BOOTSEG
mov ds, ax
mov ax, #INITSEG
mov es, ax
;此时es=9000 ds=07c0
mov cx, #256
sub si, si ;si=si-si=0
sub di, di ;di=di-di=0
;rep指令会根据cx的值,重复执行后面的指令,这里重复执行了256次movw
;movw 将DS:SI的内容移动到ES:DI
rep movw
;jmpi是跳转指令,使cs=INITSEG(0x9000),ip=go标号的地址
;程序接下来会执行cs:ip的指令,刚好是下一条指令,但是因为程序已经挪到0x9000了,jmpi指令必须要写
jmpi go, INITSEG
;程序作用是将0x07c0:0x0000处的256个字(就是512字节)移动到0x9000:0x0000处,然后跳转到go处执行
- 读取setup的4个扇区到0x90200,调用Ok_load_setup模块显示load字符,read_it模块读入system模块
go:
mov ax,cs ;cs=0x9000
mov ds,ax ;数据段寄存器设为0x9000
mov es,ax ;附加段寄存器设为0x9000
mov ss,ax ;堆栈段寄存器设为0x9000`bootsect.s`,代码如下。这段代码
mov sp,#0xff00 ;堆栈顶的偏移地址设为0xff00
load_setup:
mov dx,#0x0000
mov cx,#0x0002
mov bx,#0x0200
mov ax,#0x0200+SETUPLEN ;SETUPLEN=4
;现在我们有:
;ah=0x02,al=0x04 bh=0x02,bl=0x00 ch=0x00,cl=0x02 dh=dl=0x00
;0x13 是 BIOS 读磁盘扇区的中断,把对应的4个扇区读到0x90200
;读磁盘号 = 0x02-ah = 0
;扇区数量 = SETUPLEN = 4
;柱面号 = ch = 0
;开始扇区 = cl = 2
;磁头号 = dh = 0
;驱动器号 = dl = 0
;内存地址 = es:bx = 0x90200
int 0x13 ;BIOS 中断
;跳转到ok_load_setup程序段
jnc ok_load_setup
mov dx,#0x0000
mov ax,#0x0000 ; 复位
int 0x13
j load_setup ; 重读
;我们暂时不分析这段代码,只需要知道int 0x10是显示中断,这段代码将磁盘上存放的字符显示出来,并调用read_it读入system模块。所有模块加载完成后,使用jmpi指令跳转到0x90200,将控制权交给setup模块
Ok_load_setup: // 载入 setup 模块
mov dl,#0x00
mov ax,#0x0800 //ah=8 获得磁盘参数
int 0x13
mov ch,#0x00
mov sectors,cx
mov ah,#0x03
xor bh,bh
int 0x10 // 读光标
mov cx,#24
mov bx,#0x0007
mov bp,#msg1
mov ax,#1301
int 0x10 // 显示字符
mov ax,#SYSSEG //SYSSEG=0x1000
mov es,ax
call read_it ;读入 system 模块
jmpi 0,SETUPSEG
-
代码执行完毕后,计算机内存分布如下图:
-
总结一下,我们可以知道bootsect.s做了这么几件事:
- 从引导扇区读取512字节到0x7c00
- 从0x7c00开始运行,将0x7c00处的256个字(就是512字节)移动到0x90000处
- 读取setup的4个扇区到0x90200,显示load字符,读入system模块
- PC返回到0x90200,将控制权交给setup
-
bootsect.s成功地将各个模块从磁盘中加载到内存中,接下来轮到setup模块了!
setup.s部分
- setup模块的代码如下,主要做了以下工作:
- 获取物理内存大小等硬件信息,写到0x90000中
- 代码迁移,将(0x90000-0x120000)处的代码移动到(0-0x90000)
- 其他不是特别重要的工作
start:
mov ax,#INITSEG
mov ds,ax
mov ah,#0x03
xor bh,bh
int 0x10 ;取光标位置 dx
mov [0],dx
mov ah,#0x88
;int 0x15获得了物理内存大小,获取的值放到ax中
int 0x15
;间接寻址,在0x90002处存放
;这样OS就知道了内存的大小
mov [2],ax ...
cli ;不允许中断
mov ax,#0x0000
cld
;循环移动,将(0x90000-0x120000)处的代码移动到(0-0x90000)
do_move:
mov es,ax
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax
sub di,di
sub si,si
mov cx,#0x8000
;ds = 0x1000 si = 0
;es = 0 di = 0
;cx = 0x8000
;rep指令会根据cx的值,重复执行后面的指令,这里重复执行了0x8000次
;movsw将DS:SI(0x90000)的内容移动到ES:DI(0),一次移动1个字
rep movsw
jmp do_move
setup模块退出舞台后,需要把权力交给别的模块,怎么来做这件事呢?
- 我们知道在实模式下,
段<<4+偏移
/CS<<4+IP
表示地址空间,具有20位,寻址空间只有1MB,所以我们需要切换到保护模式! - 在保护模式下,CPU将CS看成下标,查表得到基地址,再加上IP中记录的偏移量,得到最终的地址。这样能够增大地址空间,在模式切换之前,我们必须要初始化这个表,这个工作由setup来做!
- 同时的,在保护模式中,int n的解释也与实模式不同
- 设置保护模式下的中断和寻址,这是setup模块做的第四步
end_move: mov ax,#SETUPSEG
mov ds,ax
lidt idt_48
lgdt gdt_48// 设置保护模式下的中断和寻址
idt_48:.word 0 .word 0,0 // 保护模式中断函数表
gdt_48:.word 0x800 .word 512+gdt,0x9
gdt: .word 0,0,0,0
.word 0x07FF, 0x0000, 0x9A00, 0x00C0
.word 0x07FF, 0x0000, 0x9200, 0x00C0
- gdt表的字段解释如下图:
- 将CPU切换到保护模式,跳转到0地址处执行
- CPU有一个32位寄存器CR0,最后一位如果是0,就是实模式(16位);如果是1,就是保护模式(32位)
mov ax,#0x0001
mov cr0,ax
;PC设置到table[8]+0=0处
jmpi 0,8
system部分
system模块的第一部分代码head.s
- CPU已经进入了保护模式,所以要使用32位汇编
stratup_32:
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
mov %as,%fs
mov %as,%gs//指向gdt的0x10项(数据段)
lss _stack_start,%esp //设置栈(系统栈)
//重新设置gdt表和idt表
call setup_idt
call setup_gdt
xorl %eax,%eax
1:incl %eax
movl %eax,0x000000
cmpl %eax,0x100000
je 1b //0地址处和1M地址处相同(A20没开启),就死循环
jmp after_page_tables //页表,什么东东 ?
setup_idt:
lea ignore_int,%edx
movl $0x00080000,%eax
movw %dx,%ax
lea _idt,%edi
movl %eax,(%edi)
- system模块执行完成后,需要跳到main.c处执行
- 函数调用是通过栈执行的,head.s利用栈跳转到main函数
- main函数不应该返回,一但返回,操作系统就会死机
after_page_tables:
pushl $0
pushl $0
pushl $0
pushl $_main jmp set_paging
L6: jmp L6
setup_paging: 设置页表 ret
进入main函数
- 初始化软硬件设备
void main(void)
{
mem_init();
trap_init();
blk_dev_init();
chr_dev_init();
tty_init();
time_init();
sched_init();
buffer_init();
hd_init();
floppy_init();
sti();
move_to_user_mode();
if(!fork()){init();}
}
//将mem_map数组置为0,表明内存空间没有使用
void mem_init(long start_mem,long end_mem)
{
int i;
for(i=0; i<PAGING_PAGES; i++)
mem_map[i] = USED;
i = MAP_NR(start_mem);
end_mem -= start_mem;
end_mem >>= 12;
while(end_mem -- > 0)
mem_map[i++] = 0;
}
总结
- 整理一下,我们可以知道计算机上电后,主要做了如下步骤:
-
检查硬件
-
从引导扇区读取512字节(即
bootsect.s
)到0x7c00,并从0x7c00开始运行? bootsect.s主要做了以下几件事:
- 将0x7c00处的256个字(就是512字节)移动到0x90000处
- 读取setup的4个扇区到0x90200,显示load字符,读入system模块
- PC返回到0x90200,将控制权交给setup模块
- 结束运行后,模块内存分布如下图所示
-
从0x90200处开始执行setup模块
? setup模块主要做了以下几件事:- 获取物理内存大小等硬件信息,写到0x90000中
- 代码迁移,将(0x90000-0x120000)处的代码移动到(0-0x90000)
- 其他不是特别重要的工作
- 设置保护模式下的中断表和寻址表
- 将CPU切换到保护模式,跳转到0地址处执行
-
从0x0000处执行system模块
? system模块主要做了以下几件事:- 设置gdt表和idt表、栈等信息
- 跳转到main.c处,初始化内存等信息,进入C语言的世界!
文章来源:https://blog.csdn.net/weixin_51797837/article/details/132740157
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!