xv6---Lab1: Xv6 and Unix utilities

news2025/7/17 9:11:50

目录

参考资料:

1.1进程和内存

1.2 I/O 和文件描述符

1.3管道

源码:

调试环境搭建

sleep

PingPong

primes 

find

xargs


参考资料:

Lab: Xv6 and Unix utilities

xv6-book翻译(自用)第一章 - 知乎

1.1进程和内存

  • 一个xv6进程由两部分组成,一部分是用户内存空间(指令,数据,栈),另一部分是仅对内核可见的进程状态
  • 可通过fork系统调用,创建一个新的进程。
  • exec将加载文件内存的镜像来覆盖调用exec进程的内存。
  • shell是通过fork创建一个子进程,然后子进程不停的调用用户输入的命令,然后执行exec执行。

1.2 I/O 和文件描述符

  • fd表示一个可被内核管理的对象
  • 每个进程有一张表,而xv6内核就是以fd作为表的索引,每个进程都有以0开始的文件描述符空间。
  • 按照惯例0是stdin,,1是stdout,2是stderr。 而shell任何时候都会打开3个fd,是console的默认文件描述符。
  • fork和exec的分开实现,有利于调用shell在fork之后创建子进程的时候去重定向I/O
  • dup复制一个已有的fd,返回指向同一个I/o对象的描述符,比如下面的例子可打印hello world, 在write(fd, "world\n", 6);的时候,依旧保留之前的偏移。
 fd = dup(1);
 write(1, "hello", 6);
 write(fd, "world\n", 6);

1.3管道

  • 摘自:https://blog.csdn.net/skyroben/article/details/71513385

(0)通过int pipe(int fd[2])创建管道,fd参数返回两个文件描述符,fd[0]指向管道的读端,fd[1]           指向管道的写端。fd[1]的输出是fd[0]的输入。

(1)父进程创建管道,得到两个⽂件描述符指向管道的两端

(2)父进程fork出子进程,⼦进程也有两个⽂件描述符指向同⼀管道。

(3)父进程关闭fd[0],子进程关闭fd[1],即⽗进程关闭管道读端,⼦进程关闭管道写端(因为           管道只支持单向通信)。⽗进程可以往管道⾥写,⼦进程可以从管道⾥读,管道是⽤环形           队列实现的,数据从写端流⼊从读端流出,这样就实现了进程间通信。
这里写图片描述

  • 管道是一个小的内核缓冲区,以文件描述符对的形式提供给进程,两个fd一个用于写操作,一个用于读操作。比如下面的例子实现命令wc
int p[2];
char *argv[2];
argv[0] = "wc";
argv[1] = 0;
pipe(p);

if(fork() == 0) {
    close(0);
    dup(p[0]);   将p[0]复制到fd 0上(stdin)
    close(p[0]);
    close(p[1]);  关闭stdin和stdout
    exec("/bin/wc", argv);   2.读取stdin的输入:执行wc 0, 读"hello world\n"得到 1 2 12
} else {
    write(p[1], "hello world\n", 12);  1.往管道的输出端口写入 "hello world\n"字符
    close(p[0]);
    close(p[1]);
}

源码:

git clone git://g.csail.mit.edu/xv6-labs-2020

git checkout util

调试环境搭建

xv6 2020版使用gdb调试debug的方法_Ayka的博客-CSDN博客_gdb调试 xv6

qemu-gdb debug指南之can not access memory解决! - 知乎

  • 调试内核

gdb-multiarch kernel/kernel
(gdb)set architecture riscv:rv64
(gdb)target remote localhost:26000

  • 调试某个程序:

gdb-multiarch

(gdb) file user/_xargs
(gdb) b main  设置断点在 user/_xargs的main

(gdb) b 24 设置断点在 user/_xargs的24行
(gdb) c 程序跑起来

(gdb) n 执行下一步不会进入函数

(gdb) s 执行下一步会进入函数

sleep

  • 源文档:https://pdos.csail.mit.edu/6.828/2021/labs/util.html
  • 修改Makefile:在UPROGS添加$U/_sleep\   && 添加/user/sleep.c文件
  • 观察make qemu的过程--->生成可执行程序_sleep

{ #编译sleep.c生成sleep.o }
riscv64-linux-gnu-gcc -c -o user/sleep.o user/sleep.c
{
#riscv64-linux-gnu-ld --help可知用法
#对应makefile的
_%: %.o $(ULIB)
    #通过linker script user/user.ld生成user/_sleep, 依赖xxx.o文件
    $(LD) $(LDFLAGS) -T $U/user.ld -o $@ $^
    $(OBJDUMP) -S $@ > $*.asm
    $(OBJDUMP) -t $@ | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$$/d' > $*.sym
}
riscv64-linux-gnu-ld -z max-page-size=4096 -T user/user.ld -o user/_sleep user/sleep.o user/ulib.o user/usys.o user/printf.o user/umalloc.o
riscv64-linux-gnu-objdump -S user/_sleep > user/sleep.asm
riscv64-linux-gnu-objdump -t user/_sleep | sed '1,/SYMBOL TABLE/d; s/ .* / /; /^$/d' > user/sleep.sym
{
  把生成的可执行文件导入文件系统镜像fs.img
  对应makefile的:mkfs/mkfs fs.img README $(UPROGS)
}
mkfs/mkfs fs.img README user/_cat user/_echo user/_forktest user/_grep user/_init user/_kill user/_ln user/_ls user/_mkdir user/_rm user/_sh user/_stressfs user/_usertests user/_grind user/_wc user/_zombie user/_sleep

  • 了解sleep系统调用的流程

/user/usys.S文件为sleep函数的实现,调用了/kernel/定义在user/user.h的int sleep(int);
sleep.c中的sleep--->/user/usys.S的调用--->调用/kernel/syscall.c的syscall函数--->调用到/kernel/sysproc.c的sys_sleep函数

  • 代码
#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 time\n");
      exit(1);
  } else {
      int time = atoi(argv[1]);
      sleep(time);
      exit(0);
  }
}
  • test
a123@ubuntu:~/Public/xv6-labs-2020$ ./grade-lab-util sleep 2
make: 'kernel/kernel' is up to date.
== Test sleep, no arguments == sleep, no arguments: OK (1.9s) 
== Test sleep, returns == sleep, returns: OK (1.6s) 
== Test sleep, makes syscall == sleep, makes syscall: OK (1.2s) 
a123@ubuntu:~/Public/xv6-labs-2020$ 

PingPong

  • 要求:
  1. 父进程通过pipe发送消息ping给子进程。
  2. 子进程收到后:1.打印pid+收到的pipe消息 2.发送pong消息给父进程 。
  3. 父进程收到后:打印pid+pipe消息内容
  4.  大概执行结果如下:
    $ make qemu
    ...
    init: starting sh
    $ pingpong
    4: received ping
    3: received pong
    $

简单讲下思路,创建两个pipe,在fork, 在另两个管道,一个从父进程指向子进程。一个从子进程指向父进程。在由父进程发出"ping" ,在阻塞的read, 子进程在收到"ping"后,在往管道写入"pong", 父进程收到pong便打印并结束程序。

​
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
// 

int main()
{
    int fd[2];
    pipe(fd); //fd[1]-->fd[0]
    
    int fd1[2];
    pipe(fd1);
    int ret = fork(); // get child pid
    if (ret == -1) {
        printf("fork error\n");
    } else if(ret == 0){
        int buf[8] = {'\0'};
        close(fd[1]);
        read(fd[0], buf, 5);
        printf("%d: receive %s", getpid(), buf);

        close(fd1[0]);
        write(fd1[1], "pong\n", 5);
        exit(0);   // 必须有,否则会进入usertrap

    } else {
        int buf[8] = {'\0'};
        close(fd[0]);
        write(fd[1], "ping\n", 5);


        close(fd1[1]);

        read(fd1[0], buf, 5);
        printf("%d: receive %s", getpid(), buf);
        

        exit(0);   // 必须有,否则会进入usertrap
    }
    return 0;
}

​

primes 

  • 目标:

Your goal is to use pipe and fork to set up the pipeline. The first process

1.feeds the numbers 2 through 35 into the pipeline.

2.For each prime number,

3. you will arrange to create one process that reads from its left neighbor over a pipe and writes to its right neighbor over another pipe.

Since xv6 has limited number of file descriptors and processes, the first process can stop at 35.

  • 简单的说:就是找到2-35的质数,每一个找到的质数通过fork+pipe由父进程传给子进程。然后将所有的质数打印出来
  • 就是下面这张图的意思:输入2-11然后以第一个数2作为质数,然后排除非质数4,6,8,10。 在将3作为质数,排除5,7,9,11中的9。 依次这样操作

  • 结果如下
    $ make qemu
    ...
    init: starting sh
    $ primes
    prime 2
    prime 3
    prime 5
    prime 7
    prime 11
    prime 13
    prime 17
    prime 19
    prime 23
    prime 29
    prime 31
    $
  • 思路:
  1. 实现一个函数:输入是数据+个数,输出是除当前质数的剩下的数据。比如:上图的第一个, func(int* val, int count);   // val是2~11的数组头,count是10
  2. func的结果可先通过printf打印出来;得到3 5 7 9 11 
  3. 把printf变为向子进程写入数据3 5 7 9 11, 子进程读数据。
  4. 接下来就是递归调用func函数
  • 代码
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

#define READ 0
#define WRITE 1

int func(int* val, int count)
{
    // printf("count=%d ", count);
    // for(int a=0; a < count; a++) {
    //     printf("%d ", val[a]);
    // }
    // printf("\n");

    if (count == 0) {
        printf("over\n");
        exit(0);
    }
    printf("prime %d\n", val[0]);

    int fd[2];
    pipe(fd);

    int ret = fork();
    if (ret == -1) {
        printf("fork error\n");
    } else if (ret == 0) {  // son
        close(fd[WRITE]);
        int tmp=0;
        int buf[64] = {'\0'};
        while (1)
        {
            int a = read(fd[READ], &buf[tmp], 4);
            if (4 == a) {
                // printf("[%d]read[%d]\n", a, buf[tmp]);
                tmp++;
            }
            if (a == 1 && buf[tmp] == '\n') {
                // printf("read finish flag\n");
                break;
            }
        }
        // printf("finish \n");
        close(fd[READ]);

        func(buf, tmp);

        exit(0);   // 必须有,否则会进入usertrap
    } else if (ret > 0) {  // parent
        close(fd[READ]);
        // feeds the numbers 2 through 35 into the pipeline.
        for(int i=0; i < count; i++) //2 3 4 
        {
            if (val[i] % val[0] == 0) //去掉被val[0]整除的数据
            {
                continue;
            } else {
                write(fd[WRITE], &val[i], 4);
                // printf("write=%d\n", j);
            }
        }
        // 结束标志
        char val = '\n';
        write(fd[WRITE], &val, 1);
        close(fd[WRITE]);

        wait(0);  // 等待子进程结束
        exit(0);   // 必须有,否则会进入usertrap
    }
    return 0;
}

int main()
{

    int buf[34];
    for (int i = 0; i < 34; i++)
    {
        buf[i]=i+2;
    }
    func(buf, 34);

    return 0;
}
// #endif

find

  • 目标:编写一个简单版本的UNIX查找程序:查找目录树中具有特定名称的所有文件。您的解决方案应该在user/ findc文件中。
  • 查看user/ls.c文件--->可知:ls path 命令 ==> open(path) + fstat ==>查看st.type

关于ls的实现,研究了下源码,加了些注释,基本能看懂ls这个函数就可以知道怎么写find这个功能了。

void
ls(char *path)
{
  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: //open的是一个文件,不是目录
    printf("222 %s %d %d %l\n", fmtname(path), st.type, st.ino, st.size);
    break;

  case T_DIR: //open的是一个目录
    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;
      }
      printf("333 %s %d %d %d\n", fmtname(buf), st.type, st.ino, st.size);
    }
    break;
  }
  close(fd);
}
  • 思路:1.遍历某个目录,和ls.c的实现一样。  2.比较已有的文件名和目标文件名
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

int readDir_findObj(char* path, char* obj)
{
    // printf("path[%s] obj[%s]\n", path, obj);
// (1) .
    int fd = open(path, 0);
// (2) ./
    char stat_buffer[64] = {'0'};
    strcpy(stat_buffer, path);
    char* p = stat_buffer+strlen(stat_buffer);
    *p++ = '/';

    struct dirent de;
    struct stat st;
    while(read(fd, &de, sizeof(de)) == sizeof(de)){
        if(de.inum == 0)
            continue;
        // (3) ./test
        memmove(p, de.name, DIRSIZ);
        stat(stat_buffer, &st);
        switch (st.type)
        {
            case T_DIR: //目录
                {
                    //不处理.和..目录
                    if(!strcmp(de.name, ".") ||!strcmp(de.name, "..")) {
                        continue;
                    }
                    // printf("dir %d %d %d [%s]\n", st.type, st.ino, st.size, de.name);

                    char pathtmp[64] = {'\0'};
                    strcpy(pathtmp, stat_buffer);
                    memmove(pathtmp+strlen(path), "/", 1);
                    memmove(pathtmp+strlen(path)+1, de.name, strlen(de.name));

                    readDir_findObj(pathtmp, obj);
                }
                break;
            case T_FILE: //文件
                // printf("file %d %d %d[%s]\n", st.type, st.ino, st.size, de.name);
                // find obj file
                if (!strcmp(de.name, obj)) {
                    printf("file [%s] find at [%s]\n", de.name, path);
                }
                break;
            default:
                break;
        }

    }
    close(fd);
    return 0;
}

int main(int argc, char *argv[])
{
    if(argc < 2){
        printf("please input arg\n");
        exit(0);
    }
    char* objname = argv[1];
// find objname 在当前目录下
    readDir_findObj(".", objname);
    exit(0);
    return 0;
}

xargs

  • todo


 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/9119.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

html中css的基础学习

小李胖了吗 I 都说秋天适合思念&#xff0c;其实更适合见面【小李胖了吗 I 都说秋天适合思念&#xff0c;其实更适合见面】 https://www.bilibili.com/video/BV19g411B7uL/?share_sourcecopy_web&vd_source385ba0043075be7c24c4aeb4aaa73352 通过本博文的学习&#xff0c…

常见的软件测试面试题,千万别答错了

软件测试的童鞋们&#xff0c;在面试测试工作时&#xff0c;一定遇到面试官问过这个问题&#xff1a; 软件测试的目的意义是什么&#xff1f;大家是怎么回答的呢&#xff1f;如果这个问题回答好了&#xff0c;说明你对软件测试工作的价值与意义了如指掌。 有经验的测试人员可…

[数据结构]栈和队列面试题解析

作者&#xff1a; 华丞臧. 专栏&#xff1a;【数据结构】 各位读者老爷如果觉得博主写的不错&#xff0c;请诸位多多支持(点赞收藏关注)。如果有错误的地方&#xff0c;欢迎在评论区指出。 推荐一款刷题网站 &#x1f449; LeetCode刷题网站 文章目录一、有效括号题目描述解题思…

freemarker+yml介绍 以及freemarker与JSP的区别

目录 1. freemarker介绍 2. freemarker使用步骤 2.1 在pom.xml引入freeMarker的依赖包 2.2 在springboot中添加freemarker配置 2.3 编写模板文件*.ftl(当做jsp使用即可) ​编辑 2.4 访问控制器后进行页面跳转 3. freemarker常用语法 3.1 取值 3.2 条件 3.3 循环 3…

超赞:不愧是“阿里内部Redis学习笔记”从头到尾,全是精华

近几年&#xff0c;随着移动互联网的飞速发展&#xff0c;我们享受着整个社会的技术进步带来的便利&#xff0c;但同时也给从业者带来了如何保证项目的高并发、低延时的技术挑战&#xff0c;相应的互联网技术也随之发生了重大变革&#xff0c;NoSQL技术得到了蓬勃的发展。 Red…

【JavaSE】抽象类与接口

文章目录抽象类的概念抽象类的语法抽象类的特性接口的概念接口的语法接口的特性多接口的实现接口的继承抽象类的概念 什么是抽象类呢&#xff1f;我们先来看一个例子&#xff1a;一个父类是动物类&#xff0c;两个子类一个狗狗类&#xff0c;一个猫猫类。 Animal中有一个dark…

有趣的statement stack

引子 在使用events_statements_current的过程中发现&#xff0c;同一线程在同一时刻&#xff0c;可能有多条记录&#xff0c;与直观感觉不太一样&#xff0c;于是跟踪了一下内部实现&#xff0c;有了本文。 STATEMENT STACK的定义 STATEMENT STACK 是events_statements_curr…

Linux基础内容(11)—— 进程理解

目录 1.进程状态 1.只针对操作系统的宏观概念 2.Linux的进程状态 1.运行状态(R) 2.休眠状态(S) 3.暂停状态/浅度睡眠状态(T) 4.深度睡眠状态(D) 5.当前进程正在被追踪(t) 6.死亡状态(X) 7.僵尸状态(Z) 8.孤儿进程 2.进程的优先级 1.优先级定义 2.Linux的优先级表现…

matlab 小数据法求liyapunov指数

1、内容简介 略 625-可以交流、咨询、答疑 2、内容说明 摘 要&#xff1a;从 Lyapunov 指数的定义出发&#xff1a;在常用计算最大 Lyapunov 指数的基础上&#xff0c;将自相关法和 G-P 法应用于小数 据量法中&#xff0c;得到了一种计算最大 Lyapunov 指数的改进小数据量法…

推特的算法规则你知道多少?

如果玩Twitter却不明白Twitter算法是如何运作的&#xff0c;就如同贸然出征却忘了带上武器。 社交媒体的算法正在成为你在平台中所见内容的核心&#xff0c;当知晓了该平台的推荐算法&#xff0c;自然也就知道发布什么样的内容有助于被更多用户看到。因此&#xff0c;了解了算…

Spring之依赖注入

文章目录前言一、set注入1.1 UserDao类&#xff1a;1.2 UserService类&#xff1a;1.3 spring.xml配置文件&#xff1a;1.4 测试类&#xff1a;1.5 执行结果&#xff1a;二、构造方法注入2.1 UserDao2.2 UserService类&#xff1a;1.3 spring.xml配置文件&#xff1a;2.4 测试类…

微服务改造过程中那些必须重视的问题

“微服务”近几年尤其火热&#xff0c;各大厂都在进行微服务化改造和微服务建设&#xff0c;想享受微服务化带来的好处以便对自己的系统进行改造。分布式实验室特约记者李鹏采访了广州轻阅科技系统架构师陈珙&#xff0c;就微服务与SOA的区别与联系、企业引入微服务会带来的问题…

Vue2:官方路由 Vue-Router 3.x

前端路由 前端路由&#xff1a;根据不同的url地址&#xff0c;页面上展示不同的内容&#xff08;根据url地址的不同分发到不同的组件。&#xff09; SPA 介绍 spa 是 single page application 简写&#xff0c;意思是单页面应用程序。Vue 适合开发 spa 类型的项目。 优点&…

Django 所带的用户auth_user的坑点,authenticate()校验一直为None,校验与创建所遇到的问题整理与解决

整理一下django中用户模块自定义model后登录的一些问题&#xff1a; 网上的报错解决不是万能方案&#xff0c;主要还是要自主分析原因&#xff0c;有的是有用但是导包之类的也要看清楚因为自己修改了所以有所变得&#xff0c;不自定义的话又不太好用。 在项目初期决定使用auth…

鸡卵白蛋白偶联脂多糖(OVA-LPS),麻黄多糖修饰卵白蛋白(PB-OVA)

产品名称&#xff1a;鸡卵白蛋白偶联脂多糖 英文名称&#xff1a;OVA-LPS 用途&#xff1a;科研 状态&#xff1a;固体/粉末/溶液 产品规格&#xff1a;1g/5g/10g 保存&#xff1a;冷藏 储藏条件&#xff1a;-20℃ 储存时间&#xff1a;1年 脂多糖&#xff08;Lipopolysacchar…

第四站:数组

目录 一、一维数组的创建和初始化 1.数组的创建 &#xff08;1&#xff09;基本定义&#xff0c;创建方式 &#xff08;2&#xff09;经典的错误标准的零分 2.数组的初始化 3.一维数组的使用 4.一维数组在内存中的存储 二、二维数组的创建和初始化 1.二维数组的创建 2…

SpringBoot SpringBoot 开发实用篇 2 配置高级 2.2 松散绑定

SpringBoot 【黑马程序员SpringBoot2全套视频教程&#xff0c;springboot零基础到项目实战&#xff08;spring boot2完整版&#xff09;】 SpringBoot 开发实用篇 文章目录SpringBootSpringBoot 开发实用篇2 配置高级2.2 松散绑定2.2.1 问题引入2.2.2 松散绑定2.2.3 小结2 配…

MySQL学习笔记:模型2

序言 《MySQL45讲》 为什么表数据删除一半&#xff0c;表文件大小不变&#xff1f; 表数据既可以存在共享表空间里&#xff0c;也可以是单独的文件。这个行为是由参数 innodb_file_per_table 控制的&#xff1a; 这个参数设置为 OFF 表示的是&#xff0c;表的数据放在系统共…

错字修改 | 布署1个中文文文本拼蟹纠错模型

内容一览&#xff1a;中文文本错误的种类之一为拼写错误&#xff0c;本篇文章为利用 BART 预训练方法实现中文文本纠错功能的模型部署教程。 关键词&#xff1a;BART 中文拼写纠错 NLP 本文首发自微信公众号&#xff1a;HyperAI超神经 中文文本错误3大障碍&#xff1a;拼写、语…

Chapter9.1:线性系统的状态空间分析与综合(上)

此系列属于胡寿松《自动控制原理题海与考研指导》(第三版)习题精选&#xff0c;仅包含部分经典习题&#xff0c;需要完整版习题答案请自行查找&#xff0c;本系列属于知识点巩固部分&#xff0c;搭配如下几个系列进行学习&#xff0c;可用于期末考试和考研复习。 自动控制原理(…