当前位置: 首页 > article >正文

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

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驱动最核心的工作原理。

相关文章:

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

Linux内核驱动开发入门:手把手实现虚拟CDC ACM设备驱动 第一次接触Linux内核驱动开发时,面对复杂的代码结构和晦涩的概念,我完全摸不着头脑。直到导师扔给我一个USB转串口设备:"试试看能不能让它在Linux上工作"。经过两…...

Chocolatey 安装 Python 3 时那些你可能不知道的隐藏依赖(附详细日志分析)

Chocolatey 安装 Python 3 时那些你可能不知道的隐藏依赖(附详细日志分析) 当你在 Windows 系统上使用 Chocolatey 安装 Python 3 时,表面上看只是一条简单的命令,但背后却隐藏着一系列复杂的依赖处理过程。这些自动安装的组件往往…...

Jetson Orin Nano系统降级实战:从Ubuntu 22.04回退至20.04的避坑指南

1. 为什么需要从Ubuntu 22.04降级到20.04? 最近很多使用Jetson Orin Nano开发板的开发者都遇到了一个棘手的问题:Ubuntu 22.04的软件生态兼容性。我自己在实际项目中就踩过这个坑,当时为了追求新版本的系统性能,直接安装了Ubuntu …...

NXOpen 遍历部件并对每个部件加属性

NXOpen 遍历部件并对每个部件加属性 // Mandatory UF Includes #include <uf.h> #include <uf_object_types.h> // Internal Includes #include <NXOpen/ListingWindow.hxx> #include <NXOpen/NXMessageBox.hxx> #include <NXOpen/UI.hxx> //…...

Atlas800T A2上部署Qwen2.5-Omni-7B音频模型:从驱动安装到vllm-ascend服务启动的保姆级避坑记录

Atlas800T A2服务器部署Qwen2.5-Omni-7B音频模型全流程实战指南 在昇腾Atlas800T A2服务器上部署多模态大模型Qwen2.5-Omni-7B&#xff0c;对于需要处理音频转文字任务的开发者而言&#xff0c;既是技术挑战也是效率提升的关键一步。本文将带你从零开始&#xff0c;逐步完成从硬…...

NXOpen 方式创建拉伸和预览

//用户代码 #include "ExtrudewithPreview.hpp" #include "NXOpen/Body.hxx" #include "NXOpen/Direction.hxx" #include "NXOpen/DisplayableObject.hxx" #include "NXOpen/DisplayModification.hxx" #include "…...

CSS遮罩艺术:从基础阴影到高级毛玻璃特效实战

1. 从零开始理解CSS遮罩 遮罩效果在前端开发中就像给界面元素戴上了一层"面纱"。想象一下&#xff0c;当你需要突出某个弹窗内容时&#xff0c;背后的页面会变暗——这就是最常见的遮罩应用场景。我们先从最基础的实现方式说起。 基础遮罩的实现通常需要一个覆盖全…...

IQuest-Coder-V1功能实测:一键生成高质量SQL查询脚本

IQuest-Coder-V1功能实测&#xff1a;一键生成高质量SQL查询脚本 在数据驱动的时代&#xff0c;SQL查询脚本的编写是每个数据分析师、后端工程师乃至产品经理的日常。面对复杂的业务逻辑和多表关联&#xff0c;手动编写SQL不仅耗时&#xff0c;还容易出错。有没有一种工具&…...

Nanbeige4.1-3B部署避坑指南:vLLM加载失败排查与llm.log日志分析技巧

Nanbeige4.1-3B部署避坑指南&#xff1a;vLLM加载失败排查与llm.log日志分析技巧 1. 引言&#xff1a;从部署成功到问题排查 当你满怀期待地部署一个像Nanbeige4.1-3B这样的高性能小模型时&#xff0c;最怕看到的就是服务启动失败。特别是使用vLLM这种高效推理框架时&#xf…...

SUNFLOWER MATCH LAB 效果深度评测:对比传统CNN与LSTM的识别性能

SUNFLOWER MATCH LAB 效果深度评测&#xff1a;对比传统CNN与LSTM的识别性能 向日葵的生长过程&#xff0c;就像一部无声的纪录片&#xff0c;每一天的叶片舒展、花盘转动都蕴含着丰富的信息。过去&#xff0c;我们想读懂这部纪录片&#xff0c;要么靠农学专家日复一日的田间观…...

Z-Image Turbo在工业设计中的应用:产品概念图生成

Z-Image Turbo在工业设计中的应用&#xff1a;产品概念图生成 1. 引言 工业设计师的日常工作中&#xff0c;最耗时但又最关键的环节是什么&#xff1f;答案往往是概念图的创作和渲染。传统的工作流程中&#xff0c;设计师需要先手绘草图&#xff0c;然后在专业软件中建模、渲…...

Elsevier Tracker终极指南:3分钟搞定学术论文审稿状态追踪

Elsevier Tracker终极指南&#xff1a;3分钟搞定学术论文审稿状态追踪 【免费下载链接】Elsevier-Tracker 项目地址: https://gitcode.com/gh_mirrors/el/Elsevier-Tracker 还在为Elsevier期刊审稿进度而焦虑吗&#xff1f;每天刷新页面、等待邮件通知的日子终于可以结…...

3大核心优势+4类场景方案:Lenovo Legion Toolkit让游戏本性能释放提升30%

3大核心优势4类场景方案&#xff1a;Lenovo Legion Toolkit让游戏本性能释放提升30% 【免费下载链接】LenovoLegionToolkit Lightweight Lenovo Vantage and Hotkeys replacement for Lenovo Legion laptops. 项目地址: https://gitcode.com/gh_mirrors/le/LenovoLegionToolk…...

突破性QQ音乐加密文件解码工具:qmcdump让音乐自由播放的革新方案

突破性QQ音乐加密文件解码工具&#xff1a;qmcdump让音乐自由播放的革新方案 【免费下载链接】qmcdump 一个简单的QQ音乐解码&#xff08;qmcflac/qmc0/qmc3 转 flac/mp3&#xff09;&#xff0c;仅为个人学习参考用。 项目地址: https://gitcode.com/gh_mirrors/qm/qmcdump …...

千问3.5-2B部署教程(低成本GPU方案):单卡24GB显存跑通开源VL模型实录

千问3.5-2B部署教程&#xff08;低成本GPU方案&#xff09;&#xff1a;单卡24GB显存跑通开源VL模型实录 1. 千问3.5-2B模型介绍 千问3.5-2B是Qwen系列中的小型视觉语言模型(VL)&#xff0c;它能够同时理解图片内容和处理自然语言。这个模型特别适合那些需要在有限硬件资源上…...

libssh2非阻塞模式实战:单线程管理多个SSH连接的高效技巧

libssh2非阻塞模式实战&#xff1a;单线程管理多个SSH连接的高效技巧 在当今分布式系统和自动化运维的浪潮中&#xff0c;SSH协议作为远程管理的黄金标准&#xff0c;其性能瓶颈往往出现在需要同时管理大量连接时。传统多线程方案不仅资源消耗大&#xff0c;还面临线程同步的复…...

Mermaid在线编辑器终极指南:免费实时图表创作工具完全解析

Mermaid在线编辑器终极指南&#xff1a;免费实时图表创作工具完全解析 【免费下载链接】mermaid-live-editor Edit, preview and share mermaid charts/diagrams. New implementation of the live editor. 项目地址: https://gitcode.com/GitHub_Trending/me/mermaid-live-ed…...

WinForms界面美化:用SunnyUI的UILight控件做个状态指示灯(附完整代码)

WinForms界面美化实战&#xff1a;用SunnyUI的UILight控件打造专业状态指示灯 在桌面应用开发中&#xff0c;状态指示是用户界面不可或缺的元素。传统的WinForms控件往往显得单调乏味&#xff0c;而SunnyUI的UILight控件为我们提供了一种简单高效的解决方案。这个圆形指示灯控…...

闲鱼数据采集终极指南:零代码自动化抓取二手商品信息

闲鱼数据采集终极指南&#xff1a;零代码自动化抓取二手商品信息 【免费下载链接】xianyu_spider 闲鱼APP数据爬虫 项目地址: https://gitcode.com/gh_mirrors/xia/xianyu_spider 想要轻松获取闲鱼平台上的商品数据&#xff0c;却不想编写复杂的爬虫代码&#xff1f;xia…...

文墨共鸣部署案例:中小企业低成本部署水墨风语义分析SaaS前端

文墨共鸣部署案例&#xff1a;中小企业低成本部署水墨风语义分析SaaS前端 1. 项目介绍与价值 文墨共鸣是一个将深度学习技术与传统水墨美学完美结合的语义分析系统。这个项目专门为中文文本设计&#xff0c;能够智能分析两段文字之间的语义相似度&#xff0c;判断它们是"…...

深度解析ViGEmBus:如何高效构建Windows内核级游戏控制器模拟框架

深度解析ViGEmBus&#xff1a;如何高效构建Windows内核级游戏控制器模拟框架 【免费下载链接】ViGEmBus Windows kernel-mode driver emulating well-known USB game controllers. 项目地址: https://gitcode.com/gh_mirrors/vi/ViGEmBus ViGEmBus是一款基于Windows内核…...

11款独特开源字体,让你的创意设计焕发生机

11款独特开源字体&#xff0c;让你的创意设计焕发生机 【免费下载链接】HoYo-Glyphs Constructed scripts by HoYoverse 米哈游的架空文字 项目地址: https://gitcode.com/gh_mirrors/ho/HoYo-Glyphs 在数字创作领域&#xff0c;字体是视觉表达的核心元素。然而&#xf…...

实战分享:我是如何搞定SHEIN新版反爬(anti-in, smdeviceid, armortoken, x-gw-auth)的

电商平台数据采集实战&#xff1a;逆向工程与参数生成策略 最近半年&#xff0c;电商平台的反爬机制呈现出明显的升级趋势。以某国际快时尚电商为例&#xff0c;其新增的四个核心校验参数&#xff08;anti-in、smdeviceid、armortoken、x-gw-auth&#xff09;构成了完整的安全验…...

从硬件到代码:深入理解ARM中断向量表的工作原理与设计哲学

ARM中断向量表&#xff1a;从硬件设计到软件实现的深度解析 在嵌入式系统开发中&#xff0c;中断机制是处理器响应外部事件的核心机制之一。作为ARM架构中异常处理的基础设施&#xff0c;中断向量表的设计直接影响着系统的实时性和可靠性。本文将深入探讨ARM中断向量表的工作原…...

SpringBoot项目中如何用拦截器优雅解决越权漏洞?附完整代码示例

SpringBoot拦截器实战&#xff1a;三层防御体系解决越权漏洞 在电商系统开发中&#xff0c;我们团队曾遭遇过一次严重的越权事故——某用户通过修改URL参数&#xff0c;成功访问到其他用户的订单详情页面。这次事件让我们意识到&#xff0c;权限控制绝非简单的登录验证就能解决…...

告别printf调试:手把手教你用STM32F411的USART6重定向标准输入输出

STM32F411串口调试革命&#xff1a;USART6重定向实战指南 在嵌入式开发中&#xff0c;调试信息的输出是开发者最依赖的工具之一。传统调试方式往往需要复杂的硬件调试器或频繁烧录程序&#xff0c;效率低下且不够灵活。本文将带你探索一种高效、便捷的调试方案——通过STM32F4…...

深入解析PCS1800分布式控制系统:架构设计与工业应用实践

1. PCS1800分布式控制系统架构解析 第一次接触PCS1800系统是在2013年某化工厂的DCS改造项目上。当时现场老师傅指着机柜里整齐排列的模块说&#xff1a;"这玩意儿就像人的神经系统&#xff0c;MNet是大脑&#xff0c;SNet是脊髓&#xff0c;CNet就是末梢神经。"这个…...

为什么高端芯片都爱用Flip Chip?对比Wire Bonding的5大优势详解

为什么高端芯片都爱用Flip Chip&#xff1f;对比Wire Bonding的5大优势详解 在芯片封装领域&#xff0c;Flip Chip&#xff08;倒装芯片&#xff09;技术正逐渐成为高端应用的标配。想象一下&#xff0c;当你手持最新款智能手机&#xff0c;流畅运行着复杂的AI应用时&#xff0…...

RexUniNLU新手必看:从模型下载到API服务部署完整流程

RexUniNLU新手必看&#xff1a;从模型下载到API服务部署完整流程 1. 引言&#xff1a;为什么选择RexUniNLU&#xff1f; RexUniNLU是一款基于Siamese-UIE架构的轻量级自然语言理解框架&#xff0c;它最大的特点是支持零样本学习——这意味着你不需要准备任何标注数据&#xf…...

32位MCU轻量级OTA方案设计与实现

1. 项目概述&#xff1a;专为32位MCU设计的轻量级OTA方案在嵌入式设备开发中&#xff0c;固件升级一直是个令人头疼的问题。传统方式需要拆机连接烧录器&#xff0c;对于部署在偏远或密闭环境中的设备简直是场噩梦。上周分享的UART OTA方案获得不少开发者关注&#xff0c;今天带…...