哈工大操作系统学习1——计算机的启动流程

2023-12-14 04:24:20

引言

  • 博主是非科班学生,正在学习操作系统 李治军课程,这篇文章是记录的笔记,此外也记录了课件中的部分汇编代码注释
  • 由于仅是一个初学者,如有错误请大家指正

从上电开始,计算机都做了什么

  • 首先我们要知道,汇编语言中特定寄存器的作用如下:
  1. AX,BX,CX,DX是16位数据寄存器,可以分成两个8位使用。如AX可以分为AH(AX的高8位)和AL(AX的低8位)
  2. CS (Code Segment):代码段寄存器
  3. DS (Data Segment):数据段寄存器,记录访问数据的段地址
  4. SS (Stack Segment):堆栈段寄存器
  5. ES (Extra Segment):附加段寄存器
  6. SP:指向堆栈顶的偏移地址
  • 上电后,CPU处于实模式,会去执行cs:ip地址处的指令
  • cs:ip地址即:cs寄存器值<<4 + ip寄存器值 例:0x07c0:0x0000 = 0x07c0 << 4 + 0x0000 = 0x7c00

  • 接下来我们看看,计算机上电后都做了什么,首先执行的是0x7c00处的bootsect.s代码:

bootsect.s部分

  1. 检查硬件
  2. 从引导扇区读取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处执行
  1. 读取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
	
	;0x13BIOS 读磁盘扇区的中断,把对应的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做了这么几件事:

    1. 从引导扇区读取512字节到0x7c00
    2. 从0x7c00开始运行,将0x7c00处的256个字(就是512字节)移动到0x90000处
    3. 读取setup的4个扇区到0x90200,显示load字符,读入system模块
    4. PC返回到0x90200,将控制权交给setup
  • bootsect.s成功地将各个模块从磁盘中加载到内存中,接下来轮到setup模块了!

setup.s部分

  • setup模块的代码如下,主要做了以下工作:
  1. 获取物理内存大小等硬件信息,写到0x90000中
  2. 代码迁移,将(0x90000-0x120000)处的代码移动到(0-0x90000)
  3. 其他不是特别重要的工作
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的解释也与实模式不同
  1. 设置保护模式下的中断和寻址,这是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表的字段解释如下图:
    在这里插入图片描述
  1. 将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; 
}

总结

  • 整理一下,我们可以知道计算机上电后,主要做了如下步骤:
  1. 检查硬件

  2. 从引导扇区读取512字节(即bootsect.s)到0x7c00,并从0x7c00开始运行

    ? bootsect.s主要做了以下几件事:

    1. 将0x7c00处的256个字(就是512字节)移动到0x90000处
    2. 读取setup的4个扇区到0x90200,显示load字符,读入system模块
    3. PC返回到0x90200,将控制权交给setup模块
    4. 结束运行后,模块内存分布如下图所示
      在这里插入图片描述
  3. 从0x90200处开始执行setup模块
    ? setup模块主要做了以下几件事:

    1. 获取物理内存大小等硬件信息,写到0x90000中
    2. 代码迁移,将(0x90000-0x120000)处的代码移动到(0-0x90000)
    3. 其他不是特别重要的工作
    4. 设置保护模式下的中断表和寻址表
    5. 将CPU切换到保护模式,跳转到0地址处执行
  4. 从0x0000处执行system模块
    ? system模块主要做了以下几件事:

    1. 设置gdt表和idt表、栈等信息
    2. 跳转到main.c处,初始化内存等信息,进入C语言的世界!

文章来源:https://blog.csdn.net/weixin_51797837/article/details/132740157
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。