1.打开电源
(1)x86 PC开机时CPU处于实模式,实模式的寻址方式是CS:IP (CS左移4位+IP)
(2)开机时段寄存器CS=0xFFFF,偏移量IP=0x0000,段寄存器左移4位加上偏移量是实际地址,也就是寻址地址为0xFFFF0 (ROM BIOS映射区)
(3)检查RAM,键盘,显示器,磁盘
(4)将0磁道0扇区512个字节读入0x7c00处(操作系统的引导扇区)
(5)设置cs=0x7c0,ip=0x0000
2.引导扇区代码bootsect.s
(1)将bootsect从0x7c00处移动到0x90000处
(2)将setup读入到0x90200处
(3)将system读入0x91000处
(4)将PC移动到0x90200处,bootsect结束
1. 移动bootsect.s。读取0x7c00处的内存,首先设置源地址和目标地址,并且将0x7c00内存处的256个字(512字节)移动到了0x90000
start: mov ax, #BOOTSEG mov ds, ax ! 将 ds 段寄存器设置为0x07C0(16位汇编,后面的放入前面) mov ax, #INITSEG mov es, ax ! 将 es 段寄存器设置为0x9000 sub si, si ! 源地址 ds:si 0x7c00 sub di, di ! 目标地址 es:di 0x90000 mov cx, #256 ! 设置移动计数值256字(512字节) rep movw ! 重复移动直到cx为0,将bootsect移动到了0x90000处 jmpi go, INITSEG ! 跳转到0x9000:go处,将设置es、ss、sp都为0x9000(相当于是初始化)
2. 0x13读磁盘中断,读取setup代码。在磁盘第2个扇区开始,读取4个扇区的内容,并将这4个扇区的内容写入到0x90200内存处。因为bootsect占用512字节,所以需要写在0x90000的512个字节之后,16位表示就是在0x90200处写入。
go: mov ax, cs ! 0x9000 ! 将ds、es、ss都置成移动后代码所在段处0x9000、栈顶地址设置为0x9fff00,来远离代码位置 mov ds, ax mov es, ax mov ss, ax mov sp, #0xFF00 load_setup:
// 载入setup模块, 从磁盘第2个扇区开始读4个扇区, 写入0x90200处 mov dx, #0x0000 ! 数据寄存器 mov cx, #0x0002 ! 计数寄存器 ch:cx高8位, 0x00(柱面号0); cl:低8位, 0x02(开始扇区为2) mov bx, #0x0200 ! 基址寄存器 写到es:bx 0x90200处 mov ax, #0x0200+SETUPLEN ! 累加器 ah: ax高8位, 0x20(读磁盘); al:ax低8位, 0x04(读取4个扇区) int 0x13 ! BIOS读磁盘扇区中断 jnc ok_load_setup ! 跳转指令, CF=0则跳转。如果没有读错,则跳转到加载setup执行;如果读错,则复位驱动器重新读。 mov dx, #0x0000 ! 对驱动器0进行操作 mov ax, # 0x0000 ! 复位 int 0x13 ! 执行0x13中断(BIOS读磁盘扇区的中断) j load_setup ! 重读
3. 0x10显示字符中断,显示字符在显示器上。ok_load_setup,显示欢迎页面,调用read_it读入system,最后将控制权交接给setup模块,也就是将PC移动到0x90200处,开始执行setup。bootsect正式结束。
ok_load_setup: ! 载入setup,显示开机文字,读取system ! 读取setup,显示开机文字 mov dl, #0x00 mov ax, #0x0800 ! ah=8,获得磁盘驱动器的参数 int 0x13 mov ch, #0x00 mov sectors, cx mov ah, #0x03 xor bh, bh int 0x00 ! 读光标 mov cx, #24 mov bx, #0x0007 ! 7是显示属性 mov bp, #msg1 mov ax, #1301 int 0x10 !显示字符 ! 读取system模块到0x10000处 mov ax, #SYSSEG ! SYSSEG=0X1000 mov es, ax call read_it ! 读入system jmpi 0, SETUPSEG ! 转入0x9020:0X0000执行setup.s
3.引导扇区代码setup.s
读取硬件参数,将操作系统移动到0地址处,进入保护模式,初始化一个很简单的gdt表,跳转到system模块
1. setup主要任务是读取硬件参数,初始化一个数据结构来管理这些硬件设备,并移动操作系统到内存0地址处。其中0x15中断是读取扩展内存大小(Intel原先内存只有1m,我们把1m以外的内存称为扩展内存)放在ax寄存器里,之后放到内存0x90002处。接下来do_move将0x90000处开始的数据移动到内存绝对地址0x00000处。
! 取光标位置,包括其他硬件参数,放到0x90000处 start : mov ax, #INITSEG mov ds, ax mov ah, #0x03 xor bh, bh int 0x10 mov [0], dx ! 光标位置设置为0x90000 mov ah, #0x88 int 0x15 mov [2], ax ... ! 读取扩展内存的大小(1m以外的内存称为扩展内存), 放在0x90002处 cli ! 不允许中断 mov ax, #0x0000, cld ! 设置读取目标位置和递增读取方式 ! 将system模块从0x10000动到0x00000处 do_mov: mov es, ax add ax, #0x1000 cmp ax, #0x9000 jz end_mov ! 若ax=0x9000,表示移动完成,则跳出 mov ds, ax sub di, di sub si, si ! 设置源地址0x90000和目标地址0x00000 mov cx, #0x8000 ! 设置移动数据的长度 rep movsw ! 将system模块移动到0地址 jmp do_move
2. 进入保护模式。cs左移4位+ip最大有20位地址,也就是最大内存空间只有1M。setup最后一步要从16位机切换到32寻址方式。
实模式:CS:IP,CS << 4 + IP,共20位寻址空间
保护模式:CS实际上是查表GDT(Global Descriptor Table),地址为CS查表 + IP
mov ax,#0x0001 ! 将0x0001放入ax寄存器 mov cr0,ax ! 将ax放入cr0寄存器,这一步非常重要 ! cr0寄存器最后一位是0的时候是CS:IP实模式 ! cr0寄存器最后一位是1的时候是保护模式,需要走另外一条解释指令的电路 jmpi 0,8 ! cs:8, ip:0; 这里实际上跳转的地址就是0地址
4.head.s
head.s是system中的第一个模块,负责重新加载各个数据段寄存器,重新设置中断描述符表,重新设置全局描述符表,设置分页处理机制(一个页目录表和4个页表),打开20号地址线访问4G内存,并且将main函数压栈,head执行完后弹出main,转到main函数。
startup_32: movl $0x10, %eax ! 现在开始是32位汇编,前面的放入后面 mov %ax, %ds mov %ax, %es mov %as, %fs mov %as, %gs ! 指向gdt的0x10数据项 lss _stact_start, %esp ! 设置系统栈 call setup_idt ! 设置中断描述符表子程序 call setup_gdt ! 设置全局描述符表子程序 mov $0x10, %eax ! 重载所有的段寄存器 ...
5.main函数
main函数中有大量初始化函数,例如初始化硬盘,初始化内存,将每4K内存作为一个页等等
6.系统调用
普通函数调用是通过call跳转对应函数的地址继续执行
系统调用是调用系统库中为该系统调用编写的一个接口函数,叫 API(Application Programming Interface)。API 并不能完成系统调用的真正功能,它要做的是去调用真正的系统调用,过程是:
- 把系统调用的编号存入 EAX
- 把函数参数存入其它通用寄存器
- 触发 0x80 号中断(int 0x80)
这里研究 close() 中的API,以宏_syscall1(int, close, int, fd)为例子,它在include/unistd.h中定义了展开的形式
这就是 API 的定义。它先将宏 __NR_close
存入 EAX,将参数 fd 存入 EBX,然后进行 0x80 中断调用。调用返回后,从 EAX 取出返回值,存入 __res
,再通过对 __res
的判断决定传给 API 的调用者什么样的返回值。
其中 __NR_close
在 include/unistd.h
中定义为 6
int close(int fd) { long __res; __asm__ volatile ("int $0x80" : "=a" (__res) : "0" (__NR_close),"b" ((long)(fd))); if (__res >= 0) return (int) __res; errno = -__res; return -1; }
在内核初始化时,主函数调用了 sched_init()
初始化函数:
void main(void) { // …… time_init(); sched_init(); buffer_init(buffer_memory_end); // …… }
sched_init()
在 kernel/sched.c
中定义为:
void sched_init(void) { // …… set_system_gate(0x80,&system_call); }
set_system_gate
是个宏,在 include/asm/system.h
有定义。这段宏就是填写 IDT(中断描述符表),将 system_call
函数地址写到 0x80
对应的中断描述符中,也就是在中断 0x80
发生后,自动调用函数 system_call
文章来源: 博客园
- 还没有人评论,欢迎说说您的想法!