从打开电源开始…
这神秘的黑色背后发生着什么?…
打开电源,计算机执行的第一句指令什么?
计算模型(图灵机) ⇒ 我们要 关注 指针IP 及其 指向的内容
看看x86 PC
(1) 刚开机时CPU 处于实模式(和保护模式对应,实模式的寻址CS:IP(CS 左移4 位+IP) ,和保护模式不一样!)
(2) 开机时,CS=0xFFFF; IP=0x0000
(3) 寻址0xFFFF0(ROM BIOS 映射区)
(4) 检查RAM ,键盘,显示器,软硬磁盘
(5) 将磁盘0 磁道0 扇区读入0x7c00 处
(6) 设置cs=0x07c0 ,ip=0x0000
0x7c00 处存放的代码
就是从磁盘引导扇区读入的那512 个字节
引导扇区代码: bootsect.s
.globl begtext,begdata,begbss,endtext,enddata,endbss .text // 文本段 .text 等是伪操作符,告诉编译器产生文本段,.text 用于标识文本段的开始位置。此处的.text 、.data 、.bss 表明这3 个段重叠,不分段! begtext: .data // 数据段 begdata: .bss // 未初始化数据段 begbss: entry start // 关键字entry 告诉链接器 "程序入口" start: mov ax, #BOOTSEG mov ds, ax//此条语句就是0x7c00处存放的语句! mov ax, #INITSEG mov es, ax mov cx, #256 sub si, si sub di,di//将0x07c0:0x0000 处的256 个字移动到0x9000:0x0000处 rep movw jmpi go, INITSEG BOOTSEG = 0x07c0 INITSEG = 0x9000 SETUPSEG = 0x9020
jmpi go, INITSEG
jmpi (jump intersegment 段间跳转): cs=INITSEG, ip=go
go: mov ax,cs //cs=0x9000 mov ds,ax mov es,ax mov ss,ax mov sp,#0xff00//为call 做准备! load_setup: // 载入setup 模块 mov dx,#0x0000 mov cx,#0x0002 mov bx,#0x0200 mov ax,#0x0200+SETUPLEN int 0x13 //BIOS 中断 0x13 是BIOS 读磁盘扇区的中断: ah=0x02- 读磁盘,al=扇区数量(SETUPLEN=4) ,ch= 柱面号,cl= 开始扇区,dh= 磁头号,dl= 驱动器号,es:bx=内存地址
jnc ok_load_setup mov dx,#0x0000 mov ax,#0x0000 // 复位 int 0x13 j load_setup //重读
读入setup 模块后: ok_load_setup
Ok_load_setup: // 载入setup 模块
mov dl,#0x00 mov ax,#0x0800 //ah=8 获得磁盘参数
int 0x13 mov ch,#0x00 mov sectors,cx
mov ah,#0x03 xor bh,bh int 0x10 // 读光标
mov cx,#24 mov bx,#0x0007//7 是显示属性!
mov bp,#msg1 mov ax,#1301 int 0x10 // 显示字符
mov ax,#SYSSEG //SYSSEG=0x1000
mov es,ax
call read_it // 读入system 模块
jmpi 0,SETUPSEG//转入0x9020:0x0000执行setup.s
bootsect.s据 中的数据 // 在文件末尾 sectors: .word 0 // 磁道扇区数 msg1:.byte 13,10 .ascii "Loading system..." .byte 13,10,13,10
boot 工作: 读setup ,读system…
read_it // 读入system
为什么读入system 模块还需要定义一个函数?
system 模块可能很大,要跨越磁道!
read_it: mov ax,es cmp ax,#ENDSEG jb ok1_read ENDSEG=SYSSEG+SYSSIZE SYSSIZE=0x8000 // 该变量可根据 Image 大小设定( 编译操作系统时)
ret
ok1_read:
mov ax,sectors
sub ax,sread //sread 是当前磁道已读扇区数,ax 未读扇区数
call read_track // 读磁道...
引导扇区的末尾 //BIOS 用以识别引导扇区
.org 510
.word 0xAA55 //扇区的最后两个字节
否则会打出非引导设备
可以转入setup 执行了,jmpi 0, SETUPSEG
Power On…
setup 模块,即setup.s
根据名字就可以想到: setup 将完成OS启动前的设置
start: mov ax,#INITSEG mov ds,ax mov ah,#0x03
xor bh,bh int 0x10// 取光标位置dx mov [0],dx 取出光标位置( 包括其他硬件参数)到0x90000
mov ah,#0x88 int 0x15 mov [2],ax ... //扩展内存大小 SYSSEG = 0x1000
cli /// 不允许中断
mov ax,#0x0000 cld
do_move: mov es,ax add ax,#0x1000
cmp ax,#0x9000 jz end_move
mov ds,ax sub di,di
sub si,si
mov cx,#0x8000
rep //将system 模块移到0
movsw
jmp do_move
内存地址 |
长度 |
名称 |
0x90000 |
2 |
光标位置 |
0x90002 |
2 |
扩展内存数 |
0x901FC |
2 |
根设备号 |
0x9000C |
2 |
显卡参数 |
将setup 移到0 地址处...
end_move: mov ax,#SETUPSEG mov ds,ax
lidt idt_48 lgdt gdt_48// 设置保护模式下的中断和寻址
进入保护模式的命令 ...
idt_48:.word 0 .word 0,0 // 保护模式中断函数表, 又一个函数表
gdt_48:.word 0x800 .word 512+gdt,0x9
gdt: .word 0,0,0,0
.word 0x07FF, 0x0000, 0x9A00, 0x00C0
.word 0x07FF, 0x0000, 0x9200, 0x00C0
用GDT 将cs:ip 变成物理地址
保护模式下的地址翻译和中断处理
进入保护模式
call empty_8042 mov al,#0xD1 out #0x64,al
//8042 是键盘控制器,其输出端口P2 用来控制A20 地址线
call empty_8042 mov al,#0xDF out #0x60,al
// 选通A20 地址线 call empty_8042
初始化8259( 中断控制) // 一段非常机械化的程序
mov ax,#0x0001 mov cr0,ax
jmpi 0,8
D1 表示写数据到8042 的P2
jmpi 0,8 //gdt中的8
跳到system 模块执行...
disk: Image
dd bs=8192 if=Image of=/dev/PS0 if=input file /dev/PS0是软驱A
Image: boot/bootsect boot/setup tools/system tools/build
tools/build boot/bootsect boot/setup tools/system > Image linux/Makefile
tools/system: boot/head.o init/main.o $(DRIVERS) …
$(LD) boot/head.o init/main.o $(DRIVERS) … -o tools/system
明白为什么head.s 就这样一个名字了吧?
head.s // 一段在保护模式下运行的代码
setup 是进入保护模式,head 是进入之后的初始化
stratup_32: movl $0x10,%eax mov %ax,%ds mov %ax,%es
mov %as,%fs mov %as,%gs // 指向gdt 的0x10 项( 数据段) idt_48:.word 0 word 0,0
lss _stack_start,%esp // 设置栈( 系统栈) 现在忽略中断_ idt: .fill 256,8,0
call setup_idt struct{long *a; short b;}stack_start={&user_stack[PAGE_SIZE>>2],0x10};
call setup_gdt 和前面的代码不一样了? 因为是32 位汇编代码!
xorl %eax,%eax
1:incl %eax
movl %eax,0x000000 cmpl %eax,0x100000
je 1b //0 地址处和1M 地址处相同(A20 没开启) ,就死循环
jmp after_page_tables // 页表,什么东东?
setup_idt: lea ignore_int,%edx
movl $0x00080000,%eax movw %dx,%ax
lea _idt,%edi movl %eax,(%edi)
关于汇编…head.s的汇编和前面不一样?
mov ax, cs //cs -> ax, 目标操作数在前
AT&T 美国电话电报公司,包含贝尔实验室等,1983 年AT&T UNIX 支持组发布了系统V
movl var, %eax //(var) ->%eax
movb -4(%ebp), %al // 取出一字节
(3) 内嵌汇编,gcc 编译x.c 会产生中间结果as 汇编文件x.s
__asm__(" 汇编语句": 输出 : 输入 : 破坏部分描述);
__asm__("movb %%fs:%2, %%al" :"=a"(_res) : "0"(seg), "m"(*(addr)) );
0 或空表示使用与相应输出一样的寄存器
a 表示使用eax ,并编号%0
%2 表示addr ,m表示使用内存
after_page_tables // 设置了页表之后
setup 是进入保护模式,head
after_page_tables:
pushl $0 pushl $0 pushl $0 pushl $L6
pushl $_main jmp set_paging
L6: jmp L6 //将来学到!
setup_paging: 设置页表 ret
简单的几句程序,控制流却很复杂
setup_paging 执行ret 后? 会执行函数main() |
进入main() 后的栈为0 ,0 ,0 ,L6 |
main() 函数的三个参数是0 ,0 ,0 |
main() 函数返回时进入L6 ,死循环... |
进入main 函数
在init/main.c 中 //开始C 语言程序了!
void main(void)
{ mem_init();
trap_init();
blk_dev_init();
chr_dev_init();
tty_init();
time_init();
sched_init();
buffer_init();
hd_init();
floppy_init();
sti();
move_to_user_mode();
if(!fork()){init();}
}
为什么是void?
三个参数分别是envp,argv,argc但此处main 并没使用
此处的main 只保留传统main 的形式和命名main 表示C 语言函数的入口!
main 的工作就是xx_init: 内存、中断、设备、时钟、CPU 等内容的初始化…
看一看mem_init…
在linux/mm/memory.c 中
void mem_init(long start_mem,long end_mem) //这两个参数从哪里来?
{
int i;
for(i=0; i<PAGING_PAGES; i++)
mem_map[i] = USED;
i = MAP_NR(start_mem);
end_mem -= start_mem;
end_mem >>= 12;
while(end_mem -- > 0) //干了些什么?
mem_map[i++] = 0; }
管理硬件? 如何管理?就是用数据结构+ 算法…
[No000031]操作系统 Operating Systems 之Open the OS!
原文:http://www.cnblogs.com/Chary/p/No000031.html