-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.html
547 lines (413 loc) · 38.7 KB
/
index.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
<!doctype html>
<html lang="en-us">
<head>
<meta name="generator" content="Hugo 0.68.3" />
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Axiomaster's Site | </title>
<meta property="og:title" content="Axiomaster's Site | ">
<meta property="og:type" content="website">
<meta name="Keywords" content="">
<meta name="description" content="">
<meta property="og:url" content="https://axiomaster.github.io/">
<link rel="shortcut icon" href='/favicon.ico' type="image/x-icon">
<link rel="stylesheet" href='/css/normalize.css'>
<link rel="stylesheet" href='/css/style.css'>
<link rel="alternate" type="application/rss+xml" href="https://axiomaster.github.io/index.xml" title="Axiomaster's Site" />
<script type="text/javascript" src="//cdn.bootcdn.net/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
<header id="header" class="clearfix">
<div class="container">
<div class="col-group">
<div class="site-name ">
<h1>
<a id="logo" href="https://axiomaster.github.io">
Axiomaster's Site
</a>
</h1>
</div>
<div>
<nav id="nav-menu" class="clearfix">
<a class="current" href="https://axiomaster.github.io">首页</a>
</nav>
</div>
</div>
</div>
</header>
<div id="body">
<div class="container">
<div class="col-group">
<div class="col-8" id="main">
<div class="res-cons">
<article class="post">
<header>
<h1 class="post-title">
<a href="https://axiomaster.github.io/post/2022-01-08-c-language-addition/" title="c语言补充说明">c语言补充说明</a>
</h1>
</header>
<date class="post-meta meta-date">
2022年1月8日
</date>
<div class="post-content">
1-1 从上电到引导扇区 1-2 启动补充说明 2-1 读取并执行软盘内容 2-2 确认软盘内容执行 3-1 保护模式 3-2 内存分页 3-3 保护模式补充 4-1 引入c语言 稍有经验的同学肯定知道,编译得到的二进制文件中有text, bss, data, rodata等段。我们今天试着剖析一下这些概念。
segment & section segment和section在中文某些场景下都被翻译成段,很容易搞混。我们今天这里不再翻译,直接使用这两个词。
代码经编译器编译链接后得到ELF文件,操作系统将ELF文件加载至内存中并运行。这里涉及到2个过程,链接与装载。
代码编译得到可重定位文件(.o)后,链接器(ld)对可重定位文件中的符号进行地址重定位,并最终得到可执行文件。 操作系统将可执行文件通过分页机制加载至内存之后执行。加载时,不同的页对应的属性(访问权限)不同,比如代码部分是只读可执行的,其它段是可写的等。 ELF文件作为2个过程的桥梁,就存在链接视图(Linking View)和执行视图(Execution View)两种视图。其中,section对应链接过程,segment对应装载(运行)过程。
链接时,根据不同的功能,ELF文件会划分位多个section。装载时,操作系统以页(4K)为单位进行,如果不足一个页,也要进行对齐。这时如果有多个section,每个section占用1个页,几乎每个section都会在对齐时导致一部分内存空间浪费。而对于操作系统来说,只关注每个page的读写等属性。所以操作系统就将相同读写书写的section对应为一个segment进行加载。
readelf -l可以查看section与segment对应关系:
ELF文件格式 ELF header ELF header格式是固定的,在/usr/include/elf.h中有定义:
/* The ELF file header. This appears at the start of every ELF file. */ #define EI_NIDENT (16) typedef struct { unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */ Elf32_Half e_type; /* Object file type */ Elf32_Half e_machine; /* Architecture */ Elf32_Word e_version; /* Object file version */ Elf32_Addr e_entry; /* Entry point virtual address */ Elf32_Off e_phoff; /* Program header table file offset */ Elf32_Off e_shoff; /* Section header table file offset */ Elf32_Word e_flags; /* Processor-specific flags */ Elf32_Half e_ehsize; /* ELF header size in bytes */ Elf32_Half e_phentsize; /* Program header table entry size */ Elf32_Half e_phnum; /* Program header table entry count */ Elf32_Half e_shentsize; /* Section header table entry size */ Elf32_Half e_shnum; /* Section header table entry count */ Elf32_Half e_shstrndx; /* Section header string table index */ } Elf32_Ehdr; e_entry就是程序总入口地址 e_phoff对应程序头表(Program header table)在文件中的偏移量 e_phentsize程序头表中每个条目(entry)的大小 e_phnum程序头表中条目数量,对应segment个数 Program header table 程序头表(Program header table)保存运行视图中的segment信息。……
</div>
<p class="readmore"><a href="https://axiomaster.github.io/post/2022-01-08-c-language-addition/">阅读全文</a></p>
</article>
<article class="post">
<header>
<h1 class="post-title">
<a href="https://axiomaster.github.io/post/2022-01-08-interrupt/" title="中断">中断</a>
</h1>
</header>
<date class="post-meta meta-date">
2022年1月8日
</date>
<div class="post-content">
1-1 从上电到引导扇区 1-2 启动补充说明 2-1 读取并执行软盘内容 2-2 确认软盘内容执行 3-1 保护模式 3-2 分页 3-3 保护模式补充 4-1 c语言 4-2 c语言补充 ……
</div>
<p class="readmore"><a href="https://axiomaster.github.io/post/2022-01-08-interrupt/">阅读全文</a></p>
</article>
<article class="post">
<header>
<h1 class="post-title">
<a href="https://axiomaster.github.io/post/2022-01-01-c-language/" title="引入c语言">引入c语言</a>
</h1>
</header>
<date class="post-meta meta-date">
2022年1月1日
</date>
<div class="post-content">
1-1 从上电到引导扇区 1-2 启动补充说明 2-1 读取并执行软盘内容 2-2 确认软盘内容执行 3-1 保护模式 3-2 内存分页 3-3 保护模式补充 编译过程 前面我们都是使用汇编进行编程,从这节开始引入c语言进行编写。我们知道C语言相对汇编语言,属于高级语言(^_^)。高级语言是需要编译成机器语言才可以执行的。为了让编译器编译得到的结果能够方便的在操作系统上执行,操作系统定义了二进制可执行文件的格式。linux上使用的就是ELF文件格式。
我们这次只是开发操作系统,没有做自己的C编译器,还是要使用gcc等编译器。所以我们的操作系统就要去兼容gcc编译得到的ELF文件结果。历史上,gcc也是在linux操作系统之前被开发出来的。
#include <stdio.h> int main() { printf("hello world\n"); return 0; } 我们用hello world走一遍这个流程。
我的环境是64位的,我们要编译32位的结果,所以中间会有一些特殊的编译选项。为了省事儿,也可以搭建32位的开发编译环境。
预处理 gcc -E hello.c -o hello.i 我们可以看到,7行的hello.c源文件经过预处理后,得到了733行的hello.i的文本文件。
编译器 gcc -S hello.i -o hello.s -m32 编译后得到了hello.s的汇编代码。这里,-m32是为了指定得到32位的结果,否则一些寄存器长度会错误。
汇编器 gcc -c hello.s -o hello.o -m32 经汇编之后,得到的是ELF 32-bit LSB relocatable 文件。
链接 ld -m elf_i386 hello.o -lc -e main 链接之后,得到的就是ELF 32-bit LSB executable可执行文件。……
</div>
<p class="readmore"><a href="https://axiomaster.github.io/post/2022-01-01-c-language/">阅读全文</a></p>
</article>
<article class="post">
<header>
<h1 class="post-title">
<a href="https://axiomaster.github.io/post/2021-12-26-protected_mode-addition/" title="保护模式补充">保护模式补充</a>
</h1>
</header>
<date class="post-meta meta-date">
2021年12月26日
</date>
<div class="post-content">
1-1 从上电到引导扇区 1-2 启动补充说明 2-1 读取并执行软盘内容 2-2 确认软盘内容执行 3-1 保护模式 3-2 内存分页 保护 前面讲过,保护模式的保护,是指CPU在进行内存跳转、访问时,增加了很多检查,确保内存访问安全。在分段和分页机制中,我们也看到了,段选择子,段描述符等都有很多的权限相关的标记位,CPU就是根据这些标记位及一系列访问规则判断是否有权限访问的,即进行内存保护。
数据段&代码段 段描述符中的S位(44位),表明段描述符的类型:
S=0, 系统段/门描述符 S=1, 数据段/代码段 段描述符中的TYPE位(40~43位),表明段描述符的类型。一个段可能为:
TYPE值 数据段/代码段 系统段/门描述符 0 只读 未定义 1 只读,已访问 可用286 TSS 2 读/写 LDT 3 读/写,已访问 忙的286 TSS 4 只读,向下扩展 286 调用门 5 只读,向下扩展,已访问 任务门 6 读/写,向下扩展 286 中断门 7 读/写,向下扩展,已访问 286 陷阱门 8 只执行 未定义 9 只执行、已访问 可用386 TSS A 执行/读 未定义 B 执行/读、已访问 忙的386 TSS C 只执行、一致代码 386 调用门 D 只执行、一致代码、已访问 未定义 E 执行/读、一致代码 286 中断门 F 执行/读、一致代码、已访问 386 陷阱门 从这么多种类型定义中,可以预期整个检查规则很复杂(@_@)。其中门描述符(gate)用于中断发生时的检查,CPU确认特权级等设定符合约束后,才能进入相应的处理程序。……
</div>
<p class="readmore"><a href="https://axiomaster.github.io/post/2021-12-26-protected_mode-addition/">阅读全文</a></p>
</article>
<article class="post">
<header>
<h1 class="post-title">
<a href="https://axiomaster.github.io/post/2021-12-19-page/" title="内存分页">内存分页</a>
</h1>
</header>
<date class="post-meta meta-date">
2021年12月19日
</date>
<div class="post-content">
1-1 从上电到引导扇区 1-2 启动补充说明 2-1 读取并执行软盘内容 2-2 确认软盘内容执行 3-1 保护模式 逻辑地址,线性地址,物理地址 程序编译后,程序内部使用的地址是段内偏移量,这个地址是逻辑地址。
程序加载运行时,操作系统负责多个程序的调度。程序使用的段内偏移量(逻辑地址),加上段内偏移量,就是线性地址。
如果没有分页机制,那线性地址=物理地址。启用分页机制之后,线性地址通过转换,才能对应为物理地址。
因此,逻辑地址经过分段机制转换后,得到线性地址;再经过分页机制转换后,得到物理地址。
分页 为什么要分页 段的长度不定,在分配内存时,可能会发生内存中的空闲区域小于要加载的段,或者空闲区域远远大于要加载的段。在前一种情况下,需要另外寻找合适的空闲区域;在后一种情况下,分配会成功,但太过于浪费。为了解决这个问题,从386开始,引入了分页机制。分页功能从总体上来说,是用长度固定的页来代替长度不一定的段,藉此解决因段长度不同而带来的内存空间管理问题。
页框,页表,页目录表 操作系统将线性地址按固定大小组织,每一份称之为一页(page)。页大小可以为4K,1M等,一般为4KB。物理地址按照同样大小进行划分,称为页框(frame)。操作系统需要记录线性地址与物理地址的映射关系,这个关系就记录在页表(page table)中。页表中的每一项称为PTE。
实际中,为了节省内存,会使用多级页表,即为页表再创建一个页目录表(page direcotry table)。页目录表中每的一项,称之为PDE,都指向不同的页表。
分页寻址 逻辑地址经过分段后,得到32位的线性地址。启用分页机制后,32位的线性地址会再次划分为3部分,分别表示
22~31位:指向页目录表中的某一项PDE 12~21位:指向具体某个页表中的某一项 0~11位:页内偏移量 假设我们采用2级页表,我们可以计算一下:
每个页大小4KB,12位的业内偏移量恰好可以寻址4KB偏移地址。 22~31位共10位,最多可以表示1024个不同值,因此页目录表最多只能有1024个PDE 同理,12~21位共10位,对应页表最多页也只能有1024个PTE 1024个PDE * 1024个PTE * 4KB = 4GB,分页之后能够寻址的最大空间还是4GB 启用分页机制 启用分页机制,需要进行如下3步操作:
创建页表 将cr3寄存器设置为页目录表地址 将cr0寄存器pg位置1 内存布局设计 现在的内存布局是我从其它地方抄来的。
截图中显示了页目录表,及第1页表。
页目录表中的第0项及第768项,均对应第1页表。 页目录表中第1023项(即最后一项),对应页目录表本身。 第1页表,只填写了0~255项,对应1M(4K*256)物理内存。 页目录表第0项表示线性地址最开端的4M地址,页目录表第768项对应线性地址3G开始的4M内存。均对应第1页表。当前我们在第1页表只填充了1M内存。所以线性地址开始的1M内存(0~1M)与3G开始的1M内存(3G~3G+1M),对应同一块物理内存。
这样划分,是因为之前所有的代码都是在线性地址的开始(0~1M)内存实现的。但我们会将4G内存空间划分为用户空间(0~3G)和内核空间(3G~4G)。通过将页目录表第0项与第768项指向同一块内存,则可以在使用线性地址3G以上的内核空间地址继续访问之前的1M内存。
测试 测试方法和以前相同,通过在屏幕打日志的方式判断是否执行成功。……
</div>
<p class="readmore"><a href="https://axiomaster.github.io/post/2021-12-19-page/">阅读全文</a></p>
</article>
<article class="post">
<header>
<h1 class="post-title">
<a href="https://axiomaster.github.io/post/2021-12-18-protected_mode/" title="保护模式">保护模式</a>
</h1>
</header>
<date class="post-meta meta-date">
2021年12月18日
</date>
<div class="post-content">
1-1 从上电到引导扇区 1-2 启动补充说明 2-1 读取并执行软盘内容 2-2 确认软盘内容执行 CPU保护模式 我们知道,操作系统是对计算机各种硬件资源进行管理的,通过提供各种API,方便各种应用使用计算机硬件完成各自功能。因此,开发操作系统需要受硬件实现限制,也可以理解为按照硬件接口约定调用硬件能力。比如启动扇区的最后两个字节必须是0xAA 0x55,这就是一种约定,不这样做就没法正常启动。对于386指令集的CPU,这样的硬件接口约定和指导汇总就是《Intel 80386 程序员参考手册》。
随着CPU功能的日益强大,这种硬件上的约定越来越多,其中很多还是特定历史原因造成的,或为了保持兼容性而设计的。这就导致我们需要了解的细节很多,而且不会很直观。保护模式就是实现操作系统过程中将会遇到的第一座大山,因为我们要借助CPU硬件能力实现。
实模式&保护模式 我们前面访问内存是采用的[CS:IP]=CS<<4+IP的方式就是实模式下的内存寻址方式。Intel 8086 CPU只有16位,到了80386时代(286存在时间很短),CPU进入了32位,这时再也不能采用之前8086 16位CPU的寻址方式了。同时,Intel在硬件设计中加入了很多内存访问检查等保护机制。为了与之前的CPU特性区分,Intel将8086对应的16位运行模式称为实模式,将386及之后的x86CPU对应的32位运行模式称为保护模式。
因为x86CPU是兼容之前8086CPU的,而且x86CPU启动后默认运行在实模式下。这也是为什么我们前面的代码放在最新的x86CPU上也可以运行的很happy的原因。
保护模式寻址方式 保护模式下,CPU分段寻址方式与实模式不再相同。操作系统将内存进行分段,并将分段信息以数组的形式存储在内存中。数组的每一项对应一个分段信息,称为段描述符(Segment Descriptor),这个数组被称之为段描述符表。全局有一个总的段描述符表,称为全局段描述符表(GDT),当然,也有局部段描述符表(LDT)。
可以想想,所有的内存寻址都需要通过GDT表进行,为了加速内存寻址,CPU提供了GDTR寄存器用于存储GDT对应的内存起始地址。同时,CPU提供了LGDT指令用于将GDT的内存起始地址加载到GDTR寄存器中。
有了全局段描述符表GDT,段寄存器中的值就不再像实模式下那样,是段的起始地址了。而是用来筛选出GDT表中对应的某一项的,称之为段选择子(Segment selector)。
这样,保护模式下的寻址方式大概如下:
操作系统开发者在内存中按格式创建出GDT 操作系统启动后通过LGDT指令将内存中GDT的起始地址加载到GDTR中 寻址时,[CS:IP]中,段寄存器的值被CPU理解为段选择子,通过CPU某些运算检查,从GDTR中找出某一项段描述符 段描述符通过CPU某些运算检查,得到段基址,加上偏移地址,得到最终地址结果 怎么样,被这些概念绕晕了吧? ^_^
保护 也许你会好奇,第3步的段选择子不应该就是一个数组下标吗?第4步中的段描述符不就是一个内存起始地址吗?如果让我来设计CPU,我也会这么干。但历史上,从8086到80386发展过程中,也许发生了很多因为内存访问导致的bug,导致Intel决定,在CPU中设计一套内存访问控制,权限检查机制。
因此,第3步段选择子除了数组下表的功能外,还存储了一些其它用于内存访问检查的信息,第4步段描述符除了存储内存起始地址,也存储了其它对应信息。这些信息在内存访问时,CPU会按照设计规则对齐进行检查,防止非法访问。这也就是保护模式中所谓的保护的意思。
数据结构 段描述符 段选择子 GDTR寄存器 从实模式到保护模式 从实模式切换到保护模式,要进行如下3步操作,这些操作都是Intel CPU要求的。
打开A20 Gate 加载GDT 将CR0的PE位置1 A20 Gate历史背景 8086下,CPU是16位的,地址总线20位,对应1M内存。当程序访问大于1M的地址时,系统会贴心的对地址按1M求模。这个技术称为wrap-around。
到了保护模式,我们需要访问大于1M的内存,需要关闭wrap-around机制。而这个机制,因为历史原因,被键盘控制器上的第21根地址线(A20 Gate)控制。当打开A20 Gate时,才能突破1M的内存限制。
创建并记载GDT 我们将内存空间划分为3个段。根据规定,第1个段必须为空。因为GDT中4项如下:
;0描述符 dd 0x00000000 dd 0x00000000 ;1描述符(4GB代码段描述符) dd 0x0000ffff dd 0x00cf9800 ;2描述符(4GB数据段描述符) dd 0x0000ffff dd 0x00cf9200 ;3描述符(28Kb的视频段描述符) dd 0x80000007 dd 0x00c0920b lgdt_value: dw $-gdt-1 ;高16位表示表的最后一个字节的偏移(表的大小-1) dd gdt ;低32位表示起始位置(GDT的物理地址) 跳转至32位模式 protect_mode: ;进入32位 lgdt [lgdt_value] in al, 0x92 or al, 0000_0010b out 0x92, al cli mov eax, cr0 or eax, 1 mov cr0, eax jmp dword SELECTOR_CODE:main [bits 32] ;正式进入32位 main: 测试 64位视频段描述符,我们展开后如下图:……
</div>
<p class="readmore"><a href="https://axiomaster.github.io/post/2021-12-18-protected_mode/">阅读全文</a></p>
</article>
<article class="post">
<header>
<h1 class="post-title">
<a href="https://axiomaster.github.io/post/2021-12-04-os-loader-addition/" title="确认软盘内容执行">确认软盘内容执行</a>
</h1>
</header>
<date class="post-meta meta-date">
2021年12月4日
</date>
<div class="post-content">
1-1 从上电到引导扇区 1-2 启动补充说明 2-1 读取并执行软盘内容 上一节我们根据内存地址推算,确定0xc400位置保存着我们存储在软盘中的sys.bin内容,并跳转到这个地址位置开始执行。但我们如何确认代码正确执行了呢?
hlt指令 CPU负载 当前在sys.bin中只有一个死循环,如果程序中出现一个死循环,那会将所有空闲CPU资源占满,即运行死循环的CPU负载会飙到100%。在真实机器上这点可能不好确认,但如果在虚拟机中运行操作系统,我们可以很容易的在宿主机上观察到这点。
hlt指令 当前我们的OS还没有任何实质性业务,就这样空跑CPU比较浪费。hlt指令就是为了解决这个问题的,它可以使CPU进入暂停状态,不执行任何操作。
我们修改sys.asm代码如下,在死循环中插入hlt指令。
org 0xc400 fin: hlt jmp fin 再试试效果:
bochs Debug 我们使用bochs虚拟机来运行我们写的OS,bochs提供了很多调试功能,可以看到cpu寄存器,内存等实时的数据。这样可以更直观的看到我们的OS运行情况。
这里我们用到两个调试命令:
打断点 b 0xc400 反编译内存内容 u /10 使用b在0xc400处设置断点,运行后断点命中:
命中断点后,使用反编译指令u反编译0xc400开始的2行代码,可以看到,与我们写的内容基本一致。
跳转指令不完全一致,但可以猜到是给予当前行$-3位置处。
添加打印 我们也可以通过在sys.asm调用BIOS的打印中断int 10h来往屏幕写内容,确认代码是否执行成功。
mov ax, 0xb800 mov gs, ax mov byte [gs:0xa0],'s' mov byte [gs:0xa2],'y' mov byte [gs:0xa4],'s' 可以看到,sys.asm中的打印代码成功输出了,因此可以确认sys.asm已经被正确执行了。……
</div>
<p class="readmore"><a href="https://axiomaster.github.io/post/2021-12-04-os-loader-addition/">阅读全文</a></p>
</article>
<article class="post">
<header>
<h1 class="post-title">
<a href="https://axiomaster.github.io/post/2021-11-22-os-loader/" title="读取并执行软盘内容">读取并执行软盘内容</a>
</h1>
</header>
<date class="post-meta meta-date">
2021年11月22日
</date>
<div class="post-content">
1-1 从上电到引导扇区 1-2 启动补充说明 根据前面的内容我们知道,上电之后,根据CPU硬件电路设计,会自动将软盘第1扇区加载并执行。如果我们实现的OS只需要使用512个字节的代码,那就不需要主动读取软盘,只依靠CPU的硬件设计自动读取第1扇区就可以了。但这显然不可能。
所以接下来,我们就依靠第1扇区这512个字节的代码量,读取更多的软盘内容。
读取软盘内容至内存 我们将自己写的操作系统的代码编译成二进制文件后,存储在软盘上。然后利用第1个扇区的代码(开机后被BIOS自动加载至0x7c00处并开始执行),将软盘上的内容拷贝到内存中。拷贝完成后,再跳转到我们拷贝的目的内存地址,开始执行。
软盘的结构 上图来自《30天自制操作系统》
从上图中,我们可以看到,软盘有一圈有18个扇区Sector,从外向内分为80个柱面Cylinder,正反两面分别对应不同的磁头Heads来读写,这称之为CHS寻址模式,对应三部分的首字母。前面提到每个扇区可以存储512个字节,所以整个软盘共可以存储512*80*18*2=1440KB
读取1个扇区 通过调用BIOS int 13中断,可以读取软盘数据,调用代码如下:
MOV AX, 0x0820 ; 内存目的地址 0x08200 MOV ES, AX MOV CH, 0 ; 柱面0 MOV DH, 0 ; 磁头0 MOV CL, 2 ; 扇区2 MOV AH, 0x02 ; AH=0x02 : 读盘 MOV AL, 1 ; 1个扇区 MOV BX, 0 MOV DL, 0x00 ; A驱动器 INT 0x13 ; 调用BIOS int 13中断 读取10个柱面 如果上面的代码封装为一个函数,那读取整个软盘的逻辑用c语言描述大约如下:……
</div>
<p class="readmore"><a href="https://axiomaster.github.io/post/2021-11-22-os-loader/">阅读全文</a></p>
</article>
<article class="post">
<header>
<h1 class="post-title">
<a href="https://axiomaster.github.io/post/2021-11-21-os-boot-addition/" title="启动补充说明">启动补充说明</a>
</h1>
</header>
<date class="post-meta meta-date">
2021年11月21日
</date>
<div class="post-content">
1-1 从上电到引导扇区 1-2 启动补充说明 清理屏幕 上一节我们通过在显存地址直接写入字符,打印除了“hello”。但因为显存内存储着BIOS启动过程中的一些显示信息,所以屏幕看着很乱。这一节我们在打印字符之前,先讲显存内的内容清空,再进行打印,这样看着清爽些。
清空显存,当然有个笨办法,就是像打印“hello”一样,从0xb8000开始,向显存写入空白或不可见字符。但这个比较累。还有一个办法就是调用bios提供的int 10中断。
int10中断 org 07c00h ; 告诉编译器程序加载到7c00处 ;----中断10h,卷屏,实现清屏 mov ax, 0x0600 mov bx, 0x0700 mov cx, 0 mov dx, 0x184f int 0x10 ;----直接往显存中写数据 mov ax, 0xb800 mov gs, ax mov byte [gs:0x00],'h' mov byte [gs:0x02],'e' mov byte [gs:0x04],'l' mov byte [gs:0x06],'l' mov byte [gs:0x08],'o' times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节 dw 0xaa55 ; 结束标志 以上代码中,我们先设置好参数,然后调用int 10中断,实现了清屏操作。效果如下:
关于INT 10h的更多细节,可以看这里 wikipedia INT 10h。……
</div>
<p class="readmore"><a href="https://axiomaster.github.io/post/2021-11-21-os-boot-addition/">阅读全文</a></p>
</article>
<article class="post">
<header>
<h1 class="post-title">
<a href="https://axiomaster.github.io/post/2021-11-17-os-boot/" title="从上电到引导扇区">从上电到引导扇区</a>
</h1>
</header>
<date class="post-meta meta-date">
2021年11月17日
</date>
<div class="post-content">
1-1 从上电到引导扇区 1-2 启动补充说明 上电 我们知道,现在使用的计算机都是存储程序计算机(冯·诺依曼计算机)。计算机运行时一边从内存中读取指令,一边执行指令。进一步,可能也知道对于8086 CPU,段寄存器 CS、指令寄存器 IP共同构成的地址[CS:IP]指向当前执行的指令。
但计算机上电开机之后,CS与IP中内容均为空,其它寄存器、内存的内容也为空。类似牛顿力学体系中的第一推动力一样,计算机上电后第一步该如何开始呢?牛顿给的答案是上帝提供了第一推动力。类似的,计算机启动第一步也只能通过体系外的某种方式解决。
上电后执行软件前只能靠硬件。CPU的硬件设计为上电后默认将CS置为0xF000, IP置为0xFFF0,即上电后默认执行的第一条指令位于[CS:IP]=0xFFFF0处的。而这个地址处存储的是BIOS。
BIOS 我们都知道计算机启动后会运行BIOS(basic input output system),进一步可能也知道BIOS存储在ROM中,当然也知道相对RAM,ROM掉电后信息不会丢失。计算机上电后,硬件将指令地址置为BIOS所在的0xFFFF0处,开始执行BIOS内容。
BIOS会先进行加电自检(POST, Power-On Self-Test),测试各项硬件是否正常等。
加电自检小插曲 我前一阵(2019年底)组装了一台AMD的电脑(AMD, YES!!!)。不考虑打游戏,只用来沉迷于学习,所以预算里没有考虑显卡。等所有零件到了之后迫不及待装好之后上电,然后显示器黑屏,主板上有一个标有VGA的LED红灯常亮。 想了一小会立即意识到,现在Intel的CPU基本都带了核显(大多数人觉得很鸡肋),但AMD的CPU并没有。BIOS加电自检挂这里过不去了。 后来没办法,买了一张二手的GTX960作为亮机卡。 BIOS中断 BIOS还会在内存中起始的4K处0x00000-0x003FF建立中断向量表,以及位于0x0E05B-0x0FFFE处对应的中断服务程序。
BIOS提供的中断服务可以查阅网上资料,参考资料中也提供了一份。我们会用到如下几个:
中断号 描述 int 10h 显示服务 int 13h 低级磁盘服务 int 19h 加电自检后载入操作系统 调用BIOS中断时,可能需要设置一些参数。比如int 10h中断用来在屏幕上显示信息,就需要指定显示的字符串内容,显示的颜色,位置等信息。中断信息如何设置,BIOS对此都有详细的说明。
加载bootsec BIOS加电自检后使用int 19h中断将软盘中的第一个扇区(512 byte)中的内容复制到内存0x7C00h处,然后开始执行。在Linux中,第1个扇区的内容被称为bootsec。
对于OS来说,我们可以认为计算机加电后直到将bootsec中的内容载入内存并开始执行,这些都是自动完成的,我们只需要关注从bootsec代码开始执行之后的部分就可以了。
bootsec之后的事情我们也可以大致设想一下:依靠bootsec这512个字节的代码,我们会继续读取软盘上更多的代码,然后依赖读入的代码完成更多的工作,直至整个系统运行起来。
一个简单的bootsec样例 CPU硬件设计师,BIOS系统开发等费了九牛二虎之力,终于在上电之后把OS自身最开始的512个字节的bootsec给运行起来了。作为“全村的希望”,当然是希望我们再接再励把OS运行起来的。但这里,我们不管这么多,先写一个hello world再说吧。
org 07c00h ; 告诉编译器程序加载到7c00处 ;----直接往显存中写数据 mov ax, 0xb800 mov gs, ax mov byte [gs:0x00],'h' mov byte [gs:0x02],'e' mov byte [gs:0x04],'l' mov byte [gs:0x06],'l' mov byte [gs:0x08],'o' times 510-($-$$) db 0 ; 填充剩下的空间,使生成的二进制代码恰好为512字节 dw 0xaa55 ; 结束标志 上图中,0xb800是显存开始的地址。显存中,每2个字节对应显示字符,第1个字节为字符对应ASCII码,第2个字节为字符对应的颜色。屏幕每行有80个字符,共24行。这里,我们我们只修改了屏幕左上角开始的前5个字符对应的ASCII码,所以在左上角显示出了hello5个字符。……
</div>
<p class="readmore"><a href="https://axiomaster.github.io/post/2021-11-17-os-boot/">阅读全文</a></p>
</article>
<ol class="page-navigator">
<li class="current">
<a href="https://axiomaster.github.io/">1</a>
</li>
<li >
<a href="https://axiomaster.github.io/page/2/">2</a>
</li>
<li >
<a href="https://axiomaster.github.io/page/3/">3</a>
</li>
<li >
<a href="https://axiomaster.github.io/page/4/">4</a>
</li>
<li >
<a href="https://axiomaster.github.io/page/5/">5</a>
</li>
<li >
<a href="https://axiomaster.github.io/page/6/">6</a>
</li>
<li >
<a href="https://axiomaster.github.io/page/7/">7</a>
</li>
<li >
<a href="https://axiomaster.github.io/page/8/">8</a>
</li>
<li class="next">
<a href="https://axiomaster.github.io/page/2/">下一页</a>
</li>
</ol>
</div>
<footer id="footer">
<div>
© 2022 <a href="https://axiomaster.github.io">Axiomaster's Site By </a>
</div>
<br />
<div>
<div class="github-badge">
<a href="https://gohugo.io/" target="_black" rel="nofollow"><span class="badge-subject">Powered by</span><span class="badge-value bg-blue">Hugo</span></a>
</div>
<div class="github-badge">
<a href="https://www.flysnow.org/" target="_black"><span class="badge-subject">Design by</span><span class="badge-value bg-brightgreen">飞雪无情</span></a>
</div>
<div class="github-badge">
<a href="https://github.com/flysnow-org/maupassant-hugo" target="_black"><span class="badge-subject">Theme</span><span class="badge-value bg-yellowgreen">Maupassant</span></a>
</div>
</div>
</footer>
<a id="rocket" href="#top"></a>
<script type="text/javascript" src='/js/totop.js?v=0.0.0' async=""></script>
</div>
<div id="secondary">
<section class="widget">
<form id="search" action='//www.google.com/search' method="get" accept-charset="utf-8" target="_blank" _lpchecked="1">
<input type="text" name="q" maxlength="20" placeholder="Search">
<input type="hidden" name="sitesearch" value="https://axiomaster.github.io">
<button type="submit" class="submit icon-search"></button>
</form>
</section>
<section class="widget">
<h3 class="widget-title">最近文章</h3>
<ul class="widget-list">
<li>
<a href="https://axiomaster.github.io/post/2022-01-08-c-language-addition/" title="c语言补充说明">c语言补充说明</a>
</li>
<li>
<a href="https://axiomaster.github.io/post/2022-01-08-interrupt/" title="中断">中断</a>
</li>
<li>
<a href="https://axiomaster.github.io/post/2022-01-01-c-language/" title="引入c语言">引入c语言</a>
</li>
<li>
<a href="https://axiomaster.github.io/post/2021-12-26-protected_mode-addition/" title="保护模式补充">保护模式补充</a>
</li>
<li>
<a href="https://axiomaster.github.io/post/2021-12-19-page/" title="内存分页">内存分页</a>
</li>
<li>
<a href="https://axiomaster.github.io/post/2021-12-18-protected_mode/" title="保护模式">保护模式</a>
</li>
<li>
<a href="https://axiomaster.github.io/post/2021-12-04-os-loader-addition/" title="确认软盘内容执行">确认软盘内容执行</a>
</li>
<li>
<a href="https://axiomaster.github.io/post/2021-11-22-os-loader/" title="读取并执行软盘内容">读取并执行软盘内容</a>
</li>
<li>
<a href="https://axiomaster.github.io/post/2021-11-21-os-boot-addition/" title="启动补充说明">启动补充说明</a>
</li>
<li>
<a href="https://axiomaster.github.io/post/2021-11-17-os-boot/" title="从上电到引导扇区">从上电到引导扇区</a>
</li>
</ul>
</section>
<section class="widget">
<h3 class="widget-title"><a href='/categories/'>分类</a></h3>
<ul class="widget-list">
</ul>
</section>
<section class="widget">
<h3 class="widget-title"><a href='/tags/'>标签</a></h3>
<div class="tagcloud">
<a href="https://axiomaster.github.io/tags/activity/">activity</a>
<a href="https://axiomaster.github.io/tags/algorithm/">algorithm</a>
<a href="https://axiomaster.github.io/tags/android/">android</a>
<a href="https://axiomaster.github.io/tags/aosp/">aosp</a>
<a href="https://axiomaster.github.io/tags/bazel/">bazel</a>
<a href="https://axiomaster.github.io/tags/bug/">bug</a>
<a href="https://axiomaster.github.io/tags/debug/">debug</a>
<a href="https://axiomaster.github.io/tags/debugger/">debugger</a>
<a href="https://axiomaster.github.io/tags/docker/">docker</a>
<a href="https://axiomaster.github.io/tags/golang/">golang</a>
<a href="https://axiomaster.github.io/tags/gradle/">Gradle</a>
<a href="https://axiomaster.github.io/tags/i/o/">I/O</a>
<a href="https://axiomaster.github.io/tags/idea%E6%8F%92%E4%BB%B6/">IDEA插件</a>
<a href="https://axiomaster.github.io/tags/java/">java</a>
<a href="https://axiomaster.github.io/tags/linux/">linux</a>
<a href="https://axiomaster.github.io/tags/ml/">ML</a>
<a href="https://axiomaster.github.io/tags/net/">net</a>
<a href="https://axiomaster.github.io/tags/nginx/">nginx</a>
<a href="https://axiomaster.github.io/tags/os/">OS</a>
<a href="https://axiomaster.github.io/tags/tensorflow/">Tensorflow</a>
<a href="https://axiomaster.github.io/tags/tool/">tool</a>
<a href="https://axiomaster.github.io/tags/view/">view</a>
<a href="https://axiomaster.github.io/tags/window/">window</a>
<a href="https://axiomaster.github.io/tags/zookeeper/">zookeeper</a>
<a href="https://axiomaster.github.io/tags/zygote/">zygote</a>
<a href="https://axiomaster.github.io/tags/%E5%86%85%E5%AD%98/">内存</a>
<a href="https://axiomaster.github.io/tags/%E5%90%90%E6%A7%BD/">吐槽</a>
<a href="https://axiomaster.github.io/tags/%E5%B7%A5%E5%85%B7/">工具</a>
<a href="https://axiomaster.github.io/tags/%E5%B9%B6%E5%8F%91/">并发</a>
<a href="https://axiomaster.github.io/tags/%E6%89%8B%E5%B7%A5/">手工</a>
<a href="https://axiomaster.github.io/tags/%E6%89%AF%E6%B7%A1/">扯淡</a>
<a href="https://axiomaster.github.io/tags/%E6%97%A5%E5%B8%B8/">日常</a>
<a href="https://axiomaster.github.io/tags/%E7%94%9F%E6%B4%BB/">生活</a>
<a href="https://axiomaster.github.io/tags/%E7%A5%9E%E5%A5%87%E7%9A%84bug/">神奇的bug</a>
<a href="https://axiomaster.github.io/tags/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/">设计模式</a>
<a href="https://axiomaster.github.io/tags/%E8%AF%81%E4%B9%A6/">证书</a>
<a href="https://axiomaster.github.io/tags/%E9%93%BE%E6%8E%A5/">链接</a>
</div>
</section>
<section class="widget">
<h3 class="widget-title">其它</h3>
<ul class="widget-list">
<li><a href="https://axiomaster.github.io/index.xml">文章 RSS</a></li>
</ul>
</section>
</div>
</div>
</div>
</div>
</body>
</html>