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;
}

img

我们需要注意的一点是:需要在 #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;
}

img

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);
    }
  }
}

img

find (moderate)

这里主要涉及到了文件系统的相关。我们首先来看 user/ls.c 是如何工作的。我们就了解到如下的结构:

  • struct stat 可以通过 type 来获取类型。类型也就三个:文件/设备/文件夹。

img

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

img

因此我们可以:

  • 通过递归的方式来寻找对应的文件即可。(也就是提示里面说的。也很简单。
// 此处从路径中剥离出来当前文件夹下的文件。
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。

img

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);
}

img

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

make grade

img

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

posted @ 2026-09-03 20:50  木木ちゃん  阅读(10)  评论(0)    收藏  举报