小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

史上最經(jīng)典的Linux內(nèi)核學(xué)習(xí)方法論

 lifei_szdz 2019-03-17

http://blog./uid-20586655-id-1752570.html看此鏈接

http://blog./uid-20586655-id-1752570.html

  從前在學(xué)校,混了四年,沒有學(xué)到任何東西,每天就是逃課,上網(wǎng),玩游戲,睡覺。畢業(yè)的時候,人家跟我說Makefile我完全不知,但是一說Make Love我就來勁了,現(xiàn)在想來依然覺得丟人。

  毫不夸張地說,Kconfig和Makefile是我們?yōu)g覽內(nèi)核代碼時最為依仗的兩個文件?;旧希琇inux內(nèi)核中每一個目錄下邊都會有一個Kconfig文件和一個Makefile文件。對于一個希望能夠在Linux內(nèi)核的汪洋代碼里看到一絲曙光的人來說,將它們放在怎么重要的地位都不過分。

  我們?nèi)ハ愀郏ㄟ^海關(guān)的時候,總會有免費(fèi)的地圖和各種指南拿,有了它們在手里我們才不至于無頭蒼蠅般迷惘的行走在陌生的街道上。即使在內(nèi)陸出去旅游的時候一般來說也總是會首先找份地圖,當(dāng)然了,這時就是要去買了,拿是拿不到的,不同的地方有不同的特色, 只不過有的特色是服務(wù),有的特色是索取。

  Kconfig和Makefile就是Linux Kernel迷宮里的地圖。地圖引導(dǎo)我們?nèi)フJ(rèn)識一個城市,而Kconfig和Makefile則可以讓我們了解一個Kernel目錄下面的結(jié)構(gòu)。我們每次瀏覽kernel尋找屬于自己的那一段代碼時,都應(yīng)該首先看看目錄下的這兩個文件。

  利用Kconfig和Makefile尋找目標(biāo)代碼

  就像利用地圖尋找目的地一樣,我們需要利用Kconfig和Makefile來尋找所要研究的目標(biāo)代碼。比如我們打算研究U盤驅(qū)動的實(shí)現(xiàn),因?yàn)閁盤是一種storage設(shè)備,所以我們應(yīng)該先進(jìn)入到drivers/usb/storage/目錄。但是該目錄下的文件很多,那么究竟哪些文件才是我們需要關(guān)注的?這時就有必要先去閱讀Kconfig和Makefile文件。

  對于Kconfig文件,我們可以看到下面的選項(xiàng)。



#div_code img{border:0px;} 

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/>.

  顯然,這個選項(xiàng)和我們的目的沒有關(guān)系。首先它專門針對Datafab公司的產(chǎn)品,其次雖然CompactFlash reader是一種flash設(shè)備,但顯然不是U盤。因?yàn)閐rivers/usb/storage目錄下的代碼是針對usb mass storage這一類設(shè)備,而不是針對某一種特定的設(shè)備。U盤只是usb mass storage設(shè)備中的一種。再比如:



#div_code img{border:0px;} 

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.

  很顯然這個選項(xiàng)是有關(guān)SanDisk產(chǎn)品的,并且針對的是SM卡,同樣不是U盤,所以我們也不需要去關(guān)注。

  事實(shí)上,很容易確定,只有選項(xiàng)CONFIG_USB_STORAGE才是我們真正需要關(guān)注的。



#div_code img{border:0px;} 

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文件。



#div_code img{border:0px;} 

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文件的分析,我們確定了只需要去關(guān)注CONFIG_USB_STORAGE選項(xiàng)。在Makefile文件里查找CONFIG_USB_STORAGE,從第9行得知,該選項(xiàng)對應(yīng)的模塊為usbstorage。因?yàn)镵config文件里的其他選項(xiàng)我們都不需要關(guān)注,所以Makefile的11~22行可以忽略。第24行意味著我們只需要關(guān)注 scsiglue.c、protocol.c、transport.c、usb.c、initializers.c以及它們同名的.h頭文件。

  Kconfig和Makefile很好的幫助我們定位到了所要關(guān)注的目標(biāo),就像我們到一個陌生的地方要隨身攜帶地圖,當(dāng)我們學(xué)習(xí)Linux內(nèi)核時,也要謹(jǐn)記尋求Kconfig和Makefile的幫助。

  透過現(xiàn)象看本質(zhì),獸獸門無非就是一些人體藝術(shù)展示。同樣往本質(zhì)里看過去,學(xué)習(xí)內(nèi)核,就是學(xué)習(xí)內(nèi)核的源代碼,任何內(nèi)核有關(guān)的書籍都是基于內(nèi)核,而又不高于內(nèi)核的。

  既然要學(xué)習(xí)內(nèi)核源碼,就要經(jīng)常對內(nèi)核代碼進(jìn)行分析,而內(nèi)核代碼千千萬,還前仆后繼的不斷往里加,這就讓大部分人都有種霧里看花花不見的無助感。不過 不要怕,孔老夫子早就留給我們了應(yīng)對之策:敏于事而慎于言,就有道而正焉,可謂好學(xué)也已。這就是說,做事要踏實(shí)才是好學(xué)生好同志,要遵循嚴(yán)謹(jǐn)?shù)膽B(tài)度,去理 解每一段代碼的實(shí)現(xiàn),多問多想多記。如果抱著走馬觀花,得過且過的態(tài)度,結(jié)果極有可能就是一邊看一邊丟,沒有多大的收獲。

  假設(shè)全國房價上漲1.5%,假設(shè)80后局長是農(nóng)民子弟??,既然我們的人生充滿了假設(shè),那么我在這里假設(shè)你現(xiàn)在就迫不及待的希望研究內(nèi)核中USB子系統(tǒng)的實(shí)現(xiàn),應(yīng)該沒有意見吧?那好,下面就以USB子系統(tǒng)的實(shí)現(xiàn)分析為標(biāo)本看看分析內(nèi)核源碼應(yīng)該如何入手。

  分析README

  內(nèi)核中USB子系統(tǒng)的代碼位于目錄drivers/usb,這個結(jié)論并不需要假設(shè)。于是我們進(jìn)入到該目錄,執(zhí)行命令ls,結(jié)果顯示如下:

  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的框架,感興趣的可以去看看,目前來說,它還吸引不了我們的眼球。那么首先應(yīng)該關(guān)注什么?如果迎面走來一個ppmm,你會首先看臉、腳還是其 它?當(dāng)然答案依據(jù)每個人的癖好會有所不同。不過這里的問題應(yīng)該只有一個答案,那就是Kconfig、Makefile、README。

  README里有關(guān)于這個目錄下內(nèi)容的一般性描述,它不是關(guān)鍵,只是幫助你了解。再說了,面對“read我吧read我吧”這么熱情奔放的呼喚,善良的我們是不可能無動于衷的,所以先來看看里面都有些什么內(nèi)容。



#div_code img{border:0px;} 

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內(nèi)核開發(fā)者們,專門寫了一些代碼,負(fù)責(zé)實(shí)現(xiàn)一些核心的功能,為別的設(shè)備驅(qū)動程序提供服務(wù),比如申請內(nèi)存,比如實(shí)現(xiàn)一些所有的設(shè)備都會 需要的公共的函數(shù),并美其名曰USB Core。

  時代總在發(fā)展,當(dāng)年胖楊貴妃照樣迷死唐明皇,而如今人們欣賞的則是林志玲這樣的魔鬼身材。同樣,早期的Linux內(nèi)核,其結(jié)構(gòu)并不是如今天這般有層 次感,遠(yuǎn)不像今天這般錯落有致,那時候drivers/usb/這個目錄下邊放了很多很多文件,USB Core與其他各種設(shè)備的驅(qū)動程序的代碼都堆砌

  在這里,后來,怎奈世間萬千的變幻,總愛把有情的人分兩端。于是在drivers/usb/目錄下面出來了 一個core目錄,就專門放一些核心的代碼,比如初始化整個USB系統(tǒng),初始化Root Hub,初始化主機(jī)控制器的代碼,再后來甚至把主機(jī)控制器相關(guān)的代碼也單獨(dú)建了一個目錄,叫host目錄,這是因?yàn)閁SB主機(jī)控制器隨著時代的發(fā)展,也開 始有了好幾種,不再像剛開始那樣只有一種,所以呢,設(shè)計(jì)者們把一些主機(jī)控制器公共的代碼仍然留在core目錄下,而一些各主機(jī)控制器單獨(dú)的代碼則移到 host目錄下面讓負(fù)責(zé)各種主機(jī)控制器的人去維護(hù)。

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

  剩下的幾個目錄分門別類的放了各種USB設(shè)備的驅(qū)動,比如U盤的驅(qū)動在storage目錄下,觸摸屏和USB鍵盤鼠標(biāo)的驅(qū)動在input目錄下,等等。


  我們響應(yīng)了README的熱情呼喚,它便給予了我們想要的,通過它我們了解了USB目錄里的那些文件夾都有著什么樣的角色。到現(xiàn)在為止,就只剩下內(nèi)核的地圖——Kconfig與Makefile兩個文件了。

  有地圖在手,對于在內(nèi)核中游蕩的我們來說,是件很愉悅的事情,不過,因?yàn)槲覀兊哪康氖茄芯績?nèi)核對USB子系統(tǒng)的實(shí)現(xiàn),而不是特定設(shè)備或host controller的驅(qū)動,所以這里的定位很明顯,USB Core就是我們需要關(guān)注的對象,那么接下來就是要對core目錄中的內(nèi)容進(jìn)行定位了。

  分析Kconfig和Makefile

  進(jìn)入到drivers/usb/core目錄,執(zhí)行命令ls,結(jié)果顯示如下:



#div_code img{border:0px;} 

  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

  然后執(zhí)行wc命令,如下所示。



#div_code img{border:0px;} 

# 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,為大家默默的做這么多事。不過這么多文件里不一定都是我們所需要關(guān)注的,先拿咱們的地圖來看看接下來該怎么走。


  先看看Kconfig文件,可以看到下面的選項(xiàng)。



#div_code img{border:0px;} 

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 <file:Documentation/usb/proc_usb_info.txt>.

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.

  選項(xiàng)USB_DEVICEFS與usbfs文件系統(tǒng)有關(guān)。usbfs文件系統(tǒng)掛載在/proc/bus/usb目錄,顯示了當(dāng)前連接的所有USB 設(shè) 備及總線的各種信息,每個連接的USB設(shè)備在其中都會有一個對應(yīng)的文件進(jìn)行描述。比如文件/proc/bus/usb/xxx/yyy,xxx表示總__________線的 序號,yyy表示設(shè)備所在總線的地址。不過不能夠依賴它們來穩(wěn)定地訪問設(shè)備,因?yàn)橥辉O(shè)備兩次連接對應(yīng)的描述文件可能會不同,比如,第一次連接一個設(shè)備 時,它可能是002/027,一段時間后再次連接,它可能就已經(jīng)改變?yōu)?02/048。

  就好比好不容易你暗戀的mm今天見你的時候?qū)δ銙伭藗€媚眼,你心花怒放,趕快去買了100塊彩票慶祝,到第二天再見到她的時候,她對你說你是誰啊,你悲痛欲絕的刮開那100塊彩票,上面清一色的謝謝你。

  因?yàn)閡sbfs文件系統(tǒng)并不屬于USB子系統(tǒng)實(shí)現(xiàn)的核心部分,與之相關(guān)的代碼我們可以不必關(guān)注。



#div_code img{border:0px;} 

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.

  這一項(xiàng)是有關(guān)USB設(shè)備的掛起和恢復(fù)。開發(fā)USB的人都是節(jié)電節(jié)能的好孩子,所以協(xié)議里就規(guī)定了,所有的設(shè)備都必須支持掛起狀態(tài),就是說為了達(dá)到節(jié) 電的目的,當(dāng)設(shè)備在指定的時間內(nèi),如果沒有發(fā)生總線傳輸,就要進(jìn)入掛起狀態(tài)。當(dāng)它收到一個non-idle的信號時,就會被喚醒。節(jié)約用電從USB做起。 不過這個與主題也沒太大關(guān)系,相關(guān)代碼也可以不用關(guān)注了。

  剩下的還有幾項(xiàng),不過似乎與咱們關(guān)系也不大,還是去看看Makefile。



#div_code img{border:0px;} 

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簡略多了,所以看起來也更親切點(diǎn),咱們總是拿的money越多越好,看的代碼越少越好。這里之所以會出現(xiàn) CONFIG_PCI,是因?yàn)橥ǔSB的Root Hub包含在一個PCI設(shè)備中。hcd-pci和hcd顧名而思義就知道是說主機(jī)控制器的,它們實(shí)現(xiàn)了主機(jī)控制器公共部分,按

  協(xié)議里的說法它們就是 HCDI(HCD的公共接口),host目錄下則實(shí)現(xiàn)了各種不同的主機(jī)控制器。CONFIG_USB_DEVICEFS前面的Kconfig文件里也見到了,關(guān)于usbfs的,與咱們的主題無關(guān),inode.c和devices.c兩個文件也可以不用管了。

  那么我們可以得出結(jié)論,為了理解內(nèi)核對USB子系統(tǒng)的實(shí)現(xiàn),我們需要研究

  buffer.c、config.c、driver.c、 endpoint.c、file.c、generic.c、hcd.c

  hcd.h、hub.c、message.c、notify.c、otg_whitelist.h、quirks.c、sysfs.c、urb.c 和usb.c文件。

  這么看來,好像大都需要關(guān)注的樣子,沒有減輕多少壓力,不過這里本身就是USB Core部分,是要做很多的事為咱們分憂的,所以多點(diǎn)也是可以理解的。


  下面的分析,米盧教練說了,內(nèi)容不重要,重要的是態(tài)度。就像韓局長對待日記的態(tài)度那樣,嚴(yán)謹(jǐn)而細(xì)致。

  只要你使用這樣的態(tài)度開始分析內(nèi)核,那么無論你選擇內(nèi)核的哪個部分作為切入點(diǎn),比如USB,比如進(jìn)程管理,在花費(fèi)相對不算很多的時間之后,你就會發(fā) 現(xiàn)你對內(nèi)核的理解會上升到另外一個高度,一個抱著情景分析,抱著0.1內(nèi)核完全注釋,抱著各種各樣的內(nèi)核書籍翻來覆去的看很多遍又忘很多遍都無法達(dá)到的高 度。請相信我!讓我們在Linux社區(qū)里發(fā)出號召:學(xué)習(xí)內(nèi)核源碼,從學(xué)習(xí)韓局長開始!

  態(tài)度決定一切:從初始化函數(shù)開始

  任小強(qiáng)們說房價高漲從現(xiàn)在開始,股評家們說牛市從5000點(diǎn)開始。他們的開始需要我們的錢袋,我們的開始只需要一臺電腦,最好再有一杯茶,伴著幾支小曲兒,不盯著錢總是會比較愜意的。生容易,活容易,生活不容易,因?yàn)榭傄⒅X。

  有了地圖Kconfig和Makefile,我們可以在龐大復(fù)雜的內(nèi)核代碼中定位以及縮小了目標(biāo)代碼的范圍。那么現(xiàn)在,為了研究內(nèi)核對USB子系統(tǒng)的實(shí)現(xiàn),我們還需要在目標(biāo)代碼中找到一個突破口,這個突破口就是USB子系統(tǒng)的初始化代碼。

  針對某個子系統(tǒng)或某個驅(qū)動,內(nèi)核使用subsys_initcall或module_init宏指定初始化函數(shù)。在drivers/usb/core/usb.c文件中,我們可以發(fā)現(xiàn)下面的代碼。

  940 subsys_initcall(usb_init);

  941 module_exit(usb_exit);

  我們看到一個subsys_initcall,它也是一個宏,我們可以把它理解為module_init,只不過因?yàn)檫@部分代碼比較核心,開發(fā)者們 把它看作一個子系統(tǒng),而不僅僅是一個模塊。這也很好理解,usbcore這個模塊它代表的不是某一個設(shè)備,而是所有USB設(shè)備賴以生存的模塊,Linux 中,像這樣一個類別的設(shè)備驅(qū)動被歸結(jié)為一個子系統(tǒng)。比如PCI子系統(tǒng),比如SCSI子系統(tǒng),基本上,drivers/目錄下面第一層的每個目錄都算一個子 系統(tǒng),因?yàn)樗鼈兇砹艘活愒O(shè)備。

  subsys_initcall(usb_init)的意思就是告訴我們usb_init是USB子系統(tǒng)真正的初始化函數(shù),而usb_exit() 將是整個USB子系統(tǒng)的結(jié)束時的清理函數(shù)。于是為了研究USB子系統(tǒng)在內(nèi)核中的實(shí)現(xiàn),

  我們需要從usb_init函數(shù)開始看起。



#div_code img{border:0px;} 

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標(biāo)記。

  關(guān)于usb_init,第一個問題是,第865行的__init標(biāo)記具有什么意義?

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

  受這種精神所感染,我覺得__________有必要為它說的更多些。__init的定義在include/linux/init.h文件里43 #define __init __attribute__ ((__section__ (".init.text")))

  好像這里引出了更多的疑問,__attribute__是什么?Linux內(nèi)核代碼使用了大量的GNU C擴(kuò)展,以至于GNU C成為能夠編譯內(nèi)核的唯一編譯器,GNU C的這些擴(kuò)展對代碼優(yōu)化、目標(biāo)代碼布局、安全檢查等方面也提供了很強(qiáng)的支持。而__attribute__就是這些擴(kuò)展中的一個,它主要被用來聲明一些特殊的屬性,這些屬性主要被用來指示編譯器進(jìn)行特定方面的優(yōu)化和更仔細(xì)的代碼檢查。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.

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

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

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

  這里又出現(xiàn)了一個宏__define_initcall,它用于將指定的函數(shù)指針fn放到initcall.init節(jié)里 而對于具體的subsys_initcall宏,則是把fn放到.initcall.init的子節(jié).initcall4.init里。要弄清楚.initcall.init、.init.text和.initcall4.init這樣的東東,我們還需要了解一點(diǎn)內(nèi)核可執(zhí)行文件相關(guān)的概念。

  內(nèi)核可執(zhí)行文件由許多鏈接在一起的對象文件組成。對象文件有許多節(jié),如文本、數(shù)據(jù)、init數(shù)據(jù)、bass等等。這些對象文件都是__________由一個稱為鏈接器 腳本的文件鏈接并裝入的。這個鏈接器腳本的功能是將輸入對象文件的各節(jié)映射到輸出文件中;換句話說,它將所有輸入對象文件都鏈接到單一的可執(zhí)行文件中,將 該可執(zhí)行文件的各節(jié)裝入到指定地址處。 vmlinux.lds是存在于arch// 目錄中的內(nèi)核鏈接器腳本,它負(fù)責(zé)鏈接內(nèi)核的各個節(jié)并將它們裝入內(nèi)存中特定偏移量處。

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



#div_code img{border:0px;} 

__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節(jié)的開始,__initcall_end指向它的結(jié)尾。而.initcall.init節(jié)又被分為了7個子節(jié),分別是



#div_code img{border:0px;} 

.initcall1.init

.initcall2.init

.initcall3.init

.initcall4.init

.initcall5.init

.initcall6.init

.initcall7.init

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

  至于實(shí)際執(zhí)行函數(shù)調(diào)用的地方,就在/init/main.c文件里,內(nèi)核的初始化么,不在那里還能在哪里,里面的do_initcalls函數(shù)會直接用到這里的__initcall_start、__initcall_end來進(jìn)行判斷。


  (2)模塊參數(shù)。

  關(guān)于usb_init函數(shù),第二個問題是,第868行的nousb表示什么?

  知道C語言的人都會知道nousb是一個標(biāo)志,只是不同的標(biāo)志有不一樣的精彩,這里的nousb是用來讓我們在啟動內(nèi)核的時候通過內(nèi)核參數(shù)去掉 USB子系統(tǒng)的,Linux社會是一個很人性化的世界,它不會去逼迫我們接受USB,一切都只關(guān)乎我們自己的需要。不過我想我們一般來說是不會去指定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是個模塊參數(shù)。關(guān)于模塊參數(shù),我們都知道可以在加載模塊的時候可以指定,但是如何在內(nèi)核啟動的時候指定?

  打開系統(tǒng)的grub文件,然后找到kernel行,比如:

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

  其中的root,splash,vga等都表示內(nèi)核參數(shù)。當(dāng)某一模塊被編譯進(jìn)內(nèi)核的時候,它的模塊參數(shù)便需要在kernel行來指定,格式為“模塊名.參數(shù)=值”,比如:

  modprobe usbcore autosuspend=2

  對應(yīng)到kernel行,即為 :

  usbcore.autosuspend=2

  通過命令“modinfo -p ${modulename}”可以得知一個模塊有哪些參數(shù)可以使用。同時,對于已經(jīng)加載到內(nèi)核里的模塊,它們的模塊參數(shù)會列舉在/sys/module /${modulename}/parameters/目錄下面,可以使用“echo -n ${value} > /sys/module/${modulename}/parameters/$

  {parm}”這樣的命令去修改。

  (3)可變參數(shù)宏。

  關(guān)于usb_init函數(shù),第三個問題是,pr_info如何實(shí)現(xiàn)與使用?pr_info只是一個打印信息的可辨參數(shù)宏,printk的變體,在include/linux/kernel.h里定義:

  242 #define pr_info(fmt,arg...) \

  243 printk(KERN_INFO fmt,##arg)

  99年的ISO C標(biāo)準(zhǔn)里規(guī)定了可變參數(shù)宏,和函數(shù)語法類似,比如

  #define debug(format, ...) fprintf (stderr, format, __VA_ARGS__)里面的“…”就表示可變參數(shù),調(diào)用時,它們就會替代宏體里的__VA_ARGS__。GCC總是會顯得特立獨(dú)行一些,它支持更復(fù)雜的形式,可以給可變參數(shù)取個名字,比如#define debug(format, args...) fprintf (stderr, format, args)

  有了名字總是會容易交流一些。是不是與pr_info比較接近了?除了‘##’,它主要是針對空參數(shù)的情況。既然說是可變參數(shù),那傳遞空參數(shù)也總是可以的,空即是多,多即是空,股市里的哲理這里同樣也是適合的。如果沒有‘##’,傳遞空參數(shù)的時候,比如debug ("A message");展開后,里面的字符串后面會多個多余的逗號。這個逗號你應(yīng)該不會喜歡,而‘##’則會使預(yù)處理器去掉這個多余的逗號。


  關(guān)于usb_init函數(shù),上面的三個問題之外,余下的代碼分別完成usb各部分的初始化,接下來就需要圍繞它們分別進(jìn)行深入分析。因?yàn)檫@里只是演示如何入手分析,展示的只是一種態(tài)度,所以具體的深入分析就免了吧。

  對于學(xué)習(xí)來說,無論是在學(xué)校的課堂學(xué)習(xí),還是這里說的內(nèi)核學(xué)習(xí),效果好或者壞,最主要取決于兩個方面——方法論和心理。注意,我無視了智商的差異,這玩意兒玄之又玄,岔開了說,屬于迷信的范疇。

  前面又是Kernel地圖,又是如何入手,說的都是方法論的問題,那么這里要面對的就主要是心理上的問題。

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

  第二個就是恐懼。人類進(jìn)化這么多年,面對復(fù)雜的物體和事情還是總會有天生的懼怕感,體現(xiàn)在內(nèi)核學(xué)習(xí)上面就是:那么龐大復(fù)雜的內(nèi)核代碼,讓人面對起來該情何以堪啊!

  有了這種恐懼無力感存在,心理上就會去排斥面對接觸內(nèi)核源碼,寧愿去抱著情景分析,搜集各種各樣五花八門的內(nèi)核書籍放在那里屯著,看了又忘,忘了又看,也不大情愿去認(rèn)真細(xì)致得瀏覽源碼。

  這個時候,我們在心理上是脆弱得,我們忘記了芙蓉姐姐,工行女之所以紅起來,不是她們有多好,而是因?yàn)樗齻兊眯睦碜銐驁?jiān)強(qiáng)。是的,除了向韓局長學(xué)習(xí)態(tài)度,我們還要向涌現(xiàn)出來的無數(shù)個芙蓉姐姐和工行女學(xué)習(xí)堅(jiān)強(qiáng)的心理。

  有必要再強(qiáng)調(diào)一次,學(xué)習(xí)內(nèi)核,就是學(xué)習(xí)內(nèi)核的源代碼,任何內(nèi)核有關(guān)的書籍都是基于內(nèi)核,而又不高于內(nèi)核的。內(nèi)核源碼本身就是最好的參考資料,其他任何經(jīng)典或非經(jīng)典的書最多只是起到個輔助作用,不能也不應(yīng)該取代內(nèi)核代碼在我們學(xué)習(xí)過程中的主導(dǎo)地位。

  “世界上最缺的不是金錢,而是資源?!碑?dāng)我在一份報紙上看到這句大大標(biāo)題時,我的第一反應(yīng)是——作者一定是個自然環(huán)保主義者,然后我在羞愧得反省自身的同時油然生出一股對這樣的無產(chǎn)主義理想者無比崇敬的情緒來。于是,我繼續(xù)往下看,“因此在XXX還未正式面市之時,前來咨詢的客戶已經(jīng)不少,這些有眼光的購房者明白,誰能在目前最好的購房機(jī)會下最大化地占有絕版資 源,誰就掌控了未來財富流向。”(為了避免做廣告的嫌疑,請?jiān)试S我使用XXX代替該樓盤的名字。)頓時,我悟道了!

  其實(shí),韓峰同志已經(jīng)在日記里告訴了我們資源的重要性,因此我們在學(xué)習(xí)韓峰同志嚴(yán)謹(jǐn)細(xì)致的態(tài)度同時,還要領(lǐng)悟他對資源的靈活運(yùn)用。只有在以內(nèi)核源碼為中心,堅(jiān)持各種學(xué)習(xí)資源的長期建設(shè)不動搖,才能達(dá)到韓局長那樣的高度,俯視Linux內(nèi)核世界里的人生百態(tài)。

  注意,這個觀點(diǎn)與前面所說的學(xué)習(xí)效果主要取決于方法論和心理兩個方面并不矛盾,它們屬于不同層次上的問題。


  內(nèi)核文檔

  內(nèi)核代碼中包含有大量的文檔,這些文檔對于學(xué)習(xí)理解內(nèi)核有著不可估量的價值,記住,在任何時候,它們在我們心目中的地位都應(yīng)該高于那些各式的內(nèi)核參考書。下面是一些內(nèi)核新人所應(yīng)該閱讀的文檔。

  README

  這個文件首先簡單介紹了Linux內(nèi)核的背景,然后描述了如何配置和編譯內(nèi)核,最后還告訴我們出現(xiàn)問題時應(yīng)該怎么辦。

  Documentation/Changes這個文件給出了用來編譯和使用內(nèi)核所需要的最小軟件包列表。

  Documentation/CodingStyle這個文件描述了內(nèi)核首選的編碼風(fēng)格,所有代碼都應(yīng)該遵守里面定義的規(guī)范。

  Documentation/SubmittingPatches

  Documentation/SubmittingDrivers

  Documentation/SubmitChecklist

  這三個文件都是描述如何提交代碼的,其中SubmittingPatches給出創(chuàng)建和提交補(bǔ)丁的過程,

  SubmittingDrivers描述了如何將 設(shè)備驅(qū)動提交給2.4、2.6等不同版本的內(nèi)核樹,SubmitChecklist則描述了提交代碼之前需要check自己的代碼應(yīng)該遵守的某些事項(xiàng)。

  Documentation/stable_api_nonsense.txt這個文件解釋了為什么內(nèi)核沒有一個穩(wěn)定的內(nèi)部API(到用戶空間的接口——系統(tǒng)調(diào)用——是穩(wěn)定的),它對于理解Linux的開發(fā)哲學(xué)至關(guān)重要,對于將開發(fā)平臺從其他操作系統(tǒng)轉(zhuǎn)移到Linux的開發(fā)者來說也很重要。

  Documentation/stable_kernel_rules.txt解釋了穩(wěn)定版內(nèi)核(stable releases)發(fā)布的規(guī)則,以及如何將補(bǔ)丁提交給這些版本。

  Documentation/SecurityBugs

  內(nèi)核開發(fā)者對安全性問題非常關(guān)注,如果你認(rèn)為自己發(fā)現(xiàn)了這樣的問題,可以根據(jù)這個文件中給出的聯(lián)系方式提交bug,以便能夠盡可能快的解決這個問題。

  Documentation/kernel-docs.txt這個文件列舉了很多內(nèi)核相關(guān)的文檔和書籍,里面不乏經(jīng)典之作。

  Documentation/applying-patches.txt這個文件回答了如何為內(nèi)核打補(bǔ)丁。

  Documentation/bug-hunting這個文件是有關(guān)尋找、提交、修正bug的。

  Documentation/HOWTO這個文件將指導(dǎo)你如何成為一名內(nèi)核開發(fā)者,并且學(xué)會如何同內(nèi)核開發(fā)社區(qū)合作。它盡可能不包括任何關(guān)于內(nèi)核編程的技術(shù)細(xì)節(jié),但會給你指引一條獲得這些知識的正確途徑。


  經(jīng)典書籍

  待到山花爛漫時,還是那些經(jīng)典在微笑。

  有關(guān)內(nèi)核的書籍可以用汗牛充棟來形容,不過只有一些經(jīng)典的神作經(jīng)住了考驗(yàn)。首先是5本久經(jīng)考驗(yàn)的神作(個人概括為“2+1+2”,第一個2是指2本全面講 解內(nèi)核的書,中間的1指1本講解驅(qū)動開發(fā)的書,后面的2則指2本有關(guān)內(nèi)核具體子系統(tǒng)的書,你是否想到了某某廣告里三個人突然站起單臂齊舉高呼“1比1 比 1”的場景?)。

  《Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》

  簡稱LKD,從入門開始,介紹了諸如進(jìn)程管理、系統(tǒng)調(diào)用、中斷和中斷處理程序、內(nèi)核同步、時

  間管理、內(nèi)存管理、地址空間、調(diào)試技術(shù)等方面,內(nèi)容比較淺顯易懂,個人認(rèn)為是內(nèi)核新人首先必

  讀的書籍。新人得有此書,足矣!

  《深入理解Linux內(nèi)核》

  簡稱ULK,相比于LKD的內(nèi)容不夠深入、覆蓋面不廣,ULK要深入全面得多。

  前面這兩本,一本提綱挈領(lǐng),一本全面深入。

  《Linux設(shè)備驅(qū)動程序》

  簡稱LDD,驅(qū)動開發(fā)者都要人手一本了。

  《深入理解Linux虛擬內(nèi)存管理》

  簡稱LVMM,是一本介紹Linux虛擬內(nèi)存管理機(jī)制的書。如果你希望深入的研究Linux的內(nèi)存管理

  子系統(tǒng),仔細(xì)的研讀這本書無疑是最好的選擇。

  《深入理解LINUX網(wǎng)絡(luò)內(nèi)幕》

  一本講解網(wǎng)絡(luò)子系統(tǒng)實(shí)現(xiàn)的書,通過這本書,我們可以了解到Linux內(nèi)核是如何實(shí)現(xiàn)復(fù)雜的網(wǎng)絡(luò)功能的。(忘了聲明下,我這列出來的書名是中文的,但是并不代表我建議大家去看他們的中文版,其中有的翻譯的實(shí)在太??了,呵呵)

  這5本書各有側(cè)重,正如下面的圖所展示的那樣,恰好代表了個人一直主張的內(nèi)核學(xué)習(xí)方法:首先通過LKD或ULK了解內(nèi)核的設(shè)計(jì)實(shí)現(xiàn)特點(diǎn),對內(nèi)核有個整體全局的認(rèn)識和理解,然后可分為兩個岔路,如果從事驅(qū)動開發(fā),則鉆研LDD,如果希望對內(nèi)核不是泛泛而談而是有更深入的理解,則可以選擇一個自己感興趣的子系統(tǒng),仔細(xì)分析它的代碼,不懂的地方就通過社區(qū)、郵件列表或者直接發(fā)Email給maintainer請教等途徑弄懂,切勿得過且過,這樣分析下來,對同步、中斷等等內(nèi)核的很多機(jī)制也同樣會非常了解,俗話說的一通則百通就是這個道理。當(dāng)然,如果你選擇研究的是內(nèi)存管理或者網(wǎng)絡(luò),則可以有上面的兩本書可以學(xué)習(xí),如果是其他子系統(tǒng)可能就沒有這么好的運(yùn)氣了。

  內(nèi)核社區(qū)

  最近幾年,社區(qū)網(wǎng)站非常的熱火,不過此社區(qū)非彼社區(qū)。

  Linux最大的一個優(yōu)勢就是它有一個緊密團(tuán)結(jié)了眾多使用者和開發(fā)者的社區(qū),它的目標(biāo)就是提供盡善盡美的內(nèi)核。內(nèi)核社區(qū)的中心是內(nèi)核郵件列表(Linux Kernel Mailing List,LKML),我們可以在http://vger./vger-lists.html#linux-kernel上面看到訂閱這個郵件列表的細(xì)節(jié)。

  內(nèi)核郵件列表的流量很大,每天都有幾百條消息,這里是大牛們的戰(zhàn)場,小牛們的天堂,任何一個內(nèi)核開發(fā)者都可以從中受益非淺。除了LKML,大多數(shù)子系統(tǒng)也有自己獨(dú)立的郵件列表來協(xié)調(diào)各自的開發(fā)工作,比如USB子系統(tǒng)的郵件列表可以在http://www./mailing.html 上面訂閱。

  其他網(wǎng)絡(luò)資源

  除了內(nèi)核郵件列表,還有很多其他的論壇或網(wǎng)站值得我們經(jīng)常關(guān)注。我們要知道,網(wǎng)絡(luò)上不僅有獸獸和鳳姐,也不僅有犀利哥和韓局長。http://www./ 可以通過這個網(wǎng)站上下載內(nèi)核的源代碼和補(bǔ)丁、跟蹤內(nèi)核bug等。http:// Linux和BSD內(nèi)核的技術(shù)新聞。如果沒時間跟蹤LKML,那么經(jīng)常瀏覽kerneltrap是個好主意。http:/// Linux weekly news,創(chuàng)建于1997年底的一個Linux新聞?wù)军c(diǎn)。http://zh-/mailman/listinfo/linux-kernel 這是內(nèi)核開發(fā)的中文郵件列表,里面活躍著很多內(nèi)核開發(fā)領(lǐng)域的華人,比如Herbert  Xu,、Mingming Cao、Bryan Wu等。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多