Lab 4: Shared Memory

Handed out Wednesday November 17, 2021

Due Wednesday December 1, 2021

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:

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:

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

*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().