babyos2(1)——boot
来源:互联网 发布:手机淘宝如何联系卖家 编辑:程序博客网 时间:2024/05/20 01:13
要做一个能在裸机上跑的系统,一个简单的boot loader或者是支持grub之类的是必须的。目前并没有把babyos装到实体机上、支持多系统等的打算,所以还是自己写boot loader。
PC上电后,80x86 CPU自动进入实模式,并从0xFFFF0开始自动执行,这个地址是ROM-BIOS中的地址。BIOS会执行一些检测及初始化中断向量表等,之后它将启动设备第一个扇区512字节读入内存0x7c00,并跳转过去开始执行。这一段是硬件自动完成的,给出的这几个地址也是硬件决定的,跟boot loader无关,跟内核也无关。而一旦跳转到0x7c00处开始执行,就是自己写的代码在工作了。
因为硬件自动加载到内存的数据只有512自己,所以一般操作系统需要自己把自己加载到内存中去,这就是boot loader干的事情。
在boot阶段,开始时处于16位实模式,内存的使用也非常自由。但是自由的同时需要做好规划,比如当还需要用到BIOS中断的时候,不要破坏掉BIOS所占的内存区域。BIOS的内存分布如上图所示。
# the main functionmain: xorw %ax, %ax movw %ax, %ds movw %ax, %es movw %ax, %ss movw $STACK_BOOT, %sp call clear_screen call set_video_mode call get_memory_info call load_kernel call copy_gdt_and_video_info call begin_protected_mode1: jmp 1b
babyos2的boot主要做了下面几件事情:
1.清屛,主要功能是清除掉虚拟机启动时自动打印的一些信息
2.设置显示模式,虽然这次决定不再只做一些花哨的显示相关的东西,但终究没舍得放弃1024*768的显示模式,毕竟比单纯的字符模式有趣的多。这个函数将会设置VBE 模式为0x118,即1024*768,24位的显示模式。
3.获取内存信息,这个暂时不在这里说,后面再描述。
4.加载内核。这是最主要的功能之一,把内核加载到一个临时的地方,因为一旦跳转到保护模式,实模式的BIOS中断就不能或者不容易使用了。因为babyos2从硬盘启动,这个函数主要功能是读硬盘。
5.将gdt和显示模式,及内存相关的一些信息拷贝到一个安全的地址。
6.进入保护模式。
1.clear_screen
# function to clear the screenclear_screen: movb $0x06, %ah movb $0x00, %al # roll up all rows, clear the screen movb $0x00, %ch # row of left top corner movb $0x00, %cl # col of left top corner movb $0x18, %dh # row of right bottom corner movb $0x4f, %dl # col of right bottom corner movb $0x07, %bh # property of roll up rows int $0x10ret
清屛函数使用BIOS 0x10号中断,ah=0x06,al=0x00表示上滚所有行,即清屛。
2.set_video_mode
# function to set video modeset_video_mode: xorw %ax, %ax movw %ax, %ds movw %ax, %es movw $0x800, %di # buffer # check vbe movw $0x4f00, %ax int $0x10 cmp $0x004f, %ax jne set_vga_0x13 movw 0x04(%di), %ax cmp $0x0200, %ax # vbe version < 2.0 jb set_vga_0x13 # check vbe mode 0x118 movw $0x118, %cx movw $0x4f01, %ax int $0x10 cmpb $0x00, %ah # call failed jne set_vga_0x13 cmpb $0x4f, %al # not support this mode jne set_vga_0x13 movw (%di), %ax andw $0x0080, %ax # not support Linear Frame Buffer memory model jz set_vga_0x13 # save video info movw $0x118, video_mode movw 0x12(%di), %ax movw %ax, screen_x movw 0x14(%di), %ax movw %ax, screen_y movb 0x19(%di), %al movb %al, bits_per_pixel movb 0x1b(%di), %al movb %al, memory_model movl 0x28(%di), %eax movl %eax, video_ram #set vbe mode movw $0x118, %bx addw $0x4000, %bx movw $0x4f02, %ax int $0x10 retset_vga_0x13: movb $0, %ah movb $0x13, %al int $0x10 ret
该函数首先指定了一块buffer,用来存放调用vbe相关函数后返回的结果,这里放到了es:0x800,从上面BIOS内存布局那个图可以看出,这个地址位于BIOS数据区的上面,boot sector的下面,暂时没有人使用,比较安全。
然后会检测是否支持vbe及vbe的版本,若不支持或者版本<2.0则设置显示模式位0x13;
然后检测是否支持0x118显示模式,如果不支持该模式,或者不支持Linear Frame Buffer memory model,还是设置为0x13模式;
然后把0x118显示模式下的一些信息,如width, height, bits per pixel,memory model等信息记录下来;
然后设置显示模式为0x118。至此显示模式设置完毕,可以发现此时虚拟机的窗口大小变为1024*768.
load_kernel:
# read kernel from hd# and put loader to 0x0000disk_addr_packet: .byte 0x10 # [0] size of packet 16 bytes .byte 0x00 # [1] reserved always 0 .word 0x01 # [2] blocks to read .word 0x00 # [4] transfer buffer(16 bit offset) .word 0x00 # [6] transfer buffer(16 bit segment) .long 0x01 # [8] starting LBA .long 0x00 # [12]used for upper part of 48 bit LBAs# function to read a sect from hdread_a_sect_hd: lea disk_addr_packet, %si movb $0x42, %ah movb $0x80, %dl int $0x13 ret# function to load the kernelload_kernel: lea disk_addr_packet, %si movw $TMP_KERNEL_ADDR>>4,6(%si) xorw %cx, %cx1: call read_a_sect_hd lea disk_addr_packet, %si movl 8(%si), %eax addl $0x01, %eax movl %eax, (disk_addr_packet + 8) movl 6(%si), %eax addl $512>>4, %eax movl %eax, (disk_addr_packet + 6) incw %cx cmpw $KERNEL_SECT_NUM+1, %cx jne 1b # move first sector(the loader) to 0x0000 cld # si, di increment movw $TMP_KERNEL_ADDR>>4, %ax movw %ax, %ds # DS:SI src xorw %si, %si movw $0x00, %ax movw %ax, %es # ES:DI dst xorw %di, %di movw $(LOADER_SECT_NUM*SECT_SIZE) >> 2, %cx # 512/4 times rep movsl # 4 bytes per time ret
该函数主要功能是利用BIOS中断读硬盘。
首先定义了一个数据结构disk_addr_packet,该结构指定了要读取硬盘的位置(LBA),及存放数据的buffer位置。然后每次增加LBA及buffer,并利用0x13号中断(ah=0x42)来读取数据。
babyos2将内核临时加载到0x10000。
然后将内核的第一个扇区拷贝到0x0000位置。硬盘中第一个扇区是boot,第二个扇区是loader,这里所说的内核第一个扇区是loader。
为了方便调试,babyos2的kernel采用elf格式,所以loader的作用是从临时内核的位置,按elf格式解析并加载内核各个段到指定位置。
copy_info:
# function to copy gdt and video info to a safe positioncopy_gdt_and_video_info: xorw %ax, %ax movw %ax, %ds # DS:SI src leaw gdt, %si movw $BOOT_INFO_SEG, %ax movw %ax, %es # ES:DI dst xorw %di, %di movw $(GDT_SIZE+VIDEO_INFO_SIZE),%cx # num of bytes to move rep movsb ret
这个函数比较简单,主要功能是把前面保存下来的信息拷贝到一个安全的位置,babyos2把这些数据拷贝到了0x90000。内核启动后会用到这些数据,比如显存位置,内存信息等。
begin_protected_mode:
# function to begin protected modebegin_protected_mode: cli1: inb $0x64, %al testb $0x02, %al jnz 1b movb $0xd1, %al outb %al, $0x642: inb $0x64, %al testb $0x02, %al jnz 2b movb $0xdf, %al outb %al, $0x60 lgdt gdt_ptr movl %cr0, %eax orl $CR0_PE, %eax movl %eax, %cr0 ljmp $SEG_KCODE<<3,$0 ret
这个函数将会跳转到保护模式。
8086只有20位地址总线(A19-A0),为了访问超过1M的内存,需要打开存储器的A20地址线,IBM使用了8042键盘控制器上的一根线来控制,通过访问0x64端口可以打开A20.
然后需要加载gdt;
然后就是cr0寄存器的CR0_PE位需要置1;
最后执行一个ljmp,跳转到loader执行。loader的加载地址会设置为0,所以这里跳转到SEG_KCODE段,偏移地址为0的位置执行。
.p2align 2gdt:.quad 0x0000000000000000.quad 0x00cf9a000000ffff.quad 0x00cf92000000ffff.quad 0x0000000000000000.quad 0x0000000000000000.quad 0x0000000000000000
gdt是个表,每个表项描述一个段。而SEG_KCODE是一个索引,去寻找一个段,这里指的就是内核代码段。关于这些的知识在Intel的文档上都能找到,不再赘述。
load:
.org 0_start: jmp mainmain: movl $DATA_SELECTOR, %eax movw %ax, %ds movw %ax, %es movw %ax, %fs movw %ax, %gs movw %ax, %ss movl $STACK_PM_BOTTOM, %esp call loadmain1: jmp 1b
loadmain:
/* GDT和IDT内存地址和大小 */#define IDT_ADDR (0x90000) #define IDT_SIZE (256*8)#define GDT_ADDR (IDT_ADDR + IDT_SIZE)#define GDT_LEN (5)#define GDT_SIZE (8 * GDT_LEN)/* 显示模式的一些信息的内存地址 */#define VIDEO_INFO_ADDR (GDT_ADDR + GDT_SIZE)typedef struct vidoe_info_s { uint16 video_mode; uint16 cx_screen; uint16 cy_screen; uint8 n_bits_per_pixel; uint8 n_memory_model; uint8* p_vram_base_addr;} video_info_t;video_info_t* p_video_info = (video_info_t *)VIDEO_INFO_ADDR;uint8* p_vram_base_addr = (uint8 *)0xe0000000;uint32 cx_screen = 1024;uint32 cy_screen = 768;uint32 n_bytes_per_pixel = 3;static bool is_pixel_valid(int32 x, int32 y){ if (x < 0 || y < 0 || (uint32)x >= cx_screen || (uint32)y >= cy_screen) { return false; } return true;}bool set_pixel(int32 x, int32 y, uint8 r, uint8 g, uint8 b){ uint8* pvram = NULL; if (!is_pixel_valid(x, y)) { return false; } pvram = p_vram_base_addr + n_bytes_per_pixel*y*cx_screen + n_bytes_per_pixel*x; pvram[0] = b; pvram[1] = g; pvram[2] = r; return true;}void test(){ for (int i = 100; i < 1024-100; i++) { set_pixel(i, 200, 0xff, 34, 89); }}void loadmain(void){ p_vram_base_addr = p_video_info->p_vram_base_addr; test();}
loadmain主要目的是加载elf格式的内核,但此时它还是一些测试代码,主要功能是绘制一条直线,表示顺利进入保护模式,并执行到了这里~
elf格式kernel的加载,后面再描述。
- babyos2(1)——boot
- babyos2(15)—— bug fix 1
- babyos2(0)——从零开始
- babyos2(3)—— console, kprintf
- babyos2(4)——memory ranges
- babyos2(5)——分页
- babyos2(9)——系统调用
- babyos2(16)—— sleep, wakeup
- babyos2(2)—— load elf format kernel
- babyos2(6)——IDT,中断,异常
- babyos2(8)——读IDE硬盘
- babyos2(11)——物理内存管理,伙伴系统
- babyos2(12)——pagefault, vm_area, mmap
- babyos2(7)——键盘中断、时钟中断、实时钟
- babyos2(10)——进程,调度,fork,exec,用户空间
- babyos2(13)——进程页表,fork, COW(copy on write)
- babyos2(14)—— 用户态栈的扩展,加载elf
- Spring Boot(1)——Spring Boot简介
- Python大牛之路所需技术和工具
- VS C工程【建立空工程】【打开VC6++工程:警告1 warning C4996: 'scanf': This function or variable may be】【C语言运行窗口复制粘贴编辑】
- HPU第七周周练 A
- Maven私服的搭建
- Nginx安装手册
- babyos2(1)——boot
- Matrix Factorization: A Simple Tutorial and Implementation in Python
- Linux安装ftp组件
- 关于父类静态方法能不能被子类重写详细
- ArrayList分析
- webpack:使用expose-loader 解决第三方库的插件依赖问题
- N行N列表格的路径问题
- python 字符串拼接、格式化
- 解决双系统下如何完全卸载Ubuntu系统