Linux驱动 2026年8月5日 118 分钟

libusb_handle_events详解

libusb_handle_events_timeout_completed() 详解 一、函数原型 二、函数作用 核心…

libusb_handle_events_timeout_completed() 详解

一、函数原型

int API_EXPORTED libusb_handle_events_timeout_completed(
    libusb_context *ctx,
    struct timeval *tv,
    int *completed
);

二、函数作用

核心功能:处理USB异步传输的事件循环

2.1 做什么?

1. 监听USB传输完成事件
   - 检查文件描述符(Linux: /dev/bus/usb/...)
   - 调用操作系统的poll/select机制
   - 等待传输完成通知

2. 调用回调函数
   - 传输完成 → 调用libusb_transfer->callback
   - 传输超时 → 调用回调并标记LIBUSB_TRANSFER_TIMED_OUT
   - 传输错误 → 调用回调并标记错误码

3. 处理内部事件
   - 热插拔通知
   - 设备断开连接
   - 传输取消请求

4. 返回控制权
   - 超时时间到达
   - 或 *completed 被设置为非0
   - 或 处理完所有待处理事件

2.2 不做什么?

✗ 不发起传输(需要先调用libusb_submit_transfer)
✗ 不阻塞整个程序(可以设置超时时间)
✗ 不直接返回传输数据(数据在回调函数中处理)

三、参数详解

3.1 libusb_context *ctx

// 上下文句柄
libusb_context *ctx = NULL;
libusb_init(&ctx);

// 传入NULL使用默认上下文
libusb_handle_events_timeout_completed(NULL, &tv, NULL);

// 传入特定上下文
libusb_handle_events_timeout_completed(ctx, &tv, NULL);

作用

  • 指定哪个libusb上下文的事件需要处理
  • 一个程序可以有多个上下文,各自独立

3.2 struct timeval *tv

struct timeval {
    long tv_sec;   // 秒
    long tv_usec;  // 微秒
};

// 示例1:等待1秒
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;

// 示例2:等待500毫秒
tv.tv_sec = 0;
tv.tv_usec = 500000;

// 示例3:立即返回(非阻塞)
tv.tv_sec = 0;
tv.tv_usec = 0;

// 示例4:无限等待(传NULL)
libusb_handle_events_timeout_completed(ctx, NULL, NULL);

作用

  • 控制最大阻塞时间
  • NULL = 无限等待(直到有事件或*completed被设置)
  • {0, 0} = 非阻塞,立即返回

推荐值

实时应用(视频流): 10-50ms
一般应用: 100-500ms
低功耗应用: 1-5秒

3.3 int *completed – 深度解析

3.3.1 基本用法

// 用法1:不使用(传NULL)- 最常见
libusb_handle_events_timeout_completed(ctx, &tv, NULL);

// 用法2:多线程提前退出
volatile int completed = 0;

// 事件处理线程
void event_thread(void *arg) {
    while (!completed) {
        struct timeval tv = {1, 0};
        libusb_handle_events_timeout_completed(ctx, &tv, (int*)&completed);
    }
}

// 控制线程
void stop_thread() {
    completed = 1;  // 通知事件线程退出
}

3.3.2 设计目的

问题场景

假设没有completed参数:

事件线程正在执行:
  libusb_handle_events_timeout_completed(ctx, &tv, NULL);
  └─ 正在poll()中等待,超时时间5秒

主线程想要立即停止事件线程:
  event_thread_stop = 1;
  
结果:
  事件线程需要等待最多5秒才能检查到event_thread_stop标志!
  
这5秒的延迟是不可接受的!

completed参数解决方案

有了completed参数:

事件线程:
  libusb_handle_events_timeout_completed(ctx, &tv, &completed);
  └─ libusb内部会定期检查*completed的值

主线程:
  completed = 1;
  
结果:
  libusb_handle_events_timeout_completed()内部检测到*completed=1
  立即从poll()返回(通过内部管道唤醒)
  事件线程几乎立即响应(< 1ms)!

3.3.3 工作原理

libusb内部实现机制

// libusb内部简化伪代码
int libusb_handle_events_timeout_completed(ctx, tv, completed) {
    while (1) {
        // 1. 启动前检查
        // completed && *completed 的含义:
        // - completed: 先检查指针是否为NULL
        // - *completed: 如果指针不是NULL,再检查指针指向的值
        if (completed && *completed) {
            return 0;  // 立即返回
        }
        
        // 2. 准备文件描述符集合
        fd_set read_fds, write_fds;
        int nfds = prepare_fds(&read_fds, &write_fds);
        
        // 3. 调用系统poll(这是阻塞点)
        int ret = poll(fds, nfds, timeout_ms);
        
        // 4. poll返回后再次检查
        if (completed && *completed) {
            return 0;  // 立即返回
        }
        
        // 5. 处理事件
        if (ret > 0) {
            handle_events();
        }
        
        // 6. 检查超时
        if (timeout_expired) {
            return 0;
        }
    }
}

completed && *completed 详解

这是C语言中的短路求值表达式,分两步检查:

// 完整写法(等价)
if (completed != NULL && *completed != 0) {
    // 退出
}

// 简化写法(常见)
if (completed && *completed) {
    // 退出
}

为什么需要两次检查?

// 情况1:用户传NULL(不使用completed功能)
int *completed = NULL;
libusb_handle_events_timeout_completed(ctx, &tv, completed);

// libusb内部:
if (completed && *completed) {
    //  ↑          ↑
    //  │          └─ 永远不会执行(短路了)
    //  └─ 第一步:completed是NULL,条件为假,直接跳过
}
// 结果:不会崩溃,正常工作


// 情况2:用户传有效指针,但值为0(继续运行)
volatile int flag = 0;
libusb_handle_events_timeout_completed(ctx, &tv, (int*)&flag);

// libusb内部:
if (completed && *completed) {
    //  ↑          ↑
    //  │          └─ 第二步:*completed是0,条件为假
    //  └─ 第一步:completed非NULL,条件为真,继续检查
}
// 结果:不退出,继续等待事件


// 情况3:用户传有效指针,且值为1(请求退出)
volatile int flag = 1;
libusb_handle_events_timeout_completed(ctx, &tv, (int*)&flag);

// libusb内部:
if (completed && *completed) {
    //  ↑          ↑
    //  │          └─ 第二步:*completed是1,条件为真
    //  └─ 第一步:completed非NULL,条件为真,继续检查
    return 0;  // 立即退出!
}

短路求值机制

表达式:A && B

执行顺序:
1. 先计算A
2. 如果A为假(0或NULL):
   - 不计算B(跳过)
   - 整个表达式结果为假
3. 如果A为真(非0非NULL):
   - 继续计算B
   - 整个表达式结果取决于B

这叫做"短路求值"(Short-circuit evaluation)

为什么要短路?

// 如果没有短路求值,会崩溃!

int *completed = NULL;

// ❌ 错误:直接解引用NULL指针
if (*completed != 0) {  // 段错误!Segmentation fault!
    return 0;
}

// ✅ 正确:先检查指针是否为NULL
if (completed != NULL && *completed != 0) {
    return 0;  // 安全
}

// ✅ 简化:利用短路求值
if (completed && *completed) {
    return 0;  // 安全且简洁
}

真值表

completed值*completed值第一步结果是否检查第二步最终结果行为
NULL❌ 否继续运行
&flag0✅ 是继续运行
&flag1✅ 是立即退出
&flag非0✅ 是立即退出

实际例子对比

// 例子1:不使用completed功能
libusb_handle_events_timeout_completed(ctx, &tv, NULL);
//                                                  ↑
//                                                  NULL

// libusb内部检查:
// completed = NULL
// completed && *completed
//    ↓
//   假  (短路,不检查*completed)
//    ↓
// 继续运行,不退出


// 例子2:使用completed,但还没有设置
volatile int stop = 0;
libusb_handle_events_timeout_completed(ctx, &tv, (int*)&stop);
//                                                        ↑
//                                                   指向stop变量

// libusb内部检查:
// completed = &stop (非NULL)
// *completed = 0
// completed && *completed
//    ↓           ↓
//   真          假
//    ↓           ↓
//   继续检查    整体为假
//              ↓
// 继续运行,不退出


// 例子3:使用completed,且已设置
volatile int stop = 1;  // 另一个线程设置为1
libusb_handle_events_timeout_completed(ctx, &tv, (int*)&stop);

// libusb内部检查:
// completed = &stop (非NULL)
// *completed = 1
// completed && *completed
//    ↓           ↓
//   真          真
//    ↓           ↓
//   继续检查    整体为真
//              ↓
// return 0; 立即退出!

为什么不用 if (completed) { if (*completed) { ... } }

// 方案1:嵌套if(啰嗦)
if (completed) {
    if (*completed) {
        return 0;
    }
}

// 方案2:短路求值(简洁)
if (completed && *completed) {
    return 0;
}

// 功能完全一样,但方案2:
// - 代码更短
// - 更易读
// - C语言惯用法

其他语言的类似机制

# Python
if completed and completed.value:
    return

# JavaScript
if (completed && completed.value) {
    return;
}

# Java
if (completed != null && completed.get()) {
    return;
}

# 都使用了短路求值机制

常见陷阱

// ❌ 错误:顺序反了
if (*completed && completed) {
    //  ↑           ↑
    //  如果completed是NULL,这里会崩溃!
}

// ✅ 正确:先检查指针,再解引用
if (completed && *completed) {
    // 安全
}

// ❌ 错误:用按位与
if (completed & *completed) {
    //          ^ 这是按位与,不是逻辑与!
    // 行为未定义,可能崩溃
}

// ✅ 正确:用逻辑与
if (completed && *completed) {
    //         ^^ 两个&是逻辑与
}

总结

completed && *completed 的含义:

1. 第一步:检查 completed 指针是否为 NULL
   - 如果是 NULL → 整个表达式为假,不退出
   - 如果非 NULL → 继续第二步

2. 第二步:检查 *completed 指向的值是否为 0
   - 如果是 0 → 整个表达式为假,不退出
   - 如果非 0 → 整个表达式为真,立即退出

目的:
- 安全地处理 NULL 指针(用户可以传 NULL)
- 安全地检查退出标志(用户可以设置为 1)

原理:
- 利用 C 语言的短路求值机制
- 避免解引用 NULL 指针(防止崩溃)

关键点:libusb在poll()前后都会检查,确保快速响应。


3.3.4 实际意义

意义1:低延迟停止
// 没有completed:最坏情况延迟 = timeout
struct timeval tv = {5, 0};  // 5秒超时
libusb_handle_events_timeout_completed(ctx, &tv, NULL);
// 停止延迟:最多5秒

// 有completed:几乎立即响应
volatile int completed = 0;
struct timeval tv = {5, 0};
libusb_handle_events_timeout_completed(ctx, &tv, (int*)&completed);
completed = 1;
// 停止延迟:< 1ms(内部管道唤醒时间)

意义2:优雅关闭应用程序
// 应用场景:用户点击"关闭"按钮

volatile int app_shutdown = 0;

// GUI线程
void on_close_button_clicked() {
    // 1. 设置全局退出标志
    app_shutdown = 1;
    
    // 2. 等待事件线程退出
    pthread_join(event_thread_id, NULL);
    
    // 3. 清理资源
    cleanup_usb();
    
    // 4. 退出应用
    exit(0);
}

// 事件处理线程
void *event_thread_func(void *arg) {
    while (!app_shutdown) {
        struct timeval tv = {1, 0};
        // completed指向app_shutdown
        libusb_handle_events_timeout_completed(ctx, &tv, (int*)&app_shutdown);
    }
    return NULL;
}

// 效果:
// - 用户点击关闭 → 立即响应(< 1ms)
// - 没有completed → 可能延迟1秒

意义3:多传输同步等待 – 深度解析

什么是”多传输”?

定义:在同一时间向USB设备提交多个独立的异步传输请求

多传输 ≠ 多线程处理一个传输
多传输 = 多个独立的USB传输同时进行

例如:
  传输1: 从端点0x81读取64字节
  传输2: 从端点0x82读取512字节
  传输3: 向端点0x01写入128字节
  
这3个传输可以同时提交给USB设备,并发执行。

为什么需要多传输?

场景1:提高吞吐量(流水线)

单传输模式(低效):
  提交传输1 → 等待完成 → 处理数据 → 提交传输2 → 等待完成 → ...
  
  时间线:
  |-传输1-| (空闲) |-传输2-| (空闲) |-传输3-|
  
  问题:USB总线大部分时间空闲!

多传输模式(高效):
  同时提交传输1、2、3 → 并发等待 → 同时处理
  
  时间线:
  |-传输1-|
  |-传输2-|
  |-传输3-|
  
  优势:USB总线持续繁忙,吞吐量提高3倍!

实际数据: | 模式 | 单次传输延迟 | 总时间(10次传输) | 吞吐量 | |——|————|——————|——–| | 单传输 | 10ms | 100ms | 10 MB/s | | 4个并发传输 | 10ms | 40ms | 25 MB/s | | 8个并发传输 | 10ms | 30ms | 33 MB/s |


场景2:多端点并发通信

// USB设备有多个端点:
// - 端点0x81: 视频数据(批量IN)
// - 端点0x82: 音频数据(等时IN)
// - 端点0x83: 控制状态(中断IN)

// 需要同时接收3个端点的数据
struct libusb_transfer *video_transfer;  // 传输1
struct libusb_transfer *audio_transfer;  // 传输2
struct libusb_transfer *status_transfer; // 传输3

// 同时提交
libusb_submit_transfer(video_transfer);
libusb_submit_transfer(audio_transfer);
libusb_submit_transfer(status_transfer);

// 并发接收数据
libusb_handle_events_timeout_completed(ctx, &tv, NULL);

场景3:批量操作(等待多个完成)

// 场景:发送10个命令到USB设备,需要等待全部完成
struct libusb_transfer *transfers[10];

for (int i = 0; i < 10; i++) {
    transfers[i] = /* 创建并配置传输 */;
    libusb_submit_transfer(transfers[i]);
}

// 问题:如何知道全部完成了?
// 解决:使用completed参数 + 计数器

多传输同步等待的实现

完整示例

#include <libusb-1.0/libusb.h>
#include <pthread.h>
#include <stdio.h>

// 同步上下文:跟踪多个传输的完成状态
struct sync_context {
    int pending_count;      // 待完成传输数(原子递减)
    int all_completed;      // 全部完成标志(用于completed参数)
    pthread_mutex_t lock;   // 保护pending_count的互斥锁
};

// 每个传输完成时的回调函数
void transfer_callback(struct libusb_transfer *transfer) {
    struct sync_context *sync = (struct sync_context *)transfer->user_data;
    
    // 打印完成信息
    printf("传输完成: 端点0x%02x, 状态=%d, 实际长度=%d\n",
           transfer->endpoint,
           transfer->status,
           transfer->actual_length);
    
    // 线程安全地递减计数器
    pthread_mutex_lock(&sync->lock);
    sync->pending_count--;
    
    // 如果所有传输都完成了
    if (sync->pending_count == 0) {
        sync->all_completed = 1;  // 触发completed标志
        printf("所有传输已完成!\n");
    }
    pthread_mutex_unlock(&sync->lock);
}

// 等待多个传输全部完成
int wait_for_all_transfers(libusb_context *ctx,
                           struct libusb_transfer **transfers,
                           int count)
{
    // 初始化同步上下文
    struct sync_context sync = {
        .pending_count = count,
        .all_completed = 0
    };
    pthread_mutex_init(&sync.lock, NULL);
    
    // 配置每个传输的回调和user_data
    for (int i = 0; i < count; i++) {
        transfers[i]->callback = transfer_callback;
        transfers[i]->user_data = &sync;
    }
    
    // 同时提交所有传输
    printf("提交 %d 个传输...\n", count);
    for (int i = 0; i < count; i++) {
        int ret = libusb_submit_transfer(transfers[i]);
        if (ret < 0) {
            printf("提交传输 %d 失败: %s\n", i, libusb_error_name(ret));
            sync.pending_count--;
        }
    }
    
    // 事件循环,等待全部完成
    printf("等待所有传输完成...\n");
    while (!sync.all_completed) {
        struct timeval tv = {0, 100000};  // 100ms超时
        
        // 关键:completed参数指向all_completed
        int ret = libusb_handle_events_timeout_completed(
            ctx, &tv, &sync.all_completed);
        
        if (ret < 0) {
            printf("事件处理错误: %s\n", libusb_error_name(ret));
            break;
        }
    }
    
    pthread_mutex_destroy(&sync.lock);
    return 0;
}

// 使用示例
int main() {
    libusb_context *ctx;
    libusb_device_handle *dev_handle;
    
    libusb_init(&ctx);
    dev_handle = /* 打开USB设备 */;
    
    // 创建5个并发传输
    struct libusb_transfer *transfers[5];
    
    for (int i = 0; i < 5; i++) {
        transfers[i] = libusb_alloc_transfer(0);
        unsigned char *buffer = malloc(512);
        
        // 配置批量IN传输
        libusb_fill_bulk_transfer(
            transfers[i],
            dev_handle,
            0x81,           // 端点IN
            buffer,
            512,            // 缓冲区大小
            NULL,           // 回调稍后设置
            NULL,           // user_data稍后设置
            1000            // 超时1秒
        );
    }
    
    // 等待所有传输完成
    wait_for_all_transfers(ctx, transfers, 5);
    
    // 清理
    for (int i = 0; i < 5; i++) {
        free(transfers[i]->buffer);
        libusb_free_transfer(transfers[i]);
    }
    
    libusb_close(dev_handle);
    libusb_exit(ctx);
    return 0;
}

执行时间线分析

没有completed参数的实现

// ❌ 低效实现
int count = 5;
while (count > 0) {
    struct timeval tv = {0, 100000};  // 100ms
    libusb_handle_events_timeout_completed(ctx, &tv, NULL);
    
    // 在回调中手动递减count
    // 问题:最后一个传输完成后,还要等待100ms超时!
}

时间线:
传输1完成 (10ms)
传输2完成 (15ms)
传输3完成 (18ms)
传输4完成 (20ms)
传输5完成 (22ms) ← 最后一个
(等待78ms...) ← 浪费时间!
函数返回 (100ms)

总耗时:100ms

有completed参数的实现

// ✅ 高效实现
volatile int all_done = 0;
while (!all_done) {
    struct timeval tv = {0, 100000};
    libusb_handle_events_timeout_completed(ctx, &tv, (int*)&all_done);
    
    // 最后一个传输完成时,回调设置all_done=1
    // completed参数检测到变化,立即返回!
}

时间线:
传输1完成 (10ms)
传输2完成 (15ms)
传输3完成 (18ms)
传输4完成 (20ms)
传输5完成 (22ms) ← 最后一个,回调设置all_done=1
函数立即返回 (22ms) ← 不浪费时间!

总耗时:22ms(节省78ms)

性能提升:3.5倍加速!


多传输 vs 多线程处理

澄清误区

多传输 ≠ 多线程处理一个传输

正确理解:
┌────────────────────────────────────┐
│ 多传输(Multiple Transfers)       │
│ - 多个独立的USB传输请求            │
│ - 可以在单线程中处理               │
│ - 也可以在多线程中处理             │
└────────────────────────────────────┘

┌────────────────────────────────────┐
│ 多线程(Multiple Threads)         │
│ - 处理USB事件的线程模型            │
│ - 通常只用1个线程处理事件          │
│ - 其他线程可以提交传输             │
└────────────────────────────────────┘

三种组合模式

模式1:单线程 + 多传输(最常见)

// 一个线程处理多个传输
void single_thread_multi_transfer() {
    // 创建多个传输
    struct libusb_transfer *xfr1 = /* 端点0x81 */;
    struct libusb_transfer *xfr2 = /* 端点0x82 */;
    struct libusb_transfer *xfr3 = /* 端点0x83 */;
    
    // 同时提交
    libusb_submit_transfer(xfr1);
    libusb_submit_transfer(xfr2);
    libusb_submit_transfer(xfr3);
    
    // 单个事件循环处理所有传输
    while (running) {
        libusb_handle_events_timeout_completed(ctx, &tv, NULL);
        // 这一次调用可能处理多个传输的完成事件
    }
}

优点:
  - 简单,无线程同步问题
  - 资源占用少
  - 适合大多数应用

缺点:
  - 回调函数必须快速返回
  - 不能在回调中阻塞

模式2:多线程 + 多传输(复杂应用)

// 线程1:专门处理USB事件
void *event_thread(void *arg) {
    while (event_thread_running) {
        libusb_handle_events_timeout_completed(ctx, &tv, 
            (int*)&event_thread_running);
    }
    return NULL;
}

// 线程2:提交传输和处理数据
void *worker_thread(void *arg) {
    // 提交传输
    for (int i = 0; i < 10; i++) {
        libusb_submit_transfer(transfers[i]);
    }
    
    // 处理其他工作
    while (work_to_do) {
        process_data();
        sleep(1);
    }
    return NULL;
}

// 主线程
int main() {
    pthread_create(&event_tid, NULL, event_thread, NULL);
    pthread_create(&worker_tid, NULL, worker_thread, NULL);
    
    pthread_join(worker_tid, NULL);
    event_thread_running = 0;
    pthread_join(event_tid, NULL);
}

优点:
  - 事件处理和业务逻辑分离
  - 回调可以快速返回(数据放入队列)
  - 业务线程可以阻塞

缺点:
  - 需要线程同步
  - 资源占用较多

模式3:单传输 + 单线程(最简单)

// 一次只处理一个传输
void single_thread_single_transfer() {
    for (int i = 0; i < 10; i++) {
        // 提交一个传输
        libusb_submit_transfer(transfer);
        
        // 等待完成
        volatile int done = 0;
        while (!done) {
            libusb_handle_events_timeout_completed(ctx, &tv, (int*)&done);
        }
        
        // 处理数据
        process_data(transfer->buffer);
    }
}

优点:
  - 最简单
  - 易于理解

缺点:
  - 吞吐量低
  - USB总线利用率低

对比表格

特性单线程+多传输多线程+多传输单线程+单传输
复杂度
吞吐量
CPU占用
适用场景视频流、大数据传输复杂应用、多任务简单命令、调试
线程安全无需考虑需要互斥锁无需考虑
回调限制必须快速返回可放入队列必须快速返回

实际应用场景

场景1:UVC摄像头(多传输流水线)

// 同时提交8个视频帧接收传输
#define NUM_TRANSFERS 8

struct libusb_transfer *video_transfers[NUM_TRANSFERS];

void video_callback(struct libusb_transfer *xfr) {
    if (xfr->status == LIBUSB_TRANSFER_COMPLETED) {
        // 处理视频帧
        process_video_frame(xfr->buffer, xfr->actual_length);
        
        // 立即重新提交(保持流水线)
        libusb_submit_transfer(xfr);
    }
}

// 初始化
for (int i = 0; i < NUM_TRANSFERS; i++) {
    video_transfers[i] = libusb_alloc_transfer(0);
    unsigned char *buffer = malloc(49152);  // 48KB
    
    libusb_fill_bulk_transfer(
        video_transfers[i], dev, 0x81, buffer, 49152,
        video_callback, NULL, 1000
    );
    
    libusb_submit_transfer(video_transfers[i]);
}

// 事件循环
while (streaming) {
    struct timeval tv = {0, 10000};  // 10ms
    libusb_handle_events_timeout_completed(ctx, &tv, (int*)&streaming);
}

// 优势:
// - 8个传输并发,总有数据在传输中
// - 吞吐量最大化
// - 帧丢失最小化

场景2:批量命令发送(等待全部完成)

// 向USB设备发送100个配置命令
struct libusb_transfer *cmd_transfers[100];
struct sync_context sync = {.pending_count = 100, .all_completed = 0};

for (int i = 0; i < 100; i++) {
    cmd_transfers[i] = /* 创建命令传输 */;
    cmd_transfers[i]->user_data = &sync;
    libusb_submit_transfer(cmd_transfers[i]);
}

// 等待全部完成
while (!sync.all_completed) {
    struct timeval tv = {0, 100000};
    libusb_handle_events_timeout_completed(ctx, &tv, &sync.all_completed);
}

printf("所有命令已发送并确认\n");

// 优势:
// - 所有命令并发发送
// - 最后一个完成时立即知道
// - 比串行快100倍

场景3:多端点并发接收

// USB设备有3个独立端点
struct libusb_transfer *ep81_xfr;  // 数据端点
struct libusb_transfer *ep82_xfr;  // 状态端点
struct libusb_transfer *ep83_xfr;  // 调试端点

void ep81_callback(struct libusb_transfer *xfr) {
    handle_data(xfr->buffer);
    libusb_submit_transfer(xfr);  // 持续接收
}

void ep82_callback(struct libusb_transfer *xfr) {
    handle_status(xfr->buffer);
    libusb_submit_transfer(xfr);
}

void ep83_callback(struct libusb_transfer *xfr) {
    handle_debug(xfr->buffer);
    libusb_submit_transfer(xfr);
}

// 同时提交3个端点的传输
libusb_submit_transfer(ep81_xfr);
libusb_submit_transfer(ep82_xfr);
libusb_submit_transfer(ep83_xfr);

// 单个事件循环处理所有端点
while (running) {
    libusb_handle_events_timeout_completed(ctx, &tv, (int*)&running);
    // 一次调用可能同时处理多个端点的完成事件
}

// 优势:
// - 3个端点独立工作
// - 无需轮询
// - 事件驱动,效率高

关键要点总结

1. 多传输 = 同时提交多个独立的USB传输请求
   - 不是多线程处理一个传输
   - 可以在单线程或多线程中使用

2. 为什么需要多传输?
   - 提高吞吐量(流水线效应)
   - 多端点并发通信
   - 批量操作同步等待

3. completed参数的作用:
   - 最后一个传输完成时立即退出
   - 不浪费超时等待时间
   - 性能提升数倍

4. 推荐模式:
   - 简单应用:单线程+多传输
   - 复杂应用:多线程+多传输
   - 调试/学习:单线程+单传输

5. 注意事项:
   - 回调函数必须快速返回
   - 使用互斥锁保护共享数据
   - completed指针需要volatile

意义4:避免虚假唤醒浪费CPU
// 场景:长时间等待,但需要响应停止信号

// ❌ 错误做法:短超时轮询
volatile int stop = 0;
while (!stop) {
    struct timeval tv = {0, 10000};  // 10ms
    libusb_handle_events_timeout_completed(ctx, &tv, NULL);
    // 每10ms唤醒一次检查stop标志
    // CPU占用高,功耗大
}

// ✅ 正确做法:长超时 + completed
volatile int stop = 0;
while (!stop) {
    struct timeval tv = {5, 0};  // 5秒
    libusb_handle_events_timeout_completed(ctx, &tv, (int*)&stop);
    // 有事件 → 立即唤醒处理
    // 设置stop=1 → 立即唤醒退出
    // 无事件 → 休眠5秒(低功耗)
}

性能对比: | 方案 | 唤醒频率 | CPU占用 | 停止延迟 | |——|———|———|———| | 短超时轮询(10ms) | 100次/秒 | 高 | 10ms | | 短超时轮询(1ms) | 1000次/秒 | 很高 | 1ms | | 长超时+completed | 按需唤醒 | 极低 | <1ms |


3.3.5 线程安全注意事项

问题1:需要volatile
// ❌ 错误:编译器可能优化掉completed检查
int completed = 0;

void event_thread() {
    while (!completed) {  // 编译器可能缓存completed值
        libusb_handle_events_timeout_completed(ctx, &tv, &completed);
    }
}

// ✅ 正确:使用volatile防止优化
volatile int completed = 0;

void event_thread() {
    while (!completed) {  // 每次都从内存读取
        libusb_handle_events_timeout_completed(ctx, &tv, (int*)&completed);
    }
}

问题2:原子性(可选)
// 对于简单的int赋值,volatile通常足够
volatile int completed = 0;
completed = 1;  // 单个int赋值通常是原子的

// 但如果paranoid,可以使用原子操作
#include <stdatomic.h>
atomic_int completed = ATOMIC_VAR_INIT(0);
atomic_store(&completed, 1);

// 或使用互斥锁
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int completed = 0;

void set_completed() {
    pthread_mutex_lock(&lock);
    completed = 1;
    pthread_mutex_unlock(&lock);
}

实践建议

  • 简单场景(单个标志位):volatile int足够
  • 复杂场景(多个变量):使用互斥锁或原子操作

3.3.6 常见使用模式

模式1:简单停止标志
volatile int stop_flag = 0;

void *event_loop(void *arg) {
    while (!stop_flag) {
        struct timeval tv = {1, 0};
        libusb_handle_events_timeout_completed(ctx, &tv, (int*)&stop_flag);
    }
    return NULL;
}

// 主线程
stop_flag = 1;  // 停止事件循环
pthread_join(event_thread, NULL);

模式2:条件等待
struct wait_context {
    volatile int done;
    void *result;
};

void callback(struct libusb_transfer *transfer) {
    struct wait_context *ctx = (struct wait_context *)transfer->user_data;
    ctx->result = process_data(transfer);
    ctx->done = 1;  // 触发completed
}

void *sync_transfer(libusb_device_handle *dev) {
    struct wait_context ctx = {.done = 0, .result = NULL};
    
    struct libusb_transfer *transfer = /* 分配并填充 */;
    transfer->user_data = &ctx;
    libusb_submit_transfer(transfer);
    
    // 等待该传输完成
    while (!ctx.done) {
        struct timeval tv = {0, 100000};
        libusb_handle_events_timeout_completed(libusb_ctx, &tv, (int*)&ctx.done);
    }
    
    return ctx.result;
}

模式3:多条件组合
struct event_control {
    volatile int stop_requested;
    volatile int error_occurred;
    volatile int task_completed;
};

void *event_loop(void *arg) {
    struct event_control *ctrl = (struct event_control *)arg;
    
    while (1) {
        // 组合条件:任何一个为真就退出
        int should_exit = ctrl->stop_requested || 
                         ctrl->error_occurred || 
                         ctrl->task_completed;
        
        if (should_exit) break;
        
        struct timeval tv = {1, 0};
        libusb_handle_events_timeout_completed(ctx, &tv, &should_exit);
    }
    
    return NULL;
}

// 不同场景触发不同标志
ctrl.stop_requested = 1;   // 用户请求停止
ctrl.error_occurred = 1;   // 发生错误
ctrl.task_completed = 1;   // 任务完成

3.3.7 与timeout参数的协同

completed和timeout是互补的两种退出机制:

┌─────────────────────────────────┐
│ libusb_handle_events_...()      │
│                                 │
│  退出条件:                      │
│  1. *completed != 0  ← 外部信号 │
│  2. timeout到期      ← 时间限制 │
│  3. 处理完所有事件   ← 自然退出 │
└─────────────────────────────────┘

推荐组合:

场景1:无限等待,需响应停止
  timeout = NULL
  completed = &stop_flag
  
场景2:有限等待,需响应停止
  timeout = {5, 0}
  completed = &stop_flag
  
场景3:快速轮询,不需停止
  timeout = {0, 10000}
  completed = NULL
  
场景4:等待特定传输完成
  timeout = {0, 100000}
  completed = &transfer_done

3.3.8 调试技巧

// 调试:记录completed触发时机
void *event_loop_debug(void *arg) {
    volatile int *completed = (volatile int *)arg;
    int iteration = 0;
    
    while (!*completed) {
        struct timeval tv = {1, 0};
        time_t before = time(NULL);
        
        int ret = libusb_handle_events_timeout_completed(ctx, &tv, (int*)completed);
        
        time_t after = time(NULL);
        iteration++;
        
        printf("[%d] ret=%d, elapsed=%lds, completed=%d\n",
               iteration, ret, after - before, *completed);
    }
    
    printf("事件循环退出,共迭代%d次\n", iteration);
    return NULL;
}

// 输出示例:
// [1] ret=0, elapsed=1s, completed=0
// [2] ret=0, elapsed=1s, completed=0
// [3] ret=0, elapsed=0s, completed=1  ← 立即退出
// 事件循环退出,共迭代3次

3.3.9 总结对比

特性传NULL传&completed
停止延迟最多timeout时间<1ms(内部管道唤醒)
CPU占用高(需短超时轮询)低(长超时+按需唤醒)
代码复杂度简单略复杂(需管理标志)
适用场景单线程、无停止需求多线程、需响应停止
功耗高(频繁唤醒)低(按需唤醒)

关键结论

  1. 单线程简单应用:传NULL即可
  2. 多线程/需停止控制:必须使用completed参数
  3. 等待特定条件:completed指向条件变量
  4. 优雅关闭:completed指向全局退出标志
  5. 低功耗应用:completed + 长超时(避免频繁唤醒)

四、返回值

int ret = libusb_handle_events_timeout_completed(ctx, &tv, NULL);

if (ret == 0) {
    // 成功(正常超时或处理了事件)
}
else if (ret == LIBUSB_ERROR_INTERRUPTED) {
    // 被系统信号中断(EINTR)
    // 通常可以重试
}
else {
    // 其它错误
    printf("错误: %s\n", libusb_error_name(ret));
}

常见返回值: | 返回值 | 含义 | 处理方式 | |——–|——|———| | 0 | 成功 | 继续 | | LIBUSB_ERROR_INTERRUPTED | 被信号中断 | 重试 | | LIBUSB_ERROR_NO_MEM | 内存不足 | 退出 | | LIBUSB_ERROR_INVALID_PARAM | 参数错误 | 检查代码 |


五、工作原理

5.1 内部流程图

libusb_handle_events_timeout_completed()
        ↓
   获取所有待监听的文件描述符
        ↓
   调用操作系统poll/select
        ↓
   ┌──────────────┐
   │ 超时?       │ Yes → 返回0
   └──────────────┘
         │ No
         ↓
   ┌──────────────┐
   │ *completed? │ Yes → 返回0
   └──────────────┘
         │ No
         ↓
   检查哪些文件描述符有事件
        ↓
   ┌──────────────────────┐
   │ USB传输完成事件?    │
   └──────────────────────┘
         │ Yes
         ↓
   从内核读取传输结果
         ↓
   调用libusb_transfer->callback(transfer)
         ↓
   处理下一个事件
         ↓
   所有事件处理完毕 → 返回0

5.2 与操作系统交互

Linux

// libusb内部使用
int nfds = poll(fds, nfds, timeout_ms);

// 监听的文件描述符示例
/dev/bus/usb/001/005  (USB设备)
pipe[0]               (内部通知管道)

Windows

// libusb内部使用
WaitForMultipleObjects(handles, nhandles, FALSE, timeout_ms);

// 监听的句柄
HANDLE hEvent  (USB完成事件)

macOS

// libusb内部使用
kqueue() + kevent()  (BSD风格)

六、使用场景

场景1:单线程事件循环(最简单)

#include <libusb-1.0/libusb.h>

void transfer_callback(struct libusb_transfer *transfer) {
    printf("传输完成,状态: %d\n", transfer->status);
    printf("实际传输: %d 字节\n", transfer->actual_length);
}

int main() {
    libusb_context *ctx = NULL;
    libusb_init(&ctx);
    
    libusb_device_handle *dev_handle = /* 打开设备 */;
    
    // 创建异步传输
    struct libusb_transfer *transfer = libusb_alloc_transfer(0);
    unsigned char buffer[64];
    
    libusb_fill_bulk_transfer(
        transfer,
        dev_handle,
        0x81,              // 端点IN
        buffer,
        sizeof(buffer),
        transfer_callback,
        NULL,              // user_data
        1000               // 超时1秒
    );
    
    // 提交传输
    libusb_submit_transfer(transfer);
    
    // 事件循环
    while (1) {
        struct timeval tv = {1, 0};  // 1秒超时
        int ret = libusb_handle_events_timeout_completed(ctx, &tv, NULL);
        
        if (ret < 0) {
            printf("事件处理错误: %s\n", libusb_error_name(ret));
            break;
        }
    }
    
    libusb_free_transfer(transfer);
    libusb_exit(ctx);
    return 0;
}

场景2:多线程事件循环(专用线程)

#include <pthread.h>
#include <libusb-1.0/libusb.h>

volatile int event_thread_run = 1;

void *event_thread_func(void *arg) {
    libusb_context *ctx = (libusb_context *)arg;
    
    while (event_thread_run) {
        struct timeval tv = {1, 0};
        libusb_handle_events_timeout_completed(ctx, &tv, 
            (int*)&event_thread_run);
    }
    
    return NULL;
}

int main() {
    libusb_context *ctx = NULL;
    libusb_init(&ctx);
    
    // 启动事件处理线程
    pthread_t event_thread;
    pthread_create(&event_thread, NULL, event_thread_func, ctx);
    
    // 主线程做其它工作
    libusb_device_handle *dev = /* 打开设备 */;
    
    // 提交多个异步传输
    for (int i = 0; i < 10; i++) {
        struct libusb_transfer *transfer = /* 创建传输 */;
        libusb_submit_transfer(transfer);
    }
    
    // 主线程继续做其它事情
    sleep(5);
    
    // 停止事件线程
    event_thread_run = 0;
    pthread_join(event_thread, NULL);
    
    libusb_exit(ctx);
    return 0;
}

场景3:非阻塞轮询(GUI应用)

// Qt/GTK等GUI框架中的定时器回调

void timer_callback() {
    struct timeval tv = {0, 0};  // 非阻塞
    
    int ret = libusb_handle_events_timeout_completed(ctx, &tv, NULL);
    
    if (ret < 0) {
        printf("错误: %s\n", libusb_error_name(ret));
    }
}

// Qt示例
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, this, &MyClass::timer_callback);
timer->start(50);  // 每50ms轮询一次

场景4:实时视频流(UVC摄像头)

#define NUM_TRANSFERS 4

struct libusb_transfer *transfers[NUM_TRANSFERS];
volatile int streaming = 1;

void video_callback(struct libusb_transfer *transfer) {
    if (transfer->status == LIBUSB_TRANSFER_COMPLETED) {
        // 处理视频数据
        process_frame(transfer->buffer, transfer->actual_length);
        
        // 重新提交传输(循环接收)
        if (streaming) {
            libusb_submit_transfer(transfer);
        }
    }
}

void start_video_streaming() {
    // 创建多个传输以提高吞吐量
    for (int i = 0; i < NUM_TRANSFERS; i++) {
        transfers[i] = libusb_alloc_transfer(0);
        unsigned char *buffer = malloc(49152);  // 48KB
        
        libusb_fill_bulk_transfer(
            transfers[i],
            dev_handle,
            0x81,
            buffer,
            49152,
            video_callback,
            NULL,
            1000
        );
        
        libusb_submit_transfer(transfers[i]);
    }
    
    // 快速事件循环
    while (streaming) {
        struct timeval tv = {0, 10000};  // 10ms超时
        libusb_handle_events_timeout_completed(ctx, &tv, (int*)&streaming);
    }
}

场景5:带超时重试的同步传输模拟

int sync_transfer_with_retry(struct libusb_transfer *transfer, int timeout_ms) {
    volatile int completed = 0;
    transfer->user_data = (void*)&completed;
    
    // 修改回调函数以设置completed标志
    void sync_callback(struct libusb_transfer *xfr) {
        int *flag = (int*)xfr->user_data;
        *flag = 1;
    }
    transfer->callback = sync_callback;
    
    // 提交传输
    int ret = libusb_submit_transfer(transfer);
    if (ret < 0) return ret;
    
    // 等待完成
    struct timeval start_time, now;
    gettimeofday(&start_time, NULL);
    
    while (!completed) {
        struct timeval tv = {0, 100000};  // 100ms
        ret = libusb_handle_events_timeout_completed(ctx, &tv, (int*)&completed);
        
        if (ret < 0) {
            libusb_cancel_transfer(transfer);
            return ret;
        }
        
        // 检查总超时
        gettimeofday(&now, NULL);
        int elapsed_ms = (now.tv_sec - start_time.tv_sec) * 1000 +
                        (now.tv_usec - start_time.tv_usec) / 1000;
        
        if (elapsed_ms > timeout_ms) {
            libusb_cancel_transfer(transfer);
            return LIBUSB_ERROR_TIMEOUT;
        }
    }
    
    return 0;
}

七、常见错误与解决

错误1:回调函数中阻塞

// ❌ 错误:回调中执行长时间操作
void bad_callback(struct libusb_transfer *transfer) {
    // 这会阻塞事件循环!
    sleep(1);
    heavy_computation(transfer->buffer);
}

// ✅ 正确:快速处理或放入队列
void good_callback(struct libusb_transfer *transfer) {
    // 复制数据
    memcpy(queue_buffer, transfer->buffer, transfer->actual_length);
    
    // 通知工作线程
    sem_post(&data_ready);
    
    // 重新提交
    libusb_submit_transfer(transfer);
}

错误2:忘记重新提交传输

// ❌ 错误:传输只执行一次
void callback(struct libusb_transfer *transfer) {
    process_data(transfer->buffer, transfer->actual_length);
    // 忘记重新提交!
}

// ✅ 正确:持续接收
void callback(struct libusb_transfer *transfer) {
    process_data(transfer->buffer, transfer->actual_length);
    
    if (keep_streaming) {
        libusb_submit_transfer(transfer);  // 重新提交
    }
}

错误3:多线程竞争

// ❌ 错误:多个线程同时调用
void thread1() {
    libusb_handle_events_timeout_completed(ctx, &tv, NULL);
}

void thread2() {
    libusb_handle_events_timeout_completed(ctx, &tv, NULL);  // 冲突!
}

// ✅ 正确:只用一个线程处理事件
void event_thread() {
    while (running) {
        libusb_handle_events_timeout_completed(ctx, &tv, NULL);
    }
}

void worker_thread() {
    // 只提交传输,不处理事件
    libusb_submit_transfer(transfer);
}

错误4:超时时间设置不当

// ❌ 错误:超时时间过长导致响应慢
struct timeval tv = {10, 0};  // 10秒!
libusb_handle_events_timeout_completed(ctx, &tv, NULL);
// 程序看起来"卡住"了

// ✅ 正确:根据应用调整
// 视频流
struct timeval tv_video = {0, 10000};  // 10ms

// 普通IO
struct timeval tv_io = {0, 100000};  // 100ms

// 低功耗
struct timeval tv_lowpower = {1, 0};  // 1秒

八、性能优化建议

8.1 减少系统调用开销

// 策略1:批量处理
// libusb内部会在一次调用中处理所有待处理事件

// 策略2:调整超时时间
// 视频流等实时应用:10-50ms
// 一般应用:100-500ms
struct timeval tv = {0, 50000};  // 50ms

8.2 使用多个传输提高吞吐量

// 同时提交多个传输(流水线)
#define NUM_TRANSFERS 8

for (int i = 0; i < NUM_TRANSFERS; i++) {
    libusb_submit_transfer(transfers[i]);
}

// 单次handle_events可能处理多个完成事件
libusb_handle_events_timeout_completed(ctx, &tv, NULL);

8.3 避免不必要的内存复制

// ❌ 低效
void callback(struct libusb_transfer *transfer) {
    unsigned char *copy = malloc(transfer->actual_length);
    memcpy(copy, transfer->buffer, transfer->actual_length);
    process(copy);
    free(copy);
}

// ✅ 高效(零拷贝)
void callback(struct libusb_transfer *transfer) {
    // 直接处理原始缓冲区
    process_inplace(transfer->buffer, transfer->actual_length);
}

九、与其它函数的关系

异步传输API家族:

libusb_alloc_transfer()          ← 分配传输结构
        ↓
libusb_fill_bulk_transfer()      ← 填充传输参数
        ↓
libusb_submit_transfer()         ← 提交到内核
        ↓
libusb_handle_events_timeout_completed()  ← 等待完成并调用回调
        ↓
callback(transfer)               ← 用户回调函数
        ↓
libusb_free_transfer()           ← 释放

同步传输对比

// 同步(简单但阻塞)
libusb_bulk_transfer(dev, ep, buf, len, &transferred, timeout);

// 异步(复杂但高效)
libusb_submit_transfer(transfer);
libusb_handle_events_timeout_completed(ctx, &tv, NULL);

十、调试技巧

10.1 启用调试输出

// 设置调试级别
libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);

// 或环境变量
// Linux: export LIBUSB_DEBUG=4
// Windows: set LIBUSB_DEBUG=4

10.2 检查事件循环是否卡死

void event_loop_with_watchdog() {
    time_t last_event = time(NULL);
    
    while (running) {
        struct timeval tv = {1, 0};
        int ret = libusb_handle_events_timeout_completed(ctx, &tv, NULL);
        
        time_t now = time(NULL);
        if (now - last_event > 5) {
            printf("警告: 5秒内没有事件!\n");
        }
        last_event = now;
    }
}

10.3 统计事件处理频率

int event_count = 0;
time_t start_time = time(NULL);

void event_loop_with_stats() {
    while (running) {
        struct timeval tv = {1, 0};
        libusb_handle_events_timeout_completed(ctx, &tv, NULL);
        event_count++;
        
        time_t now = time(NULL);
        if (now - start_time >= 10) {
            printf("事件频率: %.2f events/sec\n", 
                   event_count / 10.0);
            event_count = 0;
            start_time = now;
        }
    }
}

十一、总结

特性说明
功能处理USB异步传输的事件循环
阻塞可配置(通过timeout参数)
线程安全单个context只能一个线程调用
适用场景所有异步传输(批量、中断、等时)
性能高(事件驱动,无轮询开销)
复杂度中等(需要理解异步编程)

关键点

  1. 必须先调用libusb_submit_transfer()
  2. ✅ 回调函数应快速返回
  3. ✅ 一个context只用一个线程调用此函数
  4. ✅ 根据应用选择合适的超时时间
  5. ✅ 持续接收时在回调中重新提交传输

完成! 这份笔记详细讲解了libusb_handle_events_timeout_completed()的作用、参数、使用场景和最佳实践。

上一篇 USB设备描述符完全详解

一、描述符概述 USB设备通过描述符(Descriptor)向主机报告自己的特性、能力和配置。描述符是一组结构化的数据,...

下一篇 USB异步传输相关问题汇总

适用场景:Linux 用户空间使用 libusb 进行异步 USB 传输开发。 重点 API:libusb_transf...