setup.S之A20

产生的历史

16位实模式情况下,地址16位段基地址:16位偏移,这样最大地址表示值是FFFF:FFFF,也就是0x10ffef。
1)在8088时代,只有20条地址线,也就是寻址 0xFFFFF,这样能用线性地址表示出来的0x100000-0x10ffef的地址,在20根地址线表示不出来,通过求模,然后回环到,比如0x10ffef%0x100000=0xffef,也就是读写0x10ffef等于读写0xffef。
2)在80286时代,有24条地址线,硬件上可以支持0xffffff,实模式下支持地址最大范围0x10ffef,显然能表达出0x100000-0x10ffef。
关闭0x100000bit,则在0x100000-0x10ffef会回环。打开0x100000bit,在0x100000-0x10ffef不会回环。
通过打开/关闭0x100000bit,来判断0x100000-0x10ffef,是否回环。

A20与保护模式关系

1)不是打开A20,就进入保护模式系统。很多系统把A20是否打开,作为切换实模式和保护模式的一部分。
2)保护模式下,80286中A20关闭(始终为0),则用户的地址只能是:0 - (1MB-1), 2 - (3MB-1), 4 - (5MB-1)。

怎么判断A20在保护模式下是否打开

原理:如果A20Gate被打开了,则在实模式下,程序员可以直接访问100000H-10FFEFH之间的内存,如果A20 Gate被禁止,则在实模式下,若程序访问100000H-10FFEFH之间的内存,则会被硬件自动转换为0H-FFEFH之间的内存,所以我们可以利用这个差异来检测A20 Gate是否被打开。

#ifndef CONFIG_X86_VOYAGER
# This routine tests whether or not A20 is enabled.  If so, it
# exits with zf = 0.
#
# The memory address used, 0x200, is the int $0x80 vector, which
# should be safe.

A20_TEST_ADDR = 4*0x80

a20_test:
	pushw	%cx   
	pushw	%ax   
	xorw	%cx, %cx
	movw	%cx, %fs			# Low memory
	decw	%cx
	movw	%cx, %gs			# High memory area
	movw	$A20_TEST_LOOPS, %cx
	movw	%fs:(A20_TEST_ADDR), %ax
	pushw	%ax
a20_test_wait:
	incw	%ax
	movw	%ax, %fs:(A20_TEST_ADDR)
	call	delay				# Serialize and make delay constant
	cmpw	%gs:(A20_TEST_ADDR+0x10), %ax
	loope	a20_test_wait

	popw	%fs:(A20_TEST_ADDR)
	popw	%ax
	popw	%cx
	ret	

fs=0x gs=0xffff
fs:(A20_TEST_ADDR)=0x0:A20_TEST_ADDR=A20_TEST_ADDR
gs:(0x200+0x10)=0xffff0+0x10+0x200=0x100000+A20_TEST_ADDR
则(0x100000+A20_TEST_ADDR)%0x100000=A20_TEST_ADDR,所以地址0x100000+A20_TEST_ADDR回环后的地址是A20_TEST_ADDR.
则向 A20_TEST_ADDR写入一个值,则从0x100000+A20_TEST_ADDR读出也是这个值,则说明没有开启A20。
也就是cmpw %gs:(A20_TEST_ADDR+0x10), %ax,ZF=1说明没有开启A20;ZF=0说明开启了A20

控制A20的方式

1 通过键盘控制器

打开A20Gate的方法是通过设置8042芯片输出端口(64h)的2nd-bit.
Bit 0 is used to reset the CPU (go to real mode) - a reset happens when bit 0 is 0.
Bit 1 is used to control A20 - it is enabled when bit 1 is 1, disabled when bit 1 is 0.

2 通过系统控制器Port A(0x92)

Bit 0 (w): writing 1 to this bit causes a fast reset (used to switch back to real mode; for MCA this took 13.4 ms).
Bit 1 (rw): 0: disable A20, 1: enable A20.
使用这个口来打开A20的好处
1)键盘控制器的响应慢
2)嵌入式系统中没有键盘控制器

        inb     $0x92, %al                      #
        orb     $02, %al                        # "fast A20" version
        outb    %al, $0x92                      # some chips have only this

这样会带来一个问题,端口0x92读出的值的Bit 0是1的话,然后写进端口0x92,会导致一个快速启动。

a20_fast:
	inb	$0x92, %al			# Configuration Port A
	orb	$0x02, %al			# "fast A20" version
	andb	$0xFE, %al			# don't accidentally reset
	outb	%al, $0x92

多添加一句andb $0xFE, %al ,将bit 0位置位为0。

3 bois(int15 ax=240x)

INT 15 AX=2400 disable A20
INT 15 AX=2401 enable A20
INT 15 AX=2402 query status A20
INT 15 AX=2403 query A20 support (kbd or port 92)
bios例程不知道是用的哪个端口,还是其他技术 ?

setup.S(linux-2.6.11)中的A20逻辑

#ifndef CONFIG_X86_VOYAGER
a20_try_loop:

	# First, see if we are on a system with no A20 gate.
a20_none:
	call	a20_test   //0x90b45
	jnz	a20_done

	# Next, try the BIOS (INT 0x15, AX=0x2401)
a20_bios:
	movw	$0x2401, %ax
	pushfl					# Be paranoid about flags
	int	$0x15
	popfl

	call	a20_test
	jnz	a20_done

	# Try enabling A20 through the keyboard controller
#endif /* CONFIG_X86_VOYAGER */
a20_kbc:
	call	empty_8042

#ifndef CONFIG_X86_VOYAGER
	call	a20_test			# Just in case the BIOS worked
	jnz	a20_done			# but had a delayed reaction.
#endif

	movb	$0xD1, %al			# command write
	outb	%al, $0x64
	call	empty_8042

	movb	$0xDF, %al			# A20 on
	outb	%al, $0x60
	call	empty_8042

#ifndef CONFIG_X86_VOYAGER
	# Wait until a20 really *is* enabled; it can take a fair amount of
	# time on certain systems; Toshiba Tecras are known to have this
	# problem.
a20_kbc_wait:
	xorw	%cx, %cx
a20_kbc_wait_loop:
	call	a20_test
	jnz	a20_done
	loop	a20_kbc_wait_loop

	# Final attempt: use "configuration port A"
a20_fast:
	inb	$0x92, %al			# Configuration Port A
	orb	$0x02, %al			# "fast A20" version
	andb	$0xFE, %al			# don't accidentally reset
	outb	%al, $0x92

	# Wait for configuration port A to take effect
a20_fast_wait:
	xorw	%cx, %cx
a20_fast_wait_loop:
	call	a20_test
	jnz	a20_done
	loop	a20_fast_wait_loop

	# A20 is still not responding.  Try frobbing it again.
	# 
	decb	(a20_tries)
	jnz	a20_try_loop
	
	movw	$a20_err_msg, %si
	call	prtstr

a20_die:
	hlt
	jmp	a20_die

a20_tries:
	.byte	A20_ENABLE_LOOPS

a20_err_msg:
	.ascii	"linux: fatal error: A20 gate not responding!"
	.byte	13, 10, 0

	# If we get here, all is good
a20_done:

逻辑:
1.判断A20是否打开,如果打开直接跳转a20_done
2.尝试bois(int15 ax=240x)打开A20,是否成功,如果成功则跳转到a20_done
3.尝试键盘控制器打开A20,是否成功,如果成功则跳转到a20_done
4.尝试系统控制器Port A(0x92),是否成功,如果成功则跳转到a20_done

<think>嗯,用户现在想了解Linux操作系统中bootsect.s之后的加载过程或文件。首先,我需要回顾之前的对话历史,确保回答的连贯性。用户之前询问了引导扇区的功能,现在深入到了bootsect.s之后的阶段。 根据用户提供的引用内容,特别是引用[1]和[2],bootsect.s主要负责加载setup.s和system模块。现在用户关注的是之后的流程,即setup.s和head.s的作用,以及如何加载内核。 首先,我需要确认bootsect.s执行后的步骤。根据引用,bootsect.s会跳转到setup程序,也就是setup.s,位于内存的0x90200地址。接下来,setup.s会收集硬件信息,比如内存大小、显卡参数等,并移动system模块到0x00000位置,覆盖BIOS中断向量表。之后,切换到保护模式,跳转到head.s继续执行。 用户可能想知道这些步骤的具体细节,比如setup.s如何工作,head.s的作用,以及如何加载内核。需要详细说明每个阶段的任务,比如setup.s使用BIOS中断获取硬件参数,然后开启A20地址线,设置GDT,进入保护模式。然后head.s初始化IDT、GDT,启用分页,最后跳转到main.c的主函数。 另外,用户提到的文件位置也很重要,比如setup.s位于硬盘的2~5扇区,system模块从第6扇区开始。需要解释这些文件的加载过程,以及它们在内存中的位置变化。 还要注意用户可能对技术细节感兴趣,比如实模式到保护模式的切换,分页机制的初始化等。需要用简单明了的语言解释这些概念,避免过于晦涩。同时,结合之前的回答,保持结构清晰,分模块说明,可能使用表格或公式来展示内存地址的变化。 需要确保引用正确的资料,如引用[1]和[2]中的内容,比如bootsect移动自身到0x90000,加载setup0x90200,然后setup移动system到0x00000。同时,用户可能对后续流程中的关键步骤,如head.s的作用和main.c的调用感兴趣。 最后,生成相关问题时要围绕setup.s、head.s、保护模式切换、内核初始化等主题,帮助用户进一步探索。需要检查是否有遗漏的细节,比如setup程序如何收集硬件信息,或者head.s如何准备分页表,确保回答全面准确。</think>### Linux启动流程中`bootsect.s`后的加载过程详解 根据Linux 0.11内核源码及启动机制[^1][^2],`bootsect.s`完成初始引导后,系统进入**三阶段核心加载流程**。以下是每个阶段的详细技术解析: --- #### 一、`setup.s`阶段:硬件参数收集与模式切换(地址:`0x90200`) 1. **核心任务** - 通过BIOS中断获取**硬件参数**: ```c int 0x15 -> 获取内存大小(AX=0xE801) int 0x10 -> 获取显示模式参数 int 0x11 -> 获取设备列表 ``` - 将`system`模块从`0x10000`移动到`0x00000`(覆盖BIOS中断向量表)[^1] - 开启**保护模式**: - 设置GDT(全局描述符表) $$ \text{GDT结构} = \begin{cases} \text{空描述符} \\ \text{代码段基址=0, 长度=16MB} \\ \text{数据段基址=0, 长度=16MB} \end{cases} $$ - 设置CR0寄存器第0位(PE标志)为1 - 通过`jmpi 0,8`跳转到保护模式代码段 2. **关键操作** - 关闭中断(CLI) - 加载GDT和IDTR(中断描述符表寄存器) - 激活A20地址线(突破实模式1MB内存限制) --- #### 二、`head.s`阶段:保护模式初始化(地址:`0x00000`) 1. **内存布局重构** | 内存区域 | 地址范围 | 内容 | |------------------|--------------|---------------------| | 内核代码段 | 0x00000000 | `head.s`及内核代码 | | 页目录表 | 0x00001000 | 4KB初始页表 | | 页表 | 0x00002000 | 覆盖低端4MB内存 | 2. **关键技术动作** - 初始化**分页机制**: ```assembly movl $0x00002007, 0x00001000 ; 页目录项指向第一个页表 movl $0x00000007, 0x00002000 ; 页表项映射物理地址0x00000000 ``` - 设置CR3寄存器指向页目录表 - 启用CR0寄存器的分页标志(PG位) - 调用`setup_paging`完成4MB物理内存映射 - 最终通过`jmp after_page_tables`跳转到`main()`函数入口 --- #### 三、`main.c`阶段:内核初始化(地址:`0x00000`之后) 1. **启动序列** ```c void main(void) { mem_init(); // 内存管理初始化 trap_init(); // 中断向量表设置 sched_init(); // 调度器初始化 buffer_init(); // 缓冲区缓存初始化 hd_init(); // 硬盘驱动初始化 sti(); // 重新开启中断 move_to_user_mode(); if (!fork()) { init(); } // 创建第一个进程 } ``` 2. **关键数据结构初始化** - 建立`task_struct`进程控制块 - 初始化`buffer_head`磁盘缓存链表 - 加载根文件系统(根据`bootsect.s`保存的`root_dev`参数) --- #### 四、文件加载与存储布局 根据源码配置[^2]: | 文件 | 磁盘扇区 | 内存地址 | 长度 | |------------|----------|----------------|------------| | `bootsect` | 1 | 0x7C000x90000 | 512字节 | | `setup` | 2-5 | 0x90200 | 4×512字节 | | `system` | 6-240 | 0x100000x00000 | 235×512字节| --- ### 相关问题 1. 保护模式下GDT和页表如何协同工作完成内存映射? 2. `head.s`中为何需要手动建立4MB的页表结构? 3. 在`main()`函数执行前,内核已经完成了哪些关键硬件初始化? 4. 为何`bootsect.s`要将自身从0x7C00移动到0x90000? : `bootsect.s`通过BIOS中断加载后续模块并传递控制权 [^2]: Linux 0.11内核源码中启动文件的磁盘布局设计 [^3]: 保护模式切换与分页机制的底层实现原理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值