《Linux内核修炼之道》精华版.docx
《《Linux内核修炼之道》精华版.docx》由会员分享,可在线阅读,更多相关《《Linux内核修炼之道》精华版.docx(18页珍藏版)》请在冰豆网上搜索。
《Linux内核修炼之道》精华版
早上上班坐地铁要排队,到了公司楼下等电梯要排队,中午吃饭要排队,下班了追求一个女孩子也要排队,甚至在网上下载个什么门的短片也要排队,每次看见人群排成一条长龙时,才真正意识到自己是龙的传人。
那么下面咱们就说说队列(链表)。
使用链表的目的很明确,因为有很多事情要做,于是就把它放进链表里,一件事一件事的处理。
比如在USB子系统里,U盘不停的提交urb请求,USB键盘也提交,USB鼠标也提交,那USB主机控制器咋应付得过来呢?
很简单,建一个链表,然后你每次提交就是往里边插入,然后USB主机控制器再统一去调度,一个一个来执行。
这里有力得证明了,谭浩强大哥的C程序设计是我们学习Linux的有力武器,书中对链表的介绍无疑是英明的,谭大哥,您不是一个人在战斗!
内核中链表的实现位于include/linux/list.h文件,链表数据结构的定义也很简单。
21structlist_head{
22structlist_head*next,*prev;
23};
list_head结构包含两个指向list_head结构的指针prev和next,由此可见,内核中的链表实际上都是双链表(通常都是双循环链表)。
通常,我们在数据结构课堂上所了解的链表定义方式是这样的(以单链表为例):
structlist_node{
structlist_node*next;
ElemTypedata;
};
通过这种方式使用链表,对每一种数据类型,都要定义它们各自的链表结构。
而内核中的链表却与此不同,它并没有数据域,不是在链表结构中包含数据,而是在描述数据类型的结构中包含链表。
比如在hub驱动中使用structusb_hub来描述hub设备,hub需要处理一系列的事件,比如当探测到一个设备连进来时,就会执行一些代码去初始化该设备,所以hub就创建了一个链表来处理各种事件,这个链表的结构如下图。
(1)声明与初始化。
链表的声明可以使用两种方式,一种为使用LIST_HEAD宏在编译时静态初始化,一种为使用INIT_LIST_HEAD()在运行时进行初始化。
25#defineLIST_HEAD_INIT(name){&(name),&(name)}
26
27#defineLIST_HEAD(name)\
28structlist_headname=LIST_HEAD_INIT(name)
29
30staticinlinevoidINIT_LIST_HEAD(structlist_head*list)
31{
32list->next=list;
33list->prev=list;
34}
无论采用哪种方式,新生成的链表头的两个指针next、prev都初始化为指向自己。
(2)判断链表是否为空。
298staticinlineintlist_empty(conststructlist_head*head)
299{
300returnhead->next==head;
301}
(3)插入。
有了链表,自然就要往里面加东西、减东西。
就像我们每个人每天都在不停的走进去,又走出来,似是梦境又不是梦境。
一切都是不经意的。
走进去是一年四季,走出来是春夏秋冬。
list_add()和list_add_tail()这两个函数就是往队列里加东西。
67staticinlinevoidlist_add(structlist_head*new,structlist_head*head)
68{
69__list_add(new,head,head->next);
70}
84staticinlinevoidlist_add_tail(structlist_head*new,structlist_head*head)
85{
86__list_add(new,head->prev,head);
87}
其中,list_add()将数据插入在head之后,list_add_tail()将数据插入在head->prev之后。
其实对于循环链表来说,表头的next、prev分别指向链表中的第一个和最后一个节点,所以,list_add()和list_add_tail()的区别并不大。
(4)删除。
搞懂谭浩强那本书之后看这些链表的代码那就是小菜一碟。
再来看下一个list_del_init(),里的元素不能只加不减,没用了的元素就该删除掉,把空间腾出来给别人。
郭敬明说过,我生命里的温暖就那么多,我全部给了你,但是你离开了我,你叫我以后怎么再对别人笑……
链表里的元素不能只加不减,没用了的元素就应该删除掉。
254staticinlinevoidlist_del_init(structlist_head*entry)
255{
256__list_del(entry->prev,entry->next);
257INIT_LIST_HEAD(entry);
258}
list_del_init()从链表里删除一个元素,并且将其初始化。
(5)遍历。
内核中的链表仅仅保存了list_head结构的地址,我们如何通过它或取一个链表节点真正的数据项?
这就要提到有关链表的所有操作里面,最为重要超级经典的list_entry宏了,我们可以通过它很容易地获得一个链表节点的数据。
425#definelist_entry(ptr,type,member)\
426container_of(ptr,type,member)
我相信,list_entry()这个宏在Linux内核代码中的地位,就相当于广告词中的任静付笛生的洗洗更健康,相当于大美女关之琳的一分钟轻松做女人,这都是耳熟能详妇孺皆知的,是经典中的经典。
如果你说你不知道list_entry(),那你千万别跟人说你懂Linux内核,就好比你不知道陈文登不知道任汝芬你就根本不好意思跟人说你考过研,要知道每个考研人都是左手一本陈文登右手一本任汝芬。
可惜,关于list_entry,这个谭浩强老师的书里就没有了,当然你不能指责谭浩强的书不行,再好的书也不可能包罗万象。
关于list_entry(),让我们结合实例来看,还是hub驱动的那个例子,当我们真的要处理hub的事件的时候,我们当然需要知道具体是哪个hub触发了这起事件。
而list_entry的作用就是,从structlist_headevent_list得到它所对应的structusb_hub结构体变量。
比如以下四行代码:
structlist_head*tmp;
structusb_hub*hub;
tmp=hub_event_list.next;
hub=list_entry(tmp,structusb_hub,event_list);
从全局链表hub_event_list中取出一个来,叫做tmp,然后通过tmp,获得它所对应的structusb_hub。
《Linux内核修炼之道》精华分享与讨论(15)——子系统的初始化:
内核选项解析收藏
推荐博文:
Linux内核“问题门”——学习问题、经验集锦
推荐下载:
《Linux内核修炼之道》精华版之方法论
首先感谢国家。
其次感谢上大的钟莉颖,让我知道了大学不仅有校花,还有校鸡,而且很多时候这两者其实没什么差别。
最后感谢清华女刘静,让我深刻体会到了素质教育的重要性,让我感到有责任写写子系统的初始化。
各个子系统的初始化是内核整个初始化过程必然要完成的基本任务,这些任务按照固定的模式来处理,可以归纳为两个部分:
内核选项的解析以及那些子系统入口(初始化)函数的调用。
内核选项
Linux允许用户传递内核配置选项给内核,内核在初始化过程中调用parse_args函数对这些选项进行解析,并调用相应的处理函数。
parse_args函数能够解析形如“变量名=值”的字符串,在模块加载时,它也会被调用来解析模块参数。
内核选项的使用格式同样为“变量名=值”,打开系统的grub文件,然后找到kernel行,比如:
kernel/boot/vmlinuz-2.6.18root=/dev/sda1rosplash=silentvga=0x314pci=noacpi
其中的“pci=noacpi”等都表示内核选项。
内核选项不同于模块参数,模块参数通常在模块加载时通过“变量名=值”的形式指定,而不是内核启动时。
如果希望在内核启动时使用模块参数,则必须添加模块名做为前缀,使用“模块名.参数=值”的形式,比如,使用下面的命令在加载usbcore时指定模块参数autosuspend的值为2。
$modprobeusbcoreautosuspend=2
若是在内核启动时指定,则必须使用下面的形式:
usbcore.autosuspend=2
从Documentation/kernel-parameters.txt文件里可以查询到某个子系统已经注册的内核选项,比如PCI子系统注册的内核选项为:
pci=option[,option...][PCI]variousPCIsubsystemoptions:
off[X86-32]don'tprobeforthePCIbus
bios[X86-32]forceuseofPCIBIOS,don'taccess
thehardwaredirectly.Usethisifyourmachine
hasanon-standardPCIhostbridge.
nobios[X86-32]disallowuseofPCIBIOS,onlydirect
hardwareaccessmethodsareallowed.Usethis
ifyouexperiencecrashesuponbootupandyou
suspecttheyarecausedbytheBIOS.
conf1[X86-32]ForceuseofPCIConfiguration
Mechanism1.
conf2[X86-32]ForceuseofPCIConfiguration
Mechanism2.
nommconf[X86-32,X86_64]DisableuseofMMCONFIGforPCI
Configuration
nomsi[MSI]IfthePCI_MSIkernelconfigparameteris
enabled,thiskernelbootoptioncanbeusedto
disabletheuseofMSIinterruptssystem-wide.
nosort[X86-32]Don'tsortPCIdevicesaccordingto
ordergivenbythePCIBIOS.Thissortingis
donetogetadeviceordercompatiblewith
olderkernels.
biosirq[X86-32]UsePCIBIOScallstogettheinterrupt
routingtable.Thesecallsareknowntobebuggy
onseveralmachinesandtheyhangthemachine
whenused,butonothercomputersit'stheonly
waytogettheinterruptroutingtable.Try
thisoptionifthekernelisunabletoallocate
IRQsordiscoversecondaryPCIbusesonyour
motherboard.
rom[X86-32]AssignaddressspacetoexpansionROMs.
Usewithcautionascertaindevicesshare
addressdecodersbetweenROMsandother
resources.
irqmask=0xMMMM[X86-32]SetabitmaskofIRQsallowedtobe
assignedautomaticallytoPCIdevices.Youcan
makethekernelexcludeIRQsofyourISAcards
thisway.
pirqaddr=0xAAAAA[X86-32]Specifythephysicaladdress
ofthePIRQtable(normallygenerated
bytheBIOS)ifitisoutsidethe
F0000h-100000hrange.
lastbus=N[X86-32]Scanallbusesthrubus#N.Canbe
usefulifthekernelisunabletofindyour
secondarybusesandyouwanttotellit
explicitlywhichonestheyare.
assign-busses[X86-32]AlwaysassignallPCIbus
numbersourselves,overriding
whateverthefirmwaremayhavedone.
usepirqmask[X86-32]HonorthepossibleIRQmaskstored
intheBIOS$PIRtable.Thisisneededon
somesystemswithbrokenBIOSes,notably
someHPPavilionN5400andOmnibookXE3
notebooks.ThiswillhavenoeffectifACPI
IRQroutingisenabled.
noacpi[X86-32]DonotuseACPIforIRQrouting
orforPCIscanning.
routeirqDoIRQroutingforallPCIdevices.
Thisisnormallydoneinpci_enable_device(),
sothisoptionisatemporaryworkaround
forbrokendriversthatdon'tcallit.
firmware[ARM]Donotre-enumeratethebusbutinstead
justusetheconfigurationfromthe
bootloader.Thisiscurrentlyusedon
IXP2000systemswherethebushastobe
configuredacertainwayforadjunctCPUs.
noearly[X86]Don'tdoanyearlytype1scanning.
Thismighthelponsomebrokenboardswhich
machinecheckwhensomedevices'configspace
isread.Butvariousworkaroundsaredisabled
andsomeIOMMUdriverswillnotwork.
bfsortSortPCIdevicesintobreadth-firstorder.
Thissortingisdonetogetadevice
ordercompatiblewitholder(<=2.4)kernels.
nobfsortDon'tsortPCIdevicesintobreadth-firstorder.
cbiosize=nn[KMG]Thefixedamountofbusspacewhichis
reservedfortheCardBusbridge'sIOwindow.
Thedefaultvalueis256bytes.
cbmemsize=nn[KMG]Thefixedamountofbusspacewhichis
reservedfortheCardBusbridge'smemory
window.Thedefaultvalueis64megabytes.
注册内核选项
就像我们不需要明白钟莉颖是如何走上校鸡的修炼之道,我们也不必理解parse_args函数的实现细节。
但我们必须知道如何注册内核选项:
模块参数使用module_param系列的宏注册,内核选项则使用__setup宏来注册。
__setup宏在include/linux/init.h文件中定义。
171#define__setup(str,fn)\
172__setup_param(str,fn,fn,0)
__setup需要两个参数,其中str是内核选项的名字,fn是该内核选项关联的处理函数。
__setup宏告诉内核,在启动时如果检测到内核选项str,则执行函数fn。
str除了包括内核选项名字之外,必须以“=”字符结束。
不同的内核选项可以关联相同的处理函数,比如内核选项netdev和ether都关联了netdev_boot_setup函数。
除了__setup宏之外,还可以使用early_param宏注册内核选项。
它们的使用方式相同,不同的是,early_param宏注册的内核选项必须要在其他内核选项之前被处理。
两次解析
相应于__setup宏和early_param宏两种注册形式,内核在初始化时,调用了两次parse_args函数进行解析。
parse_early_param();
parse_args("Bootingkernel",static_command_line,__start___param,
__stop___param-__start___param,
&unknown_bootoption);
parse_args的第一次调用就在parse_early_param函数里面,为什么会出现两次调用parse_args的情况?
这是因为内核选项又分成了两种,就像现实世界中的我们,一种是普普通通的,一种是有特权的,有特权的需要在普通选项之前进行处理。
现实生活中特权的定义好像很模糊,不同的人有不同的诠释,比如哈医大二院的纪委书记在接受央视的采访“老人住院费550万元”时如是说:
“我们就是一所人民医院……就是一所贫下中农的医院,从来不用特权去索取自己身外的任何利益……我们不但没有多收钱还少收了。
”
人生就是如此的复杂和奇怪。
内核选项相对来说就要单纯得多,特权都是阳光下的,不会藏着掖着,直接使用early_param宏去声明,让你一眼就看出它是有特权的。
使用early_param声明的那些选项就会首先由parse_early_param去解析。
《Linux内核修炼之道》精华分享与讨论(16)——子系统的初始化:
那些入口函数收藏此文于2010-04-13被推荐到CSDN首页
如何被推荐?
推荐博文:
Linux内核“问题门”——学习问题、经验集锦
推荐下载:
《Linux内核修炼之道》精华版之方法论
内核选项的解析完成之后,各个子系统的初始化即进入第二部分—入口函数的调用。
通常USB、PCI这样的子系统都会有一个名为subsys_initcall的入口,如果你选择它们作为研究内核的切入点,那么就请首先找到它。
朱德庸在《关于上班这件事》里说,要花前半生找入口,花后半生找出口。
可见寻找入口对于咱们这一生,对于看内核代码这件事儿都是无比重要的。
但是很多时候,入口并不仅仅只有subsys_initcall一个,比如PCI。
117#definepure_initcall(fn)__define_initcall("0",fn,1)
118
119#definecore_initcall(fn)__define_initcall("1",fn,1)
120#definecore_initcall_sync(fn)__define_initcall("1s",fn,1s)
121#definepostcore_initcall(fn)__define_initcall("2",fn,2)
122#definepostcore_initcall_sync(fn)__define_initcall("2s",fn,2s)
123#definearch_initcall(fn)__define_initcall("3",fn,3)
124#definearch_initcall_sync(fn)__define_initcall("3s",fn,3s)
125#definesubsys_initcall(fn)__define_initcall("4",fn,4)
126#definesubsys_initcall_sync(fn)__define_initcall("4s",fn,4s)
127#definefs_initcall(fn)__define_initcall("5",fn,5)
128#definefs_initcall_sync(fn)__define_initcall("5s",fn,5s)
129#definerootfs_initcall(fn)__define_initcall("rootfs",fn,rootfs)
130#definedevice_initcall(fn)__define_initcall("6",fn,6)
131#definedevice_initcall_sync(fn)__define_initcall("6s",fn,6s)
132#definelate_initcall(fn)__define_initcall("7",fn,7)
133#definelate_initcall_sync(fn)__define_initcall("7s",fn,7s)
134
135#define__initcall(fn)device_initcall(fn)