MIT6.S081 ---- Lab System calls

Posted by adeelahmad on Mon, 10 Jan 2022 07:04:14 +0100

Lab System calls

Abstract design of physical resources (file system management, memory usage, CPU switching, pipeline interaction); Three states of instruction execution (M,S,U), system call; Macro kernel and micro kernel; Process mechanism (S/U mode, address space, time slice), process purpose (enhancing isolation), process address space and state items, and process design idea; Overview of xv6 startup; Safety related.
Learn the principle and implementation of system call.

Lab preparation

calling convention

The calling convention of RISC-V generally transfers parameters between registers, with a maximum of 8 integer registers (a0-a7) and 8 floating-point registers (fa0-fa7).

When a parameter is passed in an integer register, the parameter is stored in an aligned even odd register pair, and the even register stores the least significant bit. In RV32, for example, the first parameter of the function void foo (int, long, long) is passed to register a0, the second parameter is passed to registers a2 and a3, and no parameter is passed to a1. Parameters greater than twice the pointer word size are passed by reference.

The part of struct that is not passed through the parameter register is passed on the stack, and the stack pointer sp points to the first parameter that is not passed in the parameter register.

The return values are stored in registers a0 and a1 or registers fa0 and fa1. Only when the floating-point value is a single floating-point value or a struct structure composed of only one or two floating-point numbers can be returned through the floating-point register. The return values of the other two pointer word sizes are returned with a0 and a1.

Larger return values are passed in memory. The caller allocates memory space and passes the pointer of the first parameter to the callee.

Procedure of system call

initcode.S puts the parameters of exec in registers A0 and A1 and the system call number in register a7. The system call number can index the syscalls array (function pointer table) (kernel/syscall.c:108). Execute ecall (send a request to the execution environment and execute ecall in X(U,S,M)-mode, which will generate environment-call-from-x-mode exception) instruction trap into the kernel, execute uservec (execute traps generated from user space), execute usertrap (handle interrupts, exceptions and system calls from user space), and execute syscall.

Syscall (kernel/syscall.c:133) retrieves the system call number from trapframe - > A7 and indexes syscalls. Call system call to implement sys_exec, store the return value in trapframe - > A0, which is also the return value of exec() called in user space. System calls usually return negative numbers for errors and 0 or positive numbers for success. If the system call number is invalid, syscall outputs error and returns - 1.

Parameters of system call

The system call implementation in the kernel needs to find the parameters passed by the user code. Because user code calls encapsulated functions, RISC-V calling convention convention places parameters in registers. The trap code of the kernel stores the contents of the user register in the trap frame of the process for use by the kernel. The kernel functions argint, argaddr and argfd take out the parameters of the specified system call placed in the trap frame. They all call argraw (kernel/syscall.c).

Some system calls use pointers as arguments, and the kernel must use these pointers to read and write user memory. For example, the exec System Call passes to the kernel an array of pointers to an array of parameters stored in user space. These pointers pose two challenges:

  • User code may have bugs or malicious code, or an illegal pointer passed to the kernel, or the pointer intentionally points to kernel space rather than user space.
  • xv6 kernel page table mapping is different from user page table mapping, so the kernel can not use ordinary instructions to access data from the address provided by the user.

The kernel implements the function of secure data transmission to the address provided by the user. Here is the next chapter. You can complete the experiment by learning to use copyout in this chapter.

System call tracing

Implement a system call, which can print the information of the specified system call.
Determine the specified system call by receiving the parameter mask. When the system call will return, print its information (process id, system call name, return value). Child processes inherit this mask and do not affect other processes.

Add int tracemask in struct proc to determine which system calls need trace. This property does not require locking. Initialize and clear this property in allocproc and freeproc.

Add the implementation in syscall (kernel/syscall.c)

if ((p->tracemask & (1 << num)) != 0) {
    printf("%d: syscall %s -> %d\n", p->pid, syscallnames[num-1], p->trapframe->a0);
}

Implement sys in (kernel/sysproc.c)_ trace.

// Set mask that specify which system calls to trace.
uint64
sys_trace(void)
{
    int tracemask;

    if(argint(0, &tracemask) < 0)
      return -1;

    myproc()->tracemask = tracemask;

    return 0;
}

Refer to hints for other configurations.

Sysinfo

Implement a system call: collect the free memory size and number of processes of xv6, and return the value to user space.

The kernel that completes the Sysinfo system call in (kernel/sysproc.c) implements sys_sysinfo.

// Get the number of bytes of free memory,
// and the number of processes whose state is not UNUSED.
uint64
sys_sysinfo(void)
{
    uint64 uinfop; // user pointer to struct sysinfo

    if(argaddr(0, &uinfop) < 0)
        return -1;

    return setsysinfo(uinfop);
}

Complete setsyinfo and procnum in (kernel/proc.c). Pass sysinfo information to user space and count the number of processes respectively.

// collect the nunmber of processes.
// Return the number of processes whose state is not UNUSED.
uint64
procnum(void)
{
    struct proc *p;
    uint64 nproc = 0;

    for (p = proc; p < &proc[NPROC]; p++) {
        acquire(&p->lock);
        if (p->state != UNUSED) {
            nproc++;
        }
        release(&p->lock);
    }

    return nproc;
}

// Set struct sysinfo and copy back to user space.
int
setsysinfo(uint64 addr)
{
    struct proc *p = myproc();
    struct sysinfo info;

    info.freemem = kremainsize();
    info.nproc = procnum();

    if(copyout(p->pagetable, addr, (char *)&info, sizeof(info)) < 0)
      return -1;
    return 0;
}

Complete kremainsize in (kernel/kalloc.c) to realize the statistics of free memory.

// collect the amount of free memory.
// Returns the number of bytes of free memory.
uint64
kremainsize(void)
{
    struct run *r;
    uint64 freepg = 0;

    acquire(&kmem.lock);
    r = kmem.freelist;
    while (r) {
        freepg++;
        r = r->next;
    }
    release(&kmem.lock);

    return freepg << 12;
}

Topics: Operating System