libusb_bulk_transfer() 阻塞长达60s的原因

本文探讨了使用LibUSB进行Android手机AOA通信时遇到的问题:当手机断开连接时,write线程调用libusb_bulk_transfer()会阻塞60秒才返回,即使设置了超时也不生效。文章分析了问题原因,并提出了解决方案,包括使用libusb_handle_events_completed()和在读写线程间加锁确保串行执行。

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

以下内容,纯属记录。

最近调查在通过libusb与Android手机进行AOA通信,当手机拔掉,在write线程中会去调用libusb_bulk_transfer()进行相关的动作。但是会出现调用这个函数block长达60s时间才退出返回,即使设置参数timeout也没用。

截取到的log如下:
write线程第 00:00:49.532 开始写,但是直到 00:01:49.728 才返回。但是read线程每200ms timeout都能够正常返回。

01-01 00:39:53.244 D/xxx   (   40): read bulk ret = -7, transferred is 0
01-01 00:39:53.444 D/xxx   (   40): read bulk ret = -7, transferred is 0
01-01 00:39:53.648 D/xxx   (   40): read bulk ret = -7, transferred is 0
01-01 00:39:53.848 D/xxx   (   40): read bulk ret = -7, transferred is 0
01-01 00:00:49.532 D/xxx   (   40): starting write bulk, len = 10
01-01 00:00:49.728 D/xxx   (   40): read bulk ret = -7(LIBUSB_ERROR_TIMEOUT), transferred = 0


01-01 00:01:49.728 D/xxx   (   40): write ret = -7(LIBUSB_ERROR_TIMEOUT), transferred = 0
[  110.944172] usb 2-1: USB disconnect, device number 3
01-01 00:01:49.728 D/xxx   (   40): read bulk ret = -7(LIBUSB_ERROR_TIMEOUT), transferred = 0

主要原因是在read和write线程是异步执行的,在read的过程中又来了一次write,在 00:00:49.728 read返回之前就开始starting write了。我的理解是:两个线程同时调用libusb_bulk_transfer()会出现竞争关系。在libusb中为了解决这种竞争关系,引入了libusb_handle_events_completed() 函数。

关于libusb多线程应用和竞争关系,可以参照:[Multi-threaded applications and asynchronous I/O(翻译)]。
这篇文章我直接通过Google翻译出来,为了以后好理解。

个人理解,解决这个问题的方法主要是在read和write线程之间加锁,保证串行执行。因为在当前的实际情况中,write的时候比较少,串行工作不影响整体性能。

函数调用关系如下:

/** \ingroup syncio
 * Perform a USB bulk transfer. The direction of the transfer is inferred from
 * the direction bits of the endpoint address.
 *
 * For bulk reads, the <tt>length</tt> field indicates the maximum length of
 * data you are expecting to receive. If less data arrives than expected,
 * this function will return that data, so be sure to check the
 * <tt>transferred</tt> output parameter.
 *
 * You should also check the <tt>transferred</tt> parameter for bulk writes.
 * Not all of the data may have been written.
 *
 * Also check <tt>transferred</tt> when dealing with a timeout error code.
 * libusb may have to split your transfer into a number of chunks to satisfy
 * underlying O/S requirements, meaning that the timeout may expire after
 * the first few chunks have completed. libusb is careful not to lose any data
 * that may have been transferred; do not assume that timeout conditions
 * indicate a complete lack of I/O.
 *
 * \param dev_handle a handle for the device to communicate with
 * \param endpoint the address of a valid endpoint to communicate with
 * \param data a suitably-sized data buffer for either input or output
 * (depending on endpoint)
 * \param length for bulk writes, the number of bytes from data to be sent. for
 * bulk reads, the maximum number of bytes to receive into the data buffer.
 * \param transferred output location for the number of bytes actually
 * transferred.
 * \param timeout timeout (in millseconds) that this function should wait
 * before giving up due to no response being received. For an unlimited
 * timeout, use value 0.
 *
 * \returns 0 on success (and populates <tt>transferred</tt>)
 * \returns LIBUSB_ERROR_TIMEOUT if the transfer timed out (and populates
 * <tt>transferred</tt>)
 * \returns LIBUSB_ERROR_PIPE if the endpoint halted
 * \returns LIBUSB_ERROR_OVERFLOW if the device offered more data, see
 * \ref packetoverflow
 * \returns LIBUSB_ERROR_NO_DEVICE if the device has been disconnected
 * \returns another LIBUSB_ERROR code on other failures
 */
int API_EXPORTED libusb_bulk_transfer(struct libusb_device_handle *dev_handle,
    unsigned char endpoint, unsigned char *data, int length, int *transferred,
    unsigned int timeout)
{
    return do_sync_bulk_transfer(dev_handle, endpoint, data, length,
        transferred, timeout, LIBUSB_TRANSFER_TYPE_BULK);
}
static int do_sync_bulk_transfer(struct libusb_device_handle *dev_handle,
    unsigned char endpoint, unsigned char *buffer, int length,
    int *transferred, unsigned int timeout, unsigned char type)
{
    struct libusb_transfer *transfer = libusb_alloc_transfer(0);
    int completed = 0;
    int r;

    if (!transfer)
        return LIBUSB_ERROR_NO_MEM;

    libusb_fill_bulk_transfer(transfer, dev_handle, endpoint, buffer, length,
        bulk_transfer_cb, &completed, timeout);
    transfer->type = type;

    r = libusb_submit_transfer(transfer);
    if (r < 0) {
        libusb_free_transfer(transfer);
        return r;
    }

    while (!completed) {
        r = libusb_handle_events_completed(HANDLE_CTX(dev_handle), &completed);
        if (r < 0) {
            if (r == LIBUSB_ERROR_INTERRUPTED)
                continue;
            libusb_cancel_transfer(transfer);
            while (!completed)
                if (libusb_handle_events_completed(HANDLE_CTX(dev_handle), &completed) < 0)
                    break;
            libusb_free_transfer(transfer);
            return r;
        }
    }

    *transferred = transfer->actual_length;
    switch (transfer->status) {
    case LIBUSB_TRANSFER_COMPLETED:
        r = 0;
        break;
    case LIBUSB_TRANSFER_TIMED_OUT:
        r = LIBUSB_ERROR_TIMEOUT;
        break;
    case LIBUSB_TRANSFER_STALL:
        r = LIBUSB_ERROR_PIPE;
        break;
    case LIBUSB_TRANSFER_OVERFLOW:
        r = LIBUSB_ERROR_OVERFLOW;
        break;
    case LIBUSB_TRANSFER_NO_DEVICE:
        r = LIBUSB_ERROR_NO_DEVICE;
        break;
    default:
        usbi_warn(HANDLE_CTX(dev_handle),
            "unrecognised status code %d", transfer->status);
        r = LIBUSB_ERROR_OTHER;
    }

    libusb_free_transfer(transfer);
    return r;
}
/** \ingroup poll
 * Handle any pending events in blocking mode.
 *
 * Like libusb_handle_events(), with the addition of a completed parameter
 * to allow for race free waiting for the completion of a specific transfer.
 *
 * See libusb_handle_events_timeout_completed() for details on the completed
 * parameter.
 *
 * \param ctx the context to operate on, or NULL for the default context
 * \param completed pointer to completion integer to check, or NULL
 * \returns 0 on success, or a LIBUSB_ERROR code on failure
 * \see \ref mtasync
 */
int API_EXPORTED libusb_handle_events_completed(libusb_context *ctx,
    int *completed)
{
    struct timeval tv;
    tv.tv_sec = 60;
    tv.tv_usec = 0;
    return libusb_handle_events_timeout_completed(ctx, &tv, completed);
}
/** \ingroup poll
 * Handle any pending events.
 *
 * libusb determines "pending events" by checking if any timeouts have expired
 * and by checking the set of file descriptors for activity.
 *
 * If a zero timeval is passed, this function will handle any already-pending
 * events and then immediately return in non-blocking style.
 *
 * If a non-zero timeval is passed and no events are currently pending, this
 * function will block waiting for events to handle up until the specified
 * timeout. If an event arrives or a signal is raised, this function will
 * return early.
 *
 * If the parameter completed is not NULL then <em>after obtaining the event
 * handling lock</em> this function will return immediately if the integer
 * pointed to is not 0. This allows for race free waiting for the completion
 * of a specific transfer.
 *
 * \param ctx the context to operate on, or NULL for the default context
 * \param tv the maximum time to block waiting for events, or an all zero
 * timeval struct for non-blocking mode
 * \param completed pointer to completion integer to check, or NULL
 * \returns 0 on success, or a LIBUSB_ERROR code on failure
 * \see \ref mtasync
 */
int API_EXPORTED libusb_handle_events_timeout_completed(libusb_context *ctx,
    struct timeval *tv, int *completed)
<think>好的,用户想了解libusb库中的libusb_bulk_transfer函数的参数。我需要先回忆一下这个函数的具体定义和用法。 首先,libusb是一个用于USB设备访问的库,而libusb_bulk_transfer是用于批量传输的函数。批量传输是USB四种传输类型之一,适用于大量数据传输,对时间不敏感的情况,比如打印机或存储设备。 接下来,我需要确认这个函数的参数。根据记忆,函数原型大概是这样的: int libusb_bulk_transfer(libusb_device_handle *dev_handle, unsigned char endpoint, unsigned char *data, int length, int *transferred, unsigned int timeout); 每个参数的作用需要详细解释。dev_handle是设备句柄,代表打开的USB设备。endpoint是端点地址,这里要注意方向,输入还是输出,最高位表示方向。data是数据缓冲区,需要根据传输方向是读还是写来决定是输入还是输出缓冲区。length是缓冲区长度,防止溢出。transferred是实际传输的字节数,用来检查传输结果。timeout是超时时间,单位毫秒,0表示无限等待。 然后需要考虑用户可能的疑问,比如端点地址的细节,比如如何确定是IN还是OUT端点。还有数据缓冲区的使用,比如读取时需要预先分配足够空间,写入时填充数据。transferred参数的重要性,尤其是在部分传输的情况下,需要检查这个值。超时处理,以及函数返回的错误码,比如超时、I/O错误等。 可能用户在使用这个函数时遇到问题,比如传输失败,所以需要强调错误处理和返回值检查。另外,提醒用户注意端点的方向,避免混淆IN和OUT,导致传输方向错误。 需要确保解释清晰,步骤明确,参数逐一说明,并给出使用示例,帮助用户更好地理解如何调用这个函数。同时,注意使用中文,避免术语混淆,保持口语化,但专业准确。</think>libusb 是一个跨平台的 USB 设备访问库,`libusb_bulk_transfer` 是其核心函数之一,用于执行 USB **批量传输**(Bulk Transfer)。以下是该函数的参数详解: ### 函数原型 ```c int libusb_bulk_transfer( libusb_device_handle *dev_handle, unsigned char endpoint, unsigned char *data, int length, int *transferred, unsigned int timeout ); ``` ### 参数解析 1. **`dev_handle`** - 类型:`libusb_device_handle*` - 作用:已打开的 USB 设备句柄。通过 `libusb_open()` 获取,代表与设备的通信通道。 2. **`endpoint`** - 类型:`unsigned char` - 作用:指定传输方向的端点地址。 - **最高位**决定方向: - `0x80` (bit7=1):IN 传输(设备到主机) - `0x00` (bit7=0):OUT 传输(主机到设备) - 例如:`0x81` 表示端点1的IN传输。 3. **`data`** - 类型:`unsigned char*` - 作用:数据缓冲区指针。 - **IN传输**:用于接收数据的缓冲区(需预先分配内存)。 - **OUT传输**:指向待发送数据的缓冲区。 4. **`length`** - 类型:`int` - 作用:缓冲区 `data` 的长度(单位:字节)。 - IN传输:表示期望读取的最大字节数。 - OUT传输:表示实际要发送的字节数。 5. **`transferred`** - 类型:`int*` - 作用:输出参数,返回实际传输的字节数。 - 即使函数返回错误(如超时),此值仍可能包含部分传输的数据长度。 6. **`timeout`** - 类型:`unsigned int` - 作用:超时时间(单位:毫秒)。 - `0`:无限等待,直到传输完成。 - `>0`:在指定时间内未完成传输则返回超时错误(`LIBUSB_ERROR_TIMEOUT`)。 --- ### 返回值 - **成功**:返回 `0`,且 `*transferred` 表示实际传输的字节数。 - **失败**:返回错误码,例如: - `LIBUSB_ERROR_TIMEOUT`:超时 - `LIBUSB_ERROR_PIPE`:端点暂停(需清除端点状态) - `LIBUSB_ERROR_IO`:I/O 错误 --- ### 使用示例(伪代码) ```c // 假设已获取设备句柄 dev_handle unsigned char buffer[64]; // IN传输的接收缓冲区 int actual_length; int ret; // 从端点0x81读取数据 ret = libusb_bulk_transfer(dev_handle, 0x81, buffer, 64, &actual_length, 1000); if (ret == LIBUSB_SUCCESS) { printf("Received %d bytes: %s\n", actual_length, buffer); } else { printf("Error: %s\n", libusb_error_name(ret)); } ``` --- ### 注意事项 1. **端点方向**:必须与端点描述符定义的方向一致。 2. **缓冲区管理**: - IN传输:确保缓冲区足够容纳最大数据包。 - OUT传输:避免发送超过端点最大包长度的数据。 3. **错误处理**:检查返回值,并根据错误类型进行重试或复位设备。 4. **线程安全**:libusb 函数本身线程安全,但同一设备句柄的并发操作需同步。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值