《Linux Device Drivers》第五章 并发和竞态——note

本文深入探讨了并发编程中的竞态问题及其解决方法,包括锁定、互斥体、信号量、读写锁等技术。同时介绍了自旋锁、原子变量、位操作等免锁算法,以及RCU优化机制,旨在提升程序性能与资源利用效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 并发及其管理
    • 竞态通常作为对资源的共享访问结果而产生
    • 当两个执行线程需要访问相同的数据结构(或硬件资源)时,并发的可能性就永远存在
    • 只要可能就应该避免资源的共享,但共享通常是必须的,硬件本质上就是共享的
    • 访问管理的常见技术称为“锁定”或者“互斥”
  • 信号量和互斥体
    • 建立临界区:在任意给定的时刻,代码只能被一个线程执行
    • 可以使用一种锁定机制,当进程在等待对临界区的访问时,此机制可让进程进入休眠状态
    • 一个信号量本质上是一个整数值,它和一对函数联合使用,这一对函数通常称为P和V
    • 当信号量用于互斥时,信号量的值应初始化为1,这种信号量有时也称为“互斥体(mutex)”
    • Linux信号量的实现
      • <asm/semaphore.h>
      • struct semaphore;
      • void sema_init(struct semaphore *sem, int val);
      • DECLARE_MUTEX(name);
        • 称为name的信号量变量被初始化为1
      • DECLARE_MUTEX_LOCKED(name);
        • 称为name的信号量变量被初始化为0
      • void init_MUTEX(struct semaphore *sem);
      • void init_MUTEX_LOCKED(struct semaphore *sem);
      • void down(struct semaphore *sem);
        • 一直等待
      • int down_interruptible(struct semaphore *sem);
        • 操作是可中断的
        • 如果操作被中断,该函数会返回非零值
        • 通常使用的是可中断的down版本
      • int down_trylock(struct semaphore *sem);
        • 永远不会休眠
        • 如果信号量在调用时不可获得,会立即返回一个非零值
      • void up(struct semaphore *sem);
    • 读取者/写入者信号量
      • 一些任务只需要读取受保护的数据结构,而其他的则必须做出修改
      • <linux/rwsem.h>
      • struct rw_semaphore;
      • void init_rwsem(struct rw_semaphore *sem);
      • void down_read(struct rw_semaphore *sem);
        • 只读访问,可和其他读取者并发地访问
      • int down_read_trylock(struct rw_semaphore *sem);
        • 在授予访问时返回非零,其他情况下返回零
      • void up_read(struct rw_semaphore *sem);
      • void down_write(struct rw_semaphore *sem);
      • int down_write_trylock(struct rw_semaphore *sem);
      • void up_write(struct rw_semaphore *sem);
      • void downgrade_write(struct rw_semaphore *sem);
      • 最好在很少需要写访问且写入者只会短期拥有信号量的时候使用rwsem

  • completion
    • 内核编程中常见的一种模式是,在当前线程之外初始化某个活动,然后等待该活动的结束
    • <linux/completion.h>
    • DECLARE_COMPLETION(my_completion);
    • init_completion(struct completion *c);
    • void wait_for_completion(struct completion *c);
      • 非中断的等待
    • void complete(struct completion *c);
    • void complete_all(struct completion *c);
    • 一个completion通常是一个单次(one-shot)设备
    • 如果没有使用complete_all,可以重复使用一个complete结构
    • 如果使用了complete_all,则必须在重复使用该结构之前重新初始化它
    • INIT_COMPLETE(struct completion c);
    • void complete_and_exit(struct completion *c, long retval);
  • 自旋锁
    • 自旋锁可在不能休眠的代码中使用,比如中断处理例程
    • 可提供比信号量更高的性能
    • 一个自旋锁是一个互斥设备,它只能有两个值:锁定和解锁
    • 通常实现为某个整数值中的单个位
    • 如果锁可用,则“锁定”位被设置,而代码继续进入临界区
    • 如果锁被其他人获得,则代码进入忙循环并重复检查这个锁,直到该锁可用为止,这个循环就是自旋锁的“自旋”部分
    • “测试并设置”的操作必须以原子方式完成
    • 在超线程处理器上,还必须仔细处理以避免死锁,超线程处理器可实现多个虚拟的CPU,它们共享单个处理器核心及缓存
    • 自旋锁API介绍
      • <linux/spinlock.h>
      • spinlock_t
      • spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
      • void spin_lock_init(spinlock_t *lock);
      • void spin_lock(spinlock_t *lock);
        • 不可中断
        • 在获得锁之前一直处于自旋状态
      • void spin_unlock(spinlock_t *lock);
    • 自旋锁和原子上下文
      • 任何拥有自旋锁的代码都必须是原子的,不能休眠,不能因为任何原因放弃处理器,除了服务中断之外
    • 自旋锁函数
      • void spin_lock(spinlock_t *lock);
      • void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
        • 在获得自旋锁之前禁止中断,而先前的中断状态保存在flags中
      • void spin_lock_irq(spinlock_t *lock);
      • void spin_lock_bh(spinlock_t *lock);
        • 在获得锁之前禁止软件中断
      • void spin_unlock(spinlock_t *lock);
      • void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
        • flags参数必须是传递给spin_lock_irqsave的同一个变量
      • void spin_unlock_irq(spinlock_t *lock);
      • void spin_unlock_bh(spinlock_t *lock);
      • int spin_trylock(spinlock_t *lock);
        • 成功时返回非零值,否则返回零
      • int spin_trylock_bh(spinlock_t *lock);
        • 成功时返回非零值,否则返回零
    • 读取值/写入者自旋锁
      • <linux/spinlock.h>
      • rwlock_t
      • rwlock_t my_rwlock = RW_LOCK_UNLOCKED;
      • void rwlock_init(rwlock_t * lock);
      • void read_lock(rwlock_t *lock);
      • void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
      • void read_lock_irq(rwlock_t *lock);
      • void read_lock_bh(rwlock_t *lock);
      • void read_unlock(rwlock_t *lock);
      • void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
      • void read_unlock_irq(rwlock_t *lock);
      • void read_unlock_bh(rwlock_t *lock);
      • void write_lock(rwlock_t *lock);
      • void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
      • void write_lock_irq(rwlock_t *lock);
      • void write_lock_bh(rwlock_t *lock);
      • int write_trylock(rwlock_t *lock);
      • void write_unlock(rwlock_t *lock);
      • void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
      • void write_unlock_irq(rwlock_t *lock);
      • void write_unlock_bh(rwlock_t *lock);
      • 读取者/写入者锁可能造成读取者饥饿
  • 锁陷阱
    • 不明确的规则
      • 不论是信号量还是自旋锁,都不允许锁拥有者第二次获得这个相同的锁,如果试图这样做,系统将挂起(例如:如果某个获得锁的函数,调用其他试图获取相同锁的函数,我们的代码就会死锁)
    • 锁的顺序规则
      • 在必须获取多个锁时,应该始终以相同的顺序获得
      • 如果必须获得一个局部锁以及一个属于内核更中心位置的锁,则应该首先获取自己的局部锁
      • 如果拥有信号量和自旋锁的组合,则必须首先获得信号量
    • 细粒度锁和粗粒度锁的对比
      • 细粒度锁具有良好的伸缩性
      • 细粒度锁将带来某种程序的复杂性
      • 应该在最初使用粗粒度的锁
      • 使用lockmeter工具可度量内核花费在锁上的时间
        • http://oss.sgi.com/projects/lockmeter/
  • 除了锁之外的方法
    • 免锁算法
      • 经常用于免锁的生产者/消费者任务的数据结构之一是循环缓冲区
    • 原子变量
      • <asm/atomic.h>
      • atomic_t
      • 一个atomic_t变量保存一个int值,但不能记录大于24位的整数
      • void atomic_set(atomic_t *v, int i);
      • atomic_t v = ATOMIC_INIT(0);
      • int atomic_read(atomic_t *v);
      • void atomic_add(int i, atomic_t *v);
      • void atomic_sub(int i, atomic_t *v);
      • void atomic_inc(atomic_t *v);
      • void atomic_dec(atomic_t *v);
      • int atomic_inc_and_test(atomic_t *v);
      • int atomic_dec_and_test(atomic_t *v);
      • int atomic_sub_and_test(int i, atomic_t *v);
      • int atomic_add_negative(int i, atomic_t *v);
      • int atomic_add_return(int i, atomic_t *v);
      • int atomic_sub_return(int i, atomic_t *v);
      • int atomic_inc_return(atomic_t *v);
      • int atomic_dec_return(atomic_t *v);
      • 需要多个atomic_t变量的操作,仍然需要某种类型的锁
    • 位操作
      • <asm/bitops.h>
      • nr参数通常被定义为int,但在少数架构上被定义为unsigned long
      • void set_bit(nr, void *addr);
      • void clear_bit(nr, void *addr);
      • void change_bit(nr, void *addr);
      • test_bit(nr, void *addr);
      • int test_and_set_bit(nr, void *addr);
      • int test_add_clear_bit(nr, void *addr);
      • int test_and_change_bit(nr, void *addr);
    • seqlock
      • 允许读取者对资源的自由访问,但需要读取者检查是否和写入者发生冲突
      • <linux/seqlock.h>
      • seqlock_t
      • seqlock_t lock1 = SEQLOCK_UNLOCKED;
      • void seqlock_init(seqlock_t *lock);
      • unsigned int read_seqbegin(seqlock_t *lock);
      • int read_seqretry(seqlock_t *lock, unsigned int seq);
      • unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned long flags);
      • int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsigned long flags);
      • void write_seqlock(seqlock_t *lock);
      • void write_sequnlock(seqlock_t *lock);
      • void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);
      • void write_seqlock_irq(seqlock_t *lock);
      • void write_seqlock_bh(seqlock_t *lock);
      • void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);
      • void write_sequnlock_irq(seqlock_t *lock);
      • void write_sequnlock_bh(seqlock_t *lock);
    • 读取-复制-更新
      • read-copy-update(RCU)也是一种高级的互斥机制
      • 很少在驱动程序中使用
      • http://www.rdrop.com/users/paulmck/rclock/intro/rclock_intro.html
      • 针对经常发生读取而很少写入的情形做了优化
      • 被保护的资源应该通过指针访问
      • 在需要修改该数据结构时,写入线程首先复制,然后修改副本,之后用新的版本替代相关指针。当确信老的版本没有其他引用时,就可释放老的版本
      • <linux/rcupdate.h>
      • rcu_read_lock
      • rcu_read_unlock
      • void call_rcu(struct rcu_head *head, void (*func)(void *arg), void *arg);
<think>嗯,用户现在问的是关于f_printer.c的详解。首先,我需要确定用户的具体需求。他们可能是在开发或调试USB打印机功能,或者是在学习Linux内核中的USB gadget驱动。用户之前的问题是关于USB打印机驱动在插拔时导致死机的问题,所以可能他们现在想更深入理解相关代码,以便解决实际问题。 接下来,我应该回忆一下f_printer.c的作用。这个文件属于Linux内核的USB gadget驱动,用于实现USB打印机设备功能。它属于FunctionFS的一部分,允许用户空间通过文件系统接口与USB功能交互。可能用户需要了解其架构、数据结构、关键函数工作流程,以便进行调试或定制开发。 用户可能的基础是什么?如果之前的问题是关于驱动问题,他们可能有Linux内核或驱动开发的基本知识,但可能对具体模块不熟悉。需要以简明的方式解释,避免过于技术化,同时涵盖关键点。 需要检查是否有常见的疑问点,例如如何配置、如何与用户空间交互、可能的问题点在哪里。比如,用户可能遇到配置错误,或者数据传输的问题,导致之前提到的死机情况。需要解释代码中的关键部分,比如初始化流程、数据传输机制、控制请求处理等。 另外,用户可能需要知道如何调试或修改这个驱动,所以应该包括相关的调试方法配置示例。例如,使用configfs配置g_printer,查看内核日志,或者如何添加日志信息来跟踪问题。 还要注意代码结构,比如模块的入口出口函数,回调函数的作用,以及如何与硬件交互。可能需要提到结构体如printer_func_type,printer_dev,以及URB的处理流程。 需要确保解释清晰,分点说明,让用户能够逐步理解。同时,结合用户之前的问题,可能需要在最后给出实际应用中的注意事项,比如配置错误可能导致的问题,或者如何确保兼容性。 最后,总结部分需要强调该模块的作用适用场景,帮助用户将理论应用到实际问题中,比如调试他们遇到的死机问题。</think>以下是关于Linux内核中`f_printer.c`文件的详细解析: --- ### 一、文件定位与作用 - **路径** `drivers/usb/gadget/function/f_printer.c` - **功能** 实现**USB打印机设备功能**的内核驱动模块,属于Linux USB Gadget Framework的一部分。 允许将Linux设备(如树莓派、嵌入式板卡)模拟成USB打印机,通过USB接口接收打印数据。 --- ### 二、核心架构 #### 1. 模块组成 | 组件 | 描述 | |---------------------|----------------------------------------------------------------------| | `printer_func_type` | 定义USB打印机接口的FunctionFS描述符(设备类型、端点配置等) | | `printer_dev` | 设备状管理结构体(包含USB请求块URB、数据缓冲区、信号量等) | | `printer_req_match` | 匹配USB控制请求(如GET_DEVICE_ID)的过滤函数 | #### 2. 关键数据结构 ```c struct printer_dev { struct usb_function function; // USB功能基础结构 spinlock_t lock; // 并发控制锁 struct list_head tx_reqs; // 待发送请求队列 struct usb_ep *in_ep, *out_ep; // 输入/输出端点 wait_queue_head_t rx_wait; // 接收数据等待队列 atomic_t tx_bytes; // 已发送字节数统计 }; ``` --- ### 三、核心工作流程 #### 1. 初始化阶段 ```c // 模块加载时注册USB打印机功能 module_usb_function_driver(printerusb_func); // 实际初始化函数 static int __init printer_func_init(void) { return usb_function_register(&printerusb_func); } ``` #### 2. 数据传输流程 ```mermaid sequenceDiagram participant Host as USB主机(PC) participant Gadget as Linux设备 Note over Gadget: 用户空间通过write()发送数据 Gadget->>+Host: IN端点传输(USB_DIR_IN) Host-->>-Gadget: ACK确认 Note over Gadget: 数据通过out_ep接收 Gadget->>用户空间: 触发POLLIN事件 ``` #### 3. USB描述符配置 ```c // 标准USB打印机类接口描述符 static struct usb_interface_descriptor intf_desc = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bNumEndpoints = 2, // 使用BULK-INBULK-OUT端点 .bInterfaceClass = USB_CLASS_PRINTER, .bInterfaceSubClass = 1, // Printer Subclass .bInterfaceProtocol = 2, // Bidirectional protocol }; ``` --- ### 四、关键代码片段解析 #### 1. 控制请求处理 ```c static int printer_setup(struct usb_function *f, const struct usb_ctrlrequest *ctrl) { switch (ctrl->bRequest) { case GET_DEVICE_ID: // 处理主机查询设备ID value = printer_get_device_id(dev, buf, len); break; case GET_PORT_STATUS: // 返回打印机状(缺纸/错误) ((u8 *) req->buf)[0] = dev->printer_status; break; } } ``` #### 2. 数据接收逻辑 ```c static void printer_out_complete(struct usb_ep *ep, struct usb_request *req) { if (req->status == 0) { list_add_tail(&req->list, &dev->rx_buffers); // 将接收缓冲区加入队列 wake_up_interruptible(&dev->rx_wait); // 唤醒等待进程 } } ``` --- ### 五、调试与使用 #### 1. 配置示例(通过configfs) ```bash # 在USB Gadget配置中激活打印机功能 mkdir /sys/kernel/config/usb_gadget/g1 cd /sys/kernel/config/usb_gadget/g1 echo 0x1d6b > idVendor # Linux Foundation VID echo 0x0104 > idProduct # Printer Gadget PID mkdir functions/printer.usb0 ln -s functions/printer.usb0 configs/c.1/ ``` #### 2. 用户空间交互 ```c // 用户程序通过/dev/g_printer设备节点操作 int fd = open("/dev/g_printer", O_RDWR); write(fd, print_data, data_len); // 发送打印数据 poll(&fds, 1, -1); // 等待接收数据(状反馈) ``` --- ### 六、典型问题与修复 #### 1. 数据丢失问题 - **现象**:高速打印时缓冲区溢出 - **修复**:调整`printer_dev`中的`tx_req_active`计数,增加预分配URB数量: ```c // 修改默认的TX_REQ_NUM宏定义 #define TX_REQ_NUM 8 → 16 ``` #### 2. 协议兼容性问题 - **症状**:Windows提示"无法识别的USB设备" - **调试**:检查`printer_func_type`中的`HS_BULK_COMP`描述符,确认端点最大包大小符合USB2.0规范(512字节) --- ### 七、代码演进历史 - **Linux 2.6.32**:初始版本加入内核 - **Linux 4.5**:引入FunctionFS重写,支持用户空间驱动 - **Linux 5.10**:修复并发访问导致的use-after-free漏洞(CVE-2021-30002) --- ### 八、扩展阅读建议 1. 内核文档:`Documentation/usb/gadget_printer.txt` 2. USB Printer Class规范:`Universal Serial Bus Device Class Specification for Printing Devices` 3. 调试工具:`usbmon`捕获USB协议流量 如果需要对具体代码段进行更深入分析,请提供需要解读的函数或行号范围。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值