最根本需要解決的問(wèn)題是所有的libusb I/O 處理都是通過(guò)poll()/select() 系統(tǒng)調(diào)用監(jiān)控文件描述符。在asynchronous接口中直接顯露出來(lái)的,但是同樣需要注意synchronous接口是在asynchonrous接口之上實(shí)現(xiàn)的,因此同樣需要考慮。
這個(gè)問(wèn)題是如果2個(gè)以上的線(xiàn)程同時(shí)在libusb的文件描述符上調(diào)用poll() or select(),這些線(xiàn)程中只有一個(gè)會(huì)在事件到來(lái)時(shí)被喚醒,其他的會(huì)完全被忽略任何發(fā)生的事件。
思考下面的偽代碼,提交異步傳輸然后等待完成。這是一種在異步接口之上實(shí)現(xiàn)同步接口的方法 (盡管libusb的方式比這個(gè)頁(yè)面上的更高級(jí),但libusb也是用類(lèi)似的方式)。
void cb(struct libusb_transfer *transfer)
{
int *completed = transfer->user_data;
*completed = 1;
}
void myfunc() {
struct libusb_transfer *transfer;
unsigned char buffer[LIBUSB_CONTROL_SETUP_SIZE];
int completed = 0;
transfer = libusb_alloc_transfer(0);
libusb_fill_control_setup(buffer,
LIBUSB_REQUEST_TYPE_VENDOR | LIBUSB_ENDPOINT_OUT, 0x04, 0x01, 0, 0);
libusb_fill_control_transfer(transfer, dev, buffer, cb, &completed, 1000);
libusb_submit_transfer(transfer);
while (!completed) {
poll(libusb file descriptors, 120*1000);
if (poll indicates activity)
libusb_handle_events_timeout(ctx, 0);
}
printf("completed!");
// other code here
}
此處是針對(duì)一種條件的異步事件的序列化完成,這種條件是特定的傳輸完成。poll()循環(huán)設(shè)置了一個(gè)很長(zhǎng)的超時(shí)以便于在沒(méi)有任何事情發(fā)生的情況下(在某些原因下不被限制)最小化CPU利用率。
如果這是唯一的使用libusb的文件描述符的線(xiàn)程,這沒(méi)有問(wèn)題: 另一個(gè)線(xiàn)程將吞沒(méi)我們感興趣的事件是沒(méi)有危險(xiǎn)的。另一方面,如果另一個(gè)線(xiàn)程使用相同等我文件描述符,這將有可能它將接收到我們感興趣的事件。在這種情況下, myfunc()在循環(huán)下次檢測(cè)的時(shí)候,直到120秒之后將只能發(fā)現(xiàn)傳輸已經(jīng)完成。顯然2分鐘的延時(shí)是讓人無(wú)法忍受的,更不要想通過(guò)使用很短延時(shí)來(lái)解決這個(gè)問(wèn)題。
解決方案是去報(bào)沒(méi)有2個(gè)線(xiàn)程同時(shí)使用一個(gè)文件描述符。一個(gè)幼稚的實(shí)現(xiàn)將影響庫(kù)的性能, 所以libusb提供了下面的文檔化的方案來(lái)保證沒(méi)有性能方面的損失。
在我們繼續(xù)深入之前,值得提一下的是所有的libusb封裝的事件處理程序都完全支持下面文檔的方案,這包括libusb_handle_events()所有的同步I/O 函數(shù)——libusb為你把這個(gè)頭疼的事情隱藏了起來(lái)。如果你堅(jiān)持那種級(jí)別,你不需要擔(dān)心任何問(wèn)題。
問(wèn)題是當(dāng)我們面對(duì)libusb 暴露出文件描述符來(lái)允許你將異步USB I/O融入主循環(huán)的事實(shí), 有效地允許你在libusb的后面做些工作。如果你自己用libusb的文件描述符,并將它們傳遞給poll()/select()時(shí) ,你需要注意相關(guān)問(wèn)題。
事件鎖
第一個(gè)被介紹的概念是事件鎖。事件鎖用于序列化想要處理事件的線(xiàn)程,以便于在任意時(shí)間只有唯一的一個(gè)線(xiàn)程正在處理事件。
你必須在使用libusb文件描述符之前使用事件鎖函數(shù) libusb_lock_events()。一旦你離開(kāi)poll()/select()循環(huán),你必須立即使用函數(shù) libusb_unlock_events()釋放事件鎖。
讓其他線(xiàn)程為你工作
雖然事件鎖是解決方案的一個(gè)重要部分,但僅用它本身是不夠的,如果下面的情況發(fā)生你也許會(huì)驚訝...
libusb_lock_events(ctx);
while (!completed) {
poll(libusb file descriptors, 120*1000);
if (poll indicates activity)
libusb_handle_events_timeout(ctx, 0);
}
libusb_unlock_events(ctx);
...答案是這個(gè)是不對(duì)的。這是因?yàn)榇a中顯示的傳輸也許會(huì)話(huà)更長(zhǎng)的時(shí)間(比如說(shuō)30秒)完成。 直到傳出完成之前鎖都不會(huì)被釋放。
另一個(gè)與之類(lèi)似想要處理事件的代碼的線(xiàn)程也許正在處理一個(gè)傳輸,并且將在幾毫秒之后完成。由于鎖的獨(dú)占,盡管有著如此之快的完成時(shí)間,另一個(gè)線(xiàn)程不能檢測(cè)到它的這種傳輸狀態(tài),直到上面的代碼完成(30秒之后) 。
為了解決這個(gè)問(wèn)題, libusb為你提供了一種方法決定什么時(shí)候另一個(gè)線(xiàn)程正在處理事件。 它也提供了一種方法鎖住你的線(xiàn)程直到事件處理線(xiàn)程完成一個(gè)事件(并且這種機(jī)制并不需要使用文件描述符)。
在確定另一個(gè)線(xiàn)程正在處理事件,你通過(guò)libusb_lock_event_waiters()使用獲得一個(gè)事件等待鎖,然后你重新檢測(cè)是否其他線(xiàn)程仍在處理事件。如果是這樣,你可以調(diào)用libusb_wait_for_event()。
libusb_wait_for_event() 將你的程序轉(zhuǎn)到休眠狀態(tài)直到事件發(fā)生。或者知道一個(gè)線(xiàn)程釋放這個(gè)事件鎖。當(dāng)這些事件發(fā)生或者你的線(xiàn)程被喚醒,你需要重新檢測(cè)它正在等待的條件。同樣需要重新檢測(cè)是否另一個(gè)線(xiàn)程在處理事件,如果沒(méi)有,它應(yīng)該自己開(kāi)始處理事件。這應(yīng)該看起來(lái)像下面的偽代碼:
retry:
if (libusb_try_lock_events(ctx) == 0) {
// we obtained the event lock: do our own event handling
while (!completed) {
if (!libusb_event_handling_ok(ctx)) {
libusb_unlock_events(ctx);
goto retry;
}
poll(libusb file descriptors, 120*1000);
if (poll indicates activity)
libusb_handle_events_locked(ctx, 0);
}
libusb_unlock_events(ctx);
} else {
// another thread is doing event handling. wait for it to signal us that
// an event has completed
libusb_lock_event_waiters(ctx);
while (!completed) {
// now that we have the event waiters lock, double check that another
// thread is still handling events for us. (it may have ceased handling
// events in the time it took us to reach this point)
if (!libusb_event_handler_active(ctx)) {
// whoever was handling events is no longer doing so, try again
libusb_unlock_event_waiters(ctx);
goto retry;
}
libusb_wait_for_event(ctx);
}
libusb_unlock_event_waiters(ctx);
}
printf("completed!\n");
一個(gè)天真的人看到上面的代碼也許會(huì)建議這個(gè)只能支持一個(gè)事件等待者 (因此總工2個(gè)競(jìng)爭(zhēng)的線(xiàn)程,另一個(gè)處理事件),因?yàn)楫?dāng)?shù)却录臅r(shí)候,事件等待者看起來(lái)已經(jīng)事件等待鎖。但是,系統(tǒng)支持多個(gè)事件等待者,因?yàn)?a title="Wait for another thread to signal completion of an event." data="/link?url=http://libusb.sourceforge.net/api-1.0/group__poll.html#gae22755d523560be2867be7d09034ca50" rel="nofollow" target="_blank">libusb_wait_for_event() 在等待時(shí)確實(shí)放棄了鎖,在繼續(xù)之前請(qǐng)求鎖。
我們已經(jīng)實(shí)現(xiàn)了動(dòng)態(tài)處理沒(méi)有現(xiàn)成正在處理事件情況代碼(所以我們應(yīng)該自己做),并且它也能夠處理另一個(gè)線(xiàn)程正在處理事件的情況。(所以我們能承載他們)。它相當(dāng)于處理2者得結(jié)合。 舉例來(lái)說(shuō),另一個(gè)線(xiàn)程正在處理事件,但是某種原因,在我們的條件符合的情況出現(xiàn)之前,它停了下來(lái),所以我們接替處理事件。
下面介紹4個(gè)在上面?zhèn)未a中出現(xiàn)的函數(shù)。它們的重要性在上面的偽代碼中是顯而易見(jiàn)的 。
1. libusb_try_lock_events() 是一個(gè)嘗試獲取事件鎖非阻塞的函數(shù),如果已經(jīng)被占用它會(huì)返回失敗代碼。
2. libusb_event_handling_ok() 檢測(cè)libusb是否可以為你的線(xiàn)程執(zhí)行事件處理。有時(shí),libusb需要中斷事件處理器,這就是你如何能在你已經(jīng)被中斷的情況下檢測(cè)的原因。如果這個(gè)函數(shù)返回0,正確的行為是放棄事件鎖,然后重復(fù)循環(huán)。接下來(lái)的libusb_try_lock_events()將會(huì)失敗,所以你將變成一個(gè)事件等待者。想獲取更多信息,請(qǐng)閱讀下面的完整流程。
3. libusb_handle_events_locked() 是一個(gè)libusb_handle_events_timeout() 的變體,你可以在持有事件鎖的的情況下調(diào)用。 libusb_handle_events_timeout() 它本身的實(shí)現(xiàn)邏輯類(lèi)似上面的,所以確保當(dāng)你正在libusb后面工作的時(shí)候不要調(diào)用它,就如這里的原因一樣。
4. libusb_event_handler_active() 判斷是否有線(xiàn)程占用事件鎖。
你也許會(huì)驚訝為什么沒(méi)有一個(gè)函數(shù)能喚醒所有調(diào)用 libusb_wait_for_event()的線(xiàn)程。這是因?yàn)閘ibusb可以在其內(nèi)部完成這項(xiàng)操作:當(dāng)有人調(diào)用libusb_unlock_events()或者傳輸結(jié)束的時(shí)候,它將喚醒所有這樣的線(xiàn)程 (在回調(diào)函數(shù)返回的時(shí)候)。
完整流程
以上的解釋?xiě)?yīng)該足夠你繼續(xù)下去,但是如果你真的仔細(xì)思考這個(gè)問(wèn)題,你可能會(huì)對(duì)libusb的內(nèi)部有一些更多的疑問(wèn)。如果你很好奇,繼續(xù)讀下去,如果不是,請(qǐng)?zhí)^(guò)下面的章節(jié)以避免使你困惑。
首先從你腦海中跳出來(lái)的問(wèn)題是:當(dāng)另一個(gè)線(xiàn)程正在處理事件的時(shí)候,如果一個(gè)線(xiàn)程修改了需要被使用的文件描述符集合會(huì)怎么樣?
可能會(huì)發(fā)生以下2情況。
1. libusb_open() 將會(huì)增加另一個(gè)文件描述符到使用集合中,因此中斷事件處理器是合理的,以至于它接管新的描述符后重新啟動(dòng)。
2. libusb_close() 將會(huì)從使用集合中移除一個(gè)文件描述符。有很多的競(jìng)爭(zhēng)條件在這里發(fā)生,所以在這時(shí)沒(méi)有正在處理事件是很重要的。
Libusb在內(nèi)部處理這些問(wèn)題,所以應(yīng)用程序開(kāi)發(fā)者在開(kāi)啟或者關(guān)閉設(shè)備的時(shí)候不需要停止他們的事件處理器。下面是它如何工作的,先來(lái)看 libusb_close()的情況:
1. 在初始化的時(shí)候,libusb打開(kāi)一個(gè)內(nèi)部管道,然后增加管道的讀端到要使用的文件描述符集合中。
2. 在調(diào)用libusb_close()的時(shí)候,libusb在這個(gè)控制管道上寫(xiě)一些復(fù)制下來(lái)的數(shù)據(jù)。這回立即中斷事件處理程序。libusb也會(huì)在內(nèi)部為這個(gè)高級(jí)別事件記錄下來(lái)它正在嘗試中斷事件處理程序。
3. 在這時(shí),上面的一些函數(shù)開(kāi)始不同的行為:
o libusb_event_handling_ok() 開(kāi)始返回1,表示事件處理是不能繼續(xù)的。
o libusb_try_lock_events() 開(kāi)始返回1,表示另一個(gè)線(xiàn)程持有事件處理鎖,即使鎖沒(méi)有被占用。
o libusb_event_handler_active()開(kāi)始返回1,表示另一個(gè)線(xiàn)程正在處理事件,即使并有處理。
4. 上面的在事件處理停止的處理結(jié)果中發(fā)生改變。并迅速的放棄鎖,給高級(jí)別的libusb_close() 操作一個(gè)"便利"去獲取事件鎖。所有爭(zhēng)奪處理事件的線(xiàn)程都變成事件等待者。
5. 在 libusb_close()持有事件鎖,libusb可以從輪詢(xún)集合中安全的刪除文件描述符,in the safety of knowledge that 沒(méi)有任何線(xiàn)程正在輪詢(xún)那些描述符或者正嘗試訪問(wèn)輪詢(xún)集合。
6. 在獲取事件鎖后,關(guān)閉操作快速完成(通常幾毫秒) 然后直接釋放事件鎖。
7. 同時(shí),libusb_event_handling_ok() 執(zhí)行,并且其他的都返回都原始狀態(tài),文檔描述的行為。
8. 事件鎖的釋放會(huì)喚醒所有正在等待事件的線(xiàn)程,然后開(kāi)始競(jìng)爭(zhēng)再次成為事件處理程序。他們中的一個(gè)將會(huì)成功;它將重新獲得輪詢(xún)描述符的列表,然后USB I/O將會(huì)正常執(zhí)行下去。
libusb_open()是相似的,而且是一個(gè)更相似的例子,當(dāng)調(diào)用libusb_open()的時(shí)候:
1. 設(shè)備是被打開(kāi)的,并且文件描述符是被加入到輪詢(xún)集合的。
2. libusb發(fā)送一些復(fù)制數(shù)據(jù)到控制管道,然后記錄它正在嘗試修改輪詢(xún)描述符集合。
3. 事件處理程序被中斷,就如同libusb_close()的效果一樣,發(fā)生相同的行為改變,引起所有的事件處理線(xiàn)程編程事件等待者。
4. libusb_open() 實(shí)現(xiàn)使用它的免費(fèi)權(quán)利獲得事件鎖。
5. 很順利的它暫停了事件處理程序,libusb_open()釋放事件鎖。
6. 事件等待者線(xiàn)程被喚醒,然后再次爭(zhēng)奪成為事件處理程序。其中一個(gè)再次成功獲取包含新增設(shè)備的輪詢(xún)描述符列表。
結(jié)束語(yǔ)
上面的內(nèi)容也許看起來(lái)有點(diǎn)復(fù)雜,但是我希望我已經(jīng)講明白為什么這么復(fù)雜是必要的。同樣,不要忘記這只應(yīng)用于那些將使用libusb的文件描述符和集成它們到自己的輪詢(xún)循環(huán)中的應(yīng)用程序。
你也許認(rèn)為在你的多線(xiàn)程應(yīng)用程序中忽略一些上面詳細(xì)說(shuō)到的規(guī)則和鎖是沒(méi)有問(wèn)題的,因?yàn)槟悴徽J(rèn)為2個(gè)線(xiàn)程會(huì)在同一時(shí)刻輪詢(xún)一個(gè)描述符。如果是這種原因,那么這對(duì)你是個(gè)好消息,因?yàn)槟悴恍枰獡?dān)心。但是請(qǐng)注意這里,記住同步I/O函數(shù)在內(nèi)部處理事件。如果你有一個(gè)線(xiàn)程在循環(huán)中處理事件 (沒(méi)有實(shí)現(xiàn)上述文檔所說(shuō)的規(guī)則和鎖的概念) ,并且另一個(gè)線(xiàn)程正在嘗試發(fā)送一個(gè)同步USB傳輸,將發(fā)生2個(gè)線(xiàn)程監(jiān)視同一描述符的結(jié)果,并且會(huì)發(fā)生上述不希望發(fā)生的問(wèn)題。解決法案就是讓你的輪詢(xún)線(xiàn)程遵循規(guī)則,同步I/O函數(shù)也是這么做的,并且這將使線(xiàn)程之間處理的更協(xié)調(diào)。
如果你已經(jīng)有一個(gè)用于處理事件的線(xiàn)程,它占有事件鎖很長(zhǎng)時(shí)間是完全合理的。任何你從其他線(xiàn)程調(diào)用的同步I/O函數(shù)都會(huì)顯而易見(jiàn)的退回到上面詳細(xì)說(shuō)到的“事件等待者”的機(jī)制狀態(tài)下。你的事件處理線(xiàn)程唯一需要考慮做的就是與libusb_event_handling_ok()有關(guān)的事情:你必須在每個(gè)poll()之前調(diào)用它,如果已經(jīng)被使用你必須放棄事件鎖。
聯(lián)系客服