同步方式
libusb_bulk_transfer(devh, ep_bulk, buf, CAM_BUF_SZ, &len, timeout);
進入libusb研究,發現libusb是采用異步方式來實現的。在do_sync_bulk_transfer中:
staticint do_sync_bulk_transfer(struct libusb_device_handle *dev_handle,
unsignedchar endpoint,unsignedchar*buffer,int length,
int*transferred,unsignedint timeout,unsignedchar type)
{
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(HANDLE_CTX(dev_handle));
}
}
這里libusb_fill_bulk_transfer來填充bulk transfer,然后libusb_submit_transfer提交bulk transfer,最后用libusb_handle_events來等待完成。當收到回應后,bulk_transfer_cb回調設置completed,從而阻塞被接觸,函數返回。
分段處理bulk transfer
libusb_submit_transfer最終調到了submit_bulk_transfer。該函數會檢查buffer大小,如果大於MAX_BULK_BUFFER_LENGTH(16K),則分成若干16K大小的urb,每個urb指向用戶buffer的適當位置,然后用ioctl(IOCTL_USBFS_SUBMITURB)提交每個urb。
收到數據時會判斷是否收齊所有urb。在handle_bulk_completion中,若urb_idx為最后一個urb,則認為收齊了所有的urb。最終會調用usbi_handle_transfer_completion來調用urb callback。
timeout處理
若libusb_bulk_transfer傳入的timeout為0,則沒有timeout,libusb會一直等待數據。在libusb_handle_events中設置了一個2s的poll timeout,libusb會在while中一直poll,每次poll的timeout為2s。
若設置了timeout,libusb_submit_transfer會按照timeout升序將transfer插入到libusb_context的flying_transfers列表中,然后提交transfer(當然會分段了,如上所述)
libusb_handle_events會根據自己設置的2s timeout和flying_transfers中的timeout得出實際timeout,然后用poll查詢usb fd。
問題根源
libusb阻塞的原因就是超時。有時usb指紋頭返回數據較慢,在指定的timeout時間內沒有完成所有urb請求,進入超時處理。handle_timeout()會cancel掉為完成的urb(IOCTL_USBFS_DISCARDURB)。在do_sync_bulk_transfer中,由於未完成所有urb,bulk_transfer_cb沒有被調用,從而會阻塞。libusb_handle_events會繼續以2s超時來查詢fd,但由於urb已經取消,設備會返回-2(ENOENT)。
按道理講,即使超時,libusb也應該在超時后返回。libusb為什么沒有返回呢?handle_bulk_completion函數中,若讀到ENOENT,awaiting_discard會遞減至0。與awaiting_discard一起的還有awaiting_reap,若二者均為0,也會調用bulk_transfer_cb通知用戶。
awaiting_discard在超時時cancel urb時被設置。若ioctl(IOCTL_USBFS_DISCARDURB)返回EINVAL,則awaiting_reap會遞增。
經過打印驗證,發現在cancel urb時,對於已經完成的urb,會返回EINVAL。而未完成的urb,則返回0。我想這大概是一個bug,正確做法應該是不要取消已經完成的urb。
