Linux内核驱动开发入门:我是如何给一个虚拟CDC ACM设备写“Hello World”驱动的

news2026/4/3 8:37:18
Linux内核驱动开发入门手把手实现虚拟CDC ACM设备驱动第一次接触Linux内核驱动开发时面对复杂的代码结构和晦涩的概念我完全摸不着头脑。直到导师扔给我一个USB转串口设备试试看能不能让它在Linux上工作。经过两周的挣扎我终于理解了CDC ACM驱动如何作为USB与TTY子系统之间的桥梁。今天我们就从零开始用QEMU模拟一个最简单的虚拟CDC ACM设备实现一个能打印Hello World的基础驱动。1. 环境准备与基础概念在开始编码前我们需要搭建一个安全的开发环境。推荐使用Ubuntu 22.04 LTS作为开发主机配合QEMU虚拟机进行测试。这样即使驱动崩溃也不会影响主系统稳定性。必备工具链安装sudo apt install build-essential libncurses-dev flex bison libssl-dev qemu-system-x86CDC ACMCommunication Device Class Abstract Control Model是USB协议中定义的一种设备类别常用于USB转串口设备。它的核心架构包含三个关键部分USB设备驱动层处理USB协议通信和设备枚举TTY子系统接口提供标准的字符设备操作数据转换层在USB数据包和串行数据流之间转换理解这个分层架构非常重要——我们的驱动本质上是在实现这两层之间的翻译器。2. 创建最简单的内核模块框架我们先从标准的内核模块模板开始。创建一个新的目录结构~/cdc_hello/ ├── Makefile └── cdc_hello.cMakefile内容obj-m : cdc_hello.o KDIR : /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M$(PWD) modules clean: make -C $(KDIR) M$(PWD) clean基础模块代码cdc_hello.c#include linux/module.h #include linux/kernel.h static int __init cdc_hello_init(void) { printk(KERN_INFO CDC Hello: module loaded\n); return 0; } static void __exit cdc_hello_exit(void) { printk(KERN_INFO CDC Hello: module unloaded\n); } module_init(cdc_hello_init); module_exit(cdc_hello_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Your Name); MODULE_DESCRIPTION(Simple CDC ACM Hello World Driver);编译并测试这个空模块make sudo insmod cdc_hello.ko dmesg | tail -n 1 # 应该看到加载消息 sudo rmmod cdc_hello dmesg | tail -n 1 # 应该看到卸载消息3. 实现USB设备驱动基础现在我们来添加USB支持。CDC ACM驱动需要两个关键结构体usb_driver和usb_device_id。首先扩展头文件包含#include linux/usb.h #include linux/tty.h #include linux/tty_driver.h定义我们的虚拟USB设备IDstatic struct usb_device_id cdc_hello_table[] { { USB_DEVICE(0x1234, 0x5678) }, // 自定义的厂商ID和产品ID { } /* 终止项 */ }; MODULE_DEVICE_TABLE(usb, cdc_hello_table);实现probe和disconnect函数static int cdc_hello_probe(struct usb_interface *interface, const struct usb_device_id *id) { printk(KERN_INFO CDC Hello: Device connected\n); return 0; } static void cdc_hello_disconnect(struct usb_interface *interface) { printk(KERN_INFO CDC Hello: Device disconnected\n); }定义usb_driver结构体static struct usb_driver cdc_hello_driver { .name cdc_hello, .probe cdc_hello_probe, .disconnect cdc_hello_disconnect, .id_table cdc_hello_table, };修改模块初始化和退出函数static int __init cdc_hello_init(void) { int retval; printk(KERN_INFO CDC Hello: initializing\n); retval usb_register(cdc_hello_driver); if (retval) { printk(KERN_ERR USB register failed: %d\n, retval); return retval; } return 0; } static void __exit cdc_hello_exit(void) { usb_deregister(cdc_hello_driver); printk(KERN_INFO CDC Hello: driver unregistered\n); }现在我们已经有了一个能识别特定USB设备的基础框架。可以用QEMU测试这个驱动是否能正确识别虚拟设备。4. 集成TTY子系统真正的CDC ACM驱动需要在USB和TTY之间建立桥梁。让我们实现最基础的TTY操作。首先定义tty_driver和tty_operations结构体static struct tty_driver *cdc_hello_tty_driver; static int cdc_hello_tty_open(struct tty_struct *tty, struct file *file) { printk(KERN_INFO CDC Hello: tty opened\n); return 0; } static void cdc_hello_tty_close(struct tty_struct *tty, struct file *file) { printk(KERN_INFO CDC Hello: tty closed\n); } static int cdc_hello_tty_write(struct tty_struct *tty, const unsigned char *buf, int count) { printk(KERN_INFO CDC Hello: writing %d bytes\n, count); return count; } static const struct tty_operations cdc_hello_tty_ops { .open cdc_hello_tty_open, .close cdc_hello_tty_close, .write cdc_hello_tty_write, };更新probe函数来注册TTY设备static int cdc_hello_probe(struct usb_interface *interface, const struct usb_device_id *id) { int retval; printk(KERN_INFO CDC Hello: Device connected\n); // 分配TTY驱动 cdc_hello_tty_driver alloc_tty_driver(1); if (!cdc_hello_tty_driver) return -ENOMEM; // 设置TTY驱动属性 cdc_hello_tty_driver-driver_name cdc_hello; cdc_hello_tty_driver-name ttyHELLO; cdc_hello_tty_driver-type TTY_DRIVER_TYPE_SERIAL; cdc_hello_tty_driver-subtype SERIAL_TYPE_NORMAL; cdc_hello_tty_driver-flags TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV; tty_set_operations(cdc_hello_tty_driver, cdc_hello_tty_ops); // 注册TTY驱动 retval tty_register_driver(cdc_hello_tty_driver); if (retval) { printk(KERN_ERR Failed to register TTY driver\n); put_tty_driver(cdc_hello_tty_driver); return retval; } return 0; }同时更新disconnect函数static void cdc_hello_disconnect(struct usb_interface *interface) { printk(KERN_INFO CDC Hello: Device disconnected\n); tty_unregister_driver(cdc_hello_tty_driver); put_tty_driver(cdc_hello_tty_driver); }现在我们的驱动已经能够创建/dev/ttyHELLO设备节点了。虽然还不能真正通信但已经完成了基本的框架搭建。5. 实现数据通路与测试最后我们来实现最简单的数据回显功能。首先在probe函数中添加一个urbUSB Request Block用于接收数据static void cdc_hello_rx_complete(struct urb *urb) { struct tty_struct *tty urb-context; if (urb-status) { printk(KERN_ERR CDC Hello: RX error %d\n, urb-status); return; } tty_insert_flip_string(tty, urb-transfer_buffer, urb-actual_length); tty_flip_buffer_push(tty); // 重新提交URB继续接收 usb_submit_urb(urb, GFP_ATOMIC); }更新probe函数设置USB通信static int cdc_hello_probe(struct usb_interface *interface, const struct usb_device_id *id) { struct usb_device *udev interface_to_usbdev(interface); struct usb_endpoint_descriptor *ep_in, *ep_out; // ...之前的TTY初始化代码... // 查找批量传输端点 if (!usb_find_common_endpoints(interface-cur_altsetting, ep_in, ep_out, NULL, NULL)) { printk(KERN_ERR CDC Hello: no bulk endpoints found\n); return -ENODEV; } // 创建接收URB cdc_hello_rx_urb usb_alloc_urb(0, GFP_KERNEL); if (!cdc_hello_rx_urb) return -ENOMEM; // 设置URB参数 usb_fill_bulk_urb(cdc_hello_rx_urb, udev, usb_rcvbulkpipe(udev, ep_in-bEndpointAddress), cdc_hello_rx_buffer, sizeof(cdc_hello_rx_buffer), cdc_hello_rx_complete, tty); // 提交URB开始接收数据 usb_submit_urb(cdc_hello_rx_urb, GFP_KERNEL); return 0; }更新write操作实现真实的数据发送static int cdc_hello_tty_write(struct tty_struct *tty, const unsigned char *buf, int count) { struct urb *urb; int retval; urb usb_alloc_urb(0, GFP_KERNEL); if (!urb) return -ENOMEM; usb_fill_bulk_urb(urb, cdc_hello_udev, usb_sndbulkpipe(cdc_hello_udev, ep_out-bEndpointAddress), (void *)buf, count, cdc_hello_tx_complete, NULL); retval usb_submit_urb(urb, GFP_KERNEL); if (retval) { usb_free_urb(urb); return retval; } return count; }现在你可以编译加载这个模块然后在QEMU虚拟机中测试echo Hello World /dev/ttyHELLO通过dmesg应该能看到数据发送和接收的日志。虽然这个实现非常基础但它展示了CDC ACM驱动最核心的工作原理。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2478260.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…