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 | – | 假 | ❌ 否 | 假 | 继续运行 |
&flag | 0 | 真 | ✅ 是 | 假 | 继续运行 |
&flag | 1 | 真 | ✅ 是 | 真 | 立即退出 |
&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占用 | 高(需短超时轮询) | 低(长超时+按需唤醒) |
| 代码复杂度 | 简单 | 略复杂(需管理标志) |
| 适用场景 | 单线程、无停止需求 | 多线程、需响应停止 |
| 功耗 | 高(频繁唤醒) | 低(按需唤醒) |
关键结论:
- ✅ 单线程简单应用:传NULL即可
- ✅ 多线程/需停止控制:必须使用completed参数
- ✅ 等待特定条件:completed指向条件变量
- ✅ 优雅关闭:completed指向全局退出标志
- ✅ 低功耗应用: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只能一个线程调用 |
| 适用场景 | 所有异步传输(批量、中断、等时) |
| 性能 | 高(事件驱动,无轮询开销) |
| 复杂度 | 中等(需要理解异步编程) |
关键点:
- ✅ 必须先调用
libusb_submit_transfer() - ✅ 回调函数应快速返回
- ✅ 一个context只用一个线程调用此函数
- ✅ 根据应用选择合适的超时时间
- ✅ 持续接收时在回调中重新提交传输
完成! 这份笔记详细讲解了libusb_handle_events_timeout_completed()的作用、参数、使用场景和最佳实践。