linux head.s 编译,Linux0.11中的head.s代码分析

head.s程序被编译后,会被连接成system模块的最前面位置,它被setup.s加载到内存绝对地址0处开始的地方,并执行。此时Linux内核已经完全在保护模式下运行。head.s的主要功能包括:

1. 设置内核堆栈;

2. 设置中断描述符表idt;

3. 设置全局描述符表gdt;

4. 设置页目录表和页表;

5. 将/init/main.c程序的入口地址预先压入堆栈中,并在随后利用返回指令弹出该地址,去执行main()程序。

在head.s执行结束之后,其中部分内存空间将被覆盖,而控制权转至system模块中紧接在head.s之后的main程序代码。

1. 设置内核堆栈

在head.s中,堆栈段被设置为内核数据段(0x10),堆栈指针ESP设置为kernel模块中定义的全局user_stack数据的顶端,有1页内存(4K)可作为堆栈。

相关代码如下:

movl $0x10, %eax

mov %ax, %ds

mov %ax, %es

mov %ax, %fs

mov %ax, %gs

lss _stack_stack, %esp

段选择符长度为16位,包括三个部分:

B15--B3,索引值,共13位,用于选择指定描述符表中8192个描述符中的一个。处理器将该索引值乘上8(描述符的字节长度),并加上描述符表的基地址即可访问表中指定的段描述符。

B2:表指示器(TI),指出选择符所引用的描述符表。值位0表示GDT表,1表示指定当前的LDT表。

B1--B0,请求者的特权级(RPL):用于保护机制。

因此这里的0x10(0x0000000000010000),是一个描述符表项的选择符,含义是请求特权级0、选择全局描述符表、选择表中第2项,即偏移在字节16处的段描述符,它正好指向表中的数据段描述符项(实际上在setup.s中为0x00C09200000007FF,在head.s中为0x00C0920000000FFF)。

2. 设置中断描述符表(暂缺)

3. 设置全局描述符表

刚进入head.s时,使用的是在setup.s中设置的全局描述符表。head.s将重新设置全局描述符表,并在以后的代码中使用之。

全局描述符表长度为2KB,每项8个字节,共2048/8=256项。其中前4项分别为空项、代码段描述符、数据段描述符、系统段描述符,其中Linux没有使用系统段描述符。后面还预留了252项的空间,用于放置所创建任务的局部描述符(LDT)和对应的任务状态段TSS的描述符。

.quad 0x0000000000000000 空描述符

.quad 0x00C09A0000000FFF 16MB

.quad 0x00C0920000000FFF 16MB

.quad 0x0000000000000000 未使用

用于程序代码段和数据段的描述符格式如下:

B63--B56,基地址的B31--B24

B55:G,颗粒度(Granularity),指定限长字段值代表的单元含义。当为0时,限长单元值为1字节;当为1时,限长的单元值为4KB字节。

B54:X

B53:0

B52:AVL

B51--B48:段限长的B19--B16

B47:P,段存在位。如果该位为0,则描述符无效,不能用于地址变换过程。当指向该描述符的选择符被加载到段寄存器中时,处理器就会发出一个异常信号。

B46--B45:DPL,描述符特权级(Descriptor Priviledge Level),用于保护机制,共有4级。0为最高特权级,3为最低特权级。在Linux中只用到了两级:0和3。

B44:0

B43--B40:TYPE,用于区分各种不同类型的描述符。

B39--B32:基地址的B23--B16

B31--B16:段基址的B15--B0

B15--B0:段限长的B15--B0

由此我们来理解代码段描述符:

0x00C09A0000000FFF,即

0b00000000110000001001101000000000

0b00000000000000000000111111111111

段基址为0x00000000。

段限长为0x00FFF,即4K,由于颗粒度标志为1,即以4KB为单位,故最大为16MB。

同时P标志为1,DPL标志为00(特权级0),

数据段描述符类似,只不过是类型(TYPE)标志从A(1010)换成了2(0010)。

4. 设置页目录项和页表项

偏移0开始处存放页表目录;

偏移0x1000处存放第一个页表(物理内存4KB处);

偏移0x2000处存放第二个页表(物理内存8KB处);

偏移0x3000处存放第三个页表(物理内存12KB处);

偏移0x4000处存放第三个页表(物理内存16KB处);

每个页表长为4KB字节(1页内存页面),而每个页表项需要4个字节,因此一个页表共可以存放1024个表项。如果一个页表项寻址4KB的地址空间,则一个页表就可以寻址4MB的物理内存。(因此寻址16MB的物理内存共需要4个页表项)

页目录表项和页面表项的格式:

B31-B12:共20位,为物理页码。因为内存页是位于4K边界上的,所以只需20位作为物理页码,而低12位可用作属性。在一个页目录中,页表项的前20位是一个页表的起始地址;在第二级页表中,页表项的前20位包含期望内存操作的页框的地址。

B11-B9:共3位(AVL)

B8:0

B7:0

B6:D,是已修改(Dirty)标志;

B5:A,是已访问(Access)标志;

B4:0

B3:0

B2:U/S,是用户/系统属性位,指示该表项所指定的页是否是用户级页。若U/S为1,表项所指定的页是用户级页,可由任何特权级执行的程序访问;如果为0,表项所指定的页是系统级页,只能由系统特权级下执行的程序访问。

B1:R/W,读写属性位,指示该表项所指定的页是否可读、写或执行。R/W对页的写保护只在处理器处于用户特权级时发挥作用;在系统特权级下,该位被忽略。

B0:P,存在属性位,表示该表项是否有效。

在head.s中与页目录表和页表相关的操作包括:(1)在页目录表中设置页目录项指向对应的页表。(2)在页表中设置页表项指向对应的页表。

在页目录表中设置指向上述4个页表的目录项如下:

movl $pg0+7, _pg_dir 即将页目录表的第一项的内容设置为$pg0+7,即0x00001007。其中物理页码为前20位,即0x00001,表示第1个物理内存(注意到物理内存以4K为边界)。该页面对应的属性标志为后面12个字节,即0x007,表示该页存在、用户态可读写。

movl $pg1+7, _pg_dir+4 即将页目录表的第二项的内容设置为$pg1+7,因为每个页目录项占据4个字节,因此为_pg_dir+4。

movl $pg2+7, _pg_dir+8

movl $pg3+7, _pg_dir+12

在4个页表中设置页表项内容的方式如下:因为每个页表占据一个物理页面,为4KB,而每项需要4个字节,因此每个页表为1024项,4个页表共有4096项(0-0xFFF)。

每项的映射内容是当前项所映射的物理内存地址+该页的标志(这里均为7)。填充的方式是从最后一个页表的最后一项开始按倒退顺序填写。一个页表的最后一项在页表中的位置是1023*4=4092。因此最后一页的最后一项的位置就是$pg3+4092,它映射到的物理页面号为0xFFF,对应的代码如下:

movl $pg3+4092, %edi

movl $0xFFF07, %eax

之后,每填好一项,物理地址值减去0x1000,每项的值减4个字节,直至物理地址小于0。

在设置好页目录表和页表后,就可以设置页目录基址寄存器CR3的值,指向页目录表。

xor1 %eax, %eax

movl %eax, %cr3

并设置启动使用分页处理(CR0的PG标志,为31)

movl %CR0, %eax

orl $0x80000000, %eax

movl %eax, %cr0

5. 跳转到/init/main.c

至此,调用返回指令,将堆栈中的main程序的地址弹出,并开始运行/init/main.c程序。

与压栈相关的指令如下:

push1 $0

push1 $0

push1 $0

push1 $L6

push1 $_main

调用返回指令代码如下:

ret

在head.s程序执行结束后,已经正式完成了内存页目录和页表的设置,并重新设置了内核实际使用的中断描述符表和全局描述符表。此时system模块在内存中的详细映像如下:

0x0000--0x0FFF:内存页目录表(4KB)

0x1000--0x1FFF:内存页表PG0(4KB)

0x2000--0x2FFF:内存页表PG1(4KB)

0x3000--0x3FFF:内存页表PG2(4KB)

0x4000--0x4FFF:内存页表PG3(4KB)

0x5000--0x53FF:软盘缓冲区(1KB)

head.s部分代码

中断描述符表IDT(2KB)

全局描述符表GDT(2KB)

main.c程序代码

kernel模块代码

mm模块代码

fs模块代码

lib模块代码0b1331709591d260c1c78e86d0c51c18.png

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值