教你如何學習linux內核

火星人 @ 2014-03-26 , reply:0



毫不誇張地說,Kconfig 和Makefile 是我們瀏覽內核代碼時最為依仗的兩個文件。基本上,Linux內核中每一個目錄下邊都會有一個Kconfig 文件和一個Makefile 文件。對於一個希望能夠在Linux 內核的汪洋代碼里看到一絲曙光的人來說,將它們放在怎麼重要的地位都不過分。

我們去香港,通過海關的時候,總會有免費的地圖和各種指南拿,有了它們在手裡我們才不至於無頭蒼蠅般迷惘的行走在陌生的街道上。即使在內地出去旅遊的時候一般來說也總是會首先找份地圖,當然了,這時就是要去買了,拿是拿不到的,不同的地方有不同的特色, 只不過有的特色是服務,有的特色是索取。

Kconfig 和Makefile 就是Linux Kernel 迷宮裡的地圖。地圖引導我們去認識一個城市,而Kconfig和Makefile 則可以讓我們了解一個Kernel 目錄下面的結構。我們每次瀏覽kernel 尋找屬於自己的那一段代碼時,都應該首先看看目錄下的這兩個文件。

利用Kconfig 和Makefile 尋找目標代碼

就像利用地圖尋找目的地一樣,我們需要利用Kconfig 和Makefile 來尋找所要研究的目標代碼。比如我們打算研究U 盤驅動的實現,因為U 盤是一種storage 設備,所以我們應該先進入到drivers/usb/storage/目錄。但是該目錄下的文件很多,那麼究竟哪些文件才是我們需要關注的?這時就有必要先去閱讀Kconfig 和Makefile 文件。對於Kconfig 文件,我們可以看到下面的選項。

config USB_STORAGE_DATAFAB
bool "Datafab Compact Flash Reader support (EXPERIMENTAL)"
depends on USB_STORAGE && EXPERIMENTAL
help
Support for certain Datafab CompactFlash readers.
Datafab has a web page at <http://www.datafabusa.com/>.

顯然,這個選項和我們的目的沒有關係。首先它專門針對Datafab 公司的產品,其次雖然CompactFlash reader 是一種flash 設備,但顯然不是U 盤。因為drivers/usb/storage 目錄下的代碼是針對usb mass storage 這一類設備,而不是針對某一種特定的設備。U 盤只是usb mass storage設備中的一種。再比如:

config USB_STORAGE_SDDR55
bool "SanDisk SDDR-55 SmartMedia support (EXPERIMENTAL)"
depends on USB_STORAGE && EXPERIMENTAL
help
Say Y here to include additional code to support the Sandisk SDDR-55
SmartMedia reader in the USB Mass Storage driver.

很顯然這個選項是有關SanDisk 產品的,並且針對的是SM 卡,同樣不是U 盤,所以我們也不需要去關注。

事實上,很容易確定,只有選項CONFIG_USB_STORAGE 才是我們真正需要關注的。

9 config USB_STORAGE
10 tristate "USB Mass Storage support"
11 depends on USB && SCSI
12 ---help---
13 Say Y here if you want to connect USB mass storage devices to your
14 computer's USB port. This is the driver you need for USB
15 floppy drives, USB hard disks, USB tape drives, USB CD-ROMs,
16 USB flash devices, and memory sticks, along with
17 similar devices. This driver may also be used for some cameras
18 and card readers.
19
20 This option depends on 'SCSI' support being enabled, but you
21 probably also need 'SCSI device support: SCSI disk support'
22 (BLK_DEV_SD) for most USB storage devices.
23
24 To compile this driver as a module, choose M here: the
25 module will be called usb-storage.

接下來閱讀Makefile 文件。

0 #
1 # Makefile for the USB Mass Storage device drivers.
2 #
3 # 15 Aug 2000, Christoph Hellwig
4 # Rewritten to use lists instead of if-statements.
5 #
6
7 EXTRA_CFLAGS := -Idrivers/scsi
8
9 obj-$(CONFIG_USB_STORAGE) += usb-storage.o
10
11 usb-storage-obj-$(CONFIG_USB_STORAGE_DEBUG) += debug.o
12 usb-storage-obj-$(CONFIG_USB_STORAGE_USBAT) += shuttle_usbat.o
13 usb-storage-obj-$(CONFIG_USB_STORAGE_SDDR09) += sddr09.o
14 usb-storage-obj-$(CONFIG_USB_STORAGE_SDDR55) += sddr55.o
15 usb-storage-obj-$(CONFIG_USB_STORAGE_FREECOM) += freecom.o
16 usb-storage-obj-$(CONFIG_USB_STORAGE_DPCM) += dpcm.o
17 usb-storage-obj-$(CONFIG_USB_STORAGE_ISD200) += isd200.o
18 usb-storage-obj-$(CONFIG_USB_STORAGE_DATAFAB) += datafab.o
19 usb-storage-obj-$(CONFIG_USB_STORAGE_JUMPSHOT) += jumpshot.o
20 usb-storage-obj-$(CONFIG_USB_STORAGE_ALAUDA) += alauda.o
21 usb-storage-obj-$(CONFIG_USB_STORAGE_ONETOUCH) += onetouch.o
22 usb-storage-obj-$(CONFIG_USB_STORAGE_KARMA) += karma.o
23
24 usb-storage-objs := scsiglue.o protocol.o transport.o usb.o \
25 initializers.o $(usb-storage-obj-y)
26
27 ifneq ($(CONFIG_USB_LIBUSUAL),)
28 obj-$(CONFIG_USB) += libusual.o
29 endif

前面通過Kconfig 文件的分析,我們確定了只需要去關注CONFIG_USB_STORAGE 選項。在Makefile 文件里查找CONFIG_USB_STORAGE,從第9 行得知,該選項對應的模塊為usbstorage。因為Kconfig 文件里的其他選項我們都不需要關注,所以Makefile 的11~22 行可以忽略。第24 行意味著我們只需要關注 scsiglue.c、protocol.c、transport.c、usb.c、initializers.c 以及它們同名的.h 頭文件。

Kconfig 和Makefile 很好的幫助我們定位到了所要關注的目標,就像我們到一個陌生的地方要隨身攜帶地圖,當我們學習Linux 內核時,也要謹記尋求Kconfig 和Makefile 的幫助。

透過現象看本質,獸獸門無非就是一些人體藝術展示。同樣往本質里看過去,學習內核,就是學習內核的源代碼,任何內核有關的書籍都是基於內核,而又不高於內核的。

既然要學習內核源碼,就要經常對內核代碼進行分析,而內核代碼千千萬,還前仆後繼的不斷往裡加,這就讓大部分人都有種霧裡看花花不見的無助感。不過 不要怕,孔老夫子早就留給我們了應對之策:敏於事而慎於言,就有道而正焉,可謂好學也已。這就是說,做事要踏實才是好學生好同志,要遵循嚴謹的態度,去理 解每一段代碼的實現,多問多想多記。如果抱著走馬觀花,得過且過的態度,結果極有可能就是一邊看一邊丟,沒有多大的收穫。

假設全國房價上漲1.5%,假設80 后局長是農民子弟??,既然我們的人生充滿了假設,那麼我在這裡假設你現在就迫不及待的希望研究內核中USB 子系統的實現,應該沒有意見吧?那好,下面就以USB子系統的實現分析為標本看看分析內核源碼應該如何入手。

分析README

內核中USB 子系統的代碼位於目錄drivers/usb,這個結論並不需要假設。於是我們進入到該目錄,執行命令ls,結果顯示如下:

atm class core gadget host image misc mon serial storage Kconfig
Makefile README usb-skeleton.c

目錄drivers/usb 共包含有10 個子目錄和4 個文件,usb-skeleton.c 是一個簡單的USB driver的框架,感興趣的可以去看看,目前來說,它還吸引不了我們的眼球。那麼首先應該關注什麼?如果迎面走來一個ppmm,你會首先看臉、腳還是其 它?當然答案依據每個人的癖好會有所不同。不過這裡的問題應該只有一個答案,那就是Kconfig、Makefile、README。

README 里有關於這個目錄下內容的一般性描述,它不是關鍵,只是幫助你了解。再說了,面對「read我吧read 我吧」這麼熱情奔放的呼喚,善良的我們是不可能無動於衷的,所以先來看看裡面都有些什麼內容。

23 Here is a list of what each subdirectory here is, and what is contained in
24 them.
25
26 core/ - This is for the core USB host code, including the
27 usbfs files and the hub class driver ("khubd").
28
29 host/ - This is for USB host controller drivers. This
30 includes UHCI, OHCI, EHCI, and others that might
31 be used with more specialized "embedded" systems.
32
33 gadget/ - This is for USB peripheral controller drivers and
34 the various gadget drivers which talk to them.
35
36
37 Individual USB driver directories. A new driver should be added to the
38 first subdirectory in the list below that it fits into.
39
40 image/ - This is for still image drivers, like scanners or
41 digital cameras.
42 input/ - This is for any driver that uses the input subsystem,
43 like keyboard, mice, touchscreens, tablets, etc.
44 media/ - This is for multimedia drivers, like video cameras,
45 radios, and any other drivers that talk to the v4l
46 subsystem.
47 net/ - This is for network drivers.
48 serial/ - This is for USB to serial drivers.
49 storage/ - This is for USB mass-storage drivers.
50 class/ - This is for all USB device drivers that do not fit
51 into any of the above categories, and work for a range
52 of USB Class specified devices.
53 misc/ - This is for all USB device drivers that do not fit
54 into any of the above categories.

這個README 文件描述了前邊使用ls 命令列出的那10 個文件夾的用途。那麼什麼是USB Core?Linux 內核開發者們,專門寫了一些代碼,負責實現一些核心的功能,為別的設備驅動程序提供服務,比如申請內存,比如實現一些所有的設備都會 需要的公共的函數,並美其名曰USB Core。時代總在發展,當年胖楊貴妃照樣迷死唐明皇,而如今人們欣賞的則是林志玲這樣的魔鬼身材。同樣,早期的Linux 內核,其結構並不是如今天這般有層 次感,遠不像今天這般錯落有致,那時候drivers/usb/這個目錄下邊放了很多很多文件,USB Core 與其他各種設備的驅動程序的代碼都堆砌在這裡,後來,怎奈世間萬千的變幻,總愛把有情的人分兩端。於是在drivers/usb/目錄下面出來了一個core 目錄,就專門放一些核心的代碼,比如初始化整個USB 系統,初始化Root Hub,初始化主機控制器的代碼,再後來甚至把主機控制器相關的代碼也單獨建了一個目錄,叫host 目錄,這是因為USB主機控制器隨著時代的發展,也開 始有了好幾種,不再像剛開始那樣只有一種,所以呢,設計者們把一些主機控制器公共的代碼仍然留在core 目錄下,而一些各主機控制器單獨的代碼則移到 host 目錄下面讓負
責各種主機控制器的人去維護。

那麼USB gadget 那?gadget 白了說就是配件的意思,主要就是一些內部運行Linux 的嵌入式設備,比如PDA,設備本身有USB 設備控制器(USB Device Controller),可以將PC,也就是我們的主機作為master 端,將這樣的設備作為slave 端和主機通過USB 進行通信。從主機的觀點來看, 主機系統的USB驅動程序控制插入其中的USB 設備,而USB gadget 的驅動程序控制外圍設備如何作為一個USB 設備和主機通__________信。比如,我們的嵌入式板子上支持SD 卡,如果我們希望在將板子通過USB 連接到PC 之後,這個SD 卡被模擬成U 盤,那麼就要通過USB gadget 架構的驅動。

剩下的幾個目錄分門別類的放了各種USB 設備的驅動,比如U 盤的驅動在storage 目錄下,觸摸屏和USB 鍵盤滑鼠的驅動在input 目錄下,等等。

我們響應了README 的熱情呼喚,它便給予了我們想要的,通過它我們了解了USB 目錄里的那些文件夾都有著什麼樣的角色。到現在為止,就只剩下內核的地圖——Kconfig 與Makefile 兩個文件了。有地圖在手,對於在內核中遊盪的我們來說,是件很愉悅的事情,不過,因為我們的目的是研究內核對USB 子系統的實現,而不是特定設備或host controller 的驅動,所以這裡的定位很明顯,USB Core就是我們需要關注的對象,那麼接下來就是要對core 目錄中的內容進行定位了。

分析Kconfig 和Makefile

進入到drivers/usb/core 目錄,執行命令ls,結果顯示如下:

Kconfig Makefile buffer.c config.c devices.c devio.c driver.c
endpoint.c file.c generic.c hcd-pci.c hcd.c hcd.h hub.c hub.h
inode.c message.c notify.c otg_whitelist.h quirks.c sysfs.c urb.c
usb.c usb.h

然後執行wc 命令,如下所示。

# wc ?l ./*
148 buffer.c
607 config.c
706 devices.c
1677 devio.c
1569 driver.c
357 endpoint.c
248 file.c
238 generic.c
1759 hcd.c
458 hcd.h
433 hcd-pci.c
3046 hub.c
195 hub.h
758 inode.c
144 Kconfig
21 Makefile
1732 message.c
68 notify.c
112 otg_whitelist.h
161 quirks.c
710 sysfs.c
589 urb.c
984 usb.c
160 usb.h
16880 total

drivers/usb/core 目錄共包括24 個文件,16880 行代碼。core 不愧是core,為大家默默的做這麼多事。不過這麼多文件里不一定都是我們所需要關注的,先拿咱們的地圖來看看接下來該怎麼走。先看看Kconfig 文件,可以看到下面的選項。

15 config USB_DEVICEFS
16 bool "USB device filesystem"
17 depends on USB
18 ---help---
19 If you say Y here (and to "/proc file system support" in the "File
20 systems" section, above), you will get a file /proc/bus/usb/devices
21 which lists the devices currently connected to your USB bus or
22 busses, and for every connected device a file named
23 "/proc/bus/usb/xxx/yyy", where xxx is the bus number and yyy the
24 device number; the latter files can be used by user space programs
25 to talk directly to the device. These files are "virtual", meaning
26 they are generated on the fly and not stored on the hard drive.
27
28 You may need to mount the usbfs file system to see the files, use
29 mount -t usbfs none /proc/bus/usb
30
31 For the format of the various /proc/bus/usb/ files, please read
32 .
33
34 Usbfs files can't handle Access Control Lists (ACL), which are the
35 default way to grant access to USB devices for untrusted users of a
36 desktop system. The usbfs functionality is replaced by real
37 device-nodes managed by udev. These nodes live in /dev/bus/usb and
38 are used by libusb.

選項USB_DEVICEFS 與usbfs 文件系統有關。usbfs 文件系統掛載在/proc/bus/usb 目錄,顯示了當前連接的所有USB 設 備及匯流排的各種信息,每個連接的USB 設備在其中都會有一個對應的文件進行描述。比如文件/proc/bus/usb/xxx/yyy,xxx 表示總__________線的 序號,yyy 表示設備所在匯流排的地址。不過不能夠依賴它們來穩定地訪問設備,因為同一設備兩次連接對應的描述文件可能會不同,比如,第一次連接一個設備 時,它可能是002/027,一段時間后再次連接,它可能就已經改變為002/048。就好比好不容易你暗戀的mm 今天見你的時候對你拋了個媚眼,你心花怒放,趕快去買了100 塊彩票慶祝,到第二天再見到她的時候,她對你說你是誰啊,你悲痛欲絕的刮開那100 塊彩票,上面清一色的謝謝你。

因為usbfs 文件系統並不屬於USB 子系統實現的核心部分,與之相關的代碼我們可以不必關注。

74 config USB_SUSPEND
75 bool "USB selective suspend/resume and wakeup (EXPERIMENTAL)"
76 depends on USB && PM && EXPERIMENTAL
77 help
78 If you say Y here, you can use driver calls or the sysfs
79 "power/state" file to suspend or resume individual USB
80 peripherals.
81
82 Also, USB "remote wakeup" signaling is supported, whereby some
83 USB devices (like keyboards and network adapters) can wake up
84 their parent hub. That wakeup cascades up the USB tree, and
85 could wake the system from states like suspend-to-RAM.
86
87 If you are unsure about this, say N here.

這一項是有關USB 設備的掛起和恢復。開發USB 的人都是節電節能的好孩子,所以協議里就規定了,所有的設備都必須支持掛起狀態,就是說為了達到節 電的目的,當設備在指定的時間內,如果沒有發生匯流排傳輸,就要進入掛起狀態。當它收到一個non-idle 的信號時,就會被喚醒。節約用電從USB 做起。 不過這個與主題也沒太大關係,相關代碼也可以不用關注了。剩下的還有幾項,不過似乎與咱們關係也不大,還是去看看Makefile。

5 usbcore-objs := usb.o hub.o hcd.o urb.o message.o driver.o \
6 config.o file.o buffer.o sysfs.o endpoint.o \
7 devio.o notify.o generic.o quirks.o
8
9 ifeq ($(CONFIG_PCI),y)
10 usbcore-objs += hcd-pci.o
11 endif
12
13 ifeq ($(CONFIG_USB_DEVICEFS),y)
14 usbcore-objs += inode.o devices.o
15 endif
16
17 obj-$(CONFIG_USB) += usbcore.o
18
19 ifeq ($(CONFIG_USB_DEBUG),y)
20 EXTRA_CFLAGS += -DDEBUG
21 endif

Makefile 可比Kconfig 簡略多了,所以看起來也更親切點,咱們總是拿的money 越多越好,看的代碼越少越好。這裡之所以會出現 CONFIG_PCI,是因為通常USB 的Root Hub 包含在一個PCI 設備中。hcd-pci 和hcd 顧名而思義就知道是說主機控制器的,它們實現了主機控制器公共部分,按協議里的說法它們就是 HCDI(HCD 的公共介面),host 目錄下則實現了各種不同的主機控制器。CONFIG_USB_DEVICEFS 前面的Kconfig 文件里也見到了,關於usbfs 的,與咱們的主題無關,inode.c和devices.c 兩個文件也可以不用管了。

那麼我們可以得出結論,為了理解內核對USB 子系統的實現,我們需要研究

buffer.c、config.c、driver.c、 endpoint.c、file.c、generic.c、hcd.chcd.h、hub.c、message.c、notify.c、otg_whitelist.h、quirks.c、sysfs.c、urb.c 和usb.c文件。

這麼看來,好像大都需要關注的樣子,沒有減輕多少壓力,不過這裡本身就是USB Core 部分,是要做很多的事為咱們分憂的,所以多點也是可以理解的。下面的分析,米盧教練說了,內容不重要,重要的是態度。就像韓局長對待日記的態度那樣,嚴謹而細緻。

只要你使用這樣的態度開始分析內核,那麼無論你選擇內核的哪個部分作為切入點,比如USB,比如進程管理,在花費相對不算很多的時間之後,你就會發 現你對內核的理解會上升到另外一個高度,一個抱著情景分析,抱著0.1 內核完全註釋,抱著各種各樣的內核書籍翻來覆去的看很多遍又忘很多遍都無法達到的高 度。請相信我!讓我們在Linux 社區里發出號召:學習內核源碼,從學習韓局長開始!

態度決定一切:從初始化函數開始

任小強們說房價高漲從現在開始,股評家們說牛市從5000 點開始。他們的開始需要我們的錢袋,我們的開始只需要一台電腦,最好再有一杯茶,伴著幾支小曲兒,不盯著錢總是會比較愜意的。生容易,活容易,生活不容易,因為總要盯著錢。

有了地圖Kconfig 和Makefile,我們可以在龐大複雜的內核代碼中定位以及縮小了目標代碼的範圍。那麼現在,為了研究內核對USB 子系統的實現,我們還需要在目標代碼中找到一個突破口,這個突破口就是USB 子系統的初始化代碼。

針對某個子系統或某個驅動,內核使用subsys_initcall 或module_init 宏指定初始化函數。在drivers/usb/core/usb.c 文件中,我們可以發現下面的代碼。

940 subsys_initcall(usb_init);
941 module_exit(usb_exit);

我們看到一個subsys_initcall,它也是一個宏,我們可以把它理解為module_init,只不過因為這部分代碼比較核心,開發者們 把它看作一個子系統,而不僅僅是一個模塊。這也很好理解,usbcore 這個模塊它代表的不是某一個設備,而是所有USB 設備賴以生存的模塊,Linux 中,像這樣一個類別的設備驅動被結為一個子系統。比如PCI 子系統,比如SCSI 子系統,基本上,drivers/目錄下面第一層的每個目錄都算一個子 系統,因為它們代表了一類設備。

subsys_initcall(usb_init)的意思就是告訴我們usb_init 是USB 子系統真正的初始化函數,而usb_exit() 將是整個USB 子系統的結束時的清理函數。於是為了研究USB 子系統在內核中的實現,我們需要從usb_init 函數開始看起。

865 static int __init usb_init(void)
866 {
867 int retval;
868 if (nousb) {
869 pr_info("%s: USB support disabled\n", usbcore_name);
870 return 0;
871 }
872
873 retval = ksuspend_usb_init();
874 if (retval)
875 goto out;
876 retval = bus_register(&usb_bus_type);
877 if (retval)
878 goto bus_register_failed;
879 retval = usb_host_init();
880 if (retval)
881 goto host_init_failed;
882 retval = usb_major_init();
883 if (retval)
884 goto major_init_failed;
885 retval = usb_register(&usbfs_driver);
886 if (retval)
887 goto driver_register_failed;
888 retval = usb_devio_init();
889 if (retval)
890 goto usb_devio_init_failed;
891 retval = usbfs_init();
892 if (retval)
893 goto fs_init_failed;
894 retval = usb_hub_init();
895 if (retval)
896 goto hub_init_failed;
897 retval = usb_register_device_driver(&usb_generic_driver, THIS_MODULE);
898 if (!retval)
899 goto out;
900
901 usb_hub_cleanup();
902 hub_init_failed:
903 usbfs_cleanup();
904 fs_init_failed:
905 usb_devio_cleanup();
906 usb_devio_init_failed:
907 usb_deregister(&usbfs_driver);
908 driver_register_failed:
909 usb_major_cleanup();
910 major_init_failed:
911 usb_host_cleanup();
912 host_init_failed:
913 bus_unregister(&usb_bus_type);
914 bus_register_failed:
915 ksuspend_usb_cleanup();
916 out:
917 return retval;
918 }
(1)__init 標記。

關於usb_init,第一個問題是,第865 行的__init 標記具有什麼意義?

寫過驅動的應該不會陌生,它對內核來說就是一種暗示,表明這個函數僅在初始化期間使用,在模塊被裝載之後,它佔用的資源就會釋放掉用作它處。它的暗 示你懂,可你的暗示,她卻不懂或者懂裝不懂,多麼讓人感傷。它在自己短暫的一生中一直從事繁重的工作,吃的是草吐出的是牛奶,留下的是整個USB子系統的繁榮。

受這種精神所感染,我覺得__________有必要為它說的更多些。__init 的定義在

include/linux/init.h 文件里43 #define __init __attribute__ ((__section__ (".init.text")))

好像這裡引出了更多的疑問,__attribute__是什麼?Linux 內核代碼使用了大量的GNU C 擴展,以至於GNU C 成為能夠編譯內核的唯一編譯器,GNU C 的這些擴展對代碼優化、目標代碼布局、安全檢查等方面也提供了很強的支持。而__attribute__就是這些擴展中的一個,它主要被用來聲明一些特殊的屬性,這些屬性主要被用來指示編譯器進行特定方面的優化和更仔細的代碼檢查。GNU C 支持十幾個屬性,section 是其中的一個,我們查看GCC 的手冊可以看到下面的描述

『section ("section-name")'
Normally, the compiler places the code it generates in the `text' section. Sometimes,
however, you need additional sections, or you need certain particular functions to appear in
special sections.The `section' attribute specifies that a function lives in a particular section.
For example, the declaration:extern void foobar (void) __attribute__ ((section ("bar")));puts
the function 『foobar' in the 『bar' section.Some file formats do not support arbitrary sections so
the 『section' attribute is not available on all platforms. If you need to map the entire
contents of a module to a particular section, consider using the facilities of the linker instead.

通常編譯器將函數放在.text 節,變數放在.data 或.bss 節,使用section 屬性,可以讓編譯器將函數或變數放在指定的節中。那麼前面 對__init 的定義便表示將它修飾的代碼放在.init.text 節。連接器可以把相同節的代碼或數據安排在一起,比如__init 修飾的所有代碼都會 被放在.init.text 節里,初始化結束后就可以釋放這部分內存。

問題可以到此為止,也可以更深入,即內核又是如何調用到這些__init 修飾的初始化函數?要回答這個問題,還需要回顧一下subsys_initcall 宏,它也在include/linux/init.h 里定義

125 #define subsys_initcall(fn) __define_initcall("4",fn,4)

這裡又出現了一個宏__define_initcall,它用於將指定的函數指針fn 放到initcall.init 節里 而對於具體的subsys_initcall 宏,則是把fn 放到.initcall.init 的子節.initcall4.init 里。要弄清楚.initcall.init、.init.text 和.initcall4.init 這樣的東東,我們還需要了解一點內核可執行文件相關的概念。內核可執行文件由許多鏈接在一起的對象文件組成。對象文件有許多節,如文本、數據、init 數據、bass 等等。這些對象文件都是__________由一個稱為鏈接器 腳本的文件鏈接並裝入的。這個鏈接器腳本的功能是將輸入對象文件的各節映射到輸出文件中;換句話說,它將所有輸入對象文件都鏈接到單一的可執行文件中,將該可執行文件的各節裝入到指定地址處。 vmlinux.lds 是存在於arch// 目錄中的內核鏈接器腳本,它負責鏈接內核的各個節並將它們裝入內存中特定偏移量處。

我可以負責任的告訴你,要看懂vmlinux.lds 這個文件是需要一番功夫的,不過大家都是聰明人,聰明人做聰明事,所以你需要做的只是搜索initcall.init,然後便會看到似曾相識的內容

__inicall_start = .;
.initcall.init : AT(ADDR(.initcall.init) ? 0xC0000000)
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
__initcall_end = .;

這裡的__initcall_start 指向.initcall.init 節的開始,__initcall_end 指向它的結尾。而.initcall.init節又被分為了7 個子節,分別是

.initcall1.init
.initcall2.init
.initcall3.init
.initcall4.init
.initcall5.init
.initcall6.init
.initcall7.init

我們的subsys_initcall 宏便是將指定的函數指針放在了.initcall4.init 子節。其它的比如core_initcall 將函數指針放在.initcall1.init 子節,device_initcall 將函數指針放在了.initcall6.init 子節等等,都可以從 include/linux/init.h 文件找到它們的定義。各個位元組的順序是確定的,即先調用.initcall1.init 中的指針再調 用.initcall2.init 中的函數指針,等等。__init 修飾的初始化函數在內核初始化過程中調用的順序和.initcall.init 節里函 數指針的順序有關,不同的初始化函數被放在不同的子節中,因此也就決定了它們的調用順序。

至於實際執行函數調用的地方,就在/init/main.c 文件里,內核的初始化么,不在那裡還能在哪裡,裡面的do_initcalls 函數會直接用到這裡的__initcall_start、__initcall_end 來進行判斷。(2)模塊參數。

關於usb_init 函數,第二個問題是,第868 行的nousb 表示什麼?

知道C 語言的人都會知道nousb 是一個標誌,只是不同的標誌有不一樣的精彩,這裡的nousb 是用來讓我們在啟動內核的時候通過內核參數去掉 USB 子系統的,Linux 社會是一個很人性化的世界,它不會去逼迫我們接受USB,一切都只關乎我們自己的需要。不過我想我們一般來說是不會去指定nousb 的吧。如果你真的指定了nousb,那它就只會幽怨的說一句「USB support disabled」,然後退出usb_init。

nousb 在drivers/usb/core/usb.c 文件中定義為:

static int nousb; /* Disable USB when built into kernel image */
module_param_named(autosuspend, usb_autosuspend_delay, int, 0644);
MODULE_PARM_DESC(autosuspend, "default autosuspend delay");

從中可知nousb 是個模塊參數。關於模塊參數,我們都知道可以在載入模塊的時候可以指定,但是如何在內核啟動的時候指定?

打開系統的grub 文件,然後找到kernel 行,比如:

kernel /boot/vmlinuz-2.6.18-kdb root=/dev/sda1 ro splash=silent vga=0x314

其中的root,splash,vga 等都表示內核參數。當某一模塊被編譯進內核的時候,它的模塊參數便需要在kernel 行來指定,格式為「模塊名.參數=值」,比如:

modprobe usbcore autosuspend=2

對應到kernel 行,即為 :

usbcore.autosuspend=2

通過命令「modinfo -p ${modulename}」可以得知一個模塊有哪些參數可以使用。同時,對於已經載入到內核里的模塊,它們的模塊參數會列舉在/sys/module /${modulename}/parameters/目錄下面,可以使用「echo -n ${value} > /sys/module/${modulename}/parameters/${parm}」這樣的命令去修改。

(3)可變參數宏。
關於usb_init 函數,第三個問題是,pr_info 如何實現與使用?pr_info 只是一個列印信息的可辨參數宏,printk 的變體,在include/linux/kernel.h 里定義:

242 #define pr_info(fmt,arg...) \
243 printk(KERN_INFO fmt,##arg)

99 年的ISO C 標準里規定了可變參數宏,和函數語法類似,比如

#define debug(format, ...) fprintf (stderr, format, __VA_ARGS__)裡面的「…」就表示可變參數,調用時,它們就會替代宏體里的__VA_ARGS__。GCC 總是會顯得特立獨行一些,它支持更複雜的形式,可以給可變參數取個名字,比如#define debug(format, args...) fprintf (stderr, format, args)有了名字總是會容易交流一些。是不是與pr_info 比較接近了?除了『##』,它主要是針對空參數的情況。既然說是可變參數,那傳遞空參數也總是可以的,空即是多,多即是空,股市裡的哲理這裡同樣也是適合的。如果沒有『##』,傳遞空參數的時候,比如debug ("A message");展開后,裡面的字元串後面會多個多餘的逗號。這個逗號你應該不會喜歡,而『##』則會使預處理器去掉這個多餘的逗號。

關於usb_init 函數,上面的三個問題之外,餘下的代碼分別完成usb 各部分的初始化,接下來就需要圍繞它們分別進行深入分析。因為這裡只是演示如何入手分析,展示的只是一種態度,所以具體的深入分析就免了吧。

對於學習來說,無論是在學校的課堂學習,還是這裡說的內核學習,效果好或者壞,最主要取決於兩個方面——方法論和心理。注意,我無視了智商的差異,這玩意兒玄之又玄,岔開了說,屬於迷信的範疇。前面又是Kernel 地圖,又是如何入手,說的都是方法論的問題,那麼這裡要面對的就主要是心理上的問題。

而心理上的問題主要有兩個,一個是盲目,就是在能夠熟練適用Linux 之前,對Linux 為何物還說不出個道道來,就迫不及待的盲目的去研究內核的 源代碼。這一部分人會覺得既然是學習內核,那麼耗費時間在熟悉Linux 的基本操作上純粹是浪費寶貴的時間和感情。不過這樣雖然很有韓峰同志的熱情和幹勁兒,但明顯走入了一種心理誤區。重述Linus 的那句話:要先會使用它。

第二個就是恐懼。人類進化這麼多年,面對複雜的物體和事情還是總會有天生的懼怕感,體現在內核學習上面就是:那麼龐大複雜的內核代碼,讓人面對起來該情何以堪啊!有了這種恐懼無力感存在,心理上就會去排斥面對接觸內核源碼,寧願去抱著情景分析,搜集各種各樣五花八門的內核書籍放在那裡屯著,看了又忘,忘了又看,也不大情願去認真細緻得瀏覽源碼。

這個時候,我們在心理上是脆弱得,我們忘記了芙蓉姐姐,工行女之所以紅起來,不是她們有多好,而是因為她們得心理足夠堅強。是的,除了向韓局長學習態度,我們還要向湧現出來的無數個芙蓉姐姐和工行女學習堅強的心理。

有必要再強調一次,學習內核,就是學習內核的源代碼,任何內核有關的書籍都是基於內核,而又不高於內核的。內核源碼本身就是最好的參考資料,其他任何經典或非經典的書最多只是起到個輔助作用,不能也不應該取代內核代碼在我們學習過程中的主導地位。

「世界上最缺的不是金錢,而是資源。」當我在一份報紙上看到這句大大標題時,我的第一反應是——作者一定是個自然環保主義者,然後我在羞愧得反省自身的同時油然生出一股對這樣的無產主義理想者無比崇敬的情緒來。於是,我繼續往下看,「因此在XXX 還未正式面市之時,前來諮詢的客戶已經不少,這些有眼光的購房者明白,誰能在目前最好的購房機會下最大化地佔有絕版資 源,誰就掌控了未來財富流向。」(為了避免做廣告的嫌疑,請允許我使用XXX 代替該樓盤的名字。)頓時,我悟道了!

其實,韓峰同志已經在日記里告訴了我們資源的重要性,因此我們在學習韓峰同志嚴謹細緻的態度同時,還要領悟他對資源的靈活運用。只有在以內核源碼為中心,堅持各種學習資源的長期建設不動搖,才能達到韓局長那樣的高度,俯視Linux 內核世界里的人生百態。

注意,這個觀點與前面所說的學習效果主要取決於方法論和心理兩個方面並不矛盾,它們屬於不同層次上的問題。



[火星人 via ] 教你如何學習linux內核已經有202次圍觀

http://www.coctec.com/docs/linux/show-post-183284.html