Link Search Menu Expand Document

Due by 11:59 PM on Sunday, Nov 08

The System

The system is very similar to what you developed for project 2. However for obvious reasons there are quite a few differences.

Each mailbox should be identified by a unique id of type mailbox_id_t. When an application requests to open a mailbox, it needs to provide the name for that mailbox in string format. It will then be able to use the matching id for the rest of the system calls. Each mailbox can store an “unlimited” amount of data. However, that is limited by the amount available on the mailbox itself.

The data stored is binary data and should NOT be treated as strings – this means that the data may have zero bytes (i.e NUL terminators) embedded within them. To this end, you should NOT be using any string related functions on them (i.e. strlen() or strcpy()). For instance the following are both valid “messages” to put in the mailbox:

unsigned char data1[] = { 'a', 'b', 'c' };
unsigned char data2[] =  { 0x00, 255, 3, 0x00 };
This is very, very, very important. If you use string functions on the data you will get the wrong result and you will lose major points. DO NOT DO IT.

Data can be written and read from a mailbox with “random access”. This means that the calling program will identify where in the mailbox (i.e., the offset) to begin the read/write operation and provide how many bytes to read/write (i.e., the length). Once data is written to a mailbox it will stay there until it is explicitly overwritten by a process that has access to the mailbox (i.e., there is no destructive read and there is no notion of a “message state” like some IPC mechanisms that you may have seen in class)

You MUST provide a working locking/synchronization implementation for the system.

In addition to the synchronization, you must also implement the read_mbx_421 systemcall. You can implement it following the code pattern of the rest of the code (recommended) or whichever other way you see fit. As long as you do not alter the required functionality of the systemcall.

New system calls

You will notice that some systemcalls such as count, list and size are not included. While those are necessary for a really functioning system, we want to focus more on the synchronization part this time around. As such, the new system calls that make up the system are the following:

long mbox_init_421(void);
long mbox_shutdown_421(void);
long mbox_open_421(char __user* name, mailbox_id_t __user *id, uint64_t size);
long mbox_write_421(mailbox_id_t __user *id, uint8_t __user *data, uint64_t size, uint64_t offset);
long mbox_read_421(mailbox_id_t __user *id, uint8_t __user *data, uint64_t size, uint64_t offset);
long mbox_close_421(mailbox_id_t __user *id);
You are implementing mbox_read_421 yourselves.
It is very important that you add the system calls in this order exactly to the system calls table.

Remember from Project 0 that system calls are defined in the kernel by way of using a SYSCALL_DEFINE macro. So, for instance, the write_mbx_421 syscall from the list above would defined as follows:

SYSCALL_DEFINE4(mbox_write_421, mailbox_id_t __user *, id, uint8_t __user *, data, 
    uint64_t, size, uint64_t, offset) {
    /* Code goes here */
}

See the <linux/syscalls.h> file for more information about these macros.

Each system call returns an appropriate non-negative integer on success, and a negative integer on failure which indicates the error that occurred. See the <errno.h> header file for a list of error codes (this file is also located in /usr/include/errno.h). Suggested error codes for several error conditions are listed below (this does not necessarily cover all error cases you might encounter):

  • -ENOMEM: Out of memory during an allocation
  • -EFAULT: Supplied an invalid pointer or one that cannot be read/written for the entire requested length
  • -ENOENT: Invalid mailbox id specified
  • -EINVAL: Value passed is invalid
Please note the - in front of the error code. You always need to return a negative number in case of an error.

The kernel must be very careful with memory access. Remember that users often don’t properly error check their code very well and that malicious users do also exist. You should be very careful in your code to ensure that any pointers that come in from user-space. All parameters marked with __user above are checked sufficiently. Also, you should ensure to not leak private information outside the kernel, such as your internal data structure.

Usage of goto

You will notice that the code makes use of goto, something you have been told to avoid. This is one of the two valid use cases for goto. If you pay attention, you will see that allocated memory is always free’d at exactly one place per function, no matter where it is allocated or when the error occurred. This reduces the chances of errors an memory leaks. You will also notice that freeing and labels happen in the reverse order of their allocation.

If you do not know how goto works, here is a quick explanation. If some_error_condition is true, then code execution will go directly to the cleanup label. Meaning normal execution AND return 0 will be skipped and instead the lines that will be executed are free(whatever) and return -1. Execution will never flow upwards / go back to the if statement.

int my_function() {
    if (some_error_condition) {
        goto cleanup;
    }
    // Normal execution of the function
    return 0;// everything went ok

cleanup:
    free(whatever);
    return -1; // There was an error
}

Table of contents