OS-lab1


上机

1.lab1-exam:

​ 在lab1课下分支的基础上,扩展printk函数,增加新的格式字符串"%[flags] [width] [length]R",从参数表中得到两个参数,两个参数当做“%[flags] [width] [length]d”输出,具体输出形式为"(参数1,参数2)“。例如printk(”%4R", 2023, 2023); 输出为"(2023,2023)"。只需要switch代码段增加一个case即可,类似两个case:d。记着输出完第一个参数,要把neg_flag归0。

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
case 'R':
if(long_flag) {
num = va_arg(ap, long int); \\第一个参数
nun = va_arg(ap, long int); \\第二个参数,记得声明 long nun
}
else {
num = va_arg(ap, int);
nun = va_arg(ap, int);
}
if(num <0){
num = -num;
neg_flag = 1;
}
char kuo1[] = "(\0";
char kuo2[] = ")\0";
char dou[] = ",\0";
out(data, kuo1, 1);
print_num(out, data, num, 10, neg_flag,width,ladjust,padc,0);
out(data, dou, 1);
neg_flag = 0; //输出完第一个参数,记得初始neg_flag
if(nun <0){
nun = -nun;
neg_flag = 1;
}
print_num(out, data, nun, 10, neg_flag,width,ladjust,padc,0);
out(data, kuo2, 1);
break;

2.la1-extra

​ 我们在lab1课下实现的printk是把格式字符串输出到终端,extra的目标是实现sprintf函数,把要输出的内容弄到缓冲区(就是到目标字符串buf),printk的原理为:调用vptintfmt解析fmt,真正的输出是把outputk函数(在vprintfmt传入output函数作为一个参数)。注意到vprintfmt第二个参数为NULL,在声明的时候第二个形参为void * data。第一个参数函数称为回调函数,第二个参数称为回调上下文,在printk并没有用到data。我们可以借助vprintfmt函数,第一个参数传入自定义的函数,第二个参数传入buf(当前缓冲区所在的位置)。我们要实现的自定义函数就是类似的outputk,只不过功能不是输出,而是把写入到当前缓冲区的位置,这时候你突然发现当前文件下memcpy函数已经实现了!!!为了可以一直更新data,我们只需要在所有需要缓冲的地方让data加上缓冲的字符串长度。还有一个小问题是,由于在vprintfmt函数中还调用了print_str, print_char, print_num在这三个函数中,为了把更新后的data传回vprintfmt,我把这三个函数的类型改为void*,然后返回值为更新后的data。需要注意在vprintfmt对data更新的时候,要先判断data是不是NULL,因为printk也会调用vprintfmt,但传入的data为NULL。其实要写的东西不多,仔细一点就好

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
int sprintf(char *buf, const char * fmt, ...){
void *start = buf;
va_list ap;
va_start(ap, fmt);
vprintfmt(memcpy2, buf, fmt,ap );
va_end(ap);
int i = 0;
while(*(char *)start!='\0'){
i++;
start++;
}
return i;
}



void *memcpy(void *dst, const void *src, size_t n) {
void *dstaddr = dst;
void *max = dst + n;

if (((u_long)src & 3) != ((u_long)dst & 3)) {
while (dst < max) {
*(char *)dst++ = *(char *)src++;
}
return dstaddr;
}

while (((u_long)dst & 3) && dst < max) {
*(char *)dst++ = *(char *)src++;
}

// copy machine words while possible
while (dst + 4 <= max) {
*(uint32_t *)dst = *(uint32_t *)src;
dst += 4;
src += 4;
}

// finish the remaining 0-3 bytes
while (dst < max) {
*(char *)dst++ = *(char *)src++;
}
return dstaddr;
}





void vprintfmt(fmt_callback_t out, void *data, const char *fmt, va_list ap) {
char c;
const char *s;
long num;

int width;
int long_flag; // output is long (rather than int)
int neg_flag; // output is negative
int ladjust; // output is left-aligned
char padc; // padding char

for (;;) {
/* scan for the next '%' */
/* Exercise 1.4: Your code here. (1/8) */
while(*fmt!='%'&&*fmt!='\0'){
out(data,fmt,1);
if(data!=NULL){
data++;
}
fmt++;
}
/* flush the string found so far */
/* Exercise 1.4: Your code here. (2/8) */

/* check "are we hitting the end?" */
/* Exercise 1.4: Your code here. (3/8) */
if(*fmt == '\0'){
if(data!=NULL){
*(char *)data = '\0';

}

break;
}
/* we found a '%' */
/* Exercise 1.4: Your code here. (4/8) */
fmt++;
ladjust = 0;
if(*fmt == '-'){
ladjust = 1;
fmt++;
}
/* check format flag */
/* Exercise 1.4: Your code here. (5/8) */
padc = ' ';
if (*fmt == '0'){
padc = '0';
fmt++;
}
/* get width */
/* Exercise 1.4: Your code here. (6/8) */
width = 0;
while(isDigit(*fmt)){
width *= 10;
width += cToD(*fmt);
fmt++;
}

/* check for long */
/* Exercise 1.4: Your code here. (7/8) */
long_flag = 0;
if(*fmt == 'l'){
long_flag = 1;
fmt++;
}

neg_flag = 0;
switch (*fmt) {
case 'b':
if (long_flag) {
num = va_arg(ap, long int);
} else {
num = va_arg(ap, int);
}
data = print_num(out, data, num, 2, 0, width, ladjust, padc, 0);
break;

case 'd':
case 'D':
if (long_flag) {
num = va_arg(ap, long int);
} else {
num = va_arg(ap, int);
}
if(num<0){
num = -num;
neg_flag = 1;
}
data = print_num(out,data,num,10,neg_flag,width,ladjust,padc,0);

/*
* Refer to other parts (case 'b', case 'o', etc.) and func 'print_num' to
* complete this part. Think the differences between case 'd' and the
* others. (hint: 'neg_flag').
*/
/* Exercise 1.4: Your code here. (8/8) */

break;

case 'o':
case 'O':
if (long_flag) {
num = va_arg(ap, long int);
} else {
num = va_arg(ap, int);
}
data = print_num(out, data, num, 8, 0, width, ladjust, padc, 0);
break;

case 'u':
case 'U':
if (long_flag) {
num = va_arg(ap, long int);
} else {
num = va_arg(ap, int);
}
data = print_num(out, data, num, 10, 0, width, ladjust, padc, 0);
break;

case 'x':
if (long_flag) {
num = va_arg(ap, long int);
} else {
num = va_arg(ap, int);
}
data = print_num(out, data, num, 16, 0, width, ladjust, padc, 0);
break;

case 'X':
if (long_flag) {
num = va_arg(ap, long int);
} else {
num = va_arg(ap, int);
}
data = print_num(out, data, num, 16, 0, width, ladjust, padc, 1);
break;

case 'c':
c = (char)va_arg(ap, int);
data = print_char(out, data, c, width, ladjust);
break;

case 's':
s = (char *)va_arg(ap, char *);
data = print_str(out, data, s, width, ladjust);
break;

case '\0':
fmt--;
break;

default:
/* output this char as it is */
out(data, fmt, 1);
if(data!=NULL){
data++;
}
}
fmt++;
}
}

lab1思考题


一、Thinking

thinking1.1

​ x86交叉编译链:解决编译链程序和目标程序运行环境不同的问题,如在x86环境上使用编译工具进行编译汇编链接,而生成的程序需要运行在ARM开发板上

​ readelf:显示可执行程序的elf文件信息

​ objdump:显示程序信息,函数汇编等,常用于调试

​ objdump -h **.o 可查看目标文件的结构和内容
​ objdump -S 反汇编 -d可以16进制显示

​ LD(link script):主要是用来描述输入文件中的节(section)是如何映射到输出文件的,并且控制输出文件的内存布局(memory layout);源代码会编译成为目标对象文件(object file),每个对象文件中包含一系列的段(section),为LD的输入文件。

thinking1.2

发现在编译hello和readelf时的编译选项并不同

1.static:使用自带的readelf工具发现readelf文件类型为 DYN(Position-Independent Executable file),地址独立可执行文件,这是linux的一种保护方式,可以使程序在任意地址装载。hello则为简单的可执行文件,地址固定。

2.-32m:在我们的环境中,32位的程序运行的速度会更快,加上-32m选项,编译链接生成32位可执行程序,但是我们自己的readelf只能读64位。但是x86自带交叉编译工具readelf则可以读32位。

thinking1.3

启动分为两过程,硬件启动和软件启动,硬件启动的入口地址是由硬件决定的,之后才是我们内核也即软件启动,将ELF加载到内存中,此时内核入口由我们自定义的链接器决定。两个入口的含义是不一样的。


二、实验难点

1.理解我们内核镜像的结构

我们的开始是在stage2初始化CPU和内核栈,之后便调到init中的mips_init 函数用于初始化内核,我们在lab1中完成的仅仅是打印

2.readelf的补充

由ELF的文件地址计算节头表的地址时需要用 binary(const void *)如果用ehdr,ehdr + off,得到的是ehdr偏移ehdr大小的地址

3.源码的阅读


三、实验感想

1.我们现在的mos非常简单,操作系统也非常抽象,在之后的实验中,必须要多读源码,在理解的基础上逐步实现我们的mos

2.对于mos的makefile并不太熟悉,课下要多下功夫,多读源码。实验的整体难度尚可。


OS-lab1
https://etherialize.github.io/2023/03/20/OSlab1/
作者
HZY
发布于
2023年3月20日
许可协议