Project 2: Synchronization



Summary
  • Due May 22, 2014, 11:59:59PM
  • Implement semaphore for Minix and use it to solve synchronization problems
  • To be completed in teams of two
This project is all Minix, and requires the addition of a new Minix service. So please start as soon as possible. Do not wait. There are actually two separate parts to this project. But unlike the prior project, they cannot be parallelized, and must be done sequentially. You will need to build a fully working semaphore service before you can solve the synchronization problem. You will need to work closely with your partner to maximize your productivity for both parts.

Implementing a Semaphore Service

Initial note: there are files in the current kernel called sem.c and sem.h. Therefore it will probably be easier if you call your service something else ("sema" should be a good choice).

By now you should be aware of the fact that pretty much all functionality inside Minix is implemented as separate services connected to user applications via messages. Since Minix does not have a current way to synchronize processes, the first part of this project is to add semaphore support to Minix by implementing a semaphore service. This should be implemented as a system service similar to the process manager (PM) or the virtual file system (VFS). While your semaphore service needs to start up at boot time (see man boot), it need not be part of the boot image - the boot image should be for those services required during boot. The service needs to understand four different messages: sem_init, sem_release, sem_up and sema_down. The semantics of these four messages should be the following:

  • int sem_init (int start_value): This function creates a SEM_INIT message that is sent to the semaphore service. The parameter start_value specifies the initial value for the semaphore. It can be an arbitrary integer value. Typically, for a mutex (i.e. binary semaphore), this value would be +1. Once a semaphore has been initialized, it is considered active. Your semaphore service needs to support unlimited semaphores, that is until memory is exhausted.

    On success this function should return the next available semaphore number starting with 0. An out of memory failure should be reported by returning ENOMEM. All other errors should return the appropriate error code.

  • int sem_down(int semaphore_number): This function creates a SEM_DOWN message that is sent to the semaphore service. This call can only be invoked for an active semaphore. Calling it on an uninitialized semaphore should return the error EINVAL. This function implements the standard semantics ofsemaphore.p(). That is, the call decrements the counter of the corresponding semaphore by one. When the counter (after the decrement) has a value of < 0, then the calling process should be put to sleep (waiting in the queue that corresponds to the semaphore). On success sem_down should return OK (value 0).
  • int sem_up(int semaphore_number): This function creates a SEM_UP message that is sent to the semaphore service. This call can only be invoked for an active semaphore. Calling it on an uninitialized semaphore should return the error EINVAL. This function implements the standard semantics of semaphore.v(). That is, the call increments the counter of the corresponding semaphore by one. When there is at least one processes waiting (sleeping) in the queue, the first process that was put to sleep should be woken up (i.e. sleep and wake up is FIFO). On success sem_up should return OK (value 0).
  • int sem_release(int semaphore): This function creates a SEM_RELEASE message that is sent to the semaphore service. This call can only be invoked for an active semaphore. Calling it on an uninitialized semaphore should return the error EINVAL.. The purpose of this function is to release an active semaphore and put it back into the inactive state. This function must fail with the error EINUSE, a constant you must define in errorno.h, when there are currently processes waiting in the queue of the semaphore that should be released. On success sem_release should return OK (value 0).

Each of the semaphore functions should complete in a constant time, thus it is not simply okay to maintain a linked list of semaphores and perform a O(n) search for the correct one. You will need to use your knowledge of data structures to support a constant lookup time for each operation. The one exception is in sem_init where it is okay to take longer every once in awhile in order grow your data structure. However, the number of calls to sem_init that are accomplished in constant time should double each time the data structure is grown.

Implementing a System Call

Adding a new system/kernel call in Minix is not too difficult. It involves adding wrapper functions in /usr/src/lib/libc/sys-minix/ to make the call, header files in /usr/src/include/, and a server in /usr/src/servers/ to receive those new system calls and handle them. You will also need to make changes to various makefiles to ensure your new code gets compiled with the system. Finally you will need to define additional message types and parameters in the correct com.h so you can pass your messages. After you implement the server you will also need to set it to start on boot and accept messages.

As with other components, look at existing system calls and servers to figure out how things work. In particular, since you already have some familiarity with sched and pm, you might model your server code structure off of one of those (see more details below).

NOTE: this project will require more than just make hdboot, because we will be changing code outside the kernel. Any time you make changes in the kernel you must run make hdboot or rebuild the world, but often if you only make changes outside the kernel you can get away with running make in that directory (or some parent directory).

Some files may not get built/moved without a complete world build or setting lots of configuration parameters (/usr/src/etc/system.conf in particular), so if you are only changing one of those files, you can get around a complete world build by manually copying those files to their proper location (you may have to figure out where this is). This only works for a small number of system scripts/header files, any source code must be compiled as normal. You might also try running make includes etcforce from /usr/src/ and see if it installs the files you need.

Additional Information:
The key for system calls is that user processes must be able to use them to send messages to your semaphore service. you should take a close look at existing code to see how this is done. Due to the fact that the semaphore service is not included in the boot image, it cannot have an explicit process number. Thus you will need to determine the semaphore service's process number dynamically as part of the system call. There is a convenient function, minix_rs_lookup, which should help you with this. Once you have added the code for your system calls, make sure that the handler function receives the data that you send it (such as the semaphore number). Also, ensure that all files that you add are also added to the appropriate Makefiles.

Implementing a System Service
The semaphore service will require two things, making a service that is started on boot up, and putting processes to sleep and waking them up at the appropriate time. To figure out how to build a service, take a look at an existing example such as the sched system service (found under /usr/src/servers/sched). If you look at the code, you'll see routines to initialize and exit the server, routines to get and handle messages, and code to dispatch messages to different handler routines. All this is found in main.c, but code that does the real work is mostly in schedule.c. You can easily copy this code into your own directory like /usr/src/servers/sema and adapt it to suit your own needs. You'll likely keep the main dispatch loop, but obviously handle the different sema messages and send them to your semaphore routines.

You should define your messages in /usr/src/include/minix/com.h. Take a look at how other messages are defined. You'll need to set the service's permissions correctly in /usr/src/etc/system.conf to allow user processes to send messages to your semaphore service. Finally you will need to update various makefiles to ensure your semaphore service is fully built and installed. Take a look at other services, and trace where those files show up in Makefiles to help you determine where your classes need to be.

To get your service to actually run when the system boots, you will need to add it to one of the rc initialization scripts. Try to find where other servers such as printer or random are started to learn how to start your service.

Once you have the majority of your server code in place, you can test to see if it starts up and shuts down successfully by doing the following. First build your modified system (see NOTE above). After rebooting your system you should be able to start up your semaphore server via service up /usr/sbin/sema and then stop your server via service down sema.

Now you should have all the pieces to allow user processes to send messages to your service. Test to see if a user program can use your wrapper functions in libc to send messages to the new service. Once you have the communication component and the system call working correctly, then you can worry about implementing the actual semaphore functionality. There's an online doc on how to implement a semaphore service (actually lots of useful info about Minix) here. Finally, make sure you do some testing to check that your functionality works, and a process can sleep on a semaphore using sem_down and be woken up by a sem_up call later.

Part II: Using Semaphores to Solve a Synchronization Problem

Given the semaphore service, you'll need to use semaphores to solve a synchronization problem. There are a constant stream of companies coming to UCSB to recruit students and entice them with pizza. Unfortunately, both graduate and undergraduate students are hungry, and while pizza can be replenished, there is only so much pizza at one time. There's an agreement between the grad and undergrad students that undergrad students can eat the pizza as well, as long as the grad students are not in the room at the same time. If a grad student and an undergrad are in the same room at the same time, a grad student will tormet the undergrad (and dire consequences will follow). There are two tables of pizza in a single room, 6 total graduate students and 2 undergrads (small department). If a grad student is eating from either table, he/she will see the other table as well. If grad students are not eating, then they will not be in the room, and will not see any undergrads who are in the room. Finally, you cannot starve either the grads or the undergrads. Only one grad or undergrad can eat at a single table at any one time (but of course, 2 total grads or 2 total undergrads can eat at separate tables and share the room). Your job is to write two programs: one for the grad and one for the undergrad student, and use semaphores to synchronize the two types of students so that no undergrad will ever be tormented by a grad. In addition to submitting your code, also add a pizza.txt file that explains why your solution does not starve either student population.

For simplicity, assume that there is an unlimited amount of pizza, but there are only two tables to eat from. Thus only two students can eat at any given time.

Submission Process

Your submission must be submitted prior to the deadline in order to be graded. Do a man turnin to find more info about the turnin program. To submit:

  • Ensure your current working directory is a directory containing:
    • grad.c -- The file containing the code synchronize 6 graduate students who want pizza.
    • ugrad.c -- The file containing the code synchronize 2 undergraduate students who want pizza.
    • pizza.txt -- The file explaining your pizza solution and why it does not cause starvation.
    • a file named patch that contains your changes to the Minix source. The patch must apply cleanly to a fresh source code tree and successfully run your semaphore service on reboot after running make world under /usr/src/. The patch must created on your dev machine via:
      diff -ruNp minix_src_clean/ proj2/ > patch
      
  • Execute the turnin program:
    turnin proj2@cs170 grad.c ugrad.c pizza.txt patch
    

You can execute turnin as many times as required. The most recent submission prior to the deadline will be used for grading. You do not need to inform the TAs if you intend on using your two day extension for this project. Any submissions past the deadline will be assumed to be using your 2-day extension. If you have already used your extension, then your latest version before the original deadline will be graded.