Linux核心破解不可靠指南¶
- 作者:
Rusty Russell
簡介¶
歡迎,各位讀者,來到Rusty的Linux核心破解極其不可靠指南。本文件描述了核心程式碼的常用例程和一般要求:其目標是為經驗豐富的C程式設計師提供Linux核心開發的入門知識。我避免了實現細節:這是程式碼的作用,並且我忽略了整套有用的例程。
在閱讀本文之前,請理解我從未想過要編寫本文件,因為我完全不具備資格,但我一直想閱讀它,而這是唯一的途徑。我希望它能發展成為最佳實踐,常用起點和隨機資訊的綱要。
參與者¶
在任何時候,系統中的每個CPU都可以
未與任何程序關聯,正在處理硬體中斷;
未與任何程序關聯,正在處理軟中斷或tasklet;
在核心空間中執行,與程序關聯(使用者上下文);
在使用者空間中執行程序。
這些之間存在一種順序。最下面的兩個可以搶佔彼此,但除此之外是一個嚴格的層次結構:每個只能被上面的搶佔。例如,當一個softirq在CPU上執行時,沒有其他的softirq會搶佔它,但是硬體中斷可以。但是,系統中的任何其他CPU都可以獨立執行。
我們將看到許多使用者上下文可以阻止中斷的方式,以變得真正不可搶佔。
使用者上下文¶
使用者上下文是指從系統呼叫或其他陷阱進入的情況:與使用者空間一樣,你可能會被更重要的任務和中斷搶佔。你可以透過呼叫schedule()來睡眠。
注意
你始終處於模組載入和解除安裝時的使用者上下文中,以及在塊裝置層上的操作中。
在使用者上下文中,current指標(指示我們當前正在執行的任務)是有效的,並且in_interrupt()(include/linux/preempt.h)為false。
警告
請注意,如果你停用了搶佔或軟中斷(見下文),in_interrupt()將返回假陽性。
硬體中斷(Hard IRQ)¶
定時器滴答、網絡卡和鍵盤都是真實的硬體示例,它們會隨時產生中斷。核心執行中斷處理程式,為硬體提供服務。核心保證此處理程式永遠不會被重新進入:如果同一中斷到達,它將被排隊(或丟棄)。因為它停用了中斷,所以此處理程式必須快速:通常它只是確認中斷,標記一個“軟體中斷”以供執行並退出。
你可以判斷你是否處於硬體中斷中,因為in_hardirq()返回true。
警告
請注意,如果停用了中斷(見下文),這將返回假陽性。
軟體中斷上下文:Softirq和Tasklet¶
每當系統呼叫即將返回到使用者空間,或者硬體中斷處理程式退出時,任何標記為掛起的“軟體中斷”(通常由硬體中斷標記)都會執行(kernel/softirq.c)。
大部分實際的中斷處理工作都在這裡完成。在早期過渡到SMP時,只有“bottom halves”(BHs),它們沒有利用多個CPU的優勢。在我們從用火柴棍和鼻涕製成的發條計算機切換後不久,我們放棄了此限制並切換到“softirqs”。
include/linux/interrupt.h列出了不同的softirqs。一個非常重要的softirq是定時器softirq(include/linux/timer.h):你可以註冊讓它在給定的時間長度內為你呼叫函式。
Softirqs通常難以處理,因為相同的softirq會同時在多個CPU上執行。因此,更常用的是tasklet(include/linux/interrupt.h):它們是動態可註冊的(意味著你可以擁有任意多個),並且它們還保證任何tasklet一次只會在一個CPU上執行,儘管不同的tasklet可以同時執行。
警告
名稱“tasklet”具有誤導性:它們與“任務”無關。
你可以使用in_softirq()宏(include/linux/preempt.h)判斷你是否處於softirq(或tasklet)中。
警告
請注意,如果持有bottom half鎖,這將返回假陽性。
一些基本規則¶
- 沒有記憶體保護
如果你破壞了記憶體,無論是在使用者上下文還是中斷上下文中,整個機器都會崩潰。你確定你不能在使用者空間中完成你想要做的事情嗎?
- 沒有浮點或MMX
FPU上下文未儲存;即使在使用者上下文中,FPU狀態也可能與當前程序不符:你可能會弄亂某些使用者程序的FPU狀態。如果你真的想這樣做,你必須顯式儲存/恢復完整的FPU狀態(並避免上下文切換)。這通常是個壞主意;首先使用定點算術。
- 嚴格的堆疊限制
根據配置選項,對於大多數32位體系結構,核心堆疊約為3K到6K:在大多數64位架構上約為14K,並且通常與中斷共享,因此你不能全部使用它。避免深度遞迴和堆疊上的大型區域性陣列(而是動態分配它們)。
- Linux核心是可移植的
讓我們保持這種狀態。你的程式碼應為64位clean且與位元組序無關。你還應該儘量減少CPU特定的東西,例如,內聯彙編應乾淨地封裝並儘量減少,以簡化移植。通常,它應限制在核心樹的體系結構相關部分。
ioctl:不編寫新的系統呼叫¶
系統呼叫通常如下所示
asmlinkage long sys_mycall(int arg)
{
return 0;
}
首先,在大多數情況下,你不想建立新的系統呼叫。你建立一個字元裝置併為其實現適當的ioctl。這比系統呼叫靈活得多,不必在每個體系結構的include/asm/unistd.h和arch/kernel/entry.S檔案中輸入,並且更有可能被Linus接受。
如果你的所有例程只是讀取或寫入一些引數,請考慮實現一個sysfs()介面。
在ioctl內部,你處於程序的使用者上下文中。發生錯誤時,你返回一個取反的errno(參見include/uapi/asm-generic/errno-base.h,include/uapi/asm-generic/errno.h和include/linux/errno.h),否則你返回0。
睡眠後,你應檢查是否發生了訊號:Unix/Linux處理訊號的方式是使用-ERESTARTSYS錯誤暫時退出系統呼叫。系統呼叫入口程式碼將切換回使用者上下文,處理訊號處理程式,然後你的系統呼叫將被重新啟動(除非使用者停用了該功能)。因此,你應該準備好處理重新啟動,例如,如果你正在操縱某些資料結構。
if (signal_pending(current))
return -ERESTARTSYS;
如果你正在進行較長時間的計算:首先考慮使用者空間。如果你真的想在核心中執行此操作,則應定期檢查是否需要放棄CPU(請記住,每個CPU都有協作式多工處理)。成語
cond_resched(); /* Will sleep */
關於介面設計的簡短說明:UNIX系統呼叫的座右銘是“提供機制而不是策略”。
死鎖配方¶
你不能呼叫任何可能睡眠的例程,除非
你處於使用者上下文中。
你不擁有任何自旋鎖。
你已啟用中斷(實際上,Andi Kleen表示排程程式碼會為你啟用它們,但這可能不是你想要的)。
請注意,某些函式可能會隱式睡眠:常見的函式是使用者空間訪問函式(*_user)和沒有GFP_ATOMIC的記憶體分配函式。
你應該始終開啟CONFIG_DEBUG_ATOMIC_SLEEP來編譯你的核心,如果違反了這些規則,它會警告你。如果你確實違反了這些規則,你最終會鎖定你的機器。
真的。
常用例程¶
printk()¶
定義在include/linux/printk.h中
printk()將核心訊息饋送到控制檯、dmesg和syslog守護程式。它對於除錯和報告錯誤很有用,並且可以在中斷上下文中使用,但要謹慎使用:控制檯上充斥著printk訊息的機器是無法使用的。它使用與ANSI C printf基本相容的格式字串,並使用C字串連線為其提供第一個“優先順序”引數
printk(KERN_INFO "i = %u\n", i);
參見include/linux/kern_levels.h;對於其他KERN_值;這些值由syslog解釋為級別。特殊情況:對於列印IP地址,請使用
__be32 ipaddress;
printk(KERN_INFO "my ip: %pI4\n", &ipaddress);
printk()在內部使用1K緩衝區,並且不會捕獲溢位。確保這足夠了。
注意
當你在你的使用者程式中開始將printf輸入為printk時,你就會知道你是一名真正的核心駭客 :)
注意
另一個附註:原始的Unix Version 6原始碼在其printf函式的頂部有一個註釋:“Printf不應用於閒聊”。你應該遵循該建議。
copy_to_user() / copy_from_user() / get_user() / put_user()¶
定義在include/linux/uaccess.h / asm/uaccess.h中
[睡眠]
put_user()和get_user()用於從使用者空間獲取和放置單個值(例如int,char或long)。指向使用者空間的指標絕不應簡單地取消引用:應使用這些例程複製資料。兩者都返回-EFAULT或0。
copy_to_user()和copy_from_user()更通用:它們將任意數量的資料複製到使用者空間和從使用者空間複製。
警告
與put_user()和get_user()不同,它們返回未複製的資料量(即0仍然表示成功)。
[是的,這個令人反感的介面讓我畏縮。火焰戰大約每年發生一次。--RR.]
這些函式可能會隱式睡眠。這絕不應在使用者上下文之外(這是沒有意義的)、停用中斷或持有自旋鎖的情況下呼叫。
kmalloc()/kfree()¶
定義在include/linux/slab.h中
[可能睡眠:見下文]
這些例程用於動態請求指標對齊的記憶體塊,就像malloc和free在使用者空間中所做的那樣,但是kmalloc()採用額外的標誌字。重要值
GFP_KERNEL可能睡眠並交換以釋放記憶體。僅允許在使用者上下文中,但這是分配記憶體的最可靠方法。
GFP_ATOMIC不要睡眠。不如
GFP_KERNEL可靠,但可以從中斷上下文中呼叫。你真的應該有一個良好的記憶體不足錯誤處理策略。GFP_DMA分配低於16MB的ISA DMA。如果你不知道那是什麼,則不需要它。非常不可靠。
如果你看到從無效上下文中呼叫的睡眠函式警告訊息,那麼你可能從沒有GFP_ATOMIC的中斷上下文中呼叫了睡眠分配函式。你真的應該修復它。快跑,別走。
如果你至少分配PAGE_SIZE(asm/page.h或asm/page_types.h)位元組,請考慮使用__get_free_pages()(include/linux/gfp.h)。它採用order引數(0表示頁面大小,1表示雙頁,2表示四頁等)和與上面相同的記憶體優先順序標誌字。
如果你要分配超過一頁的位元組,則可以使用vmalloc()。它將在核心對映中分配虛擬記憶體。此塊在物理記憶體中不是連續的,但是MMU使其看起來對你來說是連續的(因此,它僅對CPU看起來是連續的,而不是對外部裝置驅動程式)。如果你確實需要一些奇怪的裝置的大型物理連續記憶體,那麼你遇到了一個問題:由於執行核心中的記憶體碎片會在一段時間後使其變得困難,因此Linux對它的支援很差。最好的方法是透過alloc_bootmem()例程在啟動過程的早期分配該塊。
在發明你自己的常用物件快取之前,請考慮使用include/linux/slab.h中的slab快取
current¶
定義在include/asm/current.h中
此全域性變數(實際上是一個宏)包含指向當前任務結構的指標,因此僅在使用者上下文中有效。例如,當程序進行系統呼叫時,這將指向呼叫程序的任務結構。它在中斷上下文中不是NULL。
mdelay()/udelay()¶
定義在include/asm/delay.h / include/linux/delay.h中
udelay()和ndelay()函式可用於小暫停。請勿將較大的值與它們一起使用,因為你可能會冒溢位的風險-輔助函式mdelay()在此處很有用,或者考慮msleep()。
cpu_to_be32()/be32_to_cpu()/cpu_to_le32()/le32_to_cpu()¶
定義在include/asm/byteorder.h中
cpu_to_be32()系列(其中“32”可以替換為64或16,“be”可以替換為“le”)是在核心中進行位元組序轉換的通用方法:它們返回轉換後的值。所有變體也提供相反的變體:be32_to_cpu()等等。
這些函式有兩個主要變體:指標變體,例如cpu_to_be32p(),它採用指向給定型別的指標,並返回轉換後的值。另一個變體是“in-situ”系列,例如cpu_to_be32s(),它轉換指標所引用的值,並返回void。
local_irq_save()/local_irq_restore()¶
定義在include/linux/irqflags.h中
這些例程停用本地CPU上的硬中斷,並恢復它們。它們是可重入的;將先前的狀態儲存在其一個unsigned long flags引數中。如果你知道已啟用中斷,則可以簡單地使用local_irq_disable()和local_irq_enable()。
local_bh_disable()/local_bh_enable()¶
定義在include/linux/bottom_half.h中
這些例程停用本地CPU上的軟中斷,並恢復它們。它們是可重入的;如果之前停用了軟中斷,則在呼叫這對函式之後,它們仍將被停用。它們阻止softirq和tasklet在當前CPU上執行。
smp_processor_id()¶
定義在include/linux/smp.h中
get_cpu()停用搶佔(因此你不會突然被移至另一個CPU),並返回當前處理器編號,介於0和NR_CPUS之間。請注意,CPU編號不一定是連續的。完成後,你可以使用put_cpu()再次返回它。
如果你知道你不會被另一個任務搶佔(即,你處於中斷上下文中,或者停用了搶佔),則可以使用smp_processor_id()。
__init/__exit/__initdata¶
定義在include/linux/init.h中
啟動後,核心會釋放一個特殊部分;標有__init的函式和標有__initdata的資料結構將在啟動完成後被刪除:同樣,模組在初始化後也會丟棄此記憶體。__exit用於宣告僅在退出時才需要的函式:如果未將此檔案編譯為模組,則該函式將被刪除。有關用法,請參見標頭檔案。請注意,標有__init的函式透過EXPORT_SYMBOL()或EXPORT_SYMBOL_GPL()匯出到模組是沒有意義的 - 這將中斷。
__initcall()/ module_init()¶
定義於 include/linux/init.h / include/linux/module.h
核心的許多部分作為模組(核心的動態載入部分)執行良好。 使用 module_init() 和 module_exit() 宏,可以很容易地編寫沒有 #ifdef 的程式碼,這些程式碼可以作為模組或構建到核心中執行。
module_init() 宏定義了在模組插入時(如果檔案編譯為模組)或在啟動時呼叫的函式:如果檔案沒有編譯為模組,則 module_init() 宏等同於 __initcall(),它透過連結器魔法確保該函式在啟動時被呼叫。
該函式可以返回一個負錯誤碼,以導致模組載入失敗(不幸的是,如果模組被編譯到核心中,這將不起作用)。此函式在啟用中斷的使用者上下文中呼叫,因此它可以休眠。
module_exit()¶
定義於 include/linux/module.h
此宏定義了在模組移除時(或者,如果是編譯到核心中的檔案,則永遠不會)呼叫的函式。 只有當模組使用計數達到零時,才會呼叫它。 此函式也可以休眠,但不能失敗:在返回之前,必須清理所有內容。
請注意,此宏是可選的:如果它不存在,則您的模組將不可移除(除了 ‘rmmod -f’)。
try_module_get()/ module_put()¶
定義於 include/linux/module.h
這些函式操作模組使用計數,以防止被移除(如果另一個模組使用其匯出的符號之一,則模組也無法移除:見下文)。 在呼叫模組程式碼之前,您應該對該模組呼叫 try_module_get():如果它失敗,則該模組正在被移除,您應該像它不存在一樣行事。 否則,您可以安全地進入模組,並在完成時呼叫 module_put()。
大多數可註冊的結構體都有一個所有者欄位,例如在 struct file_operations 結構體中。 將此欄位設定為宏 THIS_MODULE。
等待佇列 include/linux/wait.h¶
[睡眠]
等待佇列用於等待某人在某個條件為真時喚醒您。 必須小心使用它們以確保沒有競爭條件。 您宣告一個 wait_queue_head_t,然後想要等待該條件的程序宣告一個引用自身的 wait_queue_entry_t,並將其放入佇列中。
宣告¶
您可以使用 DECLARE_WAIT_QUEUE_HEAD() 宏或在初始化程式碼中使用 init_waitqueue_head() 例程來宣告一個 wait_queue_head_t。
排隊¶
將自己放入等待佇列相當複雜,因為您必須在檢查條件之前將自己放入佇列中。 有一個宏可以做到這一點: wait_event_interruptible() (include/linux/wait.h) 第一個引數是等待佇列頭,第二個引數是一個被評估的表示式; 當該表示式為真時,該宏返回 0,如果收到訊號,則返回 -ERESTARTSYS。 wait_event() 版本忽略訊號。
喚醒排隊的任務¶
呼叫 wake_up() (include/linux/wait.h),它將喚醒佇列中的每個程序。 除非其中一個程序設定了 TASK_EXCLUSIVE,在這種情況下,佇列的其餘部分將不會被喚醒。 在同一標頭檔案中還有其他可用的基本函式變體。
原子操作¶
某些操作保證在所有平臺上都是原子的。 第一類操作作用於 atomic_t (include/asm/atomic.h); 它包含一個有符號整數(至少 32 位長),您必須使用這些函式來操作或讀取 atomic_t 變數。 atomic_read() 和 atomic_set() 獲取和設定計數器, atomic_add(), atomic_sub(), atomic_inc(), atomic_dec(), 和 atomic_dec_and_test()(如果遞減到零則返回 true)。
是的。如果原子變數為零,則返回 true(即 != 0)。
請注意,這些函式比普通算術運算慢,因此不應不必要地使用。
第二類原子操作是對 unsigned long 的原子位操作,定義於 include/linux/bitops.h。 這些操作通常採用指向位模式的指標和一個位號:0 是最低有效位。 set_bit(), clear_bit() 和 change_bit() 設定、清除和翻轉給定的位。 test_and_set_bit(), test_and_clear_bit() 和 test_and_change_bit() 做同樣的事情,除了如果該位之前已設定則返回 true; 這些對於原子設定標誌特別有用。
可以使用大於 BITS_PER_LONG 的位索引呼叫這些操作。 但是,在大端平臺上,由此產生的行為很奇怪,因此最好不要這樣做。
符號¶
在核心內部,應用正常的連結規則(即,除非使用 static 關鍵字將符號宣告為檔案作用域,否則可以在核心中的任何位置使用)。 但是,對於模組,保留了一個特殊的匯出符號表,該表限制了核心內部的入口點。 模組也可以匯出符號。
EXPORT_SYMBOL()¶
定義於 include/linux/export.h
這是匯出符號的經典方法:動態載入的模組將能夠像往常一樣使用該符號。
EXPORT_SYMBOL_GPL()¶
定義於 include/linux/export.h
類似於 EXPORT_SYMBOL(),除了由 EXPORT_SYMBOL_GPL() 匯出的符號只能被具有指定 GPLv2 相容許可證的 MODULE_LICENSE() 的模組看到。 這意味著該函式被認為是內部實現問題,而不是真正的介面。 但是,一些維護人員和開發人員在新增任何新 API 或功能時可能需要 EXPORT_SYMBOL_GPL()。
EXPORT_SYMBOL_NS()¶
定義於 include/linux/export.h
這是 EXPORT_SYMBOL() 的變體,允許指定符號名稱空間。 符號名稱空間記錄在 符號名稱空間 中
EXPORT_SYMBOL_NS_GPL()¶
定義於 include/linux/export.h
這是 EXPORT_SYMBOL_GPL() 的變體,允許指定符號名稱空間。 符號名稱空間記錄在 符號名稱空間 中
例程和約定¶
雙鏈表 include/linux/list.h¶
核心標頭檔案中曾經有三組連結串列例程,但這一組是贏家。 如果您對單個列表沒有一些特別迫切的需求,這是一個不錯的選擇。
特別是,list_for_each_entry() 很有用。
返回約定¶
對於在使用者上下文中呼叫的程式碼,通常會違反 C 約定,並返回 0 表示成功,返回負錯誤碼(例如 -EFAULT)表示失敗。 這起初可能不直觀,但它在核心中相當普遍。
使用 ERR_PTR() (include/linux/err.h) 將負錯誤碼編碼到指標中,並使用 IS_ERR() 和 PTR_ERR() 再次將其取出:避免了用於錯誤碼的單獨指標引數。 令人討厭,但以一種好的方式。
打破編譯¶
Linus 和其他開發人員有時會在開發核心中更改函式或結構名稱; 這不僅僅是為了讓每個人都保持警惕:它反映了一個根本性的變化(例如,不能再在啟用中斷的情況下呼叫,或者執行額外的檢查,或者不執行之前捕獲的檢查)。 通常,這會伴隨著對相應的核心開發郵件列表的相當完整的註釋; 搜尋存檔。 簡單地對檔案進行全域性替換通常會使事情更糟。
初始化結構成員¶
初始化結構體的首選方法是使用 ISO C99 定義的指定初始化器,例如
static struct block_device_operations opt_fops = {
.open = opt_open,
.release = opt_release,
.ioctl = opt_ioctl,
.check_media_change = opt_media_change,
};
這使得它很容易被 grep 搜尋到,並且清楚地表明瞭哪些結構體欄位被設定。 您應該這樣做,因為它看起來很酷。
GNU 擴充套件¶
Linux 核心中明確允許使用 GNU 擴充套件。 請注意,由於缺乏通用用途,某些更復雜的擴充套件支援不好,但以下擴充套件被認為是標準的(有關更多詳細資訊,請參閱 GCC 資訊頁面上的“C 擴充套件”部分 - 是的,實際上是資訊頁面,手冊頁只是資訊中內容的簡短摘要)。
行內函數
語句表示式(即 ({ 和 }) 構造)。
宣告函式/變數/型別的屬性 (__attribute__)
typeof
零長度陣列
宏可變引數
void 指標的算術運算
非常量初始化器
彙編指令(不在 arch/ 和 include/asm/ 之外)
函式名作為字串 (__func__)。
__builtin_constant_p()
在核心中使用 long long 時要小心,gcc 為其生成的程式碼很糟糕,更糟糕的是:除法和乘法在 i386 上不起作用,因為 GCC 執行時函式從核心環境中丟失了。
C++¶
在核心中使用 C++ 通常不是一個好主意,因為核心沒有提供必要的執行時環境,並且未針對它測試包含檔案。 這仍然是可能的,但不建議這樣做。 如果您真的想這樣做,至少忘記異常。
#if¶
通常認為在標頭檔案(或 .c 檔案的頂部)中使用宏來抽象函式比在整個原始碼中使用 `#if` 預處理器語句更乾淨。
將您的內容放入核心¶
為了使您的內容成形以便正式包含,甚至為了製作一個整潔的補丁,需要完成管理工作
找出您一直在修改的程式碼的所有者。 檢視原始檔頂部、
MAINTAINERS檔案內部,以及最後的CREDITS檔案。 您應該與這些人協調以確保您沒有重複工作,或者嘗試已經被拒絕的事情。確保將您的姓名和電子郵件地址放在您建立或顯著修改的任何檔案的頂部。 這是人們發現錯誤或當他們想要進行更改時會首先檢視的地方。
通常您希望為您的核心駭客準備一個配置選項。 編輯相應目錄中的
Kconfig。 透過剪下和貼上可以輕鬆使用 Config 語言,並且在Documentation/kbuild/kconfig-language.rst中有完整的文件。在您對該選項的描述中,請確保同時向專家使用者和對您的功能一無所知的使用者說明。 在此處提及不相容性和問題。 絕對以“如果懷疑,請說 N”(或者,偶爾,`Y`)結束您的描述; 這是為那些不知道你在說什麼的人準備的。
編輯
Makefile:CONFIG 變數在此處匯出,因此您通常只需新增一行“obj-$(CONFIG_xxx) += xxx.o”。 語法記錄在Documentation/kbuild/makefiles.rst中。如果您認為您所做的事情值得注意,通常超出單個檔案(無論如何您的姓名應該在原始檔的頂部),請將您自己放入
CREDITS中。MAINTAINERS意味著您希望在對子系統進行更改時諮詢您,並瞭解錯誤; 它意味著對程式碼的某些部分進行比傳遞性承諾更多的承諾。最後,不要忘記閱讀
Documentation/process/submitting-patches.rst
核心小技巧¶
一些來自瀏覽原始碼的最愛。 隨意新增到此列表。
arch/x86/include/asm/delay.h:
#define ndelay(n) (__builtin_constant_p(n) ? \
((n) > 20000 ? __bad_ndelay() : __const_udelay((n) * 5ul)) : \
__ndelay(n))
include/linux/fs.h:
/*
* Kernel pointers have redundant information, so we can use a
* scheme where we can return either an error code or a dentry
* pointer with the same return value.
*
* This should be a per-architecture thing, to allow different
* error and pointer decisions.
*/
#define ERR_PTR(err) ((void *)((long)(err)))
#define PTR_ERR(ptr) ((long)(ptr))
#define IS_ERR(ptr) ((unsigned long)(ptr) > (unsigned long)(-1000))
arch/x86/include/asm/uaccess_32.h:
#define copy_to_user(to,from,n) \
(__builtin_constant_p(n) ? \
__constant_copy_to_user((to),(from),(n)) : \
__generic_copy_to_user((to),(from),(n)))
arch/sparc/kernel/head.S:
/*
* Sun people can't spell worth damn. "compatability" indeed.
* At least we *know* we can't spell, and use a spell-checker.
*/
/* Uh, actually Linus it is I who cannot spell. Too much murky
* Sparc assembly will do this to ya.
*/
C_LABEL(cputypvar):
.asciz "compatibility"
/* Tested on SS-5, SS-10. Probably someone at Sun applied a spell-checker. */
.align 4
C_LABEL(cputypvar_sun4m):
.asciz "compatible"
arch/sparc/lib/checksum.S:
/* Sun, you just can't beat me, you just can't. Stop trying,
* give up. I'm serious, I am going to kick the living shit
* out of you, game over, lights out.
*/
感謝¶
感謝 Andi Kleen 提出這個想法、回答我的問題、糾正我的錯誤、填寫內容等。感謝 Philipp Rumpf 提供的更多拼寫和清晰度修復,以及一些出色的非顯而易見的觀點。 感謝 Werner Almesberger 提供的關於 disable_irq() 的精彩總結,以及 Jes Sorensen 和 Andrea Arcangeli 新增的警告。 感謝 Michael Elizabeth Chastain 檢查和新增到 Configure 部分。 感謝 Telsa Gwynne 教我 DocBook。