Linux操作系統(tǒng)是UNIX操作系統(tǒng)的一種克隆系統(tǒng)个束,誕生于1991年10月5日(第一次正式向外公布的時(shí)間)。Linux操作系統(tǒng)的誕生渣叛、發(fā)展和成長過程依賴者5個(gè)重要支柱:UNIX操作系統(tǒng)丈探、Minix操作系統(tǒng)、GNU計(jì)劃桦卒、Posix標(biāo)準(zhǔn)和Internet立美。
一、Linux內(nèi)核的組成
1.1 Linux內(nèi)核源碼目錄結(jié)構(gòu)
本書范例程序基于Linux 2.6.28.6內(nèi)核源碼方灾,其目錄結(jié)構(gòu)如下:
- arch:包含和硬件體系結(jié)構(gòu)相關(guān)的代碼建蹄,每種平臺(tái)占一個(gè)相應(yīng)的目錄,如i386裕偿、arm洞慎、powerpc、mips等嘿棘;
- block:塊設(shè)備驅(qū)動(dòng)程序I/O調(diào)度劲腿;
- crypto:常用加密和散列算法(如AES、SHA等)鸟妙,還有一些壓縮和CRC校驗(yàn)算法焦人;
- Documentation:內(nèi)核各部分的通用解釋和注釋;
- drivers:設(shè)備驅(qū)動(dòng)程序重父,每個(gè)不同的驅(qū)動(dòng)占用一個(gè)子目錄花椭,如char、block房午、net矿辽、mtd、i2c等郭厌;
- fs:支持的各種文件系統(tǒng)袋倔,如EXT、FAT折柠、NTFS宾娜、JFFS2等;
- include:頭文件液走,與系統(tǒng)相關(guān)的頭文件被放置在include/linux子目錄下碳默;
- init:內(nèi)核初始化代碼;
- ipc:進(jìn)程間通信的代碼缘眶;
- kernel:內(nèi)核的最核心部分嘱根,包括進(jìn)程調(diào)度、定時(shí)器等巷懈,而和平臺(tái)相關(guān)的一部分代碼放在arch/*/kernel目錄下该抒;
- lib:庫文件代碼;
- mm:內(nèi)存管理代碼顶燕,和平臺(tái)相關(guān)的一部分代碼放在arch/*/mm目錄下凑保;
- net:網(wǎng)絡(luò)相關(guān)代碼冈爹,實(shí)現(xiàn)了各種常見的網(wǎng)絡(luò)協(xié)議;
- scripts:用于配置內(nèi)核的腳本文件欧引;
- security:主要是一個(gè)SELinux的模塊频伤;
- sound:ALSA、OSS音頻設(shè)備的驅(qū)動(dòng)核心代碼和常用設(shè)備驅(qū)動(dòng)芝此;
- usr:實(shí)現(xiàn)了用于打包和壓縮的cpio等憋肖;
1.2 Linux內(nèi)核的組成部分
如圖1.1所示,Linux內(nèi)核主要由進(jìn)程調(diào)度(SCHED)婚苹、內(nèi)存管理(MM)岸更、虛擬文件系統(tǒng)(VFS)、網(wǎng)絡(luò)接口(NET)和進(jìn)程間通信(IPC)5個(gè)子系統(tǒng)組成膊升。
圖1.1 Linux內(nèi)核的組成部分與關(guān)系
1.2.1 進(jìn)程調(diào)度
進(jìn)程調(diào)度控制系統(tǒng)中的多個(gè)進(jìn)程對(duì)CPU的訪問怎炊,使得多個(gè)進(jìn)程能在CPU中“微觀串行,宏觀并行”地執(zhí)行廓译。進(jìn)程調(diào)度處于系統(tǒng)的重心位置评肆,內(nèi)核中其他子系統(tǒng)都依賴它,因?yàn)槊總€(gè)子系統(tǒng)都需要掛起或恢復(fù)進(jìn)程责循。
圖1.2 Linux進(jìn)程狀態(tài)轉(zhuǎn)換
如圖1.2所示糟港,Linux的進(jìn)程在幾個(gè)狀態(tài)間進(jìn)行切換,在設(shè)備驅(qū)動(dòng)編程中院仿,當(dāng)請(qǐng)求的資源不能得到滿足時(shí),驅(qū)動(dòng)一般會(huì)調(diào)度其他進(jìn)程執(zhí)行速和,并使本進(jìn)程進(jìn)入睡眠狀態(tài)歹垫,直到它請(qǐng)求的資源被釋放,才會(huì)被喚醒而進(jìn)入就緒態(tài)颠放,睡眠分成可被打斷的睡眠和不可被打斷的睡眠排惨,兩者的區(qū)別在于可被打斷的睡眠在收到信號(hào)的時(shí)候會(huì)醒。
在設(shè)備驅(qū)動(dòng)編程中碰凶,當(dāng)請(qǐng)求的資源不能得到滿足時(shí)暮芭,驅(qū)動(dòng)一般會(huì)調(diào)度其他進(jìn)程執(zhí)行,其對(duì)應(yīng)進(jìn)程進(jìn)入睡眠狀態(tài)欲低,直到它請(qǐng)求的資源被釋放辕宏,才會(huì)被喚醒而進(jìn)入就緒態(tài)。
設(shè)備驅(qū)動(dòng)中砾莱,如果需要幾個(gè)并發(fā)執(zhí)行的任務(wù)瑞筐,可以啟動(dòng)內(nèi)核線程,啟動(dòng)內(nèi)核線程的函數(shù)為:
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags);
1.2.2 內(nèi)存管理
內(nèi)存管理的主要作用是控制多個(gè)進(jìn)程安全地共享主內(nèi)存區(qū)域腊瑟。當(dāng)CPU提供內(nèi)存管理單元(MMU)時(shí)聚假,Linux內(nèi)存管理完成為每個(gè)進(jìn)程進(jìn)行虛擬內(nèi)存到物理內(nèi)存的轉(zhuǎn)換块蚌。Linux 2.6引入了對(duì)無MMU CPU的支持。
一般而言膘格,Linux的每個(gè)進(jìn)程享有4GB的內(nèi)存空間峭范,03GB屬于用戶空間,34GB屬于內(nèi)核空間瘪贱,內(nèi)核空間對(duì)常規(guī)內(nèi)存纱控、I/O設(shè)備內(nèi)存以及高端內(nèi)存存在不同的處理方式。
1.2.3 虛擬文件系統(tǒng)
如圖1.3所示政敢,Linux虛擬文件系統(tǒng)(VFS)隱藏了各種硬件的具體細(xì)節(jié)其徙,為所有的設(shè)備提供了統(tǒng)一的接口。而且喷户,它獨(dú)立于各個(gè)具體的文件系統(tǒng)唾那,是對(duì)各種文件系統(tǒng)的一個(gè)抽象,它使用超級(jí)塊super block存放文件系統(tǒng)相關(guān)信息褪尝,使用索引節(jié)點(diǎn)inode存放文件的物理信息闹获,使用目錄項(xiàng)dentry存放文件的邏輯信息。
圖1.3 Linux文件系統(tǒng)
1.2.4 網(wǎng)絡(luò)接口
網(wǎng)絡(luò)接口提供了對(duì)各種網(wǎng)絡(luò)標(biāo)準(zhǔn)的存取和各種網(wǎng)絡(luò)硬件的支持河哑。如圖1.4所示避诽,在Linux中網(wǎng)絡(luò)接口可分為網(wǎng)絡(luò)協(xié)議和網(wǎng)絡(luò)驅(qū)動(dòng)程序,網(wǎng)絡(luò)協(xié)議部分負(fù)責(zé)實(shí)現(xiàn)每一種可能的網(wǎng)絡(luò)傳輸協(xié)議璃谨,網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)程序負(fù)責(zé)與硬件設(shè)備通信沙庐,每一種可能的硬件設(shè)備都有相應(yīng)的設(shè)備驅(qū)動(dòng)程序。
圖1.4 Linux網(wǎng)絡(luò)體系結(jié)構(gòu)
1.2.5 進(jìn)程通信
進(jìn)程通信支持提供進(jìn)程之間的通信佳吞,Linux支持進(jìn)程間的多種通信機(jī)制拱雏,包含信號(hào)量、共享內(nèi)存底扳、管道等铸抑。這些機(jī)制可協(xié)助多個(gè)進(jìn)程、多資源的互斥訪問衷模、進(jìn)程間的同步和消息傳遞鹊汛。Linux內(nèi)核的5個(gè)組成部分之間的依賴關(guān)系如下:
- 進(jìn)程調(diào)度與內(nèi)存管理之間的關(guān)系:這兩個(gè)子系統(tǒng)互相依賴。在多道程序環(huán)境下阱冶,程序要運(yùn)行必須為之創(chuàng)建進(jìn)程刁憋,而創(chuàng)建進(jìn)程的第一件事情,就是將程序和數(shù)據(jù)裝入內(nèi)存熙揍;
- 進(jìn)程間通信與內(nèi)存管理的關(guān)系:進(jìn)程間通信子系統(tǒng)要依賴內(nèi)存管理支持共享內(nèi)存通信機(jī)制职祷,這種機(jī)制允許兩個(gè)進(jìn)程除了擁有自己的私有空間,還可以存取共同的內(nèi)存區(qū)域;
- 虛擬文件系統(tǒng)與網(wǎng)絡(luò)接口之間的關(guān)系:虛擬文件系統(tǒng)利用網(wǎng)絡(luò)接口支持網(wǎng)絡(luò)文件系統(tǒng)(NFS)有梆,利用內(nèi)存管理支持RAMDISK設(shè)備是尖;
- 內(nèi)存管理與虛擬文件系統(tǒng)之間的關(guān)系:內(nèi)存管理利用虛擬文件系統(tǒng)支持交換,交換進(jìn)程(swapd)定期由調(diào)度程序調(diào)度泥耀,這也是內(nèi)存管理依賴于進(jìn)程調(diào)度的唯一原因饺汹。當(dāng)一個(gè)進(jìn)程存取的內(nèi)存映射被換出時(shí),內(nèi)存管理向文件系統(tǒng)發(fā)出請(qǐng)求痰催,同時(shí)兜辞,掛起當(dāng)前正在運(yùn)行的進(jìn)程。
除了這些依賴關(guān)系外夸溶,內(nèi)核中的所有子系統(tǒng)還要依賴于一些共同的資源逸吵。這些資源包括所有子系統(tǒng)都用到的例程,如分配和釋放內(nèi)存空間的函數(shù)缝裁、打印警告或錯(cuò)誤信息的函數(shù)及系統(tǒng)提供的調(diào)試?yán)痰取?/p>
1.3 Linux內(nèi)核空間與用戶空間
現(xiàn)代CPU內(nèi)部往往實(shí)現(xiàn)了不同的操作模式(級(jí)別)扫皱,不同的模式有不同的功能,高層程序往往不能訪問低級(jí)功能捷绑,而必須以某種方式切換到低級(jí)模式韩脑。
例如,ARM處理器分為7種工作模式:
- 用戶模式(usr):大多數(shù)的應(yīng)用程序運(yùn)行在用戶模式下粹污,當(dāng)處理器運(yùn)行在用戶模式下時(shí)段多,某些被保護(hù)的系統(tǒng)資源是不能被訪問的;
- 快速中斷模式(fiq):用于高速數(shù)據(jù)傳輸或通道處理壮吩;
- 外部中斷模式(irq):用于通用的中斷處理进苍;
- 管理模式(svc):操作系統(tǒng)使用的保護(hù)模式;
- 數(shù)據(jù)訪問終止模式(abt):當(dāng)數(shù)據(jù)或指令預(yù)取終止時(shí)進(jìn)入該模式鸭叙,可用于虛擬存儲(chǔ)及存儲(chǔ)保護(hù)琅捏;
- 系統(tǒng)模式(sys):運(yùn)行具有特權(quán)的操作系統(tǒng)任務(wù);
- 未定義指令中止模式(und):當(dāng)未定義的指令執(zhí)行時(shí)進(jìn)入該模式递雀,可用于支持硬件協(xié)處理器的軟件仿真。
ARM Linux的系統(tǒng)調(diào)用實(shí)現(xiàn)原理是采用swi軟中斷從用戶態(tài)usr模式轉(zhuǎn)入內(nèi)核態(tài)svc模式蚀浆。
又如缀程,X86處理器包含4個(gè)不同的特權(quán)級(jí),稱為Ring0~Ring3市俊。Ring0下杨凑,可以執(zhí)行特權(quán)級(jí)指令,對(duì)任何I/O設(shè)備都有訪問權(quán)等摆昧,而Ring3則被限制很多操作撩满。 Linux系統(tǒng)充分利用CPU這一硬件特性,但它只使用了兩級(jí)。在Linux系統(tǒng)中伺帘,內(nèi)核可進(jìn)行任何操作昭躺,而應(yīng)用程序則被禁止對(duì)硬件的直接訪問和對(duì)內(nèi)存的未授權(quán)訪問。例如伪嫁,若使用X86處理器领炫,則用戶代碼運(yùn)行在特權(quán)級(jí)3,而系統(tǒng)內(nèi)核代碼則運(yùn)行在特權(quán)級(jí)0张咳。
內(nèi)核空間和用戶空間這兩個(gè)名詞被用來區(qū)分程序執(zhí)行的這兩種不同狀態(tài)帝洪,他們使用不同的地址空間。Linux只能通過系統(tǒng)調(diào)用和硬件中斷完成從用戶空間到內(nèi)核空間的控制轉(zhuǎn)移脚猾。
二葱峡、Linux內(nèi)核的編譯及引導(dǎo)
2.1 Linux內(nèi)核的編譯
Linux驅(qū)動(dòng)工程師需要牢固地掌握Linux內(nèi)核的編譯方法以為嵌入式系統(tǒng)構(gòu)建可運(yùn)行的Linux操作系統(tǒng)映像。在編譯Linux內(nèi)核時(shí)龙助,需要配置內(nèi)核砰奕,可以使用下面的命令中的一個(gè)來配置:
# make config (基于文本的最為傳統(tǒng)的配置界面,不推薦使用)
# make menuconfig (基于文本菜單的配置界面)
# make xconfig (要求QT被安裝)
# make gconfig (要求GTK+被安裝)
在配置Linux 2.6內(nèi)核所使用的make config泌参、make menuconfig脆淹、make xconfig和make gconfig這4重方式中,最值得推薦的是make menuconfig沽一,它不依賴于QT或GTK+盖溺,且非常直觀。輸入make menuconfig命令后進(jìn)入配置界面如圖3.5所示铣缠。
內(nèi)核配置包含的項(xiàng)目相當(dāng)多烘嘱,在arch/arm/configs/目錄中包含了ARM芯片不同的demo板的默認(rèn)配置。因此蝗蛙,在內(nèi)核頂層目錄下輸入make board_defconfig就可以為demo配置內(nèi)核蝇庭,如輸入:make smdk2410_defconfig就可以為SMDK2410配置默認(rèn)內(nèi)核。
圖 1.5 Linux內(nèi)核編譯配置
編譯內(nèi)核和模塊的方法是:
# make zImage
# make modules
執(zhí)行完上述命令后捡硅,在源代碼的根目錄下會(huì)得到==未壓縮的內(nèi)核映像vmlinux和內(nèi)核符號(hào)表文件System.map==哮内,在arch/arm/boot/目錄會(huì)得到壓縮的內(nèi)核映像zImage,在內(nèi)核各對(duì)應(yīng)目錄得到選中的內(nèi)核模塊壮韭。
Linux 2.6內(nèi)核的配置系統(tǒng)由以下3個(gè)部分組成:
- Makefile:分布在Linux內(nèi)核源代碼中的Makefile北发,定義Linux內(nèi)核的編譯規(guī)則;
- 配置文件(Kconfig):給用戶提供配置選擇的功能喷屋;
- 配置工具:包括配置命令解釋器(對(duì)配置腳本中使用的配置命令進(jìn)行解釋)和配置用戶界面(提供基于字符界面和圖形界面)琳拨。這些配置工具都是使用腳本語言,如Tcl/TK屯曹、Perl等編寫狱庇。
使用make config惊畏、make menuconfig等命令后,會(huì)生成一個(gè).config配置文件密任,記錄哪些部分被編譯入內(nèi)核颜启、哪些部分被編譯為內(nèi)核模塊。
運(yùn)行make menuconfig等時(shí)批什,配置工具首先分析與體系結(jié)構(gòu)對(duì)應(yīng)的/arch/xxx/Kconfig文件(xxx即為傳入的ARCH參數(shù))农曲,/arch/xxx/Kconfig文件中除本身包含一些與體系結(jié)構(gòu)相關(guān)的配置項(xiàng)和配置菜單以外,還通過source語句引入了一系列Kconfig文件驻债,而這些Kconfig又可能再次通過source引入下一層的Kconifg乳规,配置工具依據(jù)這些Kconfig包含的菜單和項(xiàng)目即可描繪出一個(gè)如圖3.6所示的分層結(jié)構(gòu)。例如合呐,/arch/arm/Kconfig文件的結(jié)構(gòu)如下:
1 #
2 # For a description of the syntax of this configuration file,
3 # see Documentation/kbuild/kconfig-language.txt.
4 #
5
6 mainmenu "Linux Kernel Configuration"
7
8 config ARM
9 bool
10 default y
11 select HAVE_AOUT
12 select HAVE_IDE
13 select RTC_LIB
14 select SYS_SUPPORTS_APM_EMULATION
15 select HAVE_OPROFILE
16 select HAVE_ARCH_KGDB
17 select HAVE_KPROBES if (!XIP_KERNEL)
18 select HAVE_KRETPROBES if (HAVE_KPROBES)
19 select HAVE_FUNCTION_TRACER if (!XIP_KERNEL)
20 select HAVE_GENERIC_DMA_COHERENT
21 help
22 The ARM series is a line of low-power-consumption RISC chip designs
23 licensed by ARM Ltd and targeted at embedded applications and
24 handhelds such as the Compaq IPAQ. ARM-based PCs are no longer
25 manufactured, but legacy ARM-based PC hardware remains popular in
26 Europe. There is an ARM Linux project with a web page at
27 <http://www.arm.linux.org.uk/>.
……
198 config MMU
199 bool "MMU-based Paged Memory Management Support"
200 default y
201 help
202 Select if you want MMU-based virtualised addressing space
203 support by paged memory management. If unsure, say 'Y'.
……
614 config ARCH_S3C64XX
615 bool "Samsung S3C64XX"
616 select GENERIC_GPIO
617 select HAVE_CLK
618 select ARCH_HAS_CPUFREQ
619 help
620 Samsung S3C64XX series based systems
……
2.2 Kconfig和Makefile
在Linux內(nèi)核中增加程序需要完成以下3項(xiàng)工作:
- 將編寫的源代碼拷入Linux內(nèi)核源代碼的相應(yīng)目錄暮的;
- 在目錄的Kconfig文件中增加關(guān)于新源代碼對(duì)應(yīng)的編譯配置選項(xiàng);
- 在目錄的Makefile文件中增加對(duì)新代碼的編譯條目淌实。
一般而言冻辩,驅(qū)動(dòng)工程師只會(huì)在內(nèi)核源代碼的drivers目錄的相應(yīng)子目錄中增加新設(shè)備驅(qū)動(dòng)的源代碼,并增加或修改Kconfig配置腳本和Makefile腳本即可拆祈。接下來就講解一下Kconfig和Makefile的語法恨闪。
1.Makefile
這里主要對(duì)內(nèi)核源代碼各級(jí)子目錄中的kbuild(內(nèi)核的編譯系統(tǒng))Makefile進(jìn)行簡單介紹,這部分是內(nèi)核模塊或設(shè)備驅(qū)動(dòng)的開發(fā)者最常接觸到的放坏。Makefile的語法包括如下幾個(gè)方面:
(1)目標(biāo)定義
目標(biāo)定義就是用來定義哪些內(nèi)容要作為模塊編譯咙咽,哪些要編譯并鏈接進(jìn)內(nèi)核。例如:
obj-y += foo.o
表示要由foo.c或者foo.s文件編譯得到foo.o并鏈接進(jìn)內(nèi)核淤年,而obj-m則表示該文件要作為模塊編譯钧敞。除了y、m以外的obj-x形式的目標(biāo)都不會(huì)被編譯麸粮。
而更常見的做法是根據(jù).config文件的CONFIG_變量來決定文件的編譯方式溉苛,如:
obj-$(CONFIG_ISDN) += isdn.o
obj-$(CONFIG_ISDN_BSDCOMP) += isdn_bsdcomp.o
除了obj-形式的目標(biāo)以外,還有l(wèi)ib-y library庫弄诲,hostprogs-y主機(jī)程序等目標(biāo)愚战,但是基本都應(yīng)用在特定的目錄和場合下。
(2)多文件模塊的定義
最簡單的Makefile如前面的一句話的形式就夠了齐遵,如果一個(gè)模塊由多個(gè)文件組成凤巨,會(huì)稍微復(fù)雜一些,這時(shí)候應(yīng)采用模塊名加-y或-objs后綴的形式來定義模塊的組成文件洛搀,如下面的例子:
#
# Makefile for the linux ext2-filesystem routines.
#
obj-$(CONFIG_EXT2_FS) += ext2.o
ext2-y := balloc.o dir.o file.o fsync.o ialloc.o inode.o \
ioctl.o namei.o super.o symlink.o
ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o xattr_user.o xattr_trusted.o
ext2-$(CONFIG_EXT2_FS_POSIX_ACL) += acl.o
ext2-$(CONFIG_EXT2_FS_SECURITY) += xattr_security.o
ext2-$(CONFIG_EXT2_FS_XIP) += xip.o
模塊的名字為ext2,由balloc.o佑淀、dir.o留美、file.o等多個(gè)目標(biāo)文件最終鏈接生成ext2.o直至ext2.ko文件,并且是否包括xattr.o、acl.o等則取決于內(nèi)核配置文件的配置情況谎砾。例如逢倍,如果CONFIG_EXT2_FS_POSIX_ACL被選擇,則編譯acl.c得到acl.o并最終鏈接進(jìn)ext2景图。
(3)目錄層次的迭代
如下例:
obj-$(CONFIG_EXT2_FS) += ext2/
當(dāng)CONFIG_EXT2_FS的值為y或m時(shí)较雕,kbuild將會(huì)把ext2目錄列入向下迭代的目標(biāo)中。
2.Kconfig
內(nèi)核配置腳本文件的語法也比較簡單挚币,主要包括以下幾個(gè)方面:
(1)菜單入口
大多數(shù)的內(nèi)核配置選項(xiàng)都對(duì)應(yīng)Kconfig中的一個(gè)菜單入口:
614 config ARCH_S3C64XX
615 bool "Samsung S3C64XX"
616 select GENERIC_GPIO
617 select HAVE_CLK
618 select ARCH_HAS_CPUFREQ
619 help
620 Samsung S3C64XX series based systems
“config”關(guān)鍵字定義新的配置選項(xiàng)亮蒋,之后的幾行定義了該配置選項(xiàng)的屬性。配置選項(xiàng)的屬性包括類型妆毕、數(shù)據(jù)范圍慎玖、輸入提示、依賴關(guān)系笛粘、選擇關(guān)系及幫助信息和默認(rèn)值等趁怔。
每個(gè)配置選項(xiàng)都必須指定類型,類型包括bool薪前、tristate润努、string、hex和int示括,其中tristate和string是兩種基本的類型铺浇,其他類型都基于這兩種基本類型。類型定以后可以緊跟輸入提示例诀,下面的兩段腳本是等價(jià)的:
bool “Networking support”
和
bool
prompt “Networking support”
輸入提示的一般格式如下随抠,其中可選的if用來表示該提示的依賴關(guān)系:
prompt <prompt> [if <expr>]
默認(rèn)值的格式為:
default <expr> [if <expr>]
一個(gè)配置選項(xiàng)可以存在任意多個(gè)默認(rèn)值,這種情況下繁涂,只有第一個(gè)被定義的值是可用的拱她。如果用戶不設(shè)置對(duì)應(yīng)的選項(xiàng),配置選項(xiàng)的值就是默認(rèn)值扔罪。
依賴關(guān)系的格式為:
depends on(或者requires) <expr>
如果定義了多重依賴關(guān)系秉沼,他們之間用“&&”間隔。依賴關(guān)系也可以應(yīng)用到該菜單中所有的其他選項(xiàng)(同樣接受if表達(dá)式)矿酵,下面的兩段腳本是等價(jià)的:
bool “foo” if BAR
default y if BAR
和
depends on BAR
bool “foo”
default y
選擇關(guān)系(也稱為反向依賴關(guān)系)的格式為:
select <symbol> [if <expr>]
A如果選擇了B唬复,則在A被選中的情況下,B自動(dòng)被選中全肮。
kbuild Makefile中的expr(表達(dá)式)定義為:
<expr> ::= <symbol>
<symbol> ‘=’ <symbol>
<symbol> ‘!=’ <symbol>
‘(’ <expr> ‘)’
‘!’ <expr>
<expr> ‘&&’ <expr>
<expr> ‘||’ <expr>
也就是說expr是由symbol敞咧、兩個(gè)symbol相等、兩個(gè)symbol不等以及expr的賦值辜腺、非休建、與或運(yùn)算構(gòu)成乍恐。而symbol分為兩類,一類是由菜單入口定義配置選項(xiàng)定義的非常數(shù)symbol测砂,另一類是作為expr組成部分的常數(shù)symbol茵烈。
數(shù)據(jù)范圍的格式為:
range <symbol> <symbol> [if <expr>]
為int和hex類型的選項(xiàng)設(shè)置可以接受輸入值范圍,用戶只能輸入大于等于第一個(gè)symbol砌些,小于等于第二個(gè)symbol的值呜投。
幫助信息的格式為:
help(或---help---)
開始
……
結(jié)束
幫助信息完全靠文本縮進(jìn)識(shí)別結(jié)束〈媪В“---help---”和“help”在作用上沒有區(qū)別仑荐,設(shè)計(jì)“---help---”的初衷在于將文本中的配置邏輯與給開發(fā)人員的提示分開。
menuconfig關(guān)鍵字的作用與config類似有巧,但它在config的基礎(chǔ)上要求所有的子選項(xiàng)作為獨(dú)立的行顯示释漆。
(2)菜單結(jié)構(gòu)
菜單入口在菜單樹結(jié)構(gòu)中的位置可由兩種方法決定,第一種方式為:
menu “Network device support”
depends on NET
config NETDEVICES
…
endmenu
所有處于“menu”和“endmenu”之間的菜單入口都會(huì)成為“Network device support”的子菜單篮迎。而且男图,所有子菜單選項(xiàng)都會(huì)繼承父菜單的依賴關(guān)系,比如“Network device support”對(duì)“NET”的依賴會(huì)被加到了配置選項(xiàng)NETDEVICES的依賴列表中甜橱。
注意menu后面跟的“Network device support”項(xiàng)目僅僅是1個(gè)菜單逊笆,沒有對(duì)應(yīng)真實(shí)的配置選項(xiàng),也不具備3種不同的狀態(tài)岂傲。這是它和config的區(qū)別难裆。
另一種方式是通過分析依賴關(guān)系生成菜單結(jié)構(gòu)。如果菜單選項(xiàng)在一定程度上依賴于前面的選項(xiàng)镊掖,它就能成為該選項(xiàng)的子菜單乃戈。如果父選項(xiàng)為“N”,子選項(xiàng)不可見亩进;如果父選項(xiàng)可見症虑,子選項(xiàng)才能見。例如:
config MODULES
bool “Enable loadable module support”
config MODVERSIONS
bool “Set version information on all module symbols”
depends on MODULES
comment “module support disabled”
depends on !MODULES
MODVERSIONS直接依賴MODULES归薛,只有MODULES不為“n”時(shí)谍憔,該選項(xiàng)才可見。
除此之外主籍,Kconfig中還可能使用“choices…endchoice”习贫、“comment”、“if…endif”這樣的語法結(jié)構(gòu)千元,其中“choices…endchoice”的結(jié)構(gòu)為:
choice
<choice options>
<choice block>
endchoice
它定義一個(gè)選擇群苫昌,其接受的選項(xiàng)(choice options)可以是前面描述的任何屬性,例如S3C6410芯片設(shè)計(jì)的產(chǎn)品的VGA輸出分辨率可以是1024768或者800600幸海,在drivers/video/Samsung/Kconfig就定義了如下的choice:
choice
depends on FB_S3C_VGA
prompt “Select VGA Resolution for S3C Framebuffer”
default FB_S3C_VGA_1024_768
config FB_S3C_VGA_1024_768
bool “1024*768@60Hz”
---help---
TBA
config FB_S3C_VGA_640_480
bool “640*480@60Hz”
---help---
TBA
endchoice
Kconfig配置腳本和Makefile腳本編寫更詳細(xì)的信息蜡歹,可分別查看內(nèi)核溫昂Documentation目錄的kbuild子目錄下的Kconfig-language.txt和Makefiles.txt文件屋厘。
3.應(yīng)用實(shí)例:在內(nèi)核中新增驅(qū)動(dòng)代碼目錄和子目錄
下面來看一個(gè)綜合實(shí)例,假設(shè)我們要在內(nèi)核源代碼drivers目錄下為ARM體系結(jié)構(gòu)新增如下用于test driver的樹形目錄:
| -- test
| -- cpu
| -- cpu.c
| -- test.c
| -- test_client.c
| -- test_ioctl.c
| -- test_proc.c
| -- test_queue.c
在內(nèi)核中增加目錄和子目錄月而,我們需為相應(yīng)的新增目錄創(chuàng)建Makefile和Kconfig文件,而新增目錄的父目錄中的Kconfig和Makefile也許修改议纯,以便新增的Kconfig和Makefile能被引用父款。
在新增的test目錄下,應(yīng)該包含如下的Kconfig文件:
# drivers/test/Kconfig
# TEST driver configuration
#
menu “TEST Driver”
comment “ TEST Driver”
config CONFIG_TEST
bool “TEST support”
config CONFIG_TEST_USER
tristate “TEST user-space interface”
depends on CONFIG_TEST
endmenu
由于test driver對(duì)于內(nèi)核來說是新的功能瞻凤,所以需首先創(chuàng)建一個(gè)菜單TEST Driver憨攒。然后,顯示“TEST support”阀参,等待用戶選擇肝集;接下來判斷用戶是否選擇了TEST Driver,如果是(CONFIG_TEST=y)蛛壳,則進(jìn)一步顯示子功能:用戶接口與CPU功能支持杏瞻;由于用戶接口功能可以被編譯成內(nèi)核模塊,所以這里的詢問語句使用了tristate衙荐。
為了使這個(gè)Kconfig能起作用捞挥,修改arch/arm/Kconfig文件,在對(duì)應(yīng)位置增加:
source “driver/test/Kconfig”
腳本中的source意味著引用新的Kconfig文件忧吟。
在新增的test目錄下砌函,應(yīng)該包含如下Makefile文件:
# driver/test/Makefile
# Makefile for the TEST
#
obj-$(CONFIG_TEST) += test.o test_queue.o test_client.o
obj-$(CONFIG_TEST_USER) += test_ioctl.o
obj-$(CONFIG_PROC_FS) += test_proc.o
obj-$(CONFIG_TEST_CPU) += cpu/
該腳本根據(jù)配置變量的取值,構(gòu)建obj-*列表溜族,由于test目錄中包含一個(gè)子目錄cpu讹俊,當(dāng)CONFIG_TEST_CPU=y時(shí)一屋,需要將cpu目錄加入列表促绵。
# drivers/test/cpu/Makefile
# Makefile for the TEST CPU
#
obj-$(CONFIG_TEST_CPU) += cpu.o
為了使得整個(gè)test目錄能夠被編譯命令作用到界阁,test目錄父目錄中的Makefile也需新增如下腳本才能夠進(jìn)入test目錄:
obj-$(CONFIG_TEST) += test/
增加了Kconfig和Makefile之后的新的test樹型目錄為:
| -- test
| -- cpu
| -- cpu.c
| -- Makefile
| -- test.c
| -- test_client.c
| -- test_ioctl.c
| -- test_proc.c
| -- test_queue.c
| -- Makefile
| -- Config
2.3 Linux內(nèi)核的引導(dǎo)
引導(dǎo)Linux系統(tǒng)的過程包括很多階段沦泌,這里將以引導(dǎo)X86 PC為例進(jìn)行講解骄酗。引導(dǎo)X86 PC上的Linux的過程和引導(dǎo)嵌入式系統(tǒng)上的Linux的過程基本類似拌夏。不過在X86 PC上有一個(gè)BIOS(基本輸入/輸出系統(tǒng))轉(zhuǎn)移到Bootloader的過程稚新,而嵌入式系統(tǒng)往往復(fù)位后就直接運(yùn)行Bootloader盆顾;在X86 PC上的Bootloader一般為GRUB诬像,而在嵌入式系統(tǒng)中的Bootloader一般為U-Boot屋群。
圖1.6所示為X86 PC上電/復(fù)位到運(yùn)行Linux用戶空間初始化進(jìn)程的流程,在進(jìn)入與Linux相關(guān)代碼之間坏挠,會(huì)經(jīng)歷如下階段芍躏。
圖1.6 X86 PC上的Linux引導(dǎo)流程
- 當(dāng)系統(tǒng)上電或復(fù)位時(shí),CPU會(huì)將PC指針賦值為一個(gè)特定的地址0xFFFF0并執(zhí)行該地址處的指令降狠。在PC機(jī)中对竣,該地址位于BIOS中庇楞,它保存在主板上的ROM或Flash中。
- BIOS運(yùn)行時(shí)按照CMOS的設(shè)置定義的啟動(dòng)設(shè)備順序來搜索處于活動(dòng)狀態(tài)并且可以引導(dǎo)的設(shè)備否纬。若從硬盤啟動(dòng)吕晌,BIOS會(huì)將硬盤MBR(主引導(dǎo)記錄)中的內(nèi)容加載到RAM。MBR是一個(gè)512字節(jié)大小的扇區(qū)临燃,位于磁盤上的第一個(gè)扇區(qū)中(0道0柱面1扇區(qū))睛驳。當(dāng)MBR被加載到RAM中之后,BIOS就會(huì)將控制權(quán)交給MBR膜廊。
- 主引導(dǎo)加載程序查找并加載次引導(dǎo)加載程序乏沸。它在分區(qū)表中查找活動(dòng)分區(qū),當(dāng)找到一個(gè)活動(dòng)分區(qū)時(shí)爪瓜,掃描分區(qū)表中的其他分區(qū)蹬跃,以確保他們都不是活動(dòng)的。當(dāng)這個(gè)過程驗(yàn)證完成之后铆铆,就將活動(dòng)分區(qū)的引導(dǎo)記錄從這個(gè)設(shè)備中讀入RAM中并執(zhí)行它蝶缀。
- 次引導(dǎo)加載程序加載Linux內(nèi)核和可選的初始RAM磁盤,將控制權(quán)交給Linux內(nèi)核源代碼算灸。
- 運(yùn)行被加載的內(nèi)核扼劈,并啟動(dòng)用戶空間應(yīng)用程序。
嵌入式系統(tǒng)中的Linux引導(dǎo)過程與之類似菲驴,但一般更加簡潔荐吵。不論具體以怎樣的方式實(shí)現(xiàn),只要具備如下特性就可以稱其為Bootloader:
- 可以在系統(tǒng)上電或復(fù)位的時(shí)候以某種方式執(zhí)行赊瞬,這些方式包括被BIOS引導(dǎo)執(zhí)行先煎、直接在Nor Flash中執(zhí)行、Nand Flash中的代碼被MCU自動(dòng)拷入內(nèi)部或外部RAM執(zhí)行等巧涧;
- 能將U盤薯蝎、磁盤、光盤谤绳、NOR/NAND Flash占锯、ROM、SD卡等存儲(chǔ)介質(zhì)缩筛,甚或網(wǎng)口消略、串口中的操作系統(tǒng)加載到RAM并把控制權(quán)交給操作系統(tǒng)源代碼執(zhí)行。
完成上述功能的Bootloader的實(shí)現(xiàn)方式非常多樣化瞎抛,甚至本身也可以是一個(gè)簡化版的操作系統(tǒng)艺演。著名的Linux Bootloader包括應(yīng)用于PC的LILO和GRUB,應(yīng)用于嵌入式系統(tǒng)的U-Boot、Redboot等胎撤。
U-Boot的定位為“Universal Bootloader”晓殊,其功能比較強(qiáng)大,涵蓋了包括PowerPC伤提、ARM巫俺、MIPS和X86在內(nèi)的絕大部分處理器架構(gòu),提供網(wǎng)卡肿男、串口识藤、Flash等外設(shè)驅(qū)動(dòng),提供必要的網(wǎng)絡(luò)協(xié)議(BOOT次伶、DHCP、TFTP等)稽穆,能識(shí)別多種文件系統(tǒng)(cramfs冠王、fat、jffs2和registerfs等)舌镶,并附帶了調(diào)試柱彻、腳本、引導(dǎo)等工具餐胀,應(yīng)用十分廣泛哟楷。
Redboot是Redhat公司隨eCos發(fā)布的Bootloader開源項(xiàng)目,處理包含U-Boot類似的強(qiáng)大功能外否灾,它還包含GDB stub(插樁)卖擅,因此能通過串口或網(wǎng)卡與GDB進(jìn)行通信,調(diào)試GCC產(chǎn)生的任何程序(包括內(nèi)核)墨技。
我們有必要對(duì)上述流程的第5個(gè)階段進(jìn)行更詳細(xì)的分析惩阶,它完成啟動(dòng)內(nèi)核并運(yùn)行用戶空間的init進(jìn)程。
當(dāng)內(nèi)核映像被加載到RAM之后扣汪,Bootloader的控制權(quán)被釋放断楷,內(nèi)核階段就開始了。內(nèi)核映像并不是完全可直接執(zhí)行的目標(biāo)代碼崭别,而是一個(gè)壓縮過的zImage(小內(nèi)核)或bzImage(大內(nèi)核冬筒,bzImage中的b是“big”的意思)。但是茅主,并非zImage和bzImage映像中的一切都被壓縮了舞痰,否則Bootloader把控制權(quán)交給這個(gè)內(nèi)核映像它就“傻”了。實(shí)際上暗膜,映像中包含未被壓縮的部分匀奏,這部分中包含解壓縮程序,解壓縮程序會(huì)解壓映像中被壓縮的部分学搜。zImage和bzImage都是用gzip壓縮的娃善,他們不僅是一個(gè)壓縮文件论衍,而且在這兩個(gè)文件的開頭部分內(nèi)嵌有g(shù)zip解壓縮代碼聚磺。
如圖1.7所示,當(dāng)bzImage(用于i386映像)被調(diào)用時(shí)蜒蕾,它從/arch/i386/boot/head.S的start匯編例程開始執(zhí)行。這個(gè)程序執(zhí)行一些基本的硬件設(shè)置焕阿,并調(diào)用/arch/i386/boot/compressed/head.S中的startup_32例程咪啡。startup_32程序設(shè)置一些基本的運(yùn)行環(huán)境(如堆棧)后,清除BSS段暮屡,調(diào)用/arch/i386/boot/compressed/misc.c中的decompress_kernel() C函數(shù)解壓內(nèi)核撤摸。內(nèi)核被解壓到內(nèi)存中之后,會(huì)再調(diào)用/arch/i386/kernel/head.S文件中的startup_32例程褒纲,這個(gè)新的startup_32例程(稱為清除程序或進(jìn)程0)會(huì)初始化頁表准夷,并啟動(dòng)內(nèi)存分頁機(jī)制,接著為任何可選的浮點(diǎn)單元(FPU)檢測CPU的類型莺掠,并將其存儲(chǔ)起來供以后使用衫嵌。這些都昨晚之后,/init/main.c中的start_kernel()函數(shù)被調(diào)用彻秆,進(jìn)入與體系結(jié)構(gòu)無關(guān)的Linux內(nèi)核部分楔绞。
圖1.7 X86 PC上的Linux內(nèi)核初始化
[圖片上傳失敗...(image-76a602-1551370457159)]
start_kernel()會(huì)調(diào)用一系列初始化函數(shù)來設(shè)置終端,執(zhí)行進(jìn)一步的內(nèi)存配置掖棉。之后墓律,/arch/i386/kernel/process.c中的kernel_thread()被調(diào)用以啟動(dòng)第一個(gè)核心線程,該線程執(zhí)行init()函數(shù)幔亥,而原執(zhí)行序列會(huì)調(diào)用cpu_idle()等待調(diào)度耻讽。
作為核心線程的init()函數(shù)完成外設(shè)及驅(qū)動(dòng)程序的加載和初始化,掛載根文件系統(tǒng)帕棉。init()打開/dev/console設(shè)備针肥,重定向stdin、stdout和stderr到控制臺(tái)慰枕。之后具帮,它搜索文件系統(tǒng)中的init程序(也可以由“init=”命令參數(shù)指定init程序)蜂厅,并使用execve()系統(tǒng)調(diào)用執(zhí)行init程序病游。搜索init程序的順序?yàn)椋?sbin/init衬衬、/etc/init滋尉、/bin/init和/bin/sh兼砖。在嵌入式系統(tǒng)中,多數(shù)情況下丸冕,可以給內(nèi)核傳入一個(gè)簡單的shell腳本來啟動(dòng)必須的嵌入式應(yīng)用程序胖烛。
至此佩番,漫長的Linux內(nèi)核應(yīng)道和啟動(dòng)過程就此結(jié)束趟畏,而init()對(duì)應(yīng)的這個(gè)由start_kernel()創(chuàng)建的第一個(gè)線程也進(jìn)入用戶模式。
三律想、Linux下的C編程特點(diǎn)
3.1 Linux編碼風(fēng)格
Linux程序的命名習(xí)慣和Windows程序的命名習(xí)慣及著名的匈牙利命名法有很大的不同技即。在Windows程序中身笤,習(xí)慣以如下方式命名宏展鸡、變量和函數(shù):
#define PI 2.141592653 /* 用大寫字母代表宏 */
int minValue, maxValue; /* 變量:第一個(gè)單詞全小寫涤久,其后的單詞第一個(gè)字母大寫 */
void SendData(vodi); /* 函數(shù):所有單詞第一個(gè)字母都大寫定義 */
這種命名方式在程序員中非常盛行响迂,意思表達(dá)清晰且避免了匈牙利法的臃腫蔗彤,單詞之間通過首字母大寫來區(qū)分然遏。通過第1個(gè)單詞的首字母是否大寫可以區(qū)分名稱屬于變量還是屬于函數(shù)待侵,而看到整串的大寫字母可以斷定為宏秧倾。實(shí)際上,Windows的命名習(xí)慣并非僅限于Windows編程售淡,大多數(shù)領(lǐng)域的程序開發(fā)都遵照此習(xí)慣勋又。
但是Linux不以這種習(xí)慣命名楔壤,對(duì)應(yīng)于上面的一段程序蹲嚣,在Linux中會(huì)被命名為:
#define PI 3.141592653
int min_value, max_value;
void send_data(void);
上述命名方式中抖部,下劃線大行其道慎颗,不依照Windows所采用的首字母大寫以區(qū)分單詞的方式俯萎。Linux的命名習(xí)慣與Windows命名習(xí)慣各有千秋夫啊,但是既然本書和本書的讀者立足于編寫Linux程序,代碼風(fēng)格理應(yīng)保持與Linux開發(fā)社區(qū)的一致性熊榛。
Linux的代碼縮進(jìn)使用“TAB”(8個(gè)字符)来候;Linux的代碼括號(hào)“{”和“}”的使用原則如下:
- 對(duì)于結(jié)構(gòu)體、if/for/while/switch語句梆砸,“{”不另起一行帖世,例如:
struct var_data {
int len; /* 一個(gè)TAB日矫,8個(gè)字符 */
char data[0];
};
if ( a == b ) {
a = c;
d = a;
}
for ( i = 0; i < 10; i++ ) {
a = c;
d = a;
}
- 如果if、for循環(huán)后只有1行翔怎,不要加“{”和“}”,例如:
for ( i = 0; i < 10; i++ )
a = c;
- if和else混用的情況下珊膜,else語句不另起一行车柠,例如:
if ( x == y ) {
……
} else if ( x > y ) {
……
} else {
……
}
對(duì)于函數(shù),“{”另起一行溶褪,譬如:
int add(int a, int b)
{
return a + b;
}
在switch/case語句方面猿妈,Linux建議switch和case對(duì)齊彭则,例如:
switch (suffix) {
case ‘G’:
case ‘g’:
mem <<= 30;
break;
case ‘M’:
case ‘m’:
mem <<= 20;
break;
case ‘K’:
case ‘k’:
mem <<= 10;
/* fall through */
default:
break;
}
內(nèi)核下的==Documentation/CodingStyle==描述了Linux內(nèi)核對(duì)編碼風(fēng)格的要求,內(nèi)核下的==scripts/checkpatch.pl==提供了1個(gè)檢查代碼風(fēng)格的腳本芬萍。如果我們使用script/checkpatch.pl檢查包含如下代碼塊的源程序:
for ( i = 0; I < 10; i++ ) {
a = c;
}
就會(huì)產(chǎn)生“WARNING: braces {} are not necessary for single statement blocks”的警告柬祠。另外漫蛔,==請(qǐng)注意代碼中空格的應(yīng)用==。
3.2 GNU C與ANSI C
Linux上可用的C編譯器是GNU C編譯器锨天,它建立在自由軟件基金會(huì)的編程許可證的基礎(chǔ)上奶镶,因此可以自由發(fā)布。GNU C對(duì)標(biāo)準(zhǔn)C進(jìn)行一系列擴(kuò)展纤壁,以增強(qiáng)標(biāo)準(zhǔn)C的功能酌媒。
1.零長度和變量長度數(shù)組
GNU C允許使用零長度數(shù)組,在定義變長對(duì)象的頭結(jié)構(gòu)時(shí)雨席,這個(gè)特性非常有用陡厘,例如:
struct var_data {
int len;
char data[0]
};
char data[0]僅僅意味著程序中通過var_data結(jié)構(gòu)體實(shí)例的data[index]成員可以訪問len之后的第index個(gè)地址糙置,它并沒有為data[]數(shù)組分配內(nèi)存,因此sizeof(struct var_data) = sizeof(int)揉抵。假設(shè)struct var_data的數(shù)據(jù)域就保存在struct var_data緊接著的內(nèi)存區(qū)域冤今,則通過如下代碼可以遍歷這些數(shù)據(jù):
struct var_data s;
…
for ( i = 0; i < s.len; i++ )
printf(“%02x”, s.data[i]);
GNU C中也可以使用1個(gè)變量定義數(shù)組,例如下面代碼中定義的“double x[n]”:
int main(int argc, char *argv[])
{
int i, n = argc;
double x[n];
for ( i = 0, i < n; i++ )
x[i] = i;
return 0;
}
2.case范圍
GNU C支持case x…y這樣的語法阱佛,區(qū)間[x,y]的數(shù)都會(huì)滿足這個(gè)case的條件凑术,請(qǐng)看下面的代碼:
switch (ch) {
case ‘0’… ‘9’ : c -= ‘0’;
break;
case ‘a(chǎn)’… ‘f’ : c -= ‘a(chǎn)’ - 10;
break;
case ‘A’… ‘F’ : c -= ‘A’ - 10;
break;
}
代碼中的case ‘0’…‘9’等價(jià)于標(biāo)準(zhǔn)C中的:
case ‘0’: case ‘1’: case ‘2’: case ‘3’: case ‘4’:
case ‘5’: case ‘6’: case ‘7’: case ‘8’: case ‘9’:
3.語句表達(dá)式
GNU C把包含在括號(hào)中的復(fù)合語句看做是一個(gè)表達(dá)式淮逊,稱為語句表達(dá)式泄鹏,它可以出現(xiàn)在任何允許表達(dá)式的地方舶治。我們可以在語句表達(dá)式中使用原本只能在復(fù)合語句中使用的循環(huán)霉猛、局部變量等惜浅,例如:
#define min_t(type,x,y) \
({type __x = (x)}; type __y = (y); __x < __y ? __x: __y; })
int ia, ib, mini;
float fa, fb, minf;
mini = min_t(int, ia, ib);
minf = min_t(float, fa,fb);
因?yàn)橹匦露x了__x和__y這兩個(gè)局部變量坛悉,所以上述方式定義的宏將不會(huì)有副作用吹散。在標(biāo)準(zhǔn)C中,對(duì)應(yīng)的如下宏則會(huì)產(chǎn)生副作用:
#define min(x,y) ( (x) < (y) ? (x) : (y) )
代碼min(++ia, ++ib)會(huì)被展開為((++ia) < (++ib) ? (++ia) : (++ib))界轩,傳入宏的“參數(shù)”被增加2次浊猾。
4.typeof關(guān)鍵字
typeof()語句可以獲得x的類型葫慎,因此,我們可以借助typeof重新定義min這個(gè)宏:
#define min(x, y) ({ \
const typeof(x) _x = (x); \
const typeof(y) _y = (y); \
(void) (&_x == &_y); \
_x < _y ? _x : _y; })
我們不需要像min_t(type,x,y)這個(gè)宏那樣把type傳入澄港,因?yàn)橥ㄟ^typeof(x)废岂、typeof(y)可以獲得type湖苞。代碼行(void) (&_x == &_y)的作用是檢查_x和_y的類型是否一致袒啼。
5.可變參數(shù)宏
標(biāo)準(zhǔn)C就支持可變參數(shù)函數(shù)滑肉,意味著函數(shù)的參數(shù)是不固定的靶庙,例如printf()函數(shù)的原型為:
int printf( const char *format [, argument]… );
而在GNU C中六荒,宏也可以接受可變數(shù)目的參數(shù)掏击,例如:
#define pr_debug(fmt, arg…) \
printk(fmt, ##arg)
這里arg表示其余的參數(shù)砚亭,可以是零個(gè)或多個(gè)捅膘,這些參數(shù)以及參數(shù)之間的逗號(hào)構(gòu)成arg的值,在宏擴(kuò)展時(shí)替換arg署尤,例如下面代碼:
pr_debug(“%s:%d”, filename, line);
會(huì)被擴(kuò)展為:
printk(“%s:%d”, filename, line);
使用“##”的原因是處理arg不代表任何參數(shù)的情況曹体,這時(shí)候狐援,前面的逗號(hào)就變得多余了究孕。使用“##”之后,GNU C預(yù)處理器會(huì)丟棄前面的逗號(hào)微酬。這樣颗管,代碼:
pr_debug(“success!\n”);
會(huì)被正確地?cái)U(kuò)展為:
printk (“success!\n”);
而不是:
printk (“success!\n”,);
這正是我們希望看到的。
6.標(biāo)號(hào)元素
標(biāo)準(zhǔn)C要求數(shù)組或結(jié)構(gòu)體的初始化值必須以固定的順序出現(xiàn)比吭,在GNU C中衩藤,通過指定索引或結(jié)構(gòu)體成員名赏表,允許初始化值以任意順序出現(xiàn)底哗。
指定數(shù)組索引的方法是在初始化值錢添加“[INDEX] = ”跋选,當(dāng)然也可以用“[FIRST…LAST] = ”的形式指定一個(gè)范圍。例如炼列,下面的代碼定義一個(gè)數(shù)組俭尖,并把其中的所有元素賦值為0:
unsigned char data[MAX] = {[0…MAX-1] = 0};
下面的代碼借助結(jié)構(gòu)體成員名初始化結(jié)構(gòu)體:
struct file_operations ext2_file_operations = {
llseek: generic_file_llseek,
read: generic_file_read,
write: generic_file_write,
ioctl: ext2_ioctl,
mmap: generic_file_mmap,
open: generic_file_open,
release: ext2_release_file,
fsync: ext2_sync_file,
};
但是焰望,Linux 2.6推薦類似的代碼應(yīng)該盡量采用標(biāo)準(zhǔn)C的方式:
struct file_operations ext2_file_operations = {
.llseek = generic_file_llseek,
.read = generic_file_read,
.write = generic_file_write,
.aio_read = generic_file_aio_read,
.aio_write = generic_file_aio_write,
.ioctl = ext2_ioctl,
.mmap = generic_file_mmap,
.open = generic_file_open,
.release = ext2_release_file,
.fsync = ext2_sync_file,
.writev = generic_file_writev,
.sendfile = generic_file_sendfile,
};
7.當(dāng)前函數(shù)名
GNU C預(yù)定義了兩個(gè)標(biāo)志符保存當(dāng)前函數(shù)的名字,__FUNCTION__保存函數(shù)在源碼中的名字,__PRETTY_FUNCTION__保存帶語言特色的名字捆姜。在C函數(shù)中传趾,這兩個(gè)名字是相同的。
void example()
{
printf(“This is function :%s”,__FUNCTION__);
}
代碼中的__FUNCTION__意味著字符串“example”泥技。C99已經(jīng)支持__func__宏墨缘,因此建議在Linux編程中不再使用__FUNCTION__,轉(zhuǎn)而使用__func__零抬。
8.特殊屬性聲明
GNU C允許聲明函數(shù)镊讼、變量和類型的特殊屬性,以便進(jìn)行手工的代碼優(yōu)化和定制代碼檢查的方法。要指定一個(gè)聲明的屬性,只需要在聲明后添加__attribute__(( ATTRIBUTE ))。其中ATTRIBUTE為屬性說明,如果存在多個(gè)屬性,則以逗號(hào)分隔。GNU C支持noreturn返劲、format、section痰娱、aligned、packed等十多個(gè)屬性。
noreturn屬性作用于函數(shù),表示該函數(shù)從不返回。這會(huì)讓編譯器優(yōu)化代碼,并消除不必要的警告信息。例如:
#define ATTRIB_NORET __attribute__((noreturn)) …
asmlinkage NORET_TYPE void do_exit(long error_code) ATTRIB_NORET;
format屬性也用于函數(shù),表示函數(shù)使用printf狗唉、scanf或strftime風(fēng)格的參數(shù),指定format屬性也可以讓編譯器根據(jù)格式串檢查參數(shù)類型。例如:
asmlinkage int printk(const char * fmt, …) __attribute__ ((format (printf, 1, 2)));
上述代碼中的第1個(gè)參數(shù)是格式串,從第2個(gè)參數(shù)開始都會(huì)根據(jù)printf()函數(shù)的格式串規(guī)則檢查參數(shù)。
unused屬性作用于函數(shù)和變量,表示該函數(shù)或變量可能不會(huì)被用到浴滴,這個(gè)屬性可以避免編譯器產(chǎn)生警告信息降宅。
aligned屬性用于變量瘸恼、結(jié)構(gòu)體或聯(lián)合體靠闭,指定變量芬为、結(jié)構(gòu)體或聯(lián)合體的對(duì)齊方式,以字節(jié)為單位的圆,例如:
struct example_struct{
char a;
int b;
long c;
}__attribute__((aligned(4)))
表示該結(jié)構(gòu)類型的變量以4字節(jié)對(duì)齊梅掠。
packed屬性作用于變量和類型且叁,用于變量或結(jié)構(gòu)體成員時(shí)表示使用最小可能的對(duì)界,用于枚舉、結(jié)構(gòu)體或聯(lián)合體類型時(shí)表示該類型使用最小的內(nèi)存。例如:
struct example_struct {
char a;
int b;
long c __attribute__((packed));
};
編譯器對(duì)結(jié)構(gòu)體成員及變量對(duì)界的目的是為了更快地訪問結(jié)構(gòu)體成員及變量占據(jù)的內(nèi)存看尼。例如狰域,對(duì)于一個(gè)32位的整型變量小压,若以4字節(jié)方式存放(即低兩位地址為00),則CPU在一個(gè)總線周期內(nèi)就可以讀取32位镀娶;若不然闯捎,CPU需要兩次總線周期才能組合為一個(gè)32位整型祭犯。
9.內(nèi)建函數(shù)
GNU C提供了大量的內(nèi)建函數(shù)镰吵,其中大部分是標(biāo)準(zhǔn)C庫函數(shù)的GNU C編譯器內(nèi)建版本搓译,例如memcpy()等盆佣,它們與對(duì)應(yīng)的標(biāo)準(zhǔn)C庫函數(shù)功能相同。
不屬于庫函數(shù)的其他內(nèi)建函數(shù)的命名通常以__builtin開始屈暗,如下所示:
- 內(nèi)建函數(shù)__builtin_return_address(LEVEL)返回當(dāng)前函數(shù)或其調(diào)用者的返回地址,參數(shù)LEVEL指定調(diào)用棧的級(jí)數(shù)艰毒,如0表示當(dāng)前函數(shù)的返回地址狐榔,1表示當(dāng)前函數(shù)的調(diào)用者的返回地址届案;
- 內(nèi)建函數(shù)__builtin_constant_p(EXP)用于判斷一個(gè)值是否為編譯時(shí)常數(shù),如果參數(shù)EXP的值是常數(shù)差凹,函數(shù)返回1,否則返回0凹髓;
- 內(nèi)建函數(shù)__builtin_expect(EXP, C)用于為編譯器提供分支預(yù)測信息,其返回值是整數(shù)表達(dá)式EXP的值锨络,C的值必須是編譯時(shí)常數(shù)赌躺。
例如,下面的檢測第1個(gè)參數(shù)是否為編譯時(shí)常數(shù)以確定采用參數(shù)版本還是非參數(shù)版本的代碼:
#define test_bit(nr, addr) \
(__builtin_constant_p(nr)) ? \
constant_test_bit((nr) , (addr)) : \
variable_test_bit((nr) , (addr))
在使用gcc編譯C程序的時(shí)候羡儿,如果使用“-ansi -pedantic”編譯選項(xiàng)寿谴,則會(huì)告訴編譯器不使用GNU擴(kuò)展語法。例如對(duì)于下面C程序test.c:
struct var_data {
int len;
char data[0];
};
struct var_data a;
直接編譯可以通過:
gcc -c test.c
如果使用“-ansi -pedantic”編譯選項(xiàng)失受,編譯會(huì)報(bào)警:
gcc -ansi -pedantic -c test.c
test.c : 3 :warning: ISO C forbids zero-size array ‘data’
3.3 do{} while(0)
在Linux內(nèi)核中讶泰,經(jīng)常會(huì)看到do{} while(0)這樣的語句,許多人開始都會(huì)疑惑拂到,認(rèn)為do{} while(0)毫無意義痪署,因?yàn)樗粫?huì)執(zhí)行一次,加不加do{} while(0)效果是完全一樣的兄旬。其實(shí)do{} while(0)的用法主要用于宏定義中狼犯。
這里用一個(gè)簡單的宏來演示:
#define SAFE_FREE(P) do{ free(p); p = NULL;} while(0)
假設(shè)這里去掉do…while(0),即定義SAFE_DELETE為:
#define SAFE_FREE(p) free(p);p = NULL;
那么以下代碼:
if(NULL != p)
SAFE_DELETE(p)
else
…/* do something */
會(huì)被展開為:
if(NULL != P)
free(p); p = NULL;
else
…/* do something */
展開的代碼中存在兩個(gè)問題:
- 因?yàn)閕f分支后面有兩個(gè)語句领铐,導(dǎo)致else分支沒有對(duì)應(yīng)的if悯森,編譯失敗绪撵;
- 假設(shè)沒有else分支瓢姻,則SAFE_FREE中的第二個(gè)語句無論if測試是否通過,都會(huì)執(zhí)行音诈。
的確幻碱,將SAFE_FREE的定義加上{}就可以解決上述問題了,即:
#define SAFE_FREE(P) { free(p); p = NULL; }
這樣细溅,代碼展開為:
if(NULL != P)
{free(p); p = NULL;}
else
…/* do something */
但是褥傍,在C程序中,每個(gè)語句后面加分號(hào)是一種約定俗成的習(xí)慣喇聊,那么恍风,如下代碼:
if(NULL != p)
SAFE_DELETE(p);
else
…/* do something */
會(huì)被展開為:
if(NULL != P)
{free(p); p = NULL;};
else
…/* do something */
這樣,else分支就又沒有對(duì)應(yīng)的if了,編譯將無法通過朋贬。假設(shè)用了do{} while(0)鸥咖,情況就不一樣了,同樣的代碼會(huì)被展開為:
if(NULL != P)
do{ free(p); p = NULL; }while(0);
else
…/* do something */
不會(huì)再出現(xiàn)編譯問題兄世。do{} while(0)的使用完全是為了保證宏定義的使用者能無編譯錯(cuò)誤的使用宏啼辣,它不對(duì)其使用者做任何假設(shè)。
3.4 goto
用不用goto一直是一個(gè)著名的爭議話題御滩,Linux內(nèi)核源代碼中對(duì)goto的應(yīng)用非常廣泛鸥拧,但是一般只限于錯(cuò)誤處理中,其結(jié)構(gòu)如:
if(register_a() != 0)
goto err;
if(register_b() != 0)
goto err1;
if(register_c() != 0)
goto err2;
if(register_d() != 0)
goto err3;
……
unregister_d();
err3:
unregister_c();
err2:
unregister_b();
err1:
unregister_a();
err:
return ret;
這種goto用于錯(cuò)誤處理的用法實(shí)在是簡單而高效削解,只需保證在錯(cuò)誤處理時(shí)注銷富弦、資源釋放等與正常的注冊(cè)、資源申請(qǐng)順序相反氛驮。