当前位置:首页>Linux>Linux 字符设备驱动从 0 到 1 实战指南

Linux 字符设备驱动从 0 到 1 实战指南

  • 2026-09-12 09:08:32
Linux 字符设备驱动从 0 到 1 实战指南

上篇我们聊了 Linux驱动10大死亡坑点,很多同学留言:

道理都懂,但一写代码就废。

今天直接上最硬核、最实用、能直接跑的实战:

从零写一个完整的 Linux 字符设备驱动。

包含:

  • 标准驱动框架

  • 完整 open/read/write/release

  • 自动创建设备节点

  • Makefile

  • 应用层测试代码

  • 编译、加载、测试全流程

你只要跟着敲,就能在开发板/虚拟机跑起来。


一、驱动整体结构(记住这5步)

任何字符驱动,都逃不出这 5 步:

  1. 分配设备号

  2. 初始化 cdev

  3. 添加 cdev 到系统

  4. 创建类、创建设备节点

  5. 实现 file_operations


二、完整驱动代码(直接复制)

文件名:demo_char.c

//内核版本linux6.8#include<linux/module.h>#include<linux/fs.h>#include<linux/cdev.h>#include<linux/device.h>#include<linux/uaccess.h>#define DEMO_MAJOR 0    /* 自动分配主设备号 */#define DEMO_MINOR 0#define DEMO_CNT 1#define DEMO_NAME "demo_char"static dev_t dev_id;static struct cdev demo_cdev;static struct class *demo_class;static struct device *demo_device;staticintdemo_open(struct inode *inode, struct file *filp){    printk("demo_open\n");    return 0;}staticintdemo_release(struct inode *inode, struct file *filp){    printk("demo_release\n");    return 0;}staticssize_tdemo_read(struct file *filp, char __user *buf,                         size_t size, loff_t *loff){    char kernel_buf[] = "hello from kernel\n";    int ret;    ret = copy_to_user(buf, kernel_buf, sizeof(kernel_buf));    if (ret)        return -EFAULT;    return sizeof(kernel_buf);}staticssize_tdemo_write(struct file *filp, constchar __user *buf,                          size_t size, loff_t *loff){    char kernel_buf[64] = {0};    int ret;    ret = copy_from_user(kernel_buf, buf, size);    if (ret)        return -EFAULT;    printk("recv from app: %s\n", kernel_buf);    return size;}static struct file_operations fops = {    .owner = THIS_MODULE,    .open = demo_open,    .read = demo_read,    .write = demo_write,    .release = demo_release,};staticint __init demo_init(void){    int ret;    /* 1. 申请设备号 */    ret = alloc_chrdev_region(&dev_id, DEMO_MINOR, DEMO_CNT, DEMO_NAME);    if (ret)        return ret;    /* 2. 初始化 cdev */    cdev_init(&demo_cdev, &fops);    demo_cdev.owner = THIS_MODULE;    /* 3. 添加 cdev */    ret = cdev_add(&demo_cdev, dev_id, DEMO_CNT);    if (ret)        goto err_cdev_add;    /* 4. 创建类 & 设备(适配Linux 6.4+ 新接口) */    // 关键修改:移除 THIS_MODULE 参数,只传类名    demo_class = class_create(DEMO_NAME);    if (IS_ERR(demo_class)) {        ret = PTR_ERR(demo_class);        goto err_class;    }    demo_device = device_create(demo_class, NULL, dev_id, NULL, DEMO_NAME);    if (IS_ERR(demo_device)) {        ret = PTR_ERR(demo_device);        goto err_device;    }    printk("demo_char driver init success\n");    return 0;err_device:    class_destroy(demo_class);err_class:    cdev_del(&demo_cdev);err_cdev_add:    unregister_chrdev_region(dev_id, DEMO_CNT);    return ret;}staticvoid __exit demo_exit(void){    device_destroy(demo_class, dev_id);    class_destroy(demo_class);    cdev_del(&demo_cdev);    unregister_chrdev_region(dev_id, DEMO_CNT);    printk("demo_char driver exit\n");}module_init(demo_init);module_exit(demo_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("嵌入式linux系统与单片机");MODULE_DESCRIPTION("Linux char device demo");

三、Makefile(直接用)

# 定义内核源码目录(自动获取当前系统内核版本)KERNELDIR ?= /lib/modules/$(shell uname -r)/build# 定义当前工作目录PWD := $(shell pwd)# 指定要编译的内核模块目标文件(demo_char.c 对应生成 demo_char.ko)obj-m += demo_char.o# 编译目标:调用内核的Makefile编译模块all:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules# 清理目标:删除编译生成的临时文件和模块文件clean:$(MAKE) -C $(KERNELDIR) M=$(PWD) clean

四、测试APP代码(应用层)

文件名:app_test.c

#include<stdio.h>#include<fcntl.h>#include<unistd.h>#include<string.h>// 可选:增加字符串操作头文件,增强代码健壮性intmain(){    int fd;    char buf[128] = {0};    // 打开字符设备文件    fd = open("/dev/demo_char", O_RDWR);    if (fd < 0) {        perror("open /dev/demo_char failed");        return -1;    }    printf("open /dev/demo_char success, fd = %d\n", fd);    // 从驱动读取数据    ssize_t read_len = read(fd, buf, sizeof(buf));    if (read_len < 0) {        perror("read failed");        close(fd);        return -1;    }    printf("read from driver: %s\n", buf);    // 向驱动写入数据    const char *write_buf = "hello app";    ssize_t write_len = write(fd, write_buf, strlen(write_buf) + 1);  // +1 包含字符串结束符 '\0'    if (write_len < 0) {        perror("write failed");        close(fd);        return -1;    }    printf("write to driver: %s (length: %zd)\n", write_buf, write_len);    // 关闭文件描述符    close(fd);    return 0;}

五、运行命令

# 编译驱动make# 编译APPgcc app_test.c -o app_test# 加载驱动sudo insmod demo_char.ko# 运行测试sudo ./app_test# 看内核打印dmesg# 卸载sudo rmmod demo_char


六、你能看到的效果

  • 自动生成 /dev/demo_char

  • 应用 read 读到内核消息

  • 应用 write 数据,内核打印出来

  • open/release 都有日志

alen@alen-virtual-machine:~/work/linux_driver/demo_char$ sudo ./app_test open /dev/demo_char success, fd = 3read from driver: hello from kernelwrite to driver: hello app (length: 10)

最新文章

随机文章