本文参考书籍

操作系统真相还原
Linux内核完全剖析:基于0.12内核
x86汇编语言  从实模式到保护模式
ps:基于x86硬件的pc系统

实模式相关介绍

实模式在上文已经做了简要的介绍,实模式的寄存器都是16位,实模式的1MB的寻址能力是通过段基址左移四位加上段内偏移实现的,由于BIOS启动的过程中就会被cpu执行,所以当bios加载完成时,1MB的内存布局如下图所示(图片来源操作系统真相还原);

通过如图可以看到,当bios加载完成后启动扇区代码后,启动代码可以通过在对应的内存位置上传入数据,就可以在屏幕上显示相应数据,此时的bios也有相应的中断向量表,可以调用,例如读写硬盘、向显示屏显示数据等都是通过该中断向量表实现。

此时可以根据相关bios的属性,构成如下主引导程序代码;

;主引导程序
;------------------------------------------------------------
SECTION MBR vstart=0x7c00         mov ax,cs      mov ds,axmov es,axmov ss,axmov fs,axmov sp,0x7c00; 清屏利用0x06号功能,上卷全部行,则可清屏
; -----------------------------------------------------------
;INT 0x10   功能号:0x06       功能号:上卷窗口
;------------------------------------------------------
;输入
;AH 功能号= 0x06
;AL = 上卷的行数(如果为0,表示全部)
;BH = 上卷行属性
;(CL,CH) = 窗口左上角的(X,Y)位置
;(DL,DH) = 窗口右下角(X,Y)位置
;无返回值mov     ax, 0x600mov     bx, 0x700mov     cx, 0           ; 左上角: (0, 0)mov     dx, 0x184f      ; 右下角: (80,25),; VGA文本模式下,一行只能容纳80个字符,共25行; 下标从0开始,所以x18=24,0x4f=79int     0x10            ; int 0x10;;;;;;;;;    下面这三行代码获取光标的位置    ;;;;;;;;;
;.get_cursor 获取当前光标位置,在光标位置处打印字符mov ah, 3        ; 输入:3号子功能获取光标位置,需要存入ah寄存器mov bh, 0        ; bh寄存器存储的是待获取光标的页号int 0x10     ; 输出: ch=光标开始行,cl=光标结束行; dh=光标所在行号,dl=光标所在列号;;;;;;;;;    获取光标位置结束   ;;;;;;;;;;;;;;;;;;;;;;;;;     打印字符串   ;;;;;;;;;;;;mov ax, message mov bp, ax       ; es:bp 为串首地址,es此时同cs一致; 开头时已经为sreg初始化; 光标位置要用到dx寄存器中内容,cs中的光标位置可忽略mov cx, 5        ; cx 为串长度,不包括结束符0的字符个数mov ax, 0x1301   ; 子功能号13显示字符及属性,要存入ah寄存器; al设置写字符方式ah=01;显示字符串,光标跟随移动mov bx, 0x2      ; bh村粗要显示的页号,此处是第0页,; bl中是字符属性,属性黑底绿字bl = 02h)int 0x10     ; 执行BIOS 0x10 中断
;;;;;;;;;      ´打印字符串结束 ;;;;;;;;;;;;;;;jmp $       ; 进入死循环,使程序悬停在此message db "1 MBR"times 510-($-$$) db 0db 0x55,0xaa

该段代码主要就是在bios下调用了向屏幕打印输出的功能,可查bios手册对有关内容进一步了解,最后由于mbr的固定结束时0x55aa,并且要将剩余的扇区内容填满,由此便是该段代码所做的工作。

此处的程序只是简单的在启动之后就死循环,由于该扇区的大小只有512个字节,操作系统内核一般会超过该大小,并且操作系统一般存放在软盘或者硬盘上,此时还需要将操作系统加载到内存中,此时就需要使用Loader加操作系统加载到内存中。

读取硬盘数据并加载代码如下;

;-------------------------------------------------------------------------------
;功能:读取硬盘n个扇区
rd_disk_m_16:
;-------------------------------------------------------------------------------; eax=LBA扇区号; ebx=将数据写入的内存地址; ecx=读入的扇区数mov esi,eax     ;备份eaxmov di,cx       ;备份cx
;读写硬盘:
;第1步:设置要读取的扇区数mov dx,0x1f2mov al,clout dx,al            ;读取的扇区数mov eax,esi      ;恢复ax;第2步:将LBA地址存入0x1f3 ~ 0x1f6;LBA地址7~0位写入端口0x1f3mov dx,0x1f3                       out dx,al                          ;LBA地址15~8位写入端口0x1f4mov cl,8shr eax,clmov dx,0x1f4out dx,al;LBA地址23~16位写入端口0x1f5shr eax,clmov dx,0x1f5out dx,alshr eax,cland al,0x0f      ;lba第24~27位or al,0xe0       ; 设置7到4位为1110,表示lba模式mov dx,0x1f6out dx,al;第3步:向0x1f7端口写入读命令,¬0x20 mov dx,0x1f7mov al,0x20                        out dx,al;第4步:检测硬盘状态.not_ready:;同一端口,写时表示写入命令字,读时表示读入硬盘状态nopin al,dxand al,0x88      ;第4位为1表示硬盘控制器已准备好数据传输cmp al,0x08jnz .not_ready       ;若未准备好,继续等;第5步,从0x1f0端口读数据mov ax, dimov dx, 256mul dxmov cx, ax       ; di为要读取的扇区数,一个扇区有512个字节,每次读入一个字; 共需di*512/2次,所以di*256mov dx, 0x1f0.go_on_read:in ax,dxmov [bx],axadd bx,2        loop .go_on_readret

当从硬盘读取数据到内存,只需要跳转到当前加载好的内存地址处去执行相应代码就可以了,如下;

section loader vstart=LOADER_BASE_ADDR; 输出背景色绿色,前景色红色,并且跳动的字符串"1 MBR"
mov byte [gs:0x00],'2'
mov byte [gs:0x01],0xA4     ; A表示绿色背景闪烁,4表示前景色为红色mov byte [gs:0x02],' '
mov byte [gs:0x03],0xA4mov byte [gs:0x04],'L'
mov byte [gs:0x05],0xA4   mov byte [gs:0x06],'O'
mov byte [gs:0x07],0xA4mov byte [gs:0x08],'A'
mov byte [gs:0x09],0xA4mov byte [gs:0x0a],'D'
mov byte [gs:0x0b],0xA4mov byte [gs:0x0c],'E'
mov byte [gs:0x0d],0xA4mov byte [gs:0x0e],'R'
mov byte [gs:0x0f],0xA4jmp $             ; 通过死循环使程序悬停在此

此时就会在屏幕上显示完成后就进入了死循环。接下来就是进入保护模式,然后执行相应的内核代码。

Linux内核相关实模式操作流程

Linux引导启动程序的介绍
当pc的电源打开后,80x86结构的cpu将自动进入实模式,并从地址0xFFFF0开始自动给执行代码,这个地址通常是bios中的地址。pc的bios将执行某些系统检测,并在物理地址0处开始初始化中断向量。此后,它将启动设备的第一个扇区(磁盘引导扇区,512B)读入内存绝对地址0x7c00处,并跳转到这个地方。启动设备通常是软驱或硬盘。

Linux最开始执行的就是用汇编语言编写的boot/bootsect.S汇编文件,它将由bios读入到内存绝对地址0x7c00处,当它被执行时就会把自己移动到内存绝对地址0x90000(576KB)处,并把启动设备盘中后2KB代码(boot/setup.S)读入到内存0x90200处,而内核的其他部分则被读入到从内存地址0x10000(64KB)开始处,由于当时内核模块的长度不会超过0x80000字节大小(即512KB),所以bootsect程序吧内核模块读入物理地址0x10000开始位置处时并不会覆盖从0x90000(576KB)处开始的bootsect和setup模块,后面setup程序会把内核模块移动到物理内存起始位置处,这样内核模块中代码的地址即等于实际的物理地址,便于对内核代码和数据进行操作,可由如图表示;

启动部分识别主句的某些特性以及VGA卡的类型,然后将整个系统从地址0x10000移动到0x0000处,进入保护模式并跳转到系统的余下部分,此时所有32位运行方式的设置启动被完成:IDT、GDT以及LDT被加载,分页工作也设置好了,最终调用init/main.c中的main程序。

其中bootsect.S中的部分代码如下(参考Linux内核完全剖析);

!
! SYS_SIZE is the number of clicks (16 bytes) to be loaded.
! 0x3000 is 0x30000 bytes = 196kB, more than enough for current
! versions of linux
!
#include <linux/config.h>
SYSSIZE = DEF_SYSSIZE
!
!   bootsect.s      (C) 1991 Linus Torvalds
!   modified by Drew Eckhardt
!
! bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves
! iself out of the way to address 0x90000, and jumps there.
!
! It then loads 'setup' directly after itself (0x90200), and the system
! at 0x10000, using BIOS interrupts.
!
! NOTE! currently system is at most 8*65536 bytes long. This should be no
! problem, even in the future. I want to keep it simple. This 512 kB
! kernel size should be enough, especially as this doesn't contain the
! buffer cache as in minix
!
! The loader has been made as simple as possible, and continuos
! read errors will result in a unbreakable loop. Reboot by hand. It
! loads pretty fast by getting whole sectors at a time whenever possible..globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.textSETUPLEN = 4                ! nr of setup-sectors
BOOTSEG  = 0x07c0           ! original address of boot-sector
INITSEG  = DEF_INITSEG          ! we move boot here - out of the way
SETUPSEG = DEF_SETUPSEG         ! setup starts here
SYSSEG   = DEF_SYSSEG           ! system loaded at 0x10000 (65536).
ENDSEG   = SYSSEG + SYSSIZE     ! where to stop loading! ROOT_DEV & SWAP_DEV are now written by "build".
ROOT_DEV = 0                ! 根文件系统设备使用与系统引导时同样的设备
SWAP_DEV = 0                ! 交换设备使用与系统引导时同样的设备entry start                 ! 告知链接程序,程序从start标号开始执行
start:mov ax,#BOOTSEG         ! 设置ds段寄存器为0x7c0mov ds,axmov ax,#INITSEG         ! 设置es段寄存器为0x9000mov es,axmov cx,#256             ! 设置移动计数值为256字sub si,si               ! 源地址 ds:si = 0x7c00:0x0000sub di,di               !  目的地址es:di=0x9000:0x0000rep                     ! 重复执行并递减cx的值,直到cx=0为止movw                    ! 从内存[si]处移动cx个字到[di]处jmpi    go,INITSEG      ! 段间跳转,跳转到INITSEG段,偏移为gogo: mov ax,cs           ! 将ds、es和ss都置成移动后所在的段0x9000mov dx,#0xfef4  ! arbitrary value >>512 - disk parm sizemov ds,axmov es,axpush    ax      ! 临时保存段值(0x9000)mov ss,ax       ! put stack at 0x9ff00 - 12.mov sp,dx
/**  Many BIOS's default disk parameter tables will not *  recognize multi-sector reads beyond the maximum sector number*  specified in the default diskette parameter tables - this may*  mean 7 sectors in some cases.**  Since single sector reads are slow and out of the question,*  we must take care of this by creating new parameter tables*  (for the first disk) in RAM.  We will set the maximum sector*  count to 18 - the most we will encounter on an HD 1.44.  **  High doesn't hurt.  Low does.**  Segments are as follows: ds=es=ss=cs - INITSEG,*      fs = 0, gs = parameter table segment*/push    #0          ! 置段寄存器fs=0pop fsmov bx,#0x78        ! fs:bx is parameter table addressseg fslgs si,(bx)         ! gs:si is sourcemov di,dx           ! es:di is destinationmov cx,#6           ! copy 12 bytescld                 ! 清方向标致,复制时指针递增rep                 !复制12字节的软驱参数表到0x9000:0xfef4处seg gs movwmov di,dx               ! 将es:di指向新表,然后修改表中偏移4处的最大扇区数movb    4(di),*18       ! patch sector countseg fs                  ! 让中断向量0x1E的值指向新表mov (bx),di   seg fsmov 2(bx),espop ax              !as为0x9000mov fs,ax           !设置fs=gs=0x9000mov gs,axxor ah,ah           ! reset FDC 复位软盘控制器,让其采用新参数xor dl,dl           !dl=0,第一个软驱int     0x13        ! 系统调用读磁盘! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.load_setup:xor dx, dx          ! drive 0, head 0mov cx,#0x0002      ! sector 2, track 0mov bx,#0x0200      ! address = 512, in INITSEGmov ax,#0x0200+SETUPLEN ! service 2, nr of sectorsint 0x13            ! read it  设置完磁盘参数后系统调用读磁盘jnc ok_load_setup       ! ok - continue  如果读取成功就跳到ok_load_setup执行push    ax          ! dump error code 显示错误信息,出错码压入call    print_nl    !屏幕光标回车mov bp, sp          ! ss:bp指向欲显示的字call    print_hex   !显示16进制值pop ax  xor dl, dl          ! reset FDC 复位磁盘控制器 重新读xor ah, ahint 0x13j   load_setup      !重新执行该段读磁盘程序ok_load_setup:! Get disk drive parameters, specifically nr of sectors/trackxor dl,dlmov ah,#0x08        ! AH=8 is get drive parametersint 0x13            !系统调用获取磁盘驱动器参数xor ch,chseg csmov sectors,cx      mov ax,#INITSEGmov es,ax           !重置es值! Print some inane messagemov ah,#0x03        ! read cursor pos  获取光标位置xor bh,bhint 0x10            !dh代表行,dl代表列mov cx,#9           !显示9个字符mov bx,#0x0007      ! page 0, attribute 7 (normal)mov bp,#msg1        !将bp指向要显示的字符mov ax,#0x1301      ! write string, move cursorint 0x10            !写字符串并移动光标到串结束处! ok, we've written the message, now
! we want to load the system (at 0x10000)mov ax,#SYSSEG      !开始在0x10000处加载system模块mov es,ax       ! segment of 0x010000call    read_it     !读磁盘上system模块,es为输入参数call    kill_motor  !关闭驱动器马达call    print_nl    !光标回车换行! After that we check which root-device to use. If the device is
! defined (!= 0), nothing is done and the given device is used.
! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending
! on the number of sectors that the BIOS reports currently.seg csmov ax,root_dev     !取508、509字节处的根设备号并判断是否已被定义or  ax,axjne root_defined     !获取每磁道扇区数,如果是15则说明是1.2,如果是18则是1.4MBseg csmov bx,sectorsmov ax,#0x0208      ! /dev/ps0 - 1.2Mbcmp bx,#15je  root_definedmov ax,#0x021c      ! /dev/PS0 - 1.44Mbcmp bx,#18je  root_defined
undef_root:             !如果都不是则循环死机jmp undef_root
root_defined:seg csmov root_dev,ax     !将检查过的设备号保存到root_dev中! after that (everyting loaded), we jump to
! the setup-routine loaded directly after
! the bootblock:jmpi    0,SETUPSEG   !至此,所有程序加载完毕,我们就跳转到0x9020:000处去执行

当bootsect.S将setup.S的代码读取完成后跳转到setup.S处代码执行。setup.S代码的主要功能是利用bios中断读取机器系统数据,并将这些数据保存到0x90000开始的位置(覆盖了bootsect程序所在的位置),所取得的参数和保留的参数,会在内核中相关程序中使用,例如字符设备驱动程序等;然后setup程序将system模块从0x10000~0x8ffff(当时认为内核系统模块的长度不会大于512KB)整块下移动到内存绝对地址0x00000处,接下来加载中断描述符表寄存器idtr和全局描述符表寄存器gdtr,开启A20地址线,重新设置两个中断控制芯片8259A,将硬件中断号重新设置为0x20~0x2f。最后设置cpu的控制寄存器cr0,从而进入32位保护模式运行,并跳转到位于system模块最前面部分的head.s程序继续执行。由此cpu进入到保护模式运行,保护模式相关内容将在下篇文章中进行介绍。

操作系统学习:启动进入实模式相关推荐

  1. 操作系统:浅谈实模式,保护模式与长模式

    学习了操作系统的实模式.保护模式与长模式,此文作为回顾. x86 CPU 在第一次加电和每次 reset 后,都会自动进入实模式,要想进入保护模式,就需要程序员写代码实现从实模式切换到保护模式. 一. ...

  2. x86从实模式到保护模式 pdf_【自制操作系统04】从实模式到保护模式

    通过前三章的努力,我们成功将控制权转交给了 loader.asm 这个程序.具体说就是 bios 通过加载并跳转到 0x7c00(IMB大叔们定的) 把控制权转交给了我们操作系统的第一个汇编程序 mb ...

  3. (操作系统开发)从实模式---->保护模式---->IA-32e模式( 64位模式)

    实模式和保护模式都是CPU的工作模式. 实模式与保护模式介绍 在实模式下,程序可以操作任何地址空间,而且无法限制程序的执行权限.尽管这种模式给设置硬件功能带来许多方便,但却给程序执行的安全性和稳定性带 ...

  4. 一步步编写操作系统 10 cpu的实模式

    cpu的实模式 由于mbr在实模式下工作--什么?什么是实模式?这时候有同学打断了我.我心想,这下好办了--哈哈,没有啦,开个玩笑而已.我们这里所说的实模式其实就是8086 cpu的工作环境.工作方式 ...

  5. Linux文件解hgc,Linux从实模式到保护模式.pdf

    Linux从实模式到保护模式 Linux 内核源码学习 (1)- 从实模式到保护模式 notishell 发布于 3 年前 在查找资料的过程发现了一份关于 linux 内核启动的课件,在这里附上.(本 ...

  6. 操作系统学习:实模式进入保护模式

    本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 ps:基于x86硬件的pc系统 保护模式相关介绍 从实模式进入保护模式其实经历 ...

  7. 我是如何学习写一个操作系统(三):操作系统的启动之保护模式

    前言 上一篇其实已经说完了boot的大致工作,但是Linux在最后进入操作系统之前还有一些操作,比如进入保护模式.在我自己的FragileOS里进入保护模式是在引导程序结束后完成的. 实模式到保护模式 ...

  8. 操作系统引导--从实模式到保护模式

    从开始到保护--系统开机引导 ------没有一个文档能写的通俗易懂,我希望写出来. 开机引导和实模式: 两个星期加上假期吐血整理,所述为计算机的开机引导,其中包括一系列计算机内存设置等等,由于没有老 ...

  9. 【OS学习笔记】六 实模式:编写主引导扇区代码

    上一篇文章学习了:计算机的启动过程(点击链接查看上一篇文章) 这篇文章学习记录为:编写主引导扇区代码. 参考:<X86汇编语言-从实模式到保护模式>-李忠.纯学习笔记,更详细内容请阅读正版 ...

最新文章

  1. 告别视频通话“渣画质”,英伟达新算法最高压缩90%流量
  2. 为Android运行新的英特尔模拟器
  3. SQL查询月初与月末时间
  4. 【Linux开发】彻底释放Linux线程的资源
  5. 使用JMX透过防火墙远程监控tomcat服务
  6. Sql Server2008——远程过程调用失败
  7. Android数据的四种存储方式SharedPreferences、SQLite、Content Provider和File (二) —— SQLite...
  8. 2-6 基于SpringBoot的SpringSecurity环境快速搭建与验证
  9. #ifdef __cplusplus extern “C”的作用详解
  10. 1090 Highest Price in Supply Chain (25)(25 分)
  11. 【动态规划】0/1背包问题
  12. jsp文件里java代码的作用_如何使用JSP 2避免JSP文件中的Java代码?
  13. Graph Structure of Neural Networks何凯明团队
  14. 电脑下载的准考证去哪里了
  15. smartq ten3 android4.2 v1.1,全线升级Android 4.2 智器平板新体验
  16. Mac 怎样安装虚拟机(VMware fusion 12)
  17. Cobar的安装和配置步骤
  18. 【快应用】十大手机厂商共推快应用标准
  19. 渗透测试信息收集笔记(信息搜集、后台查找)
  20. 陶哲轩实分析 5.2 节习题试解

热门文章

  1. 使用 Python 和 OpenCV 构建 SET 求解器
  2. 终于有人把 Python 讲清楚了!
  3. Pytorch和Tensorflow,谁会笑到最后?
  4. 追溯XLNet的前世今生:从Transformer到XLNet
  5. ICLR 2019最佳论文揭晓!NLP深度学习、神经网络压缩夺魁 | 技术头条
  6. 80+机器学习数据集,还不快收藏
  7. 马斯克连发三推,发布退出OpenAI内情
  8. 9月推荐 | 精选机器学习文章Top10
  9. 精选机器学习开源项目Top10
  10. Google把AI芯片装进IoT设备,与国内造芯势力何干?