SEO基础

SEO基础

Products

当前位置:首页 > SEO基础 >

哈尔滨网站制作哪家好?薇网站建设具体有哪些分工?

96SEO 2026-02-19 10:48 2


如果理解不了上面的…这篇文章写的很好是理解操作系统加载部分的基础

哈尔滨网站制作哪家好?薇网站建设具体有哪些分工?

https://www.cnblogs.com/chuganghong/p/15415208.html

流程图

不必全部看懂。

我觉得可能对读者理解后面的内容有帮助所以先给出这张图。

Kernel

在加载kernel到内存中之前先要有一个内核。

我们马上写一个。

下面的代码在文件kernel.asm中。

[section

$上面的代码是展示了一个汇编函数的模板但又不是典型的汇编函数模板。

[section

.text]是伪指令不会被CPU执行仅仅只是告知程序员下面的代码是可执行代码提高代码的可读性。

global

coderet这才是汇编函数的标准模板。

func_name是函数名;some

code和ret都是函数体。

ret可以理解为C语言中的return。

在kernel.asm中函数名是_start这固定的只能是这个名字由编译器或链接机制决定我也不是特别清楚。

mov

把kernel.asm编译成elf格式的目标文件kernel.o。

nasm

kernel.o

kernel.asm使用连接器ld把目标文件kernel.o连接成32位的可执行文件kernel.bin并且使用0x30400作为文本段的起始点。

ld

-Ttext

convnotrunc挂载软盘a.img以便在下面把loader.bin、kernel.bin写入软盘。

sudo

mount

经过上面的一系列步骤就得到了一个可执行的内核文件kernel.bin。

虽然简单但我们写的这个操作系统无论变得多复杂都是在这个简单的内核文件上慢慢添加功能变成的。

加载内核

内核代码在kernel.bin文件中。

kernel.bin是kernel.asm经过编译后的二进制文件。

加载内核就是把kernel.bin从软盘中读取到内存中。

根据文件名在软盘的根目录中找到目标文件的第一个FAT项的编号同时也是在数据区的第一个扇区的编号。

由于文件的所有FAT项构成一个单链表变量这个单链表读取每个FAT项对应的扇区的数据复制到内存中。

和读取引导扇区到内存中的方法高度相似在代码上只有很小的差异。

在后面不能理解加载内核的代码时请回头看看《开发引导扇区》。

CPU模式

13h会把从软盘中读取到的数据存储到es:bx指向的内存中。

就从es:bx这条指令称呼它为指令不严谨我没见过在汇编代码中直接使用这样的指令开始讲述本节的内容。

先回答第二个问题它的值表示一个内存地址物理内存地址不是在高级编程语言例如C语言编写的程序中出现的内存地址。

后者是虚拟内存地址。

什么叫物理内存地址先解释什么是物理地址。

拿软盘来说。

在软盘的引导扇区的最后两个字节存储0x55AA那么在软盘中偏移量是510个字节的存储空间中能看到0x55AA。

如果510是虚拟地址在软盘中偏移量是510个字节的存储空间中可能看不到0x55AA。

所以对“物理地址”和“虚拟地址”的理解是前者和存储空间一一对应如果0x55AA存储在软盘的引导扇区中地址为510的存储空间中就能在这个地址的存储空间中找到0x55AA中这两个字节的数据。

后者和存储空间不是一一对应的0x55AA存储在虚拟地址510的存储空间中在软盘中地址为510的存储空间找不到0x55AA这两个字节的数据。

理解了“物理地址”再把软盘换成内存想必非常容易理解“物理内存地址”的含义。

那么es:bx的值究竟应该如何计算呢计算方法取决于CPU所处的模式。

先介绍一下“实模式”。

实模式

实际上我们已经体验过“实模式了。

引导扇区程序就运行在实模式下。

我认为对这种古老的历史知识不必深究。

实模式对我而言有用的知识点是

在这种模式下物理内存地址的计算方法也就是计算es:bx的值的方法。

例如要把内核读取到物理内存地址是0x91000的存储空间中只需把es的值设置成0x9000把bx的值设置成0x1000。

为什么这样做就能表示物理内存地址0x91000呢这涉及到选择子、“地址总线”等知识。

我认为不知道这些古老的概念暂时并不妨碍我们继续开发自己的操作系统内核。

就算花点时间弄明白了过段时间又忘记了不忘记作用似乎也不大仅仅满足了自己的好奇心。

不如先跳过这种细枝末节、不影响大局的知识点降低自己的学习难度。

以后有时间再弄清楚这种细节。

内存条可以随意增加空间能使用的最大内存就取决于计算机能寻址多大的内存地址。

计算机的内存寻址又受制于寄存器能提供的内存地址值和地址总线能传递的内存地址值。

不可避免地要介绍一下地址总线。

我也只是稍微了解一点够用的知识。

CPU和内存之间通过地址总线交流可以这么简单粗暴地理解。

CPU把内存地址例如0x9000:0x1000通过地址总线告知内存要读取0x9000:0x1000处的数据。

首先0x9000、0x1000这两个数值能存储在16位寄存器es、ax中。

如果超过16位寄存器的数值存储范围CPU就不能读取0x9000:0x1000处的数据。

地址总线接收到的内存地址是0x9000:0x1000计算出来的数值0x91000。

如果0x91000超出地址总线的存储范围CPU也不能读取到目标数据。

2^{20}-1

计算机寻址的最小单位是“字节”而不是“bit”。

所以实模式下计算机能使用的内存最大是

2^{20}

220−11因为内存地址的初始值是0。

从0到最大值$2-$1总计有

2^{20}-11

各位的电脑内存是多大远远大于1M。

实模式很快就不能满足需求所以就出现了“保护模式”。

保护模式

在实模式下任何指令能使用任何合法的内存地址指向的内存空间。

想象一下A地址存储小明的代码XMB地址存储小王的代码XW。

小明和小王约定好执行完XM后跳转到XW执行XW后跳转到XM。

可是小王和小明吵了一架偷偷地在XW中把A地址处的代码全部擦除执行完XW后小明的代码就再也不会执行了。

再举个例子你一边用编辑器写代码一边用某个音乐播放器听音乐音乐播放器偷偷修改了正在运行中的编辑器所占用的内存中的数据导致你运行自己的代码时总是出错。

当然你肯定没有遇到过这种奇怪的事情。

因为你的电脑的CPU不是运行在实模式下。

通过两个例子应该能够知道实模式的弊端。

保护模式中的“保护”二字集中体现在对指令能使用的内存空间做了限制不能像在实模式下那样能随心所欲地使用任何合法的内存地址指向的内存空间。

保护模式下仍然用es:bx这种格式的数据表示内存地址可计算es:bx的值的方法和实模式下大为不同。

GDT

Table对应的中文术语是“全局描述符表。

在后面还会遇到LDT。

全局描述符是一段8个字节的数据。

这8个字节包含三项内容段界限、段基址、段属性。

在前面我讲过保护模式下指令不能访问任意内存空间的指令能访问的内存空间是被限制的。

怎么实现这种限制呢

划定一段内存空间规定这段内存空间只有具有某种属性的指令才能访问。

怎么划定一段内存空间非常自然地想到在指定一个初始值再指定一个界限值就能确定一段内存空间。

然后再用“段属性”规定访问这段内存空间需要具备的条件。

“初始值”就是“段基址”“界限值”就是“段界限”。

段属性是什么有点难说清楚后面再说。

在逻辑上全局描述符的结构很清晰可它的实际结构却非常不规则。

先看看全局描述符的结构示意图。

怎么解读这张图

示意图中把描述符分为“代码段”描述符和“数据段”描述符。

是哪种描述符由段属性决定。

介绍描述符结构的终极目的是用代码表示描述符而且是用汇编代码表示。

描述符的本质是64个bit要把一段内存空间的信息初始地址–段基址、这段内存空间的长度减去1–段界限、这段内存空间的属性–段属性按描述符的结构存储到这64个bit中。

所谓“结构”可以理解为按某种规则解读一段数据。

例如桌子上按顺序放着10支铅笔每支红色铅笔表示1年2支红色铅笔表示2年3支红色铅笔表示3年按照这个规定10支红色铅笔表示10年。

再规定前四支铅笔表示小明的年龄后六支铅笔表示小王的年龄。

给你10支持按顺序排列的铅笔你能从中查询到小明和小王的年龄吗这一定是一件非常容易的事。

分别看看前4支、后6支铅笔中红色铅笔的数量即可。

不知道这个例子是否恰当。

我想表达的意思是结构是一种规定好的规则例如前4支铅笔表示小明的年龄读数据要按这种规则去读存数据也要按这种规则去存。

小明的年龄只能从前4支铅笔读取也只能用前4支铅笔存储。

typedef

}Descriptor;我当初怎么都理解不了汇编代码表示的描述符结构直到看到用C语言表示的描述符结构后才豁然开朗所以先给出C代码希望跟我有同样困惑的人能和我一样顿悟。

因为我只是普通的人当初理解“结构”和代码的对应关系着实花了不少时间所以在这里写得比较啰嗦实际上是把我当时的理解过程全部写了出来。

聪明人请快速跳过这段。

用汇编语言怎么表示描述符结构我决定先不照搬之前已经写好的代码而是和读者朋友一起现场徒手再写一次描述符结构的汇编代码A。

请对照前面的描述符结构图写代码。

过程如下

使用汇编代码中的宏来实现描述符结构。

这个宏有三个参数分别是段界限、段基址、段属性命名为seg_limit、seg_base、seg_attr。

每个参数的长度都是4个字节。

段界限1是seg_limit的低16位代码是seg_limit

0xFFFF。

段基址1是seg_base的低24位代码是seg_base

0xFF)

16)8)。

段基值2是seg_base的高8位代码是seg_base24。

分别用

%1、%2、%3表示上面的三个参数分别等价于A代码中的seg_base、seg_limit、seg_attr。

%macro

0xFFFFFF。

把A中的6个字节拆分成4个字节和2个字节原因是在nasm汇编中只存在dw、db这样的伪指令而不存在能表示6个字节的伪指令。

二者都能把段基址1存储到正确的内存空间只是受限于汇编语法把6个字节拆分成两部分来处理。

dw、db都是伪指令。

伪指令是指不被处理器直接支持的指令最终会被翻译成机器指令被处理器处理。

dw一个字两个字节。

db一个字节。

再比较二者对属性的处理。

A代码

4)A代码和B代码对属性的处理结果是一致的都符合nasm汇编语法。

B代码很容易理解。

A代码有点难懂展开分析一下。

在描述符结构图中BYTE5、BYTE6中存储的数据混合了段界限的高4位和全部属性。

8。

混合体总计12位高4位的前面是低12位所以必须把获取到的属性的高4位左移12位因此最终结果是(seg_attr

混合体的高4位、低8位都已经填充数据剩余中间4位用来存储段界限的高4位。

段界限的高4位是seg_limit

16。

要把这4位存储在混合体的中间4位需要跳过混合体的低8位使用(seg_limit

最后把混合体的三部分用|运算符拼接起来就是这样的(seg_attr

0xFF)

GDT存储在内存的一段空间内。

CPU要想正确读取GDT中的描述符需要先把这段空间的初始地址和空间界限存储在专门的寄存器gdtptr中。

在前面介绍保护模式时我提到过要确定一段内存空间至少需要两个值这段内存空间的初始值和这段内存空间的长度。

GDT也存储在一段内存空间中只需提供空间初始值和空间长度就能找出存储GDT的这段内存空间。

gdtptr的设计正是如此低16位存储界限高32位存储基地址。

16位界限

空间长度L)

1因为内存地址的初始值是0。

内存的第1个字节内存地址是0x0内存的第2个字节内存地址是0x1。

GDT的第1个字节内存地址是基地址0GDT的第2个字节内存地址是基地址1GDT的第3个字节内存地址是基地址2GDT的第L个字节内存地址是基地址L-1。

这就是“16位界限

空间长度L)

0c9ah;LABLE_GDT_FLAT_WR:Descriptor

0fffffh,

293hLABLE_GDT_FLAT_WR_TEST:Descriptor

5242880,

0c92hLABLE_GDT_FLAT_WR_16:Descriptor

0fffffh,

0892hLABLE_GDT_FLAT_WR:Descriptor

0fffffh,

0这段代码中的GdtPtr存储的值就是要填充到寄存器gdtptr中的值。

GdtLen

LABEL_GDT中的$表示当前位置即GdtLen之前的位置。

LABEL_GDT表示从存储当前二进制文件当前源文件编译之后得到的二进制文件的内存的初始地址到LABEL_GDT这个位置的字节偏移量。

汇编代码中的变量名称都能理解成这个变量相对于存储当前二进制文件的内存空间的初始位置的字节偏移量。

从LABEL_GDT到GdtLen是GDT表的长度。

GdtPtr

0GdtPtr的前16位存储GDT的长度减去1就是GDT的界限后32位存储0就是GDT的基地址。

这和gdtptr需要的数据结构正好一致。

事实上往gdtptr中存储的值就是GdtPtr中的数据。

完成这个操作的指令是

[GdtPtr]表示内存地址GdtPtr指向的内存空间中存储的数据而不是指内存地址这个值本身。

可以把[GdtPtr]理解成指针。

lgdt

关于GDT的理论知识只剩下全局描述符的指针和选择子还没有讲解。

让我们看看全局描述符的代码顺便理解一下全局描述符中的段属性。

LABEL_GDT:

LABLE_GDT_FLAT_WR_16:Descriptor

0fffffh,

Descriptor是创建描述符的宏。

汇编函数中的宏和C语言中的宏的作用相同。

注意我没有说Descriptor是创建“全局”描述符的宏。

因为用这个宏既能创建全局描述符又能创建局部描述符。

LABEL_GDT。

使用宏Descriptor三个参数都是0。

意思是这个描述符的描述的内存空间的段基址、段界限、段属性都是0。

这是一个空描述符是GDT的初始位置。

把GDT中的第一个描述符设计成空描述符是为了定位其他描述符时有一个参照系。

LABLE_GDT_FLAT_WR_16。

段基址是0段界限是0fffffh段属性是0892h。

段界限是0fffffh字节还是0fffffhG这由段属性中的一个属性决定。

下面详细介绍段属性。

描述符LABLE_GDT_FLAT_WR_16的属性值是0892h。

这个段属性是怎么确定的又应该怎么解读?

1001

0010。

这个二进制值的存储方式是“小端法”。

把它填入下面的表格中。

怎么填写?

从1000

0010的最右边开始依次把值填入两个表格的第0列、第1列、第2列直至填充到第11列。

表格一

1这两个表格是段属性的12个bit拼接在一起形成的仅仅只有表格的第1列、第2列所存储的数据的含义不同。

第1列、第2列是R、C的那个表格是代码段的段属性表格R、C分别表示是否可读、是否依从。

最关键的是X列表示是否可执行。

表格一中的X位上的值是0这表示这个描述符所描述的内存空间中存储的是“数据”所以这种描述符叫“代码段”描述符。

两个表格的第0列到第3列对应描述符结构图中的TYPE。

每个位的含义请看下面的两张图。

上面是对描述符结构图中TYPE的介绍。

下面介绍除TYPE外的其他属性。

S。

S是0时描述符是系统段/门描述符。

S是1时描述符是代码段/数据段描述符。

DPL。

描述符的特权级值可以是0或1或2。

数字越小特权级越高。

P。

P是0时描述符指向的段在内存中不存在。

P是1时描述符指向的段在内存中存在。

AVL。

保留位。

我没用到过。

暂时不用关注。

D/B。

比较复杂。

太琐碎了。

只需知道它决定段使用的操作数、内存地址的位数或者决定堆栈扩展的方向。

用到这一位时查资料即可。

毕竟记住这种细节用处不大成本不小。

G。

在前面我提过段界限除了具体数值还有一个数值单位。

这个单位由G决定。

G是granularity意思是“粒度”。

当G的值是0时段界限粒度是字节当G的粒度是1时段界限粒度是4kB。

描述符LABLE_GDT_FLAT_WR_16的段属性的G位是1段界限是0fffffh

4kb

再分析一个描述符LABLE_GDT_FLAT_WR。

它的段属性是0c92h。

我想把这个描述符设置成段界限粒度为4kB数据段可读写32位。

把属性0c92h换算成二进制形式110010010010然后填入数据段属性的表格。

DPL

1W位是1表示描述符指向的段是可读可写的。

S是1表示描述符是代码段或数据段描述符。

D/B是1表示描述符指向的段是32位的。

G是1表示描述符指向的段的段界限的粒度是4kB。

和我前面的设想吻合。

选择子

在保护模式下es:bx中的es中存储的数据叫做“选择子”。

GDT中包含多个描述符选择子的作用是从GDT中指定目标选择子。

可以近似地把GDT理解成C语言中的数组数组的元素的数据类型是描述符选择子是数组的索引。

像这样简化地理解选择子只是为了帮助我们体会选择子的作用。

它的作用是什么从GDT或LDT中找出目标描述符。

在前面我提过描述符分为全局描述符和局部描述符。

由全局描述符组成的描述符表叫GDT由局部描述符表组成的描述符表叫LDT。

给出一个选择子是从GDT还是LDT中挑选目标描述符呢?这是由选择子中的某些数据决定的。

来了解一下选择子的结构。

选择子的前2个bit存储RPL对应的中文术语是“请求特权级”。

RPL和DPL一样有三个取值00、01、11。

选择子的第2个bi存储TI。

TI是0时是GDT的选择子。

TI是1时是LDT的选择子。

选择子的剩余13位存储描述符索引。

这才是目标描述符在描述符表中的索引。

描述符索引本质是目标描述符在描述符表中的字节偏移量。

每个描述符的长度是8个字节。

描述符表能容纳的描述符的最大数量是

2^{13}

gdtptr的低16位存储GDT的段界限也就是说GDT的最大长度是

216

2每个描述符的长度是8个字节那么GDT中所有描述符的长度之和也就是GDT的长度是

2^{13}

3代码中的第0列初始值规定为0是选择子例如SelectFlatX。

表面上看选择子是对应描述符的内存地址相对于空描述符的内存地址的偏移量。

这个偏移量一定是8个字节的整数倍。

选择子的前三位分别是CPL和TI后13位是描述符在描述符表中的索引。

代码中的选择子是描述符之间的偏移量理解偏移量和选择子结构如何吻合是一个难点。

描述符相对于描述符表的初始地址及空描述符表的地址的偏移量必定是8个字节的整数倍。

若觉得不能透彻理解可以举例子看看。

丢弃偏移量的低3位相当于把偏移量左移3位等价于除以8结果是描述符的数量。

也就是说偏移量的高13位是选择子对应的描述符相对于描述符数组的初始位置的偏移量但这个偏移量的单位不再是字节而是描述符。

简单说偏移量的高13位是描述符在描述符表中的描述符偏移量。

描述符偏移量不就是描述符索引么

偏移量的高13位是描述符在描述符表中的索引理由在上面说了。

偏移量的低3位是0正好对应选择子的低3位。

把选择子的低3位即CPL、TI设置成0从结构和数据的对应看当然没问题。

可是选择子的低3位不会总是0需要存储具有特定意义的数据。

怎么办实现这个目的对描述符内存地址的偏移量进行修改就能实现这个目的。

正如上面的代码中的SelectVideo。

它的选择子的计算方式是LABLE_GDT_VIDEO

LABEL_GDT

SelectVideo的TI是0CPL是3。

这表示SelectVideo是GDT的选择子当前特权级是3。

代码中的其他选择子都是描述符内存地址相对于GDT初始地址的原始偏移量没有修改低3位存储的TI和CPL。

寻址方式

es:bx这种形式的内存地址叫“逻辑地址”。

es中的值是描述符表的选择子。

根据选择子在描述符表中选择它对应的描述符。

从描述符中获取这个描述符指向的内存空间根据描述符中包含的段基址和段界限就能划定一段内存空间。

bx就是在这段内存空间中的地址偏移量。

而es就是上图中的SEG。

进入保护模式

假如打开了A20地址线却还处在实模式下此时会错误处理超过了2的20次方的地址。

cli;

SelectFlatX:(BaseOfLoaderPhyAddr

LABEL_PM_START)从实模式进入保护模式使用这几行代码就可以了。

要理解每行代码的含义又涉及到一些古老的细节知识。

我以为这些东西作用不大在后续功能开发中不会用到第二次。

所以对进入保护模式的方法掌握到这个程度就足够了。

重新放置内核

内核文件kernel.bin已经从软盘中读入内存了为什么还要重新放置内核呢因为这个内核文件是elf格式的。

这种格式的可执行文件不仅包含可执行的指令还包括一些额外数据。

这种起“过渡”作用的废话真的需要吗不写这句话直接介绍elf又显得太突兀。

ELF

format。

linux系统上的目标文件、可执行文件都是elf文件。

elf文件包含elf头、section头表、segment、program

header

ELF的知识远远不止上面这么一点但了解上面的知识足以让我们理解重新放置内核这个操作。

好奇心强烈的读者朋友可以看看下面的补充知识。

不看也不会妨碍我们继续开发自己的操作系统。

复制段

重新放置内核处理的是kernel.bin。

它是ELF格式的可执行文件。

所谓重新放置就是把segment0、segment1、segment2等复制到内存中的某些位置。

具体流程如下

从ELF

header中记录着segment在文件中的偏移量p_offset、应该在内存中的地址p_vaddr和它的大小p_filesz。

遍历program

每个program_header的大小是32字节。

program_header

program_header_table

复制program_header对应的段。

Memcpy(program_header.p_vaddr,

address_of_kernel,

对应伪代码中的program_header_table也是ELF头的e_phoff。

mov

esi,

p_vaddreax的最终结果是段应该被重新放置到的内存地址。

mov

eax,

三个参数每个占用32位4个字节2个字占用6个字,12个字节add

esp,

ecx是e_phnum。

重新复制一个段后ecx的值应该减去1。

dec

ecx;

一个program_header的大小是20H处理完一个program_header后;

esi,

重新放置内核后怎么知道有没有把内核中的代码段放置到了正确的内存位置呢

我试过这样不能验证内核被正确地重新放置了。

内核也就是kernel.bin是一个ELF文件除了包含代码段还包含很多CPU不能识别的数据。

直接运行kernel.binCPU遇到不能识别的数据就不处理遇到了代码段就执行所以即使没有正确地放置内核也能看到内核运行效果。

也许是因为我们目前的内核太简单。

验证内核有没有被正确放置到内存中的方法是使用bochs查看内存中的数据。

让我们一起来验证一下。

实例分析ELF文件

table在文件中的偏移量e_phoff、第一个代码段在段中的偏移量p_offset、第一个代码段被重新放置到内存中后的初始地址p_vaddr、程序在内存中的入口地址e_entry。

program

e_entry。

在文件中的偏移量是18H长度是4个字节值是00

header中的偏移量是4H在文件中的偏移量是0x340x4长度是4个字节。

00和前面转换34

p_vaddr。

在段中的偏移量是8H在文件中的偏移量是0x340x8长度是4个字节。

程序的入口在内存中的地址是e_entry段在内存中的地址是p_vaddr。

e_entry

第一个代码段在文件中的偏移量是0x0应该被重新放置到内存地址p_vaddr即0x30000。

程序的入口的内存地址e_entry即0x30400在文件内的偏移量是0x400。

00000000:

..ef..........s这是第一个代码段中的数据没有搞错吗怎么这个代码段包含了ELF

Header和program

table。

可我在《一个操作系统的实现》、《程序员的自我修改—链接、装载与库》、维基百科中看到的ELF文件结构示意图都把segment和ELF画成互不包含的两个部分。

怎么理解两类矛盾的现象是这些书写错了吗

它们没有错。

正确的理解应该是这些资料中的图是第二个、第三个代码段反正不是第一个代码段和ELF

Header、program

在data中从0x400到0x41C都是代码段中的指令就是下面这块数据。

00000400:

..ef..........s程序在内存中的初始地址是0x30400。

我们只需对比内存0x30400到0x3041C中的数据是否和上面文件中0x400到0x41C数据一一相等就能判断内核是否被正确地放置到了内存中。

表格中的地址偏移量是指文件地址偏移量和内存地址偏移量。

两个偏移量相等只不过地址的基址不同。

启动bochs查看内存地址0x30400、0x30401、0x30402中的数据并填入下面的表格。

(0)

fe表格中的数据都是十六进制数据无论是否有没有0x前缀。

十六进制数据中的字母大小写不敏感。

比较下面几个地址处的数据是否相等。

一眼就能看出文件中的数据和内存中对应位置的数据相等。

我只对比了几个地址的数据。

读者朋友如果不相信这个结果可以继续看看其他地址处的数据是否相同。

如果你经过对比后发现内存中的第一个段的数据和文件中的段的数据完全相同就可以做出判断了内核被正确地重新放置到了内存中。

留一个疑问。

我们对比的是内存中的第一个段的数据和文件中的段的数据然而重新放置内核并不是直接从软盘中的文件中读取数据再放置而是从内存的一个位置把内核数据重新放置到另一个位置。

我的问题是对比内存中的数据和软盘中的文件中的数据效果等同于对比内存中的数据和另一段内存中的数据。

为什么二者的效果是等同的

补充知识

段保存未初始化的变量.rodata段保存字符串或只读变量.data段保存已经初始化了的全局或局部静态变量。

它们都是section或segment。

在可执行文件中它们是segment。

在目标文件等非执行文件中它们是section。

在”kernel这个小节编译kernel.asm产生的kernel.o是目标文件kernel.bin是可执行文件。

使用file命令查看这两个文件。

[rootlocalhost

executable。

二者都是ELF文件但前者是relocatable后者是executable。

只有ELF

Header的位置在ELF文件中是固定的在开头。

其他元素例如program

header

想了解更多关于ELF的知识请去看《一个操作系统的实现》的5.3节和《程序员的自我修改》的第3章。

《一个操作系统的实现》中的elf文件的结构和《程序员的自我修改》中的elf文件的结构不同。

另外像.rodata、.bss、.text这些在《一个操作系统的实现》中没有出现。

我不清楚像.rodata、.bss、.text这些和section头表、segment头表、program

header

我不知道怎么用更好的方式写剩下的内容就直接送上添加了详细注释的loader.asm吧。

org

wTotalSectorCount为0时这个值记录扇区数BS_DrvNum

未使用BS_BootSig

数据段描述符LABLE_GDT_FLAT_WR:Descriptor

0fffffh,

下面的代码把kernel.bin从软盘中加载到内存中和从软盘中读取loader.bin相似;

ax,

SEARCH_FILE_IN_ROOT_DIRECTORY:cmp

cx,

低5位设置为0其余位数保持原状。

回到正在遍历的根项的大小jmp

FILE_FOUND:mov

低5位设置为0其余位数保持原状。

回到正在遍历的根目录项的初始位置;

di,

簇号就是FAT项的编号把FAT项的编号换算成字节数;;push

bx;mov

商在ax中余数在dx中。

商是扇区偏移量余数是在扇区内的字节偏移量;

ax,

而不是jz。

昨天一定是在这里弄错了导致浪费几个小时调试。

;jz

READ_FILE_OVER

SelectFlatX:(BaseOfLoaderPhyAddr

100h

SelectFlatX:(BaseOfLoaderPhyAddr

LABEL_PM_START);

BaseOfKernel:OffSetOfLoader;jmp

BaseOfKernel:40h;jmp

BootMessageFirstSectorOfRootDirectory

equ

是在FAT1区域的偏移。

要把它转化为在软盘中的扇区号需加上FAT1对软盘的偏移量。

;

mov

用扇区偏移量计算出在某柱面某磁道的扇区偏移量可以直接调用ReadSectorcall

ReadSector;pop

三个参数每个占用32位4个字节2个字占用6个字,12个字节add

esp,

在32位模式下这两步操作不需要。

而且我没有找到把大操作数赋值给小存储单元的指令。

;

mov



SEO优化服务概述

作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。

百度官方合作伙伴 白帽SEO技术 数据驱动优化 效果长期稳定

SEO优化核心服务

网站技术SEO

  • 网站结构优化 - 提升网站爬虫可访问性
  • 页面速度优化 - 缩短加载时间,提高用户体验
  • 移动端适配 - 确保移动设备友好性
  • HTTPS安全协议 - 提升网站安全性与信任度
  • 结构化数据标记 - 增强搜索结果显示效果

内容优化服务

  • 关键词研究与布局 - 精准定位目标关键词
  • 高质量内容创作 - 原创、专业、有价值的内容
  • Meta标签优化 - 提升点击率和相关性
  • 内容更新策略 - 保持网站内容新鲜度
  • 多媒体内容优化 - 图片、视频SEO优化

外链建设策略

  • 高质量外链获取 - 权威网站链接建设
  • 品牌提及监控 - 追踪品牌在线曝光
  • 行业目录提交 - 提升网站基础权威
  • 社交媒体整合 - 增强内容传播力
  • 链接质量分析 - 避免低质量链接风险

SEO服务方案对比

服务项目 基础套餐 标准套餐 高级定制
关键词优化数量 10-20个核心词 30-50个核心词+长尾词 80-150个全方位覆盖
内容优化 基础页面优化 全站内容优化+每月5篇原创 个性化内容策略+每月15篇原创
技术SEO 基本技术检查 全面技术优化+移动适配 深度技术重构+性能优化
外链建设 每月5-10条 每月20-30条高质量外链 每月50+条多渠道外链
数据报告 月度基础报告 双周详细报告+分析 每周深度报告+策略调整
效果保障 3-6个月见效 2-4个月见效 1-3个月快速见效

SEO优化实施流程

我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:

1

网站诊断分析

全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。

2

关键词策略制定

基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。

3

技术优化实施

解决网站技术问题,优化网站结构,提升页面速度和移动端体验。

4

内容优化建设

创作高质量原创内容,优化现有页面,建立内容更新机制。

5

外链建设推广

获取高质量外部链接,建立品牌在线影响力,提升网站权威度。

6

数据监控调整

持续监控排名、流量和转化数据,根据效果调整优化策略。

SEO优化常见问题

SEO优化一般需要多长时间才能看到效果?
SEO是一个渐进的过程,通常需要3-6个月才能看到明显效果。具体时间取决于网站现状、竞争程度和优化强度。我们的标准套餐一般在2-4个月内开始显现效果,高级定制方案可能在1-3个月内就能看到初步成果。
你们使用白帽SEO技术还是黑帽技术?
我们始终坚持使用白帽SEO技术,遵循搜索引擎的官方指南。我们的优化策略注重长期效果和可持续性,绝不使用任何可能导致网站被惩罚的违规手段。作为百度官方合作伙伴,我们承诺提供安全、合规的SEO服务。
SEO优化后效果能持续多久?
通过我们的白帽SEO策略获得的排名和流量具有长期稳定性。一旦网站达到理想排名,只需适当的维护和更新,效果可以持续数年。我们提供优化后维护服务,确保您的网站长期保持竞争优势。
你们提供SEO优化效果保障吗?
我们提供基于数据的SEO效果承诺。根据服务套餐不同,我们承诺在约定时间内将核心关键词优化到指定排名位置,或实现约定的自然流量增长目标。所有承诺都会在服务合同中明确约定,并提供详细的KPI衡量标准。

SEO优化效果数据

基于我们服务的客户数据统计,平均优化效果如下:

+85%
自然搜索流量提升
+120%
关键词排名数量
+60%
网站转化率提升
3-6月
平均见效周期

行业案例 - 制造业

  • 优化前:日均自然流量120,核心词无排名
  • 优化6个月后:日均自然流量950,15个核心词首页排名
  • 效果提升:流量增长692%,询盘量增加320%

行业案例 - 电商

  • 优化前:月均自然订单50单,转化率1.2%
  • 优化4个月后:月均自然订单210单,转化率2.8%
  • 效果提升:订单增长320%,转化率提升133%

行业案例 - 教育

  • 优化前:月均咨询量35个,主要依赖付费广告
  • 优化5个月后:月均咨询量180个,自然流量占比65%
  • 效果提升:咨询量增长414%,营销成本降低57%

为什么选择我们的SEO服务

专业团队

  • 10年以上SEO经验专家带队
  • 百度、Google认证工程师
  • 内容创作、技术开发、数据分析多领域团队
  • 持续培训保持技术领先

数据驱动

  • 自主研发SEO分析工具
  • 实时排名监控系统
  • 竞争对手深度分析
  • 效果可视化报告

透明合作

  • 清晰的服务内容和价格
  • 定期进展汇报和沟通
  • 效果数据实时可查
  • 灵活的合同条款

我们的SEO服务理念

我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。

提交需求或反馈

Demand feedback