操作系统学习:启动进入实模式
本文参考书籍
操作系统真相还原
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进入到保护模式运行,保护模式相关内容将在下篇文章中进行介绍。
操作系统学习:启动进入实模式相关推荐
- 操作系统:浅谈实模式,保护模式与长模式
学习了操作系统的实模式.保护模式与长模式,此文作为回顾. x86 CPU 在第一次加电和每次 reset 后,都会自动进入实模式,要想进入保护模式,就需要程序员写代码实现从实模式切换到保护模式. 一. ...
- x86从实模式到保护模式 pdf_【自制操作系统04】从实模式到保护模式
通过前三章的努力,我们成功将控制权转交给了 loader.asm 这个程序.具体说就是 bios 通过加载并跳转到 0x7c00(IMB大叔们定的) 把控制权转交给了我们操作系统的第一个汇编程序 mb ...
- (操作系统开发)从实模式---->保护模式---->IA-32e模式( 64位模式)
实模式和保护模式都是CPU的工作模式. 实模式与保护模式介绍 在实模式下,程序可以操作任何地址空间,而且无法限制程序的执行权限.尽管这种模式给设置硬件功能带来许多方便,但却给程序执行的安全性和稳定性带 ...
- 一步步编写操作系统 10 cpu的实模式
cpu的实模式 由于mbr在实模式下工作--什么?什么是实模式?这时候有同学打断了我.我心想,这下好办了--哈哈,没有啦,开个玩笑而已.我们这里所说的实模式其实就是8086 cpu的工作环境.工作方式 ...
- Linux文件解hgc,Linux从实模式到保护模式.pdf
Linux从实模式到保护模式 Linux 内核源码学习 (1)- 从实模式到保护模式 notishell 发布于 3 年前 在查找资料的过程发现了一份关于 linux 内核启动的课件,在这里附上.(本 ...
- 操作系统学习:实模式进入保护模式
本文参考书籍 1.操作系统真相还原 2.Linux内核完全剖析:基于0.12内核 3.x86汇编语言 从实模式到保护模式 ps:基于x86硬件的pc系统 保护模式相关介绍 从实模式进入保护模式其实经历 ...
- 我是如何学习写一个操作系统(三):操作系统的启动之保护模式
前言 上一篇其实已经说完了boot的大致工作,但是Linux在最后进入操作系统之前还有一些操作,比如进入保护模式.在我自己的FragileOS里进入保护模式是在引导程序结束后完成的. 实模式到保护模式 ...
- 操作系统引导--从实模式到保护模式
从开始到保护--系统开机引导 ------没有一个文档能写的通俗易懂,我希望写出来. 开机引导和实模式: 两个星期加上假期吐血整理,所述为计算机的开机引导,其中包括一系列计算机内存设置等等,由于没有老 ...
- 【OS学习笔记】六 实模式:编写主引导扇区代码
上一篇文章学习了:计算机的启动过程(点击链接查看上一篇文章) 这篇文章学习记录为:编写主引导扇区代码. 参考:<X86汇编语言-从实模式到保护模式>-李忠.纯学习笔记,更详细内容请阅读正版 ...
最新文章
- 告别视频通话“渣画质”,英伟达新算法最高压缩90%流量
- 为Android运行新的英特尔模拟器
- SQL查询月初与月末时间
- 【Linux开发】彻底释放Linux线程的资源
- 使用JMX透过防火墙远程监控tomcat服务
- Sql Server2008——远程过程调用失败
- Android数据的四种存储方式SharedPreferences、SQLite、Content Provider和File (二) —— SQLite...
- 2-6 基于SpringBoot的SpringSecurity环境快速搭建与验证
- #ifdef __cplusplus extern “C”的作用详解
- 1090 Highest Price in Supply Chain (25)(25 分)
- 【动态规划】0/1背包问题
- jsp文件里java代码的作用_如何使用JSP 2避免JSP文件中的Java代码?
- Graph Structure of Neural Networks何凯明团队
- 电脑下载的准考证去哪里了
- smartq ten3 android4.2 v1.1,全线升级Android 4.2 智器平板新体验
- Mac 怎样安装虚拟机(VMware fusion 12)
- Cobar的安装和配置步骤
- 【快应用】十大手机厂商共推快应用标准
- 渗透测试信息收集笔记(信息搜集、后台查找)
- 陶哲轩实分析 5.2 节习题试解