96SEO 2026-02-23 14:57 14
跳到ucore操作系统在内存中的入口位置kern/init.c中的kern_init函数的起始地址3.

kernel/debug/kdebug.ccprintf(%s\n\n,
切换到保护模式启用分段机制grade_backtrace();d.
print_ticks();//向终端打印时间信息1s打印一次b.
在这里我们将通过另外一个更加简单的软件-bootloader来完成这些工作。
为此我们需要完成一个能够切换到x86的保护模式并显示字符的bootloader为启动操作系统ucore做准备。
整个bootloader执行代码小于512个字节这样才能放到硬盘的主引导扇区中。
基于分段机制的内存管理CPU的中断机制外设串口/并口/CGA时钟硬盘2.
编译运行bootloader的过程调试bootloader的方法PC启动bootloader的过程ELF执行文件的格式和加载外设访问读硬盘在CGA上显示字符串3.
OS的方法函数调用关系在汇编级了解函数调用栈的结构和处理过程中断管理与软件相关的中断处理外设管理时钟实验内容
这个bootloader可以切换到X86保护模式能够读磁盘并加载ELF执行文件格式并显示字符。
而这lab1中的OS只是一个可以处理时钟中断和显示字符的幼儿园级别OS。
项目组成
定义了最先执行的函数start部分初始化从实模式切换到保护模式调用bootmain.c中的bootmain函数│
通过屏幕、串口和并口显示字符串,加载ucore操作系统到内存然后跳转到ucore的入口处执行.|
其中补全print_stackframe函数是需要完成的练习。
其他实现部分不必深究。
│
此文件是由tools/vector.c在编译ucore期间动态生成的├──
一个C语言小程序是辅助工具用于生成一个符合规范的硬盘主引导扇区。
|
单步调试和查看BIOS代码如果你是想看BIOS的汇编可试试如下方法
在看到gdb的调试界面(gdb)后执行如下命令就可以看到BIOS在执行了
可以看到后续的BIOS代码。
首先在CPU加电之后CPU里面的ROM存储器会将其里面保存的初始值传给各个寄存器
IP。
此时系统处于实模式并且截止到目前为止系统的总线还不是我们平常的32位
而我们的BIOS启动固件就在这个1M的空间里面。
BIOS启动固件需要提供以下的一些功能☆基本输入输出的程序☆系统设置信息☆开机后自检程序☆系统自启动程序在此我们需要找到CPU加电之后的第一条指令的位置然后在这里break单步跟踪BIOS的执行
所以BIOS的第一条指令的位置为0xffff0在这里因为此时我们的地址空间只有20位所以是0xffff0。
在这里我们利用
continue可以看到电脑在运行到kern_init是会触发break然后又紧接着在下一步continue执行调试
debug会出现一个新的终端分为上下两个窗口上面的窗口显示运行到的源码下面的窗口是gdb调试界面。
由上面的分析可知:BIOS的第一条指令的位置为0xffff0**查看
$0x3630,$0xf000e05b可以看到BIOS的第一条指令是一条跳转指令
ljmp然后程序会跳转到0xf000e05b开始进行一系列的操作。
在截图中我们看到pc0xfff0这是因为在x86的机器里面并没有pc这个寄存器所谓的pc值是通过CS:IP而得到的因此这里的PC所代表的是eip寄存器里面的值低
*[地址]便可以在指定内存地址设置断点当qemu中的cpu执行到指定地址时便会将控制权交给gdb。
n/s都是C语言级的断点定位。
s会进入C函数内部,但是不会进入没有定位信息的函数比如没有加-g编译的代码因为其没有C代码的行数标记没办法定位n不会。
ni/si都是汇编级别的断点定位。
si会进入汇编和C函数内部,ni不会
归纳:当要进入没有调试信息的库函数调试的时候用si是唯一的方法。
当进入有调试信息的函数用si和s都可以但是他们不同si是定位到汇编级别的第一个语句但是s是进入到C级别的第一个语句.gdb的单步命令:
3、如何使能和进入保护模式。
1、为何要开启A20以及如何开启A20
首先关于A20我们通过查询资料以及说明文档可以知道早期的8086CPU所提供的地址线只有20位
所以可寻址空间为0~2^20(1MB)但是8086的数据处理位宽16位无法直接访问1M的地址空间
PC的寻址结构是segment:offsetsegment和offset都是16位寄存器
最大值是0ffffh所以换算成物理地址的计算方法是把segment左移4位再加上offset
所以segment:offset所能表示的最大为10ffefh而这个地址超过了1M
但是从下一代的80286开始地址线成为了24位所能访问的地址空间超过了1M
此时寻址超过1M时会报错出现了向下不兼容所以为了解决这个问题采用了A20机制。
A20
这样来控制A20地址线的打开与关闭所以在实模式下需要确保A20开关处于关闭状态这样可以防止访问大于1M的地址空间但
是在保护模式下我们需要访问更大的内存空间所以需要将A20的开关打开如果在保护模式下A20的开关未打开的话此时我们只能访问奇数兆的内存
即只能访问0—1M2—3M4—5M……所以如果我们要进入保护模式首先就需要把A20开关给打开。
2、如何初始化GDT表
接下来我们需要了解下GDT表全局描述符表在整个操作系统中我们只有一张GDT表
在Intel里面有一个专门的寄存器GDTR用来存放GDT的入口地址
以后CPU就可以通过GDTR来访问GDT了。
3、如何使能和进入保护模式
关于这一点我们需要了解一个寄存器CR0首先我们来看下CR0寄存器的各个位代表什么在这里由于我们需要进入保护模式所以暂时可以先不用管其他的位只需关注最低位的PE即可
当PE置1的时候进入保护模式实质上是开启了段级保护只是进行了分段没有开启分页机制
如果要开启分页机制的话我们需要同时置位PE和PG。
有了初步了解之后我们便知道的开启保护模式的相关操作
Gate其次加载全局描述符表GDT最后只需要将CR0寄存器的最低位置为1即可。
接下来我们通过观察代码来查看UCore具体是如何实现相应的操作的#
等到空闲之后我们将0xdf写入60h端口至此来打开A20开关。
outb
(第20位)首先是开启A20根据上文我们知道需要将第20位为1即可
根据说明书我们可以知道A20地址线由键盘控制器8042进行控制
我们的A20所对应的是8042里面的P21引脚所以问题就变成了我们需要将P21引脚置1。
对于8042芯片来说有两个端口地址60h和64h。
我们首先利用0x64端口传递一个写入的指令然后由0x60端口读进去相应的参数来将P21置1。
的指令。
在这里可能有人会有疑问既然我们只需要将P21置为1就可以了
那么我们是不是可以传入多种不同的参数只需要对应的位为1就好了答案是不行的。
我们传入的0xdf参数在这里也相当于一条指令通过这条指令我们可以将A20的开关打开。
在这里我们还需要注意一个问题就是当前端口(60h或者64h)是否空闲
只有当这两个端口空闲的时候我们才可以向其传入数据。
boot/bootasm.S
等到空闲之后我们将0xdf写入60h端口至此来打开A20开关0xdf
只是进行了分段没有开启分页机制如果要开启分页机制的话我们需要同时置位PE和PG。
#
对于硬盘来说我们知道是分成许多扇区的其中每个扇区的大小为512字节。
读取扇区的流程我们通过查询指导书可以看到1、等待磁盘准备好2、发出读取扇区的命令3、等待磁盘准备好4、把磁盘扇区数据读到指定内存。
所以我们来看一下关于0号硬盘的I/O端口在这里我们可以看到对于0号硬盘的读取操作是通过一系列的寄存器完成的
所以在读取硬盘时我们也是通过对这些硬盘进行操作从而得到相应的数据。
在加载操作开始之前我们需要对ELFHDR进行判断观察是否是一个合法的ELF头3.
检查0x1F7的最高两位如果是01那么证明磁盘准备就绪跳出循环否则继续等待。
/*
SECTSIZE的定义我们通过追踪可以看到是512即一个扇区的大小*
继续对虚存va和secno进行自加操作直到读完所需读的东西为止。
readsect((void
SECTSIZE的定义我们通过追踪可以看到是512即一个扇区的大小
格式在elf.h中定义readseg((uintptr_t)ELFHDR,
调用readseg函数从ELFHDR处读取8个扇区的大小。
//
在加载操作开始之前我们需要对ELFHDR进行判断观察是否是一个合法的ELF头if
我们需要实现函数调用堆栈因此我们需要首先针对函数堆栈的操作做一些相关的了解对于函数堆栈来说可以分为以下三部分操作1、首先保存原相关寄存器的状态即将相关参数以及寄存器的当前状态压入栈2、其次在栈中进行函数操作即完成函数的相关功能3、最后释放栈空间回复原寄存器状态。
要实现以上的相关操作我们就需要对函数栈的结构有相关的了解
指向%esp栈顶(存储的是上一层的ebp也就是返回地址)当我们传完参数时我们进行push操作将原ebp的值压入栈
然后通过一个movl操作将返回地址压入对应的栈便实现了对函数栈的搭建。
所以一般而言
ss:[ebp8]处为第一个参数值最后一个入栈的参数值此处假设其占用4字节内存
而在每一层函数调用中都能通过当时的ebp值“向上栈底方向”能获取返回地址、参数值
“向下栈顶方向”能获取函数局部变量值。
最后在函数调用结束后我们只需要将ebp还原并且跳转到返回地址即可。
接下来我们来观察具体实现的代码我们需要在lab1中完成kernel/kdebug.c中函数print_stackframe的实现
可以通过函数print_stackframe来跟踪函数调用堆栈中记录的返回地址。
print_stackframe21kernel/kdebug.c
1.首先通过两个函数得到寄存器ebp和eip的值并存到变量里。
原ebp的值就存在ebp的位置eip的值存在ebp4的位置所以在这里通过数组的操作实现具体功能。
ebp
中断描述符表也可简称为保护模式下的中断向量表中一个表项占多少字节其中哪几位代表中断处理代码的入口
其中0~15位和48~63位分别为offset的低16位和高16位。
通过段选择子获得段基址加上段内偏移量即可得到中断处理代码的入口。
b.
完善kern/trap/trap.c中对中断向量表进行初始化的函数idt_init。
vectors数组即可。
我们需要对所有的中断入口进行初始化在这里我们首先需要对中断有一个大概的了解.
由CPU外部设备引起的外部事件如I/O中断、时钟中断、控制台中断等是异步产生的即产生的时刻不确定与CPU的执行无关我们称之为异步中断(asynchronous
把在CPU执行指令期间检测到不正常的或非法的条件(如除零错、地址访问越界)所引起的内部事件,称作同步中断(synchronous
interrupt)也称内部中断简称异常(exception)。
描述符联系起来。
同GDT(全局描述符表地址映射)一样IDT(中断描述符表)是一个8字节的描述符数组
IDT可以位于内存的任意位置CPU通过IDT寄存器IDTR的内容来寻址IDT的起始地址。
所以我们在进行初始化时只需要将这一点拿出来单独初始化即可。
kern/trap/trap.c中对中断向量表进行初始化的函数idt_init
在kernel/mm/memlayout.h中SETGATE(idt[i],
使操作系统每遇到100次时钟中断后调用print_ticks子程序向屏幕上打印一行文字”100
在上面我们已经将idt中断向量符表完成了初始化的操作所以我们在这里可以直接对其进行调用即可
两者联合便是中断处理程序的入口地址。
我们可以看到当出发了中断之后
所以在第三问我们需要调用时钟中断并且完成对于时钟中断的相关操作。
trap_dispatch()
每当ticks计数达到100时即出发了100次时钟中断后时钟中断会print“100
ticks”。
print_ticks();//向终端打印时间信息
{print_trapframe(tf);panic(unexpected
作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。
| 服务项目 | 基础套餐 | 标准套餐 | 高级定制 |
|---|---|---|---|
| 关键词优化数量 | 10-20个核心词 | 30-50个核心词+长尾词 | 80-150个全方位覆盖 |
| 内容优化 | 基础页面优化 | 全站内容优化+每月5篇原创 | 个性化内容策略+每月15篇原创 |
| 技术SEO | 基本技术检查 | 全面技术优化+移动适配 | 深度技术重构+性能优化 |
| 外链建设 | 每月5-10条 | 每月20-30条高质量外链 | 每月50+条多渠道外链 |
| 数据报告 | 月度基础报告 | 双周详细报告+分析 | 每周深度报告+策略调整 |
| 效果保障 | 3-6个月见效 | 2-4个月见效 | 1-3个月快速见效 |
我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:
全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。
基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。
解决网站技术问题,优化网站结构,提升页面速度和移动端体验。
创作高质量原创内容,优化现有页面,建立内容更新机制。
获取高质量外部链接,建立品牌在线影响力,提升网站权威度。
持续监控排名、流量和转化数据,根据效果调整优化策略。
基于我们服务的客户数据统计,平均优化效果如下:
我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。
Demand feedback