lab1
调试准备
我使用Docker的容器作为开发环境,课表是用的2021年的,然后为了方便使用VScode进行写代码以及调试。
配置
首先在xv6-labs-2021目录下创建.vscode文件夹,在这个文件夹下新建launch.json、tasks.json,内容分别如下:
// launch.json
{
"version": "0.2.0",
"configurations": [
{
"name": "xv6debug",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/kernel/kernel",
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"miDebuggerServerAddress": "127.0.0.1:25000", //见.gdbinit 中 target remote xxxx:xx
"miDebuggerPath": "/usr/bin/gdb-multiarch", // which gdb-multiarch
"MIMode": "gdb",
"preLaunchTask": "xv6build"
}
]
}
// tasks.json
{
"version": "2.0.0",
"options": { //指定make qemu的执行位置
"cwd": "${workspaceFolder}"
},
"tasks": [
{
"label": "xv6build",
"type": "shell",
"isBackground": true,
"command": "make qemu-gdb",
"problemMatcher": [
{
"pattern": [
{
"regexp": ".",
"file": 1,
"location": 2,
"message": 3
}
],
"background": {
"beginsPattern": ".*Now run 'gdb' in another window.",
"endsPattern": "."
}
}
],
"group": {
"kind": "build",
"isDefault": true
}
}
]
}
我在这个过程中出现了
Unexpected GDB output from command "-target-select remote 127.0.0.1:25000". Remote communication error. Target disconnected.: Connection reset by peer
的错误,然后去.gdbinit注释到target remote那行,但是因为make clean后会重新生成,所以直接去.gdbini.tmpl-riscv中注释。
set confirm off
set architecture riscv:rv64
# target remote 127.0.0.1:25000
symbol-file kernel/kernel
set disassemble-next-line auto
set riscv use-compressed-breakpoints yes
这样就可以正常按F5在vscode中进行调试了。
开始lab
C语言中的int main(int argc, char* argv[]) 的含义
- argc是命令参数个数。
- argv是字符指针,argv[0]代表可执行程序,argv[1]是第一个参数,类推。
sleep
根据提示,参考echo.c,grep.c,rm.c,使用atoi把参数转为int。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char* argv[])
{
if(argc<2)
{
fprintf(1, "usage: sleep <tick count>\n");
exit(1);
}
sleep(atoi(argv[1]));
exit(1);
}
之后再makefile中找到UPROGS添加一行*$U*/_sleep\,在终端输入make qemu
$sleep
usage: sleep <tick count>
$ sleep 10 //停顿1s
测试还可以直接用./grade--lab-util sleep(或其他作业名)
pingpong
这里要提前了解pipe/fork的用法。
大致意思就是fd[0]是读管道,fd[1]是写管道。
fork被调用一次,却能够返回两次,它可能有三种不同的返回值:
- 在父进程中,fork返回新创建子进程的进程ID;
- 在子进程中,fork返回0;
- 如果出现错误,fork返回一个负值;
#include "kernel/fd_types.h"
#include "kernel/types.h"
#include "user/user.h"
int main()
{
int pfd[2];
int cfd[2];
pipe(pfd);
pipe(cfd);
char buf[1];
if(fork() == 0)
{
close(pfd[1]); //close write
close(cfd[0]); //close read
read(pfd[0], buf, 1);
printf("%d: received p%sng\n", getpid(), buf);
write(cfd[1], "o", 1);
close(cfd[1]);
}
else if(fork() > 0)
{
close(pfd[0]);
close(cfd[1]);
write(pfd[1], "i", 1);
close(pfd[1]);
read(cfd[0], buf, 1);
printf("%d: received p%sng\n", getpid(), buf);
}
exit(0);
}
$ pingpong
6: received ping
5: received pong
prime
父线程将 2 ~ 35 数字输入pipe,fork的子进程如同一个递归操作,将第一个数字打印,并将其余不能被这个数字整除的输入到pipe,不断重复操作,直至最后一个数字。 最后打印出来的结果将都会是素数。这篇文章 解释了这个模型。需要注意的是xv6的fd有限,需要注意的是范围为0~15,所以需要将不用的fd close。
#include "kernel/types.h"
#include "user/user.h"
void filter(int out, int input)
{
close(input);
int div;
// 1. 输出第一个
if (read(out, &div, sizeof(int)) <= 0)
exit(0);
fprintf(2, "prime %d \n", div);
int p[2];
pipe(p);
// 2. 过滤,符合要求的发送到新的pipe
int num;
while (read(out, &num, sizeof(int)))
{
if (num % div != 0)
{
write(p[1], &num, sizeof(int));
}
}
close(out);
// 3. 让子进程继续重复操作
if (fork())
{
filter(p[0], p[1]);
return;
}
wait(0);
exit(0);
}
int main(int argc, char *argv[])
{
int p[2];
pipe(p);
if (fork())
{
filter(p[0], p[1]);
}
else
{
for (int i = 2; i <= 35; i++)
{
write(p[1], &i, sizeof(i));
}
wait(0);
exit(0);
}
return 0;
}
find
主体思路,参考ls.c,需要注意的是范围为0~15,所以需要将不用的fd close。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
char* fmtname(char *path) // 从路径中获取文件名
{
char *p;
for (p = path + strlen(path); p >= path && *p != '/'; p--); // 从后往前找到第一个'/'
return ++p;
}
void find(char* path, char* name) // 递归查找
{
char buf[512], *p;
int fd;
struct dirent de;
struct stat st;
if ((fd = open(path, 0)) < 0) // 打开文件失败
{
fprintf(2, "find: cannot open %s\n", path);
close(fd);
return;
}
if (fstat(fd, &st) < 0) // 获取文件信息失败
{
fprintf(2, "find: cannot stat %s\n", path);
close(fd);
return;
}
switch (st.type) // 判断文件类型
{
case T_FILE: // 文件
if(strcmp(fmtname(path), name) == 0)
{
printf("%s\n", path);
}
break;
case T_DIR: // 目录
if (strlen(path) + 1 + DIRSIZ + 1 > sizeof buf)
{
printf("find: path too long\n");
break;
}
strcpy(buf, path);
p = buf + strlen(buf);
*p++ = '/';
while (read(fd, &de, sizeof(de)) == sizeof(de)) // 读取目录下的文件
{
if (de.inum == 0) // 无效的文件
continue;
if(strcmp(de.name, ".") == 0 || strcmp(de.name, "..") == 0) // "." ".." 跳过
continue;
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;
if(stat(buf, &st) < 0)
{
printf("find: cannot stat %s\n", buf);
continue;
}
find(buf, name);
}
break;
}
close(fd);
}
int main(int argc, char* argv[])
{
if(argc != 3)
{
fprintf(2, "usage: find <path> <name>\n");
exit(1);
}
find(argv[1], argv[2]);
exit(0);
}
xargs
xargs就是在输出前加上额外信息
> xargs echo bye
in:hello too
out:bye hello too
#include "kernel/types.h"
#include "user/user.h"
int main(int argc, char* argv[])
{
char buf2[512]; // 读取的字符
char buf[32][32];
char *pass[32];
for(int i=0;i<32;++i)
{
pass[i] = buf[i];
}
int i;
for(i=1;i<argc;++i)
{
strcpy(buf[i-1], argv[i]);
}
int n;
while ((n = read(0, buf2, sizeof(buf2))) > 0) // 从标准输入读取
{
int pos = argc - 1; // 从第一个参数开始
char *c = buf[pos]; // 指向第一个参数的首地址
for (char *p = buf2; *p; p++)
{
if (*p == ' ' || *p == '\n') // 遇到空格或者换行符
{
*c = '\0';
pos++;
c = buf[pos];
}
else
*c++ = *p;
}
*c = '\0';
pos++;
pass[pos] = 0;
if (fork())
{
wait(0);
}
else
exec(pass[0], pass);
}
if (n < 0)
{
printf("xargs: read error\n");
exit(0);
}
exit(0);
}
创建time.txt 中随便加一个数字即可。