===================== Lab 4: Shared Memory ===================== **Bonus, no firm deadline, submit before the final exam** --------------------- Objectives --------------------- * Implement Shared Memory --------------------- Preliminaries --------------------- For this assignment we will use some starter code which is needed for Lab 4 (the same base used for Lab 3). You can get the starter code from the `lab4 repository `__ on github. ------------------------- Implement Shared Memory ------------------------- In this assignment you are implementing support to enable two processes to share a memory page. This is implemented by having an entry in both process page tables point to the same physical page. Start by looking at the user program `shm_cnt.c `__. In this program we fork a process, then both processes open a shared memory segment with the same id using: :: shm_open(1,(char **)(&counter)); For this system call, the first parameter gives an id for the shared memory segment, and the pointer is used to return a pointer to the shared page. By having this pointer be of type ``shm_cnt``, we can access this struct off of this pointer and we would be accessing the shared memory page. The code then proceeds to have both processes go through a loop repeatedly incrementing the counter in the shared page (acquiring a user level spin lock to make sure we don't lose updates; test your program without the lock and see if it makes a difference). The ``uspinlock`` is implemented in the starter code in ``uspinlock.c`` and ``uspinlock.h`` -- take a look. At the end, each process prints the value of the counter, closes the shared memory segment and exits using ``shm_close(1)``. One of them should have a value of 20000 reflecting updates from both the processes. Check your code without the spinlock to see if you lose updates. Your task is to implement ``shm_open`` and ``shm_close``. They are already added as system calls; you should write your code in shm.c ``shm_open`` looks through the shm_table to see if this segment id already exists. If it doesn't then it needs to allocate a page and map it, and store this information in the shm_table. Don't forget to grab the lock while you are working with the ``shm_table`` (why?). If the segment already exists, increase the reference count, and use ``mappages`` to add the mapping between the virtual address and the physical address. In either case, return the virtual address through the second parameter of the system call. ``shm_close`` is simpler: it looks for the shared memory segment in ``shm_table``. If it finds it it decrements the reference count. If it reaches zero, then it clears the shm_table. You do not need to free up the page since it is still mapped in the page table. Okay to leave it that way. --------------- Survival Guide --------------- For this assignment, you are given a lot of starter code, and you only have to implement the two system calls ``shm_open`` and ``shm_close``. The system calls have been already added and all you have to do is to fill in the implementation in ``shm.c`` If you open up this file, you'll see the following data structure defined: .. code-block:: c struct { struct spinlock lock; struct shm_page { uint id; char *frame; int refcnt; } shm_pages[64]; } shm_table; This defines the shared memory table that we will use to keep track of up to 64 pages of shared memory. Each page has: * An id, this is an integer given by the program to specify the shared memory segment. Two programs that shm_open the same id should get the same physical page. * A pointer to the physical frame. This is a pointer to the physical page that we will share. * A reference count, which indicates the number of processes sharing this page. If we close the shared memory region, we don't want to remove the page unless no one else is sharing it. The assignment describes shm_open as: "shm_open looks through the shm_table to see if this segment id already exists. If it doesn't then it needs to allocate a page and map it, and store this information in the shm_table. Dont forget to grab the lock while you are working with the shm_table (why?). If the segment already exists, increase the refence count, and use mappages to add the mapping between the virtual address and the physical address. In either case, return the virtual address through the second parameter of the system call." This is basically a full description of what you need to do. To break it down in more detail. Look through the shm_table to see if the id we are opening already exists. Two cases: * **Case 1**: It already exists, which means another process did a ``shm_open`` before us. In this case, we find the physical address of the page in the table, and map it to an available page in our virtual address space. To map the page (i.e., add it to the page table) you need to use the ``mappages`` function which we saw already as part of ``allocuvm``. ``mappages`` prototype looks like this: .. code-block:: c int mappages(pde_t *pgdir, void *va, uint size, uint pa, int perm); /* * pgdir is the page directory pointer (which you can get from the proc * structure for your process. * * va is a free virtual address you want to attach your page to (e.g., * sz, perhaps rounded up). * * size is the size you are mapping, which in our case is a single page (i.e., PGSIZE). * * pa is the physical address which is the frame pointer you get from shm_table * (but pass it through the V2P macro) * * permissions are a set of PTE permissions. Use PTE_W|PTE_U to say the * page is writeable and accessible to the user. */ Finally, you increment ``refcnt`` and return the pointer to the virtual address using something like .. code-block:: c *pointer=(char *)va; You should also update sz since your virtual address space expanded. * **Case 2**: shared memory segment does not exist (not found in the table), which means we are the first process to do ``shm_open()``. In this case, we find an empty entry in the shm_table, and initialize its id to the id passed to us. We then ``kalloc`` a page and store its address in frame (we got our physical page). Finally, we set the ``refcnt`` to 1. At this point, the remaining implementation is similar to case 1: we map the page to an available virtual address space page (e.g., ``sz``), and return a pointer through the pointer parameter. That's it! Be careful to use the embedded spin lock to avoid race conditions on the shm_table (do you see how that can be a problem?). Basically, use the acquire and release calls that are used in shm_init at the appropriate places in your code. Figuring out that you are done correctly? Run the shm_cnt program that is given to you. The last process to exit should print 20000 for the counter. Since each process increments the counter only 10000 times, this means that they successfully shared the page. You may be tempted to use ``allocuvm``. Unfortunately you cannot for two reasons: 1. In case 2, you don't want to allocate a physical page, only to ``mappages`` to an existing page. 2. In case 1, even though you need to allocate and map a physical page, you need a pointer to the frame. While you can dig it out from the page table, it is a bit tricky. So, its simpler to do your own ``allocuvm()`` in case 1 where you need it, and this way you have the pointer to the frame returned from ``allocuvm()``.