LWN:GDB 與 io_uring!
關注了就能看到更多這么棒的文章哦~
GDB and io_uring
By Jake Edge
March 31, 2021
DeepL assisted translation
https://lwn.net/Articles/851076/
在用 GDB 來調試那些使用了 io_uring 的程序時,出現(xiàn)了一個問題,從而引出了后續(xù)的一系列可能的解決方案,并且最終有一個方案被合并到 Linux 5.12-rc5 中。這個問題來源于 5.12 合并窗口中改變了 io_uring 線程的創(chuàng)建方式,它們跟使用 io_uring 的進程關聯(lián)了起來。這些 "I/O 線程" 在內核中得到特殊的處理,這就導致了 GDB 這邊出現(xiàn)問題(很可能其他使用 ptrace()的程序也會出錯)。解決方法就是按照跟其他線程一樣的方式來對待它們,因為事實已經證明,對它們進行特殊化處理所引起的問題要比所解決的問題更加多。
Stefan Metzmacher 在 3 月 20 日向 io-uring 郵件列表報告了這個問題。他希望將 GDB 連到(attach to)一個使用了 io_uring 的程序進程,但 GDB 調試器 "進入了一個死循環(huán),因為它無法 attach 到 io_threads"。PF_IO_WORKER 線程是 io_uring 中用在可能會發(fā)生阻塞操作的情況下。他在 bug 報告之后提出了兩個 patch set,來通過不同的方式隱藏這些線程。之所以要隱藏掉它們,是因為 GDB 只要看不到這些線程,就不會去試圖 attach 到這些線程上去。在 5.12 之前,這些線程也是存在的,但并沒有不與 io_uring-using 進程關聯(lián)起來,所以 GDB 就看不到。
當然,對于開發(fā)者來說,如果不能在使用了 io_uring 的代碼上運行 GDB 調試器,這可不是一件好事,尤其是,如果他們應用程序中的 io_uring 支持代碼很可能是比較新的,因此它更可能需要更多調試方式。io_uring 子系統(tǒng)的維護者 Jens Axboe 很快就出面幫助 Metzmacher 解決了這個問題。Axboe 發(fā)布了一個 patch set,其中實現(xiàn)了對隱藏 PF_IO_WORKER 線程的效果,以及對這些線程相關的 signal 處理能力的一些調整。其實,他就是完全移除了這些線程接收 sginal 的能力。
這讓 Eric W. Biederman 有些不滿意。他想知道為什么 io_uring 線程就不能接收信號,尤其是 SIGSTOP。ptrace()使用 SIGSTOP 來 attach 到正在運行的進程,但 I/O thread 缺乏一些用戶空間的正常上下文,無法處理信號。Linus Torvalds 解釋說,signal 處理是在線程返回用戶空間時進行的,但對于內核線程來說,這個動作永遠不會發(fā)生。他在另一封郵件中進一步描述了這一點:
SIGSTOP 處理本質上是在 signal handling 的時候進行的,而 signal handling 本質上是在 "返回用戶空間" 時完成的。
于是:你根本無法向內核線程發(fā)送任何信號,除非它會明確地對這些 signal 進行手動處理。在這方面,SIGSTOP 就跟 user space "實際上" 發(fā)出的 signal 的處理程序沒有什么不同。
實際上,內核線程唯一能處理的信號是 SIGKILL (在此情況下結束線程)。
[……]我確實也認為 IO thread 不需要進行 signal 處理,因為它們根本無法用正常的方式來處理 signal。
幾天后,Axboe 發(fā)布了另一個版本的 patch set,并詳細介紹了這個問題以及提出的解決方案:
Stefan 報告說,在 attach 到一個使用了 io_uring 的任務上的時候,gdb 會非常困惑,反復嘗試 attach 到 IO thread 上,盡管每次它都會收到一個 -EPERM 返回值。這個 patch set 會忽略 same_thread_group() 中的 PF_IO_WORKER 類型的線程,除非是在做我們特地進行的統(tǒng)計工作。
同時我們還避免在/proc/<pid>/task/ 中列出 IO thread,這樣 gdb 就不會認為它應該 stop 并 attach 這些線程。這樣一來就跟之前的內核行為保持一致了。之前的內核中,這些異步線程與擁有 io_uring 的進程無關,因此 gdb(和其他工具)就不會去理會這些進程的。
但似乎那些補丁改得有點過了。Biederman 指出,這些線程完全不會再顯示在/proc 中,這樣以來 top 等診斷工具也就看不到了。Torvalds 指出,就連 ps 也都看不到這些進程了,因此他 "認為把它們隱藏起來不是一個正確的做法"。有一些討論在說是不是把這類線程列在在 /proc 其他子目錄下,但 Axboe 認為這可能會讓那些常用工具處理簡單,但很可能會 "搞亂一些東西"。Biederman 說,需要有一些機制來告訴 GDB(和其他調試器)這些線程是特殊的:"我在想,在嘗試 attach 時得到-EPERM(或者可能其他不同的錯誤代碼)是不是就已經能判斷出這個線程不可以被調試了。"
Axboe 在 patch cover letter 中提到,這里的底層問題可能真的是 GDB 的 bug,Biederman 似乎表示贊同了,但 Oleg Nesterov 表示反對。"內核改變了規(guī)則,這導致了 GDB 出錯"。但 Biederman 認為,這并不是嚴格意義上的 regression,"這其實是 gdb 沒有支持好新的功能"。不過,哪怕真的算是 gdb 的 bug,Axboe 也認為要等 gdb 的更新版能到用戶手里的話需要等待的時間還是太長了,所以才需要用這種方式來解決;除此之外,"我覺得可能 gdb 不是唯一一個會出問題的工具,還會有其他工具沒想到這里有些線程是無法 attach 的。"
所以,人們希望有某種解決方案,能讓所有的一切都能 "正常工作"——而這似乎正是 Torvalds 想出的辦法所能實現(xiàn)的效果:
實際上,也許正確的處理方法是干脆讓所有的 io 線程都接受 signal,從而將所有特殊情況都處理掉。
當然,這些信號永遠不會傳遞到用戶空間,但如果我們:
在有待處理的 signal 的時候,就讓這個 thread 主體執(zhí)行 get_signal()
允許對他們進行 ptrace_attach
這樣它們看起來就跟普通線程差不多了,只是從來不做 user-space 的 signal 處理。
之前我們讓 IO thread 采用特殊方式來處理 signal,這已經引起了很多問題,也許解決的辦法就是干脆不讓它們這么特殊?
Axboe 也同意這種 "支持 signal,這樣就能讓所有一切都可以默認正常工作"。為此,他嘗試按照這個思路進行修改后再使用 GDB attach,并獲得了成功。于是在 3 月 25 日提出了第一版 "允許 IO thread 使用 signal" 的 patch set,經過一些修正后,在 3 月 26 日提出了第 2 版。后者在 3 月 28 日迅速被 5.12-rc5 所采納。此外 kernel 也 revert 了一周前 5.12-rc4 所合入的一些臨時的 fix。
總之這個“I/O 線程直接像其他線程和進程一樣接受信號,而不是成為一種特殊情況”的想法,大大地簡化了解決方案。就像 Axboe 在第一個版本的 cover letter 中所說的那樣,事后來看,這個方案是顯而易見的:
就像其他大多數(shù)的好想法一樣,只要一聽到,你就明白了這是一個好想法。事實上,我們最終用這個方案的時候,不需要處理任何特殊情況了,這就清楚地表明,這個方案確實是正確的解決方案。事實上,這組 patch 中大多數(shù)是 revert 代碼,這進一步證明了這一點。
最后終于出現(xiàn)了這個更好的解決方案。至少在一定程度上得益于 Torvalds 重新思考了自己的決策,重新考慮了一些假設條件。雖然 PF_IO_WORKER 線程對發(fā)送給它們的 signal 不能做任何事情,但也并不需要去拒絕這些信號。一旦認識清楚這一點,patch set 就相當簡單了。而與此同時,對于使用 io_uring 的代碼的開發(fā)者來說,這個令人不快的問題也被快速消滅了。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長按下面二維碼關注,關注 LWN 深度文章以及開源社區(qū)的各種新近言論~
