核心同步
在現代核心內部,許多東西可能在同一時間碰觸同一份資料:多顆各自執行核心程式碼的 CPU、在一次更新中途到來的中斷,以及核心先佔一個任務去執行另一個。如果其中兩者未經協調就同時修改一個共享清單或計數器,資料就會損毀。核心同步(kernel synchronization)就是核心在這種激烈並行下用來讓自身內部資料保持一致的一套技術。這與任何並行程式中對共享資料加鎖是同一個問題,但多了額外的嚴苛限制,因為某些核心程式碼執行的場合絕對不能睡眠、也不能被中斷。
具體來說,核心會依情境在數種工具間做選擇。自旋鎖(spinlock)是一種忙碌等待的鎖:CPU 就只是不斷迴圈直到取得鎖,這對極短的臨界區間沒問題,且是中斷情境(程式碼不能睡眠的地方)下唯一安全的選擇——但若持有過久就會浪費 CPU。互斥鎖(mutex)或號誌(semaphore)能讓等待者睡眠,這對較長的等待較有效率,但只能用在獲准睡眠的程式碼裡(不能用在中斷處理常式中)。為了完全避免鎖定,核心使用每 CPU 資料(per-CPU data,每顆 CPU 有自己的一份,因此無共享、無需鎖)與讀取-複製-更新(RCU),後者讓眾多讀者在毫無鎖的情況下進行,而寫者則做出一個新版本,並在所有讀者都結束後才釋放舊版本。核心也透過在本地 CPU 上停用先佔或停用中斷來同步,使某段序列相對於那些特定干擾成為不可分割的。
它之所以重要,是因為這裡的核心錯誤會卡死或當掉整台機器,而非只是某一個應用程式,也因為其規則十分微妙:在中斷情境中拿一個會睡眠的鎖、或在持有自旋鎖時做緩慢的工作,都是經典的災難。一個關鍵且誠實的重點是:鎖只有在每一條碰觸該資料的路徑都拿同一把鎖時,才能保護那份資料——任何一處粗心、未同步的存取,都會像使用者空間裡的號誌那樣,重新引入競爭條件。
兩顆 CPU 同時都想往同一個核心清單加入一個項目。沒有鎖的話,兩者讀到相同的尾端指標,其中一個的更新覆蓋掉另一個——丟失一個項目、毀掉一份清單。在清單更新外圍加上自旋鎖後,第二顆 CPU 自旋等待第一顆所需的那幾道指令的時間,然後繼續;清單保持一致。若這個更新可能要花很久,核心就會改用互斥鎖,讓等待者睡眠而非燒掉一顆 CPU。
依情境選鎖:短而不可睡眠的工作用自旋鎖;較長且可睡眠的工作用互斥鎖。
RCU 並非免費的午餐:它在以讀取為主的資料上大放異彩,因為讀者不拿鎖,但寫者要付出代價,並必須等過一段寬限期才能回收舊資料。選錯原語——在不能睡眠的地方用會睡眠的鎖,或自旋鎖持有過久——是核心死結與停頓的主要原因。