This project demonstrates how to create a Linux kernel module that represents a character device. This device, when read from, will provide the value of the jiffies variable. jiffies is a global kernel variable that indicates the number of clock ticks since system boot.
The project is divided into two main parts:
- Kernel Space: The kernel module code which creates the character device.
- User Space: A simple C program to interact with the created character device.
The kernel module performs the following key actions:
- Initialization and Cleanup: Two functions (
test_hello_initandtest_hello_exit) are defined to handle the initialization and cleanup when the module is loaded and unloaded, respectively. - Device Operations: Callback functions (e.g.,
device_open,device_read,device_write,device_release) are defined to handle operations performed on the character device. - Device Registration: The module allocates a device number, initializes the character device structure, and registers it with the kernel.
The user-space program performs the following actions:
- Opens the character device.
- Reads the
jiffiesvalue twice, with a sleep interval of one second between the readings. - Computes and prints the difference between the two
jiffiesreadings.
Kernel Space:
To compile the kernel module:
$ makeLoad the module:
$ sudo insmod <module_name>.koCheck for any printed messages:
$ dmesgTo remove the module:
$ sudo rmmod <module_name>User Space:
Compile:
$ gcc user_program.c -o user_programRun:
$ ./user_programInformally, a "jiffy" is a term for any short period of time. In the Linux kernel, jiffies represents the number of clock ticks since system boot. It's a global variable provided by the kernel and is used frequently for timing operations.
Character devices are one of the ways to interface with the kernel. They are simple to write and can be accessed as regular files. In this project, the character device /dev/jiffies is created, and it supports basic file operations like open, read, write, and close.
When a user reads from this device, the value of jiffies is returned. When written to, the device doesn't perform any specific operation.
The kernel space and user space in Linux are distinct, with the kernel having access to system memory and hardware, while user space interacts with the kernel through system calls.
In this project, put_user is used to safely copy the value of jiffies from kernel space to user space. This function ensures that the kernel doesn't inadvertently overwrite user space memory.
This project provides a practical example of kernel module programming and user-kernel interactions in Linux. It emphasizes the creation and use of character devices and safe memory operations between kernel and user spaces.
📌 Definition:
The put_user function is a built-in Linux kernel utility that writes a simple variable from the kernel's address space to user space.
🔍 Usage:
put_user(x, ptr);Where:
x: The value you wish to copy to user space.ptr: Address in user space where the data should be copied to.
📎 Characteristics:
- 🚀 It's optimized for speed and designed specifically for simple data types.
- 🚫 Doesn't support larger data types like structures or arrays.
- 🎯 For larger or complex data types, kernel developers typically resort to functions like
copy_to_user.
📘 Jiffies:
- What is it? It's a global variable in Linux that denotes the number of clock ticks (or interrupts) since the system was booted up.
- Location:
<linux/jiffies.h> - Trivia: The term "jiffy" informally refers to any short duration of time.
- Usage in context: In our character driver example, when read is invoked, it will return the value of 'jiffies' which gives a glimpse into system uptime in terms of clock ticks.
🤓 Fun Fact: If you ever notice the difference in the output value of jiffies to be 250 after waiting a second, it suggests that the system's timer has been set to generate 250 interrupts every second.
🛠 put_user vs. copy_to_user:
put_useris faster and more efficient for copying small amounts of data (up to 8 bytes).- The size
put_userwill copy depends on the type of the pointer argument, determined during compile-time. - For transferring larger data or when the size is determined during runtime,
copy_to_useris the preferred choice.
🔗 References:
- Linux kernel documentation
- Linux kernel source code
Technical Questions:
-
🤔 How is
put_userdifferent fromcopy_to_userin terms of use-cases?- 📘 Answer:
put_useris designed for transferring simple data types likecharorintand is efficient for small data sizes, up to 8 bytes. On the other hand,copy_to_useris suited for larger or variable-sized data transfers.
- 📘 Answer:
-
🤔 What are the implications if you try using
put_userfor complex data structures?- 📘 Answer:
put_useris not designed to handle complex data structures or arrays. If misused in such scenarios, it could lead to unexpected behavior or errors.
- 📘 Answer:
-
🤔 What is the significance of the
jiffiesvariable in the Linux kernel?- 📘 Answer:
jiffiesis a global variable representing the number of clock ticks (or interrupts) since the system was started. It offers a way to track system uptime in terms of clock ticks.
- 📘 Answer:
Design Questions:
-
🤔 If you were to design a system that needed frequent communication between user space and kernel space, what precautions would you take to ensure efficiency and safety?
- 📘 Answer: I would ensure that data transfers are minimized by only sending necessary data. I'd use optimized functions like
put_userwhen applicable. Additionally, proper error checking mechanisms would be in place to handle issues like invalid pointers, non-resident memory areas, etc. Security checks, like ensuring only privileged processes can communicate with specific kernel modules, would also be crucial.
- 📘 Answer: I would ensure that data transfers are minimized by only sending necessary data. I'd use optimized functions like
-
🤔 Can you think of a scenario where using
put_usermight be disadvantageous despite its speed?- 📘 Answer: Yes, if the size or structure of the data isn't known until runtime, or if the data is complex, using
put_usercan be disadvantageous. In such cases, thecopy_to_userfunction might be more appropriate due to its flexibility.
- 📘 Answer: Yes, if the size or structure of the data isn't known until runtime, or if the data is complex, using
- The module initializes with
test_hello_init(). - It creates a device class named "myclass".
- It allocates a major and minor device number for our character device.
- The actual device is then created, and its operations are initialized.
- When the module is unloaded with
rmmod, the cleanup functiontest_hello_exit()is executed to remove the device and release resources.
class_create(): Creates a new device class.alloc_chrdev_region(): Allocates a range of character device numbers.device_create(): Creates a device and registers it with sysfs.cdev_init(): Initializes a character device to link it with file operations.cdev_add(): Adds the character device to the system.device_destroy(): Removes a device that was created with device_create().class_destroy(): Destroys a device class.cdev_del(): Removes a character device from the system.unregister_chrdev_region(): Releases a range of device numbers.
-
🤔 What's the purpose of the
put_userfunction, and why is it used in this code?- 📘 Answer: The
put_userfunction is used to copy a simple variable from the kernel's space to user space. In this code, it's employed to copy the value ofjiffiesto user space when the device is read.
- 📘 Answer: The
-
🤔 What would happen if the read request size (
count) is less than the size ofjiffies?- 📘 Answer: The
device_readfunction checks this scenario. Ifcountis less thansizeof(jiffies), the function will return-EINVAL, indicating an invalid argument.
- 📘 Answer: The
-
🤔 Why doesn't the
device_writefunction handle data from user space?- 📘 Answer: In this particular example, the
device_writefunction is implemented as a stub. It doesn't process the data written to the device but acknowledges the write by returning thecount.
- 📘 Answer: In this particular example, the
-
🤔 If you were to extend this module to allow configurations (e.g., change frequency of updating jiffies or apply some transformations before returning), how would you design it?
- 📘 Answer: I would introduce module parameters using the
module_parammacro. These parameters could be changed at module insertion time. Furthermore, I'd modify thedevice_readfunction to processjiffiesbased on these configurations before returning it to user space.
- 📘 Answer: I would introduce module parameters using the
-
🤔 This module uses the name "jiffies" for the device, which is quite generic. What are the implications, and how would you improve it?
- 📘 Answer: Using a generic name could lead to conflicts or confusion with other modules or devices. A better approach would be to use a more unique name, possibly with a prefix related to the specific project or company, ensuring it remains descriptive but less likely to conflict with other device names.
[root@fedora 22_put_user]# sudo chmod 666 /dev/jiffies
[root@fedora 22_put_user]# ./testapp
First Read:4311173915
Second Read:4311174915
Difference:1000