当前位置:首页>Linux>Linux字符驱动5分钟拿捏

Linux字符驱动5分钟拿捏

  • 2026-09-10 18:55:25
Linux字符驱动5分钟拿捏

刚学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#include#include#include// 设备相关参数,别乱动!              #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");

四、应用层测试代码:简单到离谱!

再新建一个app_chrdev.c文件,粘下面的代码——这就是我们的“用户程序”,用来测试驱动好不好使:

C                  #include#include#include#includeint 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;             }

五、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驱动、串口驱动,都是换汤不换药。

这篇教程的代码我已跑通,嵌入式开发板也能用。新手不用纠结原理,先抄作业跑通,再慢慢琢磨每个函数的作用,入门效率直接翻倍!

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

最新文章

随机文章