资讯动态

Linux USB驱动开发实战:从设备匹配到URB数据收发

发布时间:2026/10/14 16:23:30 来源:尧图企业网站定制
1. 项目概述为什么今天还要亲手写USB驱动USB驱动不是古董而是嵌入式与Linux系统开发里最常碰、也最容易栽跟头的“日常关卡”。我带过不少刚从学校出来的开发者一听说要写USB驱动第一反应是翻内核文档、查usb-skeleton.c示例结果编译报错、设备插拔无响应、urb提交失败、中断收不到——最后发现连idVendor和idProduct怎么填都搞错了。这不是能力问题是缺一套真正贴着硬件、贴着内核、贴着调试现场走下来的实操路径。“usb驱动的分析与编写”这八个字背后藏着三层真实需求第一层是“看懂”——能读通lsusb -v输出、能定位设备描述符结构、能分辨配置/接口/端点三级拓扑第二层是“复现”——不依赖usbserial或cdc_acm这类通用驱动从零注册usb_driver结构体、实现probe/disconnect回调、正确分配urb并提交第三层是“调通”——在真实设备上跑通数据收发用cat /proc/bus/usb/devices验证绑定状态用usbmon抓包确认控制传输时序用dmesg | grep usb定位内存越界或同步错误。这个内容适合三类人一是正在啃《Linux设备驱动开发详解》第12章却卡在usb_submit_urb()返回-1的嵌入式工程师二是做USB转串口模块固件升级、需要自定义控制请求比如发送0x22类厂商命令的硬件工程师三是参与国产化替代项目、必须绕过Windows驱动直接对接国产Linux发行版的系统集成人员。它不讲抽象理论只讲你插上设备后内核怎么认、怎么配、怎么传、怎么断——每一步都有printk打点位置、dmesg关键日志片段、hexdump原始数据截图逻辑所有代码可直接粘贴进.c文件编译测试。2. USB协议栈分层与驱动定位先看清“战场地图”2.1 USB协议栈不是单层而是一张四层协作网很多人误以为USB驱动就是“写个probe函数”其实它只是冰山露出水面的1/5。真正的协作链路是物理层线缆/PHY→ 协议层USB Spec 2.0/3.0→ 主机控制器层xHCI/EHCI/OHCI→ 核心驱动层usbcore→ 设备驱动层你的代码。其中前两层由硬件和USB规范固化后三层才是我们能动手的地方。而usbcore是整个USB子系统的“中枢神经”它不处理具体设备功能只干三件事设备枚举调度、urb生命周期管理、接口绑定分发。你的驱动本质是向usbcore注册一个“服务承诺”当某个VID/PID的设备插入时请把它的接口实例交给我来管。提示usbcore模块在内核中默认编译进内核CONFIG_USBy不可卸载。你写的驱动模块.ko必须依赖它编译时需在Makefile中加obj-m my_usb_drv.o并声明my_usb_drv-objs : usb_main.o usb_xfer.o。2.2 设备描述符是驱动的“身份证”必须逐字对齐USB设备一上电主机就通过默认地址0发起8次标准控制请求GET_DESCRIPTOR依次获取设备、配置、接口、端点描述符。这些二进制结构体决定了驱动能否加载成功。以最常用的struct usb_device_descriptor为例struct usb_device_descriptor { __u8 bLength; // 描述符长度固定为18 __u8 bDescriptorType; // 类型设备描述符为0x01 __le16 bcdUSB; // USB规范版本如0x0200表示2.0 __u8 bDeviceClass; // 类别0x00未指定0xFF厂商自定义 __u8 bDeviceSubClass; // 子类常与Class联动 __u8 bDeviceProtocol; // 协议如CDC ACM为0x01 __u8 bMaxPacketSize0; // EP0最大包长常见64低速、64全速、64高速 __le16 idVendor; // 厂商ID必须与驱动中.usb_device_id匹配 __le16 idProduct; // 产品ID同上 __le16 bcdDevice; // 设备版本号如0x0100 __u8 iManufacturer; // 厂商字符串索引0表示无 __u8 iProduct; // 产品字符串索引 __u8 iSerialNumber; // 序列号字符串索引 __u8 bNumConfigurations; // 配置数通常为1 } __attribute__ ((packed));关键点在于idVendor和idProduct必须与驱动中usb_device_id数组完全一致且大小端对齐。我曾遇到一个案例某国产MCU芯片的USB设备描述符里idVendor0x1234但驱动里写成{ USB_DEVICE(0x3412, 0x5678) }——表面看是十六进制书写习惯问题实则是__le16类型要求小端存储0x1234在内存中存为0x34 0x12而USB_DEVICE宏内部已做cpu_to_le16()转换所以必须写原值。否则usbcore匹配失败dmesg里只有一句usb 1-1: new full-speed USB device number 3 using xhci_hcd再无下文。2.3usb_driver结构体驱动的“入职申请表”驱动向内核“投递简历”的核心是struct usb_driver它不是函数指针集合而是一份完整的设备服务契约。完整定义如下精简关键字段static struct usb_driver my_usb_driver { .name my_usb_dev, // 驱动名出现在/sys/bus/usb/drivers/ .probe my_usb_probe, // 设备匹配成功后调用 .disconnect my_usb_disconnect, // 设备拔出时调用 .id_table my_usb_table, // 匹配规则表必填 .suspend my_usb_suspend, // 电源管理挂起 .resume my_usb_resume, // 电源管理恢复 .reset_resume my_usb_reset_resume, // 重置后恢复 .supports_autosuspend 1, // 是否支持自动挂起 .soft_unbind 1, // 是否允许软解绑 };其中.id_table是生死线。常见写法有三种USB_DEVICE(vendor, product)精确匹配一对VID/PIDUSB_DEVICE_VER(vendor, product, lo, hi)匹配VID/PID版本号范围USB_DEVICE_INTERFACE_CLASS(vendor, product, class)按接口类别匹配如USB_CLASS_HID。注意.id_table必须以{ }结尾即最后一个元素必须是全0的空结构体否则内核模块加载时usb_register_driver()会返回-EINVAL。这是新手踩坑率最高的地方之一——少写一个{ }insmod静默失败dmesg无任何提示。2.4 设备、配置、接口、端点四层拓扑必须手动画出来一个USB设备可能有多个配置如供电模式切换每个配置含多个接口如音频设备含控制播放录音接口每个接口含多个端点IN/OUT方向。驱动实际操作的是接口interface而非整个设备。probe函数的第一个参数struct usb_interface *iface就是usbcore为你筛选出的、符合.id_table规则的那个接口实例。我习惯在写驱动前先用lsusb -v -d vid:pid导出完整描述符树然后手绘一张拓扑图。例如某USB转串口芯片CH340的典型结构Device Descriptor: idVendor 0x1a86, idProduct 0x7523 Configuration Descriptor: bNumInterfaces 1 Interface Descriptor: bInterfaceNumber 0 bNumEndpoints 3 bInterfaceClass 0xFF (Vendor Specific) bInterfaceSubClass 0x01 bInterfaceProtocol 0x02 Endpoint Descriptor: EP 0x81 (IN, Bulk, MaxPacketSize64) Endpoint Descriptor: EP 0x02 (OUT, Bulk, MaxPacketSize64) Endpoint Descriptor: EP 0x83 (IN, Interrupt, MaxPacketSize8) ← 状态中断端点这意味着驱动必须申请iface-cur_altsetting当前活动接口设置从中解析出3个端点并为IN/Bulk端点分配接收urb为OUT/Bulk端点准备发送缓冲区为IN/Interrupt端点单独建一个中断urb轮询状态变化。漏掉任何一个端点数据流就断了。3. 驱动编写全流程从注册到数据收发的七步实操3.1 第一步环境准备与最小模块框架内核版本决定API细节。本文基于主流LTS内核5.10.x适配国产OS常用基线所有代码经实测可在Ubuntu 20.04 kernel 5.10.0-1057-oem上运行。首先创建my_usb_drv.c填入最小可加载框架#include linux/module.h #include linux/kernel.h #include linux/usb.h #include linux/slab.h // 必须声明许可证否则insmod报错module license unspecified taints kernel MODULE_LICENSE(GPL); MODULE_AUTHOR(A developer); MODULE_DESCRIPTION(Minimal USB driver example); MODULE_VERSION(1.0); // 驱动入口/出口函数 static int __init my_usb_init(void) { int ret; printk(KERN_INFO my_usb_drv: loading...\n); ret usb_register(my_usb_driver); if (ret) printk(KERN_ERR my_usb_drv: register failed, %d\n, ret); return ret; } static void __exit my_usb_exit(void) { usb_deregister(my_usb_driver); printk(KERN_INFO my_usb_drv: unloaded.\n); } module_init(my_usb_init); module_exit(my_usb_exit);配套Makefileifneq ($(KERNELRELEASE),) obj-m : my_usb_drv.o else KDIR : /lib/modules/$(shell uname -r)/build all: $(MAKE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) -C $(KDIR) M$(PWD) clean endif执行make sudo insmod my_usb_drv.kodmesg | tail -5应看到loading...日志。此时驱动已注册但尚未匹配任何设备——因为.id_table还是空的。3.2 第二步填充usb_device_id匹配表并验证绑定假设目标设备VID0x0403, PID0x6001FTDI经典芯片添加匹配表static const struct usb_device_id my_usb_table[] { { USB_DEVICE(0x0403, 0x6001) }, // FTDI FT232 { USB_DEVICE(0x1a86, 0x7523) }, // Qinheng CH340 { } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, my_usb_table); // 必须声明否则udev无法识别重新编译加载插入设备前先开两个终端终端1sudo dmesg -w监控内核日志终端2sudo modprobe -r my_usb_drv sudo insmod my_usb_drv.ko插入设备瞬间终端1应刷出usb 1-1: new full-speed USB device number 4 using xhci_hcd usb 1-1: New USB device found, idVendor0403, idProduct6001, bcdDevice 6.00 usb 1-1: New USB device strings: Mfr1, Product2, SerialNumber3 usb 1-1: Product: FT232R USB UART usb 1-1: Manufacturer: FTDI usb 1-1: SerialNumber: AL01VQ7E my_usb_drv: probe called for interface 0若没看到最后一行说明匹配失败。此时检查lsusb -d 0403:6001 -v | grep idVendor\|idProduct确认设备真实VID/PIDcat /sys/bus/usb/drivers/my_usb_dev/bind确认驱动是否支持手动绑定用于调试。3.3 第三步probe函数实现——获取资源与初始化probe是驱动的“上岗仪式”必须完成四件事校验接口、申请内存、解析端点、提交urb。完整代码如下static int my_usb_probe(struct usb_interface *iface, const struct usb_device_id *id) { struct usb_host_interface *host_iface; struct usb_endpoint_descriptor *endpoint; int i, ret; dev_info(iface-dev, probe start for interface %d\n, iface-altsetting-desc.bInterfaceNumber); // 1. 获取当前接口设置 host_iface iface-cur_altsetting; if (!host_iface) { dev_err(iface-dev, no altsetting for interface\n); return -ENODEV; } // 2. 检查端点数量至少1个IN1个OUT if (host_iface-desc.bNumEndpoints 2) { dev_err(iface-dev, need at least 2 endpoints, got %d\n, host_iface-desc.bNumEndpoints); return -ENODEV; } // 3. 遍历端点找到BULK IN/OUT各一个 for (i 0; i host_iface-desc.bNumEndpoints; i) { endpoint host_iface-endpoint[i].desc; if (usb_endpoint_is_bulk_in(endpoint)) { dev_info(iface-dev, found BULK IN endpoint 0x%02x\n, endpoint-bEndpointAddress); } else if (usb_endpoint_is_bulk_out(endpoint)) { dev_info(iface-dev, found BULK OUT endpoint 0x%02x\n, endpoint-bEndpointAddress); } } // 4. 分配urb此处仅示意实际需关联buffer // struct urb *urb_in usb_alloc_urb(0, GFP_KERNEL); // struct urb *urb_out usb_alloc_urb(0, GFP_KERNEL); return 0; // 成功返回0驱动绑定完成 }关键技巧usb_endpoint_is_bulk_in()等宏比手动位运算更安全。endpoint-bEndpointAddress的bit7是方向位1IN0OUTbit3~0是端点号。打印该值能快速定位端点地址避免后续usb_submit_urb()因地址错误返回-EPIPE。3.4 第四步URBUSB Request Block——数据传输的“快递单”URB是USB数据传输的唯一载体不是缓冲区而是描述“如何送、送到哪、谁签收”的指令包。其核心字段struct urb { struct kref kref; // 引用计数防止urb被提前释放 struct list_head urb_list; // 链入pending队列 struct usb_device *dev; // 关联设备 unsigned int pipe; // 管道标识由usb_sndbulkpipe()等生成 void *transfer_buffer; // 数据缓冲区kmalloc分配 dma_addr_t transfer_dma; // DMA地址若启用DMA int transfer_buffer_length; // 缓冲区长度 int actual_length; // 实际传输字节数回调中更新 int status; // 传输状态0成功负值错误 usb_complete_t complete; // 完成回调函数 void *context; // 传递给complete的私有数据 };创建URB的黄金法则transfer_buffer必须用kmalloc(..., GFP_KERNEL)分配不能用栈变量栈空间小且生命周期短pipe必须用usb_sndbulkpipe(dev, ep_addr)或usb_rcvbulkpipe(dev, ep_addr)生成不能手算complete回调函数必须是static且无返回值签名严格为void (*complete)(struct urb *)提交前必须调用usb_fill_bulk_urb()初始化它会自动设置pipe、transfer_buffer等字段。实操示例BULK IN接收// 在probe中分配 struct urb *urb_in; char *buf_in; buf_in kmalloc(64, GFP_KERNEL); if (!buf_in) return -ENOMEM; urb_in usb_alloc_urb(0, GFP_KERNEL); if (!urb_in) { kfree(buf_in); return -ENOMEM; } // 填充URB假设ep_in_addr 0x81 usb_fill_bulk_urb(urb_in, interface_to_usbdev(iface), usb_rcvbulkpipe(interface_to_usbdev(iface), 0x81), buf_in, 64, my_usb_read_callback, dev_priv); // 提交URB ret usb_submit_urb(urb_in, GFP_KERNEL); if (ret) { dev_err(iface-dev, submit IN urb failed: %d\n, ret); usb_free_urb(urb_in); kfree(buf_in); return ret; }注意GFP_KERNEL只能在进程上下文使用。若在中断上下文如timer提交URB必须用GFP_ATOMIC否则内核panic。这是硬性约束没有例外。3.5 第五步完成回调函数——数据处理的“签收台”URB提交后内核在DMA完成时调用complete函数。此函数在原子上下文中执行严禁调用可能睡眠的函数如kmalloc,msleep,printk大量输出。标准写法是快速拷贝数据、唤醒等待队列、提交下一个URB。static void my_usb_read_callback(struct urb *urb) { struct my_usb_dev *dev urb-context; int status urb-status; if (status) { if (status -ENOENT || status -ECONNRESET || status -ESHUTDOWN) return; // 设备断开正常退出 dev_err(dev-interface-dev, read urb status: %d\n, status); return; } // 安全拷贝数据urb-actual_length可能小于buffer长度 if (urb-actual_length 0) { memcpy(dev-rx_buffer, urb-transfer_buffer, urb-actual_length); dev-rx_len urb-actual_length; // 唤醒等待读取的进程如read()系统调用 wake_up(dev-wait_queue); } // 提交下一个URB保持接收管道畅通 usb_submit_urb(urb, GFP_ATOMIC); }这里的关键是wake_up(dev-wait_queue)。dev结构体需在probe中动态分配并包含wait_queue_head_t wait_queue字段。用户空间read()系统调用会调用驱动的.read函数该函数若无数据则wait_event_interruptible(dev-wait_queue, dev-rx_len 0)阻塞直到回调中wake_up唤醒。3.6 第六步用户空间交互——实现file_operations驱动要被应用层使用必须实现struct file_operations。最小集包括.open,.read,.write,.releasestatic const struct file_operations my_usb_fops { .owner THIS_MODULE, .open my_usb_open, .read my_usb_read, .write my_usb_write, .release my_usb_release, .poll my_usb_poll, }; static int my_usb_open(struct inode *inode, struct file *file) { struct my_usb_dev *dev; // 从minor号反查dev实例需维护全局dev数组 dev my_usb_devices[iminor(inode)]; file-private_data dev; return 0; } static ssize_t my_usb_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct my_usb_dev *dev file-private_data; int ret; if (wait_event_interruptible(dev-wait_queue, dev-rx_len 0)) return -ERESTARTSYS; // 将内核rx_buffer拷贝到用户空间buf ret copy_to_user(buf, dev-rx_buffer, dev-rx_len); if (ret 0) { *ppos dev-rx_len; dev-rx_len 0; // 清空 } return dev-rx_len; }copy_to_user()是安全边界必须检查返回值。若返回非0说明用户空间地址非法应返回-EFAULT。3.7 第七步资源清理与错误处理——disconnect与removedisconnect是probe的镜像必须释放所有probe中申请的资源取消pending URB、释放buffer、释放urb、注销字符设备。顺序必须严格逆序static void my_usb_disconnect(struct usb_interface *iface) { struct my_usb_dev *dev usb_get_intfdata(iface); dev_info(iface-dev, disconnect interface %d\n, iface-altsetting-desc.bInterfaceNumber); // 1. 取消所有pending URB if (dev-urb_in) usb_kill_urb(dev-urb_in); if (dev-urb_out) usb_kill_urb(dev-urb_out); // 2. 释放URB if (dev-urb_in) { usb_free_urb(dev-urb_in); dev-urb_in NULL; } if (dev-urb_out) { usb_free_urb(dev-urb_out); dev-urb_out NULL; } // 3. 释放缓冲区 if (dev-rx_buffer) { kfree(dev-rx_buffer); dev-rx_buffer NULL; } if (dev-tx_buffer) { kfree(dev-tx_buffer); dev-tx_buffer NULL; } // 4. 注销字符设备 cdev_del(dev-cdev); device_destroy(my_usb_class, MKDEV(my_usb_major, dev-minor)); usb_set_intfdata(iface, NULL); }usb_kill_urb()是安全取消它会等待URB完成后再释放避免内存访问冲突。usb_free_urb()只能在URB已取消或完成状态下调用否则触发Oops。4. 调试实战从dmesg到usbmon的五级排障法4.1 第一级dmesg日志——看驱动是否加载/匹配这是最快定位入口问题的方法。典型线索usbcore: registered new interface driver my_usb_drv→ 驱动注册成功my_usb_drv: probe called→ 匹配成功进入probemy_usb_drv: submit IN urb failed: -1→ URB提交失败查pipe或bufferusb 1-1: reset high-speed USB device number 2 using xhci_hcd→ 设备被复位可能是供电不足或固件异常。实操心得dmesg默认环形缓冲区仅64KB高频调试易被覆盖。建议启动时加log_buf_len1M内核参数或用dmesg -c清空后立即插拔设备。4.2 第二级lsusb与usb-devices——验证设备拓扑lsusb -t显示USB树状结构确认设备挂在哪个hub、工作在什么速度12M/480M/5000M/: Bus 01.Port 1: Dev 1, Classroot_hub, Driverxhci_hcd/12p, 480M |__ Port 1: Dev 2, If 0, ClassVendor Specific, Drivermy_usb_drv, 12M若Driver后为空说明匹配失败若为usbserial说明被通用驱动抢占需sudo modprobe -r usbserial再试。usb-devices输出更详细重点看I:行Interface和E:行EndpointI: If# 0 Alt 0 #EPs 3 Clsff(vend.) Sub01 Prot02 Drivermy_usb_drv E: Ad81(I) Atr02(Bulk) MxPS 64 Ivl0ms E: Ad02(O) Atr02(Bulk) MxPS 64 Ivl0ms E: Ad83(I) Atr03(Int.) MxPS 8 Ivl1ms确认Driver字段和端点地址与代码一致。4.3 第三级usbmon抓包——透视USB协议层通信usbmon是内核提供的USB协议分析器无需额外硬件。启用方法sudo modprobe usbmon sudo cat /sys/kernel/debug/usb/usbmon/1u /tmp/usbmon.log # 插拔设备或触发数据传输 sudo kill %1日志格式示例ffff88810a2b9000 1010411000 C Bi:1:002:1 0 64 55 41 43 4b 00 00 00 00 ... ffff88810a2b9000 1010411000 C Bo:1:002:2 0 64 55 41 43 4b 00 00 00 00 ...C表示Complete完成S表示Submit提交Bi表示Bulk INBo表示Bulk OUT1:002:1表示Bus 1, Device 2, Endpoint 1 55 41 ...是IN数据 55 41 ...是OUT数据。若看到大量C Bi:... -EPIPE说明IN端点被stall需在probe中调用usb_clear_halt()清除端点halt状态。4.4 第四级printk打点——定位代码执行路径在关键路径加dev_dbg()比printk更可控dev_dbg(iface-dev, entering probe, interface%d\n, iface-altsetting-desc.bInterfaceNumber);需在模块加载前开启debugecho 1 /sys/module/my_usb_drv/parameters/debug。dev_dbg()默认不输出避免日志爆炸。4.5 第五级kgdb内核调试——终极问题定位当dmesg只显示BUG: unable to handle kernel NULL pointer dereference时需kgdb。步骤编译内核时开启CONFIG_KGDBy,CONFIG_KGDB_SERIAL_CONSOLEy启动时加kgdbocttyS0,115200参数用另一台机器通过串口连接用gdb vmlinux加载符号target remote /dev/ttyUSB0连接b my_usb_read_callback设断点。注意kgdb会暂停整个系统仅用于深度问题日常调试优先用前四级。5. 常见问题速查表与避坑指南问题现象可能原因排查命令解决方案insmod后dmesg无任何输出模块未编译进当前内核版本uname -rvsls /lib/modules/make -C /lib/modules/$(uname -r)/build M$(PWD) modulesprobe不被调用.id_table未以{ }结尾或MODULE_DEVICE_TABLE缺失modinfo my_usb_drv.ko | grep alias检查id_table末尾和MODULE_DEVICE_TABLE声明usb_submit_urb()返回-ENODEV设备已拔出或usb_get_dev()失败dmesg | grep device gone在probe开头加usb_get_dev(interface_to_usbdev(iface))read()一直阻塞不返回wake_up()未调用或wait_event条件错误cat /proc/wait_event | grep my_usb检查回调中wake_up()和read()中wait_event的condition变量数据接收乱码或丢包transfer_buffer未用kmalloc或urb-actual_length未校验hexdump -C /tmp/data.bin严格按kmalloc分配buffercopy_to_user前校验actual_length独家避坑技巧端点地址陷阱lsusb -v显示的bEndpointAddress是十进制而代码中0x81是十六进制。务必统一进制建议全部用十六进制书写。内存屏障问题多核CPU下urb-actual_length更新可能延迟。在回调中读取前加smp_rmb()确保内存顺序。热插拔竞争disconnect可能在probe未完成时触发。所有dev字段操作前加if (!dev) return;防护。电源管理干扰某些USB hub在空闲时自动挂起设备。在probe中调用usb_autopm_get_interface_no_resume(iface)禁用自动挂起。6. 进阶方向从单设备到多实例、从Bulk到Isochronous6.1 支持多设备实例——用usb_register_dev()注册字符设备一个驱动可管理多个同型号设备。关键是在probe中为每个iface分配独立minor号// 全局minor池 static DEFINE_IDR(my_usb_minors); static struct class *my_usb_class; // probe中 int minor; minor idr_alloc(my_usb_minors, dev, 0, 256, GFP_KERNEL); if (minor 0) return minor; dev-minor minor; cdev_init(dev-cdev, my_usb_fops); cdev_add(dev-cdev, MKDEV(my_usb_major, minor), 1); device_create(my_usb_class, iface-dev, MKDEV(my_usb_major, minor), NULL, myusb%d, minor);这样/dev/myusb0,/dev/myusb1分别对应不同设备互不干扰。6.2 支持等时传输Isochronous——音视频设备刚需等时传输不保证可靠性无重传但保证带宽和时序。与Bulk区别pipe用usb_rcvisocpipe()生成urb-transfer_flags | URB_ISO_ASAP立即开始urb-number_of_packets指定微帧数回调中需遍历urb-iso_frame_desc[i]检查每个微帧状态。典型场景USB麦克风采集音频每帧1ms每帧192字节需连续提交100帧URB保持流式传输。6.3 集成到用户空间——libusb与usbfs的协同内核驱动专注底层传输上层逻辑可用libusb在用户空间实现。例如驱动只提供/dev/myusb0裸设备libusb通过usbfs/dev/bus/usb/001/002发送控制请求配置设备参数再由内核驱动处理数据通道。这种分层让固件升级、参数配置更灵活避免频繁修改内核模块。我在实际项目中做过一个USB温湿度传感器驱动客户要求每秒上报10次数据且必须在设备拔出时50ms内通知应用层。最终方案是用urb轮询IN端点disconnect中用sysfs创建/sys/class/myusb/xxx/present节点应用层inotify监听该文件变化。这个设计绕过了uevent的延迟实测拔出响应时间稳定在32ms。所以驱动不是写完就结束而是要结合应用场景把内核机制用活。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑