• xv6---Lab: Xv6 and Unix utilities


    目录

    参考资料:

    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);的时候,依旧保留之前的偏移。
    1. fd = dup(1);
    2. write(1, "hello", 6);
    3. 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
    1. int p[2];
    2. char *argv[2];
    3. argv[0] = "wc";
    4. argv[1] = 0;
    5. pipe(p);
    6. if(fork() == 0) {
    7. close(0);
    8. dup(p[0]); 将p[0]复制到fd 0上(stdin)
    9. close(p[0]);
    10. close(p[1]); 关闭stdin和stdout
    11. exec("/bin/wc", argv); 2.读取stdin的输入:执行wc 0, 读"hello world\n"得到 1 2 12
    12. } else {
    13. write(p[1], "hello world\n", 12); 1.往管道的输出端口写入 "hello world\n"字符
    14. close(p[0]);
    15. close(p[1]);
    16. }

    源码:

    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函数

    • 代码
    1. #include "kernel/types.h"
    2. #include "kernel/stat.h"
    3. #include "user/user.h"
    4. int main(int argc, char *argv[]){
    5. if(argc != 2){
    6. fprintf(2, "usage: sleep time\n");
    7. exit(1);
    8. } else {
    9. int time = atoi(argv[1]);
    10. sleep(time);
    11. exit(0);
    12. }
    13. }
    • test
    1. a123@ubuntu:~/Public/xv6-labs-2020$ ./grade-lab-util sleep 2
    2. make: 'kernel/kernel' is up to date.
    3. == Test sleep, no arguments == sleep, no arguments: OK (1.9s) 
    4. == Test sleep, returns == sleep, returns: OK (1.6s) 
    5. == Test sleep, makes syscall == sleep, makes syscall: OK (1.2s) 
    6. a123@ubuntu:~/Public/xv6-labs-2020$ 

    PingPong

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

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

    1. #include "kernel/types.h"
    2. #include "kernel/stat.h"
    3. #include "user/user.h"
    4. //
    5. int main()
    6. {
    7. int fd[2];
    8. pipe(fd); //fd[1]-->fd[0]
    9. int fd1[2];
    10. pipe(fd1);
    11. int ret = fork(); // get child pid
    12. if (ret == -1) {
    13. printf("fork error\n");
    14. } else if(ret == 0){
    15. int buf[8] = {'\0'};
    16. close(fd[1]);
    17. read(fd[0], buf, 5);
    18. printf("%d: receive %s", getpid(), buf);
    19. close(fd1[0]);
    20. write(fd1[1], "pong\n", 5);
    21. exit(0); // 必须有,否则会进入usertrap
    22. } else {
    23. int buf[8] = {'\0'};
    24. close(fd[0]);
    25. write(fd[1], "ping\n", 5);
    26. close(fd1[1]);
    27. read(fd1[0], buf, 5);
    28. printf("%d: receive %s", getpid(), buf);
    29. exit(0); // 必须有,否则会进入usertrap
    30. }
    31. return 0;
    32. }

    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。 依次这样操作

    • 结果如下
    1. $ make qemu
    2. ...
    3. init: starting sh
    4. $ primes
    5. prime 2
    6. prime 3
    7. prime 5
    8. prime 7
    9. prime 11
    10. prime 13
    11. prime 17
    12. prime 19
    13. prime 23
    14. prime 29
    15. prime 31
    16. $
    • 思路:
    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函数
    • 代码
    1. #include "kernel/types.h"
    2. #include "kernel/stat.h"
    3. #include "user/user.h"
    4. #define READ 0
    5. #define WRITE 1
    6. int func(int* val, int count)
    7. {
    8. // printf("count=%d ", count);
    9. // for(int a=0; a < count; a++) {
    10. // printf("%d ", val[a]);
    11. // }
    12. // printf("\n");
    13. if (count == 0) {
    14. printf("over\n");
    15. exit(0);
    16. }
    17. printf("prime %d\n", val[0]);
    18. int fd[2];
    19. pipe(fd);
    20. int ret = fork();
    21. if (ret == -1) {
    22. printf("fork error\n");
    23. } else if (ret == 0) { // son
    24. close(fd[WRITE]);
    25. int tmp=0;
    26. int buf[64] = {'\0'};
    27. while (1)
    28. {
    29. int a = read(fd[READ], &buf[tmp], 4);
    30. if (4 == a) {
    31. // printf("[%d]read[%d]\n", a, buf[tmp]);
    32. tmp++;
    33. }
    34. if (a == 1 && buf[tmp] == '\n') {
    35. // printf("read finish flag\n");
    36. break;
    37. }
    38. }
    39. // printf("finish \n");
    40. close(fd[READ]);
    41. func(buf, tmp);
    42. exit(0); // 必须有,否则会进入usertrap
    43. } else if (ret > 0) { // parent
    44. close(fd[READ]);
    45. // feeds the numbers 2 through 35 into the pipeline.
    46. for(int i=0; i < count; i++) //2 3 4
    47. {
    48. if (val[i] % val[0] == 0) //去掉被val[0]整除的数据
    49. {
    50. continue;
    51. } else {
    52. write(fd[WRITE], &val[i], 4);
    53. // printf("write=%d\n", j);
    54. }
    55. }
    56. // 结束标志
    57. char val = '\n';
    58. write(fd[WRITE], &val, 1);
    59. close(fd[WRITE]);
    60. wait(0); // 等待子进程结束
    61. exit(0); // 必须有,否则会进入usertrap
    62. }
    63. return 0;
    64. }
    65. int main()
    66. {
    67. int buf[34];
    68. for (int i = 0; i < 34; i++)
    69. {
    70. buf[i]=i+2;
    71. }
    72. func(buf, 34);
    73. return 0;
    74. }
    75. // #endif

    find

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

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

    1. void
    2. ls(char *path)
    3. {
    4. char buf[512], *p;
    5. int fd;
    6. struct dirent de;
    7. struct stat st;
    8. if((fd = open(path, 0)) < 0){
    9. fprintf(2, "ls: cannot open %s\n", path);
    10. return;
    11. }
    12. if(fstat(fd, &st) < 0){
    13. fprintf(2, "ls: cannot stat %s\n", path);
    14. close(fd);
    15. return;
    16. }
    17. switch(st.type){
    18. case T_FILE: //open的是一个文件,不是目录
    19. printf("222 %s %d %d %l\n", fmtname(path), st.type, st.ino, st.size);
    20. break;
    21. case T_DIR: //open的是一个目录
    22. if(strlen(path) + 1 + DIRSIZ + 1 > sizeof buf){
    23. printf("ls: path too long\n");
    24. break;
    25. }
    26. strcpy(buf, path);
    27. p = buf+strlen(buf);
    28. *p++ = '/';
    29. //遍历读出目录下的文件
    30. while(read(fd, &de, sizeof(de)) == sizeof(de)){
    31. if(de.inum == 0)
    32. continue;
    33. //拷贝文件名
    34. memmove(p, de.name, DIRSIZ);
    35. p[DIRSIZ] = 0;
    36. //得到文件属性
    37. if(stat(buf, &st) < 0){
    38. printf("ls: cannot stat %s\n", buf);
    39. continue;
    40. }
    41. printf("333 %s %d %d %d\n", fmtname(buf), st.type, st.ino, st.size);
    42. }
    43. break;
    44. }
    45. close(fd);
    46. }
    • 思路:1.遍历某个目录,和ls.c的实现一样。  2.比较已有的文件名和目标文件名
    1. #include "kernel/types.h"
    2. #include "kernel/stat.h"
    3. #include "user/user.h"
    4. #include "kernel/fs.h"
    5. int readDir_findObj(char* path, char* obj)
    6. {
    7. // printf("path[%s] obj[%s]\n", path, obj);
    8. // (1) .
    9. int fd = open(path, 0);
    10. // (2) ./
    11. char stat_buffer[64] = {'0'};
    12. strcpy(stat_buffer, path);
    13. char* p = stat_buffer+strlen(stat_buffer);
    14. *p++ = '/';
    15. struct dirent de;
    16. struct stat st;
    17. while(read(fd, &de, sizeof(de)) == sizeof(de)){
    18. if(de.inum == 0)
    19. continue;
    20. // (3) ./test
    21. memmove(p, de.name, DIRSIZ);
    22. stat(stat_buffer, &st);
    23. switch (st.type)
    24. {
    25. case T_DIR: //目录
    26. {
    27. //不处理.和..目录
    28. if(!strcmp(de.name, ".") ||!strcmp(de.name, "..")) {
    29. continue;
    30. }
    31. // printf("dir %d %d %d [%s]\n", st.type, st.ino, st.size, de.name);
    32. char pathtmp[64] = {'\0'};
    33. strcpy(pathtmp, stat_buffer);
    34. memmove(pathtmp+strlen(path), "/", 1);
    35. memmove(pathtmp+strlen(path)+1, de.name, strlen(de.name));
    36. readDir_findObj(pathtmp, obj);
    37. }
    38. break;
    39. case T_FILE: //文件
    40. // printf("file %d %d %d[%s]\n", st.type, st.ino, st.size, de.name);
    41. // find obj file
    42. if (!strcmp(de.name, obj)) {
    43. printf("file [%s] find at [%s]\n", de.name, path);
    44. }
    45. break;
    46. default:
    47. break;
    48. }
    49. }
    50. close(fd);
    51. return 0;
    52. }
    53. int main(int argc, char *argv[])
    54. {
    55. if(argc < 2){
    56. printf("please input arg\n");
    57. exit(0);
    58. }
    59. char* objname = argv[1];
    60. // find objname 在当前目录下
    61. readDir_findObj(".", objname);
    62. exit(0);
    63. return 0;
    64. }

    xargs

    • todo


     

  • 相关阅读:
    Mysql语句的RegExp的用法与案例
    MySQL的视图,用户管理,C语言连接
    Linux小工具(grep+cut+sort+uniq+tee+diff+patch+paste+tr)
    skywalking全链路追踪
    BVR电线与RV电线的区别有哪些?
    【工具代码合集】
    【AI视野·今日NLP 自然语言处理论文速览 第三十五期】Mon, 18 Sep 2023
    STM32F103C8T6基于Arduino框架下利用定时器跑RBG灯闪烁
    【无标题】
    这几个点让我买了Watch Ultra
  • 原文地址:https://blog.csdn.net/m0_37844072/article/details/127803361