linux 0.12引导启动程序

引导启动程序

91年16bit实模式的引导代码是Minix上的as86编译器编译的,现在改为as编译,进入32bit保护模式下后就是gas(现as)编译

在这里插入图片描述

bootsect.S

在这里插入图片描述
功能:

将自己从0x7c00:0移动到0x9000:0处,并且读入setup.s(4*512=2kb)到自己后面

将system模块读入到0x1000:0处,然后控制转移给setup.s

  • 在第一个扇区的倒数第6个字节开始分别是SWAP设备号和根文件系统设备号

setup.S

  1. 通过BIOS中断得到磁盘和内存的信息,以及设置当时的显示卡,并且将数据放在bootsect.s处(0x9000:0)

  2. 移动system模块到0x0000:0开始,

  3. 加载临时的GDT和IDT(head.s中重新设置)

  4. 通过键盘控制器端口开启A20地址线,

  5. 设置中断控制器8259A,

    硬件中断从0x20开始,从片接IR2引脚

    设置完后屏蔽所有中断,置位ISR寄存器

  6. CR0,第0位置位(进入保护模式),jmp刷新流水线,并且将控制转移到system模块的head.s

    关于显示卡设置略过,没意义

8259A中断控制器(设置太复杂

ICW1:设置成中断沿边沿触发,多片级联

ICW2:设置中断0-7对应中断号0x20-0x27,从片0x28-0x2f

ICW3:设置中断优先级0->(8-15)->(3-7),因为IR2连从片

ICW4:设置中断非自动结束,需要中断处理过程发送EOI

  • 大致原理:

    1. 设置初始化命令字
    2. 写入操作命令字

    优先级请求被选中,8259A发出INT信号给CPU,CPU发出的INTA信号响应,当CPU发出第2个INTA信号,通知8259A在数据总线上给出中断号

    1. 自动结束中断
    2. 手动(发送EOI命令),来复位ISR(正在服务寄存器)对应的中断位,从片也要发
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
#include "../include/linux/config.h"

INITSEG = DEF_INITSEG
SYSSEG = DEF_SYSSEG
SETUPSEG= DEF_SETUPSEG

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

entry start
start:
mov ax,#INITSEG
mov ds,ax

!int 0x15功能0x88取得扩展内存存在0x90002
mov ah,#0x88
int 0x15
mov [2],ax

!0x12功能,获取EGA配置信息
mov ah,#0x12
mov bl,#0x10
int 0x10
mov [8],ax
mov [10],bx
mov [12],cx
!
mov ax,#0x5019
cmp bl,#0x10
je novga
call chsvga
novga:
mov [14],ax
mov ah,#0x03
xor bh,bh
int 0x10
mov [0],dx

!取显卡当前显示模式
mov ah,#0x0f
int 0x10
mov [4],bx
mov [6],ax

!取第一个硬盘的参数
mov ax,#0x0000
mov ds,ax
lds si,[4*0x41] !中断向量表中参数表地址hd0
mov ax,#INITSEG
mov es,ax
mov di,#0x0080 !存放到0x9000:0080
mov cx,#0x10
rep
movsb
!hd1参数表地址
mov ax,#0x0000
mov ds,ax
lds si,[4*0x46]
mov ax,#INITSEG
mov es,ax
mov di,#0x0090
mov cx,#0x10
rep
movsb

!检查系统是否有第2个硬盘
mov ax,#0x1500
mov dl,#0x81 !驱动器号0x81第一个硬盘
int 0x13
jc no_disk1
cmp ah,#3 !是否是硬盘
je is_disk1
no_disk1:
mov ax,#INITSEG !第2个存储介质不存在,
mov es,ax
mov di,#0x0090
mov cx,#0x10
mov ax,#0x00
rep
stosb
is_disk1:
cli !禁止外部中断

!首先将system移动到从内存地址0x0000开始
mov ax,#0x0000
cld
do_move:
mov es,ax
add ax,#0x1000
cmp ax,#0x9000
jz end_move
mov ds,ax !初始化为0x1000:0x0
sub di,di
sub si,si
mov cx,#0x8000 !
rep
movsw
jmp do_move

!加载段描述符
end_move:
mov ax,#SETUPSEG
mov ds,ax !指向本程序
lidt idt_48 !加载IDT寄存器
lgdt gdt_48 !加载GDT寄存器

!开启A20地址线
call empty_8042 !键盘芯片,等待输入缓冲区为空
mov al,#0xD1
out #0x64,al
call empty_8042
mov al,#0xDF
out #0x60,al
call empty_8042 !

!主片端口0x20,0x21,从片端口0xA0-0xA1,
mov al,#0x11
out #0x20,al !ICW1=0x11
.word 0x00eb,0x00eb
out #0xA0,al !从片
.word 0x00eb,0x00eb

!Liux系统硬件中断从0x20开始
mov al,#0x20
out #0x21,al !ICW2=0x20(对应中断号从0x20开始
.word 0x00eb,0x00eb
mov al,#0x28
out #0xA1,al !从片ICW2
.word 0x00eb,0x00eb
mov al,#0x04 !主片ICW3=0x04
out #0x21,al !

.word 0x00eb,0x00eb
mov al,#0x02
out #0xA1,al !从片ICW3

.word 0x00eb,0x00eb
mov al,#0x01
out #0x21,al !主片ICW4=0x01

.word 0x00eb,0x00eb
out #0xA1,al !从片ICW4
.word 0x00eb,0x00eb
mov al,#0xFF !屏蔽所有的中断(主+从
out #0x21,al
.word 0x00eb,0x00eb
out #0xA1,al

!修改cr0,进入保护模式
mov ax,#0x0001
lmsw ax
jmpi 0,8 !第一个段选择子=代码段描述符
!从上面开始机器去执行system中的代码

!当输入缓冲器为空时,执行写命令,(键盘控制器的端口作为与A20地址线,
empty_8042:
.word 0x00eb,0x00eb
in al,#0x64 !读取AT键盘控制器状态寄存器
test al,#2 !
jnz empty_8042
ret

!显示卡部分未写


gdt:
.word 0,0,0,0
!内核代码段选择符0x08
.word 0x07FF
.word 0x0000
.word 0x9A00
.word 0x00C0
!内核数据段 0x10
.word 0x07FF
.word 0x0000
.word 0x9200
.word 0x00C0
idt_48:
.word 0
.word 0,0
gdt_48:
.word 0x800
.word 512+gdt,0x9 !0x9000:512+gdt

msg1:
.ascii "Press <RETURN> to see SVGA-modes available or any other key to continue."
db 0x0d,0x0a,0x0a,0x00
msg2:
.ascii "Mode: COLSxROWS:"
db 0x0d,0x0a,0x0a,0x00
msg3:
.ascii "Choose mode by pressing the corresponding number."
db 0x0d,0x0a,0x00

!显卡特征数据串
idati:
.ascii "761295520"
idcandt:
.byte 0xa5
idgenoa:
.byte 0x77,0x00,0x66,0x99
idparadise:
.ascii "VGA="
!..................

.text
endtext:
.data
enddata:
.bss
endbss:

在这里插入图片描述

head.s

后面的都是AT&T格式,采用gas编译,第6个扇区开始,

重新设置IDT,IDT用简单中断处理过程填满IDT表,避免找不到中断处理历程出错,(后续在改IDT)

重新设置GDT,仍是一个内核代码段一个数据段描述符,段长修改为16MB,和IDT一起放在head.s末尾

内核栈定义在/kernel/sched.c中(此时我还没看main.c后面代码)

初始化协处理器(浮点运算)

转跳到0x0540:0x0000处执行页目录和页表初始化

0x0000:0x0到0x0500:0作为1个页目录和4个页表的内存空间,紧跟1024个字节作为软盘缓冲区,(原来是head.s代码,现在接下来代码写覆盖了)

通过设置前16MB的线性地址和物理地址是一样的(经过分页部件转换后也一样)

将main参数和main地址入栈,控制转移到main中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
.text
.globl _idt,_gdt,_pg_dir,_tmp_floppy_area
_pg_dir:

startup_32:
movl $0x10,%eax #数据段选择子
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
mov %ax,%gs
#设置内核的栈,_stack_start定义在 kernel/sched.c中
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

xorl %eax,%eax
1:
incl %eax
movl %eax,0x000000 #向0x0地址写入eax值
cmpl %eax,0x100000 #判断地址1M是否也是这个值,
je 1b

movl %cr0,%eax
andl $0x80000011,%eax #PG,PE,ET位
orl $2,%eax #set MP位
movl %eax,%cr0
call check_x87
jmp after_page_tables


#
check_x87:
fninit #初始化协处理器
fstsw %ax
cmpb $0,%al
je 1f
movl %cr0,%eax
xorl $6,%eax
movl %eax,%cr0
ret

.align 2
1: .byte 0xDB,0xE4
ret

#设置中断门描述符,然后填入IDT表中256
setup_idt:
lea ignore_int,%edx
movl $0x00080000,%eax #GDT代码段选择符
movw %dx,%ax #低位是过程入口点0-15bit
movw $0x8E00,%dx #中断门描述符属性
lea _idt,%edi
mov $256,%ecx
rp_sidt:
movl %eax,(%edi) #
movl %edx,4(%edi)
addl $8,%edi
dec %ecx
jne rp_sidt #!=0就转跳

lidt idt_descr #加载IDTR
ret

setup_gdt:
lgdt gdt_descr
ret

.org 0x1000
pg0:
.org 0x2000
pg1:
.org 0x3000
pg2:
.org 0x4000
pg3:

.org 0x5000
_tmp_floppy_area:
.fill 1024,1,0

after_page_tables:
pushl $0
pushl $0
pushl $0
pushl $L6
pushl $_main
jmp setup_paging
L6:
jmp L6

int_msg:
.asciz "Unknown interrupt\n\r" #每个字符串后面会添加NULL字符

.align 2 #2^2=4字节对齐
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 #int_msg地址入栈
call _printk #此函数在/kernel/printk.c中
popl %eax #int_msg地址给eax寄存器
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret #通用中断返回

.align 2
setup_paging:
movl $1024*5,%ecx #1页目录+4页表清零
xorl %eax,%eax
xorl %edi,%edi

cld;rep;stosl #将EAX填入ES:EDI中

#填写页目录前4项,分配所有的16Mb物理内存空间
movl $pg0+7,_pg_dir #+7表示31的属性
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 #16Mb-4096+7

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函数

.align 2
.word 0 #4字节对齐,和下面组合起来=4字节
idt_descr:
.word 256*8-1
.long _idt
.align 2
.word 0
gdt_descr:
.word 256*8-1
.long _gdt
.align 3
_idt:
.fill 256,8,0
_gdt:
.quad 0x0000000000000000
.quad 0x00c09a0000000fff #修改代码段段长度=16MB(下同
.quad 0x00c0920000000fff
.quad 0x0000000000000000
.fill 252,8,0

在这里插入图片描述


关于A20地址线和8259A编程大致了解就好

参考<<X86实模式保护模式>>和<<linux内核完全注释0.12>>

seg指定段超越前缀
BIOS磁盘服务int 0x13
汇编jXX转移指令参考
BIOS int 0x10中断
Intel 8042键盘控制器详细介绍
IA-32指令手册