Linux内核驱动加载过程
驱动加载分为两种情况:静态加载和动态加载。
1. 静态加载
静态加载的方法是把驱动程序直接编译进内核,然后内核在启动过程中由do_initcall()函数加载。
do_initcalls()函数路径在/init/main.c
过程如下:
start_kernel()--->rest_init()--->kernel_init()--->do_basic_setup()--->do_initcalls()
do_initcalls()函数内容如下:
staticvoid __init do_initcalls(void)
{
initcall_t *fn;
for (fn = __early_initcall_end; fn <__initcall_end; fn++)
do_one_initcall(*fn);
}
该函数中会将在__initcall_start和__initcall_end之间定 义的各个模块依次加载,其中.initcall.init段包含了这之间的内容。
在/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 = .;
}
从中能够了解到这两个宏之间依次排列了14个等级的宏,由于这些宏是按先后顺序链接的,所以也就表示,这14个宏有优先级:0>1>1s>2>2s………>7>7s,其具体的意义需要看/include/linux/init.h文件:
#definepure_initcall(fn) __define_initcall("0",fn,0)
#definecore_initcall(fn) __define_initcall("1",fn,1)
#definecore_initcall_sync(fn) __define_initcall("1s",fn,1s)
#definepostcore_initcall(fn) __define_initcall("2",fn,2)
#definepostcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#definearch_initcall(fn) __define_initcall("3",fn,3)
#definearch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#definesubsys_initcall(fn) __define_initcall("4",fn,4)
#definesubsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#definefs_initcall(fn) __define_initcall("5",fn,5)
#definefs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#definerootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#definedevice_initcall(fn) __define_initcall("6",fn,6)
#definedevice_initcall_sync(fn) __define_initcall("6s",fn,6s)
#definelate_initcall(fn) __define_initcall("7",fn,7)
#definelate_initcall_sync(fn) __define_initcall("7s",fn,7s)
在静态编译时module_init就相当于device_initcall。在的内核 中,gianfar_device使用的是arch_initcall,而gianfar_driver使用的是module_init,因为arch_initcall的优先级大于module_init,所以gianfar设备驱动的device先于driver在总线上添加。
在linux内核代码里,通过调用subsys_initcall来进行各种子系统的初始化,在
#definepure_initcall(fn) __define_initcall("0",fn,0)
#definecore_initcall(fn) __define_initcall("1",fn,1)
#definecore_initcall_sync(fn) __define_initcall("1s",fn,1s)
#definepostcore_initcall(fn) __define_initcall("2",fn,2)
#definepostcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#definearch_initcall(fn) __define_initcall("3",fn,3)
#definearch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#definesubsys_initcall(fn) __define_initcall("4",fn,4)
#definesubsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#definefs_initcall(fn) __define_initcall("5",fn,5)
#definefs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#definerootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#definedevice_initcall(fn) __define_initcall("6",fn,6)
#definedevice_initcall_sync(fn) __define_initcall("6s",fn,6s)
#definelate_initcall(fn) __define_initcall("7",fn,7)
#definelate_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 中。
start_kernel->rest_init系统启动后会在rest_init中会创建kernel_init内核线程
kernel_init->do_basic_setup->do_initcalls
do_initcalls中会把.initcall.init.中的函数依次执行一遍:
for(call = __initcall_start; call < __initcall_end; call++) {
. .....
result =(*call)();
. ........
}
这个__initcall_start是在文件
.initcall.init: AT(ADDR(.initcall.init) - LOAD_OFFSET) {
__initcall_start = .;
INITCALLS
__initcall_end= .;
}
INITCALLS被定义于
#defineINITCALLS \
*(.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. 动态加载
动态加载方式的初始化函数调用(模块方式)。 在
#definecore_initcall(fn) module_init(fn)
#definepostcore_initcall(fn) module_init(fn)
#definearch_initcall(fn) module_init(fn)
#definesubsys_initcall(fn) module_init(fn)
#definefs_initcall(fn) module_init(fn)
#definedevice_initcall(fn) module_init(fn)
#definelate_initcall(fn) module_init(fn)
这是在定义MODULE的情况下对subsys_initcall的定义,就是说对于驱动模块使用subsys_initcall等价于使用module_init
① module_init分析先看看module_init宏究竟做了什么
#definemodule_init(init fn) \
staticinline initcall_t __inittest(void) \ /*定义此函数用来检测传入函数的类型,并在编译时提供警告信息*/
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));/*声明init_modlue为 initfn的别名,insmod只查找名字为init_module函数并调用*/
typedefint (*initcall_t)(void); /*函数类型定义*/
在以模块方式编译一个模块的时候,会自动生成一个xxx.mod.c文件,在该文件里面定义一个struct module变量,并把init函数设置为上面的init_module()而上面的这个init_module,被alias成模块的初始化函数(参考
也就是说,模块装载的时候(insmod,modprobe),sys_init_module()系统调用会调用module_init指定的函数(对于编译成>模块的情况)。
② module的自动加载内核在启动时已经检测到了系统的硬件设备,并把硬件设备信息通过sysfs内核虚拟文件系统导出。sysfs文件系统由系统初始化脚本挂载到/sys上。udev扫描sysfs文件系统,根据硬件设备信息生成热插拔(hotplug)事件,udev再读取这些事件,生成对应的硬件设备文件。由于没有实际的硬件插拔动作,所以这一过程被称为coldplug。
udev完成coldplug操作,需要下面三个程序:
udevtrigger——扫描sysfs文件系统,生成相应的硬件设备hotplug事件。
udevd——作为deamon,记录hotplug事件,然后排队后再发送给udev,避免事件冲突(raceconditions)。
udevsettle——查看udev事件队列,等队列内事件全部处理完毕才退出。
要规定事件怎样处理就要编写规则文件了.规则文件是udev的灵魂,没有规则文件,udev无法自动加载硬件设备的驱动模块。它一般位于
③ 将first.c编译成一个动态库,其中,函数first()和函数three()放在两个不同的初始化段里,函数second()默认放置;编译main.c,链接到由first.c编译成的动态库,观察各函数的执行顺序。
# cat first.c#include
typedefint (*fn) (void);
int first(void)
{
printf("first\n");
return 0;
}
__attribute__((__section__(".init_array.2")))static fn init_first = first;
int three(void)
{
printf("three\n");
return 0;
}
__attribute__((__section__(".init_array.1")))static fn init_three = three;
int second()
{
printf("second\n");
return 0;
}
# catmain.c
#include
int second();
intmain()
{
printf("main\n");
second ();
}
# catmk.sh
gcc-fPIC -g -c first.c
gcc-shared -g -o liba.so first.o
cp libfirst.so/lib/ -fr
gccmain.c libfirst.so
ldconfig
./first.out
# gcc-fPIC -g -c first.c
# gcc-shared -g -o libfirst.so first.o
# cp libfirst.so/lib/
# gccmain.c libfirst.so
#ldconfig
# ./first.out
first
three
main
second
3. 总结
① 静态加载
⑴概念
在执行make menuconfig命令进行内核配置裁剪时,在窗口中可以选择是否编译入内核,还是放入/lib/modules/下相应内核版本目录中,还是不选。
⑵操作步骤
linux设备一般分为:字符设备、块设备和网络设备,每种设备在内核源代码目录树drivers/下都有对应的目录,其加载方法类似,下面以字符设备静态加载为例,假设驱动程序源代码名为ledc.c,具体操作步骤如下:
第一步:将ledc.c源程序放入内核源码drivers/char/下;
第二步:修改drivers/char/Config.in文件,具体修改如下:
按照打开文件中的格式添加即可;
在文件的适当位置(这个位置随便都可以,但这个位置决定其在make menuconfig窗口中所在位置)加入以下任一段代码:
tristate 'LedDriver' CONFIG_LEDC
if [ "$CONFIG_LEDC" ="y" ];then
bool ' Support for led on h9200 board' CONFIG_LEDC_CONSOLE
fi
说明:以上代码使用tristate来定义一个宏,表示此驱动可以直接编译至内核(用*选择),也可以编制至/lib/modules/下(用M选择),或者不编译(不选)。
bool 'LedDriver' CONFIG_LEDC
if [ "$CONFIG_LEDC" ="y" ];then
bool ' Support for led on h9200 board' CONFIG_LEDC_CONSOLE
fi
说明:以上代码使用tristate来定义一个宏,表示此驱动只能直接编译至内核(用*选择)或者不编译(不选),不能编制至/lib/modules/ 下(用M选择)。
第三步:修改drivers/char/Makefile文件
在适当位置加入下面一行代码:
obj-$(CONFIG_LEDC) += ledc.o
或者在obj-y一行中加入ledc.o,如:
obj-y += ledc.o mem.o 后面不变;
OK,经过以上的设置就可以在执行makemenuconfig命令后的窗口中的character devices---> 中进行选择配置了,选择后重新编译就ok了。
② 动态加载
动态加载是将驱动模块加载到内 核中,而不能放入/lib/modules/下。
在2.4内核中,加载驱动命令为:insmod,删除模块为:rmmod;
在2.6以上内核中,除了insmod与rmmod外,加载命令还有modprobe;
insmod与modprobe不同之处:
insmod 绝对路径/××.o,而modprobe ××即可,不用加.ko或.o后缀,也不用加路径;最重要的一点是:modprobe同时会加载当前模块所依赖的其它模块;
lsmod查看当前加载到内核中的所有驱动模块,同时提供其它一些信息,比如其它模块是否在使用另一个模块。