Dududida's blog

A man

  1. 1. xv6的安装
  2. 2. 关于各种系统调用
  3. 3. Lab util: Unix utilities
    1. 3.1. sleep
    2. 3.2. pingpong
    3. 3.3. primes
    4. 3.4. find
    5. 3.5. xargs
  4. 4. Lab:system calls
    1. 4.1. System call tracing
    2. 4.2. Sysinfo
  5. 5. Lab: page tables
    1. 5.1. Speed up system calls
    2. 5.2. Print a page table
    3. 5.3. Detecting which pages have been accessed
  6. 6. Lab: traps
  7. 7. Lab: Copy-on-Write Fork for xv6
  8. 8. Lab: Multithreading
  9. 9. Lab: networking
  10. 10. Lab: locks
  11. 11. Lab: file system
  12. 12. Lab: mmap

xv6的安装

1
2
3
4
5
6
7
8
9
$ sudo apt-get install git build-essential gdb-multiarch qemu-system-misc gcc-riscv64-linux-gnu binutils-riscv64-linux-gnu 

$ git clone git://g.csail.mit.edu/xv6-labs-2021
$ cd xv6-labs-2021
$ git checkout util

$ make qemu ##运行xv6
$ Ctrl+a x ##退出xv6

关于各种系统调用

  • pipe():int pipe(int fds[2]); 在进程的打开文件表中查找前两个可用位置,并将它们分配给管道的读p[0]写p[1]端。
  • chdir():int chdir(const char *path); 用于改变当前工作目录,其参数为Path 目标目录(类似于cd)

Lab util: Unix utilities

  • 以下路径都在xv6-labs-2021/下面

sleep

user/目录下新建sleep.c,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
int main(int argc, char *argv[]){
if(argc<2){ //缺少参数
fprintf(2,"miss argument !\n");
exit(1);
}
int time;
time = atoi(argv[1]);
if(time<0){ //参数不合法 ,实际运行中好像没反应。但是make grade是过了
fprintf(2,"invalid argument !\n");
exit(1);
}
sleep(time);
exit(0);
}

打开Makefile,在179行找到UPROGS,在其中添加sleep。

回到xv6-labs-2021/,$ make qemu

测试是否通过,在xv6-labs-2021/目录下:$ ./grade-lab-util sleep

pingpong

不加wait,会出现缓冲区冲突,交替写入。

1.8晚上。自己测试没问题,在进行成绩测试时没通过。

在多次执行pingpong时,输出的pid依次增加,按理说不应该这样。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int main(int argc,char *argv[]){
int p1[2]; //pipe1
int p2[2]; //pipe2
pipe(p1);
pipe(p2);

int pid;
char readBuffer[20];
int rsize;

if(fork()===0){
pid = getpid();
close(p1[1]);
close(p2[0]);
write(p2[1],"p",1);
rsize = read(p1[0],readBuffer,1);
if(rsize){
fprintf(1,"%d:received ping\n",pid);
exit(0);
}
exit(1);
}
pid = getpid();
close(p1[0]);
close(p2[1]);
write(p1[1],"p",1);

int userStat;
wait(&userStat);

rsize = read(p2[0],readBuffer,1);
if(rsize){
fprintf(1,"%d:received pong\n",pid);
exit(0);
}
exit(1);
}

primes

题意:用一堆进程,在他们之间构建pipeline,来构建出一个质数筛选器

参考资料:Bell Labs and CSP Threads 中描述

1
2
3
4
5
6
p = get a number from left neighbor
print p
loop:
n = get a number from left neighbor
if (p does not divide n)
send n to right neighbor

每个进程打印一个质数,并做一次筛选,剩下的通过管道传到下一个进程

问题在于如何在多个进程间建立起pipeline?

以下代码参考自知乎-渡船-6.s081 2021 lab1 Xv6 and Unix utilities

  • 我们认为进程统一从pipe1得到数据,将剩余数据传入pipe2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

#define READ 0
#define WRITE 1
#define NULL (int *)0

int main(int argc, char *argv[])
{
int pipe1[2];
pipe(pipe1);
int pid = fork();
if (pid > 0)
{ // 父进程(0)里面
close(pipe1[READ]); // 关闭父进程的读端
for (int i = 2; i <= 35; i++)
{
write(pipe1[WRITE], &i, sizeof(int));
}
// 写入完毕,关闭父进程写端,释放出文件描述符
close(pipe1[WRITE]);
wait(NULL); // 有一个子进程退出,这个父进程即退出
exit(0);
}
else
{ // 子进程里面,他们的行为是类似的
int min;
close(pipe1[WRITE]); // 关闭写入端
//注意,这时子进程(0)的状态,与后面所有后代进程进入循环的初始状态一致
while (read(pipe1[READ], &min, sizeof(int)))
{ // 能从管道里读出数据,就继续执行
printf("prime %d\n", min);
int pipe2[2]; //用于往后传
pipe(pipe2);
int i;
while (read(pipe1[READ], &i, sizeof(int)))
{
if (i % min)
{ // 除不尽
write(pipe2[WRITE], &i, sizeof(int));
}
}
close(pipe2[WRITE]); // 向子进程写完了,关闭
int pid2 = fork(); // 子1再fork
if (pid2 == 0)
{ // 子(2)进程里面
pipe1[READ] = dup(pipe2[READ]);
/*
!!!
这里非常关键,这一步将fd[READ]指向pipe2的读端。
使得后续进程从pipe1里去读时,实际读的是其父进程的pipe2
*/
close(pipe2[READ]);
}
else
{
close(pipe2[READ]);
wait(NULL);
exit(0); //pipe1销毁,因为已经没有文件描述符指向它
}
}
exit(0);
}
exit(0);
}

手写画图模拟执行过程

find

以官网例子为例,find . b表示在.目录及其子目录下查找名为b的数据文件。反映在argv[]中,即在名为argv[1]目录及其子目录中查找名为argv[2]的数据文件(这点困扰了我很久)

难点在于:

  • 了解LINUX的目录文件的组织结构
  • 读懂ls.c中的ls()中的代码。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"
#include "kernel/fs.h"

void find(char *path, char *fname)
{
char buf[512], *p; // buf存的是路径字符串
int fd;
struct dirent de;
struct stat st;

if ((fd = open(path, 0)) < 0) // 打开文件
{
fprintf(2, "find: cannot open %s\n", path);
return;
}

if (fstat(fd, &st) < 0) // 把文件中的信息存入结构体st中
{
fprintf(2, "find: cannot stat %s\n", path);
close(fd);
return;
}

/*
根据fs.h
Directory is a file containing a sequence of dirent structures.
即目录文件是一个包含了一系列dirent这个结构体的文件,dirent结构体中有inum和name
*/

//这里与ls.c中的区别:不需要switch
// DIRSIZ在fs.h中定义,大小为14
// 传入的path即是当前的路径字符串
if (strlen(path) + 1 + DIRSIZ + 1 > sizeof buf)
{
printf("find: path too long\n");
break;
}
strcpy(buf, path); // 将path复制到buf
p = buf + strlen(buf);
/*
这是在初始化p指针,将p指向buf的数据的最后。
buf是数组的起始地址,strlen(buf)是已存放数据的长度。
*/
*p++ = '/'; // 在后面加上‘/’
while (read(fd, &de, sizeof(de)) == sizeof(de)) // 读取sizeof(dirent)大小的数据,即一个文件
{
/*
de.inum==0表示这是一块已经初始化并且可以
用来创建文件或者文件夹的位置,应当无视这一块空间
*/
if (de.inum == 0)
continue;
memmove(p, de.name, DIRSIZ); // p就是用于访问buf的指针,往路径后面添加
p[DIRSIZ] = 0; // 这是在干啥?处理某种潜在的bug
if (stat(buf, &st) < 0) // stat()用于获取文件信息,参数1就是文件路径名
{
printf("find: cannot stat %s\n", buf);
continue;
}
if ((strcmp(de.name, fname) == 0) && (st.type == T_FILE)) // 数据文件
{
printf("%s\n", buf);
}
if ((st.type == T_DIR) && (strcmp(".", de.name) != 0) && (strcmp("..", de.name) != 0)) //
{
// 不能递归. ..
find(buf, fname);
}
}
close(fd);
}

int main(int argc, char *argv[])
{
if (argc < 2)
{
printf("Missing parameters\n");
exit(0);
}
/*
find函数的行为:
在这个目录里面找。
如果这个目录里面的文件依旧是目录文件,那么递归调用find。
若为文件文件,则与argv[2]的值比对。若匹配,打印路径。退出。
*/
find(argv[1], argv[2]);
exit(0);
}

xargs

完成于2023.2.8 21:42

题意:将来自标准输入的字符串当作命令来执行

难点:

  • 读懂题意,每次读题就要读很久
  • 掌握挨个挨个读取字符的方法,’\0’和’\n’的作用
  • stdin中的一个字符串结束标志就是读到’ ‘,而不是’\0’,在字符数组中才表现为’\0’
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include "kernel/param.h"
#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

#define NULL (int *)0

int main(int argc, char *argv[])
{
// 进程自动打开0,1,2
// 将argv中的命令行参数读出,放入argvArray数组
char *argvArray[MAXARG]; // 传参都用指针数组传,argv就是一个指针数组。
int i;
for (i = 1; i < argc; i++)
{
argvArray[i-1] = argv[i]; // 注意argv[0]是xargs。不需要
}

// 再读stdin里面的参数
char buf[512], *p; // p是用来访问buf的指针
p = buf; // 先指向起始位置
argvArray[argc - 1] = buf;
/*
以echo hello too | xargs echo bye为例子
argc为3,但实际上此时argvArray中只有两个参数:echo bye
下一步是在argvArray[2]中继续添加参数
*/
int newArgc = argc; //增加后的参数数量
char ch;
while (read(0, &ch, sizeof(char)))
{
if (ch == ' ')
{
//代表字符串结束,加上一个'\0'
*p = '\0';
p++; //这里p立刻后移,否则下面这一步就指向null了。卡了好久
argvArray[newArgc] = p; //新的argv放入
/*
实际上,buf是一直在向前移,永不后退,也不用清空,buf里面类似于
abc'\0'a'\0'
so buffer should be big enough
*/
newArgc++;
}
else if(ch == '\n'){ //这时就要fork,执行命令
*p = '\0';
argvArray[newArgc] = 0; //做一个结尾
int pid = fork();
if(pid){ //父进程
wait(NULL);
memset(buf,0,sizeof(buf)); //这个时候buffer才清零,不清零应该是会有影响的
p = buf; //指针回溯
newArgc = argc; //这个也回溯
}
else{
exec(argvArray[0],argvArray);
exit(0);
}
}
else{ //普通的读取
*p = ch;
p++;
}
}
exit(0);
}

lab1的重难点总结:

  • 掌握unix下编程的基本格式
  • 掌握父子进程通过管道进行通信的原理并实现通信。
  • 理解linux下文件的组织形式及相关结构体的组成

Lab:system calls

参考文件:

  • user/user.h:罗列系统调用的接口
  • user/usys.pl:配置文件
  • kernel/syscall.h:罗列系统调用号

System call tracing

要点:

  • 命令行中的数,是int32表示,从低位开始依次标号0、1、2、3…31,每一位表示一个系统调用是否跟踪。为1则跟踪,为0则不跟踪。例如trace 32 grep hello README跟踪的是read系统调用,read的系统调用号为5。32在补码的表示下,低6位为100 000。也就是说,它这样表示,其实最多只能跟踪31个系统调用。不过目前只有22个,暂时没有超标。
  • 对user/trace.c的分析:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    #include "kernel/param.h"
    #include "kernel/types.h"
    #include "kernel/stat.h"
    #include "user/user.h"
    int main(int argc, char *argv[]){
    int i;
    char *nargv[MAXARG];
    if(argc < 3 || (argv[1][0] < '0' || argv[1][0] > '9')){ //只有两个参数 或者 系统调用号不是数字,报错
    fprintf(2, "Usage: %s mask command\n", argv[0]);
    exit(1);
    }
    if (trace(atoi(argv[1])) < 0) { //调用trac,说明trace应该是返回int,且-1表示报错。并且trace的作用是设置mask,把argv[1]传进去
    fprintf(2, "%s: trace failed\n", argv[0]);
    exit(1);
    }
    for(i = 2; i < argc && i < MAXARG; i++){ //提取出要执行的命令。例子:grep hello README
    nargv[i-2] = argv[i];
    }
    exec(nargv[0], nargv);
    /*
    调用exec后,调用该函数的进程的虚拟地址空间的代码段、数据段、堆、栈被释放,
    替换成新进程的代码段、数据段、堆、栈,而PCB依旧使用之前进程的PCB。所以之前通过
    trace设置的mask还在。接下来,当一个进程调用一个系统调用时,我们都通过myproc()获取它的mask。
    对比,确认是要跟踪的系统调用,打印信息。

    并且要求子进程也跟踪,也就是要继承父进程的pcb中的mask,还要修改
    */
    exit(0);
    }

实现步骤:

  1. 在Makefile中,将$U/_trace添加到 UPROGS。

  2. 在user/user.h中,添加int trace(int);

  3. 在user/usys.pl中,添加entry("trace");

  4. 在kernel/syscall.h中,添加#define SYS_trace 22

  5. 在kernel/proc.h/struct proc中,添加int mask;

  6. 在kernel/sysproc.c中,添加函数:

    1
    2
    3
    4
    5
    6
    7
    uint64 sys_trace(void){   //for lab:TRACE
    int mask;
    if(argint(0, &mask) < 0)
    return -1;
    myproc()->mask=mask;
    return 0;
    }
  7. 在kernel/proc.c/fork()中,添加:

    1
    2
    // Copy mask from parent to child.    //for lab:TRACE
    np->mask=p->mask;
  8. 在kernel/syscall.c中,添加关于sys_trace的信息

  9. 扩展kernel/syscall.c/syscall(),在真正执行系统调用之前,若需要跟踪此系统调用,打印跟踪信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    void
    syscall(void)
    {
    int num;
    struct proc *p = myproc(); //获取当前的进程的pcb

    int mask;
    char *syscall_name[24]={0,"fork","exit","wait","pipe","read","kill","exec",
    "fstat","chdir","dup","getpid","sbrk","sleep","uptime",
    "open","write","mknod","unlink","link","mkdir","close",
    "trace","sysinfo"};

    num = p->trapframe->a7; //从a7寄存器取出系统调用号
    if(num > 0 && num < NELEM(syscalls) && syscalls[num]) { //正确,则执行系统调用
    p->trapframe->a0 = syscalls[num]();
    } else {
    printf("%d %s: unknown sys call %d\n",
    p->pid, p->name, num);
    p->trapframe->a0 = -1;
    }
    mask=p->mask;
    if(mask&1<<num){ //按位与,只要一位对应上,就代表需要跟踪这个系统调用
    printf("%d: syscall %s -> %d\n",p->pid,syscall_name[num],p->trapframe->a0); //a0存的是系统调用的返回值
    }
    }

重难点:

  • 了解syscall所经过的路径,一定经过syscall函数进入内核空间。通过ecall指令跳转过去
  • 了解pcb的组成成员以及exec之后,pcb还是原来的pcb

Sysinfo

目的:打印空闲内存(单位字节)、打印有多少进程的状态是UNUSED

  • 使用copyout()来实现从内核复制数据到用户空间,位于kernel/vm.c中。下面是分析
    1
    2
    3
    4
    5
    6
    7
    8
    // Copy from kernel to user.
    // Copy len bytes from src to virtual address dstva in a given page table.
    // Return 0 on success, -1 on error.
    int
    copyout(pagetable_t pagetable, uint64 dstva, char *src, uint64 len)

    // 其中pagetable_t是一个uint64 *
    // 回顾了一下页表的理论知识,确实就应该是一个数组,每一项表示一个页表条目,最高位表示是否在内存中

实现步骤:

  1. 在Makefile中,将$U/_sysinfotest添加到 UPROGS。

  2. 在user/user.h中,添加int sysinfo(struct sysinfo*);,并且在文件最前面添加struct sysinfo;

  3. 在user/usys.pl中,添加entry("sysinfo");

  4. 在kernel/syscall.h中,添加#define SYS_sysinfo 23

  5. 在kernel/kalloc.c中,添加函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    /*
    用户空间分配内存使用malloc,分配的虚拟内存
    kalloc.c是用来分配内核内存的函数,分配物理内存
    */
    uint64 get_freemem(void) //获得空闲内存的数量,多少字节。
    {
    struct run *r;
    uint64 len=0;
    acquire(&kmem.lock); //获得锁
    r = kmem.freelist;
    while(r)
    {
    r = r->next;
    len++;
    }
    release(&kmem.lock);
    return len*PGSIZE; //PGSIZE即页面的大小,为4096。定义在riscv.h里面
    }
  6. 在kernel/proc.c中,添加函数:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    uint64 get_unused_proc(void)
    {
    struct proc *p;
    uint64 num=0;
    for (p = proc; p < &proc[NPROC]; p++)
    {
    acquire(&p->lock); //这是锁,在struct proc的注释中,有写哪些成员在访问时必须要先获取锁
    if (p->state != UNUSED)
    {
    num++;
    }
    release(&p->lock);
    }
    return num;
    }
  7. 在kernel/defs.h中找到kalloc.c和proc.c的位置,添加刚才添加的函数原型

  8. 在kernel/sysproc.c中,添加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    #include "sysinfo.h"

    uint64 sys_sysinfo(void)
    {
    struct sysinfo info;
    info.freemem=get_freemem();
    info.nproc=get_unused_proc();

    uint64 user_si; // user pointer to struct sysinfo
    struct proc *p = myproc();

    if(argaddr(0, &user_si) < 0)
    return -1;//fetch user pointer to struct sysinfo from syscall arg
    if(copyout(p->pagetable, user_si, (char *)&info, sizeof(info)) < 0)
    return -1;
    return 0;
    }
  9. 在kernel/syscall.c中,添加关于sys_sysinfo的信息。(第一次因为忘了这一步,导致报错unknown sys call 23)

2023.2.18下午完

Lab: page tables

!!!注意,在进行每个大实验之前,一定要切换分支

1
2
3
$ git fetch
$ git checkout pgtbl
$ make clean

否则代码里根本找不到这次实验需要的结构,比如kernel/memlayout.h/USYSCALL,在切换分支前,根本就没有。

前两个实验没有切换,倒是没有影响。这里要学习git的一些知识,暂时没搞透,搞透了再写写

Speed up system calls

目的:加速getid()这个系统调用

大意:有一些内核和用户共享的只读页面,来避免模式切换,以此来加快系统调用。这里在kernel/memlayout.h中给你定义了一个虚拟地址USYSCALL,是一个页面的地址,这个页面最开始的地方,又有一个struct,里面放了一个pid。

实现步骤:

// 这里基本是仿照着对trapframe的分配和释放来书写代码,都是分配一个页面,都是里面有一个结构体

  1. 在kernel/proc.h/struct proc中添加struct usyscall *usyscall;。(用于指向共享页面)
  2. 在kernel/proc.c/allocproc()中添加
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // Allocate a usyscall page.      for lab speedup syscall
    if((p->usyscall = (struct usyscall *)kalloc()) == 0){ 、
    //这里应该是物理分配和初始化,后面要做一个映射,否则不能保证它所描述的layout
    freeproc(p);
    release(&p->lock);
    return 0;
    }

    // Initialize the usyscall page. for lab speedup syscall
    p->usyscall->pid=p->pid;
  3. 在kernel/proc.c/proc_pagetable()中,添加:
    1
    2
    3
    4
    5
    6
    7
    // map the share page just below TRAPFRAME,  for lab speedup syscall 
    //这里就实现了内存映射,记住,mappages()就是做内存映射的
    if(mappages(pagetable, USYSCALL, PGSIZE,
    (uint64)(p->usyscall), PTE_R | PTE_U) < 0){
    uvmfree(pagetable, 0);
    return 0;
    }
  4. 在kernrl/proc.c/freeproc()中,添加
    1
    2
    3
    4
    // 释放物理内存
    if(p->usyscall)
    kfree((void*)p->usyscall);
    p->usyscall = 0;
  5. 在kernel/proc.c/proc_freepagetable()中,添加:
    1
    2
    // 取消映射
    uvmunmap(pagetable, USYSCALL, 1, 0);

重难点:

  • 理解物理页面的分配和虚拟地址的映射是分两步
  • 了解xv6下虚拟地址空间的组织结构
  • 理解页表的组织结构,就是一个uint64的数组,存放虚拟地址

目的:打印pid==1的进程的页表

要点:

  • 这里是多级页表,要依次往下探,所以递归进行
  • 与kernel/vm.c/freewalk()的逻辑和遍历路径一致,freewalk的功能是释放分配给页表的物理内存,取消虚拟地址映射。并且物理内存的释放是从下到上。

实现步骤:

  1. 在kernel/exec.c/exec()中的return argc之前,添加:
    1
    2
    3
    if(p->pid==1){    //for lab:print ptl
    vmprint(p->pagetable);
    }
  2. 在kernel/vm.c中实现功能:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // for lab:print ptl
    void fmt_print(int deepth,int i,pte_t pte,uint64 child){
    switch(deepth){
    case 0:printf("..");break;
    case 1:printf(".. ..");break;
    case 2:printf(".. .. ..");break;
    default:break;
    }
    printf("%d: pte %p pa %p\n",i,pte,child);
    }

    void
    vmprint_func(pagetable_t pagetable,int deepth)
    {
    // there are 2^9 = 512 PTEs in a page table.
    for(int i = 0; i < 512; i++){
    pte_t pte = pagetable[i]; //这是一个指针数组,得到了地址
    if((pte & PTE_V) && (pte & (PTE_R|PTE_W|PTE_X)) == 0){ //这个页表条目指向了下一级页表
    // this PTE points to a lower-level page table.
    uint64 child = PTE2PA(pte); //pte转为物理地址,应该说,在直到最底端之前,所谓了pa,都是下一级的虚拟地址而已。
    fmt_print(deepth,i,pte,child);
    vmprint_func((pagetable_t)child,deepth+1); //递归的进行
    } else if(pte & PTE_V){ //到底了
    uint64 child = PTE2PA(pte);
    fmt_print(deepth,i,pte,child);
    //叶子页表,不用递归了
    }
    }
    }

    void vmprint(pagetable_t pagetable){
    printf("page table %p\n",pagetable);
    vmprint_func(pagetable,0);
    }
  3. 在kernel/defs.h中找到vm.c对应得地方,添加对vmprint()的原型说明。

重难点:

  • 了解页表的组织结构,实现对页表的递归遍历并打印层次结构信息。
  • 通过对标识位的逻辑运算,判定PTE是否映射有对象。

Detecting which pages have been accessed

目的:检测哪些页面的Accessed标志位为1,这里要了解sv39结构的虚拟地址的组成。

实现思路:大体也就是通过遍历页表,检测标志位

实现步骤:

  1. 在kernel/riscv.h中定义Accessed标志位:
    1
    #define PTE_A (1L << 6) // 1 ->accessed    for lab detect accessed
  2. 在kernel/sysproc.c中实现syscall的外壳:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #ifdef LAB_PGTBL
    int
    sys_pgaccess(void) //for lab detecting accessed
    {
    // lab pgtbl: your code here.

    uint64 start_va; //开始的虚拟地址
    if(argaddr(0, &start_va) < 0)
    return -1;

    int page_num; //页面号
    if(argint(1, &page_num) < 0)
    return -1;

    uint64 result_va; //存储结果的buffer
    if(argaddr(2, &result_va) < 0)
    return -1;

    struct proc *p = myproc();
    if(pgaccess(p->pagetable,start_va,page_num,result_va) < 0) //真正的功能实现,在vm.c中实现
    return -1;

    return 0;
    }
    #endif
  3. 在kernel/vm.c中实现功能:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // for lab detect accessed
    int pgaccess(pagetable_t pagetable,uint64 start_va, int page_num, uint64 result_va)
    {
    if (page_num > 64)
    {
    panic("pgaccess: too much pages");
    return -1;
    }
    unsigned int bitmask = 0;
    int cur_bitmask = 1;
    int count = 0;
    uint64 va = start_va;
    pte_t *pte;
    for (; count < page_num; count++, va += PGSIZE)
    {
    if ((pte = walk(pagetable, va, 0)) == 0)
    panic("pgaccess: pte should exist");
    if ((*pte & PTE_A))
    {
    bitmask |= (cur_bitmask<<count);
    *pte &= ~PTE_A;
    }
    }
    copyout(pagetable,result_va,(char*)&bitmask,sizeof(bitmask));
    return 0;
    }
  4. 在kernel/defs.h中找到vm.c的位置,添加pgaccess()的原型。

!!注意,这三个实验的验证是通过make qemu,使用pgtaltest命令来验证。

2.22。ugetpid不通过,不知道为啥。

Lab: traps

研究系统调用是如何通过traps机制实现的。

关于traps机制:直译陷阱。异常的一种,请结合csapp笔记来看

实现一个用户级的trap handling

Lab: Copy-on-Write Fork for xv6

就是实现一个写时复制的fork

csapp:fork()为了给这个新进程创建虚拟内存,它创建了当前进程的mm_struct、区域结构和页表的原样副本。

它将两个进程中的每个页面都标记为只读,并将两个进程中的每个区域结构都标记为私有的写时复制。

Lab: Multithreading

实现一个用户级的线程包,来实现线程的切换,以此来加快程序运行速度。并且实现一个mem barrier

mem barrier机制

Lab: networking

为网络接口控制器写一个设备驱动

Lab: locks

parallelism、lock:关于锁和并发。有点复杂。不用写上去,搞不定

Lab: file system

为xv6系统添加大文件和符号链接

Lab: mmap

本文最后更新于 天前,文中所描述的信息可能已发生改变