Linux Kernel Character Module (Part 3)

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
}
Linux Kernel Character Module (Part 3)

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 &lt;linux/init.h&gt;          // Module initialization and exit macros
#include &lt;linux/module.h&gt;        // Basic kernel module functionality
#include &lt;linux/moduleparam.h&gt;   // Module parameter support
#include &lt;linux/fs.h&gt;            // File system related (file operation structures, etc.)
#include &lt;linux/kdev_t.h&gt;        // Device number related
#include &lt;linux/cdev.h&gt;          // Character device structure
#include &lt;linux/device.h&gt;        // Device model related (class/device)
#include &lt;linux/uaccess.h&gt;       // User space and kernel space data exchange (copy_to/from_user)
#include &lt;linux/slab.h&gt;          // Kernel memory allocation (kmalloc/kfree)
#include &lt;linux/string.h&gt;        // String operation functions
#include &lt;linux/atomic.h&gt;        // Atomic operations
#include &lt;asm/atomic.h&gt;          // 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(&amp;v)) {
        atomic64_inc(&amp;v);       // Restore count value
        return -EBUSY;          // Device busy error (already opened by another process)
    }
    printk("cdev file open!\r\n");
    file-&gt;private_data = &amp;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-&gt;private_data;
    
    // Prepare kernel data (fixed string)
    strcpy(dev_test-&gt;kbuf, "Hello imx8mm!");
    
    // Calculate actual readable length
    size_t len = strlen(dev_test-&gt;kbuf);
    if (len &gt; size)
        len = size;
    
    // Copy data to user space
    if (copy_to_user(buf, dev_test-&gt;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-&gt;private_data;
    printk("write cdev file ....\r\n");
    
    // Limit write length (to prevent buffer overflow)
    if (size &gt; sizeof(dev_test1-&gt;kbuf) - 1)
        size = sizeof(dev_test1-&gt;kbuf) - 1;
    
    // Copy data from user space
    if (copy_from_user(dev_test1-&gt;kbuf, buf, size) != 0) {
        printk("write file error\r\n");
        return -EFAULT;         // Return error on copy failure
    }
    dev_test1-&gt;kbuf[size] = '\0'; // Add string terminator
    printk("write data is :%s\r\n", dev_test1-&gt;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(&amp;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(&amp;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(&amp;dev1.cdev_test, &amp;cdev_test_ops);
    dev1.cdev_test.owner = THIS_MODULE;
    
    // Add character device to system
    ret = cdev_add(&amp;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(&amp;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(&amp;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 &lt;stdio.h&gt;       // Standard input/output library (provides printf and other functions)
#include &lt;sys/types.h&gt;   // System data type definitions (like dev_t, mode_t, etc.)
#include &lt;sys/stat.h&gt;    // File status information (file permission macros)
#include &lt;fcntl.h&gt;       // File control options (O_RDWR, etc.)
#include &lt;unistd.h&gt;      // 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 (&lt;0 indicates open failure)
    if (fd &lt; 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 &lt;linux/init.h&gt;
#include &lt;linux/module.h&gt;
#include &lt;linux/moduleparam.h&gt;
#include &lt;linux/fs.h&gt;
#include &lt;linux/kdev_t.h&gt;
#include &lt;linux/cdev.h&gt;
#include &lt;linux/device.h&gt;
#include &lt;linux/uaccess.h&gt;
#include &lt;linux/slab.h&gt;       // Memory allocation functions like kmalloc
#include &lt;linux/string.h&gt;     // String operation functions
#include &lt;linux/atomic.h&gt;     // Atomic operations
#include &lt;asm/atomic.h&gt;       // 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(&amp;spinlock);     // Acquire spinlock
   if (flag != 1) {          // Check if device is already occupied
       spin_unlock(&amp;spinlock);
       return -EBUSY;        // Return device busy error
   }
   flag = 0;                 // Mark device as occupied
   spin_unlock(&amp;spinlock);
   
   printk("cdev file open!\r\n");
   file-&gt;private_data = &amp;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-&gt;private_data;
   strcpy(dev_test-&gt;kbuf, "Hello imx8mm!"); // Fill kernel buffer
   
   size_t len = strlen(dev_test-&gt;kbuf);
   if (len &gt; size)
       len = size;           // Ensure not exceeding user requested size
   
   // Copy kernel data to user space
   if (copy_to_user(buf, dev_test-&gt;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-&gt;private_data;
   printk("write cdev file ....\r\n");
   
   // Limit write size not to exceed buffer capacity
   if (size &gt; sizeof(dev_test1-&gt;kbuf) - 1)
       size = sizeof(dev_test1-&gt;kbuf) - 1;
   
   // Copy data from user space
   if (copy_from_user(dev_test1-&gt;kbuf, buf, size) != 0) {
      printk("write file error\r\n");
      return -EFAULT;        // Return memory error
   }
   dev_test1-&gt;kbuf[size] = '\0'; // Add string terminator
   printk("write data is :%s\r\n", dev_test1-&gt;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(&amp;spinlock);    // Acquire spinlock
   flag = 1;                   // Mark device as available
   spin_unlock(&amp;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(&amp;spinlock); // Initialize spinlock
   
   // Dynamically allocate device number
   ret = alloc_chrdev_region(&amp;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(&amp;dev1.cdev_test, &amp;cdev_test_ops);
   
   // Add character device to system
   ret = cdev_add(&amp;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(&amp;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(&amp;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. 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.
  • 2. Device State Management:
    • 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.
  • 3. Data Buffer:
    • 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.
  • 4. Device Registration Process:
    • 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.
  • 5. Resource Cleanup:
    • 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 &lt;stdio.h&gt;      // Standard input/output library (provides printf and other functions)
    #include &lt;sys/types.h&gt;  // System data type definitions
    #include &lt;sys/stat.h&gt;   // File status information (provides file permission macros)
    #include &lt;fcntl.h&gt;      // File control options (provides open function flags)
    #include &lt;unistd.h&gt;     // 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 &lt; 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;
    }

    Leave a Comment