linux设备和驱动加载的先后顺序
點(diǎn)擊打開鏈接
Linux驅(qū)動(dòng)先注冊(cè)總線,總線上可以先掛device,也可以先掛driver,那么究竟怎么控制先后的順序呢。
Linux系統(tǒng)使用兩種方式去加載系統(tǒng)中的模塊:動(dòng)態(tài)和靜態(tài)。
靜態(tài)加載:將所有模塊的程序編譯到Linux內(nèi)核中,由do_initcall函數(shù)加載
核心進(jìn)程(/init/main.c)kernel_inità do_basic_setup()àdo_initcalls()該函數(shù)中會(huì)將在__initcall_start和__initcall_end之間定 義的各個(gè)模塊依次加載。那么在__initcall_start 和 __initcall_end之間都有些什么呢?
找到/arch/powerpc/kernel/vmlinux.lds文件,找到.initcall.init段:
?
.initcall.init : {
? __initcall_start = .;
? *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
? __initcall_end = .;
? }
?
可以看出在這兩個(gè)宏之間依次排列了14個(gè)等級(jí)的宏,由于這其中的宏是按先后順序鏈接的,所以也就表示,這14個(gè)宏有優(yōu)先級(jí):0>1>1s>2>2s………>7>7s。
那么這些宏有什么具體的意義呢,這就要看/include/linux/init.h文件:
?
#define pure_initcall(fn)????????????? __define_initcall("0",fn,0)
#define core_initcall(fn)????????????? __define_initcall("1",fn,1)
#define core_initcall_sync(fn)?????????? __define_initcall("1s",fn,1s)
#define postcore_initcall(fn)????????????? __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn)??? __define_initcall("2s",fn,2s)
#define arch_initcall(fn)??????? __define_initcall("3",fn,3)
#define arch_initcall_sync(fn)??????????? __define_initcall("3s",fn,3s)
#define subsys_initcall(fn)????????? __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn)?????? __define_initcall("4s",fn,4s)
#define fs_initcall(fn)?????????????????? __define_initcall("5",fn,5)
#define fs_initcall_sync(fn)???????? __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)??????????? __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn)????????? __define_initcall("6",fn,6)
#define device_initcall_sync(fn)?????? __define_initcall("6s",fn,6s)
#define late_initcall(fn)???????? __define_initcall("7",fn,7)
#define late_initcall_sync(fn)???????????? __define_initcall("7s",fn,7s)
?
??? 這里就定義了具體的宏,我們平時(shí)用的module_init在靜態(tài)編譯時(shí)就相當(dāng)于device_initcall。舉個(gè)例子,在2.6.24的內(nèi)核 中:gianfar_device使用的是arch_initcall,而gianfar_driver使用的是module_init,因?yàn)?arch_initcall的優(yōu)先級(jí)大于module_init,所以gianfar設(shè)備驅(qū)動(dòng)的device先于driver在總線上添加。
?
?
系統(tǒng)初始化函數(shù)集(subsys_initcall)和初始化段應(yīng)用
前言:前段時(shí)間做一個(gè)項(xiàng)目需要設(shè)計(jì)一個(gè)動(dòng)態(tài)庫,并希望在加載庫的同時(shí)自動(dòng)執(zhí)行一些初始化動(dòng)作,于是聯(lián)想到了linux內(nèi)核眾子系統(tǒng)的初始化,于是研 究之,并在過這程中發(fā)現(xiàn)了初始化段的存在,利用初始化段實(shí)現(xiàn)了該功能。工作一年,筆記積累多了,慢慢變得雜亂無章,于是開博,一方面整理筆記,梳理知識(shí), 另一方面和大家交流,共同進(jìn)步。
?
?
keyword:subsys_initcall, init, init_call
?
1 系統(tǒng)初始化調(diào)用函數(shù)集分析(靜態(tài))
1.1 函數(shù)定義
?在linux內(nèi)核代碼里,運(yùn)用了subsys_initcall來進(jìn)行各種子系統(tǒng)的初始化,具體怎么初始化的呢?其實(shí)并不復(fù)雜。以2.6.29內(nèi)核作為例子。在<include/linux/init.h>下就能找到subsys_initcall的定義:
#define pure_initcall(fn)????????????? __define_initcall("0",fn,0)
#define core_initcall(fn)????????????? __define_initcall("1",fn,1)
#define core_initcall_sync(fn)??? __define_initcall("1s",fn,1s)
#define postcore_initcall(fn)????? __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn)????????????? __define_initcall("3",fn,3)
#define arch_initcall_sync(fn)??? __define_initcall("3s",fn,3s)
#define subsys_initcall(fn)????????? __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn)????????????????? __define_initcall("5",fn,5)
#define fs_initcall_sync(fn)??????? __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)?????????? __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn)???????? __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn)?????????????? __define_initcall("7",fn,7)
#define late_initcall_sync(fn)????? __define_initcall("7s",fn,7s)
?
而__define_initcall又被定義為
#define __define_initcall(level,fn,id) \
?static initcall_t? __initcall_##fn##id?? __used \
?__attribute__((__section__(".initcall" level ".init"))) = fn
?
所以?subsys_initcall(fn) == __initcall_fn4 它將被鏈接器放于section? .initcall4.init 中。(attribute將會(huì)在另一篇文章中介紹)
?
1.2 初始化函數(shù)集的調(diào)用過程執(zhí)行過程:
start_kernel->rest_init
系統(tǒng)在啟動(dòng)后在rest_init中會(huì)創(chuàng)建init內(nèi)核線程
init->do_basic_setup->do_initcalls
do_initcalls中會(huì)把.initcall.init.中的函數(shù)依次執(zhí)行一遍:
?
for (call = __initcall_start; call < __initcall_end; call++) {
.??? .....
result = (*call)();
.??? ........
}
?
這個(gè)__initcall_start是在文件<arch/xxx/kernel/vmlinux.lds.S>定義的:
?.initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
?? __initcall_start = .;
?? INITCALLS
?? __initcall_end = .;
? }
?
INITCALLS被定義于<asm-generic/vmlinux.lds.h>:
#define INITCALLS?????? \
?? *(.initcall0.init)????? \
?? *(.initcall0s.init)????? \
?? *(.initcall1.init)????? \
?? *(.initcall1s.init)????? \
?? *(.initcall2.init)????? \
?? *(.initcall2s.init)????? \
?? *(.initcall3.init)????? \
?? *(.initcall3s.init)?? ???\
?? *(.initcall4.init)????? \
?? *(.initcall4s.init)????? \
?? *(.initcall5.init)????? \
?? *(.initcall5s.init)????? \
?? *(.initcallrootfs.init)????? \
?? *(.initcall6.init)????? \
?? *(.initcall6s.init)????? \
?? *(.initcall7.init)????? \
?? *(.initcall7s.init)
?
?
2 基于模塊方式的初始化函數(shù)(動(dòng)態(tài))
2.1函數(shù)定義subsys_initcall的靜態(tài)調(diào)用方式應(yīng)該講清楚來龍去脈了,現(xiàn)在看看動(dòng)態(tài)方式的初始化函數(shù)調(diào)用(模塊方式)。 在<include/linux/init.h>里,如果MODULE宏被定義,說明該函數(shù)將被編譯進(jìn)模塊里,在系統(tǒng)啟動(dòng)后以模塊方式被調(diào) 用。
#define core_initcall(fn)???????? module_init(fn)
#define postcore_initcall(fn)? module_init(fn)
#define arch_initcall(fn)??????? module_init(fn)
#define subsys_initcall(fn)??? module_init(fn)
#define fs_initcall(fn)???????????? module_init(fn)
#define device_initcall(fn)???? module_init(fn)
#define late_initcall(fn)???????? module_init(fn)
這是在定義MODULE的情況下對(duì)subsys_initcall的定義,就是說對(duì)于驅(qū)動(dòng)模塊,使用subsys_initcall等價(jià)于使用module_init
?
?
2.2 module_init 分析下面先看看module_init宏究竟做了什么
#define module_init(initfn)???? \
?static inline initcall_t __inittest(void)? \ /*定義此函數(shù)用來檢測傳入函數(shù)的類型,并在編譯時(shí)提供警告信息*/
?{ return initfn; }???? \
?int init_module(void) __attribute__((alias(#initfn))); /*聲明init_modlue為 initfn的別名,insmod只查找名字為init_module函數(shù)并調(diào)用*/
?
typedef int (*initcall_t)(void); /*函數(shù)類型定義*/
?
在以模塊方式編譯一個(gè)模塊的時(shí)候,會(huì)自動(dòng)生成一個(gè)xxx.mod.c文件, 在該文件里面定義一個(gè)struct module變量,并把init函數(shù)設(shè)置為上面的init_module() 而上面的這個(gè)init_module,被alias成模塊的初始化函數(shù)(參考<gcc關(guān)鍵字:__attribute__, alias, visibility, hidden>)。
?
也就是說,模塊裝載的時(shí)候(insmod,modprobe),sys_init_module()系統(tǒng)調(diào)用會(huì)調(diào)用module_init指定的函數(shù)(對(duì)于編譯成>模塊的情況)。
?
2.3 module的自動(dòng)加載內(nèi)核在啟動(dòng)時(shí)已經(jīng)檢測到了系統(tǒng)的硬件設(shè)備,并把硬件設(shè)備信息通過sysfs內(nèi)核虛擬文件系統(tǒng)導(dǎo)出。sysfs文件系統(tǒng)由系統(tǒng)初始化腳本掛載到/sys上。udev掃描sysfs文件系統(tǒng),根據(jù)硬件設(shè)備信息生成熱插拔(hotplug)事件,udev再讀取這些事件,生成對(duì)應(yīng)的硬件設(shè)備文件。由于沒有實(shí)際的硬件插拔動(dòng)作,所以這一過程被稱為coldplug。
udev完成coldplug操作,需要下面三個(gè)程序:
udevtrigger——掃描sysfs文件系統(tǒng),生成相應(yīng)的硬件設(shè)備hotplug事件。
udevd——作為deamon,記錄hotplug事件,然后排隊(duì)后再發(fā)送給udev,避免事件沖突(race conditions)。
udevsettle——查看udev事件隊(duì)列,等隊(duì)列內(nèi)事件全部處理完畢才退出。
要規(guī)定事件怎樣處理就要編寫規(guī)則文件了.規(guī)則文件是udev的靈魂,沒有規(guī)則文件,udev無法自動(dòng)加載硬件設(shè)備的驅(qū)動(dòng)模塊。它一般位于<etc/udev/rules.d>
?
?
3初始化段的應(yīng)用這里給出一個(gè)簡單的初始化段的使用例子,將a.c編譯成一個(gè)動(dòng)態(tài)庫,其中,函數(shù)a()和函數(shù)c()放在兩個(gè)不同的初始化段里,函數(shù)b()默認(rèn)放置;編譯main.c,鏈接到由a.c編譯成的動(dòng)態(tài)庫,觀察各函數(shù)的執(zhí)行順序。
# cat a.c #include <stdio.h>
typedef int (*fn) (void);
int a(void)
{
??? printf("a\n");
??? return 0;
}
__attribute__((__section__(".init_array.2"))) static fn init_a = a;
int c(void)
{
??? printf("c\n");
??? return 0;
}
__attribute__((__section__(".init_array.1"))) static fn init_c = c;
?
int b()
{
??? printf("b\n");
??? return 0;
}
?
# cat main.c
#include<stdio.h>
int b();
int main()
{
??? printf("main\n");
??? b();
}
?
# cat mk.sh
?
gcc -fPIC -g -c a.c
gcc -shared -g -o liba.so a.o
cp liba.so /lib/ -fr
gcc main.c liba.so
ldconfig
./a.out?
?
# gcc -fPIC -g -c a.c
# gcc -shared -g -o liba.so a.o
# cp liba.so /lib/
# gcc main.c liba.so
# ldconfig
# ./a.out
a
c
main
b
?
?
在類unix操作系統(tǒng)中,驅(qū)動(dòng)加載方式一般分為:動(dòng)態(tài)加載和靜態(tài)加載,下面分別對(duì)其詳細(xì)論述。
一、動(dòng)態(tài)加載
動(dòng)態(tài)加載是將驅(qū)動(dòng)模塊加載到內(nèi) 核中,而不能放入/lib/modules/下。
??? 在2.4內(nèi)核中,加載驅(qū)動(dòng)命令為:insmod ,刪除模塊為:rmmod;
??? 在2.6以上內(nèi)核中,除了insmod與rmmod外,加載命令還有modprobe;
??? insmod與modprobe不同之處:
??? insmod 絕對(duì)路徑/××.o,而modprobe ××即可,不用加.ko或.o后綴,也不用加路徑;最重要的一點(diǎn)是:modprobe同時(shí)會(huì)加載當(dāng)前模塊所依賴的其它模塊;
??? lsmod查看當(dāng)前加載到內(nèi)核中的所有驅(qū)動(dòng)模塊,同時(shí)提供其它一些信息,比如其它模塊是否在使用另一個(gè)模塊。
二、靜態(tài)加載
(一)概念
??? 在執(zhí)行make menuconfig命令進(jìn)行內(nèi)核配置裁剪時(shí),在窗口中可以選擇是否編譯入內(nèi)核,還是放入/lib/modules/下相應(yīng)內(nèi)核版本目錄中,還是不選。
(二) 操作步驟
??? linux設(shè)備一般分為:字符設(shè)備、塊設(shè)備和網(wǎng)絡(luò)設(shè)備,每種設(shè)備在內(nèi)核源代碼目錄樹drivers/下都有對(duì)應(yīng)的目錄,其加載方法類似,以下以字符設(shè)備靜 態(tài)加載為例,假設(shè)驅(qū)動(dòng)程序源代碼名為ledc.c,具體操作步驟如下:
????第一步:將ledc.c源程序放入內(nèi)核源碼drivers/char/下;
????第二步:修改drivers/char/Config.in文件,具體修改如下:
?? ?? ?? 按照打開文件中的格式添加即可;
?? ?? ?? 在文件的適當(dāng)位置(這個(gè)位置隨便都可以,但這個(gè)位置決定其在make menuconfig窗口中所在位置)加入以下任一段代碼:
?? ?? ???
?? ?? ?? tristate 'LedDriver' CONFIG_LEDC
?? ?? ?? if [ "$CONFIG_LEDC" = "y" ];then
?? ?? ?? bool '?? Support for led on h9200 board' CONFIG_LEDC_CONSOLE
?? ?? ?? fi
?? ?? ?? 說明:以上代碼使用tristate來定義一個(gè)宏,表示此驅(qū)動(dòng)可以直接編譯至內(nèi)核(用*選擇),也可以編制至/lib/modules/下(用M選擇), 或者不編譯(不選)。
?? ?? ?? bool 'LedDriver' CONFIG_LEDC
?? ?? ?? if [ "$CONFIG_LEDC" = "y" ];then
?? ?? ?? bool '?? Support for led on h9200 board' CONFIG_LEDC_CONSOLE
?? ?? ?? fi
?? ?? ?? 說明:以上代碼使用tristate來定義一個(gè)宏,表示此驅(qū)動(dòng)只能直接編譯至內(nèi)核(用*選擇)或者不編譯(不選),不能編制至/lib/modules/ 下(用M選擇)。
???
????第三步:修改drivers/char/Makefile文件
?? ?? ?? 在適當(dāng)位置加入下面一行代碼:
?? ?? ?? obj-$(CONFIG_LEDC)?? +=?? ledc.o
?? ?? ?? 或者在obj-y一行中加入ledc.o,如:
?? ?? ?? obj-y += ledc.o mem.o 后面不變;
??? OK,經(jīng)過以上的設(shè)置就可以在執(zhí)行make menuconfig命令后的窗口中的character devices---> 中進(jìn)行選擇配置了。選擇后重新編譯就ok了。
總結(jié)
以上是生活随笔為你收集整理的linux设备和驱动加载的先后顺序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android开发概要记录
- 下一篇: linux 其他常用命令