Homework 3
W4118 Fall 2009
UPDATED: 10/12/2009 at 5:45pm EST
DUE: Thursday 10/22/2009 at 11:59pm EST

Individual non-programming problems are to be done individually. Group programming problems are to be done in your assigned programming groups. All homework submissions are to be made via Git using similar instructions as for previous homeworks but using the new repository for this homework. You will use an individual repository for submitting the individual non-programming problems, and a group repository for submitting the programming problems. Be aware that commits pushed after the deadline will not be considered. Refer to the homework policy section on the class web site for further details.

The Git repository you will use for the individual non-programming submission is

ssh+git://UNI@os1.cs.columbia.edu/git/UNI/hmwk3.written (Replace UNI with your own UNI)

The Git repository you will use for the group programming submission is

ssh+git://UNI@os1.cs.columbia.edu/git/TEAMID/hmwk3 (Replace TEAMID with your own TEAMID)

Individual Non-Programming Problems:

Exercise numbers refer to the course textbook, Operating System Concepts. Each problem is worth 3 points.

  1. Exercise 6.10

  2. Exercise 6.11

  3. Exercise 6.12

  4. Exercise 6.13

  5. Exercise 6.14

  6. Exercise 6.37

  7. Exercise 7.11

  8. Exercise 21.15

  9. How and why does the implementation of a spinlock differ between a uniprocessor kernel versus a multiprocessor kernel configuration in Linux?

  10. Describe the circumstances under which Linux uses atomic operations, spinlocks, reader-writer locks, and semaphores. Explain why each mechanism is needed.
Please complete and submit a private group programming assignment evaluation. For your convenience, the evaluation template to complete is already in your repository for your individual written assignment submission.

Group Programming Problems:

You will be using and modifying the Linux 2.6.27 kernel for this assignment. For all programming problems you will be required to submit source code, a README file documenting your files and code, and a test run of your programs. The README should explain any way in which your solution differs from what was assigned, and any assumptions you made. Refer to the homework submission page on the class web site for additional submission instructions.

Make at least ten commits with Git. The point is to make incremental changes and use an iterative development cycle. Follow the Linux kernel coding style.

Userspace controlled Semaphores. You will implement a system wide, userspace controlled, binary semaphore mechanism. The semaphore construct will be provided by a kernel-level implementation, but the policy for deciding which task to run when there is contention is determined by a userspace daemon that you implement. Given a set of tasks that are waiting on a semaphore, the userspace daemon will pick the task that gets the semaphore when it is available. This problem is worth 70 points.

  1. You will implement the semaphore mechanism by adding six system calls:
    int sem_open(const char *name);					/* syscall number: 333 */
    int sem_close(int semid);					/* 334 */
    int sem_down(int semid);					/* 335 */
    int sem_up(int semid);						/* 336 */
    int sem_list(struct sem_info *buf, size_t len, int wait);	/* 337 */
    int sem_grant(const char *name, pid_t pid);			/* 338 */
    
    struct sem_info {
    	int locked; /* 0 is unlocked, 1 is locked */
    	int num_waiters;
    	struct {
    		pid_t	tgid;
    		pid_t	pid;
    	} waiters[]; /* the array size is num_waiters */
    	char name[]; /* semaphore name, null-terminated */
    };
    
    The scope of a semaphore is system wide (i.e. two separate processes may access the same semaphore). Semaphores are uniquely identified by their name. In other words, different processes can refer to the same semaphore by using the same name.

    Your semaphore implementation should satisfy the following additional requirements:

    1. You should not wake up all tasks waiting on a semaphore, but only one at a time (avoiding the thundering herd).
    2. Each task should have its own set of semids, similar to the idea that each task has its own set of file descriptors. You will find it very helpful to maintain a semaphore descriptor table to keep track of the semids. When a task is created, it should be created with an empty semaphore descriptor table.
    3. When a task exits such that its semids are no longer in use, the referenced semaphores should be implicitly closed, which is the same behavior as close() with file descriptors.
    4. The system calls should return 0 on success unless stated otherwise, or the appropriate error code.
    5. The only kernel synchronization primitives that you are allowed to use for this assignment are spinlocks and atomic counters.
    6. Use the files kernel/usem.c and include/linux/usem.h to implement your semaphore mechanism.

  2. We will provide you with a userspace daemon to determine the order in which waiting tasks gain access to the semaphore. You should test your kernel implementation with the provided userspace daemon to make sure it is working properly. The daemon will have the characteristics described below and is available at:
    ssh+git://UNI@128.59.23.135/git/hmwk3.userspace (Replace UNI with your own UNI)