|
刚学Linux驱动,被内核态、用户态整懵!教程要么全理论粘代码就错,要么步骤跳步,新手直接一脸懵。 直到吃透字符设备驱动才发现,驱动开发居然这么简单!不用死磕原理,5分钟复制粘贴就搞定,这份抄作业级实操教程,全干货无废话,小白轻松拿捏Linux驱动入门! 不想手动复制的话,公众号回复chrdev,自动获取本地完整代码工程! 一、先搞懂:字符设备驱动到底是个啥? 用大白话讲:字符设备驱动就是“用户程序”和“电脑硬件”的中间人。 比如你想让LED灯亮、让串口传数据,都得靠它在中间传话——用户程序喊一句“亮灯”,驱动翻译成硬件能听懂的话,硬件执行后再通过驱动把结果传回来。 它最牛的地方在于:用open/read/write这三个简单命令就能搞定交互,不用复杂操作,适配绝大多数Linux系统,堪称驱动入门“万金油”。 二、环境准备:10秒搞定 不用装一堆花里胡哨的软件,就3个东西: •电脑装个Ubuntu(虚拟机也行,WSL2也能凑活) •安装编译工具和内核头文件 直接复制下面这句命令,粘贴到终端回车,等着就行: Bash sudo apt install gcc make linux-headers-$(uname -r) |
三、内核驱动代码:直接抄,别瞎改! 新建一个文件叫demo_chrdev.c,把下面的代码粘进去——我已经帮你们排好坑了,改都不用改,改了反而容易错! C #include#include<linux fs="fs">#include<linux uaccess="uaccess">#include<linux cdev="cdev">#include<linux device="device">// 设备相关参数,别乱动! #define MAJOR_NUM 100 #define DEVICE_NAME "demo_chrdev" #define CLASS_NAME "demo_class" // 内核和用户交互的缓冲区,相当于“传话筒” static char kernel_buf[64] = "hello from kernel"; static struct cdev chrdev_cdev; static struct class *chrdev_class; static struct device *chrdev_device; // 用户程序调用read时,驱动执行这个函数(给用户传数据) static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *ppos) { int len = strlen(kernel_buf); int to_copy = (size < len) ? size : len; if (*ppos >= len) return 0; // 把内核的数据拷贝给用户,别搞反了! if (copy_to_user(buf, kernel_buf, to_copy)) return -EFAULT; *ppos = to_copy; return to_copy; } // 用户程序调用write时,驱动执行这个函数(接收用户数据) static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *ppos) { int to_copy = (size < 63) ? size : 63; // 把用户的数据拷贝到内核,超了会报错! if (copy_from_user(kernel_buf, buf, to_copy)) return -EFAULT; kernel_buf[to_copy] = '\0'; printk("kernel recv: %s\n", kernel_buf); return to_copy; } // 用户程序打开设备时,驱动喊一句“我准备好了” static int demo_open(struct inode *inode, struct file *file) { printk("device opened\n"); return 0; } // 用户程序关闭设备时,驱动说“下次见” static int demo_release(struct inode *inode, struct file *file) { printk("device closed\n"); return 0; } // 驱动的“功能清单”,告诉内核我能干嘛 static struct file_operations fops = { .owner = THIS_MODULE, .open = demo_open, .release = demo_release, .read = demo_read, .write = demo_write, }; // 驱动初始化(相当于“开机启动”) static int __init demo_init(void) { int ret; dev_t dev = MKDEV(MAJOR_NUM, 0); // 申请设备号,相当于给驱动办“身份证” ret = register_chrdev_region(dev, 1, DEVICE_NAME); if (ret < 0) { printk("Failed to register chrdev region\n"); return ret; } // 初始化驱动核心 cdev_init(&chrdev_cdev, &fops); chrdev_cdev.owner = THIS_MODULE; ret = cdev_add(&chrdev_cdev, dev, 1); if (ret < 0) { printk("Failed to add cdev\n"); unregister_chrdev_region(dev, 1); return ret; } // 创建设备类,让系统认识这个驱动 chrdev_class = class_create(THIS_MODULE, CLASS_NAME); if (IS_ERR(chrdev_class)) { printk("Failed to create class\n"); cdev_del(&chrdev_cdev); unregister_chrdev_region(dev, 1); return PTR_ERR(chrdev_class); } // 创建设备节点,用户程序能找到驱动了 chrdev_device = device_create(chrdev_class, NULL, dev, NULL, DEVICE_NAME); if (IS_ERR(chrdev_device)) { printk("Failed to create device\n"); class_destroy(chrdev_class); cdev_del(&chrdev_cdev); unregister_chrdev_region(dev, 1); return PTR_ERR(chrdev_device); } printk("chrdev init ok\n"); return 0; } // 驱动卸载(相当于“关机清理”) static void __exit demo_exit(void) { dev_t dev = MKDEV(MAJOR_NUM, 0); device_destroy(chrdev_class, dev); class_destroy(chrdev_class); cdev_del(&chrdev_cdev); unregister_chrdev_region(dev, 1); printk("chrdev exit ok\n"); } module_init(demo_init); module_exit(demo_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("linuxROS"); MODULE_DESCRIPTION("字符设备驱动实操demo");</linux></linux></linux></linux> |
四、应用层测试代码:简单到离谱! 再新建一个app_chrdev.c文件,粘下面的代码——这就是我们的“用户程序”,用来测试驱动好不好使: C #include#include<fcntl.h>#include<string.h>#include<unistd.h>int main() { // 打开驱动设备,相当于“给中间人打电话” int fd = open("/dev/demo_chrdev", O_RDWR); char buf[64]; if (fd < 0) { perror("open failed"); return 1; } printf("Device opened successfully\n"); // 从驱动读取数据,相当于“听中间人回话” memset(buf, 0, sizeof(buf)); int n = read(fd, buf, sizeof(buf) - 1); if (n < 0) { perror("read failed"); close(fd); return 1; } printf("app read (%d bytes): %s\n", n, buf); // 关闭连接,礼貌收尾 close(fd); return 0; }</unistd.h></string.h></fcntl.h> |
五、Makefile:编译脚本,一键搞定! 最后建一个Makefile文件(注意首字母大写),粘下面的代码——不用懂原理,它就是帮我们自动编译的“工具人”: Makefile obj-m += demo_chrdev.o KERNELDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) all: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules gcc app_chrdev.c -o app_chrdev clean: $(MAKE) -C $(KERNELDIR) M=$(PWD) clean rm -f app_chrdev |
六、运行步骤:复制粘贴,别瞎点! 到这一步,文件已经准备好三个了:demo_chrdev.c、app_chrdev.c、Makefile,接下来按顺序执行下面的命令,一步都别错! Bash # 1. 编译代码(等着就行,别打断) make # 2. 加载驱动模块(必须加sudo,不然没权限) sudo insmod demo_chrdev.ko # 3. 运行测试程序(同样要sudo) sudo ./app_chrdev # 4. 查看驱动日志(看看驱动有没有干活) dmesg | tail # 5. 测试完卸载驱动(别占着资源) sudo rmmod demo_chrdev |
七、运行结果:这样就是成功了! 如果看到下面的输出,恭喜你!驱动跑通了! 应用层打印(终端直接显示): Plain Text Device opened successfully app read (17 bytes): hello from kernel |
内核日志(dmesg命令输出): Plain Text chrdev init ok device opened device closed |
八、小白避坑指南:血的教训! 1.所有带sudo的命令千万别省!普通用户没权限操作内核,省了就会报“Permission denied”; 2.主设备号MAJOR_NUM别乱改!如果编译报错说“设备号被占用”,改成101、102试试; 3.卸载驱动用rmmod,别直接删文件!删了文件驱动还在内存里,下次编译会冲突; 4.编译报错先检查内核头文件!如果提示“找不到xxx.h”,就是前面的环境准备命令没执行对; 5.不用手动创建设备节点!这个驱动会自动创建/dev/demo_chrdev,手动创建反而会冲突; 6.如果还是提示“权限不够”,执行这句:sudo chmod 666 /dev/demo_chrdev,给设备节点开权限。 文末小结:驱动入门就这么简单! 其实Linux驱动没那么玄乎,字符设备驱动就是入门的“敲门砖”——掌握了open/read/write的交互逻辑,后续不管是做LED驱动、串口驱动,都是换汤不换药。 这篇教程的代码我已跑通,嵌入式开发板也能用。新手不用纠结原理,先抄作业跑通,再慢慢琢磨每个函数的作用,入门效率直接翻倍! 最后说句实在的:驱动开发就是“抄作业+排坑”,关注我,后续还有更多实操教程,让你少走弯路! 免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |