跳到主要內容

Why Linux kernel has ACCESS_ONCE()?

在 LWN 看到一篇好文章,討論Linux kernel 中的 ACCESS_ONCE() macro 的用法與意義。文章討論到 multi-thread 程式共用變數所需要注意的一個細節:要使用 volatile 關鍵字去修飾變數以避免編譯器的最佳化造成的問題。

文中以一個在 Linux kernel 的例子來說明,但考慮到這個議題在任何 multi-thread 程式(或任何有 context-switch 的程式中)都會遇到,所以我用簡單的情況來舉例。考慮某個 thread 有如下程式碼片段:

  1. for (;;) {
  2. ...
  3.    if (global_status == ST_MOVE) {
  4.        do_something();
  5.    }
  6. ...

它會依據 global_status 的值來決定程式流程,而這個變數可能會在另一個 thread function 中被修改(也可能在 signal handler 中被修改),所以每次 loop 時,可能會執行 do_something() 也可能不會執行它。問題在於編譯器的最佳化往往都會作在語言沒有清楚定義的地方。在 C 語言中,由於沒有規定 multi-thread 的共享變數的行為(在 C11 前幾乎對此沒有著墨),所以若 for loop 中沒有對 global_status 任何修改,編譯器有可能將上述 loop 中的判斷整個拿掉:

  1. // if global_status is ST_SLEEP
  2. for (;;) {
  3. ...
  4.    /*
  5.    if (global_status == ST_MOVE) {
  6.        do_something();
  7.    }*/
  8. ...
所以一般要避免這種情況發生,都會以 volatile 修飾 globe_status 變數。這時,我們再來看看 ACCESS_ONCE() 的定義:
#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
很清楚地,就是希望以這種方式來存取變數,但問題在於:為何不從一開始就以 volatile 宣告,而是直到要存取時特別聲明呢?

為的是最佳化。Jonathan Corbet  特別在回應中提到,如果只在需要時聲明,便可以只在不須最佳化的地方避免錯誤,而在其他地方依然享有對此變數的最佳化。Linux kernel 為了最佳化,連變數的存取方式都很細緻呢...不過這也進一步要求 programmer 完全掌握 C 語言的各種黑暗角落...也因為如此,該文引來很多激烈的論戰。:-)

留言

張貼留言