babyos2(1)——boot

news/2024/12/29 17:27:35/

要做一个能在裸机上跑的系统,一个简单的boot loader或者是支持grub之类的是必须的。目前并没有把babyos装到实体机上、支持多系统等的打算,所以还是自己写boot loader。
PC上电后,80x86 CPU自动进入实模式,并从0xFFFF0开始自动执行,这个地址是ROM-BIOS中的地址。BIOS会执行一些检测及初始化中断向量表等,之后它将启动设备第一个扇区512字节读入内存0x7c00,并跳转过去开始执行。这一段是硬件自动完成的,给出的这几个地址也是硬件决定的,跟boot loader无关,跟内核也无关。而一旦跳转到0x7c00处开始执行,就是自己写的代码在工作了。
因为硬件自动加载到内存的数据只有512自己,所以一般操作系统需要自己把自己加载到内存中去,这就是boot loader干的事情。
BIOS内存分布
在boot阶段,开始时处于16位实模式,内存的使用也非常自由。但是自由的同时需要做好规划,比如当还需要用到BIOS中断的时候,不要破坏掉BIOS所占的内存区域。BIOS的内存分布如上图所示。

# the main function
main:xorw    %ax,            %axmovw    %ax,            %dsmovw    %ax,            %esmovw    %ax,            %ssmovw    $STACK_BOOT,   %spcall    clear_screencall    set_video_modecall    get_memory_infocall    load_kernelcall    copy_gdt_and_video_infocall    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 screen
clear_screen:movb    $0x06, %ahmovb    $0x00, %al     # roll up all rows, clear the screenmovb    $0x00, %ch     # row of left top cornermovb    $0x00, %cl     # col of left top cornermovb    $0x18, %dh     # row of right bottom cornermovb    $0x4f, %dl     # col of right bottom cornermovb    $0x07, %bh     # property of roll up rowsint     $0x10
ret

清屛函数使用BIOS 0x10号中断,ah=0x06,al=0x00表示上滚所有行,即清屛。

2.set_video_mode

# function to set video mode
set_video_mode:xorw    %ax,        %axmovw    %ax,        %dsmovw    %ax,        %esmovw    $0x800,        %di # buffer# check vbemovw    $0x4f00,   %axint     $0x10cmp     $0x004f,   %axjne     set_vga_0x13movw    0x04(%di),  %axcmp     $0x0200,   %ax # vbe version < 2.0jb      set_vga_0x13# check vbe mode 0x118movw    $0x118,        %cxmovw    $0x4f01,   %axint     $0x10cmpb    $0x00,     %ah # call failedjne     set_vga_0x13cmpb    $0x4f,     %al # not support this modejne     set_vga_0x13movw    (%di),      %axandw    $0x0080,   %ax # not support Linear Frame Buffer memory modeljz      set_vga_0x13# save video infomovw    $0x118,        video_modemovw    0x12(%di),  %axmovw    %ax,        screen_xmovw    0x14(%di),  %axmovw    %ax,        screen_ymovb    0x19(%di),  %almovb    %al,        bits_per_pixelmovb    0x1b(%di),  %almovb    %al,        memory_modelmovl    0x28(%di),  %eaxmovl    %eax,       video_ram#set vbe modemovw    $0x118,        %bxaddw    $0x4000,   %bxmovw    $0x4f02,   %axint     $0x10retset_vga_0x13:movb    $0,            %ahmovb    $0x13,     %alint     $0x10ret

该函数首先指定了一块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 0x0000
disk_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 hd
read_a_sect_hd:lea     disk_addr_packet,   %simovb    $0x42,              %ahmovb    $0x80,              %dlint     $0x13ret# function to load the kernel
load_kernel:lea     disk_addr_packet,   %simovw    $TMP_KERNEL_ADDR>>4,6(%si)xorw    %cx,                %cx1:call    read_a_sect_hdlea     disk_addr_packet,   %simovl    8(%si),             %eaxaddl    $0x01,              %eaxmovl    %eax,               (disk_addr_packet + 8)movl    6(%si),             %eaxaddl    $512>>4,            %eaxmovl    %eax,               (disk_addr_packet + 6)incw    %cxcmpw    $KERNEL_SECT_NUM+1,    %cxjne     1b# move first sector(the loader) to 0x0000cld                                                 # si, di incrementmovw    $TMP_KERNEL_ADDR>>4,               %axmovw    %ax,                                %ds     # DS:SI srcxorw    %si,                                %simovw    $0x00,                             %axmovw    %ax,                                %es     # ES:DI dstxorw    %di,                                %dimovw    $(LOADER_SECT_NUM*SECT_SIZE) >> 2, %cx     # 512/4 times rep     movsl                                       # 4 bytes per timeret

该函数主要功能是利用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 position
copy_gdt_and_video_info:xorw    %ax,                        %axmovw    %ax,                        %ds     # DS:SI srcleaw    gdt,                        %simovw    $BOOT_INFO_SEG,                %axmovw    %ax,                        %es     # ES:DI dstxorw    %di,                        %dimovw    $(GDT_SIZE+VIDEO_INFO_SIZE),%cx        # num of bytes to moverep     movsbret

这个函数比较简单,主要功能是把前面保存下来的信息拷贝到一个安全的位置,babyos2把这些数据拷贝到了0x90000。内核启动后会用到这些数据,比如显存位置,内存信息等。

begin_protected_mode:

# function to begin protected mode
begin_protected_mode:cli
1:inb     $0x64,         %altestb   $0x02,         %aljnz     1bmovb    $0xd1,         %aloutb    %al,            $0x642:inb     $0x64,         %altestb   $0x02,         %aljnz     2bmovb    $0xdf,         %aloutb    %al,            $0x60lgdt    gdt_ptrmovl    %cr0,           %eaxorl     $CR0_PE,       %eaxmovl    %eax,           %cr0ljmp    $SEG_KCODE<<3,	$0ret

这个函数将会跳转到保护模式。
8086只有20位地址总线(A19-A0),为了访问超过1M的内存,需要打开存储器的A20地址线,IBM使用了8042键盘控制器上的一根线来控制,通过访问0x64端口可以打开A20.
然后需要加载gdt;
然后就是cr0寄存器的CR0_PE位需要置1;
最后执行一个ljmp,跳转到loader执行。loader的加载地址会设置为0,所以这里跳转到SEG_KCODE段,偏移地址为0的位置执行。

.p2align 2
gdt:
.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,            %eaxmovw    %ax,                    %dsmovw    %ax,                    %esmovw    %ax,                    %fsmovw    %ax,                    %gsmovw    %ax,                    %ssmovl    $STACK_PM_BOTTOM,      %espcall    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的加载,后面再描述。


http://www.ppmy.cn/news/314499.html

相关文章

1189 SEARCH

1189 SEARCH 一、用普通深搜 这是洛谷唯一一个迭代加深的题目&#xff0c;真的是太少了&#xff0c;用它来练练手 这个问题的求解思路就是说按照它所给的方向进行一个搜索&#xff0c;而不是我们一贯的使用上下左右的顺序了&#xff0c;这就是我之前和zqc问的一样&#xff0c;…

通俗易懂的卷积神经网络教程-第二讲

先回忆第一讲的内容 我们通过把图片数字化&#xff0c;变成一个个的小点&#xff0c;再把小点点给拉成一行&#xff0c;变成自变量。然后我们把图片中的数字作为标签&#xff0c;也就是因变量&#xff0c;我们设定一张图片有10个因变量&#xff0c;这十个变量值是0或1的数字&am…

原理介绍_正则表达式

文章目录 元字符反义其它符号贪婪和懒惰模式常用示例1. 不匹配 辅助工具教程 元字符 所谓元字符就是指那些在正则表达式中具有特殊意义的专用字符&#xff0c;可以用来规定其前导字符&#xff08;即位于元字符前面的字符&#xff09;在目标对象中的出现模式。 符号作用示例.匹…

b'\xe5\x8f\x98\xe6\x80\x81'

>>>变态.encode(utf-8) b\xe5\x8f\x98\xe6\x80\x81 >>> print(b\xe5\x8f\x98\xe6\x80\x81.decode(utf-8)) 变态

xorg

导航 (返回顶部) 1. xorg概述2. 只安装必要 2.1 基础包:xorg-server,xorg-xinit2.2 显卡驱动:xf86-video-intel2.3 输入设备:xf86-input-libinput2.4 小结 3. group4. gentoo相关链接5. Graphical user interface 图形用户界面概述 1. xorg概述 2. 只安装必要2.1 基础包:xorg-…

12.18

在object类中 tostring 直接打印对象的额名字 就是调用对象的tostring方法。。p p.tostring 直接打印对象的地址值没有意义&#xff0c;所以需要重写object类的tostring方法&#xff0c;打印对象的属性&#xff08;name.age&#xff09; voerride public string tostring&a…

X11 linux配置和windows配置

LINUX部分 &#xff08;服务器需要有ssh功能安装apt-get install openssh-server 卸载apt-get remove openssh-server&#xff09; 1、linux编辑/etc/ssh/sshd_config 文件&#xff0c;激活X11转发。 X11Forwarding yes 需要重启&#xff08;service ssh restart&#xff09; …

一种基于卷积神经网络的数据驱动故障预测方法(含代码)

本文以CWRU轴承故障的振动信号数据库作为模型的训练集和测试集 并根据现有论文的思路和模型框架&#xff0c;用pytorch复现了论文的模型结构和性能&#xff0c;在二分类问题中准确率高达100% 本文在理论方面不再过多赘述&#xff0c;详细可看博主之前的博客或观看论文原文 数…