上篇我们聊了 Linux驱动10大死亡坑点,很多同学留言:
道理都懂,但一写代码就废。
今天直接上最硬核、最实用、能直接跑的实战:
从零写一个完整的 Linux 字符设备驱动。
包含:
你只要跟着敲,就能在开发板/虚拟机跑起来。
一、驱动整体结构(记住这5步)
任何字符驱动,都逃不出这 5 步:
分配设备号
初始化 cdev
添加 cdev 到系统
创建类、创建设备节点
实现 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)