• ch1_系统启动_setup.S


    1 功能分析

    大写的.s 后缀名, 是为了说明是一个16位, 实模式下的汇编语言, 小写的 s 是保护模式下的汇编语言;

    1.1 使用中断,读取机器参数

    setup.S 是一个操作系统的加载程序, 主要作用使用 ROM BIOS 中断读取机器系统数据, 并将这些数据保存到0X90000开始的位置, 即覆盖掉原先bootsect 程序所在地方,

    所取得的参数和保留的内存位置见表 6–2 所示。

    这些参数将被内核中相关程序使用,例如字符设备驱动程序集中的 console.c 和 tty_io.c程序等。

    利用BIOS中断程序填下面这张表格的内容:

    在这里插入图片描述
    如表中,

    1)保存光标的位置
    2)得到扩展内存的大小
    3)得到显示卡当前的显示模式
    4)检测显示方式
    5)读取硬盘参数表信息

    1.2 setup 移动 system模块

    然后, setup 程序将 system 模块从 0x10000-0x8ffff 整块向下移动到内存绝对地址 0x00000 处(当时认为内核系统模块 system 的长度不会超过此值:512KB)。

    因为我们已经使用完所有的BIOS中断程序,所以由BIOS在0地址处建立的中断向量表也可以覆盖掉。

    ; 之前bootsect引导程序将system模块移动到(0x10000)处,
    ; 并把自己移动到(0x90000)处,把setup加载在它后面
    
    
    ; setup 程序将整个system模块移动到0x00000处,
    ; 即把从0x10000到0x8ffff的内存数据块整块的向内存地址低端移动了0x10000的位置
    
    	mov	ax,#0x0000
    	cld			! 'direction'=0, movs moves forward
    do_move:
    	mov	es,ax		! destination segment
    	add	ax,#0x1000
    	cmp	ax,#0x9000  ! 判断代码是否移动完成
    	jz	end_move    ! 移动完成则跳转
    	mov	ds,ax		! source segment
    	sub	di,di
    	sub	si,si
    	mov cx,#0x8000  ! 循环移动,循环次数,每次循环完次数减 移动0x8000字
    	rep		! 用于把内容从ds:si 复制es:di  以字节单位
    	movsw           ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中
    	jmp	do_move
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    1.3 加载描述符表

    原始的实模式下, 16位时, 寻址方式是: 段寄存器左移四位 + 偏移寄存器。
    此时,加载的描述符表包含两种:

    • . 中断描述符表 (IDT) interupt descriptor
    • . 全局描述符表 GDT (global descriptor table )

    全局描述符表的出现, 是为了解决32位 保护模式下的寻址方式问题

    GDT 表: 是使用段寄存器CS作为一个索引在一个地址表里找到32位的基地址,
    然后再和偏移寄存器EIP 中的32位 数值相加,
    得到最终的地址放到地址总线上去选定内存。

    而为了让硬件找到这个表, GDT 表的起始地址被放在了一个GDTR 的寄存器中;


    为进入保护模式做准备,

    1. 加载中断描述符表寄存器(IDTR)和全局描述符表寄存器(GDTR),
    2. 开启 A20 地址线,
    3. 重新设置两个中断控制芯片 8259A,
    4. 将硬件中断号重新设置为 0x20 - 0x2f。

    实模式和保护模式下的寻址方式的区别
    在这里插入图片描述
    a)图记住一点,段的最大长度固定为64KB;

    b)图段寄存器中保存的不再是段基地址而是描述符表的索引,并且段的最大长度是可变的。

    在这里插入图片描述

    1.4 模式切换

    setup.s 从实模式 切换到 保护模式,

    设置CPU的机器状态字寄存器CR0的PE位,进入 32 位保护模式运行,并跳转到位于 system 模块最前面部分的 head.s 程序继续运行。

    进入保护模式:jmpi 0,8
    加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式

    ;进入保护模式,只是跳转到绝对地址0x00000处
    ; 加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式
    	mov	ax,#0x0001	! protected mode (PE) bit 保护模式比特位(PE)
    	lmsw	ax		! This is it!             加载状态寄存器
    	;段选择符8表示请求特权0级,使用GDT第二个段描述符
    	jmpi	0,8		! jmp offset 0 of segment 8 (cs)  跳转至cs段偏移地址位0处(system已经移动到0x00000处)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2 .设备的划分

    2.1. 磁盘

    • 软盘: 是早期的产物, 负责从计算机上搬运出数据, 现在这个基本功能使用U盘完成了。 软盘并不是装在电脑里面的,而是可移动的,一般用来存储文件和不同电脑之间进行拷贝文件,就功能上来说它和现在的U盘是一样的,只是外形、存储原理不一样,它的容量要比硬盘小的多,比如最常用的3.5英寸的软盘容量只有1.44MB。

    • 硬盘:一般都装在机箱里面,容量较大,用来存储数据。

    磁盘包括软盘和硬盘

    一个磁盘由多个盘片(如下图中的 0 号盘片)叠加而成。盘片的表面涂有磁性物质,这些磁性物质用来记录二进制数据。因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面。

    在这里插入图片描述

    2.2 磁道

    每个盘片被划分成多个圆圈, 由内向外,圆圈逐个变大, 这样每个圆圈就形成了所谓的磁道。

    在对每个圈,进行分段的划分弧度, 这样一个个分段的弧, 就形成了一个个扇区。
    在这里插入图片描述

    由此,

    1. 不同的磁道, 容量大小不同, 因为圆圈的大小不同;
    2. 同一个磁道下的各个扇区相同, 因为是在同一个磁道下划分的。

    2.3 柱面

    所有的盘面中相对位置相同的磁道组成柱面, 类似于一个圆柱面,
    每个盘面对应一个磁头。所有的磁头都是连在同一个磁臂上的,因此所有磁头只能“共进退”。

    在这里插入图片描述

    磁盘的物理地址
    可用(柱面号,盘面号,扇区号)来定位任意一个“磁盘块”

    可根据该地址读取一个“块”,操作如下:

    ① 根据“柱面号”移动磁臂,让磁头指向指定柱面;

    ② 激活指定盘面对应的磁头;

    ③ 磁盘旋转的过程中,指定的扇区会从磁头下面划过,这样就完成了对指定扇区的读/写

    3. 描述符表

    描述符表其实就是内存中描述符项的一个阵列。
    在这里插入图片描述

    描述符表有两类:

    • 全局描述符表(Gobal descriptor table-GDT)和

    • 局部描述符表(Local descri ptan⁡table−LDT)。

    处理器是通过:
    使用GDTR寄存器来定位GDT表
    LDTR寄存器来定位当前的LDT表。

    这两个寄存器以线性地址的方式保存了描述符表的基地址和表的长度。

    完整setup.s 代码

    !
    !	setup.s		(C) 1991 Linus Torvalds
    !
    ! setup.s is responsible for getting the system data from the BIOS,
    ! and putting them into the appropriate places in system memory.
    ! both setup.s and system has been loaded by the bootblock.
    !
    ! This code asks the bios for memory/disk/other parameters, and
    ! puts them in a "safe" place: 0x90000-0x901FF, ie where the
    ! boot-block used to be. It is then up to the protected mode
    ! system to read them from there before the area is overwritten
    ! for buffer-blocks.
    !
    ; setup从BIOS中获取数据,并将这些数据保存到0x90000开始的位置处(0x90000-0x901FF覆盖了原来bootsect程序所在的地方)
    ; 此时setup和system已经由bootsect引导块加载到内存中
    ; 
    ! NOTE! These had better be the same as in bootsect.s!
    
    INITSEG  = 0x9000	! we move boot here - out of the way 原来bootsect所在段
    SYSSEG   = 0x1000	! system loaded at 0x10000 (65536).   system所在0x10000处
    SETUPSEG = 0x9020	! this is the current segment          本程序所在段地址
    
    .globl begtext, begdata, begbss, endtext, enddata, endbss
    .text
    begtext:
    .data
    begdata:
    .bss
    begbss:
    .text
    
    entry start
    start:
    
    ! ok, the read went well so we get current cursor position and save it for
    ! posterity.
    ; 保存光标的位置
    ; 使用BIOS中断取屏幕当前光标的位置(列,行),保存到内存(0x90000)处,2个byte
    ; 控制台初始化程序会到此处读取该值
    ; BISO 中断0x10 功能号 ah = 0x30 ,读光标的位置
    ; 输入:bh=页号
    ; 返回:返回:ch = 扫描开始线,cl = 结束开始线,dh = 行号(0x00顶端),dl=列号(0x00最左边)
    	mov	ax,#INITSEG ! this is done in bootsect already, but...
    	mov	ds,ax
    	mov	ah,#0x03	! read cursor pos 功能号 ah = 0x30 ,读光标的位置
    	xor	bh,bh
    	int	0x10		! save it in known place, con_init fetches
    	mov	[0],dx		! it from 0x90000.  将ds设置成0x90000(INITSEG)
    
    ! Get memory size (extended mem, kB)
    ; 得到扩展内存的大小
    ; 利用BIOS中断0x15 功能号 ah= 0x88取系统所含扩展内存大小并保存到0x90002处
    ; 返回: ax= 0x10000(1M)处开始的扩展内存大小,若出错CF置位,ax=出错码
    	mov	ah,#0x88
    	int	0x15
    	mov	[2],ax      !扩展内存的大小保存到0x90002处
    
    ! Get video-card data:
    ; 得到显示卡当前的显示模式
    ; 调用BIOS中断0x10,功能号 ah = 0x0f
    ; 返回:ah=字符列数,al=显示模式,bh=显示当前页数
    
    	mov	ah,#0x0f
    	int	0x10
    	mov	[4],bx		! bh = display page
    	mov	[6],ax		! al = video mode, ah = window width
    
    ! check for EGA/VGA and some config parameters
    ; 检测显示方式
    ; 调用BIOS中断0x10, 功能号 ah=0x12,bl=0x10
    	mov	ah,#0x12
    	mov	bl,#0x10
    	int	0x10
    	mov	[8],ax      ! 0x90008 =ax
    	mov	[10],bx     ! 0x9000A = 安装的显示内存,0x9000B = 显示状态
    	mov	[12],cx		!0X9000C = 显卡特性参数
    
    ! Get hd0 data
    ; 取第一个硬盘信息
    ; 第一个硬盘参数表的首地址是中断向量0x41的向量值
    ; 第二个紧跟着对应着中断向量0x46
    ; 下面两个程序分别复制BIOS有关硬盘参数表,
    ; 第一个硬盘存放在0x90080,第二个硬盘存放在0x90090
    	mov	ax,#0x0000
    	mov	ds,ax
    	lds	si,[4*0x41]         !取中断向量0x41对应的地址 ,hd0参数表的地址--> ds:si
    	mov	ax,#INITSEG         
    	mov	es,ax
    	mov	di,#0x0080          !传输的目的地址(0x9000:0x0080) -->es:di
    	mov	cx,#0x10            ! 循环次数,每次循环完次数减一,共传输16个字节
    	rep						! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中
    	movsb                   ! 用于把内容从ds:si 复制es:di  以字节单位
    
    ! Get hd1 data
    
    	mov	ax,#0x0000
    	mov	ds,ax
    	lds	si,[4*0x46]         !取中断向量0x41对应的地址 ,hd0参数表的地址--> ds:si
    	mov	ax,#INITSEG 
    	mov	es,ax
    	mov	di,#0x0090
    	mov	cx,#0x10
    	rep
    	movsb
    
    
    ! Check that there IS a hd1 :-)
    ; 检测是否有第二个硬盘,如果没有则把第2个清零
    ; 利用BIOS中断调用0x13的取盘的类型,功能号 ah =0x15
    	mov	ax,#0x01500
    	mov	dl,#0x81         ! dl = 驱动器号(0x8X是硬盘,0x81是第一个硬盘,0x82是第二个硬盘)
    	int	0x13
    	jc	no_disk1         ! 第二个不存在
    	cmp	ah,#3			 ! ah =类型码 指硬盘
    	je	is_disk1         ! 存在
    
    ; 第二个硬盘不存在,对第二个硬盘表清零
    no_disk1:
    	mov	ax,#INITSEG
    	mov	es,ax
    	mov	di,#0x0090
    	mov	cx,#0x10
    	mov	ax,#0x00
    	rep
    	stosb
    ; 第二个硬盘存在,进入保护模式,从此开始不允许中段
    is_disk1:
    
    ! now we want to move to protected mode ...
    
    	cli			! no interrupts allowed !
    
    ! first we move the system to it's rightful place
    ; bootsect引导程序将system模块移动到(0x10000)处,
    ; 并把自己移动到(0x90000)处,把setup加载在它后面
    ; 下面这段程序将整个system模块移动到0x00000处,
    ; 即把从0x10000到0x8ffff的内存数据块整块的向内存地址低端移动了0x10000的位置
    
    	mov	ax,#0x0000
    	cld			! 'direction'=0, movs moves forward
    do_move:
    	mov	es,ax		! destination segment
    	add	ax,#0x1000
    	cmp	ax,#0x9000  ! 判断代码是否移动完成
    	jz	end_move    ! 移动完成则跳转
    	mov	ds,ax		! source segment
    	sub	di,di
    	sub	si,si
    	mov 	cx,#0x8000   ! 循环移动,循环次数,每次循环完次数减 移动0x8000字
    	rep				! 用于把内容从ds:si 复制es:di  以字节单位
    	movsw           ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中
    	jmp	do_move
    
    ! then we load the segment descriptors
    ; 加载段描述符,设置全局描述符表和中断描述表
    
    end_move:
    	mov	ax,#SETUPSEG	! right, forgot this at first. didn't work :-)
    	mov	ds,ax
    ; lidt指令用于加载中断描述符表(IDT)寄存器
    ; 中断描述符表中每一个8个字节对应每个中断发生时所需要的中断程序地址入口
    	lidt	idt_48		! load idt with 0,0   
    ; lgdt指令用于加载全局描述符表(GDT)寄存器
    ; 全局描述符表中每个描述符项(8字节)描述了保护模式下数据段和代码段的信息
    	lgdt	gdt_48		! load gdt with whatever appropriate
    
    ! that was painless, now we enable A20
    ; 开启A20地址线,为了能够访问和使用1MB以上的物理内存
    	call	empty_8042    ! 测试8042状态寄存器,等待输入缓冲器空,
    	mov	al,#0xD1		  ! command write 0xD1命令码表示写数据到8042的P2端口
    	out	#0x64,al
    
    	call	empty_8042    !等待输入缓冲器空,看命令是否被接受
    	mov	al,#0xDF		  ! A20 on
    	out	#0x60,al
    	call	empty_8042    !若此时输入缓冲器为空,则表示A20线也选通 
    
    ! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
    ! we put them right after the intel-reserved hardware interrupts, at
    ! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
    ! messed this up with the original PC, and they haven't been able to
    ! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
    ! which is used for the internal hardware interrupts as well. We just
    ! have to reprogram the 8259's, and it isn't fun.
    
    ; 重新对中断进行编程
    
    	mov	al,#0x11		! initialization sequence
    	out	#0x20,al		! send it to 8259A-1  发送到8259A主芯片
    ;   0x00eb直接使用机器码表示两条相对跳转指令,起延时作用
    	.word	0x00eb,0x00eb		! jmp $+2, jmp $+2
    	out	#0xA0,al		! and to 8259A-2   再发送到8259A从芯片
    	.word	0x00eb,0x00eb
    ;   系统硬件中断号被设置成0x20开始
    	mov	al,#0x20		! start of hardware int's (0x20)
    	out	#0x21,al		!送主芯片ICW2命令字,设置起始中断,要送奇端口
    	.word	0x00eb,0x00eb
    
    	mov	al,#0x28		! start of hardware int's 2 (0x28)
    	out	#0xA1,al        !送主芯片ICW2命令字,从芯片的起始中断号
    
    	.word	0x00eb,0x00eb
    	mov	al,#0x04		! 8259-1 is master
    	out	#0x21,al        !ICW3
    	.word	0x00eb,0x00eb
    	mov	al,#0x02		! 8259-2 is slave
    	out	#0xA1,al
    	.word	0x00eb,0x00eb
    	mov	al,#0x01		! 8086 mode for both
    	out	#0x21,al
    	.word	0x00eb,0x00eb
    	out	#0xA1,al
    	.word	0x00eb,0x00eb
    	mov	al,#0xFF		! mask off all interrupts for now
    	out	#0x21,al
    	.word	0x00eb,0x00eb
    	out	#0xA1,al
    
    ! well, that certainly wasn't fun :-(. Hopefully it works, and we don't
    ! need no steenking BIOS anyway (except for the initial loading :-).
    ! The BIOS-routine wants lots of unnecessary data, and it's less
    ! "interesting" anyway. This is how REAL programmers do it.
    !
    ! Well, now's the time to actually move into protected mode. To make
    ! things as simple as possible, we do no register set-up or anything,
    ! we let the gnu-compiled 32-bit programs do that. We just jump to
    ! absolute address 0x00000, in 32-bit protected mode.
    
    ; 进入保护模式,只是跳转到绝对地址0x00000处
    
    ; 加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式
    	mov	ax,#0x0001	! protected mode (PE) bit 保护模式比特位(PE)
    	lmsw	ax		! This is it!             加载状态寄存器
    	;段选择符8表示请求特权0级,使用GDT第二个段描述符
    	jmpi	0,8		! jmp offset 0 of segment 8 (cs)  跳转至cs段偏移地址位0处(system已经移动到0x00000处)
    
    ! This routine checks that the keyboard command queue is empty
    ! No timeout is used - if this hangs there is something wrong with
    ! the machine, and we probably couldn't proceed anyway.
    
    ; 检差键盘命令队列是否为空
    ; 只有当输入缓冲器为空(键盘控制器状态寄存器位1 = 0)才可以进行写命令
    empty_8042:
    	.word	0x00eb,0x00eb      !延时作用
    	in	al,#0x64	! 8042 status port
    	test	al,#2		! is input buffer full?
    	jnz	empty_8042	! yes - loop
    	ret
    
    ; GDT全局描述符表开始处,描述符表由多个8字节长的描述符项组成,
    ; 3个描述符项
    ; 第一项没有作用,但是必须存在
    ; 第二项是系统代码段描述符
    ; 第三项是系统数据段描述符
    gdt:
    	.word	0,0,0,0		! dummy  第一个描述符 不用
    
    ; 在GDT表这里的偏移量是0x80,它是内核代码段选择符的值
    	.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
    	.word	0x0000		! base address=0
    	.word	0x9A00		! code read/exec
    	.word	0x00C0		! granularity=4096, 386
    
    ; 在GDT表这里的偏移量是0x10,它是内核数据段选择符的值
    	.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
    	.word	0x0000		! base address=0
    	.word	0x9200		! data read/write
    	.word	0x00C0		! granularity=4096, 386
    
    ; 加载中断描述符表寄存器(idtr)
    ; 这里设置一个长度为0的空表
    idt_48:
    	.word	0			! idt limit=0
    	.word	0,0			! idt base=0L
    
    ; 加载全局描述符表寄存器(gdtr)
    ; GDT表长度为2kb
    gdt_48:
    	.word	0x800		! gdt limit=2048, 256 GDT entries
    	.word	512+gdt,0x9	! gdt base = 0X9xxxx
    	
    .text
    endtext:
    .data
    enddata:
    .bss
    endbss:
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
  • 相关阅读:
    程序的耦合
    Java日志源码详解,SpringBoot日志 slf4j、logback、log4j
    第4章 docker仓库管理
    交互式shell和非交互式shell、登录shell和非登录shell
    区块链9999999666666
    Elasticsearch近实时架构
    【读论文】【精读】3D Gaussian Splatting for Real-Time Radiance Field Rendering
    《公共政策学》重点整理
    MyBatis基础之注解与SQL 语句构建器
    OCR开源工具箱MMOCR安装及使用示例(英文识别)
  • 原文地址:https://blog.csdn.net/chumingqian/article/details/128108687