我正在努力在Linux上實現pthread取消,而沒有任何在我其他一些最近問題中討論過的「不愉快行爲」(有些人可能會說錯誤)。到目前爲止,pthread取消的Linux/glibc方法一直將它視爲不需要內核支持的方法,並且可以在庫級進行處理,純粹通過在進行系統調用之前啓用異步取消並恢復先前的取消狀態在系統調用返回之後。這有至少兩個問題,其中一個非常嚴重:在用戶空間中實現可取消的系統調用
- 取消可以在系統調用從內核空間返回之後,但在用戶空間保存返回值之前執行。如果系統調用分配資源,則會導致資源泄漏,並且無法使用取消處理程序對其進行修補。
- 如果在線程在可取消的系統調用中被阻塞時處理信號,則整個信號處理程序在啓用異步取消的情況下運行。這可能是非常危險的,因爲信號處理程序可能會調用異步信號安全但不是異步取消安全的函數。
我的第一個解決問題的想法是設置一個標誌線程處於取消點,而不是啓用異步取消,並且設置此標誌時,讓取消信號處理程序檢查保存的指令指針看它是否指向一個系統調用指令(特定於arch)。如果是這樣,則表示系統調用未完成,並且在信號處理程序返回時將重新啓動,因此我們可以取消。如果沒有,我認爲系統調用已經返回,並推遲取消。但是,還有一個競爭條件 - 線程可能根本沒有到達系統調用指令,在這種情況下,系統調用可能會阻止並且從不響應取消。另一個小問題是,如果在輸入信號處理程序時設置了取消點標誌,則從信號處理程序執行的不可取消系統調用錯誤地變爲可取消。
我正在尋找一種新方法,並尋找有關它的反饋。
- 之前的系統調用的完成收到的任何取消請求必須在任何時間顯著間隔系統調用塊之前被加載,而不是當它掛起的重新啓動由於中斷由一個:必須滿足的條件信號處理器。
- 完成系統調用後收到的任何取消請求必須推遲到下一個取消點。
我想到的想法需要爲可取消的系統調用包裝器專門組裝。基本思路是:
- 將即將到來的syscall指令的地址推入堆棧。
- 將堆棧指針存儲在線程本地存儲中。
- 測試線程本地存儲中的取消標誌;如果設置了跳轉到取消例程。
- 進行系統調用。
- 清除保存在線程本地存儲中的指針。
取消然後操作將涉及:
- 將解除標誌的目標線程的線程本地存儲。
- 測試目標線程的線程本地存儲中的指針;如果不爲空,則向目標線程發送取消信號。
該消除信號處理程序將則:
- 檢查所保存的堆棧指針(在信號上下文)等於在線程局部存儲器中的保存的指針。如果沒有,那麼取消點被信號處理程序中斷,現在沒有任何事情要做。
- 檢查程序計數器寄存器(保存在信號上下文中)是否小於或等於保存在保存的堆棧指針處的地址。如果是這樣,這意味着系統調用還沒有完成,並且我們執行取消操作。
我看到迄今唯一的問題是在信號處理程序的第1步:如決定不採取行動,那麼信號處理程序返回後,線程可以留待阻塞的系統調用,忽略了正待取消請求。爲此,我看到兩個可能的解決方案:
- 在這種情況下,安裝一個計時器將信號傳遞到特定的線程,基本上每隔幾毫秒重試一次,直到我們幸運。
- 再次提高取消信號,但從取消信號處理程序返回而不取消屏蔽取消信號。當中斷的信號處理程序返回時它會自動取消屏蔽,然後我們可以再次嘗試。不過,這可能會干擾信號處理程序中取消點的行爲。
任何想法,哪種方法是最好的,或者如果還有其他更重要的缺陷我錯過了?
加括號的評論完全是理想的解決方案。整個問題源於缺乏原子解鎖取消和系統調用的任何機制,並且它需要在用戶空間(它們看起來像它們屬於內核空間)中進行嚴重破解來解決它。 – 2011-04-16 14:05:26
你說得對,我的擔心是錯誤的。在取消信號被「錯誤地」阻塞的時候,取消標誌已經被設置,並且信號處理器執行的任何取消點將立即動作,不需要信號傳送它。除非發現不可預見的問題,否則我傾向於接受這個答案。 – 2011-04-16 18:37:51
@R .:我能想到的唯一的另一個問題是,在取消操作和系統調用包裝器之間需要一個內存屏障。 – caf 2011-04-17 02:48:24