OS_ucore

1、操作系统要干啥?哪些操作/任务不属于操作系统的管理范畴?

2、工具使用

阅读源码工具:understannd

源码文档自动生成工具:doxygen

虚拟运行环境:qemu

Lab0

1、了解汇编

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
int count = 1;
int value = 1;
int buf[10];

void main(){
asm(
"cld \n\t"
"rep \n\t"
"stosl"
:
: "c" (count), "a"(value), "D" (buf[0])
:
);
}

/*
这段 C 代码给出了一个简单的函数,其中包括了一段内联汇编。
该代码试图展示如何使用 `rep stos` 指令来填充一个缓冲区,但存在一些问题和错误。
我会逐步解释代码,然后提出建议的更正。

1. 变量定义:
```c
int count = 1;
int value = 1;
int buf[10];
```
你定义了三个变量:一个 `count` 用于记录要重复的次数,
一个 `value` 用于指定要存储的值,
以及一个 `buf` 数组用于存储数据。

2. 主函数和内联汇编:
```c
void main(){
asm(
"cld \n\t"
"rep \n\t"
"stsol"
:
: "c" (count), "a"(value), "D" (buf[0])
);
}
```

- `cld`: 清除方向标志(Direction Flag)。这确保了字符串操作(如 `stos`)从低地址向高地址操作。
- `rep stos`: 这应该是一个组合的指令,意思是"重复存储"。
你试图使用这个指令将 `value` 的内容存储到 `buf` 指定的内存位置,
并根据 `count` 的值重复该操作。但这里有个小错误: 你写的是 `"stsol"`,它是不正确的。应该是 `"stosl"`。
- 输出约束(Output operands): 这里没有列出输出操作数,所以你只有一个冒号。
- 输入约束(Input operands): `"c" (count), "a"(value), "D" (buf[0])`,这里:
* `"c" (count)`:把 `count` 放入 `ECX` 寄存器(这是 `rep` 指令使用的计数寄存器)。
* `"a"(value)`:把 `value` 放入 `EAX` 寄存器(这是 `stosl` 指令使用的源数据寄存器)。
* `"D" (buf[0])`:把 `buf` 的首地址放入 `EDI` 寄存器(这是 `stosl` 指令使用的目标地址寄存器)。
但这样写是不准确的,应该是 `"D" (buf)`。

修正后的代码如下:

```c
int count = 1;
int value = 1;
int buf[10];

void main(){
asm(
"cld \n\t"
"rep \n\t"
"stosl"
:
: "c" (count), "a"(value), "D" (buf)
);
}
```

注意:使用内联汇编通常是平台和编译器特定的。
上述代码假定你正在使用 GCC 或其它支持 GNU 汇编语法的编译器,并且在一个 x86 架构的机器上运行。
*/

/*
在 GCC 的内联汇编中,第三个冒号后面的部分是用于指定被汇编代码修改的寄存器列表的,也叫做“Clobber List”。

以下是 `asm` 的基本格式:

asm ( assembly code
: output operands // optional
: input operands // optional
: list of clobbered registers // optional
);


1. Assembly Code: 包含汇编指令的字符串。
2. Output Operands: 输出操作数列表。
3. Input Operands: 输入操作数列表。
4. Clobber List: 表示汇编指令可能会更改的寄存器列表,
这告诉编译器这些寄存器的值可能会被修改,所以不要依赖它们的原始值。

在你的例子中,你使用了 `"c" (count), "a"(value), "D" (buf[0])` 为汇编指令提供输入操作数,
但你并没有指定输出操作数,也没有列出任何可能被修改的寄存器。

虽然在此例中,你知道 `ECX`, `EAX`, 和 `EDI` 寄存器会被修改,
但在许多情况下,列出clobbered registers是很有必要的,因为它可以帮助编译器更好地优化生成的代码。

所以,理论上,你的代码应该像这样:

int count = 1;
int value = 1;
int buf[10];

void main(){
asm(
"cld \n\t"
"rep \n\t"
"stosl"
:
: "c" (count), "a"(value), "D" (buf)
: "ecx", "eax", "edi"
);
}


这里,`"ecx", "eax", "edi"` 是clobber list,告诉编译器这些寄存器的值已经被汇编代码修改了。
*/

3、掌握指针和类型转换相关的C编程

实验源码如下

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
#include <stdio.h>

#define STS_IG32 0xE
#define STS_TG32 0xF

typedef unsigned uint32_t;

#define SETGATE(gate, istrap, sel, off, dpl){\
(gate).gd_off_15_0 = (uint32_t)(off) & 0xffff;\
(gate).gd_ss = (sel);\
(gate).gd_args = 0;\
(gate).gd_rsv1 = 0;\
(gate).gd_type = (istrap) ? STS_TG32 : STS_IG32;\
(gate).gd_s = 0;\
(gate).gd_dpl = (dpl);\
(gate).gd_p = 1;\
(gate).gd_off_31_16 = (uint32_t)(off) >> 16;\
}

struct gatedesc{
unsigned gd_off_15_0: 16;
unsigned gd_ss: 16;
unsigned gd_args: 5;
unsigned gd_rsv1: 3;
unsigned gd_type: 4;
unsigned gd_s: 1;
unsigned gd_dpl: 2;
unsigned gd_p: 1;
unsigned gd_off_31_16: 16;
};

int main(void){
unsigned before;
unsigned intr;
unsigned after;
struct gatedesc gintr;

intr = 8;
before = after = 0;
gintr = *((struct gatedesc*)&intr);
SETGATE(gintr, 0, 1, 2, 3);
intr = *((unsigned*)&(gintr));
printf("intr is 0x%x\n", intr);
printf("gintr is 0x%llx\n", gintr);
return 0;
}

自己根据输出即可推出来是怎么做的。注意,gintr等于0xee0000010002中,最高16位全部等于0没有展示出来。而%x只能展示32位,所以intr的输出结果为:0x10002

一个被系统认为是符合规范的硬盘主引导扇区的特征是什么?

硬盘主引导扇区(Master Boot Record,MBR)是位于硬盘第一个扇区(通常是逻辑地址0号扇区)的512字节的数据结构,用于引导计算机操作系统。一个符合规范的硬盘主引导扇区应该包含以下几个特征:

  1. 引导代码(Boot Code): 前446个字节用于存储引导代码,这是引导加载程序(Boot Loader)的代码,负责加载操作系统。这段代码必须是有效的汇编代码,能够启动计算机。

  2. 分区表(Partition Table): 接下来的64字节用于存储分区表,每个分区表项占16字节。一个硬盘可以分为最多4个主分区,每个分区表项描述一个分区的起始位置、大小和分区类型等信息。

  3. 签名字节(Signature): 最后的两个字节(0x55AA)是MBR的签名,标志这个扇区是有效的MBR扇区。这个签名是个小端字节序的16位值,它告诉操作系统这个扇区包含了引导信息,是一个有效的MBR。

总结起来,一个被系统认为是符合规范的硬盘主引导扇区应该包含引导代码、分区表和签名字节。这些特征是为了确保引导加载程序可以正确地读取分区信息,从而启动操作系统。如果这些特征中的任何一个缺失或损坏,可能导致系统无法正确引导。

引导扇区代码(tools/sign.c

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
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <sys/stat.h>
int main(int argc, char *argv[]) {
struct stat st;
if(argc != 3) {
fprintf(stderr, "Usage: <input filename> <output filename>\n");
return -1;
}
if (stat(argv[1], &st) != 0) {
fprintf(stderr, "Error opening file '%s': %s\n", argv[1], strerror(errno));
return-1;
}
printf("'%s' size: %lld bytes\n", argv[1], (long long)st.st_size);
if (st.st_size > 510) {
fprintf(stderr, "%lld >> 510!!\n", (long long)st.st_size);
return-1;
}
char buf[512];
memset(buf, 0, sizeof(buf));
FILE*ifp=fopen(argv[1], "rb");
int size = fread(buf, 1,st.st_size, ifp);
if (size != st.st_size) {
fprintf(stderr, "read '%s' error, size is %d.\n", argv[1], size);
return-1;
}
fclose(ifp);
buf[510]= 0x55;
buf[511] =0xAA;
FILE*ofp = fopen(argv[2], "wb+");
size = fwrite(buf, 1, 512, ofp);
if (size != 512) {
fprintf(stderr, "write '%s' error, size is %d.\n", argv[2], size);
return -1;
}
fclose(ofp);
printf("build 512 bytes boot sector: '%s' success!\n", argv[2]);
return 0;
}

/*
注释
这是一个简单的C程序,用于生成一个包含引导代码的512字节的引导扇区(Boot Sector)。这样的引导扇区通常用于创建可引导的存储介质,例如硬盘、U盘等。以下是程序的主要功能和流程:

1. 使用 `stat` 函数获取指定输入文件(`argv[1]`)的信息,主要是文件大小。

2. 打印输入文件的大小。

3. 检查文件大小是否超过了510字节。如果超过,打印错误信息并退出程序。

4. 读取输入文件的内容(最多510字节),将内容存储在一个大小为512字节的缓冲区中。

5. 在缓冲区的倒数第二个字节(第510字节)写入0x55,倒数第一个字节(第511字节)写入0xAA。这是引导扇区的标志,用于告诉计算机系统这是一个有效的引导扇区。

6. 打开输出文件(`argv[2]`),将修改后的缓冲区写入文件。

7. 打印成功信息并返回0。

这个程序的目的是创建一个符合标准的引导扇区,其中包含引导代码和标志字节。请注意,生成的引导扇区在实际使用时需要按照引导扇区的规范进行进一步配置,以确保它能够正确地引导计算机系统。

以下是程序的一些可能的改进和注意事项:

- 添加更多的错误检查,例如检查文件是否成功打开。
- 考虑处理更多的边界情况,例如文件是否为空。
- 考虑使用二进制文件读写模式("rb" 和 "wb+")而不是文本模式。
- 考虑使用 `perror` 函数来打印与 `fopen`、`fread`、`fwrite` 相关的错误信息。
- 考虑处理大端和小端字节序的问题,特别是当涉及到二进制文件时。
*/