【51CTO精選譯文】這年頭,Linux成了一個時髦詞,。自詡對電腦玩的精通的學(xué)生和IT人士們,,沒有哪個不在自己的電腦上安裝一、兩個Linux,,并自覺趕上了時髦,。然而,在Ubuntu或SUSE的論壇中,,經(jīng)常有這樣的對話: “你學(xué)Linux學(xué)了這么久,,都學(xué)到了什么?” “哦,,我現(xiàn)在Linux的安裝,、升級、桌面美化都很熟練,!你看我這是最新版的Ubuntu,,桌面很漂亮吧!” “……” Linux社區(qū)中有一句名言:如果你進入你的操作系統(tǒng)不知道該做什么,,那最好還是關(guān)掉電腦,,一定有更重要的事等著你去做。說真的,,如果對Linux命令不熟練,,真的不能算是學(xué)過Linux。然而另一方面,,Linux內(nèi)核雖然是一般用戶可學(xué)可不學(xué)的內(nèi)容,,但可以說卻是Linux操作系統(tǒng)中最好玩的部分。尤其對于開發(fā)者而言,,Linux內(nèi)核開發(fā)絕對是最理想的磨練場所,。51CTO編輯一直認為,,國外之所以IT技術(shù)大拿林立,和他們從小接觸類UNIX系統(tǒng),、把玩內(nèi)核開發(fā)是脫不了關(guān)系的,。 下面是Linux內(nèi)核開發(fā)者Robert Love寫的一篇入門文章,號稱“包教會”,,推薦對Linux內(nèi)核開發(fā)感興趣的學(xué)生、Linux愛好者,、開發(fā)者以及系統(tǒng)管理員們一定不要錯過,。當(dāng)然,雖然標(biāo)題說是包教會,,你可能需要一定的Linux命令以及C語言的基礎(chǔ),。 以下是正文內(nèi)容: Linux內(nèi)核一直都被視為學(xué)習(xí)Linux最難的一塊,相信大家也一定看過不少關(guān)于內(nèi)核的文章,,但捫心自問,,你現(xiàn)在究竟掌握了多少?本文將從零開始介紹被視為高深的Linux內(nèi)核,,內(nèi)容涉及內(nèi)核源代碼的下載,,編譯,安裝,,以及內(nèi)核開發(fā)相關(guān)的內(nèi)容,。 如何獲取Linux內(nèi)核源代碼 下載Linux內(nèi)核當(dāng)然要去官方網(wǎng)站了,網(wǎng)站提供了兩種文件下載,,一種是完整的Linux內(nèi)核,,另一種是內(nèi)核增量補丁,它們都是tar歸檔壓縮包,。除非你有特別的原因需要使用舊版本的Linux內(nèi)核,,否則你應(yīng)該總是升級到最新版本。 使用Git 由Linus領(lǐng)頭的內(nèi)核開發(fā)隊伍從幾年前就開始使用Git版本控制系統(tǒng)管理Linux內(nèi)核了(參考閱讀:什么是Git,?),,而Git項目本身也是由Linus創(chuàng)建的,它和傳統(tǒng)的CVS不一樣,,Git是分布式的,,因此它的用法和工作流程很多開發(fā)人員可能會感到很陌生,但我強烈建議使用Git下載和管理Linux內(nèi)核源代碼,。 你可以使用下面的Git命令獲取Linus內(nèi)核代碼樹的最新“推送”版本: $ git clone git://git./pub/scm/linux/kernel/git/torvalds/linux-2.6.git 然后使用下面的命令將你的代碼樹與Linus的代碼樹最新狀態(tài)同步: $ git pull 安裝內(nèi)核源代碼 內(nèi)核包有GNU zip(gzip)和bzip2格式,。Bzip2是默認和首選格式,因為它的壓縮比通常比gzip更好,,bzip2格式的Linux內(nèi)核包一般采用linux-x.y.z.tar.bz2形式的文件名,,這里的x.y.z是內(nèi)核源代碼的具體版本號,下載到源代碼包后,解壓和抽取就很簡單了,,如果你下載的是bzip2包,,運行: $ tar xvjf linux-x.y.z.tar.bz2 如果你下載的是gzip包,則運行: $ tar xvzf linux-x.y.z.tar.gz 無論執(zhí)行上面哪一個命令,,最后都會將源代碼解壓和抽取到linux-x.y.z目錄下,,如果你使用Git下載和管理內(nèi)核源代碼,你不需要下載tar包,,只需要運行g(shù)it clone命令,,它就會自動下載和解壓。 內(nèi)核源代碼通常都會安裝到/usr/src/linux下,,但在開發(fā)的時候最好不要使用這個源代碼樹,,因為針對你的C庫編譯的內(nèi)核版本通常也鏈接到這里的。 應(yīng)用補丁 Linux內(nèi)核開發(fā)人員會將自己的修改做成補丁與其它人員分享,,而且補丁是增量的,,增量補丁是從一個內(nèi)核樹移動到另一個內(nèi)核樹的有效方法,不用下載完整的內(nèi)核包就可以升級內(nèi)核,,不僅可節(jié)省帶寬,,也節(jié)省了內(nèi)核升級時間,應(yīng)用補丁之前先進入內(nèi)核源代碼樹所在目錄,,然后運行: $ patch –p1 < ../patch-x.y.z 注意,,補丁包也有明確的版本號,這里的版本號與Linux內(nèi)核源代碼的版本號要一致,,內(nèi)核和補丁版本號不一致時,,強制應(yīng)用補丁會引起意想不到的后果。 內(nèi)核源代碼樹介紹 內(nèi)核源代碼樹分為許多目錄,,它們下面又包含許多子目錄,,源代碼樹的頂級目錄及其描述參見下表。
在源代碼樹的根目錄下還有很多文件需要說明,COPYING是內(nèi)核許可描述文件(即GNU GPL v2),,CREDITS是參與Linux內(nèi)核的開發(fā)人員名單,,MAINTAINERS列出了維護各個子系統(tǒng)和驅(qū)動的個人,Makefile是內(nèi)核Makefile的基礎(chǔ),。
生成內(nèi)核 生成內(nèi)核其實很簡單,,甚至比編譯和安裝其它系統(tǒng)級組件,如glibc還要簡單,,從2.6版本開始,,Linux內(nèi)核引入了一個新的配置和生成系統(tǒng),,它使生產(chǎn)內(nèi)核的操作變得更加簡單了。 配置內(nèi)核 既然已經(jīng)拿到內(nèi)核源代碼,,那我們在開始編譯前就可以根據(jù)需要自行配置和定制,,可以編譯你指定的功能和想要的驅(qū)動,配置內(nèi)核是生成內(nèi)核必須的一步,,因為內(nèi)核提供了大量的功能,,支持各種不同的硬件,有很多都需要配置,,內(nèi)核配置是由配置選項控制的,,配置選項都有CONFIG前綴,例如,,對稱多處理(SMP)是由CONFIG_SMP配置選項配置的,如果設(shè)置了這個選項,,SMP就被啟用了,,反之則被禁用,配置選項可以確定會生成哪個文件,,也可以通過預(yù)處理指令操控代碼,。 配置選項可以控制生成過程要么是布爾型,要么是三態(tài)型,,布爾型就是“是”或“否”,,大部分內(nèi)核配置選項都屬于布爾型,如CONFIG_PREEMPT,,而三態(tài)型則在“是”和“否”的基礎(chǔ)上,,又增加一個“模塊”選項,模塊選項表示配置選項被設(shè)置了,,但最后會編譯成模塊,,而不是直接編譯進內(nèi)核,模塊可以理解為可獨立動態(tài)載入的對象,,一般來說,,驅(qū)動配置通常都是三態(tài)型。 配置選項也可以是字符串或整數(shù),,這樣的選項不會控制生成過程,,指定的值由內(nèi)核源代碼訪問預(yù)處理宏時使用,例如,,可以為某個配置選項指定靜態(tài)分配數(shù)組的大小,。 Linux廠商也會隨發(fā)行版提供預(yù)編譯的內(nèi)核,如Canonical為Ubuntu,,或Red Hat為Fedora提供的內(nèi)核,,這樣的內(nèi)核通常只啟用了需要的內(nèi)核功能,,幾乎所有驅(qū)動都被編譯成模塊了,這樣的內(nèi)核提供了一個良好的基礎(chǔ)內(nèi)核和廣泛的硬件模塊支持,,無論如何,,想要成為內(nèi)核高手,你應(yīng)該編譯自己的內(nèi)核,。 值得慶幸的是,,內(nèi)核提供了很多工具簡化配置 ,最簡單的工具是基于文本命令行的實用程序,,如: $ make config 這個工具會一個選項一個選項地配置,,但用戶需要參與,如指定“是(y)”,,“否(m)”還是“模塊(m)”,,整個配置過程需要很長的時間,因此,,除非是有人按小時計費請你升級內(nèi)核,,實在找不出別的理由用這種最原始的方法配置內(nèi)核了,相反,,有現(xiàn)成的基于ncurses的圖形化工具可以代替,。 $ make menuconfig 或是基于gtk+的圖形化工具 $ make gconfig 上述三個工具都將配置選項分成多個類別,如“處理器類型和特征”,,你可以在這些類別上來回移動,,查看內(nèi)核選項,當(dāng)然也可以修改它們的設(shè)置了,。 下面這個命令會根據(jù)你的架構(gòu)創(chuàng)建一個默認的配置基礎(chǔ),。 $ make defconfig 雖然默認配置有些武斷(在i386上,默認配置是由Linus配置的),,但如果你從未配置過內(nèi)核,,它提供了一個良好的開端。 配置選項存儲在源代碼樹根目錄下一個名叫.config的文件中,,你可以打開這個文件手工編輯其中的配置選項,,修改后或要在新的內(nèi)核源代碼樹上應(yīng)用現(xiàn)有配置文件,你可以使用下面的命令驗證和更新配置: $ make oldconfig 在生成內(nèi)核之前必須運行這個命令,。 配置選項CONFIG_IKCONFIG_PROC指定了完整的內(nèi)核配置文件壓縮包位置,,默認是/proc/config.gz,這樣在生成新內(nèi)核時要克隆現(xiàn)有的配置就變得非常簡單了,。如果你當(dāng)前的內(nèi)核開啟了這個選項,,你可以從/proc拷貝該配置文件,然后在此基礎(chǔ)上生成新的內(nèi)核: $ zcat /proc/config.gz > .config $ make oldconfig 內(nèi)核配置好后,,使用下面的命令進行生成: $ make 和2.6以前的內(nèi)核不一樣,,在生成內(nèi)核前不再需要執(zhí)行make dep命令了,,依賴樹會自動維護,也不需要再指定特定的生成類型,,如bzImage,,或獨立生成模塊,默認Makefile規(guī)則會自動處理好一切,。 將干擾信息最小化 在生成過程中會遭到警告和錯誤的干擾,。最小化干擾信息的一個訣竅是重定向make的輸出,但仍然會看到一些警告和錯誤: $ make > ../detritus 如果你想查看生成輸出,,你可以事后閱讀這個文件,,如果你完全不想看到任何輸出,那么就重定向到/dev/null: $ make > /dev/null 同時執(zhí)行多個生成作業(yè) Make命令提供了一個功能可以將生成過程拆分成多個平行的作業(yè),,這些作業(yè)可以獨立運行,,也可以并行運行,在多處理器系統(tǒng)上可以極大地提高生成速度,,也提高了處理器利用率,,因為生成大型源代碼樹會出現(xiàn)大量的I/O等待時間。 默認情況下,,make只能拆分成一個作業(yè),,因為Makefiles常常會包含不正確的依賴信息,,如果真是這樣,,多個并行執(zhí)行的作業(yè)將會引起混亂,最終會導(dǎo)致生成過程失敗,,如果Makefiles中的依賴信息無誤,,那么完全可以拆分成多個作業(yè)執(zhí)行,如: $ make –jn 這里的n表示拆分的作業(yè)數(shù)量,,通常按每個處理器拆分成1-2個作業(yè),,例如,在一個16核心的機器上 ,,你可以運行: $ make -j32 > /dev/null 使用distcc或ccache等優(yōu)秀的工具也可以大大提高生成速度,。
安裝新內(nèi)核 內(nèi)核生成好之后,你需要安裝它,,如何安裝于系統(tǒng)架構(gòu)和引導(dǎo)加載程序有關(guān),,我們以x86架構(gòu),grub引導(dǎo)加載程序為例進行說明,。 首先將arch/i386/boot/bzImage拷貝到/boot,,重命名為vmlinuz- version,這里的version也是版本號,,然后編輯/boot/grub/grub.conf,,為新內(nèi)核添加相應(yīng)的項目,,如果是使用LILO引導(dǎo)裝載程序,則修改/etc/lilo.conf文件,,然后運行l(wèi)ilo,。 模塊的安裝與系統(tǒng)架構(gòu)無關(guān),都是自動完成的,,以root用戶運行: % make modules_install 這個命令會將所有編譯好的模塊安裝到/lib/modules下對應(yīng)的子目錄中,。 生成過程會在源代碼樹根目錄下創(chuàng)建一個System.map文件,它包含一個符號查找表,,映射內(nèi)核符號到它們的起始地址,,在調(diào)試期間可以用它將內(nèi)存地址轉(zhuǎn)換成函數(shù)和變量名。 可能會遇到的問題 與普通用戶空間的應(yīng)用程序相比,,Linux內(nèi)核有多個特殊的屬性,,下面是我認為最重要的一些不同: ◆內(nèi)核既不訪問C庫也不訪問標(biāo)準C頭; ◆內(nèi)核是用GNU C編碼的,; ◆內(nèi)核缺少用戶空間提供的內(nèi)存保護,; ◆內(nèi)核不能容易地執(zhí)行浮點運算; ◆內(nèi)核有一個小型的固定大小的進程堆棧,; ◆由于內(nèi)核支持異步中斷和SMP,,因此同步和并發(fā)是內(nèi)核主要擔(dān)心的問題; ◆可移植性也很重要,。 下面我們就逐個來了解一下這些問題,,所有內(nèi)核開發(fā)人員都必須記住它們。 無libc或標(biāo)準頭 和用戶空間應(yīng)用程序不一樣,,內(nèi)核并沒有鏈接到標(biāo)準的C庫,,也沒有鏈接到任何其它的庫,這樣設(shè)計的原因有很多,,包括如先有雞還是先有蛋的問題,,但主要原因還是速度和內(nèi)核大小,不要說完整的C庫,,就是它的一個子集也夠大,,內(nèi)核太大只會導(dǎo)致效率低下。 不要擔(dān)心,,許多常用的libc函數(shù)都在內(nèi)核中實現(xiàn)了,,例如,常見的字符串操作函數(shù)就位于lib/string.c中,,只需要包括它的頭文件<linux/string.h >就可以了,。 這里的頭文件指的是內(nèi)核源代碼樹中的頭文件,內(nèi)核也只能使用樹內(nèi)的頭文件,,基礎(chǔ)文件位于源代碼根目錄的include/目錄下,,例如,,<linux/inotify.h>頭文件就位于include/linux/inotify.h。 與架構(gòu)相關(guān)的頭文件則位于arch/<architecture>/include/asm,,例如,,如果在x86架構(gòu)下編譯,與你架構(gòu)相關(guān)的文件就是arch/x86/include/asm,,只需要在引用這些頭的地方加上asm/前綴即可,,如<asm/ioctl.h>。 漏掉的大部分都是類似printf()這樣的函數(shù),,內(nèi)核不會使用printf(),,但它提供了printk()函數(shù),其表現(xiàn)絕不比printf()差,,printk()會拷貝格式化的字符串到內(nèi)核日志緩沖區(qū),,syslog程序就是從這里讀取信息的,其用法也和printf()類似: printk("Hello world! A string '%s' and an integer '%d'\n", str, i); printf()和printk()之間最大的不同是,,printk()允許你指定一個優(yōu)先級標(biāo)記,,syslogd使用這個標(biāo)記確定在哪里顯示內(nèi)核消息,下面是一個使用優(yōu)先級標(biāo)記的示例: printk(KERN_ERR "this is an error!\n"); 注意在KERN_ERR和打印的消息之間沒有逗號,,這是故意這么設(shè)計的,,優(yōu)先級使用一個預(yù)定義的字符定義,在編譯期間它與打印的信息是串聯(lián)的,。 GNU C 和許多Unix內(nèi)核類似,,Linux內(nèi)核也是用C編寫的,但也許會讓人很意外,,內(nèi)核不是用嚴謹?shù)腁NSI C編寫的,,內(nèi)核開發(fā)人員用的卻是gcc(GNU編譯器集,,包含了編譯內(nèi)核和Linux C程序的C編譯器)中的各種語言擴展,。 內(nèi)核開發(fā)人員同時使用了C語言的ISO C99和GNU C擴展,這些變化讓Linux內(nèi)核與gcc結(jié)合得更緊密,,但最近又出現(xiàn)了一個編譯器 – 英特爾的C編譯器 – 也對gcc的功能支持得相當(dāng)好,,因此也可以用它來編譯Linux內(nèi)核。最低支持的gcc版本是3.2,,建議采用gcc 4.4或更高的版本編譯,。使用ISO C99擴展也是可以的,因為C99是C語言的官方版本,。 內(nèi)聯(lián)函數(shù) C99和GNU C都支持內(nèi)聯(lián)函數(shù),,內(nèi)聯(lián)函數(shù)是直接插入到每個函數(shù)調(diào)用的位置的,消除了函數(shù)調(diào)用和返回的開銷,,允許進一步優(yōu)化,,因為編譯器可以同時優(yōu)化調(diào)用者和被調(diào)用函數(shù),,但它也有缺點,代碼大小會增加,,因為函數(shù)的內(nèi)容被直接復(fù)制到所調(diào)用者內(nèi)部了,,因此也會增加內(nèi)存消耗和指令緩存空間。內(nèi)核開發(fā)人員一般在小型時間很關(guān)鍵的函數(shù)中才會使用內(nèi)聯(lián)函數(shù),。 定義函數(shù)時,,使用static和inline關(guān)鍵字聲明內(nèi)聯(lián)函數(shù),例如: static inline void wolf(unsigned long tail_size) 函數(shù)必須先聲明后使用,,否則編譯器就不能使函數(shù)內(nèi)聯(lián),,一般做法是將內(nèi)聯(lián)函數(shù)放在頭文件中,因為它們被標(biāo)記為static,,不會創(chuàng)建輸出函數(shù),,如果內(nèi)聯(lián)函數(shù)僅在一個文件中使用,可以放在該文件的頂部,。 在內(nèi)核中,,與復(fù)雜的宏相比,出于安全和可讀性方面考慮,,內(nèi)聯(lián)函數(shù)是首選,。 內(nèi)聯(lián)匯編 Gcc C編譯器允許在C函數(shù)中嵌入?yún)R編指令,asm()編譯器指令用于內(nèi)聯(lián)匯編代碼,,例如,,這個內(nèi)聯(lián)匯編指令執(zhí)行x86處理器的rdtsc指令,返回時間戳寄存器(tsc)的值: unsigned int low, high; Linux內(nèi)核是用C和匯編語言混合編寫的,,與底層硬件相關(guān)的代碼很多都是用匯編語言寫的,,剩下的大部分內(nèi)核代碼都是直接用C編寫的。 分支注解 Gcc C編譯器內(nèi)置了一個指令優(yōu)化條件分支,,內(nèi)核將這個打包成易于使用的宏 - likely()和unlikely(),。 先看下面這樣的if語句: if (error) { /* ... */ } 將這個分支標(biāo)記為非常不可能采用 /* we predict 'error' is nearly always zero ... */ if (unlikely(error)) { /* ... */ } 相反,將這個分支標(biāo)記為非??赡懿捎?/p> /* we predict 'success' is nearly always nonzero ... */ if (likely(success)) { /* ... */ } 當(dāng)分支指令已經(jīng)知道一個優(yōu)先級,,或你想在一種情況下優(yōu)化另一種情況時應(yīng)該使用上述指令,最重要的是,,當(dāng)分支正確標(biāo)記時,,這些指令會提升性能,但如果分支標(biāo)記錯誤則會降低性能,,在內(nèi)核代碼中,,unlikely()要使用得更多,因為if語句傾向于表示一種特殊情況。 無內(nèi)存保護 當(dāng)用戶空間的應(yīng)用程序嘗試一個非法的內(nèi)存訪問時,,內(nèi)核可以捕捉到錯誤,,發(fā)送SIGSEGV信號,殺掉進程,,如果內(nèi)核嘗試一個非法的內(nèi)存訪問時,,結(jié)果就不受控制了,因為誰也無法去控制內(nèi)核,,這也是內(nèi)核最主要的失誤,。 此外,內(nèi)核內(nèi)存也是不可分頁的,,因此你消耗的每個內(nèi)存字節(jié)都比物理內(nèi)存的一個字節(jié)要少,。 不能(容易)使用浮點數(shù) 當(dāng)用戶空間進程使用浮點指令時,內(nèi)核要負責(zé)處理從整型到浮點模式的轉(zhuǎn)換,。 與用戶空間不一樣,,內(nèi)核不能無縫支持浮點數(shù),因為它自己不能輕易地捕捉到自己,,在內(nèi)核中使用浮點數(shù)需要手動保存和恢復(fù)浮點數(shù)寄存器,,因此除非卻有必要,否則盡量不要在內(nèi)核中做浮點運算,。 小型,,固定大小的堆棧 用戶空間可以靜態(tài)分配許多不同的堆棧,包括巨型結(jié)構(gòu)和千元數(shù)組,,這個行為是合法的,,因為用戶空間有很大的堆棧,并可以動態(tài)增長,。 內(nèi)核堆棧不大也不是動態(tài)的,,相反,它很小且是固定的,,內(nèi)核堆棧的精確大小根據(jù)架構(gòu)有所不同,,在x86上,堆棧大小是在編譯時確定的,,一般是4KB或8KB,,歷史上,,內(nèi)核堆棧有2頁,,通常表示它處于32位架構(gòu)上,大小是8KB,,如果是16KB就表示是64位架構(gòu),,總之大小是固定的,每個進程接收它自己的堆棧。 同步和并發(fā) 內(nèi)核最容易受競爭條件影響,,和一個單線程的用戶空間應(yīng)用程序不一樣,,有許多內(nèi)核特性允許同時訪問共享資源,因此需要同步以防止競爭,,特別是: ◆Linux是一種搶占式多任務(wù)操作系統(tǒng),,進程是由內(nèi)核的進程調(diào)度器隨意調(diào)度和再次調(diào)度的,內(nèi)核必須在這些任務(wù)之間同步,; ◆Linux支持對稱多處理(SMP),,因此,如果沒有適當(dāng)?shù)谋Wo,,在兩個或多個處理器上同時執(zhí)行的內(nèi)核代碼可能會同時訪問相同的資源,; ◆中斷是異步發(fā)生的,因此,,如果沒有適當(dāng)?shù)谋Wo,,在訪問資源期間也可能發(fā)生中斷,中斷處理程序可能就會訪問到相同的資源,; ◆Linux是有優(yōu)先權(quán)的,,因此,如果沒有適當(dāng)?shù)谋Wo,,內(nèi)核代碼可能會優(yōu)先執(zhí)行,,訪問其它代碼正在使用的資源。 解決這些問題的一般方法是自旋鎖和信號量,。 可移植性的重要性 雖然用戶空間應(yīng)用程序一般不會太重視可移植性,,但Linux的確是一個可移植性操作系統(tǒng),應(yīng)該保持一致,,這意味著與架構(gòu)無關(guān)的C代碼必須在大量的系統(tǒng)上正確地編譯和運行,,與架構(gòu)相關(guān)的代碼必須在內(nèi)核源代碼樹中使用特定的目錄分隔開。 總結(jié) 可以肯定,,內(nèi)核有它獨特的性質(zhì),,它有它自己的一些原則,不過,,內(nèi)核的復(fù)雜性和障礙與其它大型軟件項目相比,,并沒有什么大的不同,Linux開發(fā)道路上最重要的一步是認識到內(nèi)核并不可怕,,不熟悉,?當(dāng)然!不可逾越,?當(dāng)然不是,! |
|