File system
The goal of this lab is to implement the equivalent of the function mmap in xv6.
The system call (beginner level)
Start by getting the code from xv6:
- git clone https://gitlab.inf.telecom-sudparis.eu/csc4508/xv6-riscv.git if you start with a new copy
- or git checkout master if you already have a local copy of the repository
Then create a local branch to work:
- void* mmap(void* addr, int size, int prot, int flag, int fd, int offset). This function implements exactly mmap, but during the first part of the lab, you will ignore the prot and flag parameters.
- and void munmap(void* addr, int size)
These functions must be added in a new file named kernel/mmap.c. You always need to include the headers types.h, riscv.h and param.h in your file. You must also add the corresponding object $K/mmap.o to the list OBJS in the Makefile.
For the moment, the mmap function must display its arguments on the terminal before returning the pointer -1, which means that an error has occurred, and the munmap function must display its arguments on the terminal. To simplify error handling, create a mmap.h file in which you will add the following macro:
- open the file README in read/write mode (remember to include kernel/fcntl.h to have access to the O_RDONLY flags, or O_RDWR),
- calculate the file size with the fstat function (remember to include stat.h to have the definition of the stat structure),
- map the whole file to the address 0x10000000,
- display the content of the memory mapped on the terminal,
- unmap the file.
For each system call, check the return value and stop the program in the case of an error. For now, the program should terminate with an error since the mmap function returns an error.
Don't forget to add your test program to the UPROGS list in the Makefile.
To access the solution, you must first add the repository which contains the solution with the following command sequence:
Then, you can access the solution with the following command:
You can show the difference to the original code with git diff master.
Initial implementation (advanced level)
- delete the entries in the page table that have already been associated,
- free the physical pages already allocated.
Verify that your test program correctly displays the contents of the file. In order to speed up the tests you are going to perform, feel free to display only the first 207 characters of the file (this is its first paragraph).
To access the solution, if it is not already done, add the repository which contains the solution with the following command sequence:
Then, you can access the solution with the following command:
You can show the difference with the original code with git diff master (or git diff mmap-soluce-exo1 to see the difference with the previous exercise).
Software segment (adventurer level)
We now want to lazily map the file. Technically, in mmap, instead of mapping the file, you only need to save the association between the virtual address space (addr, size) and the file (fd, offset, size) in a structure. Then, as no page is physically associated with this structure, access to the area will generate a page fault. You must therefore modify how to handle these faults in xv6 to map the requested page if needed.
Modify the way to map a file so as to not give user access: remove the flag PTE_U when mapping the page (you can leave read and write permissions, they will be dealt with later). In case of page fault, simply add this permission back for the moment.
To do this, you need to modify the code that handles traps from the user space. Currently, a page fault simply prints a generic message about an "unexpected scause" and kills the process. If the cause is a load or a store page fault, try to load the page.
You can get the cause of the trap by examining the scause register with uint64 r_scause(). On a load page fault, scause == 0xd, and on a store page fault, scause == 0xf.
Note that the faulty address accessed by a process is recorded in the stval register at the time of the fault. You can find this address with the function uint64 r_stval().
Add definitions for the protections PROT_READ and PROT_WRITE in mmap.h, and treat these protections properly.
To access the solution, if it is not already done, add the repository which contains the solution with the following command sequence:
Then, you can access the solution with the following command:
You can show the difference with the original code with git diff master (or git diff mmap-soluce-exo2 to see the difference with the previous exercise).
Duplication of processes (guru level)
In order to be able to manage the sharing of segments between a parent and its child, you have to restructure the code.
To do this, instead of directly storing the segments in the processes, define a global array of segments with a maximum size of 1024. Then, in a process, instead of directly storing segments, store pointers to segments in this global array segments. Beware of concurrency issues, because the pool of segments is now shared among all processes.
You can amend the function procinit to initialize any lock you need.You may encounter a kernel panic sched locks when adding locks. It can happen if you hold a non sleeping lock when sched() is called, for instance because you go to sleep when taking a sleep lock.
It comes from the implementation of locks: in an effort to avoid deadlocks, xv6 disables interrupts while holding a lock. It effectively prevents preemption, so sched() ought to never be called on this core while the lock is held. However, sched() can be called explicitly, for instance via a call to sleep().
The solution is to use only sleep locks, or to manage to release the lock before going to sleep.
Add a MAP_SHARED flag to mmap.h.
When a segment is noted as MAP_SHARED, the segments are shared between a parent and its children instead of being duplicated upon fork. Otherwise, the child must duplicate the parent's segments as is currently done.
Update your test program to check that the parent and the child can communicate through the segment. For instance, make the child wait for some signal from the parent.
To access the solution, if it is not already done, add the repository which contains the solution with the following command sequence:
Then, you can access the solution with the following command:
You can show the difference with the original code with git diff master (or git diff mmap-soluce-exo3 to see the difference with the previous exercise).
Deleting a mapping (guru level)
Segmentation (guru level)
- A text segment containing the program code,
- A data segment containing the initialized data of the program,
- A bss segment containing the data not initialized, i.e. initialized with 0s, of the program,
- A stack segment of fixed size,
- A heap segment that can grow.
All these segments must be defined as not being of the type MAP_SHARED so that they are duplicated automatically during a fork().
To build these segments, you need to analyze and modify the exec function (file exec.c), especially starting from line 42.
Also modify the fork() function to stop calling uvmcopy which takes care of copying the memory of a process (the memory of the segments which you have just defined). Instead, you should always call proc_pagetable() which allows to create a new page table in which the kernel is mapped. If your code is correct, depending on the MAP_SHARED flag, the parent's segments will all be copied to the child, or shared with the child.
Copy-on-write fork, (master guru level)
We only provide a solution to the first four exercises. To access the solution, if it is not already done, add the repository which contains the solution with the following command sequence:
Then, you can access the solution with the following command: