Introduction && Link:

It is a Note about The MIT course 6.S081. arranged by following it’s lab order
https://pdos.csail.mit.edu/6.S081/2021/index.html
https://pdos.csail.mit.edu/6.S081/2021/labs/util.html

Lab1 Utilities

Key Points

  1. The kernel uses the hardware protection mechanisms to ensure that each process executing user space can access only its own memory
  2. Fork creates a new process, called the child process, with exactly the same memory contents as the calling process

pingpong

  • what is the difference of two code block

    1
    2
    3
    int fd[2];
    pid=fork();
    pipe(fd);
    1
    2
    3
    pid=fork();
    int fd[2];
    pipe(fd);

    all have 4 fd genereated
    but the first one two process’s fd have no connection.
    and the second one has

  • remember to exit at every child process, otherwise it will execute the code below

xargs

  • what is char* a[10]?

    format a = {char* char* …. char*}

  • exec(command, argv)

    the argv[0] should be not be filled with parameter but with command name

  • there are two types format: “123\n456” and 123 456, how we deal with it.

    we can devide with space of \n and treate “ as one of the char, delete it at the last step

  • we should clear all the uninitial string otherwise we will get random value like “" which will cause a lot of confusion to us
    random value
  • if you wanna pass char par[10][10] as a parameter in function you should write prototype like below

    func(char * par[10][10]), that means you should write out the dimension of it

  • what is the difference

    char *par[] = {}; char * par[10] = {}; char *par[10], the former two are empty array, the last one is the right defination

  • why we should fork and call exec

    exec system call will replace the the calling process, that is why

  • what is the difference between strlen and sizeof
    pls Do use sizeof with memset
  • we can compare char with == but we should compare string by using strcmp
  • “” is a string ‘’ is a char
  • how to use pointer array properly
    1
    2
    3
    4
    5
    char *pass_par[10]; 
    char * test = "xargs";
    pass_par[0] = test;
    printf("%s\n", pass_par[0]);

Lab2 System calls

Mode

mode

Machine
mode is mostly intended for configuring a computer

supervisor mode is for executing privileged instructions: for example, enabling and disabling interrupts, reading and writing the register that holds the address of a page
table

CPUs provide a special instruction that switches the CPU from user mode
to supervisor mode. (RISC-V provides
the ecall)

xv6 is monolithic kernel, with all kernel in one mode

Kernel

the hardware only uses the low 39 bits when looking up virtual addresses in page tables; and
xv6 only uses 38 of those 39 bits. Thus, the maximum address is 2
38 − 1 = 0x3fffffffff

Each process has two stacks: a user stack and a kernel stack

In Entry.S it’s _entry set up a stack so that xv6 can run C code start.c

System call tracing

Preparation:

  1. read at least twice the instructions
  2. add print command to check the execute order of each function
  3. you can check how the kill pass all the argument

Note:

  1. The most important part is how to pass the argument to the system call
  2. To read the document, you will know the argument is stored in which Register, and you can use function argint
    to “Fetch the nth 32-bit system call argument”

Sysinfo

Preparation:

  1. the trick part about the compilation is pls use sys_sysinfo instead of sys_info
  2. A process’s most important pieces of kernel state are its page table, its kernel
    stack, and its run state.
  3. sysinfotest -> kernel/sysproc.c -> kernel/kalloc*proc

Note:

  1. The most important part is to understand the function copyout
  2. for counting proc, we should caculate all the proc that not in UNUSED mode
  3. for counting mem, we should times the page size

Lab3 Page Table

Note:

  1. VA and PA is translated by map
  2. MMU won’t store any map
  3. kernel will write the map value into the satp register
  4. page is 4 KB in the Risk V, which required
  5. for satp, it is divided into three pieces 25+27+12, 12 is the byte within the page, index is for the map to translate to physical page
  6. every process keeps it’s own page table
  7. Kernel is using “direct mapping”

The Steps to translate the virtual to physical

  1. the virtual address is divided into PTE-selector and offset, for example 9+9+9(PTE-selector) + 12(Offset)
  2. for the first page table it is satp + value(first 9 bit), in it we can get the PPN of next page(it’s physical address)

concepts of each key defination

  1. pte = page table entries, it is actually an address
  2. ppn = physical table number
  3. pagetable is an array string like below
    [*pte1, *pte2, *pte3 …. *pte512]
  4. Each PTE contains a 44-bit physical page
    number (PPN) and some flags
  5. Sv39 Risc-V only uses bottom 27 bit for virtual address, that means 27 power of 2 is the max number of PTES
  6. each pte actually has 64 bit(8 byte), but it only uses the bottom 54 bits
  7. each page table has 512 ptes, in total it is 4096 byte
  8. *pte = ppn(44) + flag(12), pte is an address it is value is ppn+flag
  9. physical address is 56 bit
    10 last physical address(56) = ppn(44) + Virtual offset(12)
  10. the middle physical(56)= ppn(44)+ empty zero(12)
  11. every VA will has a unique three level pagetable chain
  12. walk function will create a link from VA to pte based on one certain first level pagetable
  13. mappages will store the pa to the last pagetable pte

rewrite Walk in non recursive way

script
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
#define BIT2OFFSET(level, va)  ((va >> 12) >> level*9) & 0x1ff
#define PTETOPA(pte) ((pte >> 10) << 12)
#define PATOPTE(pa) (((uint64)pa >> 12) << 10)
pte_t *
walk(pagetable_t pagetable, uint64 va, int alloc)
{
if(va > MAXVA)
panic("walk");

// get pte from first level page
uint64 *first_pte = &pagetable[BIT2OFFSET(2, va)];
// if valid first page
if((*first_pte & 0x1) != 0) //valid
{
//get next pagetable address
pagetable = (uint64 *)PTETOPA(*first_pte);
uint64 *second_pte = &pagetable[BIT2OFFSET(1, va)];
if((*second_pte & 0x1) != 0)
{
pagetable = (uint64 *)PTETOPA(*second_pte);
return &pagetable[PX(0, va)];
}
else //second lvl page create
{
//uint64 *second_pte = (uint64*)pagetable[BIT2OFFSET(1, va)];
if(!alloc || ((pagetable = kalloc()) == 0))
{
return 0;
}
memset(pagetable, 0, PGSIZE);
*second_pte = PA2PTE(pagetable) | PTE_V;
// once created, go to next lvl
pagetable = (uint64 *)PTETOPA(*second_pte);
//uint64 *third_pte = &pagetable[BIT2OFFSET(0, va)];
return (pte_t*)PTETOPA(*second_pte);
//return &pagetable[PX(0, va)];
}
}
else //if first is not valid
{
if(!alloc || ((pagetable = kalloc()) == 0))
{
return 0;
}
else
{
memset(pagetable, 0, PGSIZE);
*first_pte = PA2PTE(pagetable) | PTE_V;
}
pagetable = (uint64 *)PTETOPA(*first_pte);
uint64 *second_pte = &pagetable[BIT2OFFSET(1, va)];
if((*second_pte & 0x1) != 0)
{
pagetable = (uint64 *)PTETOPA(*second_pte);
//uint64 *third_pte = &pagetable[BIT2OFFSET(0, va)];
return &pagetable[PX(0, va)];
}
else //second lvl page create
{
uint64 *second_pte = &pagetable[BIT2OFFSET(1, va)];
if(!alloc || ((pagetable = kalloc()) == 0))
{
return 0;
}
else
{
memset(pagetable, 0, PGSIZE);
*second_pte = PA2PTE(pagetable) | PTE_V;
}
// once created, go to next lvl
pagetable = (uint64 *)PTETOPA(*second_pte);
//uint64 *third_pte = &pagetable[BIT2OFFSET(0, va)];
return &pagetable[PX(0, va)];
}
}
}

Free process’s memory

  1. it should free it’s page table and the physical memory it refers to
  2. Is only the leaf pagetable has the PTE_R PTE_W flag?
  1. pretty easy, we can just copy and modify freewalk to print

Speed up system calls(Task)

preparation

  1. check the user side of ugetpid, to understand how the app gonna use the interface

    script
    1
    2
    3
    4
    5
    int ugetpid(void)
    {
    struct usyscall *u = (struct usyscall *)USYSCALL;
    return u->pid;
    }

    the function directly convert the USYSCALL Address to struct usyscall, and return the stored value of pid

  2. To do this, we can kalloc a page and set it readable to userspace, Then in Kernel space we can directly store the pid into it

  3. USYSCALL is a virtual address

  4. mappages’s main purpose is to map va to pa, it use walk() to create a pte for the va, and store the pa into that pte

  5. Each process has a separate page table(First Level), and when xv6 switches between processes, it also changes
    page tables

  6. we should create a new pagetable specify for this task, that means each process has two pagetable,
    one is the original, other is the store pid one

Plan

  1. you know the Physical Address
  2. you know the VA address
  3. you can use mappages to map va to physical
  4. then you can init this value all done

A kernel page table per process(Task)

  1. modify struct proc add kernel field
  2. every User process will keep an extra identical kernel_pagetable
  3. at before, all the process share the same kernel page table

Simplify (HARD)

  1. this feature should use the User_kernel_page_table

when did it translated

copyout and copyin copy data to and from user virtual addresses provided as
system call arguments; they are in vm.c because they need to explicitly translate those addresses in order to find the corresponding physical memory
and after the user_kernel_page_table setup, it will translate by hardware

how this Virtual address range?

From 0 to bottom

Detecting which pages have been accessed (HARD)

how to determine PTE_A?

Inside riscv-privileged.pdf

where to set PTE_A

Risc-V done by itself: Each leaf PTE contains an accessed (A) and dirty (D) bit. The A bit indicates the virtual page has
been read, written, or fetched from since the last time the A bit was cleared. The D bit indicates
the virtual page has been written since the last time the D bit was cleared

Trap

Three main way to trigger Trap

1.System call, user executes the ecall

2.Exception

3.Device interrupt

Trap’s feature

1.transparent

2.Three distinct case trap: user space / kernel space /timer interrupt

Trap procedure

1.hardware action, Vector Assembly

2.C Trap handler

3.system call & drver

Kernel will do instead of CPU

  1. switch to kernel page table

2.switch to a stack in the kernel

  1. save register

What is stvec

The kernel writes the address of its trap handler here; the RISC-V jumps here to
handle a trap
Without stvec Then a trap could switch to supervisor mode while still running user instructions

What is trampoline

as the name it is, it is a trampline code for user process, it has all the important function such as uservec.
and it is stored at every pages