ch1_系统启动_setup.S
1 功能分析
大写的.s
后缀名, 是为了说明是一个16位, 实模式下的汇编语言, 小写的 s 是保护模式下的汇编语言;
1.1 使用中断,读取机器参数
setup.S
是一个操作系统的加载程序, 主要作用使用 ROM BIOS 中断读取机器系统数据, 并将这些数据保存到0X90000
开始的位置, 即覆盖掉原先bootsect 程序所在地方,
所取得的参数和保留的内存位置见表 6–2 所示。
这些参数将被内核中相关程序使用,例如字符设备驱动程序集中的 console.c 和 tty_io.c程序等。
利用BIOS中断程序填下面这张表格的内容:
如表中,
1)保存光标的位置
2)得到扩展内存的大小
3)得到显示卡当前的显示模式
4)检测显示方式
5)读取硬盘参数表信息
1.2 setup
移动 system
模块
然后, setup 程序将 system 模块从 0x10000-0x8ffff 整块向下移动到内存绝对地址 0x00000 处(当时认为内核系统模块 system 的长度不会超过此值:512KB)。
因为我们已经使用完所有的BIOS中断程序,所以由BIOS在0地址处建立的中断向量表也可以覆盖掉。
; 之前bootsect引导程序将system模块移动到(0x10000)处,
; 并把自己移动到(0x90000)处,把setup加载在它后面; setup 程序将整个system模块移动到0x00000处,
; 即把从0x10000到0x8ffff的内存数据块整块的向内存地址低端移动了0x10000的位置mov ax,#0x0000cld ! 'direction'=0, movs moves forward
do_move:mov es,ax ! destination segmentadd ax,#0x1000cmp ax,#0x9000 ! 判断代码是否移动完成jz end_move ! 移动完成则跳转mov ds,ax ! source segmentsub di,disub si,simov cx,#0x8000 ! 循环移动,循环次数,每次循环完次数减 移动0x8000字rep ! 用于把内容从ds:si 复制es:di 以字节单位movsw ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中jmp do_move
1.3 加载描述符表
原始的实模式下, 16位时, 寻址方式是: 段寄存器左移四位 + 偏移寄存器。
此时,加载的描述符表包含两种:
- . 中断描述符表 (IDT) interupt descriptor
- . 全局描述符表 GDT (global descriptor table )
全局描述符表的出现, 是为了解决32位 保护模式下的寻址方式问题,
GDT 表: 是使用段寄存器CS
作为一个索引在一个地址表里找到32位的基地址,
然后再和偏移寄存器EIP 中的32位 数值相加,
得到最终的地址放到地址总线上去选定内存。
而为了让硬件找到这个表, GDT 表的起始地址被放在了一个GDTR 的寄存器中;
为进入保护模式做准备,
- 加载中断描述符表寄存器(IDTR)和全局描述符表寄存器(GDTR),
- 开启 A20 地址线,
- 重新设置两个中断控制芯片 8259A,
- 将硬件中断号重新设置为 0x20 - 0x2f。
实模式和保护模式下的寻址方式的区别
a)图记住一点,段的最大长度固定为64KB;
b)图段寄存器中保存的不再是段基地址而是描述符表的索引,并且段的最大长度是可变的。
1.4 模式切换
setup.s
从实模式 切换到 保护模式,
设置CPU的机器状态字寄存器CR0的PE位,进入 32 位保护模式运行,并跳转到位于 system 模块最前面部分的 head.s 程序继续运行。
进入保护模式:jmpi 0,8
加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式
;进入保护模式,只是跳转到绝对地址0x00000处
; 加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式mov ax,#0x0001 ! protected mode (PE) bit 保护模式比特位(PE)lmsw ax ! This is it! 加载状态寄存器;段选择符8表示请求特权0级,使用GDT第二个段描述符jmpi 0,8 ! jmp offset 0 of segment 8 (cs) 跳转至cs段偏移地址位0处(system已经移动到0x00000处)
2 .设备的划分
2.1. 磁盘
软盘: 是早期的产物, 负责从计算机上搬运出数据, 现在这个基本功能使用U盘完成了。 软盘并不是装在电脑里面的,而是可移动的,一般用来存储文件和不同电脑之间进行拷贝文件,就功能上来说它和现在的U盘是一样的,只是外形、存储原理不一样,它的容量要比硬盘小的多,比如最常用的3.5英寸的软盘容量只有1.44MB。
硬盘:一般都装在机箱里面,容量较大,用来存储数据。
磁盘包括软盘和硬盘。
一个磁盘由多个盘片(如下图中的 0 号盘片)叠加而成。盘片的表面涂有磁性物质,这些磁性物质用来记录二进制数据。因为正反两面都可涂上磁性物质,故一个盘片可能会有两个盘面。
2.2 磁道
每个盘片被划分成多个圆圈, 由内向外,圆圈逐个变大, 这样每个圆圈就形成了所谓的磁道。
在对每个圈,进行分段的划分弧度, 这样一个个分段的弧, 就形成了一个个扇区。
由此,
- 不同的磁道, 容量大小不同, 因为圆圈的大小不同;
- 同一个磁道下的各个扇区相同, 因为是在同一个磁道下划分的。
2.3 柱面
所有的盘面中相对位置相同的磁道组成柱面, 类似于一个圆柱面,
每个盘面对应一个磁头。所有的磁头都是连在同一个磁臂上的,因此所有磁头只能“共进退”。
磁盘的物理地址
可用(柱面号,盘面号,扇区号)来定位任意一个“磁盘块”
可根据该地址读取一个“块”,操作如下:
① 根据“柱面号”移动磁臂,让磁头指向指定柱面;
② 激活指定盘面对应的磁头;
③ 磁盘旋转的过程中,指定的扇区会从磁头下面划过,这样就完成了对指定扇区的读/写
3. 描述符表
描述符表其实就是内存中描述符项的一个阵列。
描述符表有两类:
全局描述符表(Gobal descriptor table-GDT)和
局部描述符表(Local descri ptantable−LDT)。
处理器是通过:
使用GDTR寄存器来定位GDT表。
LDTR寄存器来定位当前的LDT表。
这两个寄存器以线性地址的方式保存了描述符表的基地址和表的长度。
完整setup.s 代码
!
! setup.s (C) 1991 Linus Torvalds
!
! setup.s is responsible for getting the system data from the BIOS,
! and putting them into the appropriate places in system memory.
! both setup.s and system has been loaded by the bootblock.
!
! This code asks the bios for memory/disk/other parameters, and
! puts them in a "safe" place: 0x90000-0x901FF, ie where the
! boot-block used to be. It is then up to the protected mode
! system to read them from there before the area is overwritten
! for buffer-blocks.
!
; setup从BIOS中获取数据,并将这些数据保存到0x90000开始的位置处(0x90000-0x901FF覆盖了原来bootsect程序所在的地方)
; 此时setup和system已经由bootsect引导块加载到内存中
;
! NOTE! These had better be the same as in bootsect.s!INITSEG = 0x9000 ! we move boot here - out of the way 原来bootsect所在段
SYSSEG = 0x1000 ! system loaded at 0x10000 (65536). system所在0x10000处
SETUPSEG = 0x9020 ! this is the current segment 本程序所在段地址.globl begtext, begdata, begbss, endtext, enddata, endbss
.text
begtext:
.data
begdata:
.bss
begbss:
.textentry start
start:! ok, the read went well so we get current cursor position and save it for
! posterity.
; 保存光标的位置
; 使用BIOS中断取屏幕当前光标的位置(列,行),保存到内存(0x90000)处,2个byte
; 控制台初始化程序会到此处读取该值
; BISO 中断0x10 功能号 ah = 0x30 ,读光标的位置
; 输入:bh=页号
; 返回:返回:ch = 扫描开始线,cl = 结束开始线,dh = 行号(0x00顶端),dl=列号(0x00最左边)mov ax,#INITSEG ! this is done in bootsect already, but...mov ds,axmov ah,#0x03 ! read cursor pos 功能号 ah = 0x30 ,读光标的位置xor bh,bhint 0x10 ! save it in known place, con_init fetchesmov [0],dx ! it from 0x90000. 将ds设置成0x90000(INITSEG)! Get memory size (extended mem, kB)
; 得到扩展内存的大小
; 利用BIOS中断0x15 功能号 ah= 0x88取系统所含扩展内存大小并保存到0x90002处
; 返回: ax= 0x10000(1M)处开始的扩展内存大小,若出错CF置位,ax=出错码mov ah,#0x88int 0x15mov [2],ax !扩展内存的大小保存到0x90002处! Get video-card data:
; 得到显示卡当前的显示模式
; 调用BIOS中断0x10,功能号 ah = 0x0f
; 返回:ah=字符列数,al=显示模式,bh=显示当前页数mov ah,#0x0fint 0x10mov [4],bx ! bh = display pagemov [6],ax ! al = video mode, ah = window width! check for EGA/VGA and some config parameters
; 检测显示方式
; 调用BIOS中断0x10, 功能号 ah=0x12,bl=0x10mov ah,#0x12mov bl,#0x10int 0x10mov [8],ax ! 0x90008 =axmov [10],bx ! 0x9000A = 安装的显示内存,0x9000B = 显示状态mov [12],cx !0X9000C = 显卡特性参数! Get hd0 data
; 取第一个硬盘信息
; 第一个硬盘参数表的首地址是中断向量0x41的向量值
; 第二个紧跟着对应着中断向量0x46
; 下面两个程序分别复制BIOS有关硬盘参数表,
; 第一个硬盘存放在0x90080,第二个硬盘存放在0x90090mov ax,#0x0000mov ds,axlds si,[4*0x41] !取中断向量0x41对应的地址 ,hd0参数表的地址--> ds:simov ax,#INITSEG mov es,axmov di,#0x0080 !传输的目的地址(0x9000:0x0080) -->es:dimov cx,#0x10 ! 循环次数,每次循环完次数减一,共传输16个字节rep ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中movsb ! 用于把内容从ds:si 复制es:di 以字节单位! Get hd1 datamov ax,#0x0000mov ds,axlds si,[4*0x46] !取中断向量0x41对应的地址 ,hd0参数表的地址--> ds:simov ax,#INITSEG mov es,axmov di,#0x0090mov cx,#0x10repmovsb! Check that there IS a hd1 :-)
; 检测是否有第二个硬盘,如果没有则把第2个清零
; 利用BIOS中断调用0x13的取盘的类型,功能号 ah =0x15mov ax,#0x01500mov dl,#0x81 ! dl = 驱动器号(0x8X是硬盘,0x81是第一个硬盘,0x82是第二个硬盘)int 0x13jc no_disk1 ! 第二个不存在cmp ah,#3 ! ah =类型码 指硬盘je is_disk1 ! 存在; 第二个硬盘不存在,对第二个硬盘表清零
no_disk1:mov ax,#INITSEGmov es,axmov di,#0x0090mov cx,#0x10mov ax,#0x00repstosb
; 第二个硬盘存在,进入保护模式,从此开始不允许中段
is_disk1:! now we want to move to protected mode ...cli ! no interrupts allowed !! first we move the system to it's rightful place
; bootsect引导程序将system模块移动到(0x10000)处,
; 并把自己移动到(0x90000)处,把setup加载在它后面
; 下面这段程序将整个system模块移动到0x00000处,
; 即把从0x10000到0x8ffff的内存数据块整块的向内存地址低端移动了0x10000的位置mov ax,#0x0000cld ! 'direction'=0, movs moves forward
do_move:mov es,ax ! destination segmentadd ax,#0x1000cmp ax,#0x9000 ! 判断代码是否移动完成jz end_move ! 移动完成则跳转mov ds,ax ! source segmentsub di,disub si,simov cx,#0x8000 ! 循环移动,循环次数,每次循环完次数减 移动0x8000字rep ! 用于把内容从ds:si 复制es:di 以字节单位movsw ! rep是repeat,rep配合 movw(movsb) 就是多次复制直到cx=0为止 复制的次数放在cx中jmp do_move! then we load the segment descriptors
; 加载段描述符,设置全局描述符表和中断描述表end_move:mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-)mov ds,ax
; lidt指令用于加载中断描述符表(IDT)寄存器
; 中断描述符表中每一个8个字节对应每个中断发生时所需要的中断程序地址入口lidt idt_48 ! load idt with 0,0
; lgdt指令用于加载全局描述符表(GDT)寄存器
; 全局描述符表中每个描述符项(8字节)描述了保护模式下数据段和代码段的信息lgdt gdt_48 ! load gdt with whatever appropriate! that was painless, now we enable A20
; 开启A20地址线,为了能够访问和使用1MB以上的物理内存call empty_8042 ! 测试8042状态寄存器,等待输入缓冲器空,mov al,#0xD1 ! command write 0xD1命令码表示写数据到8042的P2端口out #0x64,alcall empty_8042 !等待输入缓冲器空,看命令是否被接受mov al,#0xDF ! A20 onout #0x60,alcall empty_8042 !若此时输入缓冲器为空,则表示A20线也选通 ! well, that went ok, I hope. Now we have to reprogram the interrupts :-(
! we put them right after the intel-reserved hardware interrupts, at
! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really
! messed this up with the original PC, and they haven't been able to
! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f,
! which is used for the internal hardware interrupts as well. We just
! have to reprogram the 8259's, and it isn't fun.; 重新对中断进行编程mov al,#0x11 ! initialization sequenceout #0x20,al ! send it to 8259A-1 发送到8259A主芯片
; 0x00eb直接使用机器码表示两条相对跳转指令,起延时作用.word 0x00eb,0x00eb ! jmp $+2, jmp $+2out #0xA0,al ! and to 8259A-2 再发送到8259A从芯片.word 0x00eb,0x00eb
; 系统硬件中断号被设置成0x20开始mov al,#0x20 ! start of hardware int's (0x20)out #0x21,al !送主芯片ICW2命令字,设置起始中断,要送奇端口.word 0x00eb,0x00ebmov al,#0x28 ! start of hardware int's 2 (0x28)out #0xA1,al !送主芯片ICW2命令字,从芯片的起始中断号.word 0x00eb,0x00ebmov al,#0x04 ! 8259-1 is masterout #0x21,al !ICW3.word 0x00eb,0x00ebmov al,#0x02 ! 8259-2 is slaveout #0xA1,al.word 0x00eb,0x00ebmov al,#0x01 ! 8086 mode for bothout #0x21,al.word 0x00eb,0x00ebout #0xA1,al.word 0x00eb,0x00ebmov al,#0xFF ! mask off all interrupts for nowout #0x21,al.word 0x00eb,0x00ebout #0xA1,al! well, that certainly wasn't fun :-(. Hopefully it works, and we don't
! need no steenking BIOS anyway (except for the initial loading :-).
! The BIOS-routine wants lots of unnecessary data, and it's less
! "interesting" anyway. This is how REAL programmers do it.
!
! Well, now's the time to actually move into protected mode. To make
! things as simple as possible, we do no register set-up or anything,
! we let the gnu-compiled 32-bit programs do that. We just jump to
! absolute address 0x00000, in 32-bit protected mode.; 进入保护模式,只是跳转到绝对地址0x00000处; 加载机器状态字(控制寄存器CR0),将0位置1,CPU切换到保护模式mov ax,#0x0001 ! protected mode (PE) bit 保护模式比特位(PE)lmsw ax ! This is it! 加载状态寄存器;段选择符8表示请求特权0级,使用GDT第二个段描述符jmpi 0,8 ! jmp offset 0 of segment 8 (cs) 跳转至cs段偏移地址位0处(system已经移动到0x00000处)! This routine checks that the keyboard command queue is empty
! No timeout is used - if this hangs there is something wrong with
! the machine, and we probably couldn't proceed anyway.; 检差键盘命令队列是否为空
; 只有当输入缓冲器为空(键盘控制器状态寄存器位1 = 0)才可以进行写命令
empty_8042:.word 0x00eb,0x00eb !延时作用in al,#0x64 ! 8042 status porttest al,#2 ! is input buffer full?jnz empty_8042 ! yes - loopret; GDT全局描述符表开始处,描述符表由多个8字节长的描述符项组成,
; 3个描述符项
; 第一项没有作用,但是必须存在
; 第二项是系统代码段描述符
; 第三项是系统数据段描述符
gdt:.word 0,0,0,0 ! dummy 第一个描述符 不用; 在GDT表这里的偏移量是0x80,它是内核代码段选择符的值.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb).word 0x0000 ! base address=0.word 0x9A00 ! code read/exec.word 0x00C0 ! granularity=4096, 386; 在GDT表这里的偏移量是0x10,它是内核数据段选择符的值.word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb).word 0x0000 ! base address=0.word 0x9200 ! data read/write.word 0x00C0 ! granularity=4096, 386; 加载中断描述符表寄存器(idtr)
; 这里设置一个长度为0的空表
idt_48:.word 0 ! idt limit=0.word 0,0 ! idt base=0L; 加载全局描述符表寄存器(gdtr)
; GDT表长度为2kb
gdt_48:.word 0x800 ! gdt limit=2048, 256 GDT entries.word 512+gdt,0x9 ! gdt base = 0X9xxxx.text
endtext:
.data
enddata:
.bss
endbss:
ch1_系统启动_setup.S相关推荐
- 错误提示没了_ESC错误排查-系统启动篇
# ECS从入门到精通(错误排查) # Linux启动与登录问题 Linux 启动与登录问题是 ECS 的高频问题,而往往处理不及时会直接影响到 用户业务的正常可持续运行,因此也变成了我们处理问题优先 ...
- Linux系统启动任务的写法
1.到/etc/rc.d目录 # cd /etc/rc.d 2.修改rc.local # vim ./rc.local 你之前是怎么启动nginx和php命令复制即可. 例如: /usr/local/ ...
- linux基础—课堂随笔010_系统启动和内核管理
系统启动和内核管理 Linux: kernel+rootfs kernel: 进程管理.内存管理.网络管理.驱动程序.文件系统.安全功能 rootfs:程序和glibc 库:函数集合, functio ...
- Linux 引导和系统启动
bootstrap 引导程序;鞋带 -> 简称 boot 启动 pull oneself up by one's bootstraps.(体现计算机系统启动的难处) Linux系统启动分为两大部 ...
- Android 系统启动过程
文章来源于网络,心得来源于整理. 请尊重作者:http://hi.baidu.com/guoxiaoming/blog/item/24e9e9f8c9628f1fd9f9fd89.html/cmtid ...
- RHEL系统启动流程
从按下PC的POWER开始到用户看到登陆界面发生了什么? 概括的说:初始化BIOS-->载入启动加载器-->载入内核-->启动init服务 1.初始化BIOS BIOS在所有OS的作 ...
- 嵌入式学习笔记-记录系统启动次数
在实际应用的过程中,我偶尔会用到计算开发板的启动次数,最笨的方法将所有log保存,最后就是查看log,数一下启动次数,可以通过查找关键字的方式(当然,我不会笨到启动一次记录一次的,嘿嘿).感觉这样太麻 ...
- linux校时写入硬件,手动校正 Linux 系统时间并把时间写入硬件,系统启动时自动校正时间并把时间写入硬件。...
手动校正 Linux 系统时间并把系统时间写入硬件,系统启动时自动校正时间并把时间写入硬件. 下面以 CentOS 6.5 (32位)系统为例,设置系统时间:手动校正 Linux 系统时间并把时间写入 ...
- 微软官方pe工具_小白用户如何制作系统启动版,微软出官方工具啦,简单一键制作...
制作系统安装盘对于我看来是一件非常简单的事,多次帮朋友的实战操作让我对此轻车熟路.然而,很多朋友都是请维修店的师傅花钱处理,不仅花钱还很有可能有绑定软件,当初小编出了一个制作启动盘的教程,很多新手用户 ...
最新文章
- Maven 学习Tips
- 论怎么写好一篇实验报告
- 计算机网络·CSMA/CD协议有关计算
- javascript中常用数组方法详细讲解
- mysql权限表_MySQL 数据库赋予用户权限操作表
- 冲刺第三天 11.27 TUE
- 腾讯视频app官方下载_腾讯视频怎么快进
- jupyter notebook选择conda环境
- R7-9 红色警报 (25 分)
- python递归求13的n次方_Python题目:递归的简单题目,求阶乘,求n-m的累积和,求斐波那契...
- 卖两本windows phone的书
- 在java中怎么表示三角函数_【Math】三角函数在Java中是怎么表示的?
- systemd 简介
- Unity 360全景图转换为天空盒子
- 通关Bandit(0-32)命令大全
- Java实现hanoi塔
- Python数据分析 2.Matplotlib绘图—常用统计图
- 对html基础内容的理解
- 幼儿教师个人简历模板范文
- 团队协作软件有哪些?助力团队高效办公用这一款工具