我是靠谱客的博主 斯文小蜜蜂,这篇文章主要介绍Linux0.00 代码解析(二),现在分享给大家,希望可以做个参考。

Linux 0.00 的编译、运行、源码下载:
http://blog.csdn.net/longintchar/article/details/78757065
Linux 0.00 Makefile 解读:
http://blog.csdn.net/longintchar/article/details/78857966
Linux 0.00 代码解析——boot.s
http://blog.csdn.net/longintchar/article/details/78766916


Linux-0.00的代码分为两部分——boot.shead.s.

boot.s采用as86语言编写,是引导启动程序,先把内核代码加载到物理地址0x10000处,然后把内核代码移动到物理地址0处,接下来设置临时GDT表等信息,再把处理器设置成保护模式,最后跳转到内核代码处(0地址)运行。

head.s是内核代码,采用GNU as汇编语言编写,实现了2个运行在特权及3上的任务,它们在时钟中断控制下相互切换运行,一个在屏幕上打印“A”,另一个在屏幕上打印“B”。

本文要分析的是head.s。请注意,这段代码在运行的时候,它的起始位置在物理地址0处

1. 设置DS,ES,SS,ESP

复制代码
1
2
3
4
5
SCRN_SEL = 0x18 TSS0_SEL = 0x20 LDT0_SEL = 0x28 TSS1_SEL = 0X30 LDT1_SEL = 0x38

SCRN_SEL等都是符号常量,代表某选择子的值,这样写可读性好。相当于c语言中的#define SCRN_SEL 0x18.

复制代码
1
2
3
4
5
6
.code32 .global startup_32 .text startup_32: movl $0x10,%eax mov %ax,%ds

.code32 是我自己加的,不然编译会报错。这句伪指令告诉编译器,下面的代码要编译成32位代码。
.global 表示标识符是外部的或者全局的。
.text 标识正文段的开始,并切换到text段。

movl $0x10,%eax , 0x10是数据段(在boot.s文件中定义)的选择子,此数据段的基地址为0,界限值是0x7FF(10进制2047),粒度4KB;因为粒度是4KB,所以段长度是(2047+1)*4KB=8MB;DPL=0,向上扩展,可读可写。
mov %ax,%ds ,加载ds。

复制代码
1
lss init_stack,%esp

init_stack处有6个字节,见

复制代码
1
2
3
init_stack: # Will be used as user stack for task0. .long init_stack .word 0x10

这是一个远指针,前4个字节是偏移,后2个字节是段选择子,这句代码表示用偏移加载esp,用数据段选择子0x10加载ss.

2. setup_idt

此过程用于在IDT(中断描述符表)中安装中断门。代码是

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
setup_idt: lea ignore_int,%edx movl $0x00080000,%eax movw %dx,%ax /* selector = 0x0008 = cs */ movw $0x8E00,%dx /* interrupt gate - dpl=0, present */ lea idt,%edi mov $256,%ecx rp_sidt: movl %eax,(%edi) movl %edx,4(%edi) addl $8,%edi dec %ecx jne rp_sidt lidt lidt_opcode ret

中断门描述符

中断门描述符如下图:

这里写图片描述

下面是低32位(代码中用eax存储),上面是高32位(代码中用edx存储)。

可以看出,中断门定义了一个长指针(段选择符:过程入口点偏移值),当发生中断的时候,处理器使用这个长指针把程序执行权转移到中断处理过程中。

在edx和eax中组装中断门描述符

lea指令是取有效地址(偏移值)。lea ignore_int,%edx表示把ignore_int处的有效地址传给edx. 注意,是取ignore_int处的偏移地址,而不是ignore_int处存储的内容。这样,过程入口点偏移值31-16组装完毕。
movl $0x00080000,%eax, 段选择符(=0x08,索引1,内核代码段)组装完毕。
movw %dx,%ax, 过程入口点偏移值15-0组装完毕。
movw $0x8E00,%dx edx的低16位组装完毕。

中断处理过程就是ignore_int,用于在屏幕上打印一个’c’.

复制代码
1
2
3
4
5
6
7
8
9
10
ignore_int: push %ds pushl %eax movl $0x10, %eax mov %ax, %ds #上一行和此行用内核数据段加载ds movl $67, %eax #打印字符'c',实际上用AL来传参 call write_char #调用过程 write_char popl %eax pop %ds iret

注意write_char这个过程没有指定DS,但是确引用了DS,比如指令movl scr_loc, %ebx. 所以在调用write_char之前,一定要给DS赋合适的值。

write_char这个过程,我已经在代码后面添加了注释。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
write_char: push %gs pushl %ebx mov $SCRN_SEL, %ebx #SCRN_SEL是显存段的选择子 mov %bx, %gs #gs指向显存段 movl scr_loc, %ebx #scr_loc处存放的是显示位置 shl $1, %ebx #ebx*2,得到偏移,因为一个字符用2个字节来描述 movb %al, %gs:(%ebx) #al中是字符的ASCII码,属性用默认的 shr $1, %ebx #还原ebx incl %ebx #ebx自增1,算出下一个位置 cmpl $2000, %ebx #比较ebx和2000 jb 1f #若 ebx < 2000 则跳转到1 movl $0, %ebx #说明ebx==2000,因为位置只有0~1999,所以把ebx置为0 1: movl %ebx, scr_loc #把ebx存入scr_loc处,更新显示位置 popl %ebx pop %gs ret

可以看出,write_char的功能是把AL中的字符打印到屏幕上。
位置由scr_loc处存储的4字节的值指定(实际上取值0~1999),打印后更新位置(计数加1)。

复制代码
1
scr_loc:.long 0 #代码中留出了4字节存放位置

填写IDT

lea idt,%edi表示把idt处的有效地址加载到edi.

idt标号处的代码是:

复制代码
1
2
.align 8 idt: .fill 256,8,0

fill伪指令的格式是
.fill repeat,size,value
表示产生repeat个大小为size字节的重复拷贝。size最大是8,size字节的值是value.
所以,.fill 256,8,0表示产生8*256字节,全部用0填充。IDT最多可有256个描述符,每个描述符占8个字节。

复制代码
1
2
3
4
5
6
7
mov $256,%ecx rp_sidt: movl %eax,(%edi) movl %edx,4(%edi) addl $8,%edi dec %ecx #当ecx为0时,会使ZF=0 jne rp_sidt #若ZF!=0则跳转到rp_sidt

Intel语法的间接内存引用的格式为:
section:[base + index * scale + displacement]
而在AT&T语法中对应的形式为:
section:displacement (base, index, scale)
其中,base和index是任意的32-bit base和index寄存器。scale可以取值1,2,4,8。如果不指定scale值,则默认值为1。section可以指定任意的段寄存器作为段前缀,默认的段寄存器在不同的情况下不一样。

举例:

IntelAT&T
[base + index * scale + displacement]section:displacement(base, index, scale)
[eax + _variable]_variable(%eax)
[eax * 4 + _array]_array(, %eax, 4)
[ebx + eax * 8 + _array]_array(%ebx, %eax, 8)

所以,movl %eax,(%edi)表示把eax的值传送到地址edi处,即用eax填充IDT表的0~3字节;movl %edx,4(%edi)表示把edx的值传送到地址[edi+4]处,即用edx填充IDT表的4~7字节;这样,IDT表中第0个中断门就安装好了。同理,循环安装,一共是256个中断门。

加载IDTR

lidt lidt_opcode 加载IDTR寄存器,lidt_opcode处定义了6个字节。前2字节是界限值,界限值是表的总长度减去1;后4字节是IDT的线性基地址。因为本文件运行时的起始地址就在物理地址0处,所以线性基地址就是idt表示的值。

复制代码
1
2
3
lidt_opcode: .word 256*8-1 # idt contains 256 entries .long idt # This will be rewrite by code.

3. setup_gdt

这个过程就一句话

复制代码
1
2
3
setup_gdt: lgdt lgdt_opcode ret

加载GDTR寄存器。
看一下 lgdt_opcode 处都有什么:

复制代码
1
2
3
lgdt_opcode: .word (end_gdt-gdt)-1 .long gdt # This will be rewrite by code.

前2字节是GDT的界限值,后4字节是GDT的线性基地址。

复制代码
1
2
3
4
5
6
7
8
9
10
11
gdt: .quad 0x0000000000000000 /* NULL descriptor */ .quad 0x00c09a00000007ff /* 8Mb 0x08, base = 0x00000 */ .quad 0x00c09200000007ff /* 8Mb 0x10 */ .quad 0x00c0920b80000002 /* screen 0x18 - for display */ .word 0x0068, tss0, 0xe900, 0x0 # TSS0 descr 0x20 .word 0x0040, ldt0, 0xe200, 0x0 # LDT0 descr 0x28 .word 0x0068, tss1, 0xe900, 0x0 # TSS1 descr 0x30 .word 0x0040, ldt1, 0xe200, 0x0 # LDT1 descr 0x38 end_gdt:

一共定义了8个段描述符。

索引号选择子描述符类型基地址段界限粒度PDPL备注
0-空描述符------
10x08代码段00X7FF4KB10内核代码段,非一致性,可读
20x10数据段00X7FF4KB10内核数据段,向上扩展,可写
30x18数据段0XB80000X24KB10内核显存段,向上扩展,可写
40x20TSS段tss00X681B13任务0的TSS段,不忙
50x28LDT段ldt00X401B13任务0的LDT描述符
60x30TSS段tss10X681B13任务1的TSS段,不忙
70x38LDT段ldt10X401B13任务1的LDT描述符

4. 重新加载段寄存器

因为GDT的内容改变了,所以应该重新加载所有段寄存器。

复制代码
1
2
3
4
5
6
movl $0x10,%eax # reload all the segment registers mov %ax,%ds # after changing gdt. mov %ax,%es mov %ax,%fs mov %ax,%gs lss init_stack,%esp

注意:因为内核代码段和boot.s文件中的定义一样,所以不用重新加载CS

5. 设置定时芯片8253

关于这个定时芯片可以参考我的博文http://blog.csdn.net/longintchar/article/details/78885556

Intel 8253芯片是可编程计数器/定时器。该芯片提供了3个独立的16位计数器通道,每个通道可以工作在不同的工作方式下。通过向8253写入一个控制字和一个初始计数值,就可以使它开始计数。

控制字格式如下图:
这里写图片描述

代码中写入的控制字是0x36,选中通道0,先读写低字节再读写高字节,工作方式3,采用二进制计数。
通道0的端口是0x40, 先向其写入初始计数值的低字节,再写入初始计数值的高字节。

假设N为初始计数值。在工作方式3下,方波的频率是输入时钟频率的N分之一,又因为计数器的输入时钟频率是1.193180MHz=1193180Hz,所以

复制代码
1
2
1193180/N = 方波的频率(Hz)

movl $11930, %eax表示计数值N=11930,1193180/11930约等于100,
所以方波的频率是100Hz,周期是10ms,也就是10ms产生一个方波上升沿,此上升沿可以产生中断请求,即10ms产生一次中断。

复制代码
1
2
3
4
5
6
7
8
9
# setup up timer 8253 chip. movb $0x36, %al # al中是控制字 movl $0x43, %edx # 端口是0x43 outb %al, %dx # 把al中的控制字写入端口0x43 movl $11930, %eax # timer frequency 100 HZ movl $0x40, %edx # 端口是0x40 outb %al, %dx # 先写低字节 movb %ah, %al # 再写高字节 outb %al, %dx

【未完待续】

最后

以上就是斯文小蜜蜂最近收集整理的关于Linux0.00 代码解析(二)的全部内容,更多相关Linux0.00内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(58)

评论列表共有 0 条评论

立即
投稿
返回
顶部