Character Device Project

For this project you will extend the dummy character device that we looked at in class so that it can be written to and so that only one thread can access it at a time.

The device will simply be a 128 byte buffer. Writing to the device overwrites the current contents of the buffer and reading from the device retrieves the current contents of the buffer.

I recommend reading all of these instructions before getting started so you have a good sense of the goals of the entire project.

Testing Program

Before you change the module, write a program in test_hello.c which you will use to test it. You can try out this test program on the existing module’s functionality before modifying the module. As it is, writing to the device won’t change anything, but you’ll be able to see that a write was attempted by looking at the dmesg output.

The program should do the following:

System Calls

Rather than using fopen and the other C standard library file functions, use the POSIX system calls open, read, and write. You can read the man pages for these system calls with man 2 open, man 2 read, and man 2 write. You can also look at the example code from in_class-file_system_calls for an example.

All 3 of these system calls will return a negative value if there is an error, and will set the global variable errno to indicate what the error was. If there are any errors with these sytem calls, your test program should report them using the perror function and exit, returning errno. perror is aware of errno. It takes a string argument and prints out that string along with a description of the error to standard error.

So to open the device file in read/write mode and exit if there is an error, you will do something like this:

int fd = open("/dev/hello_char", O_RDWR);

if(fd < 0) {
    perror("Error opening device");
    return errno;
}

You will need to run your test program as root. If you do not, you will not have permissions to access the device. If you run the above code as a regular user, perror will print this:

Error opening device: Permission denied

You can run the program as root like this:

sudo ./test_hello

Reading User-entered Text

To get the string to write to the device from the user, you can use getline as we have done in the past, or write your own code to keep filling a buffer with the result of getchar() until you hit a newline.

Example Output

Here is the output from my test_hello program. Note that this is what the output will look like once you have completed the entire assignment. At first the string that you read from the device will be "Hello, world, I'm a character device!".

$ sudo ./test_hello
Opening device /dev/hello_char
Enter a string to place in the device buffer:
Hi there, device!
Writing "Hi there, device!" to the device buffer
Press enter to read from the device buffer

Reading from the device buffer
Read the following string from the device buffer:
Hi there, device!

Compiling, Loading, and Unloading the module

Running make will compile both the module and test_hello.c. If you only want to compile one or the other you can run make test_hello or make hello_char.ko. To clean up all the files created during compilation you can run make clean.

To load the module:

sudo insmod hello_char.ko

To unload the module:

sudo rmmod hello_char

During development when you make changes to the module code you will need to recompile it, unload the module, and then load it again.

Writing to the Device Buffer

The module you are given creates a string in the dev_read function named data which is copied to the user. Instead of copying a local string to the user, we want to have a buffer that is global within the module that the dev_write function can write to and the dev_read function can read from.

Declare a static array of char in hello_char.c before all of the function definitions. We want a buffer that is 128 bytes long, so #define a constant named BUFFER_LENGTH or something similar to be 128 and use that for the length.

You now need to change the dev_write function so that it writes the contents of the buffer that is passed in to the global static buffer. If the buffer that the user wants to write is longer than 128 bytes, you should only copy 128 bytes from the user’s buffer, since trying to copy more would overflow the device’s buffer.

The device’s buffer should always be a valid C string, which means that the stored string needs to end with the null character ('\0'). If the buffer that the user is writing is longer than 128 characters, or if the buffer that the user is writing does not end in a null character, you will need to add the null character to the device’s buffer in the appropriate location.

The kernel provides a function named copy_from_user which you can use to copy from the user’s buffer to the device’s buffer. It has the following prototype:

unsigned long copy_from_user(void *to, const void *from, unsigned long n);

It copies n bytes from the buffer from to the buffer to.

Synchronization

We only want one thread to be able to access this device at a time, so we need to add a mutex to the code.

You will need to include <linux/mutex.h> to be able to use the kernel’s mutex. Declare the mutex where all the other static variables are declared. A kernel mutex is declared with a macro like so:

static DEFINE_MUTEX(hello_char_mutex);

This defines a mutex named hello_char_mutex. The mutex still needs to be initialized, which you should do in the hello_char_init function. Initialize the mutex like so:

mutex_init(&hello_char_mutex);

In the dev_open function we want to try to lock the mutex. If the mutex is already locked we don’t want to block, because that will lock up the kernel. You can try to lock the mutex with this function call:

mutex_trylock(&hello_char_mutex)

This will return 1 if the lock was successfully aquired, or 0 if the mutex is already locked. If the mutex is already locked, log a KERN_INFO message that the device is already in use and return -EBUSY which will let the process that is trying to open the device know that it is already in use.

The dev_release function should unlock the mutex so that another process can open the device. Do this by calling this:

mutex_unlock(&hello_char_mutex);

The mutex must be destroyed when the module is unloaded. Do this in the hello_char_exit function like so:

mutex_destroy(&hello_char_mutex);

Testing

Test that your synchronization works as intended by running your test_hello program in a terminal. Write to the buffer and then let the program sit blocking waiting for you to press enter. In another terminal, try to run another instance of test_hello. It should immediately exit with an error saying that the device is busy.

Submission

Push your code to gitkeeper. For full credit you must meet the following requirements: