欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

跳转到保护模式并显示一个LOGO

发布时间:2025/6/15 编程问答 42 豆豆
生活随笔 收集整理的这篇文章主要介绍了 跳转到保护模式并显示一个LOGO 小编觉得挺不错的,现在分享给大家,帮大家做个参考.

注:本程序为原创,若发现bug,万望指出,若有问题,欢迎交流,转载请指明出处。若能有助于一二访客,幸甚。


以下为结果截图,显示的LOGO为小篆字体的欢迎 baby os 加载完成...几个字。



保护模式
参考资料:
《Intel 64 and IA-32 Architectures Software Developer's Manual》
《Orange's 一个操作系统的实现'》
《X86/X64 体系探测及编程》
《30天自制操作系统》
《Linux 内核完全剖析》

0.概述

Intel IA 32下,CUP有两种工作模式:实模式和保护模式。打开PC,开始时CPU工作在实模式下,即此前几篇东西写的代码都是在实模式下的。
实模式下有16位的寄存器、16位的数据总线、及20位的地址总线,1MB的寻址能力。物理地址的计算方法:
物理地址(Physical Address) = 段值(Segment)* 16 + 偏移值(Offset) 其中段和偏移都是16位的。
从80386开始,Intel的CPU 进入32位时代,80386有32位地址总线,寻址能力达到4GB.
保护模式保护处理器的某些资源不能被随意访问,如处理器的硬件资源和系统的软件资源,如CR0等控制寄存器,GDT、IDT等系统级的数据结构,OS kernel的代码和数据等。
x86的segmentation和paging即分段和分页机制是实施保护措施的手段。分段和分页实行了不同的内存管理模式和访问控制。

1.权限和环境

4个权限级别:0~3,0为最高级别。
3种权限类型:CPL、DPL、RPL:
1)CPL(current privilege level):当前的权限级别,指示当前代码在哪个权限级别,CPL的值存放在CS寄存器Selector域的RPL。(另外,SS寄存器的Selector的RPL总等于CPL)。
2)DPL(Descriptor Privilege Level):DPL存放在描述符Descriptor(包括段描述符Segment Descriptor和门描述符Gate Descriptor)里的DPL域,它指示访问这些segment所需要的权限级别
3)RPL(Requested Privilege Level):存放在访问者所使用的选择子Selector的Bit0和Bit1,指示发起访问的访问者使用什么样的权限对目标进行访问。
若CPL > DPL表示当前运行的代码的权限级别不足,不能对segment或gate进行访问。

从实模式进入保护模式,段式管理机制必须建立,分页机制是可选的,当分页机制关闭时,从段式内存管理中得到的线性地址(linear address)就是物理地址。

2.段式管理所使用的资源

硬件资源:
1)CR0、CR4
2)GDTR、LDTR(可选)、IDTR、TR
3)段选择子寄存器:ES、CS、SS、DS、FS、GS寄存器

数据结构:
1)GDT、LDT(可选)、IDT
2)TSS
3)段描述符(Segment Descriptor):系统(System)段描述符、代码(Code)/数据(Data)段描述符
4)门描述符(Gate Descriptor):包括调用门(Call-gate),中断/陷阱门(Interrupt/Trap-gate)和任务门(Task-gate)
5)选择子(Selector):存放在段寄存器里。

分段机制的内存管理职责:从逻辑地址(Logic address)转换为处理器的线性地址(Linear address).

3.分页机制使用的资源:

1)控制寄存器:CR0、CR2、CR3、CR4
2)IA32_EFER
页转换表:
1)PDPT(Page Directory Pointer Table)
2)PDT(Page Directory Table)
3)PT(Page Table)

分页机制内存管理职责:从处理器的线性地址(即virtual address)映射到物理地址。

read/write的内存设备RAM(DRAM)、read-only的内存设备ROM(EPROM),及memory mapped I/O设备都可以映射到物理地址空间上。
典型的ROM设备映射到物理地址空间的高端和低端,Video和IGD设备的buffer映射到A0000H到BFFFFH的物理地址空间,PCIe等设备映射到物理地址空间的E0000000位置上,I/O APIC 设备映射到FEC00000以上的位置,等等。
经过页式转换形成的物理地址,可以映射到DRAM或外部存储设备Disk上。

4.段式内存管理

两方面的管理:
1)内存管理:为地址的转换提供基础
Linear Address = base + offset
与实模式下的原理是一致的,实模式下段的base = selector << 4; 保护模式下,base从segment descriptor里加载而来。
2)保护措施:对访问行为的控制
对段的limit、type、privilege检查

5.段式管理的数据结构

1)段选择子(Segment Selector)
RPL:bit0~bit1,请求访问者所使用的权限级别
TI:Table Indicator,描述符表索引位,bit2, TI=0表示GDT,TI=1表示LDT。
Index:Descriptor Index,它是Descriptor在GDT/LDT中的序号。bit3~bit15,13位,范围0~8191,即可寻址8192个descriptor。

2)描述符表(Descriptor Table)
Segment Selector用于在Descriptor Table中查找descriptor。
描述符表由描述符表寄存器进行定位,对应GDT,LDT,IDT有GDTR,LDTR,IDTR。在IA32中,这三个寄存器都是48位,包括低16位为Limit和髙32位为Base,加载描述符表方法为lgdt, lldt, lidt。
其中Limit用于检查Selector是否超出GDT的limit,如同数组的长度一样,判断数组是否越界。

3)段描述符(Segment Descriptor)
段描述符要么存放在描述符表里,要么被加载到段寄存器里。被加载到段寄存器后,它所描述的段变成了active状态。
描述符有两大类:段描述符和门描述符。

6.切换到保护模式

Intel推荐的步骤:
1)关中断,包括可屏蔽中断和不可屏蔽中断
2)使用lgdt加载GDTR
3)置cr0 的PE位,切换到保护模式
4)使用far jmp/call,提供一个同级权限的CS Selector更新CS寄存器
5)若需要使用LDT,用lldt加载LDTR
6)使用ltr加载TR
7)更新SS、DS寄存器
8)使用lidt加载IDTR
9)开中断


程序源码:

boot.s:

[cpp] view plaincopyprint?
  • #--------------------------------------------------------------  
  • # 文件:boot.s  
  • # 描述:1.清屏  
  • #       2.设置显示模式为0x103(800*600,256色)  
  • #       3.读取软盘,将内核加载到内存  
  • #       4.将内核第一个扇区(load.s)移动到内存0x0000位置  
  • #       5.将引导扇区中的GDT及新显示模式的一些参数移动到指定位置  
  • #       6.开启A20总线,置位cr0寄存器的PE位,进入保护模式  
  • # 时间:2012-12-29 21:47:12  
  • # 作者:guzhoudiaoke@126.com  
  • #--------------------------------------------------------------  
  •   
  • .include "include/kernel.inc"  
  •   
  • .section .text  
  • .global _start  
  • .code16  
  •   
  • _start:  
  •     jmp     main  
  •   
  • #---------------------------------------------------------------  
  • # 清屏:  
  • #   设置屏幕背景色,调色板的索引0指代的颜色为背景色  
  • #   先不考虑效率,只考虑可读性,故ah,al分开赋值  
  • #---------------------------------------------------------------  
  • clear_screen:               # 清屏函数  
  •     movb    $0x06,  %ah     # 功能号0x06  
  •     movb    $0,     %al     # 上卷全部行,即清屏  
  •     movb    $0,     %ch     # 左上角行  
  •     movb    $0,     %ch     # 左上角列    
  •     movb    $24,    %dh     # 右下角行  
  •     movb    $79,    %dl     # 右下角列  
  •     movb    $0x07,  %bh     # 空白区域属性  
  •     int     $0x10  
  •       
  •     ret  
  •   
  • #--------------------------------------------------------------------  
  • # 设置显示模式:  
  • #   1.检查VBE是否存在,即显卡是否支持VESA BIOS EXTENSION  
  • #   2.检查VBE版本,是否为2.0以上  
  • #   3.检查要设置的mode的一些参数,看是否符合要求  
  • #   4.设置显示模式为VBE 0x103(800*600,256色)  
  • #   5.记录新显示模式的一些参数  
  • #   6.若上面检查或设置失败,则设置显示模式为VGA 0x13(320*200,256色)  
  • #--------------------------------------------------------------------  
  • set_video_mode:  
  •     movw    $0x800,             %ax  
  •     movw    %ax,                %es  
  •     movw    %ax,                %ds  
  •     xorw    %di,                %di  
  • check_vbe:  
  •     movb    $0x4f,              %ah         # 表示使用VBE标准  
  •     movb    $0x00,              %al         # 功能号  
  •     int     $0x10  
  •     cmp     $0x004f,            %ax         # 若有VBE,AX应该为0x004f  
  •     jne     set_mode_vga_0x13  
  •     movw    0x04(%di),          %ax  
  •     cmp     $0x0200,            %ax         # 若VBE版本不是2.0以上  
  •     jb      set_mode_vga_0x13  
  • check_vbe_mode:                             # 检查MODE_VBE_0x13的参数  
  •     movw    $VIDEO_MODE_0x103,  %cx  
  •     movb    $0x4f,              %ah         # 表明VBE标准  
  •     movb    $0x01,              %al         # 子功能号  
  •     int     $0x10  
  •     cmpb    $0x00,              %ah         # 是否调用成功  
  •     jne     set_mode_vga_0x13  
  •     cmpb    $0x4f,              %al         # 是否支持该模式  
  •     jne     set_mode_vga_0x13  
  •     cmpb    $8,                 0x19(%di)   # 颜色是否占8bit  
  •     jne     set_mode_vga_0x13  
  •     cmpb    $4,                 0x1b(%di)   # 颜色的指定方法为4(调色板方式)  
  •     jne     set_mode_vga_0x13  
  •     movw    (%di),              %ax  
  •     andw    $0x0080,            %ax  
  •     jz      set_mode_vga_0x13               # AX第bit7是否为1(线性帧缓存是否有效)  
  • set_mode_vbe:                               # 下面设置模式  
  •     movw    $VIDEO_MODE_0x103,  %bx  
  •     addw    $0x4000,            %bx         # BX第14个比特表示是否使用大的线性缓存区  
  •     movb    $0x4f,              %ah         # 表示使用VBE标准  
  •     movb    $0x02,              %al         # 功能号,表示设置模式  
  •     int     $0x10  
  • save_video_mode_info:                       # 记录切换到的模式的一些参数信息  
  •     movw    $VIDEO_MODE_0x103,  video_mode  
  •     movw    0x12(%di),          %ax  
  •     movw    %ax,                screen_x  
  •     movw    0x14(%di),          %ax  
  •     movw    %ax,                screen_y  
  •     movl    0x28(%di),          %eax  
  •     movl    %eax,               video_ram  
  •     movw    $1,                 %ax  
  •   
  •     ret  
  • set_mode_vga_0x13:                          # 若不支持VBE则设置为VGA 0x13 mode  
  •     movb    $0,                 %ah         # 功能号0x0  
  •     movb    $VIDEO_MODE_0x13,   %al         # 显示模式  
  •     int     $0x10  
  •     movw    $0x13,              video_mode  
  •     movw    $320,               screen_x  
  •     movw    $200,               screen_y  
  •     movl    $0xb8000,           video_ram  
  •       
  •     ret  
  •   
  • #----------------------------------------------------------------  
  • # 读取软盘一个扇区:  
  • #   使用BIOS INT 0x13中断读软盘,使用前需要设置ES:BX作为缓冲区  
  • #   AX为相对扇区号,基于相对扇区号,为学习软盘的知识,使用了由  
  • #   相对扇区号来读软盘的方式,也可以直接设置读取扇区数而读连续的  
  • #   多个扇区。但好像有不能跨越磁道、不能超过64KB等限制,要小心。  
  • #   柱面号、磁头号、扇区号计算公式如下:  
  • #   柱面号CH = N / 36,令x = N % 36  
  • #   磁头号DH = x / 18,扇区号CL = x % 18 + 1(因为从1开始,故加1)  
  • #-----------------------------------------------------------------  
  • read_a_sect:  
  •     movb    $36,    %dl  
  •     divb    %dl  
  •     movb    %al,    %ch     # 柱面号=N / 36, 假设x = N % 36  
  •     movb    %ah,    %al     # AL = N % 36  
  •     xorb    %ah,    %ah     # AH = 0, 则AX = AL = N % 36  
  •     movb    $18,    %dl  
  •     divb    %dl  
  •     movb    %al,    %dh     # 磁头号DH = x / 18  
  •     movb    %ah,    %cl     # CL = x % 18  
  •     incb    %cl             # 扇区号CL = x % 18 + 1  
  •   
  •     movb    $0x00,  %dl     # 驱动器号DL = 0,表示第一个软盘即floppya  
  •     movb    $0x02,  %ah     # 功能号0x02表示读软盘  
  •     movb    $0x01,  %al     # 读取一个扇区数  
  •   
  • re_read:                    # 若调用失败(可能是软盘忙损坏等)则重新调用  
  •     int     $0x13  
  •     jc      re_read         # 若进位位(CF)被置位,表示调用失败  
  •       
  •     ret  
  •   
  • #-------------------------------------------------------------------  
  • # 读取内核到内存  
  • #   该函数读取baby OS 的内核到内存,第一个扇区为引导扇区,需要读取  
  • #   的是从第二个扇区(相对扇区号1)开始的KERNEL_SECT_NUM个扇区  
  • #   ES:BX为缓冲区,为读取内核的临时位置0x10000  
  • #-------------------------------------------------------------------  
  • read_kernel:  
  •     movw    $0x1000,            %ax       
  •     movw    %ax,                %es     # ES:BX 为缓冲区地址  
  •     xorw    %bx,                %bx  
  •     movw    $0x00,              %si     # 已经读取的扇区数  
  •     movw    $0x01,              %di     # 相对扇区号  
  • 1:    
  •     movw    %di,                %ax     # 将相对扇区号传给AX作为参数  
  •     call    read_a_sect  
  •   
  •     incw    %si  
  •     incw    %di  
  •     addw    $512,               %bx  
  •     cmpw    $KERNEL_SECT_NUM,   %si  
  •     jne     1b  
  •   
  •     ret  
  •   
  • #--------------------------------------------------------------------  
  • # 移动内核第一个扇区:  
  • #   内核从软盘读取到内存的一个临时位置,现在将第一个扇区移动到内存  
  • #   0x0000处,第一个扇区即load.s,它将会把内核剩余部分移动到它的后面,  
  • #   之所以分两次移动,是因为若内核较大,一次移动会覆盖0x7c00处的代码,  
  • #   即引导扇区的代码,导致运行出错。  
  • #--------------------------------------------------------------------  
  • move_first_sect_of_kernel:  
  •     cli                                 # 指明SI,DI递增  
  •     movw    $0x1000,            %ax  
  •     movw    %ax,                %ds     # DS:SI 为源地址  
  •     xorw    %si,                %si  
  •     movw    $0x00,              %ax  
  •     movw    %ax,                %es     # ES:DI 为目标地址  
  •     xorw    %di,                %di  
  •     movw    $512 >> 2,            %cx     # 移动512/4 次  
  •     rep     movsl                       # 每次移动4个byte  
  •   
  •     ret  
  •   
  •   
  • #--------------------------------------------------------------------  
  • # 移动GDT及新显示模式的参数信息到指定位置  
  • #   该函数把GDT及参数信息移动到指定的位置,以便于以后使用  
  • #--------------------------------------------------------------------  
  • move_gdt_and_video_info:  
  •     xorw    %ax,                        %ax  
  •     movw    %ax,                        %ds     # DS:SI 为源地址  
  •     leaw    gdt,                        %si  
  •     movw    $GDT_ADDR >> 4,               %ax     # 由要保存的地址来计算段基址  
  •     movw    %ax,                        %es     # ES:DI 为目的地址  
  •     xorw    %di,                        %di  
  •     movw    $GDT_SIZE+VIDEO_INFO_SIZE,  %cx     # 移动的双字个数  
  •     rep     movsb  
  •   
  •     ret  
  •   
  • #--------------------------------------------------------------------  
  • # 开启保护模式:     
  • #   1.关中断  
  • #   2.加载GDT  
  • #   3.开启A20总线,置cr0的PE位,切换到保护模式  
  • #   4.far jmp/call,用一个CS Selector 更新CS 寄存器,开始执行新代码  
  • #--------------------------------------------------------------------  
  • enter_protected_mode:  
  •     cli                                 # 关中断  
  •     lgdt    gdt_ptr                     # 加载GDT  
  •   
  • enable_a20:   
  •     inb     $0x64,          %al         # 从端口0x64读取数据  
  •     testb   $0x02,          %al         # 测试读取数据第二个bit  
  •     jnz     enable_a20                  # 忙等待  
  •   
  •     movb    $0xdf,          %al  
  •     outb    %al,            $0x64       # 将0xdf写入端口0x60  
  •   
  •     movl    %cr0,           %eax        # 读取cr0寄存器  
  •     orl     $0x01,          %eax        # 置位最后以为即PE位  
  •     movl    %eax,           %cr0        # 写cr0寄存器  
  •   
  •     ljmp    $CODE_SELECTOR, $0x00       # 跳转到代码段,即load.s处开始执行  
  •       
  •     ret   
  •   
  • #--------------------------------------------------------------------  
  • # 开始执行后,会跳转到此处开始执行  
  • #--------------------------------------------------------------------     
  • main:  
  •     movw    %cx,        %ax  
  •     movw    %ax,        %ds  
  •     movw    %ax,        %es  
  •     movw    %ax,        %ss  
  •     movw    $0x1000,    %sp  
  •   
  •     call    clear_screen                # 清屏  
  •     call    set_video_mode              # 设置显示模式  
  •     call    read_kernel                 # 从软盘读取内核  
  •     call    move_first_sect_of_kernel   # 将内核第一个扇区load.s移动到0x0000  
  •     call    move_gdt_and_video_info     # 将GDT和显示模式信息保存起来  
  •     call    enter_protected_mode        # 进入包含模式  
  •   
  • 1:  
  •     jmp     1b  
  •   
  • gdt:  
  •     .quad   0x0000000000000000          # 空描述符  
  •     .quad   0x00cf9a000000ffff          # 代码段描述符  
  •     .quad   0x00cf92000000ffff          # 数据段描述符  
  •     .quad   000000000000000000          # 留待以后使用  
  •     .quad   000000000000000000          # 留待以后使用  
  • video_mode:                             # 显示模式  
  •     .short  0  
  • screen_x:                               # 水平分辨率  
  •     .short  0   
  • screen_y:                               # 垂直分辨率  
  •     .short  0     
  • video_ram:                              # video_ram地址  
  •     .long   0  
  • gdt_ptr:                                # 用与lgdt 加载GDT  
  •     .word   screen_x - gdt - 1          # GDT段限长  
  •     .long   GDT_ADDR                    # GDT基地址  
  •   
  •     .org    0x1fe,  0x90                # 用nop 指令填充  
  •     .word   0xaa55                      # 引导扇区标志  

  • load.s: [cpp] view plaincopyprint?
  • #*************************************************************************  
  • #   > File:      load.s  
  • #   > Desc:      1.设置新的数据段等  
  • #               2.将内核剩余部分移动到load.s后面  
  • #               3.显示babyos 加载成功的Logo  
  • #   > Author:    孤舟钓客  
  • #   > Mail:      guzhoudiaoke@126.com   
  • #   > Time:      2012年12月30日 星期日 22时23分55秒  
  • #*************************************************************************  
  •   
  • .include "include/kernel.inc"  
  •   
  • .section .text  
  • .global _start  
  •   
  • .org    0  
  •   
  • _start:  
  •     movl    $DATA_SELECTOR,         %eax  
  •     movw    %ax,                    %ds  
  •     movw    %ax,                    %es  
  •     movw    %ax,                    %fs  
  •     movw    %ax,                    %gs  
  •     movw    %ax,                    %ss  
  •     movl    $STACK_BOTTOM,          %esp  
  •   
  • load_lefted_kernel:  
  •     cld  
  •     movl    $0x10200,               %esi  
  •     movl    $0x200,                 %edi  
  •     movl    $(KERNEL_SECT_NUM-1)<<7,%ecx  
  •     rep     movsl  
  •   
  • show_logo:  
  •     movl    $0xe0000000,            %edi  
  •     addl    $272 + 800*10,          %edi  
  •     movl    $0x400,                 %esi  
  •   
  •     movl    $128,                   %ebx  
  •     movl    $1,                     %eax  
  • 1:    
  •     movl    $256,                   %ecx  
  • set_line_mem:     
  •     cmpb    $255,                   (%esi)  
  •     je      2f  
  •     movb    %al,                    (%edi)  
  • 2:  
  •     inc     %esi  
  •     inc     %edi  
  •     loop    set_line_mem  
  •       
  •     addl    $800-256,               %edi  
  •     decl    %ebx  
  •     jnz     1b  
  •   
  • 3:    
  •     jmp     3b  
  •   
  •     .org    512,    0x90      

  • baby os 暂时使用下面的简单logo:


    o(∩∩)o...哈哈,这个logo 使用小篆字体,还是很有中国特色的呦~


    总结

    以上是生活随笔为你收集整理的跳转到保护模式并显示一个LOGO的全部内容,希望文章能够帮你解决所遇到的问题。

    如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。