文章目录
- 一、写在前面
- 二、整体参考:
- 三、环境搭建
- 1.镜像获取:
- 2.容器创建:
- 3.编译:
- 4.浅浅尝试:
- 5.VSCode远程编译:
- bug1️⃣在xv6-labs-2021目录下创建.vscode文件夹,在这个文件夹下新建launch.json、tasks.json
- bug2️⃣VSCode上安装Docker插件、Remote-SSH和Remote Explorer。然后点击连接已有容器(很简单自己摸索熟悉下功能也挺好)
- bug3️⃣关于报错“Configured debug type 'cppdbg' is not supported.”。需要在远程容器中下载C/C++拓展,但是在线下载特别慢,推荐离线下载。[离线拓展](https://blog.csdn.net/L5887876989/article/details/131499664)
- bug4️⃣关于报错127.0.0.1:25000: Connection timed out,全网唯一!
- 四、实验开始 lab01 unix工具
docker run --cap-add=SYS_PTRACE --security-opt seccomp=unconfined -it -p 1234:22 --platform linux/amd64 -v "$PWD:/csapp" --name=csapp centos /bin/bash
(加了个端口映射,不用拉镜像,直接运行此命令就行)
2.bug: make qemu的时候报错rosetta error: Unimplemented syscall number 282
解决:
点击顶部菜单栏中的 Docker > Preferences,进入 Docker 设置页面。
在设置页面的左侧导航栏中,点击 “General”。
在右侧窗口中,取消选中 “Use Rosetta for x86/amd64 emulation on Apple Silicon” 选项。
关闭设置页面,并重新运行 make qemu-nox 命令。
一、写在前面
好吧,我刚刚看完第一个讲座 B站课程双语 ,感觉比我一开始就搭建环境更有方向。
前面划掉的文字应该也可以看见一个arm架构小白的混乱吧。嘤嘤嘤我啥也没有,只有勇气。
建议看讲义->刷习题->听讲座的顺序
二、整体参考:
大佬写得很精简的博客
官网的课程安排
讲义中文版
极速吸收! 课程讲座翻译文档
三、环境搭建
1.镜像获取:
docker pull linxi177229/mit6.s081:latest
2.容器创建:
docker run --name mit6.s081 linxi177229/mit6.s081 /bin/bash
(也可以使用- v参数挂载到主机文件的,但咱们这个后续采用图形化界面调试,不许挂了哈)
3.编译:
cd xv6-labs-2020
git checkout util
make qemu
4.浅浅尝试:
make CPUS=1 qemu-gdb # 第一个终端
gdb-multiarch # 新开一个终端嗷 宝子
5.VSCode远程编译:
bug1️⃣在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
}
}
]
}
bug2️⃣VSCode上安装Docker插件、Remote-SSH和Remote Explorer。然后点击连接已有容器(很简单自己摸索熟悉下功能也挺好)
bug3️⃣关于报错“Configured debug type ‘cppdbg’ is not supported.”。需要在远程容器中下载C/C++拓展,但是在线下载特别慢,推荐离线下载。离线拓展
bug4️⃣关于报错127.0.0.1:25000: Connection timed out,全网唯一!
虽然这个bug改得我是目眦尽裂咬牙切齿,但是心平气和长命百岁。
我还一直以为是我的防火墙给那个墙掉了,呜呜这个远程连接,我从六月份初从公司请假就卡在那里了,放假回来改改改改到现在。
解决办法是:多理解项目,咱们需要移动两个终端,一个终端要运行gdb-multiarch,而咱们这一个终端就负责图形化界面的调试。
最后来张项目第一步:
然后,请继续拿出眼睛,看看咱们的第0章。好痛苦啊,但是这是摸鱼最佳方式!
当天下午就复现了bug,我真的会谢,我不知道什么原因总是超时。
然后不使用远图形界面也超时,后面我百般摸索发现是等的时间太长了,所以使用vs code的时候就超时了。
不管了,先上手再说,因为我可以使用土土的方式验证(直接make qemu),学习新思想,没有什么可以阻挡。
四、实验开始 lab01 unix工具
1️⃣ sleep n
在xv6中实现UNIX程序sleep;您的sleep程序应该暂停用户指定数量的时钟。一个时钟是由xv6内核定义的时间概念,即来自计时器芯片的两个中断之间的时间。您的解决方案应该在user/sleep.c文件中实现。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char **argv){
if(argc!=2){
fprintf(2,"usage:sleep number");
}
int n=atoi(argv[1]);//命令行只接受字符,字符转的整型
sleep(n); //system call
exit(0);
}
2️⃣pingpong
编写一个程序,使用 UNIX 系统调用通过一对管道在两个进程之间“乒乓”一个字节,每个方向一个。父进程应向子进程发送一个字节;子进程应打印“: received ping”,其中 是其进程 ID,将管道上的字节写入父进程,然后退出;父进程应从子进程读取该字节,打印“: received pong”,然后退出。您的解决方案应位于文件user/pingpong.c中。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#define stdout 1
#define stderr 2
#define W 1
#define R 0
int main(void){
int child_p[2],father_p[2];
pipe(child_p);
pipe(father_p);
int p=fork();//为啥要单列,因为每调一次就是一个新进程
if (p< 0) {
fprintf(stderr, "fork执行错误,pid小于0!\n");
}
else if(p== 0) { //child 先读再写
close(father_p[W]);
close(child_p[R]);
char buf[2];
read(father_p[R],buf,sizeof(buf));
printf("pid:[%d] received ping:[%s]\n",getpid(),buf);
write(child_p[W], "o", sizeof("o"));
close(child_p[W]);
}
else { //father 先写再读
close(father_p[R]);
close(child_p[W]);
char buf[2];
write(father_p[W],"o",sizeof("o"));
close(father_p[W]);
read(child_p[R], buf, sizeof(buf));
printf("pid:[%d] received pong:[%s]\n",getpid(),buf);
}
exit(0);
}
3️⃣primes
使用管道编写素数筛的并发版本。这个想法来自 Unix 管道的发明者 Doug McIlroy。本页中间的图片 和周围的文字解释了如何做到这一点。您的解决方案应该在文件user/primes.c中。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#define stdin 0
#define stdout 1
#define stderr 2
#define W 1
#define R 0
// 使用多进程和管道,每一个进程作为一个 stage,筛掉某个素数的所有倍数(筛法)。
//值得注意的是close的对象:之前用过的,前面创建不用的
void handlePrime(int *left)
{
int prime;
read(left[R],&prime,sizeof(prime));
if(prime==-1){
exit(0);
}
printf("primes are %d\n",prime);
int right[2];
pipe(right);
if(fork()==0){
close(right[W]);
close(left[R]);
handlePrime(right);
}
else {
int buf;
close(right[R]);
while (read(left[R],&buf,sizeof(buf))&&buf!=-1)
{
if(buf%prime!=0)
write(right[W],&buf,sizeof(buf));
}
buf=-1;
write(right[W],&buf,sizeof(buf));
wait(0);
exit(0);
}
}
void main(int argc, char *argv[])
{
int p[2];
pipe(p);
if(fork()==0){
close(p[W]);
handlePrime(p);
exit(0);
}
else {
close(p[R]);
int i;
for(i=2;i<=35;i++)
write(p[W],&i,sizeof(i));
i=-1;
write(p[W],&i,sizeof(i));
wait(0);
exit(0);
}
}
4️⃣find
编写一个简单版本的 UNIX find 程序:查找目录树中具有特定名称的所有文件。您的解决方案应位于文件user/find.c中。
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
void find(char *path,char *target)
{
char buf[512], *p;
int fd;
struct dirent de;
struct stat st;
if((fd = open(path, 0)) < 0){
fprintf(2, "ls: cannot open %s\n", path);
return;
}
if(fstat(fd, &st) < 0){
fprintf(2, "ls: cannot stat %s\n", path);
close(fd);
return;
}
switch(st.type){
case T_FILE:
//printf("%s %d %d %l\n", fmtname(path), st.type, st.ino, st.size);
// 如果文件名结尾匹配 `/target`,则视为匹配
if(strcmp(path+strlen(path)-strlen(target), target) == 0)
printf("%s\n", path);
break;
case T_DIR:
if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
printf("ls: 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;
memmove(p, de.name, DIRSIZ);
p[DIRSIZ] = 0;
if(stat(buf, &st) < 0){
printf("ls: cannot stat %s\n", buf);
continue;
}
if(strcmp(buf+strlen(buf)-2, "/.") != 0 && strcmp(buf+strlen(buf)-3, "/..") != 0) {
find(buf, target); // 递归查找
}
//printf("%s %d %d %d\n", fmtname(buf), st.type, st.ino, st.size);
}
break;
}
close(fd);
}
int main(int argc, char *argv[])
{
if(argc < 3){//find 查找处 查找目标
exit(0);
}
char target[512];
target[0]='/';
strcpy(target+1,argv[2]);
find(argv[1],target);
exit(0);
}
5️⃣xargs:编写 UNIX xargs 程序的简单版本:从标准输入读取行并对每行运行一个命令,并将行作为参数提供给命令。您的解决方案应位于文件user/xargs.c中。
//涉及到很多指针
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"
// 带参数列表,执行某个程序
void run(char *program, char **args) {
if(fork() == 0) { // child exec
exec(program, args);
exit(0);
}
return; // parent return
}
int main(int argc, char *argv[]){
char buf[2048]; // 读入时使用的内存池
char *p = buf, *last_p = buf; // 当前参数的结束、开始指针
char *argsbuf[128]; // 全部参数列表(一个指针数组,每个指针可以指向一个字符串),包含 argv 传进来的参数和 stdin 读入的参数
char **args = argsbuf; // 将 args 指针指向 argsbuf 数组的首地址;指向 argsbuf 中第一个从 stdin 读入的参数
for(int i=1;i<argc;i++) {
// 将 argv 提供的参数加入到最终的参数列表中
*args = argv[i];
args++;
}
char **pa = args; // 开始读入参数
while(read(0, p, 1) != 0) {//命令行读入,标准输入
if(*p == ' ' || *p == '\n') {
// 读入一个参数完成
*p = '\0'; // 将空格替换为 \0 分割开各个参数,这样可以直接使用内存池中的字符串作为参数字符串
// 而不用额外开辟空间
*(pa++) = last_p;
last_p = p+1;
if(*p == '\n') {
// 读入一行完成
*pa = 0; // 参数列表末尾用 null 标识列表结束
run(argv[1], argsbuf); // 执行最后一行指令
pa = args; // 重置读入参数指针,准备读入下一行
}
}
p++;
}
if(pa != args) { // 如果最后一行不是空行
// 收尾最后一个参数
*p = '\0';
*(pa++) = last_p;
// 收尾最后一行
*pa = 0; // 参数列表末尾用 null 标识列表结束
// 执行最后一行指令
run(argv[1], argsbuf);
}
while(wait(0) != -1) {}; // 循环等待所有子进程完成,每一次 wait(0) 等待一个
exit(0);
}