Lab 1: Utilities
Lab: Xv6 and Unix Utilities
sleep (easy)
我们首先来修改 Makefile。它就在我们的主目录下。我们只需要增加一行:
@@ -195,6 +195,7 @@ UPROGS=\
$U/_logstress\
$U/_forphan\
$U/_dorphan\
+ $U/_sleep\
此后本实验中所有的新文件在编辑完成后都需要在 UPROGS 处增加一个 $U/_[fileBaseNameNoExtension] 即可。
fileBaseNameNoExtension: 例如 sleep.c => sleep
接下来我们来看 pause 是如何实现的系统调用,以及对应的接口实现。
// kernel/sysproc.c #L65-#L85
uint64
sys_pause(void)
{
int n;
uint ticks0;
argint(0, &n);
if(n < 0)
n = 0;
acquire(&tickslock);
ticks0 = ticks;
while(ticks - ticks0 < n){
if(killed(myproc())){
release(&tickslock);
return -1;
}
sleep(&ticks, &tickslock);
}
release(&tickslock);
return 0;
}
// user/user.h #L25
int pause(int);
这样我们参考其他文件就可以写出:
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char *argv[]) {
if (argc == 1 || argc > 2) {
fprintf(2, "usage: sleep [integer]\n");
exit(1);
}
int ticks = atoi(argv[1]);
pause(ticks);
return 0;
}

我们需要注意的一点是:需要在 #include "user/user.h" 前加上 #include "kernel/types.h". 具体可以参见 issues #452
sixfive (moderate)
sixfive 首先我们要拆解需求:
- 识别
-\r\t\n./作为我们的分隔符。 - 将分割出来的字符串转换成数字。
- 判断数字是否是 5 或者 6 的倍数。
对于第一个需求,我们在提示中可以看到:可以使用 strchr。我们首先先看一下 strchr 的实现,在 user/ulib.c 中:
char*
strchr(const char *s, char c)
{
for(; *s; s++)
if(*s == c)
return (char*)s;
return 0;
}
实现也很简单,其实就是判断 字符 c 是否存在于提供的字符串 s 中。在找到任意的一个匹配字符的时候,返回指向这个字符的指针。因此如果找到则返回指针,否则为 null。
这样也就又立刻印证了提示1中所说的:每次只读入一个字符。这样我们就需要通过 read 系统调用来一个个字符地读取,并且将它送入到 strchr 中来判断是否需要分割。如果不需要,我们则加到当前字符的末尾。否则我们将立刻将 buffer 转换成数字,判断是否可以打印。因此主逻辑就清晰了:
void sixfive(int fd) {
int n = -1;
Buffer *buffer = create_buffer(512);
char ch;
// 一个个地读入字符并且判断。
while ((n = read(fd, &ch, sizeof(ch))) > 0) {
if (strchr(" -\r\t\n./,", ch) == 0) {
append_buffer(buffer, ch);
} else {
append_buffer(buffer, '\0'); // 小心 '\0'。
print_sixfive(to_int_buffer(buffer));
}
}
if (n < 0) {
fprintf(2, "sixfive: read error\n");
exit(1);
}
// 注意最后收尾。当读到EOF的时候,我们会立刻离开 while 循环,因此需要特殊处理最后的 buffer。这里对应提示3.
if (buffer->pt != 0) {
append_buffer(buffer, '\0');
print_sixfive(to_int_buffer(buffer));
}
destroy_buffer(buffer);
}
如果觉得动态的 buffer 太麻烦,也可以直接用 char [512] 来写,做好边界处理即可。但是我们此处采用了动态的buffer来进行管理,写了一些简单的辅助函数:
// 管理 buffer 的主要结构,包括大小,当前的size,以及存储的数据结构。
struct Buffer {
int cap;
int pt;
char *buf;
};
typedef struct Buffer Buffer;
Buffer *create_buffer(int buf_len) {
Buffer *buffer = malloc(sizeof(Buffer));
if (buffer == 0) {
fprintf(2, "sixfive: malloc failed\n");
exit(1);
}
buffer->cap = buf_len;
buffer->pt = 0;
buffer->buf = malloc(sizeof(char) * buf_len);
if (buffer->buf == 0) {
fprintf(2, "sixfive: malloc failed\n");
exit(1);
}
memset(buffer->buf, 0, sizeof(char) * buf_len);
return buffer;
}
// 用于满的时候扩容。
void expand_buffer(Buffer *buffer) {
char *buf_exp = malloc(sizeof(char) * buffer->cap * 2);
if (buf_exp == 0) {
fprintf(2, "sixfive: malloc failed\n");
exit(1);
}
memset(buf_exp, 0, sizeof(char) * buffer->cap * 2);
if (buffer->pt > 0) {
memcpy(buf_exp, buffer->buf, sizeof(char) * (buffer->pt + 1));
}
buffer->cap *= 2;
free(buffer->buf);
buffer->buf = buf_exp;
}
int is_full_buffer(Buffer *buffer) { return buffer->pt == buffer->cap; }
// 这里就是直接append字符了。
void append_buffer(Buffer *buffer, char ch) {
if (is_full_buffer(buffer)) {
expand_buffer(buffer);
}
buffer->buf[buffer->pt] = ch;
buffer->pt++;
}
// 清空buffer但是不回收资源。
void reset_buffer(Buffer *buffer) {
memset(buffer->buf, 0, sizeof(char) * buffer->cap);
buffer->pt = 0;
}
// 转换成数字。
int to_int_buffer(Buffer *buffer) {
int number = atoi(buffer->buf);
reset_buffer(buffer);
return number;
}
// 这里才真正回收处理资源。
void destroy_buffer(Buffer *buffer) {
if (buffer) {
if (buffer->buf) {
free(buffer->buf);
}
free(buffer);
}
}
// 判断是否要进行打印。
void print_sixfive(int number) {
if (number != 0 && (number % 5 == 0 || number % 6 == 0)) {
printf("%d\n", number);
}
}
最后展示一下 main 函数就好了。
int main(int argc, char *argv[]) {
if (argc == 1) {
fprintf(2, "Usage: sixfive [files...]\n");
exit(1);
}
// open the file and associate with fd.
int i;
int fd = 0;
for (i = 1; i < argc; i++) {
char *filename = argv[i];
if ((fd = open(filename, O_RDONLY)) < 0) {
fprintf(2, "sixfive: cannot open %s\n", filename);
exit(1);
}
// after open the file, get the fd to read the file.
sixfive(fd);
close(fd);
}
return 0;
}

memdump (easy)
memdump 的主要思想其实就是 指针转换 / 解指针。
- 对于 i,要求将指针指向的连续四字节打印成 32 位数字,也就是:
(int *)data。 - 对于 p,要求将指针指向的连续八字节打印成 64 位数字,也就是:
(long *)data。 - 对于 h,要求将指针指向的连续二字节打印成 16 位数字,也就是:
(short *)data。 - 对于 c,要求将指针指向的连续一字节打印成一个ASCII字符,也就是:
*char。 - 对于 s,要求将指针指向的连续八字节视为一个指向字符串的指针,打印这个指针指向的字符串,也就是:
(char **)(*data) - 对于 S,要求将指针指向的连续字节视为一个带有 '\0' 终止符的字符串,也就是:
data。
需要注意,每次我们打印完成后,都需要将指针向前移动它需要的长度。由于 C 语言中没有引用,因此我们按引用传递需要传递原字符串的地址。这样我们就有:
void print_32_decimal(char **data_pt) {
char str[4];
memcpy(str, *data_pt, sizeof(str));
printf("%d\n", *(int *)(str));
*data_pt += 4;
}
void print_64_hexadecimal(char **data_pt) {
char str[8];
memcpy(str, *data_pt, sizeof(str));
long number = *(long *)str;
printf("%lx\n", number);
*data_pt += 8;
}
void print_16_decimal(char **data_pt) {
char str[2];
memcpy(str, *data_pt, sizeof(str));
short number = *(short *)str;
printf("%d\n", number);
*data_pt += 2;
}
void print_8_char(char **data_pt) {
char ch = **data_pt;
printf("%c\n", ch);
*data_pt += 1;
}
void print_string(char **data_pt) {
const char *str = *(const char **)(*data_pt);
printf("%s\n", str);
*data_pt += 8;
}
void print_null_term_string(char **data_pt) {
const char *str = *data_pt;
printf("%s\n", str);
*data_pt += strlen(str);
}
void
memdump(char *fmt, char *data)
{
// Your code here.
int i;
char *data_pt = data;
for (i = 0; fmt[i] != '\0'; i++) {
switch (fmt[i]) {
case 'i':
print_32_decimal(&data_pt);
break;
case 'p':
print_64_hexadecimal(&data_pt);
break;
case 'h':
print_16_decimal(&data_pt);
break;
case 'c':
print_8_char(&data_pt);
break;
case 's':
print_string(&data_pt);
break;
case 'S':
print_null_term_string(&data_pt);
break;
default:
fprintf(2, "Format error: \'iphcsS\'\n");
exit(1);
}
}
}

find (moderate)
这里主要涉及到了文件系统的相关。我们首先来看 user/ls.c 是如何工作的。我们就了解到如下的结构:
struct stat可以通过type来获取类型。类型也就三个:文件/设备/文件夹。

- 文件夹是连续排布的
struct dirent的文件,因此不断读取struct dirent来获取文件夹下面的对应文件。

因此我们可以:
- 通过递归的方式来寻找对应的文件即可。(也就是提示里面说的。也很简单。
// 此处从路径中剥离出来当前文件夹下的文件。
char *get_filename(char *path) {
char *p;
// Find first character after last slash.
for (p = path + strlen(path); p >= path && *p != '/'; p--)
;
p++;
return p;
}
void find(char *pathname, char *filename) {
int fd;
struct dirent de;
struct stat st;
if ((fd = open(pathname, O_RDONLY)) < 0) {
fprintf(2, "find: cannot open %s\n", pathname);
return;
}
if ((fstat(fd, &st) < 0)) {
fprintf(2, "find: cannot stat %s\n", pathname);
close(fd);
return;
}
// Now we need to dive into current path(file).
char *buf;
switch (st.type) {
case T_DEVICE:
case T_FILE:
// 寻找到文件之后就可以返回输出了。
if (strcmp(get_filename(pathname), filename) == 0) {
printf("%s\n", pathname);
close(fd);
return;
}
break;
case T_DIR:
while (read(fd, &de, sizeof(de)) == sizeof(de)) {
// 此处我们需要避开 . 和 .. 。
if (de.inum == 0 || strcmp(de.name, ".") == 0 ||
strcmp(de.name, "..") == 0) {
continue;
}
int len = strlen(pathname) + 1 + DIRSIZ + 1;
buf = (char *)malloc(sizeof(char) * len);
if (!buf) {
close(fd);
fprintf(2, "find: Buffer allocate failed\n");
exit(-1);
}
memset(buf, '\0', sizeof(char) * len);
strcpy(buf, pathname);
char *buf_pt = buf + strlen(buf);
*buf_pt++ = '/';
// memmove(dst, src, LEN) 用于搬运 src 到 src + LEN 的内存到 dst。这里其实就是 append。
memmove(buf_pt, de.name, DIRSIZ);
buf_pt[DIRSIZ] = '\0';
find(buf, filename);
if (buf) {
free(buf);
}
}
break;
}
close(fd);
}
需要注意 find 的最后一个测试需要完成 exec。所以会暂时出现 failed。

exec (moderate)
这个就更简单了。只需要注意一下 fork,在子程序内用 exec 执行命令,注意可执行程序也要包含在 argv 里面,然后将找到的文件和带有的 argv 拼在一起。最后可能需要再增加一个 NULL 作为 argv 的收尾。主要的结构其实就是:
if (fork() == 0) {
deal with argv...
exec(cmd, argv);
close(fd);
exit(0);
}
wait(0);
close(fd);
return;
这样我们有:
void find_exec(int args, char *pathname, char *filename, char *command, char **cmd_argvs) {
int fd;
struct dirent de;
struct stat st;
if ((fd = open(pathname, O_RDONLY)) < 0) {
fprintf(2, "find: cannot open %s\n", pathname);
return;
}
if ((fstat(fd, &st) < 0)) {
fprintf(2, "find: cannot stat %s\n", pathname);
close(fd);
return;
}
// Now we need to dive into current path(file).
char *buf;
switch (st.type) {
case T_DEVICE:
case T_FILE:
if (strcmp(get_filename(pathname), filename) == 0) {
// 主要的修改就在这里。
if (fork() == 0) {
close(fd);
cmd_argvs[args] = pathname;
cmd_argvs[args + 1] = 0;
exec(command, cmd_argvs);
exit(0);
}
wait(0);
close(fd);
return;
}
break;
case T_DIR:
while (read(fd, &de, sizeof(de)) == sizeof(de)) {
if (de.inum == 0 || strcmp(de.name, ".") == 0 ||
strcmp(de.name, "..") == 0) {
continue;
}
int len = strlen(pathname) + 1 + DIRSIZ + 1;
buf = (char *)malloc(sizeof(char) * len);
if (!buf) {
close(fd);
fprintf(2, "find: Buffer allocate failed\n");
exit(-1);
}
memset(buf, '\0', sizeof(char) * len);
strcpy(buf, pathname);
char *buf_pt = buf + strlen(buf);
*buf_pt++ = '/';
// copy the file name under directory, use memmove to append.
memmove(buf_pt, de.name, DIRSIZ);
buf_pt[DIRSIZ] = '\0';
find_exec(args, buf, filename, command, cmd_argvs);
if (buf) {
free(buf);
}
}
break;
}
close(fd);
}

最后注意补上一个 time.txt 即可。
make grade

本文没有采用AI辅助写作。
代码部分的测试文件采用了glm-5.3-flash进行辅助编写。

浙公网安备 33010602011771号