『7x24小时有问必答』
刚学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驱动、串口驱动,都是换汤不换药。
这篇教程的代码我已跑通,嵌入式开发板也能用。新手不用纠结原理,先抄作业跑通,再慢慢琢磨每个函数的作用,入门效率直接翻倍!

最后说句实在的:驱动开发就是“抄作业+排坑”,关注我,后续还有更多实操教程,让你少走弯路!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

上一主题上一主题         下一主题下一主题
QQ手机版小黑屋粤ICP备17165530号

关于我们·投诉举报· 用户帮助· 联系我们 · 本站服务 · 版权声明· 隐私政策 · 投搞指南

法律保护:PLC技术网,plcjs.com,plcjs.net等字样
Copyright 2010-2030. All rights reserved. 


微信公众号二维码 抖音二维码 百家号二维码 今日头条二维码哔哩哔哩二维码