Description
Introduction: This assignment is all about getting familiar with Linux Modules and Linux Device Drivers. At first you will learn how to code a Linux kernel module, how to install the module and how to run the module. Then you will write a module that will enable you to install a Linux device driver. This assignment write-up is structured as follows: 1. What is a Linux kernel module 2. How Linux kernel module works 3. How to code a Linux kernel module 4. What are Linux device drivers 5. How do device drivers work 6. How to code a device driver using Linux kernel module programming You may use the VM from the first programming assignment, or any Linux installation that runs on your laptop. It will be useful to obtain the current version of the Linux kernel running on your machine. This is obtained as follows: 1. Go to a shell terminal and type uname –r 2. You will get an output like x.y.z-ab-something else a. X is the major number b. Y is the minor number. If even that means the version is stable. If its odd that means its still in experimental version c. Z is the revision number. 3. To check the source code, you can go to this folder /usr/src/$(uname -r) Loadable Kernel Modules(LKM): LKMs are object files that are used to extend the running kernel’s functionalities of the current operating system. This is basically a piece of binary code that can be inserted and installed in the kernel on the fly. As you know if you want to make a change in the current OS, after you make changes, you have to reboot your computer, like what you did in the first assignment when adding a system call. After you reboot, the changes that you made are installed in the kernel. Now as you can see, this approach is a bit painstaking. To make this approach more dynamic, LKMs are introduced where you can add extensions to the kernel on the fly without the need to reboot. This comes very handy when you are trying to work with some device and just be done with it very fast and then uninstall the device without needing to reboot, thus saving time and energy and also space, because you can uninstall the module after your work is done. How to work with modules: 1. Get the helloModule.c and store it in a folder named “module” 2. Open the file 3. The init.h is required for the initialization of the module and the module.h is required to let the kernel know that this is a LKM. 4. I have coded two simple functions in the module code namely hello_init() and hello_exit(). I want hello_init to execute when the module starts to work and hello_exit when the module gets uninstalled. To make sure this happens, at the end of the code I have added these two lines. a. module_init(hello_init) b. module_exit(hello_exit) What this means is that when the module is getting started, the kernel follows the function pointed to by module_init() and executes that function. Similarly, when the module is uninstalled, the kernel follows the function pointed to by module_exit(), in this case, the function hello_exit(). 5. As you are coding in the kernel, you cannot use the printf function. Instead you have to use the function printk function. The KERN_ALERT is used to let the kernel know the importance of the message you are trying to print with the printk function. If it’s KERN_ALERT then the message will be written in the log file in the location /var/log/syslog file. The contents of the log file can be seen from the terminal using the command dmesg or sudo tail –f /var/log/syslog. There are other kernel message importance levels too (KERN_INFO, KERN_EMERG etc). 6. To check what is happening, you can type dmesg or sudo tail –f /var/log/syslog in another terminal and check what is happening when you are trying to install the module. 7. Now you have to write a makefile. Create a file named Makefile and type the following line in it a. obj-m:=helloModule.o b. Here m means module. You are telling the compiler to create a module object named helloModule.o 8. Now to compile the module, type in the terminal “make –C /lib/modules/$(uname -r)/build M=$PWD modules” 9. now in the command prompt type the following: ls 10. You will see there is a file named helloModule.ko. This is the kernel module (ko) object you will be using to insert in the basic kernel image. 11. Now in the terminal type “sudo insmod helloModule.ko” . 12. Now if you type lsmod you will see your module is now inserted in the kernel. 13. Now type “dmesg” or “sudo tail /var/log/syslog” and you will see expected output is printed because the hello_init() function was executed when the module was installed. 14. Now to remove the kernel type “sudo rmmod helloModule” and then the module will be removed as it can be ascertained by typing the lsmod command. Type dmesg to see if expected output is printed. Device Driver: Remember that in Linux device I/O is modeled using files. Reading from and writing to a file will invoke the associated device driver to do the actual reading and writing. All device drivers have two numbers associated with it, namely major and minor number. The major number is a number that is unique to every device driver and the minor number is to differentiate all the devices belonging to that device driver. So for example, for a hard disk, there are many partitions. To differentiate the hard disk device driver, major number is used whereas to differentiate the different partitions, minor number is used. Kernel major number As it can be seen from the above diagram, there are three drivers namely Driver1, Driver2 and Driver 3. The numbers 1, 2 and 3 are the major numbers. These major numbers are associated with device drivers in the kernel to differentiate one device driver from another. To code a Device Driver, the major number has to be unique. Also, Device Driver 1 works with Device11, Device22, and Device33. The numbers 11, 22 and 33 associated with the devices are called the minor numbers which are used to differentiate the devices associated with one particular device driver. Driver 1 Driver 2 Driver 3 Device 11 Device 22 Device 33 For example, if you type ls –l /dev | grep sda, this will give you all the device files (or the current partitions) associated with the hard disk device drivers. You will see the partitions are listed with their corresponding major and minor numbers. There are two kinds of device drivers namely Character Device Driver and Block Device Driver. Character Device Driver: 1. Reads from the device character by character 2. Writes to the device character by character 3. Operates blocking mode which means when the user writes info to the device, must wait before the device finishes execution. They are most common of all device drivers. Block Device Driver: 1. Reads large chunks of information. 2. Very CPU intensive, takes some time to finish the execution. 3. They are asynchronous, the user does not need to wait for the reading and writing to be completed. Creating Device File for a Device Driver: To work with device drivers, you have to work with the corresponding device files. These files are stored in the /dev folder. If you type in the terminal “ls /dev” you can see all the device files in the machine. You have to create a file in this folder to work with the character device driver you will be coding. the command to do that is “sudo mknod -m . For example , “sudo mknod –m 777 /dev/simple_character_device c 240 0” where ‘c’ is for creating a character driver, ‘777’ so that the creator, the group the creator belongs to and all the others can read, write and execute the file, ‘240’ is the major number of the driver that will be associated with this device file ,‘0’ is the minor number of the device and ‘simple_character_device’ is the name of the device file. The major number that you will be giving should be unique. Check with the command cat /proc/devices to see the current devices and their major numbers. Use a major number that is not taken by any of the device drivers currently installed in your machine. The assignment: In this assignment, what you have to do is to code a simple character device driver, install it and then create a device file in /dev folder associated with that device driver. Then read from and write to that file from a testapp that you will be coding from the user space. So here are the steps: 1. Create the skeleton of your device driver module 2. Code your file operations 3. Make and Install the module 4. Create a device file for this device 5. Create a test app that is an interactive program that will allow you to read from or write to that device file. Open, read, write, close File operations So the above diagram is an overview what is going on when you are working with a device driver. From the user-space, you will be trying to access the device file. The device file is associated with a particular device driver. When from the user-space you issue the commands open, read, write or close (when you echo hello >file.txt the operations performed are open the file, write “hello” to the file and then close the file sequentially. Similarly when you type cat file.txt, the operations performed are open the file, read the file content and then close the file), the kernel sees that you are trying to perform file operations on a device file that is associated with a particular device driver (remember major number?). It then invokes the corresponding file operations for that device driver and then the device driver talks to the physical device to perform those file operations on the physical device. User Space Device file Device Driver Physical Device Kernel In this assignment, we will be only writing our data in the device file instead of the actual physical device. In your extra credit(more on extra credit section), you will be asked to create a emulated device and perform file operations for that device from the user-space using the device driver module you coded for that emulated device. Outline of the device driver code: 1. First some header files need to be included. With the other header files necessary for module programming, you will also need to include two more. They are linux/fs.h to get the functions that are related to device driver coding and asm/uaccess.h to enable you to get data from userspace to kernel and vice versa. 2. Declare the init and exit functions as you do for module programming and make the module_init and module_exit to point to those functions. In the init function you have to register the character driver using the function register_chrdev() function. This function takes three parameters namely the major number of the driver, the name of the driver and a pointer to the file operations structure you want this driver to execute. 3. Similarly, in the exit function, you have to unregister the driver using the function unregister_chrdev(). This function takes the major number and the name of the character driver you want gone. Check google if you have any problems regarding these two functions. 4. Now you would want the device driver to perform some file operations. For that you need the file_operations structure which you can find in the (lib/modules/$(uname – r)/build/include/linux/fs.h) header file. Check the file_operations structure in the header file and create a similar structure with the same file_operations type and with a different name because you want your device driver to perform only a few of those operations. For this assignment, you have to perform open, close, read and write operations only. So you have to include the four corresponding function pointers (.open, .close, .read, .write) for these four operations and make them point to the four functions you will be writing for these operations. 5. Inside the device driver, the function you will be writing to open the file takes two parameters. The first one is the exact inode which represents the actual physical file on the hard disk and the second parameter is the abstract open file that contains all the necessary file operations in the file_operations structure . You don’t have to do anything extra in this function. Just print the number of times the device has been opened until now. Do the same thing for the close function. 6. The data from kernel cannot be used in the userspace. So you have to use two functions inside the device driver namely copy_to_user and copy_frm_user. Each of these two functions takes three parameters. a. Copy_to_user(destination,source,size) to get data from kernel to userspace b. Copy_from_user(destination,source,size) to get data from userspace to kernel 7. The read function takes four parameters. The first one is the file pointer, the second one is the user space buffer where you will be storing your read data, the third one is the number of space available in the userspace buffer and the last one is the current position of the opened file. The data read from the device will be stored in the device_buffer array defined above in the code. You just have to copy the data from the device_buffer to buffer and print that in the terminal. Use the function copy_to_user() to copy data from the device_buffer to the userspace buffer that is buffer variable that’s present in the function’s arguments. You will also have to print the number of bytes read in the file in the log file (/var/log/syslog). 8. The write function does the same thing. What you try to write is stored in the userspace buffer and then it is copied to device_driver variable and then it is written in the device file opened. All you have to do is to use the function copy_from_user() to copy data to buffer to device_buffer. Be careful of how to use the offset variable so that you don’t overwrite the previous data. In both read and write functions, you have to make sure the offset is properly set. Your write function should be coded such as it does not overwrite the previous data (if there are any) in the device file. Instead of writing from the start, your write function should be able to append to the device file. You will also have to print the number of bytes written in the file in the log file (/var/log/syslog). Installing the module: 1. Create your makefile as described in the previous sections when working with the helloModule.c 2. Edit your Makefile 3. Compile your module using the make command you used before. 4. Install your module by the insmod command. 5. Check the log file as described in previous sections to check if it’s properly installed. Check with the “cat /proc/devices” command. Creating a device file and test the driver code: 1. Create a device file for this device by the command as described in the previous section. 2. Then try to echo and cat that particular file and see if your device is working by examining the log file. Writing an interactive test program: 1. Now you have to write an interactive test program that will allow you to read from and write to the device file 2. You have to use the location of the device file you created in the /dev folder while writing the test code. 3. Your interactive program at first should give the user the following options: a. Press r to read from device b. Press w to write to the device c. Press e to exit from the device d. Press anything else to keep reading or writing from the device e. Enter command: 4. If the user presses ‘r’ then you should print the data read from the device file. the format of the output should be like this: a. Data read from the device: 5. If the user presses ‘w’ then you should ask for the data to be written from the user. The format should be like this: a. Enter data you want to write to the device: 6. If the user presses ‘e’ then you should quit the testapp. 7. If the user presses something else, you should continue giving the user the options like you did in step 3 in this section. References: 1. You can use the Linux manual pages to check the functions and their functionalities. 2. https://www.fsl.cs.sunysb.edu/kernel-api/re941.html 3. https://lxr.free-electrons.com/ident?i=unregister_chrdev 4. https://www.fsl.cs.sunysb.edu/kernel-api/re256.html 5. https://www.fsl.cs.sunysb.edu/kernel-api/re257.html Extra credit: 1. As we have discussed in the earlier sections, we are actually writing to and reading from the device file we created in the /dev folder. For the extra credit, you are required to create an emulated physical device and read and write from and to that emulated device using the character device driver module you coded. 2. Or, create a simple block device driver and do the same file operations like you did for character device driver. 3. To receive extra-credit, you will need to present your extra credit to your TA when you are interview graded. Grading: • 50 percent for code completion and correct execution • 10 percent for code cleanliness and neatness • 40 percent for the interview grading session • Each extra credit item is worth up to 5% extra credit (total of 10% extra credit)