Overview
You will write a loadable kernel module. If you have an idea which you can convince me is a good idea that can not be done as a module, but only via direct modification to the kernel source, then you may do that, but you must convince me that that is the only way to do it and to do it right before proceeding along that path.
Idea Proposal
Using what you have learned in class and in labs, Innovate an idea for a useful kernel module. Write a one-page proposal for
(a) what the idea is,
(b) how long you will need to take it
(c) if you will need to be on a team in order to finish it
(d) who your team members will be
(e) why you need a team to finish on time
(f) the division of labor between team members
(g) what resources you have found already that will help you
(h) why you believe you will be able to finish it on time
That one-page proposal is due on the first friday of April. It will be graded on a-h as well as on (i) is it a kernel module, and (j) is it innovative and/or useful (useful without innovative is fine, but neither innovative nor useful is not). Thus, 10 criteria, 10 points for each criteria, for a 100 point grade. Also, if you fail to convince me that you will be able to both learn from this and complete it on time, you will be given a different project, of the instructor's choosing.
Students that have taken Technical Report Writing have found it useful to have this idea proposal follow the same format as the feasibility study assigned in that class.
The exact details of how to write a kernel module shouldn't be necessary for this proposal. In general, your module can execute code when it is loaded and when it is unloaded, and typically the sort of code that will be executed when loading is code that registers your functions to be called at certain times (perhaps even replacing other functions in the kernel prior to loading) and unregisters those functions when unloading the module. Think in those general terms when writing this proposal. Feel free to search the web for more detailed resources, but be sure that whatever you find is valid for the 2.6 line of Linux kernels.
How to write a kernel module - Example 1
Code for a simple loadable kernel module can be found on a following page.
A makefile for a simple loadable kernel module can be found on another following page. When you type it in, be sure to use the tab key instead of spaces. I want to give you this code to try out before trying out the second example, because it is simpler and so that you realize that this isn't as intimidating of a process as it might seem.
Assuming that you're root, and that the kernel module is named hello.ko then the following would load it into memory:
insmod hello.ko
If it printed out anything via printk when it was loading then you could see it by checking the end of dmesg via the following:
dmesg | tail
When you want to unload that kernel module, you can use the following command:
rmmod hello.ko
If it printed out anything via printk when it was unloading then you could see it by checking the end of dmesg via the following:
dmesg | tail
obj-m := hello.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD)
clean:
rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
rm -rf .tmp_versions
#include
static int hello_init(void)
{
printk("Hello world\n");
return 0;
}
static void hello_exit(void)
{
printk("Goodbye world\n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_AUTHOR("CSIS 430");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Hello world");
How to write a kernel module - Example 2
Creating a loadable kernel module in Linux that uses /proc to allow user-level code to access it.
Code for a simple loadable kernel module that provides a 1K buffer in /proc/buffer1k can be found on a following page.
A makefile for that simple loadable kernel module can be found on another following page.
When you type it in, be sure to use the tab key instead of spaces.
Assuming that you're root, and that the kernel module is named buffer1k.ko then the following would load it into memory:
insmod buffer1k.ko
If it printed out anything via printk when it was loading then you could see it by checking the end of dmesg via the following:
dmesg | tail
To use this example, you need to be root to write to it:
echo "hi" > /proc/buffer1k
But any user can read from what was written into that buffer.
cat /proc/buffer1k
When you want to unload that kernel module, you can use the following command:
rmmod buffer1k.ko
If it printed out anything via printk when it was unloading then you could see it by checking the
end of dmesg via the following:
dmesg | tail
obj-m := buffer1k.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD)
clean:
rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c
rm -rf .tmp_versions
/**
* buffer1k.c - create a 1k buffer in /proc at /proc/buffer1k
*
*/
#include /* Specifically, a module */
#include /* We're doing kernel work */
#include /* Necessary because we use the proc fs */
#include /* for copy_from_user */
#define PROCFS_MAX_SIZE 1024
/* the above is why this is 1k */
#define PROCFS_NAME "buffer1k"
/* where in /proc we're puttying this */
/**
* This structure hold information about the /proc file
* Note that we can declare static variables in a kernel module.
* This is far better than declaring a global as it will be module specific.
*/
static struct proc_dir_entry *Our_Proc_File;
/**
* The buffer used to store characters for this module
*/
static char procfs_buffer[PROCFS_MAX_SIZE];
/**
* The size of the buffer
* Note that this can be at most PROCFS_MAX_SIZE
*/
static unsigned long procfs_buffer_size = 0;
/**
* This function is called then the /proc file is read
*
*/
int
procfile_read(char *buffer,
char **buffer_location,
off_t offset, int buffer_length, int *eof, void *data)
{
int ret;
printk("procfile_read (/proc/%s) called\n", PROCFS_NAME);
if (offset > 0) {
/* we have finished to read, return 0 */
ret = 0;
} else {
/* fill the buffer, return the buffer size */
memcpy(buffer, procfs_buffer, procfs_buffer_size);
/* note that yes,
* we can safely use certain standard library functions
* such as memcpy,
* but not any that might actually call the OS
*/
ret = procfs_buffer_size;
}
return ret;
}
/**
* This function is called when the /proc file is written
*
*/
int procfile_write(struct file *file, const char *buffer, unsigned long count,
void *data)
{
/* get buffer size */
procfs_buffer_size = count;
if (procfs_buffer_size > PROCFS_MAX_SIZE ) {
procfs_buffer_size = PROCFS_MAX_SIZE;
}
/* write data to the buffer */
if ( copy_from_user(procfs_buffer, buffer, procfs_buffer_size) ) {
/* we use copy_from_user in case segments happen to be used,
* also because the kernel "page table" and the user page
* table have different virtual addresses, and
* "buffer" is a userspace address
*
* We didn't need to do this on read because the
* copying from kernel space is something the
* I/O related system calls (like "read()") do for us
*/
return -EFAULT;
}
return procfs_buffer_size;
}
/**
*This function is called when the module is loaded
*
*/
static int buffer1k_init(void)
{
/* create the /proc file */
Our_Proc_File = create_proc_entry(PROCFS_NAME, 0644, NULL);
if (Our_Proc_File == NULL) {
remove_proc_entry(PROCFS_NAME, &proc_root);
printk("Error: Could not initialize /proc/%s\n",
PROCFS_NAME);
return -ENOMEM;
}
Our_Proc_File->read_proc = procfile_read;
Our_Proc_File->write_proc = procfile_write;
Our_Proc_File->owner = THIS_MODULE;
Our_Proc_File->mode = S_IFREG | S_IRUGO;
Our_Proc_File->uid = 0;
Our_Proc_File->gid = 0;
Our_Proc_File->size = 37;
printk("/proc/%s created\n", PROCFS_NAME);
return 0; /* everything is ok */
}
/**
*This function is called when the module is unloaded
*
*/
static void buffer1k_exit()
{
remove_proc_entry(PROCFS_NAME, &proc_root);
printk("/proc/%s removed\n", PROCFS_NAME);
}
module_init(buffer1k_init);
module_exit(buffer1k_exit);
MODULE_AUTHOR("CSIS 430");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("A 1K buffer");
Detailed Design Document
Do all the hard work, aside from writing code. Provide a detailed design document. This should indicate that you've thought about issues such as what kernel data structures you'll be interacting with, how you'll ensure you don't corrupt them as you interact with them, what kernel functions you'll call, how you'll interact with anything else (I/O, reading a configuration file, etc...), and so on. This should be very detailed - to illustrate how detailed, if you're writing this in C (assembler is another possibility) from this design document, I should be able to determine all the {} that you'll be using. This should be at least 3 pages long, and will be on a check, check plus, check minus, zero scale. If you fail to do this, or if you get a zero, then you'll fail the project - because if you fail to have this sufficiently detailed and thought out, I don't want to bother seeing you try to muddle through crafting a kernel module. Students have found it useful to apply what they learned in software engineering to the creation of this detailed design document.
Due: October 29th
Status Report
A status report. Again, graded on the check, check plus, check minus, or 0 scale.
Document how things have changed (if at all) since the detailed design document, and the progress regarding your implementation of that design.
Due: November 19th
Demonstrations and Final Code/Report
Provide a demonstration of your kernel module to me. You will be graded on the same scale as the detailed design document. Set up an appointment with me for this demonstration. Demonstrations must be completed by when the final exam would be scheduled for.
Demonstrations Due: December 3
Final Code/Report Due: December 3
You are to turn in:
Hardcopy of code a final progress report which contains:
how well you followed your design document
how well you didn't follow it
why you didn't follow it
(you may liberally copy and paste from the status report for this final report)
a summary of the project (this should ideally just be a copy of the proposal, but probably should be enhanced with additional details)
what you learned while doing this project
A Bad Example
I wanted to provide an example of something that would not make for a good loadable kernel module. This is the best bad example that I could come up with.
Inter-process communication on most OSes is relatively unstructured. What about having an OS provided mechanism for structured inter-process communication?
Structured IPC done entirely in userspace
Processes interested in being part of this structured IPC memory map in (read only) a particular file which is owned by root but has read access to everyone.
A root setuid program takes arguments (or piped data) and structures it and adds it to the file, updating earlier contents of the file to reflect the new data. It acquires a write (exclusive) lock on the file when doing so.
Non-root processes can call the setuid program.
The setuid program really only enforces the structured aspect of the data.
Processes interested in reading from the memory mapped file may acquire a read (shared) lock on the file when doing so.
Note the complete lack of a need for the OS to be involved.
A good loadable kernel module does something that can't be done in userspace. Possibilities
are:
1) allows access to kernel data structures not otherwise accessible
2) allows access to hardware/devices not otherwise accessible
3) does something which is done faster when in the kernel than in userspace (note that this is rare and few - for example, the above structured IPC might be slightly faster if that setuid program was really a system call instead, but whether or not that would be worth the tradeoff would be dependent on the frequency that such calls were made - if made often, it would be worth it, if not made often, it would not be worth it. An example of how it might be worth it would be if a kernel-level parser for some networked structured data format was supported and a network RPC mechanism had been implemented on top of that, and networked RPC was the main thing that you wanted to do - this way, instead of having the data copied around from network card to kernel to user space to data parser space to the shared memory, it could conceivably go directly from the network card to the data parser (in kernel) to the shared memory)