Skip to content

jessehui/OS-Learning

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

35 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OS Study Notes

Orange's 一个操作系统的实现 阅读笔记

NASM

什么是NASM?
NASM是一个为可移植性与模块化而设计的一个80x86的汇编器。它支持相当多 的目标文件格式,包括Linux和'NetBSD/FreeBSD','a.out','ELF','COFF',微软16 位的'OBJ'和'Win32'。它还可以输出纯二进制文件。它的语法设计得相当的简 洁易懂,和Intel语法相似但更简单。它支持'Pentium','P6','MMX','3DNow!', 'SSE' and 'SSE2'指令集.

在NASM中, 任何不被方括号[]括起来的标签或者变量名都被认为是地址, 访问标签中的内容必须使用[].所以:

mov ax, BootMessage
BootMessage:    db  "Hello, OS World!"

将会把这个字符串的首地址送到ax中. 如果有

foo     dw      1
mov     ax, foo;    将把foo的地址放入ax中
mov     bx, [foo];  将把foo的值(1)放入bx中

offset这个关键字在NASM中也是不需要的, 因为不加[]时就表示offset.

汇编指令

  • ORG是Origin的缩写:起始地址,源。在汇编语言源程序的开始通常都用一条ORG伪指令来实现规定程序的起始地址。如果不用ORG规定则汇编得到的目标程序将从0000H开始。例如:
         ORG 2000H   
    START:MOV  AX,#00H

汇编语言源程序中若没有ORG伪指令,则程序执行时,指令代码被放到自由内存空间的CS:0处;若有ORG伪指令,编译器则把其后的指令代码放到ORG伪指令指定的偏移地址。两个ORG伪指令之间,除了指令代码,若有自由空间,则用0填充。

  • JMP $
    $,代表当前地址。 $ 放在 LJMP 之后,它就代表这条指令本身的地址。 JMP $,就是转移到该指令的本身地址。 JMP $,就是原地转移的意思,即 死循环。 一旦有中断发生,就可以去执行中断程序。 注: 而$$表示一个节(section)的开始处被汇编的地址. 在写程序的过程中, $-$$会经常用到.表示本行距离程序开始处的相对距离.
    times 510-($-$$) db 0;表示将0这个字节重复510-($-$$)遍也就是剩下的空间都填充0直到510.

  • SHL,将目的操作数顺序左移1位或CL寄存器中指定的位数。左移一位时,操作数的最高位移入进位标志位CF,最低位补零。

引导扇区

当计算机电源被打开, 会先进行加电自检(POST), 然后寻找启动盘, 如果是选择从软盘启动, 计算机就会去检查软盘的0面0磁道1扇区, 如果它以0xAA55结束, 则BIOS认为它是一个引导扇区. 一个正确的引导扇区除了以0xaa55结束以外, 还应该包含一段少于512个字节的执行码. 接下来, 一旦BIOS发现了引导扇区, 就会将这512个字节的内容装载到内存地址0000:7c00处, 然后跳转到0000:7c00彻底将控制权交给这段代码. 到此为止, 计算机不再由BIOS控制, 而变成操作系统的一部分来控制.

调试

在Windows下, 可以选择的调试方法包括把引导扇区的代码的ORG 07c00h改为ORG 0100h就可以编译成一个.COM文件在DOS下运行了. 调试环境: Bochs
Bochs(发音:box)是一个以GNU宽通用公共许可证发放的开放源代码的x86、x86-64IBM PC兼容机模拟器和调试工具。它支持处理器(包括保护模式),内存,硬盘,显示器,以太网,BIOS,IBM PC兼容机的常见硬件外设的仿真。
许多客户操作系统能通过该仿真器运行,包括DOS,Microsoft Windows的一些版本, AmigaOS 4, BSD, Linux, MorphOS, Xenix和Rhapsody (Mac OS X的前身). Bochs能在许多主机操作系统运行,例如Windows, Windows Mobile, Linux, Mac OS X, iOS, PlayStation 2.
Bochs主要用于操作系统开发(当一个模拟操作系统崩溃,它不崩溃主机操作系统,所以可以调试仿真操作系统)和在主机操作系统运行其他来宾操作系统。它也可以用来运行不兼容的旧的软件(如电脑游戏)。
它的优点在于能够模拟跟主机不同的机种,例如在Sparc系统里模拟x86,但缺点是它的速度却慢得多。

bximage: 生成虚拟软盘(floppy disk)或虚拟硬盘(hard disk)
dd if=boot.bin of=a.img bs=512 count=1 conv=nontrunc ; 将引导扇区写进软盘
最后一个参数表示不裁剪,不截断(truncated). 使用真实软盘装载时不需要此参数, 因为真的不会被截断. 然后再a.img所在目录下新建bochsrc配置文件, 然后在终端输入bochs即可运行. 如果运行黑屏则是调试模式, 终端输入c解决.

当程序越来越大的时候, 限定引导扇区空间为512个字节就会造成错误.解决办法(1)写一个引导扇区,可以读取程序并运行;(2)借助别的东西,比如DOS, 把程序编译成COM文件, 让DOS执行它.

souce命令

source命令也称为“点命令”,也就是一个点符号(.)。source命令通常用于重新执行刚修改的初始化文件,使之立即生效,而不必注销并重新登录。

bochs调试命令

  1. b addr : 在addr处设置断点 例: b 0x30440
  2. c: 继续执行, 直到遇到断点
  3. s: 单步执行
  4. n: 单步执行, 遇到函数直接跳出

伪指令

指令是在执行阶段发挥作用的,由CPU(Intel、AMD等)来执行。伪指令是在编译阶段发挥作用的,由汇编器(MASM、TASM等)来解释。

  • db定义字节类型变量,一个字节数据占1个字节单元(8Bit),读完一个,偏移量加1
  • dw定义字类型变量,一个字数据占2个字节单元(16Bit),读完一个,偏移量加2
  • dd定义双字类型变量,一个双字数据占4个字节单元(32Bit),读完一个,偏移量加4

在段[SECTION .gdt]中并列有3个Descriptor, 看上去是个结构数组.这个数组的名字叫Descriptor. GdtLen是GDT的长度, GdtPtr也是个数据结构, 6个字节, 前2个是GDT的界限, 后4个是GDT的基地址.(GDT: Global Descriptor Table)

寄存器

首先 cpu中寄存器用于存储内存中数据的物理地址.

  • cs 为代码段寄存器,一般用于存放代码.通常和IP 使用用于处理下一条执行的代码
    cs:IP
    基地址:偏移地址. cs地址对应的数据 相当于c语言中的代码语句.

  • ds 为数据段寄存器,一般用于存放数据. ds地址对应的数据 相当于c语言中的全局变量.

  • ss 为栈段寄存器,一般作为栈使用 和sp搭档;ss地址对应的数据 相当于c语言中的局部变量.
    ss相当于堆栈段的首地址,sp相当于堆栈段的偏移地址.

  • es 为扩展段寄存器.

保护模式

关于IA32和X86:

  • IA32 : 32 bits Intel Architecture (32位带宽Intel构架)
  • IA64 : 64 bits Intel Architecture (64位带宽Intel构架)
  • i386 : Intel 386 ( 老的386机器,也泛指IA32体系的CPU)
  • i486 : Intel 486
  • i586 : Intel 586 ( Pentium ,K6 级别CPU )
  • i686 : Intel 686 ( Pentium II, Pentium III , Pentim 4, K7 级别CPU )

以上的86 也可以叫做 x86, 通称说 x86也是指 IA32构架CPU. x86是一个intel通用计算机系列的编号,也标识一套通用的计算机指令.集合。 在IA32下, CPU有2种工作模式: 实模式和保护模式. 打开PC, 开始时CPU是工作在实模式下的, 经过某种机制后,才进入保护模式.
保护模式下, CPU有着巨大的寻址能力, 并为强大的32位操作系统提供了更好的硬件保障.

实模式:
Intel 8086是16位CPU, 有着16位寄存器, 16位数据总线, 20位地址总线和1MB寻址能力(2^20). 一个地址由段和偏移2部分组成, 物理地址遵循计算公式:
物理地址(physical address) = 段值(segment)*16 + 偏移(offset)
段值和偏移都是16位.

保护模式:
新模式下的地址仍然用"段: 偏移"这样的形式来表示. 只不过保护模式下的__段__的概念发生了根本性地变化. 实模式下, 段值还是可以看做是地址的一部分的. 而保护模式下, 虽然段值仍然由原来16位的cs, ds等寄存器表示, 但此时它仅仅变成了一个索引. 这个索引指向一个数据结构的一个表项, 表项中详细定义了段的起始地址, 界限, 属性等. 这个数据结构, 就是__GDT__. GDT的作用是用来提供段式存储机制, 这种机制通过段寄存器和GDT中的描述符共同提供的. GDT: Global Descriptor table.

代码段和数据段描述符:
| Byte7 | Byte6 | Byte 5 | Byte 4|Byte 3|Byte 2| Byte 1 | Byte 0| |31..24 段基址2| 属性等 | 23..0 段基址1 | 15..0 段界限1 |

Selector选择子:

  • 15..3: 描述符索引;
  • 2: TI;
  • 1..0: RPL; 当TI和RPL都为0时, 选择子就变成了对应描述符相对于GDT基址的偏移.

打开地址线A20: 8086时, 只有20条地址总线, 寻址超过1MB, 直接wrap回去重新从地址零开始寻址. 可是到80286, 可以访问到1MB以上的空间了, 系统不再wrap回到开头. 为了向上兼容, 使用8042键盘控制器来控制第20个地址位, 就是A20地址线(8086时, A0~A19),如果不被打开, 第20个地址位将总会是0. 为了访问所有内存, 需要把A20打开, 开机时默认它是关闭的. 要打开就要操作端口92h.

LDT, GDT, GDTR, LDTR 区别

保护模式下的段寄存器 由 16位的选择器 与 64位的段描述符寄存器 构成.
段描述符寄存器: 存储段描述符 选择器:存储段描述符的索引 Reference: [http://www.techbulo.com/708.html]

GDT

全局描述符表GDT(Global Descriptor Table)在整个系统中,全局描述符表GDT只有一张(一个处理器对应一个GDT),GDT可以被放在内存的任何位置,但CPU必须知道GDT的入口,也就是基地址放在哪里,Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定在内存中某个位置之后,可以通过LGDT指令将GDT的入口地址装入此寄存器,从此以后,CPU就根据此寄存器中的内容作为GDT的入口来访问GDT了。GDTR中存放的是GDT在内存中的基地址和其表长界限。
32位基地址和16位表界限.

段选择子(Selector): 由GDTR访问全局描述符表是通过“段选择子”(实模式下的段寄存器)来完成的。段选择子是一个16位的寄存器(同实模式下的段寄存器相同)

LDT

局部描述符表LDT(Local Descriptor Table)局部描述符表可以有若干张,每个任务可以有一张。我们可以这样理解GDT和LDT:GDT为一级描述符表,LDT为二级描述符表。

LDT和GDT从本质上说是相同的,只是LDT嵌套在GDT之中。LDTR记录局部描述符表的起始位置,与GDTR不同,LDTR的内容是一个段选择子。

页式存储

页: 就是一块内存, 80386中, 页的大小是固定的4096字节(4KB). Pentium中, 页的大小还可以是2MB或者4MB.
在未打开分页机制时, 线性地址等同于物理地址, 所以可以理解为逻辑地址通过分段机制直接转换成物理地址. 但当分页开启时, 情况发生变化, 分__段__机制(segment)将逻辑地址转换成线性地址, 线性地址再通过分__页__机制转换成物理地址.
分页的主要目的是实现虚拟存储器.

转换使用两级页表, 第一级叫做页目录, 大小4KB, 存储在一个物理页中, 每个表项4字节长, 共有1024个表项, 每一个表项对应第二级的一个页表, 第二级的每一个页表也有1024个表项, 每个表项对应一个物理页. 页目录表的表项简称PDE, Page Directory Entry. 页表的表项简称PTE, Page Table Entry. 总共占用: 4KB*1024 = 4096 * 1024 = 4MB空间.

CR3: 又叫做PDBR, Page-Directory Base Register. 高20位是页目录表首地址的高20位,页目录表首地址的低12位会是0(页目录表是4KB对齐的).

所以我们用了4MB的空间来存放页表,并用它映射了4GB的内存空间. 有时候不需要这么大内存,就不需要这么大的空间存放页表. 所以根据内存的容量, 合理分配. 那么程序如何知道机器的内存容量呢? 一种通用性强的方法是利用中断15h

中断

在实模式下用int 15h 得到内存信息, 然后再保护模式下把它们显示出来. 并不是故意搞复杂, 而是在保护模式下, 中断机制发生了很大的变化. 原来的中断向量表已经被IDT所代替. 实模式下能用的BIOS中断在保护模式下已经不能用了. IDT跟GDT,LDT有相似的地方. 其实它也是个描述符表, 叫做中断描述符表(Interrupt Descriptor Table).

异常类型:
fault: 可被更正的异常. 一旦更正,可以继续执行. 处理器保存产生fault之前的那条指令.返回地址 是产生fault的指令. trap: 也允许程序继续执行. 异常处理程序的返回地址将会是产trap的指令之后的指令. Abort: 一种不总是报告精确异常发生位置的异常, 不允许程序或任务继续执行. 而是用来报告严重错误的.

保护模式下的IO

用户进程如果不被允许是无法进行IO操作的. 限制通过两个方面: IOPL和IO许可位图.
IOPL: 位于寄存器eflags的第12,13位. 指令in, ins, out, outs, cli, sti只有在CPL<= IOPL时才能执行. 这些指令被称为IO敏感指令. 低特权级访问这些IO敏感指令会导致常规保护错误.

IO许可位图: TSS偏移102字节处有一个被称作"I/O位图基础"的东西, 它是一个以TSS的地址为基址的偏移.指向的就是IO许可位图. 它的每一位表示一个字节的端口地址是否可用. 0表示可用, 1表示不可用. 每个任务都可以有单独的TSS, 所以每一个任务可以有它单独的IO许可位图. IO许可位图必须以0FFh结尾.

进入保护模式

一个操作系统从开机到开始运行, 大致经历: 引导 -> 加载内核入内存 -> 跳入保护模式 -> 开始执行内核. 在内核开始执行之前不但要加载内核, 而且还有准备保护模式等一系列工作, 全都交给引导扇区来做, 512字节很可能不够用. 所以, 把这个过程交给另外的模块来完成, 把这个模块叫做loader.

FAT12

是DOS时代就开始使用的文件系统. 几乎所有文件系统都会把磁盘划分为若干层次以方便组织和管理, 包括:扇区(Sector, 磁盘上的最小数据单元), 簇(cluster,一个或多个扇区), 分区(partition, 通常指整个文件系统).
引导扇区是整个软盘的第0个扇区, 这个扇区中有一个很重要的数据结构叫做BPB(BIOS parameter block). 紧接着引导扇区的是两个完全相同的FAT表, 每个占用9个扇区. 第二个FAT之后是根目录区的第一个扇区. 根目录区后边是数据区.

汇编和C互相调用

汇编代码中:

extern choose ; int choose(int a , int b) 在c文件中已经定义
[section .data] ; 数据在此
num1st      dd      3
num2nd      dd      4

[section .text] ; 代码在此

global _start   ; 必须导出_start这个入口, 以便让连接器识别
global myprint  ; 导出这个函数为了让c文件使用,所以用global

_start:
    ....;
    ....;
    push    dword   [num2nd]    ;
    push    dword   [num1st]    ;
    call chooose;   choose(num1st, num2nd);
    ....;

ELF executable linkable format

常被称为ELF格式,在计算机科学中,是一种用于执行档、目的档、共享库和核心转储的标准文件格式。

Makefile

= 用来定义变量. 最重要的语法:

target: prerequisites command

要想得到target, 需要执行command. target依赖prerequisites, 当prerequisites中至少一个文件比target文件新时, command才被执行.

例子:

ASM     = nasm
ASMFLAGS    = -I include/
TARGET      = boot.bin loader.bin


loader.bin : loader.asm include/load.inc include/fat12hdr.inc include/pm.inc
            $(ASM) $(ASMFLAGS) -o $@ $<

这里ASM和ASMFLAGS就是两个变量, 使用他们的时候要用$(ASM)和$(ASMFLAGS). $@代表target, $&lt;代表prerequisites的第一个名字.

进程

进程就是一块需要执行的或大或小的代码. 我们需要一个数据结构记录一个进程的状态. 在进程要被挂起时,进程信息被写入这个数据结构, 等到进程重新启动时, 这个信息被读出来.

进程状态保存用栈: 先进先出 后进后出. 保存进程状态那个东西称为进程表. 也叫进程控制块(PCB, process control block). 进程有很多, 所以就有很多进程表, 形成一个进程表数组.

进程栈: 进程运行时自身的堆栈.
进程表: 存储进程状态信息的数据结构.
内核栈: 进程调度模块运行是使用的堆栈.

ESP: ESP指向栈顶,程序执行时移动,ESP减小分配空间.ESP增大释放空间,ESP又称为栈指针。

TSS 全称task state segment,是指在操作系统进程管理的过程中,任务(进程)切换时的任务现场信息。 TSS在任务切换过程中起着重要作用,通过它实现任务的挂起和恢复。所谓任务切换是指,挂起当前正在执行的任务,恢复或启动另一任务的执行。在任务切换过程中,首先,处理器中各寄存器的当前值被自动保存到TR(任务寄存器)所指定的TSS中;然后,下一任务的TSS的选择子被装入TR;最后,从TR所指定的TSS中取出各寄存器的值送到处理器的各寄存器中。由此可见,通过在TSS中保存任务现场各寄存器状态的完整映象,实现任务的切换。

About

读 Orange's 一个操作系统的实现

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published