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.
相关推荐
Gofarlic_oms14 小时前
利用API实现ANSYS许可证管理自动化集成
运维·服务器·开发语言·matlab·自动化·负载均衡
Chat_zhanggong3455 小时前
主推RK3567J作用有哪些?
人工智能·嵌入式硬件
Ww.xh5 小时前
STM32与ESP8266AT指令超时重传方案
stm32·单片机·嵌入式硬件
LCG元5 小时前
STM32实战:基于STM32F103的智能共享充电宝管理系统
stm32·单片机·嵌入式硬件
ss2736 小时前
食谱推荐系统功能测试如何写?
java·数据库·spring boot·功能测试
点灯师6 小时前
基于单片机的智能家居智能雨水自动关窗控制系统设计
单片机·嵌入式硬件·毕业设计·智能家居·课程设计·期末大作业
Smart-佀6 小时前
涨薪秘技:智能家居中的BLE协议与实现
网络·arm开发·嵌入式硬件·microsoft
l1t6 小时前
DeepSeek总结的数据库外部表
数据库
m0_674294646 小时前
如何编写SQL存储过程性能对比_记录执行时间评估优化效果
jvm·数据库·python
倔强的石头1066 小时前
【Linux指南】基础IO系列(八):实战衔接 —— 给微型 Shell 添加完整重定向功能
linux·运维·服务器