Linux0.11从开机加电到执行main的过程

摘要

从开机加电到main函数一般涉及到汇编、C程序、uboot等等;一般分为三部分执行:
一、启动BIOS(uboot)准备实模式下的中断向量表和中断服务程序
二、从启动盘加载操作系统到内存,加载操作系统程序的工作就是利用第一步中准备的中断服务程序实现的
三、为执行32位的main做过度

启动BIOS准备模式下的中断向量表和中断服务程序

在加电的一开始内存里是没有任何东西的,但CPU的逻辑电路被设计为只能运行内存里的程序,如果要运行操作系统(把操作系统看成是一段C代码写的程序即可,本质上还是一段程序),就必须将硬盘中的操作系统程序加载到RAM中。

一般加载操作系统都是由Firmware(不知道拼写对没有)完成的,即BIOS或者UBOOT,像X86是读取指定位置的数据;(这一段还需调查下仔细改)

BIOS是如何启动的?

一、启动原理
在开机的一瞬间呢,不能靠软件的方法来执行BIOS那么就只能用硬件的方式进行实现。在X86上,加电会先进入16位实模式状态运行,CPU的硬件逻辑会设计为加电瞬间强行将CS的值置为0XF000、IP的值置为0XFFF0,这样PC指针即CS:IP就会指向0XFFFF0这个地址位置。
(在实模式下CS:IP为16位,即IP,寻址空间为0X00000-0XFFFFF;在保护模式下为线性地址,指令指针为32位,即EIP)

将BIOS代码的第一条放在0XFFFF0位置即可执行BIOS程序;

在这里插入图片描述

Q:BIOS如何从硬盘加载到内存????

内存:
其中0~FFFFFH的低端1MB内存非常特殊,因为最初的8086处理器能够访问的内存最大只有1MB,这1MB的低端640KB被称为基本内存,而A0000H~BFFFFH要保留给显示卡的显存使用,C0000H~FFFFFH则被保留给BIOS使用,其中系统BIOS一般占用了最后的64KB或更多一点的空间,显卡BIOS一般在C0000H~C7FFFH处,IDE控制器的BIOS在C8000H~CBFFFH处。

当我们按下电源开关时,电源就开始向主板和其它设备供电,此时电压还不太稳定,主板上的控制芯片组会向CPU发出并保持一个RESET(重置)信号,让CPU内部自动恢复到初始状态,但CPU在此刻不会马上执行指令。当芯片组检测到电源已经开始稳定供电了(当然从不稳定到稳定的过程只是一瞬间的事情),它便撤去RESET信号(如果是手工按下计算机面板上的Reset按钮来重启机器,那么松开该按钮时芯片组就会撤去RESET信号),CPU马上就从地址FFFF0H处开始执行指令,从前面的介绍可知,这个地址实际上在系统BIOS的地址范围内,无论是Award BIOS还是AMI BIOS,放在这里的只是一条跳转指令,跳到系统BIOS中真正的启动代码处。

详细见 <BIOS研发技术剖析>

640KB~1MB 上位内存(这个区域的地址分配给ROM,相应的384KB的RAM被屏蔽掉。所谓的影子内存技术,就是把ROM内容读取到对应地址的RAM中,以后系统就从RAM中读取数据,而不是从原来的ROM读取数据,从而提高速度。

阴影是指在启动期间将BIOS代码从慢速ROM芯片复制到快速RAM芯片的技术,以便更快地访问BIOS例程。DOS和其他操作系统可能经常访问BIOS例程。如果从RAM而不是从较慢的ROM芯片访问BIOS,系统性能将大大提高。

相关资料:

https://www.rigacci.org/docs/biblio/online/firmware/shadow.htm
https://blog.csdn.net/weixin_43858632/article/details/120113231

PC系统的ROM BIOS存储在主板上的ROM芯片中。在较旧的主板上,BIOS将驻留在一个、两个或四个EPROM芯片中。在较新的主板上,BIOS可能保存在闪存芯片中。在这两种情况下,芯片一次访问8位,而486或386DX系统上的RAM一次访问32位。此外,ROM芯片的访问时间也较慢-150ns至200ns,而RAM的访问时间为60ns或70ns。

64KB内存范围F000-FFFF保留给ROM BIOS。ROM芯片可在此地址访问。RAM中也存在相同的地址范围。如果启用了阴影(在某些系统中为CMOS设置选项,在其他系统中为非可选选项),BIOS将在启动过程中从ROM芯片复制到RAM中的同一位置。系统BIOS阴影通常应在所有PC上启用。

CMOS设置中通常有一个选项来启用视频BIOS阴影。视频BIOS通常位于视频卡上的ROM芯片中。在具有内置视频的主板上,视频BIOS可能与系统BIOS位于同一128K芯片中(可能在E000处寻址)。视频BIOS的地址通常为C000-C7FF。如果启用了视频BIOS阴影,系统BIOS将在其引导过程中将视频BIOS复制到位于C000-C7FF的RAM中。视频BIOS阴影通常会在所有PC上启用。

BIOS还允许隐藏其他ROM,如网卡上的ROM。这些ROM通常位于C800和EFFF之间的上部内存区域中。


二、BIOS在内存中加载中断向量表和中断服务程序
在X86电脑上BIOS会做很多操作如检测显卡、内存等等但是最重要的是在内存中建立中断向量表和中断服务程序

BIOS在一开始会用内存里一段空间(比如用1KB,这个在一些系统中是可以自己定义的)构建一个中断向量表(Interrupt Vector Table),在紧挨着它的位置用256字节的内存空间构建BIOS数据区(0x00400~0x004FF),并在大约57KB后的位置加载了8KB左右的与中断向量表相应的若干中断服务程序(Interrupt Service)。

0x00100就是256字节,那么0X00400就是1KB 也就是从0x00000-0x003FF就是1KB;

中断向量表有256个中断向量,每个中断向量占4个字节,其中两个是CS两个是IP,直接拿到地址跳到具体的中断服务程序;

加载操作系统内核程序并为保护模式做准备

从这里执行uboot操作,把软盘中的操作系统程序加载至内存;

对于Linux0.11,计算机将三批逐次加载操作系统的内核代码,第一批由BIOS中断的int 0x19把第一扇区bootsect的内容加载到内存,第二批第三批在bootsect的指挥下,分别把其后的4个扇区和随后的240个扇区的内容加载到内存;

一个扇区512字节

一、加载引导程序(bootsect)

经过BIOS代码之后计算机完成了一系列的自检操作,计算机硬件体系结构的设计和BIOS会让CPU接受到一个0X19中断,CPU接受到这个中断后进入中断向量表寻找0X19这个中断向量。

接下来中断向量把CPU指向0X0E6F2,即启动加载服务程序的入口地址(像不像加载BIOS里对应的Win10 boot Manager地址?)。这个中断服务程序的作用就是把硬盘中的第一扇区中的程序(512B)加载到内存中的指定位置。这个中断服务程序的功能是BIOS实现设计好的,代码是固定的与linux无关。无论LINUX的内核是如何设计的这段BIOS程序所要做的就是“找到硬盘”并“加载第一扇区”,其余什么也没做什么也不知道;

按照上述规则,将0x19中断向量所指的中断服务程序,将512B的程序加载到内存的0X07C00处;在第一扇区程序载入后标志着Linux0.11的代码即将发挥作用了。

第一扇区的程序bootsect.s是由汇编所写,下面工作是执行此程序然后将硬盘的第二批第三批代码载入内存;

注:BIOS程序固化在主机板上的ROM中,是根据具体的主机板而不是根据具体的操作系统设计的,对于BIOS而言,“约定”街道启动操作系统的命令,”定位识别“只从启动扇区把代码加载到0X07C00内存这个位置(参见Seabios 0.6.0/Boot.c中的boot_disk函数)(之后可以贴一下SeaBios代码和运行结果)

二、加载第二部分内核代码—setup
1、bootsect对内存的规划
BIOS已经把biitsect载入内存了,现在它的作用就是把第二批和第三批程序陆续加载到内存中,为了把第二批和第三批程序加载到内存中的适当位置,bootsect首先做的工作是规划内存

在实模式下,寻找的最大范围是1MB.为了规划内存,bootsect首先设计了如下代码:

.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.text

SETUPLEN = 4				! nr of setup-sectors
BOOTSEG  = 0x07c0			! original address of boot-sector
INITSEG  = 0x9000			! we move boot here - out of the way
SETUPSEG = 0x9020			! setup starts here
SYSSEG   = 0x1000			! system loaded at 0x10000 (65536).
ENDSEG   = SYSSEG + SYSSIZE		! where to stop loading

! ROOT_DEV:	0x000 - same type of floppy as boot.
!		0x301 - first partition on first drive etc
ROOT_DEV = 0x306

这些源代码作用是对后续操作所涉及的内存位置进行设置,包括将要加载setup程序的扇区数(SETUPLEN)以及被加载到的位置(SETUPSEG ),启动扇区被BIOS加载的位置(BOOTSEG)及将要移动到的新位置(INITSEG),内核被加载的位置(SYSSEG)、内核的末尾位置(ENDSEG)及根文件系统设备号(ROOT_DEV)。这些位置位置是为了保障将要载入内存的代码与已经载入内存的代码及数据各在其位,互补覆盖,并且各自有够用的内存空间。

实模式下的内存规划
操作系统的设计是需要结合芯片手册全面地、整体地考虑内存规划的;

2、复制bootsect

接下来bootsect启动程序将它自身(全部512B内容)从内存0x7c00(BOOTSEG)处复制至内存(INITSEG)处。

执行这个操作代码如下:

entry _start

_start:
	mov	ax,#BOOTSEG
	mov	ds,ax
	mov	ax,#INITSEG
	mov	es,ax
	mov	cx,#256
	sub	si,si !让si置为0
	sub	di,di !让di置为0
	rep
	movw

源地址 ds:si = 0×07C0:0×0000
目的地址 es:di = 0×9000:0×0000
重复执行,直到cx = 0
movw 移动1 个字
rep: 重复执行该语句直至寄存器cx为0
movw: 将DS:SI的内容送至ES:DI,note! 是复制过去,原来的代码还在。

倒腾地方是为了让别人用

在这次复制过程中,ds(0x07C0)和si(0x0000)联合使用,构成了源地址0x07C00;es(0x90000)和di(0x0000)联合使用,构成了目的地址0x90000,而mov cx,#256这一行循环控制量,提供了需要复制的”字“数(一个字为2字节,256个字正好是512字节,也就是第一扇区的字节数)

通过代码可以看出,BOOTSEG和INITSEG开始发挥作用,系统根据自己的需要安排代码了。

之后会执行如下代码

jmpi	go,INITSEG
go:	mov	ax,cs

注:
jmpi(Jump Intersegment)段间跳转
16位实模式下

要把go赋值给IP,INITSEG给CS
go是什么呢?后面有标号;
汇编的标号实际上就是个地址
go就是从strat开始的地方经过了多少偏移,也就是go在什么位置,比如说300

注意start是从0x07c00开始执行的,现在已经把程序挪动到了0x90000,那么现在就是从0x90000+go的位置开始执行(也就是还是顺序执行程序);

在这里插入图片描述

实模式下
如jmpi	0,90000		!则表示程序跳到90000:0去继续执行,使得CS=90000,IP=0
如jmpi	go,INITSEG	!INITSEG指出跳转到的段地址,假设INITSEG=0x90000,标号go是段内偏移地址

在之前CS为0x07c0,执行完后CS变成了0x90000(INITSEG),

IP的值从0x90000到go:mov ax,cs这一行对应指令的偏移,
也就是此时CS:IP指向go:mov ax,cs这一行。

此前的0X07C00这个位置是根据”两头约定“和”定位识别“而确定的。从现在起,操作系统已经不需要完全依赖BIOS,可以按照自己的意志把自己的代码安排在内存中的自己想要的位置。

bootsect复制到了新的地方,并且要在新的地方继续执行下去,因为代码的整体位置发生了变化,所以代码的各个段也会发生变化。前面已经变成了CS,现在对DS\ES\SS\SP进行调整:

go:	mov	ax,cs
	mov	ds,ax
	mov	es,ax
! put stack at 0x9ff00.
	mov	ss,ax
	mov	sp,#0xFF00		! arbitrary value >>512

! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.

上述代码的作用是通过ax,用cs的值0x9000来把DS\ES\SS设置为和CS相同的位置,并将栈顶指针SP指向偏移地址0XFF00处。

在这里插入图片描述SS和SP联合使用,构成了栈数据在内存中的位置值,对这两个寄存器的设置为后面程序的栈操作(push\pop)打下了基础

在设置SS和SP之前,没有出现过栈操作的指令,而在此后陆续使用了。标志着可以开始进行一些复杂指令
栈操作是有方向的,是由高地址到低地址的方向。

DS/SS/ES/FS/GS这些段寄存器在CPU中,SS指向栈段,此区域将按照栈机制进行管理

SP:栈顶指针寄存器,指向栈段的当前栈顶;

至此,bootsect的第一步操作,即规划内存并把自身从0x07c00的位置复制到0x90000的位置的动作已经完成了

3、将setup程序加载到内存中
在这里插入图片描述

在这里插入图片描述

下面bootsect程序要执行到它的第二步工作:将setup程序加载到内存中。

加载setup程序,要借助BIOS提供的int 0x13中断向量所指向的中断服务程序来完成。
这个中断和之前的0x19有点不同

int 0x19中断向量所指向的启动加载服务程序是BIOS执行的,而int 0x13的中断服务程序是Linux操作系统自身的启动代码bootsect执行的。

int 0x19的中断服务程序只负责把硬盘的第一扇区的代码加载到0x7c00位置,而0x13的中断服务程序则不然,它可以根据设计者的意图,把指定扇区的代码加载到内存的指定位置。

使用int 0x13中断时,就要事先将指定的扇区、加载的内存位置等信息传递给服务程序,即传参。
在这里插入图片描述

load_setup:
	mov	dx,#0x0000		! drive 0, head 0
	mov	cx,#0x0002		! sector 2, track 0
	mov	bx,#0x0200		! address = 512, in INITSEG
	mov	ax,#0x0200+SETUPLEN	! service 2, nr of sectors
	int	0x13			! read it
	jnc	ok_load_setup		! ok - continue
	mov	dx,#0x0000
	mov	ax,#0x0000		! reset the diskette
	int	0x13
	j	load_setup

读入完setup后就马上跳到了ok_load_setup


ok_load_setup:

! Get disk drive parameters, specifically nr of sectors/track

	mov	dl,#0x00
	mov	ax,#0x0800		! AH=8 is get drive parameters
	int	0x13
	mov	ch,#0x00
	seg cs
	mov	sectors,cx
	mov	ax,#INITSEG
	mov	es,ax

! Print some inane message

	mov	ah,#0x03		! read cursor pos
	xor	bh,bh
	int	0x10
	
	mov	cx,#24
	mov	bx,#0x0007		! page 0, attribute 7 (normal)
	mov	bp,#msg1
	mov	ax,#0x1301		! write string, move cursor
	int	0x10

0x10中断是打印东西,bp是显示的东西的地址,也就是0x10显示msg1这个偏移内的东西

在这里插入图片描述

系统给BIOS中断服务程序传参是通过几个通用寄存器实现的,这是汇编程序的常用方法,与C语言不同。
参数传递完毕后,执行int 0x13指令,产生0x13中断,通过中断向量表找到这个中断服务程序,将第二扇区开始的4个扇区,即setup.s对应的程序加载至内存的SETUPSEG(0x90200处),复制后的bootsect的起始位置是0x90000,占用512字节的内存空间。不难看出0x90200紧挨着bootsect的尾端,所以bootsect和setup是连在一起的。

现在,操作系统已经从软盘加载了5个扇区的代码,等bootsect执行完毕后,setup这个程序就要开始工作了。

三、加载第三部分代码----system模块

依然是用的int 13
bootsect将执行第三批程序的载入工作,即将系统模块载入内存

这次加载的扇区是240个,所以屏幕信息显示比较麻烦;
显示器也是一个外设,需要用到其他BIOS中断,可以不仔细看其代码

bootsect借着int 0x13中断,将240个扇区的system模块加载进内存,加载工作主要是由bootsect调用read_it子程序完成的,这个子程序将软盘的第六扇区开始的240个扇区的system模块加载至内存的SYSSEG(0x10000)处往后的120KB空间中。

然后确认一下根设备号
系统有一个根文件系统,其他文件挂接在其上,而不是同等地位。必须在一个正在运行的系统上利用工具做出一个文件系统并加载至本机。
因此Linux0.11的启动需要两部分,系统内核镜像和根文件系统。

	seg cs
	mov	ax,root_dev
	cmp	ax,#0
	jne	root_defined
	seg cs
	mov	bx,sectors
	mov	ax,#0x0208		! /dev/ps0 - 1.2Mb
	cmp	bx,#15
	je	root_defined
	mov	ax,#0x021c		! /dev/PS0 - 1.44Mb
	cmp	bx,#18
	je	root_defined
undef_root:
	jmp undef_root
root_defined:
	seg cs
	mov	root_dev,ax

现在bootsect程序的任务都已经完成,下面通过执行

jmpi	0,SETUPSEG

这句语句跳转至0x90200处,就是前面讲过的第二批程序------setup程序加载的位置。
CS:IP指向setup程序的第一条指令,意味着由setup程序接着bootsect程序继续执行。
也就是把控制权交给了setup(进入了0x90200)
(现在可以看setup.s了)
setup程序现在开始执行,它做的第一件事情是利用BIOS提供的中断服务程序从设备上提取内核运行所需的机器系统数据,其中包括光标位置、显示页面等数据。并分别从中断向量0x41和0x46向量值所指的内存地址处获取硬盘参数表1,2把他们放在0x9000:0x0080和0x9000:0x0090处。
这些机器系统数据被加载到内存的0x90000~0x901FC位置。
这些数据将在以后main函数执行时发挥重要的作用
在这里插入图片描述
setup.s开始便先检查硬件看看有什么东西,如图所示


entry start
start:

! ok, the read went well so we get current cursor position and save it for
! posterity.

	mov	ax,#INITSEG	! this is done in bootsect already, but...
	mov	ds,ax
	mov	ah,#0x03	! read cursor pos
	xor	bh,bh
	int	0x10		! save it in known place, con_init fetches
	mov	[0],dx		! it from 0x90000.
! Get memory size (extended mem, kB)

	mov	ah,#0x88
	int	0x15 !把0x88作为参数,用0x15来获取内存的大小
	mov	[2],ax !获取出的值放入ax里,然后把ax赋值给了2,这个2是间接寻址,前面一直保有9000的段寄存器;具体放到哪里可以看图

! Get video-card data:

	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

	mov	ah,#0x12
	mov	bl,#0x10
	int	0x10
	mov	[8],ax
	mov	[10],bx
	mov	[12],cx

! Get hd0 data

	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x41]
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0080
	mov	cx,#0x10
	rep
	movsb

! Get hd1 data

	mov	ax,#0x0000
	mov	ds,ax
	lds	si,[4*0x46]
	mov	ax,#INITSEG
	mov	es,ax
	mov	di,#0x0090
	mov	cx,#0x10
	rep
	movsb

在这里插入图片描述

注意BIOS提取的机器系统数据将覆盖bootsect程序所在部分区域。这些数据由于是要留用的,所以在它们失去使用价值之前,一定不能覆盖掉。

到现在为止操作系统内核程序的加载工作已经完成,接下来对Linux0.11而言将实现从实模式到保护模式的转变。

开始向32位模式转变,为main函数的调用做准备

在这里插入图片描述

一、关中断并将system移动到内存地址起始位置0x00000

将CPU的标志寄存器(EFLAGS)中的中断运行标志(IF)置0.这意味着程序在接下来的执行过程中无论是否发生中断,系统都不会响应。


! now we want to move to protected mode ...

	cli			! no interrupts allowed !

! first we move the system to it's rightful place

	mov	ax,#0x0000
	cld			! 'direction'=0, movs moves forward

注:关中断和开中断操作将在操作系统代码中频繁出现,总是在一个完成操作的两端出现。目的是避免中断在此期间介入。

接下来代码将为操作系统进入保护模式做准备,此处即将进行实模式下中断向量表和保护模式下中断描述符表(IDT)的交接工作。

setup程序将位于0x10000的内核程序复制至内存地址的起始位置0x00000处,代码如下:

do_move:
	mov	es,ax		! destination segment
	add	ax,#0x1000   
	! ax 现在为1000,es为0000
	cmp	ax,#0x9000
	!更改cx,当ax=9000时候结束
	jz	end_move
	mov	ds,ax		! source segment 把1000给了ds
	sub	di,di  !di=0
	sub	si,si  !si=0
	mov 	cx,#0x8000 
	rep
	movsw
	jmp	do_move

这个复制动作将BIOS中断向量表和BIOS数据区完全覆盖,使他们不复存在,直到新的中断服务体系构建完毕之前,操作系统不再具备响应并处理中断的能力。

在这里,把所有的操作系统代码全部移动到了0
之前0x07c00处这样setup就搞坏了所以要先放到9000

二、设置中断描述符表和全局描述符表
(建议看完三后再返回看二好理解点)
在这里插入图片描述

在这里插入图片描述以前CS放的就是地址,现在CS里放的是查表的下标(索引),这个段的基址是放在表项中的;jmpi 0,8的8就是要查找GDT表在这个表中选出一个元素,这个元素就是个地址(基址),这个基址取出后再加上IP(32位加32位还是32位);

那么这个表中必须得有内容,如果没有内容不行
所以setup在此初始化了表


end_move:
	mov	ax,#SETUPSEG	! right, forgot this at first. didn't work :-)
	mov	ds,ax
	lidt	idt_48		! load idt with 0,0
	lgdt	gdt_48		! load gdt with whatever appropriate
  .......
gdt:
	.word	0,0,0,0		! dummy

	.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		! base address=0
	.word	0x9A00		! code read/exec
	.word	0x00C0		! granularity=4096, 386

	.word	0x07FF		! 8Mb - limit=2047 (2048*4096=8Mb)
	.word	0x0000		! base address=0
	.word	0x9200		! data read/write
	.word	0x00C0		! granularity=4096, 386

idt_48:
	.word	0			! idt limit=0
	.word	0,0			! idt base=0L

gdt_48:
	.word	0x800		! gdt limit=2048, 256 GDT entries
	.word	512+gdt,0x9	! gdt base = 0X9xxxx

这个表项的初始化是由硬件管理的直接放到指定位置去;
在这里插入图片描述

setup程序继续为保护模式做准备,此时要通过setup程序自身提供的数据信息对**中断描述符表寄存器(IDTR)全局描述符表寄存器(GDTR)**进行初始化设置。

GDT(Global Descriptor Table)

在保护模式下,不光提供了强大的寻址能力,还提供了内存保护,能够防止用户程序改写内核代码,并为操作系统提供了更好的硬件保障。在该模式下,有了32位的寄存器,一个寄存器就可以表示4G的地址空间,那段值加偏移这样的寻址方式是不是就不用了呢?当然不会。在这里,而是用了另外的方式来表示。

采用的方式就是引入了数据结构GDT,即全局描述符表,不知道有多少人听过?说简单点,GDT就是一个数组,每一个元素就是一个描述符,多个组合一起就构成了全局描述符表。而每一个描述符共64位,包含了以下的这些信息:段基址、段长度、属性。原来的段寄存器,比如CS,DS等存的值则不是段偏移了,而是GDT的索引,通过该索引就可以找到对应的描述符。

在这里插入图片描述
它其实是在系统中唯一的存放段寄存器内容的数组,配合程序进行保护模式下的段寻址,它在操作系统的进程切换中具有重要意义,可理解为所有进程的总目录表,其中存放每一个任务(task)局部描述符表(LDT)地址和任务状态段(TSS)地址,完成进程中各段的寻址、现场保护与现场恢复。

GDTR(Global Descriptor Table Register,GDT基地址寄存器)
GDT可以存放在内存的任何位置,当程序通过段寄存器引用一个段描述符时,需要取得GDT的入口,GDTR标识的即为此入口。在操作系统对GDT的初始化完成后,可以用LGDT指令将GDT基址加载至GDTR。

IDT(Interrupt Descriptor Table)保护模式下所有中断服务程序的入口地址,类似于实模式下的中断向量表

IDTR(Interrupt Descriptor Table Register,IDT基地址寄存器)保持IDT的起始地址。

段寄存器中保存的是索引,那么段寄存器中的数据
在这里插入图片描述
三、打开A20,实现32位寻址
在这里插入图片描述

! that was painless, now we enable A20

	call	empty_8042
	mov	al,#0xD1		! command write
	out	#0x64,al
	call	empty_8042
	mov	al,#0xDF		! A20 on
	out	#0x60,al
	call	empty_8042
.......

! 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.
	mov	ax,#0x0001	! protected mode (PE) bit
	lmsw	ax		! This is it!
	jmpi	0,8		! jmp offset 0 of segment 8 (cs)

现在不用CS:IP了,要从1M变成4GB;
所以要换寻址模式,切换到32位机(保护模式)

LMSW: Load Machine Status Word
置处理器状态字。但是只有操作数的低4位被存入CR0,只有PE,MP,EM和TS被改写,CR0其他位不受影响。

如果要看详细的资料可以参考Intel的IA32手册第二卷:指令集参考

http://developer.intel.com/design/Pentium4/manuals/

在这里插入图片描述切换为GDT后需要理解其是什么意思,详细见本文:

二、设置中断描述符表和全局描述符表

操作系统在最后执行了jmpi 0 8
这个08不是80了,其实跳到的是0x0000地址处
因为寻址方式发生了改变!

四、为保护模式下执行head.s
在这里插入图片描述head.s采用的汇编和刚才完全不一样,
在bootset和setup用了16位的汇编
head后面用的是32位的汇编
然后在.c文件中有些.c文件要严格的按照我们的方式去控制执行,所以用的内嵌汇编;
在这里插入图片描述

在这里插入图片描述C语言一个函数调用另一个函数最主要就是靠的栈

待续

五、head.s开始执行

Linux0.11源码下载网址:

https://github.com/karottc/linux-0.11

待续

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值