DAY69 Practical Guide to Linux Character Device Drivers

Practical Guide to Linux Character Device Drivers

1. Core Concepts Review (Understand the Basics First)

1.1 What is a Character Device Driver?

A character device driver is a kernel program that operates character devices, where data is accessed sequentially as a "byte stream." Its core functions are:

  • Providing unified file operation interfaces (open/read/write/close) to applications;
  • Interfacing with hardware to implement specific hardware control logic (for demonstration purposes, printk is used here instead of actual hardware operations).

1.2 Three Essential Elements of Driver Implementation (Your Focus)

  1. Hardware Operation Methods: Implement functions like open/read/write/close to encapsulate hardware control logic;
  2. Device Number Allocation: The device number is the driver's "ID card" (32-bit, with 12 bits for the major number and 20 bits for the minor number). The major number represents the device type, while the minor number distinguishes devices of the same type;
  3. Driver Registration: Register the driver's operation methods and device number with the kernel so the kernel can recognize and associate the device.

1.3 Key Tools and Commands

Tool/Command Purpose
arm-linux-gnueabihf-gcc Cross-compiler for compiling ARM architecture applications
make zImage Compile the Linux kernel, bundling the driver module into the kernel image
cat /proc/devices View registered driver device numbers (major number + device name)
mknod Manually create a device node (applications access the driver via this node)
dmesg View kernel print messages (output from printk in the driver)

2. In-Depth Analysis of Driver Code (Your demo_driver.c)

Your demo_driver.c perfectly implements the three essential elements of a character device driver. Let's break down the core logic step by step:

2.1 Step 1: Implement Hardware Operation Methods (file_operations Structure)

The core of the driver is the struct file_operations structure, which defines the operation interfaces callable by applications. The corresponding code is:

c 复制代码
// Define hardware operation methods (for demonstration, only print debug messages)
static int open(struct inode *inode, struct file *file) {
  printk("demo1_init open\n");  // Triggered when the application calls open
  return 0;
}

static ssize_t read(struct file *file, char __user *buf, size_t size, loff_t *loff) {
  printk("demo1_init read\n");   // Triggered when the application calls read
  return 0;
}

static ssize_t write(struct file *file, const char __user *buf, size_t size, loff_t *loff) {
  printk("demo1_init write\n");  // Triggered when the application calls write
  return 0;
}

static int close(struct inode *inode, struct file *file) {
  printk("demo1_init close\n");  // Triggered when the application calls close
  return 0;
}

// Bind operation methods to the file_operations structure
static struct file_operations fops = {
  .owner = THIS_MODULE,  // Declare the module owning the driver (prevents accidental unloading)
  .open = open,          // Associate the open function
  .read = read,          // Associate the read function
  .write = write,        // Associate the write function
  .release = close       // Associate the close function (release is called when the application closes)
};

Principle : When an application calls open("/dev/demo1", O_RDWR), the kernel finds the corresponding driver via the device number and then calls the bound open function in the driver.

2.2 Step 2: Apply for a Device Number (Static Allocation Method)

The device number is the "agreement credential" between the driver and the kernel. You used static allocation (specifying a fixed major number 255 and minor number 0). Code analysis:

c 复制代码
#define DEV_MAJOR 255  // Major number (custom, must ensure it's not already in use)
#define DEV_MINOR 0    // Minor number
#define DEV_NAME "demo1"  // Device name

static dev_t dev;  // Stores the combined 32-bit device number

// Combine major and minor numbers: MKDEV(major, minor)
dev = MKDEV(DEV_MAJOR, DEV_MINOR);

// Statically allocate the device number: parameters (device number, number of devices, device name)
register_chrdev_region(dev, 1, DEV_NAME);

Note : Static allocation requires ensuring the major number is not already used by another driver. Check occupied major numbers via cat /proc/devices. If you don't want to specify manually, you can use alloc_chrdev_region for dynamic allocation (the kernel automatically assigns an unused major number).

2.3 Step 3: Register the Driver with the System (cdev Structure)

The kernel manages character device drivers via the cdev structure. You need to associate file_operations with the device number and register it with the kernel. Code analysis:

c 复制代码
static struct cdev cdev;  // Core structure for character device drivers

// Initialize cdev: bind cdev with file_operations
cdev_init(&cdev, &fops);

// Register cdev with the kernel: parameters (cdev pointer, device number, number of devices)
cdev_add(&cdev, dev, 1);

Driver Loading/Unloading : Define the driver's loading and unloading entry points via the module_init and module_exit macros:

c 复制代码
// Executed when the driver loads (during insmod or kernel startup)
static int __init demo1_init(void) {
  // Device number allocation + driver registration logic (explained above)
  printk("-------------------------------------- demo1_init\n");
  return 0;
}

// Executed when the driver unloads (rmmod)
static void __exit demo1_exit(void) {
  cdev_del(&cdev);  // Remove cdev from the kernel
  unregister_chrdev_region(dev, 1);  // Release the device number
  printk("-------------------------------------- demo1_exit\n");
}

module_init(demo1_init);  // Register the loading entry
module_exit(demo1_exit);  // Register the unloading entry

3. Application Code Analysis (Your main.c)

The application accesses the driver via the "device node" (/dev/demo1), essentially calling the driver's file_operations interfaces. Code logic:

c 复制代码
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
  // Open the device node: corresponds to the driver's open function
  int fd = open("/dev/demo1", O_RDWR);
  if (fd < 0) {
    perror("open demo1 failed");  // Open failed (e.g., device node not created)
    return 1;
  }

  char buf[10] = {0};
  read(fd, buf, sizeof buf);  // Read from the driver: corresponds to the driver's read function
  write(fd, buf, sizeof buf); // Write to the driver: corresponds to the driver's write function
  close(fd);                  // Close the driver: corresponds to the driver's close function

  return 0;
}

Interaction Flow Between Application and Driver :
app: open() → kernel: sys_open() → driver: open() → kernel returns file descriptor fd → app: read/write/close() → driver: corresponding function executes

4. Complete Practical Steps (From Compilation to Verification)

You've completed the code writing and application compilation. Now follow these steps to compile, flash, and verify the driver:

4.1 Step 1: Add the Driver to the Kernel (Critical!)

To load the driver during kernel startup, add demo_driver.c to the kernel source and modify the corresponding Makefile and Kconfig (refer to previous kernel compilation notes):

  1. Copy demo_driver.c to the kernel source's drivers/char/ directory (default directory for character device drivers);

  2. Modify drivers/char/Makefile, adding the line:

    makefile 复制代码
    obj-$(CONFIG_DEMO_DRIVER) += demo_driver.o
  3. Modify drivers/char/Kconfig, adding the driver configuration option:

    kconfig 复制代码
    config DEMO_DRIVER
        tristate "Demo Character Device Driver"
        help
          This is a demo character device driver for IMX6ULL.
  4. Enable the driver via menuconfig:

    bash 复制代码
    make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- menuconfig

    Navigate to "Character devices" → select "Demo Character Device Driver" (choose Y to compile into the kernel), then save and exit.

4.2 Step 2: Compile the Kernel to Generate zImage

bash 复制代码
make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- all -j16

After successful compilation, the zImage image containing the demo driver will be generated in the arch/arm/boot/ directory.

4.3 Step 3: Flash the Kernel and Boot the Development Board

Download zImage and the corresponding device tree (imx6ull.dtb) to the development board via TFTP or flash them to an SD card, then boot the development board.

4.4 Step 4: Verify Driver Registration Success

  1. Check Device Number : Execute the following command on the development board terminal. If you see 255 demo1, it indicates successful driver registration:

    bash 复制代码
    cat /proc/devices  
  2. Create Device Node : Applications access the driver through device nodes. Run the following command to create one (ensure the major/minor numbers match the driver):

    bash 复制代码
    mknod /dev/demo1 c 255 0  
    • c: Denotes a character device.
    • 255: Major device number.
    • 0: Minor device number.

4.5 Step 5: Run Application for Verification

  1. Copy the compiled demo1app to the development board via NFS (refer to earlier NFS mounting notes).

  2. Run the application:

    bash 复制代码
    ./demo1app  
  3. Check Driver Output : Execute dmesg. If the following output appears, it confirms successful interaction between the application and driver:

    复制代码
    -------------------------------------- demo1_init  
    demo1_init open  
    demo1_init read  
    demo1_init write  
    demo1_init close  

5. Troubleshooting Common Issues

5.1 demo1 Not Listed in cat /proc/devices

  • Driver Not Compiled into Kernel : Verify DEMO_DRIVER is enabled in menuconfig and recompile the kernel.
  • Major Number Conflict : Modify DEV_MAJOR to an unused value (e.g., 240) and recompile.

5.2 Application Fails to open("/dev/demo1")

  • Missing Device Node : Execute mknod /dev/demo1 c 255 0.
  • Insufficient Permissions : Run chmod 777 /dev/demo1 to grant full access.

5.3 printk Output Not Visible in dmesg

  • Kernel Log Level Restriction : Execute echo 8 > /proc/sys/kernel/printk to enable all log levels.
相关推荐
zdl6861 分钟前
spring Profile
java·数据库·spring
Gauss松鼠会7 分钟前
【GaussDB】GaussDB 表的创建与使用之临时表
数据库·database·opengauss·gaussdb
RestCloud10 分钟前
Oracle CDC实战:如何构建企业级实时数据同步架构
数据库·oracle·etl·etlcloud·数据同步·数据集成平台
zhougl99610 分钟前
Maven build配置
java·linux·maven
单片机设计星球12 分钟前
51单片机的【智能家居系统】仿真设计
嵌入式硬件·51单片机·智能家居
逐步前行12 分钟前
STM32_SysTick_系统定时器
stm32·单片机·嵌入式硬件
Predestination王瀞潞12 分钟前
CentOS7虚拟机安装过程中没有打开网卡,ip addr无法查看es33这个情况下的解决方法
服务器·网络·tcp/ip
dgfhf17 分钟前
使用Python处理计算机图形学(PIL/Pillow)
jvm·数据库·python
૮・ﻌ・18 分钟前
Node.js - 04:MongoDB、会话控制
数据库·mongodb·node.js·jwt·token·cookie·session
闻哥21 分钟前
MySQL三大日志深度解析:redo log、undo log、binlog 原理与实战
android·java·jvm·数据库·mysql·adb·面试