Linux Kernel Character Module (Part 3)
1. Atomic Operations
Atomic operations are indivisible single-step operations that either complete entirely or do not occur at all in a multi-core/concurrent environment, and cannot be interrupted by other threads. They are used to avoid race conditions and ensure the correctness of concurrent data structures without always using heavyweight locks.
Why They Are Needed When multiple execution units (CPU cores/threads) access shared resources simultaneously, three classic problems can arise:
Race Conditions
Problem: The execution result depends on the order of instruction execution, making it susceptible to modification by other cores during runtime.Typical Scenario: Bank transfer operations, Thread A (deposit 100) and Thread B (withdraw 200).
// Shared account
int account_balance = 1000;
// Transfer function (non-atomic)
void transfer(int amount)
{
int current = account_balance; // Step 1: Read balance
int new_balance = current + amount; // Step 2: Calculate new balance
account_balance = new_balance; // Step 3: Write new balance
}

Causes of Race Conditions:
Non-atomic Operations: The transfer operation is split into three independent steps: “read-modify-write”.Overlapping Execution Timing: The operation steps of two threads interleave in time.State Overwrite: The thread that completes last overwrites the result of the previous thread.
Basic Functions
Below are the basic operation functions for atomic operations (partial), for detailed reference see in the kernel:<span>arch/x86/include/asm/atomic.h</span> (integer atomic operations), <span>arch/x86/include/asm/bitops.h</span> (bit atomic operations), <span>arch/x86/include/asm/barrier.h</span> (memory barriers).
Basic Atomic Operation Functions (atomic_t)
| Function Prototype | Description | Memory Barrier | Example |
|---|---|---|---|
<span>void atomic_add(int i, atomic_t *v)</span> |
Atomic addition | None | <span>atomic_add(5, &counter)</span> |
<span>void atomic_sub(int i, atomic_t *v)</span> |
Atomic subtraction | None | <span>atomic_sub(3, &counter)</span> |
<span>void atomic_inc(atomic_t *v)</span> |
Atomic increment by 1 | None | <span>atomic_inc(&counter)</span> |
<span>void atomic_dec(atomic_t *v)</span> |
Atomic decrement by 1 | None | <span>atomic_dec(&counter)</span> |
<span>int atomic_read(const atomic_t *v)</span> |
Atomic read | None | <span>int val = atomic_read(&counter)</span> |
<span>void atomic_set(atomic_t *v, int i)</span> |
Atomic set | None | <span>atomic_set(&flag, 1)</span> |
RMW (Read-Modify-Write) Operations
| Function Prototype | Description | Memory Barrier | Example |
|---|---|---|---|
<span>int atomic_add_return(int i, atomic_t *v)</span> |
Add and return new value | Full barrier | <span>int new = atomic_add_return(10, &counter)</span> |
<span>int atomic_sub_return(int i, atomic_t *v)</span> |
Subtract and return new value | Full barrier | <span>int new = atomic_sub_return(5, &counter)</span> |
<span>int atomic_inc_return(atomic_t *v)</span> |
Increment and return new value | Full barrier | <span>if (atomic_inc_return(&users) > MAX)</span> |
<span>int atomic_dec_return(atomic_t *v)</span> |
Decrement and return new value | Full barrier | <span>if (atomic_dec_return(&resources) < 0)</span> |
<span>int atomic_cmpxchg(atomic_t *v, int old, int new)</span> |
Compare and exchange | Full barrier | <span>atomic_cmpxchg(&lock, 0, 1)</span> |
<span>int atomic_xchg(atomic_t *v, int new)</span> |
Atomic exchange | None | <span>int old = atomic_xchg(&value, 100)</span> |
Test Operations
| Function Prototype | Description | Memory Barrier | Example |
|---|---|---|---|
<span>int atomic_inc_and_test(atomic_t *v)</span> |
Increment and test if zero | Full barrier | <span>if (atomic_inc_and_test(&refcount))</span> |
<span>int atomic_dec_and_test(atomic_t *v)</span> |
Decrement and test if zero | Full barrier | <span>if (atomic_dec_and_test(&count)) free()</span> |
<span>int atomic_sub_and_test(int i, atomic_t *v)</span> |
Subtract and test if zero | Full barrier | <span>if (atomic_sub_and_test(10, &credits))</span> |
<span>int atomic_add_negative(int i, atomic_t *v)</span> |
Add and test if negative | Full barrier | <span>if (atomic_add_negative(-5, &balance))</span> |
Bit Operation Functions
| Function Prototype | Description | Memory Barrier | Example |
|---|---|---|---|
<span>void set_bit(int nr, volatile unsigned long *addr)</span> |
Set bit | None | <span>set_bit(0, &flags)</span> |
<span>void clear_bit(int nr, volatile unsigned long *addr)</span> |
Clear bit | None | <span>clear_bit(3, &status)</span> |
<span>void change_bit(int nr, volatile unsigned long *addr)</span> |
Toggle bit | None | <span>change_bit(7, &mask)</span> |
<span>int test_and_set_bit(int nr, volatile unsigned long *addr)</span> |
Set bit and return original value | Full barrier | <span>if (!test_and_set_bit(0, &lock))</span> |
<span>int test_and_clear_bit(int nr, volatile unsigned long *addr)</span> |
Clear bit and return original value | Full barrier | <span>if (test_and_clear_bit(1, &flags))</span> |
<span>int test_and_change_bit(int nr, volatile unsigned long *addr)</span> |
Toggle bit and return original value | Full barrier | <span>if (test_and_change_bit(2, &state))</span> |
64-bit Atomic Operations (atomic64_t)
| Function Prototype | Description | Memory Barrier | Example |
|---|---|---|---|
<span>void atomic64_add(long i, atomic64_t *v)</span> |
64-bit atomic addition | None | <span>atomic64_add(1000, &big_counter)</span> |
<span>long atomic64_read(const atomic64_t *v)</span> |
64-bit atomic read | None | <span>long val = atomic64_read(&counter)</span> |
<span>long atomic64_cmpxchg(atomic64_t *v, long old, long new)</span> |
64-bit compare and exchange | Full barrier | <span>atomic64_cmpxchg(&ptr, old_ptr, new_ptr)</span> |
<span>long atomic64_xchg(atomic64_t *v, long new)</span> |
64-bit atomic exchange | None | <span>long old = atomic64_xchg(&value, 0)</span> |
Memory Barrier Functions
| Function | Description | Usage Scenario |
|---|---|---|
<span>mb()</span> |
Full barrier (both read and write ordering) | Strong ordering guarantee |
<span>rmb()</span> |
Read barrier (LoadLoad + LoadStore) | Read operation ordering |
<span>wmb()</span> |
Write barrier (StoreStore) | Write operation ordering |
<span>smp_mb()</span> |
SMP full barrier | Multi-core systems |
<span>smp_rmb()</span> |
SMP read barrier | Multi-core read ordering |
<span>smp_wmb()</span> |
SMP write barrier | Multi-core write ordering |
Notes
From the functions above, it can be seen that atomic operations are primarily used to protect a single value, and all atomic operations are mainly applicable in the following scenarios:
- • Simple counters (reference counting)
- • Status flags (device busy flags)
- • Resource counting (number of available resources)
<span>Non-applicable scenarios</span>
- • Protection of complex data structures (requires locks)
- • Long operations (atomic operations should be short)
- • Multi-variable synchronization (requires locks or RCU)
Example Code
Driver Code
#include <linux/init.h> // Module initialization and exit macros
#include <linux/module.h> // Basic kernel module functionality
#include <linux/moduleparam.h> // Module parameter support
#include <linux/fs.h> // File system related (file operation structures, etc.)
#include <linux/kdev_t.h> // Device number related
#include <linux/cdev.h> // Character device structure
#include <linux/device.h> // Device model related (class/device)
#include <linux/uaccess.h> // User space and kernel space data exchange (copy_to/from_user)
#include <linux/slab.h> // Kernel memory allocation (kmalloc/kfree)
#include <linux/string.h> // String operation functions
#include <linux/atomic.h> // Atomic operations
#include <asm/atomic.h> // Atomic operations (compatible with old kernels)
// Device private data structure
struct dev_data {
int major; // Major device number
int minor; // Minor device number
dev_t dev_num; // Device number (major + minor)
struct cdev cdev_test; // Character device structure
struct class *class; // Device class pointer
struct device *device; // Device node pointer
char kbuf[32]; // Kernel buffer (stores read/write data)
};
// Declare device instance
struct dev_data dev1;
// Atomic variable (for single-process access control)
static atomic64_t v = ATOMIC_INIT(1);
// Device open function
static int cdev_test_open(struct inode *inode, struct file *file)
{
// Atomic decrement by 1 and test if zero (implementing mutual exclusion)
if (!atomic64_dec_and_test(&v)) {
atomic64_inc(&v); // Restore count value
return -EBUSY; // Device busy error (already opened by another process)
}
printk("cdev file open!\r\n");
file->private_data = &dev1; // Store device data pointer
return 0;
}
// Device read function
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
struct dev_data *dev_test = (struct dev_data *)file->private_data;
// Prepare kernel data (fixed string)
strcpy(dev_test->kbuf, "Hello imx8mm!");
// Calculate actual readable length
size_t len = strlen(dev_test->kbuf);
if (len > size)
len = size;
// Copy data to user space
if (copy_to_user(buf, dev_test->kbuf, len) != 0) {
printk("cdev test read error\r\n");
return -EFAULT; // Return error on copy failure
}
printk("read cdev file ....\r\n");
return len; // Return actual number of bytes read
}
// Device write function
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
struct dev_data *dev_test1 = (struct dev_data *)file->private_data;
printk("write cdev file ....\r\n");
// Limit write length (to prevent buffer overflow)
if (size > sizeof(dev_test1->kbuf) - 1)
size = sizeof(dev_test1->kbuf) - 1;
// Copy data from user space
if (copy_from_user(dev_test1->kbuf, buf, size) != 0) {
printk("write file error\r\n");
return -EFAULT; // Return error on copy failure
}
dev_test1->kbuf[size] = '\0'; // Add string terminator
printk("write data is :%s\r\n", dev_test1->kbuf);
return size; // Return actual number of bytes written
}
// Device close function
static int cdev_test_release(struct inode *inode, struct file *file)
{
atomic64_inc(&v); // Atomic increment (release device lock)
printk("release cdev file ....\r\n");
return 0;
}
// File operation structure (define device operation functions)
static struct file_operations cdev_test_ops = {
.owner = THIS_MODULE, // Module owner
.open = cdev_test_open, // Open
.read = cdev_test_read, // Read
.write = cdev_test_write, // Write
.release = cdev_test_release // Close
};
// Module initialization function
static int __init module_param_init(void)
{
int ret;
// Dynamically allocate device number (automatically allocate major device number)
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_chardev");
if (ret) {
printk("alloc register chrdev region error: %d\n", ret);
return ret;
}
// Get allocated major/minor device number
dev1.major = MAJOR(dev1.dev_num);
dev1.minor = MINOR(dev1.dev_num);
printk("alloc register major %d minor %d\n", dev1.major, dev1.minor);
// Initialize character device
cdev_init(&dev1.cdev_test, &cdev_test_ops);
dev1.cdev_test.owner = THIS_MODULE;
// Add character device to system
ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
if (ret) {
printk("cdev_add failed: %d\n", ret);
goto add_fail;
}
// Create device class (in /sys/class/)
dev1.class = class_create(THIS_MODULE, "test");
if (IS_ERR(dev1.class)) {
ret = PTR_ERR(dev1.class);
printk("class_create failed: %d\n", ret);
goto class_fail;
}
// Create device node (in /dev/)
dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
if (IS_ERR(dev1.device)) {
ret = PTR_ERR(dev1.device);
printk("device_create failed: %d\n", ret);
goto device_fail;
}
return 0;
// Error handling (reverse release resources)
device_fail:
class_destroy(dev1.class);
class_fail:
cdev_del(&dev1.cdev_test);
add_fail:
unregister_chrdev_region(dev1.dev_num, 1);
return ret;
}
// Module exit function
static void __exit module_param_exit(void)
{
// Destroy device node
device_destroy(dev1.class, dev1.dev_num);
// Destroy device class
class_destroy(dev1.class);
// Delete character device
cdev_del(&dev1.cdev_test);
// Release device number
unregister_chrdev_region(dev1.dev_num, 1);
printk("----chrdev end----\n");
}
// Register module initialization and exit functions
module_init(module_param_init);
module_exit(module_param_exit);
// Module metadata
MODULE_LICENSE("GPL"); // Open source license
MODULE_AUTHOR("XSX"); // Author
MODULE_DESCRIPTION("Module parameter example"); // Description
MODULE_VERSION("V1.0"); // Version
Key Functionality Description:
Atomic Lock Mechanism
- • Use atomic64_t to implement single-process access control for the device
- • Atomic decrement by 1 check during open (0 indicates first open)
- • Atomic increment by 1 during release to free the lock
Data Flow
- • read: returns the fixed string “Hello imx8mm!”
- • write: receives user data and prints it (limited to 32 bytes)
Application Layer Code
#include <stdio.h> // Standard input/output library (provides printf and other functions)
#include <sys/types.h> // System data type definitions (like dev_t, mode_t, etc.)
#include <sys/stat.h> // File status information (file permission macros)
#include <fcntl.h> // File control options (O_RDWR, etc.)
#include <unistd.h> // POSIX operating system API (open/close/sleep, etc.)
int main(int argc, char *argv[])
{
// Open character device file (located in /dev/test)
// O_RDWR: open in read/write mode
int fd = open("/dev/test", O_RDWR);
// Check if file descriptor is valid (<0 indicates open failure)
if (fd < 0) {
// Print error message (device open failed)
printf("open error \r\n");
return fd; // Return error code (negative value)
}
// Sleep for 5 seconds (keep device open)
// Purpose: simulate device occupied state, test driver mutual access mechanism
sleep(5);
// Close device file descriptor
close(fd);
return 0; // Normal exit
}
Program Function Description
Device Open Test
- • Attempt to open the /dev/test device node
- • Use the O_RDWR flag to indicate opening in read/write mode
- • Check return value to verify success
Mutual Access Test
- • sleep(5) keeps the program in an open state for 5 seconds
- • During this time, other processes attempting to open the same device will be denied (return -EBUSY)
- • Verify the effectiveness of the driver atomic lock mechanism
Data Flow
- • close(fd) closes the device file descriptor
- • Triggers the driver’s release function
- • Releases the atomic lock (allowing other processes to access)
2. Spinlocks
Spinlocks are a type of low-level synchronization primitive in the Linux kernel, primarily used to protect short critical sections in multi-processor systems (SMP) or preemptive single-processor systems. The core feature is that when a thread attempts to acquire a lock that is held by another thread, it will engage in busy waiting (continuously looping to check the lock status) instead of going to sleep. As mentioned above, atomic operations only operate on certain variables; if we need to protect a range of code for reading and writing, we need spinlocks.
Working Principle
Attempt to Acquire Lock: The thread attempts to acquire the lock using atomic operations (like test-and-set or compare-and-swap).Lock is Held: If it fails, the thread enters a tight loop repeatedly checking the lock status.Lock is Released: If it succeeds, the thread enters the critical section.
Usage Scenarios
Critical Section is Very Short: Typically less than the time for two context switches.Interrupt Context: Scenarios where sleeping is not allowed.Real-time Requirements: Scenarios with high real-time requirements (to avoid scheduling delays).
Functions
Basic Spinlock Functions
| Function Name | Function Prototype | Description |
|---|---|---|
<span>DEFINE_SPINLOCK</span> |
<span>DEFINE_SPINLOCK(lock)</span> |
Static declaration and initialization of spinlock |
<span>spin_lock_init</span> |
<span>void spin_lock_init(spinlock_t *lock)</span> |
Dynamic initialization of spinlock |
<span>spin_lock</span> |
<span>void spin_lock(spinlock_t *lock)</span> |
Acquire spinlock (blocking) |
<span>spin_unlock</span> |
<span>void spin_unlock(spinlock_t *lock)</span> |
Release spinlock |
<span>spin_is_locked</span> |
<span>int spin_is_locked(spinlock_t *lock)</span> |
Check if the lock is held |
Interrupt-Safe Versions
| Function Name | Function Prototype | Description |
|---|---|---|
<span>spin_lock_irq</span> |
<span>void spin_lock_irq(spinlock_t *lock)</span> |
Acquire lock and disable local CPU interrupts |
<span>spin_unlock_irq</span> |
<span>void spin_unlock_irq(spinlock_t *lock)</span> |
Release lock and enable local CPU interrupts |
<span>spin_lock_irqsave</span> |
<span>void spin_lock_irqsave(spinlock_t *lock, unsigned long flags)</span> |
Acquire lock and save interrupt state |
<span>spin_unlock_irqrestore</span> |
<span>void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)</span> |
Release lock and restore interrupt state |
Bottom Half of Context Switching
| Function Name | Function Prototype | Description |
|---|---|---|
<span>spin_lock_bh</span> |
<span>void spin_lock_bh(spinlock_t *lock)</span> |
Acquire lock and disable soft interrupts |
<span>spin_unlock_bh</span> |
<span>void spin_unlock_bh(spinlock_t *lock)</span> |
Release lock and enable soft interrupts |
Non-blocking Try Functions
| Function Name | Function Prototype | Description |
|---|---|---|
<span>spin_trylock</span> |
<span>int spin_trylock(spinlock_t *lock)</span> |
Attempt to acquire lock non-blockingly |
<span>spin_trylock_bh</span> |
<span>int spin_trylock_bh(spinlock_t *lock)</span> |
Attempt to acquire lock non-blockingly and disable soft interrupts |
Read-Write Spinlock Functions
| Function Name | Function Prototype | Description |
|---|---|---|
<span>DEFINE_RWLOCK</span> |
<span>DEFINE_RWLOCK(lock)</span> |
Static declaration and initialization of read-write lock |
<span>rwlock_init</span> |
<span>void rwlock_init(rwlock_t *lock)</span> |
Dynamic initialization of read-write lock |
<span>read_lock</span> |
<span>void read_lock(rwlock_t *lock)</span> |
Acquire read lock |
<span>read_unlock</span> |
<span>void read_unlock(rwlock_t *lock)</span> |
Release read lock |
<span>write_lock</span> |
<span>void write_lock(rwlock_t *lock)</span> |
Acquire write lock |
<span>write_unlock</span> |
<span>void write_unlock(rwlock_t *lock)</span> |
Release write lock |
<span>read_lock_irqsave</span> |
<span>void read_lock_irqsave(rwlock_t *lock, unsigned long flags)</span> |
Acquire read lock and save interrupt state |
<span>read_unlock_irqrestore</span> |
<span>void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags)</span> |
Release read lock and restore interrupt state |
<span>write_lock_irqsave</span> |
<span>void write_lock_irqsave(rwlock_t *lock, unsigned long flags)</span> |
Acquire write lock and save interrupt state |
<span>write_unlock_irqrestore</span> |
<span>void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags)</span> |
Release write lock and restore interrupt state |
Debugging and Verification Functions
| Function Name | Function Prototype | Description |
|---|---|---|
<span>spin_trylock_retry</span> |
<span>int spin_trylock_retry(spinlock_t *lock, int retries)</span> |
Attempt to acquire lock with retries |
<span>spin_lock_nested</span> |
<span>void spin_lock_nested(spinlock_t *lock, int subclass)</span> |
Lock nesting debugging |
<span>spin_lock_held</span> |
<span>int spin_lock_held(spinlock_t *lock)</span> |
Check if the current CPU holds the lock |
Notes
No Sleeping: It is absolutely forbidden to call kmalloc(GFP_KERNEL), msleep(), or other functions that may sleep while holding a spinlock.Short Critical Sections: The time holding the lock should be far less than the overhead of two context switches (usually < 10μs).Interrupt Safety: Must use _irqsave or _irq variants in interrupt contexts.Symmetric Operations: spin_lock_irqsave must be paired with spin_unlock_irqrestore (with the same flags variable).
Code
Driver Code
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h> // Memory allocation functions like kmalloc
#include <linux/string.h> // String operation functions
#include <linux/atomic.h> // Atomic operations
#include <asm/atomic.h> // Atomic operations (architecture specific)
// Device data structure
struct dev_data
{
int major; // Major device number
int minor; // Minor device number
dev_t dev_num; // Device number (major + minor)
struct cdev cdev_test; // Character device structure
struct class *class; // Device class pointer
struct device *device; // Device node pointer
char kbuf[32]; // Kernel buffer
};
struct dev_data dev1; // Global device instance
static spinlock_t spinlock; // Spinlock instance
static int flag = 1; // Device open flag (1=available, 0=busy)
// Device open function
static int cdev_test_open(struct inode *inode, struct file *file)
{
spin_lock(&spinlock); // Acquire spinlock
if (flag != 1) { // Check if device is already occupied
spin_unlock(&spinlock);
return -EBUSY; // Return device busy error
}
flag = 0; // Mark device as occupied
spin_unlock(&spinlock);
printk("cdev file open!\r\n");
file->private_data = &dev1; // Store device data in file private data
return 0;
}
// Device read function
static ssize_t cdev_test_read(struct file *file, char __user *buf, size_t size, loff_t *off)
{
struct dev_data *dev_test = (struct dev_data*)file->private_data;
strcpy(dev_test->kbuf, "Hello imx8mm!"); // Fill kernel buffer
size_t len = strlen(dev_test->kbuf);
if (len > size)
len = size; // Ensure not exceeding user requested size
// Copy kernel data to user space
if (copy_to_user(buf, dev_test->kbuf, len) != 0) {
printk("cdev test read error\r\n");
return -EFAULT; // Return memory error
}
printk("read cdev file ....\r\n");
return len; // Return actual readable length
}
// Device write function
static ssize_t cdev_test_write(struct file *file, const char __user *buf, size_t size, loff_t *off)
{
struct dev_data *dev_test1 = (struct dev_data*)file->private_data;
printk("write cdev file ....\r\n");
// Limit write size not to exceed buffer capacity
if (size > sizeof(dev_test1->kbuf) - 1)
size = sizeof(dev_test1->kbuf) - 1;
// Copy data from user space
if (copy_from_user(dev_test1->kbuf, buf, size) != 0) {
printk("write file error\r\n");
return -EFAULT; // Return memory error
}
dev_test1->kbuf[size] = '\0'; // Add string terminator
printk("write data is :%s\r\n", dev_test1->kbuf); // Output written content
return size; // Return actual written length
}
// Device release function
static int cdev_test_release(struct inode *inode, struct file *file)
{
spin_lock(&spinlock); // Acquire spinlock
flag = 1; // Mark device as available
spin_unlock(&spinlock); // Release lock
printk("release cdev file ....\r\n"); // Kernel log output
return 0; // Success return
}
// File operation structure
static struct file_operations cdev_test_ops = {
.owner = THIS_MODULE, // Module owner
.open = cdev_test_open, // Open function
.read = cdev_test_read, // Read function
.write = cdev_test_write, // Write function
.release = cdev_test_release // Release function
};
// Module initialization function
static int __init module_param_init(void)
{
int ret;
spin_lock_init(&spinlock); // Initialize spinlock
// Dynamically allocate device number
ret = alloc_chrdev_region(&dev1.dev_num, 0, 1, "alloc_chardev");
if (ret) {
printk("alloc register chrdev region error: %d\n", ret);
return ret;
}
printk("alloc register chrdev is ok!\n");
// Get major/minor device number
dev1.major = MAJOR(dev1.dev_num);
dev1.minor = MINOR(dev1.dev_num);
printk("alloc register major %d minor %d\n", dev1.major, dev1.minor);
// Initialize character device
dev1.cdev_test.owner = THIS_MODULE;
cdev_init(&dev1.cdev_test, &cdev_test_ops);
// Add character device to system
ret = cdev_add(&dev1.cdev_test, dev1.dev_num, 1);
if (ret) {
printk("cdev_add failed: %d\n", ret);
goto add_fail;
}
// Create device class
dev1.class = class_create(THIS_MODULE, "test");
if (IS_ERR(dev1.class)) {
ret = PTR_ERR(dev1.class);
printk("class_create failed: %d\n", ret);
goto class_fail;
}
// Create device node
dev1.device = device_create(dev1.class, NULL, dev1.dev_num, NULL, "test");
if (IS_ERR(dev1.device)) {
ret = PTR_ERR(dev1.device);
printk("device_create failed: %d\n", ret);
goto device_fail;
}
return 0; // Initialization successful
// Error handling path
device_fail:
class_destroy(dev1.class); // Destroy device class
class_fail:
cdev_del(&dev1.cdev_test); // Delete character device
add_fail:
unregister_chrdev_region(dev1.dev_num, 1); // Release device number
return ret; // Return error code
}
// Module exit function
static void __exit module_param_exit(void)
{
// Clean up resources
device_destroy(dev1.class, dev1.dev_num); // Destroy device node
class_destroy(dev1.class); // Destroy device class
cdev_del(&dev1.cdev_test); // Delete character device
unregister_chrdev_region(dev1.dev_num, 1); // Release device number
printk("----chrdev end----\n"); // Exit log
}
// Module entry and exit
module_init(module_param_init); // Specify initialization function
module_exit(module_param_exit); // Specify exit function
// Module metadata
MODULE_LICENSE("GPL"); // License
MODULE_AUTHOR("XSX"); // Author
MODULE_DESCRIPTION("Module parameter example"); // Description
MODULE_VERSION("V1.0"); // Version
Key Functionality Description
- 1. Mutex Protection:
- • Use mutex
<span>mutex</span>to protect<span>flag</span>variable, ensuring atomic operations for device open state. - • Implement mutual access in
<span>open()</span>and<span>release()</span>functions, ensuring only one process can open or close the device at a time.
- • Functionality:
<span>flag</span>variable is used to mark whether the device is available (1 means available, 0 means opened). - • Specific Implementation:
- • When the device is opened, if
<span>flag</span>is 0, return<span>-EBUSY</span>, preventing other processes from opening the device again, ensuring the device is in single-use state during opening.
- • Functionality: Use
<span>kbuf[32]</span>as the kernel space buffer. - • Specific Implementation:
- •
<span>read()</span>operation returns the fixed string<span>"Hello imx8mm!"</span>. - •
<span>write()</span>operation copies user data to<span>kbuf</span>buffer and prints it out.
- • Functionality: Dynamically allocate device number and initialize character device.
- • Specific Implementation:
- • Use
<span>alloc_chrdev_region</span>to dynamically allocate device number. - • Use
<span>cdev_init</span>to initialize character device. - • Use
<span>cdev_add</span>to add character device to the system. - • Use
<span>class_create</span>and<span>device_create</span>to create device class and device node.
- • Functionality: Reverse release all resources upon exit.
- • Specific Implementation:
- • Use
<span>goto</span>statements to implement a unified error handling path, ensuring correct release of resources during program execution, avoiding resource leaks.
Application Code
#include <stdio.h> // Standard input/output library (provides printf and other functions)
#include <sys/types.h> // System data type definitions
#include <sys/stat.h> // File status information (provides file permission macros)
#include <fcntl.h> // File control options (provides open function flags)
#include <unistd.h> // UNIX standard functions (provides sleep, close, etc.)
/**
* @brief Main function - Test character device driver
*
* @param argc Number of command line arguments
* @param argv Command line argument array
* @return int Program exit status
*/
int main(int argc, char *argv[])
{
// Open device file "/dev/test"
// O_RDWR: open in read/write mode
int fd = open("/dev/test", O_RDWR);
// Check if file open was successful
if (fd < 0)
{
// Print error message on open failure
printf("open error \r\n");
return fd; // Return error code (negative value)
}
// Sleep for 5 seconds after successfully opening the device
// This simulates keeping the device open for a period
sleep(5);
// Close device file descriptor
close(fd);
// Program exits normally
return 0;
}