操作系统内核如何实现编写和内核还有引导程序的关系

题目是起的简陋一点 但是不影响我们的操作 长话短说 自己看代码 加了注释了
今天我们讲讲如何实现内核 还有内核的意义 我们的扇区0的引导扇区的程序又是干嘛的
一 加载内核
第 1 阶段:BIOS 运行阶段,在这个阶段主要对系统中的硬件进行初始化,并创建实模式下的中断系
统,最后将 cpu 在第 2 阶段运行的可执行文件 bootsect.bin 加载到物理内存,并跳转运行可执行文件 bootsect.bin;
第 2 阶段:引导任务运行阶段,在这个阶段中 cpu 在实模式下运行可执行文件是 bootsect.bin。因为
引导任务的可执行文件 bootsect.bin 的大小不能超过 512B,所以在引导任务运行阶段无法做太多工作。因
此本阶段的主要工作是:将 cpu 在第 3 阶段运行的可执行文件 kernel.bin 加载到物理内存(如图 1.3所示),并开启分段机制进入保护模式后,跳转运行可执行文件kernel.bin
第 3 阶段:内核运行阶段,在这个阶段中 cpu 运行操作系统的核心代码——内核。

这里几个例子
1.引导程序
.code16
BOOTSEG = 0x7c0#在实模式下给cs段进行赋值16位的逻辑段
KERNELLEN = 1#加载一个扇区521B
ljmp $BOOTSEG, $go#自动跳转到执行引导程序的起始地址
go:
movw %cs, %ax #代码段和数据段共享一个bin程序
movw %ax, %ds
movb $0x42, %ah#把磁盘中的数据加载到物理内存
movb $0x80, %dl #主机中第一个硬盘号默认为0x80号
movw $parameters, %si#传参ds:ip指向参数的地址
int $0x13 #调用13号中断把硬盘中的数据调入物理内存
cli#关掉16位模式的中断系统
lgdt gdt_48 #加载GDT存储段描述符其他段的所有段描述符 方便segment 进行跳转
movw $1, %ax #给16位控制寄存器赋值1
lmsw %ax #控制寄存器通过通用寄存器赋值CR0的第一位为1 代表cpu进入保护模式
ljmp $8, $0#跳转到对应的段描述符 也就是kernel的head.s其实地址
parameters:#总共16个字节 调用13号中断的参数
.word 0x0010 #默认值
.word KERNELLEN #需要从硬盘加载到物理内存占的总扇区数目
.long 0x07e00000 #把扇区1加载到物理内存的起始地址
.quad 1#扇区1号
gdt_48:
.word (gdt_end-gdt)-1 #边界值=边界长度-1
.long 0x7c00+gdt #gdt 全局段描述符表的起始地址
gdt:
.quad 0 #默认值0项 必须添加
.quad 0x00409a007e0001ff #内核cs段描述符
.quad 0x004092007e0001ff#内核ds段第一个描述符
.quad 0x0040920b80000f9f#内核ds段第二个描述符
gdt_end:
.org 0x1fe #给引导程序的其他内存单元置0
.word 0xaa55#16位bios系统进行识别是否调入物理内存
2.kernel的身体
kernel_start:
movw $0x10, %ax #把内核的数据段1的段选择子赋值给ds让它自动去获取对应位置的段描述符
movw %ax, %ds
movw %ax, %ss#把内核的数据段1赋值给ss 自动在GDT的全局段描述符里面寻找对应的段描述符 栈段和数据段1共享段
movw $0x18, %ax#把内核的数据段2赋值给es 自动寻找对应的段描述符
movw %ax, %es #对应的是显卡的段描述符
movl $init_stack_end, %esp #初始化栈段的地址结尾赋值给esp
call print_msg
jmp .
init_stack:
.fill 50, 4, 0 .fill repeat,size,value 申请200个字节=50*4的内存给栈段
init_stack_end:
3.kernel的函数模块
.global print_msg#声明全局的函数名 不然其他的文件不认识这个符号名
print_msg:
movl $msg, %esi #要把对应的ascii的首地址赋值给ds:esi
movl $0, %edi #给es:esi所在的显存偏移置0
movl msg_len, %ecx#给ecs赋值ascii的个数
movb $0x2, %al#设置颜色字体属性
cld#给flags寄存器的df位进行赋值0 正向拷贝字符串
1: movsb#把ds:esi的字符拷贝到es:edi 并且esi和edi自动增1
stosb#设置当前所在的es:edi内存位置存储进去al的值 并且di自动增加1
loop 1b #循环拷贝13次 直到ecx为0
ret#返回调用函数的位置的下一条机器指令
msg:
.ascii “hello, world.”
msg_len:
.long . - msg
我这里都加了注释 我就直接把我的体会和感悟写一下把
1.gdt 是什么
gdt是一个表 全局描述符 用来存储着我们的段符号 段描述符是干嘛的 看下面
2.段选择子
段选择子是因为我们在32位的模式下 我们的16位系统的bios中断系统会把扇区0号的引导程序加载到0x7c00到0x7dff的位置 所以这个时候我们的段寄存器 本身是16位 所以并不需要引入段选择子这个概念
但是32位系统的时候 这个时候不满足条件 我们的段寄存器16位 而且比如cs段 它也没有ecs这个玩意儿
所以只能引入了段选择子 段选择子16位 目的是为了我们进行在GDT表中寻找对应的段描述符
段选择子这个数据结构有三个段
操作系统内核如何实现编写和内核还有引导程序的关系_第1张图片
就是这个拉 仔细看看 index 目的是为了进行获取gdt对应的项 也就是段描述符的位置 TI=0的时候代表自动选择在GDT中寻找段描述符 TI=1时候目的是为了在LDT(局部段描述符)中寻找对应的段描述符
RPL目的是为了请求访问段 当RPL的数越小的时候 就会权限越大 所以当我们访问内核的段时候 必须数字<=内核的DPL(当前段的特权级)
3.段描述符
这个数据结构是这样的操作系统内核如何实现编写和内核还有引导程序的关系_第2张图片

而且每个段描述符只有8个字节 每个bit都起到一个作用 DPL 目的是为了保护访问 防止系统乱访问 防止用户可以随意修改系统禁区
4.我们的CR0是干嘛的 是控制寄存器通过通用寄存器赋值CR0的第一位为1 代表cpu进入保护模式
CR0是控制寄存器 它不可以直接被赋值 只能通过通用寄存器去存取
5.cli是用来关闭当前16位的中断系统
我们的32位和16位模式下 有不一样的bios中断系统
6.我们的段选择子 在我们的系统处于内核的32位保护模式下 我们的段寄存器存储或者跳转 访问还是怎么样 都是通过段选择子 访问段描述符 进行访问对应的线性地址空间
7.地址转换
16位的地址空间叫物理地址空间 或者逻辑地址空间 而我们的32位系统下 称为线性地址空间 而且32位模式下 也就是保护模式下 这个叫做虚拟内存空间 可以访问4G的空间 前提是分页机制开启 也就是CR0的最高位PG 置1 如果没有 那么线性地址空间和物理地址空间是一样的 只能访问16MB的空间 CR0最低位PE=1 从实模式会转换到保护模式
8.GDT和GDTR
我们的GDTR是存储全局描述符表的寄存器 通过传参方式调用LGDT 指令 进行加载GDT表的内容
9.线性地址
线性地址=段起始地址 +偏移地址
10.还有我们的段寄存器其实本质并不是16位 当我们访问对应段的时候 段寄存器存储着段选择子 但是这个时候 分可见和不可见 可见是16位的段选择子 不可见的是88=64+24 位 存储着对应的段线性起始地址和边界值(偏移量) 还有段的属性 也就是可读可写 或者可执行等属性 也就是段描述符的TYPE 控制的
段描述符的G 0的时候 单位是1B G1的时候是4KB
因为我们的段长度=(段边界值+1)G
11.当cpu模式进入32位的时候 会重新加载cs:eip cs存储的就是选择子了
12.
内核的栈、数据和机器指令在虚拟内存中的线性地址空间分别是内核的栈段、数据段和代码段在虚
拟内存中的线性地址空间的子集。即栈段中的不一定都是栈、数据段中的不一定都是数据、代码段中的
不一定都是机器指令。但是整个栈段都可以作为栈、整个数据段中的内容都可以作为数据、整个代码段
中的内容都可以作为机器指令。
13
80386有四个32位的控制寄存器,分别命名位CR0、CR1、CR2和CR3。但CR1被保留, 供今后开发的处理器使用,在80386中不能使用CR1,否则会引起无效指令操作异常。CR0包括指 示处理器工作方式的控制位,包含启用和禁止分页管理机制的控制位,包含控制浮点协处理器 操作的控制位。CR2及CR3由分页管理机制使用。CR0中的位5—位30及CR3中的位0至位11是保留 位,这些位不能是随意值,必须为0。
控制寄存器CR0的低16位等同于80286的机器状态字MSW。

你可能感兴趣的:(操作系统内核如何实现编写和内核还有引导程序的关系)