linux0.12 head.s代码解析

  1. 重新设置IDT和GDT,为256个中断门设置默认的中断处理函数
  2. 检查A20地址线是否启用
  3. 设置数学协处理器
  4. 将main函数相关的参数压栈
  5. 设置分页机制,将页表映射到0~16MB的物理内存上
  6. 返回main函数执行

源码详细注释如下:

c 复制代码
/*
 *  linux/boot/head.s
 *
 *  (C) 1991  Linus Torvalds
 */

/*
 *  head.s contains the 32-bit startup code.
 *
 * NOTE!!! Startup happens at absolute address 0x00000000, which is also where
 * the page directory will exist. The startup code will be overwritten by
 * the page directory.
 */
.text
.globl _idt,_gdt,_pg_dir,_tmp_floppy_area
_pg_dir:
startup_32:
# 段寄存器先指向setup.S中的数据段
	movl $0x10,%eax
	mov %ax,%ds
	mov %ax,%es
	mov %ax,%fs
	mov %ax,%gs
	lss _stack_start,%esp # 设置栈指针和栈段寄存器
# 设置IDT和GDT
	call setup_idt
	call setup_gdt
# 重新加载段寄存器和栈
	movl $0x10,%eax
	mov %ax,%ds
	mov %ax,%es
	mov %ax,%fs
	mov %ax,%gs
	lss _stack_start,%esp
# 检查A20地址线是否启用
	xorl %eax,%eax
1:	incl %eax            # check that A20 really IS enabled
	movl %eax,0x000000   # loop forever if it isn't
	cmpl %eax,0x100000
	je 1b
/*
 * NOTE! 486 should set bit 16, to check for write-protect in supervisor
 * mode. Then it would be unnecessary with the "verify_area()"-calls.
 * 486 users probably want to set the NE (#5) bit also, so as to use
 * int 16 for math errors.
 */
	movl %cr0,%eax          # check math chip
	andl $0x80000011,%eax   # 清楚其他位,保留PG PE ET位
	orl $2,%eax             # set MP位,表示启用数学协处理器
	movl %eax,%cr0          # 写回CR0寄存器
	call check_x87
	jmp after_page_tables

# 设置协处理器
check_x87:
	fninit                  # 初始化协处理器
	fstsw %ax               # 取协处理器状态字到ax寄存器
	cmpb $0,%al
	je 1f                   # 如果协处理器状态字为0,则有协处理器,跳转1
	movl %cr0,%eax
	xorl $6,%eax            # 清除MP、设置EM位
	movl %eax,%cr0
	ret
.align 2
1:	.byte 0xDB,0xE4         # 等价于fsetpm,用于设置协处理器模式
	ret

setup_idt:
/*
 * %eax:
 * 位31-16: 0x0008 (段选择子)
 * 位15-0:  ignore_int地址的低16位
 * %edx:
 * 位31-16: 0x8E00 (中断门属性)
 * 位15-0:  ignore_int地址的高16位
*/
	lea ignore_int,%edx
	movl $0x00080000,%eax
	movw %dx,%ax
	movw $0x8E00,%dx

	lea _idt,%edi          # edi指向IDT的基地址
	mov $256,%ecx          # 256个中断门
rp_sidt:
	movl %eax,(%edi)
	movl %edx,4(%edi)
	addl $8,%edi           # 移动到下一个中断门描述符
	dec %ecx               # 循环计数器减1
	jne rp_sidt
	lidt idt_descr         # 加载IDT描述符
	ret

/*
 *  setup_gdt
 *
 *  This routines sets up a new gdt and loads it.
 *  Only two entries are currently built, the same
 *  ones that were built in init.s. The routine
 *  is VERY complicated at two whole lines, so this
 *  rather long comment is certainly needed :-).
 *  This routine will beoverwritten by the page tables.
 */
setup_gdt:
	lgdt gdt_descr
	ret

/*
 * 物理地址    内容          大小      用途
 * 0x0000   页目录(_pg_dir)  4KB      页目录表
 * 0x1000   页表0(pg0)      4KB       映射0-4MB物理内存
 * 0x2000   页表1(pg1)      4KB       映射4-8MB物理内存  
 * 0x3000   页表2(pg2)      4KB       映射8-12MB物理内存
 * 0x4000   页表3(pg3)      4KB       映射12-16MB物理内存
 * 0x5000   软盘缓冲区      4KB       软盘DMA缓冲区
 */
.org 0x1000
pg0:
.org 0x2000
pg1:
.org 0x3000
pg2:
.org 0x4000
pg3:
.org 0x5000
/*
 * tmp_floppy_area is used by the floppy-driver when DMA cannot
 * reach to a buffer-block. It needs to be aligned, so that it isn't
 * on a 64kB border.
 */
_tmp_floppy_area:
	.fill 1024,1,0

after_page_tables:
	pushl $0
	pushl $0
	pushl $0               # 这些是main函数的参数
	pushl $L6              # 如果main函数决定返回,则返回地址为L6
	pushl $_main           # main函数地址压栈
	jmp setup_paging       # 跳转设置分页机制
L6:
	jmp L6                 # main应该永远不会返回,以防万一,我们不知道会发生什么

# 默认中断处理程序
int_msg:
	.asciz "Unknown interrupt\n\r"
.align 2
ignore_int:
	pushl %eax
	pushl %ecx
	pushl %edx
	push %ds
	push %es
	push %fs
	movl $0x10,%eax
	mov %ax,%ds
	mov %ax,%es
	mov %ax,%fs
	pushl $int_msg
	call _printk
	popl %eax
	pop %fs
	pop %es
	pop %ds
	popl %edx
	popl %ecx
	popl %eax
	iret

.align 2
setup_paging:
# 清零页目录和页表区域
	movl $1024*5,%ecx
	xorl %eax,%eax
	xorl %edi,%edi
	cld;rep;stosl
# 设置页目录项
	movl $pg0+7,_pg_dir
	movl $pg1+7,_pg_dir+4
	movl $pg2+7,_pg_dir+8
	movl $pg3+7,_pg_dir+12
# 通过循环将页表映射到物理内存,从高地址向低地址填充
	movl $pg3+4092,%edi
	movl $0xfff007,%eax
	std
1:	stosl
	subl $0x1000,%eax
	jge 1b

	xorl %eax,%eax
	movl %eax,%cr3          # 设置CR3寄存器,指向页目录的起始地址
	movl %cr0,%eax
	orl $0x80000000,%eax
	movl %eax,%cr0          # 启用分页机制
	ret                     # 由于之前压栈了main,返回main函数执行

.align 2
.word 0
idt_descr:
	.word 256*8-1		# idt contains 256 entries
	.long _idt
.align 2
.word 0
gdt_descr:
	.word 256*8-1		# so does gdt (not that that's any
	.long _gdt		# magic number, but it works for me :^)

	.align 3
_idt:	.fill 256,8,0		# idt is uninitialized

_gdt:	.quad 0x0000000000000000	/* NULL descriptor */
	.quad 0x00c09a0000000fff	    /* 内核代码段 16Mb */
	.quad 0x00c0920000000fff	    /* 内核数据段 16Mb */
	.quad 0x0000000000000000	    /* 暂时未使用 */
	.fill 252,8,0			        /* space for LDT's and TSS's etc */
相关推荐
uncle_ll3 小时前
Git 别名:用简短命令大幅提升开发效率
linux·git
半梦半醒*3 小时前
ansible中配置并行以及包含和导入
linux·运维·ssh·ansible·负载均衡
路溪非溪3 小时前
Linux驱动开发重要操作汇总
linux·运维·驱动开发
Coision.4 小时前
硬件:51单片机的按键、中断、定时器、PWM及蜂鸣器
linux·嵌入式硬件·51单片机
wydxry5 小时前
Linux 系统上配置 GitHub 账号并克隆私有仓库
linux·github
青草地溪水旁7 小时前
66多路复用 I/O 函数——`select`函数
linux·socket编程·i/o复用
轻松Ai享生活7 小时前
5 天学习 Linux Kernel 主要原理 | Day 2:Linux 进程管理与调度
linux
轻松Ai享生活7 小时前
5 天学习 Linux Kernel 主要原理 | Day 1:Linux 内核基础与架构总览
linux
哈喽H9 小时前
centos系统的linux环境不同用户,环境变量不同如何配置?
linux·centos