Chinaunix首页 | 论坛 | 博客
  • 博客访问: 238635
  • 博文数量: 108
  • 博客积分: 3285
  • 博客等级: 中校
  • 技术积分: 1360
  • 用 户 组: 普通用户
  • 注册时间: 2008-04-28 15:43
文章分类

全部博文(108)

文章存档

2014年(1)

2012年(3)

2011年(28)

2010年(20)

2009年(24)

2008年(32)

我的朋友

分类: LINUX

2010-10-12 10:40:39

内核什么时候加载模块?怎么找到合适的驱动?


在Linux底下写过driver模块的对这个宏一定不会陌生。 module_init宏在MODULE宏有没有定义的情况下展开的内容是不同的,如果这个宏没有定义,基本上表明阁下的模块是要编译进内核的(obj- y)。
1.在MODULE没有定义这种情况下,module_init定义如下:


#define module_init(x)        __initcall(x);

因为
#define __initcall(fn)                               device_initcall(fn)
#define device_initcall(fn)                __define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
        static initcall_t __initcall_##fn##id __used \
        __attribute__((__section__(".initcall" level ".init"))) = fn

所以,module_init(x)最终展开为:

static initcall_t __initcall_##fn##id __used \
        __attribute__((__section__(".initcall" level ".init"))) = fn

更直白点,假设阁下driver所对应的模块的初始化函数为int gpio_init(void),那么module_init(gpio_init)实际上等于:

static initcall_t  __initcall_gpio_init_6 __used __attribute__((__section__(".initcall6.init"))) = gpio_init;
就是声明一类型为initcall_t(typedef int (*initcall_t)(void))函数指针类型的变量__initcall_gpio_init_6并将gpio_init赋值与它。
这里的函数指针变量声明比较特殊的地方在于,将这个变量放在了一名为".initcall6.init"节中。接下来结合vmlinux.lds中的
.initcall.init : AT(ADDR(.initcall.init) - (0xc0000000 -0x00000000)) {
  __initcall_start = .;
  *(.initcallearly.init) __early_initcall_end = .; *(.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 = .;
  }
以及do_initcalls:
static void __init do_initcalls(void)
{
        initcall_t *call;

        for (call = __initcall_start; call < __initcall_end; call++)
                do_one_initcall(*call);

        /* Make sure there is no pending stuff from the initcall sequence */
        flush_scheduled_work();
}
那么就不难理解阁下模块中的module_init中的初始化函数何时被调用了:在系统启动过程中start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()。

2.在MODULE被定义的情况下(大部分可动态加载 的driver模块都属于此, obj-m),module_init定义如下:

#define module_init(initfn)                                        \

        static inline initcall_t __inittest(void)                \
        { return initfn; }                                        \
        int init_module(void) __attribute__((alias(#initfn)));

这段宏定义关键点是后面一句,通过alias将initfn变名为init_module。前面那个__inittest的定义其实是种技巧,用来对 initfn进行某种静态的类型检查,如果阁下将模块初始化函数定义成,比如,void gpio_init(void)或者是int gpio_init(int),那么在编译时都会有类似下面的warning:
GPIO/fsl-gpio.c: In function '__inittest':
GPIO/fsl-gpio.c:46: warning: return from incompatible pointer type

通过module_init将模块初始化函数统一别名为init_module,这样以后insmod时候,在系统内部会调用 sys_init_module()去找到init_module函数的入口地址。


如果objdump -t gpio.ko,就会发现init_module和gpio_init位于相同的地址偏移处。简言之,这种情况下模块的初始化函数在insmod时候被调 用。





如何调整Linux内核启动中的驱动初始化顺序


在linux中应用程序如何调用模块内的函数
http://hi.baidu.com/zengzhaonong/blog/item/707d4d669c1fbf25ab184cef.html
Linux设备模型之注册设备devices
http://dev.firnow.com/course/6_system/linux/Linuxjs/20100309/197243.html
文件系统注册及mount过程分析
%3D1

write的奥秘



主 设备号驱动程序在初始化时,会注册它的驱动及对应主设备号到系统中,这样当应用程序访问设备节点时,系统就 知道它所访问的驱动程序了。你可以通过/proc/devices文 件来驱动系统设备的主设备号。

次设备号 驱 动程序遍历设备时,每发现一个它能驱动的设备,就创建一个设备对象,并为其分配一个次设备号以区分不同的设备。这样当应用程序访问设备节点时驱动程序就可 以根据次设备号知道它说访问的设备了。

系统中的每一个字符设备和块设备(网络接口没有设备号)都有一个设备号, 传统的UNIX以及早期版本Linux中 的设备号是16位的,主次设备号都是8位 的,低8位为次设备号,高8位为 主设备号,因此系统最多分别支持65536个字符设备和65536个 块设备,这个限制已经不能满足当前层出不穷的各种新设备的需要,所以Linux2.6中对 设备号已经进行了扩展,一个设备号为32位,主设备号为12位, 次设备号为20位,但是这32位 设备号的编码方式有新旧两种,旧的设备编号格式为:最高12位为主设备号,最低20位 为次设备号;新的设备编号格式为:bit[19:8]是主设备号,bit[31:20]是 次设备号的高12位,bit[7:0]是 次设备号的低8位。如果知道了一个设备的主设备号major和 次设备号minor,那么用MKDEV(major,minor)生 成是该设备的旧格式的设备号,用new_encode_dev(MKDEV(major,minor))生 成的则是新格式的设备号。Linux支持的各种设备的主设备号定义在include/linux/major.h文 件中,而已经在官方注册的主设备号和次设备号在Documentation/devices.txt文件中 可以找到。

老式16位设备 号、32位旧格式设备号以及32位新格式设备号的转换操作函数如下:

new_encode_dev(dev_t dev)函数

32位旧格式设备号dev转换成32位 新格式设备号。

new_decode_dev(u32 dev)函数

32位新格式设备号转换成32位旧格式设备号。

old_encode_dev(dev_t dev)函数

32位旧格式设备号转换成 老式16位设备号。

dev_t old_decode_dev(u16 val)函 数

将老式16位设备号转换成32位 旧格式设备号。

Linux中设备节点是通过mknod命 令来创建的。一个设备节点其实就是一个文件,Linux中 称为设备文件。有一点必要说明的是,在Linux中,所有的设备访 问都是通过文件的方式,一般的数据文件程序普通文件,设备节点称为设备文件。在Linux内 核中网络设备也是通过文件操作的,称为网络设备文件,在用户空间是通过socket接 口来访问的。socket号就是网络设备文件描述符。

如:mknod /dev/mydevice c 254 0

(c代 表子都设备,254为 主设备号,0为 次设备号)

Openclose等 操作/dev/下 设备文件,内核根据文件的主设备号找到对应的设备驱动

主 设备号可以分为动态和静态申请。

设备文件Linux使用对文件一样管理方式来管理设备,所以对于系统中的每个字符设备或者块设备都必须 为其创建一个设备文件,这个设备文件就是放在/dev/目 录下的设备节点,它包含了该设备的设备类型(块设备或字符设备)、设备号(主设备号和次设备号)以及设备访问控制属性等。设备文件可以通过手工用mknod命令生成也可以由udev用 户工具软件在系统启动后根据/sys目录下每个设备的实际信息创 建,使用后一种方式可以为每个设备动态分配设备号,而不必分配固定的设备号,如果系统中的设备不多,而且设备类型又是常见的,可以使用手工方式生成设备文 件,为常用设备创建一个已经分配号的设备号对应的设备文件,这样比较方便。如果系统很大,系统中的设备太多,那么最好动态分配设备号,由udev在系统启动之后根据设备实际信息自动创建设备文件。


内核启动时,设备及驱动初始化的实现

参见include/linux/init.h和vmlinux.lds
1)
所有标识为__init的函数在链接的时候都放在.init.text这个区段内,
在这个区段中,函数的摆放顺序是和链接的顺序有关的,是不确定的。
2)
所有的__init函数在区段.initcall.init中还保存了一份函数指针,
在初始化时内核会通过这些函数指针调用这些__init函数指针,
并在整个初始化完成后,释放整个init区段(包括.init.text,.initcall.init等),
注意,这些函数在内核初始化过程中的调用顺序只和这里的函数指针的顺序有关,
和1)中所述的这些函数本身在.init.text区段中的顺序无关。
在2.4内核中,这些函数指针的顺序也是和链接的顺序有关的,是不确定的。
在2.6内核中,initcall.init区段又分成7个子区段,分别是
.initcall1.init
.initcall2.init
.initcall3.init
.initcall4.init
.initcall5.init
.initcall6.init
.initcall7.init
当需要把函数fn放到.initcall1.init区段时,只要声明
core_initcall(fn);
即可。
其他的各个区段的定义方法分别是:
core_initcall(fn) --->.initcall1.init
postcore_initcall(fn) --->.initcall2.init
arch_initcall(fn) --->.initcall3.init
subsys_initcall(fn) --->.initcall4.init
fs_initcall(fn) --->.initcall5.init
device_initcall(fn) --->.initcall6.init
late_initcall(fn) --->.initcall7.init
而与2.4兼容的initcall(fn)则等价于device_initcall(fn)。
各个子区段之间的顺序是确定的,即先调用.initcall1.init中的函数指针
再调用.initcall2.init中的函数指针,等等。
而在每个子区段中的函数指针的顺序是和链接顺序相关的,是不确定的。
在内核中,不同的init函数被放在不同的子区段中,因此也就决定了它们的调用顺序。
这样也就解决了一些init函数之间必须保证一定的调用顺序的问题。

Uboot完成系统的引导并将Linux内核拷贝到内存之后,bootm -> do_bootm_linux()跳转到kernel的起始位置;

     压缩过的kernel入口在arch/arm/boot/compressed/head.S,它将调用函数decompress_kernel()解 压,打印“Uncompressing Linux...”,调用gunzip(),打印"done, booting the kernel."
       然后call_kernel,执行解压后的kernel,经linux/arch/arm/kernel/head.S调用start_kernel转入 体系结构无关的通用C代码,在start_kernel()中完成了一系列系统初始化,设备及驱动的注册即在此时完成:
-------------------------
asmlinkage void __init [b]start_kernel[/b](void)
{
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];
···········································································
printk(KERN_NOTICE "Kernel command line: %s\n", saved_command_line);
                                                          //打印内核命令行
parse_early_param();
parse_args("Booting kernel", command_line, __start___param,
     __stop___param - __start___param,
     &unknown_bootoption);
                                                        //解析由BOOT传递的启动参数
···········································································
/* Do the rest non-__init'ed, we're now alive */
rest_init();
}
start_kernel()中的函数rest_init()将创建第一个核心线程kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND),调用init()函数:
static int [b]init[/b](void * unused)-------------------
{
                ·······················
                 do_basic_setup();
                ······················
/*
  * We try each of these until one succeeds.
  *
  * The Bourne shell can be used instead of init if we are
  * trying to recover a really broken machine.
  */
if (execute_command) { //判断在启动时是否指定了init参数
                                      //如果指定则执行用户init进程,成功将不会返回
  run_init_process(execute_command);
  printk(KERN_WARNING "Failed to execute %s.  Attempting "
     "defaults...\n", execute_command);
}
               /*   如果没有指定init启动参数,则查找下面的目录init进程,成功将不会返回,否则打印出错信息   */
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
panic("No init found.  Try passing init= option to kernel.");
}
继而调用函数do_basic_setup()(此时与体系结构相关的部分已经初始化完了,现在开始初始化设备了):
/*
* Ok, the machine is now initialized. None of the devices
* have been touched yet, but the CPU subsystem is up and
* running, and memory and process management works.
*
* Now we can finally start doing some real work..
*/
static void __init [b]do_basic_setup[/b](void)-----------------
{
/* drivers will send hotplug events */
init_workqueues();
usermodehelper_init();
driver_init();     //建立设备模型子系统
#ifdef CONFIG_SYSCTL
sysctl_init();
#endif
/* Networking initialization needs a process context */
sock_init();
do_initcalls();   //系统初始化(包括设备,文件系统,内核模块等)
}
-------------------------
/**
* driver_init - initialize driver model.
*
* Call the driver model init functions to initialize their
* subsystems. Called early from init/main.c.
*/
void __init [b]driver_init[/b](void)
{
/* These are the core pieces */
devices_init();
                       -------------
                                  int __init devices_init(void)
                                  {
                   return subsystem_register(&devices_subsys);
                                  }
                        -----------------------
buses_init();
classes_init();
firmware_init();
/* These are also core pieces, but must come after the
  * core core pieces.
  */
platform_bus_init();
system_bus_init();
cpu_dev_init();
memory_dev_init();
attribute_container_init();
}
---------------------------
extern initcall_t __initcall_start[], __initcall_end[];
static void __init [b]do_initcalls[/b](void)
{
initcall_t *call;
int count = preempt_count();
for (call = __initcall_start; call -----------------
  __initcall_start = .;
   *(.initcall1.init)
   *(.initcall2.init)
   *(.initcall3.init)
   *(.initcall4.init)
   *(.initcall5.init)
   *(.initcall6.init)
   *(.initcall7.init)
  __initcall_end = .;
---------------------
#ifndef MODULE     /*    如果驱动模块静态编译进内核   */
  ···············································
/* initcalls are now grouped by functionality into separate
* subsections. Ordering inside the subsections is determined
* by link order.
* For backwards compatibility, initcall() puts the call in
* the device init subsection.
*/
#define __define_initcall(level,fn) \
static initcall_t __initcall_##fn __attribute_used__ \
__attribute__((__section__(".initcall" level ".init"))) = fn
#define core_initcall(fn)  __define_initcall("1",fn)
#define postcore_initcall(fn)  __define_initcall("2",fn)
#define arch_initcall(fn)  __define_initcall("3",fn)
                                           [b] //此处初始化了设备
[/b]                                           /*----eg:arch_initcall(at91sam9261_device_init)---
                                               static int __init at91sam9261_device_init(void)
                                               {
                                                 at91_add_device_udc();
                                                 at91_add_device_dm9000();
                                                 armebs3_add_input_buttons();
                                                 return platform_add_devices(at91sam9261_devices, ARRAY_SIZE(at91sam9261_devices));
                                                }
                                        ------------------------*/
#define subsys_initcall(fn)  __define_initcall("4",fn)
#define fs_initcall(fn)  __define_initcall("5",fn)
#define device_initcall(fn)  __define_initcall("6",fn)
                                           [b]//此处初始化了静态编译的驱动模块[/b]
#define late_initcall(fn)  __define_initcall("7",fn)
#define __initcall(fn) device_initcall(fn)
  /**
* module_init() - driver initialization entry point
* @x: function to be run at kernel boot time or module insertion
*
* module_init() will either be called during do_initcalls (if
* builtin) or at module insertion time (if a module).  There can only
* be one per module.
*/
#define module_init(x) __initcall(x);
                                       //静态编译的驱动模块作为device_initcall在内核启动就被do_initcalls
/**
* module_exit() - driver exit entry point
* @x: function to be run when driver is removed
*
* module_exit() will wrap the driver clean-up code
* with cleanup_module() when used with rmmod when
* the driver is a module.  If the driver is statically
* compiled into the kernel, module_exit() has no effect.
* There can only be one per module.
*/
#define module_exit(x) __exitcall(x);
#else /* MODULE    如果驱动模块动态加载入内核   */
  ···············································
/* Each module must use one module_init(), or one no_module_init */
#define module_init(initfn)     \
static inline initcall_t __inittest(void)  \
{ return initfn; }     \
int init_module(void) __attribute__((alias(#initfn)));
     //insmod 是通过系统调用sys_init_module(const char *name_user, struct module *mod_user)
     //将动态驱动模块载入到内核空间
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn)     \
static inline exitcall_t __exittest(void)  \
{ return exitfn; }     \
void cleanup_module(void) __attribute__((alias(#exitfn)));
阅读(650) | 评论(0) | 转发(0) |
0

上一篇:glibc 和系统调用

下一篇:可变参数宏

给主人留下些什么吧!~~