全网最最实用--基于Mac ARM 芯片实现操作系统MIT 6.S081-lab1


1.环境搭建:
##### 整体参考https://zhuanlan.zhihu.com/p/166413604
##### 1.1工具链参考:https://zhuanlan.zhihu.com/p/534634352
1.其中命令换成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 应用程序。
点击顶部菜单栏中的 Docker > Preferences,进入 Docker 设置页面。
在设置页面的左侧导航栏中,点击 “General”。
在右侧窗口中,取消选中 “Use Rosetta for x86/amd64 emulation on Apple Silicon” 选项。
关闭设置页面,并重新运行 make qemu-nox 命令。

##### 1.2 编辑器使用Clion
##### 1.3开启远程调试参考https://blog.csdn.net/violajie/article/details/127318710
发现我容器的端口映射最开始在创建的时候没有设定,原来我打算更新容器配置;后来找遍方法发现不如重来。

一、写在前面

好吧,我刚刚看完第一个讲座 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);
}
  • 0
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值