linux设备驱动之__init和__exit




像你写C程序需要包含C库的头文件那样,Linux内核编程也需要包含Kernel头文件,大多的Linux驱动程序需要包含下面三个头文件: 



#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
其中,init.h 定义了驱动的初始化和退出相关的函数,kernel.h 定义了经常用到的函数原型及宏定义,module.h 定义了内核模块相关的函数、变量及宏。 


      几乎每个linux驱动都有个module_init(与module_exit的定义在Init.h (\include\linux) 中)。没错,驱动的加载就靠它。为什么需要这样一个宏?原因是按照一般的编程想法,各部分的初始化函数会在一个固定的函数里调用比如:


void init(void)


{


    init_a();


    init_b();


}


如果再加入一个初始化函数呢,那么在init_b()后面再加一行:init_c();这样确实能完成我们的功能,但这样有一定的问题,就是不能独立的添加初始化函数,每次添加一个新的函数都要修改init函数。可以采用另一种方式来处理这个问题,只要用一个宏来修饰一下:


void init_a(void)


{


}


__initlist(init_a, 1);


它是怎么样通过这个宏来实现初始化函数列表的呢?先来看__initlist的定义:

#define __init __attribute__((unused, __section__(".initlist")))


#define __initlist(fn, lvl) \
static initlist_t __init_##fn __init = { \
 magic:    INIT_MAGIC, \
 callback: fn, \
 level:   lvl }


请注意:__section__(".initlist"),这个属性起什么作用呢?它告诉连接器这个变量存放在.initlist区段,如果所有的初始化函数都是用这个宏,那么每个函数会有对应的一个initlist_t结构体变量存放在.initlist区段,也就是说我们可以在.initlist区段找到所有初始化函数的指针。怎么找到.initlist区段的地址呢?


extern u32 __initlist_start;
extern u32 __initlist_end;


这两个变量起作用了,__initlist_start是.initlist区段的开始,__initlist_end是结束,通过这两个变量我们就可以访问到所有的初始化函数了。这两个变量在那定义的呢?在一个连接器脚本文件里


 . = ALIGN(4);
 .initlist : {
  __initlist_start = .;
  *(.initlist)
  __initlist_end = .;
 }
这两个变量的值正好定义在.initlist区段的开始和结束地址,所以我们能通过这两个变量访问到所有的初始化函数。


      与此类似,内核中也是用到这种方法,所以我们写驱动的时候比较独立,不用我们自己添加代码在一个固定的地方来调用我们自己的初始化函数和退出函数,连接器已经为我们做好了。先来分析一下module_init。定义如下:


#define module_init(x)     __initcall(x);              //include\linux\init.h


#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


      如果某驱动想以func作为该驱动的入口,则可以如下声明:module_init(func);被上面的宏处理过后,变成__initcall_func6 __used加入到内核映像的".initcall"区。内核的加载的时候,会搜索".initcall"中的所有条目,并按优先级加载它们,普通驱动程序的优先级是6。其它模块优先级列出如下:值越小,越先加载。


#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)


可以看到,被声明为pure_initcall的最先加载。


      module_init除了初始化加载之外,还有后期释放内存的作用。linux kernel中有很大一部分代码是设备驱动代码,这些驱动代码都有初始化和反初始化函数,这些代码一般都只执行一次,为了有更有效的利用内存,这些代码所占用的内存可以释放出来。


      linux就是这样做的,对只需要初始化运行一次的函数都加上__init属性,__init 宏告诉编译器如果这个模块被编译到内核则把这个函数放到(.init.text)段,module_exit的参数卸载时同__init类似,如果驱动被编译进内核,则__exit宏会忽略清理函数,因为编译进内核的模块不需要做清理工作,显然__init和__exit对动态加载的模块是无效的,只支持完全编译进内核。


      在kernel初始化后期,释放所有这些函数代码所占的内存空间。连接器把带__init属性的函数放在同一个section里,在用完以后,把整个section释放掉。当函数初始化完成后这个区域可以被清除掉以节约系统内存。Kenrel启动时看到的消息“Freeing unused kernel memory: xxxk freed”同它有关。




      我们看源码,init/main.c中start_kernel是进入kernel()的第一个c函数,在这个函数的最后一行是rest_init();


static void rest_init(void)
{
     .....


     kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
     unlock_kernel();
     cpu_idle();


     .....
}
创建了一个内核线程,主函数kernel_init末尾有个函数:


 /*
  * Ok, we have completed the initial bootup, and
  * we're essentially up and running. Get rid of the

  * initmem segments and start the user-mode stuff..
  */
 init_post();


这个init_post中的第一句就是free_initmem();就是用来释放初始化代码和数据的。


void free_initmem(void)
{
    if (!machine_is_integrator() && !machine_is_cintegrator()) {
    free_area((unsigned long)(&__init_begin),
     (unsigned long)(&__init_end),
     "init");
     }
}


接下来就是kernel内存管理的事了。


【2】

在kernel中有很多__init,这个东东到底是何方神圣捏?且听小生我一一道来。
下面是其定义:
file:/include/linux/init.h
 43 #define __init      __attribute__ ((__section__ (".init.text"))) __cold
 44 #define __initdata  __attribute__ ((__section__ (".init.data")))
 45 #define __exitdata  __attribute__ ((__section__(".exit.data")))
 46 #define __exit_call __attribute_used__ __attribute__ ((__section__ (".exitcall.exit")))

也许你会问那 __attribute__ ((__section__ (".init.text"))) __cold是什么东东阿?
且看 info gcc C Extensions Attribute Syntax
section ("SECTION-NAME")'
     Normally, the compiler places the objects it generates in sections
     like `data' and `bss'.  Sometimes, however, you need additional
     sections, or you need certain particular variables to appear in
     special sections, for example to map to special hardware.  The
     `section' attribute specifies that a variable (or function) lives
     in a particular section.  For example, this small program uses
     several specific section names:
          struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };
          struct duart b __attribute__ ((section ("DUART_B"))) = { 0 };
          char stack[10000] __attribute__ ((section ("STACK"))) = { 0 };
          int init_data __attribute__ ((section ("INITDATA"))) = 0;

          main()
          {
            /* Initialize stack pointer */
            init_sp (stack + sizeof (stack));

            /* Initialize initialized data */
            memcpy (&init_data, &data, &edata - &data);

            /* Turn on the serial ports */
            init_duart (&a);
            init_duart (&b);
          }

     Use the `section' attribute with an _initialized_ definition of a
     _global_ variable, as shown in the example.  GCC issues a warning
     and otherwise ignores the `section' attribute in uninitialized
     variable declarations.

     You may only use the `section' attribute with a fully initialized
     global definition because of the way linkers work.  The linker
     requires each object be defined once, with the exception that
     uninitialized variables tentatively go in the `common' (or `bss')
     section and can be multiply "defined".  You can force a variable
     to be initialized with the `-fno-common' flag or the `nocommon'
     attribute.

     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.

简单来说是指示gcc把标记的数据或者函数放到指定sector。
linux中把一些启动及初始化时候用的数据用__init标识,然后在适当的时候把它们释放,回收内存。
说到这个__init,就不能不说module_init,subsys_initcall。
在init.h中我们能够找到 #define subsys_initcall(fn)     __define_initcall("4",fn,4)
又是一个宏定义,简直是无极中的圆环套圆环之城阿。
file:/include/linux/init.h
100 /* initcalls are now grouped by functionality into separate 
101  * subsections. Ordering inside the subsections is determined
102  * by link order. 
103  * For backwards compatibility, initcall() puts the call in 
104  * the device init subsection.
105  *
106  * The `id' arg to __define_initcall() is needed so that multiple initcalls
107  * can point at the same handler without causing duplicate-symbol build errors.
108  */
109 

110 #define __define_initcall(level,fn,id) \
111     static initcall_t __initcall_##fn##id __attribute_used__ \
112     __attribute__((__section__(".initcall" level ".init"))) = fn


subsys_initcall(usb_init)转换后就变成了 static initcall_t  __initcall_usbinit4   __attribute_used__ \
__attribute__((__section__(".initcall 4.init"))) = usb_init
就是把usb_init的函数入口指针存放在.initcall4.init中。
file:/include/asm-generic/vmlinux.lds.h
239 #define INITCALLS                           \
240     *(.initcall0.init)                      \
241     *(.initcall0s.init)                     \
242     *(.initcall1.init)                      \
243     *(.initcall1s.init)                     \
244     *(.initcall2.init)                      \
245     *(.initcall2s.init)                     \
246     *(.initcall3.init)                      \
247     *(.initcall3s.init)                     \
248     *(.initcall4.init)                      \
249     *(.initcall4s.init)                     \
250     *(.initcall5.init)                      \
251     *(.initcall5s.init)                     \
252     *(.initcallrootfs.init)                     \
253     *(.initcall6.init)                      \
254     *(.initcall6s.init)                     \
255     *(.initcall7.init)                      \
256     *(.initcall7s.init)


file:/arch/kernel/vmlinux_32.lds.S
144   .initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
145     __initcall_start = .;
146     INITCALLS
147     __initcall_end = .;
148   }

展开
   .initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
     __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 = .;
   }


那么系统是如何执行这些函数呢?
此话就长了阿~ 话说盘古开天芙蓉姐姐补天后我们来到了main.c这个linux中举足轻重的文件
进入start_kernel
start_kernel  -->rest_init() -->kernel_init()  --> do_basic_setup()  -->do_initcalls()

这个do_initcalls()就是调用这些函数的地方。
file:/init/main.c
662 static void __init do_initcalls(void)
663 {
664     initcall_t *call;
665     int count = preempt_count();
666 
667     for (call = __initcall_start; call < __initcall_end; call++) {
668         ktime_t t0, t1, delta;
669         char *msg = NULL;
670         char msgbuf[40];
671         int result;
672 
673         if (initcall_debug) {
674             printk("Calling initcall 0x%p", *call);
675             print_fn_descriptor_symbol(": %s()",
676                     (unsigned long) *call);
677             printk("\n");
678             t0 = ktime_get();
679         }
680 
681         result = (*call)();
682 
683         if (initcall_debug) {
684             t1 = ktime_get();
685             delta = ktime_sub(t1, t0);
686 
687             printk("initcall 0x%p", *call);
688             print_fn_descriptor_symbol(": %s()",
689                     (unsigned long) *call);
690             printk(" returned %d.\n", result);
691 
692             printk("initcall 0x%p ran for %Ld msecs: ",
693                 *call, (unsigned long long)delta.tv64 >> 20);
694             print_fn_descriptor_symbol("%s()\n",
695                 (unsigned long) *call);
696         }
697 
698         if (result && result != -ENODEV && initcall_debug) {
699             sprintf(msgbuf, "error code %d", result);
700             msg = msgbuf;
701         }
702         if (preempt_count() != count) {
703             msg = "preemption imbalance";
704             preempt_count() = count;
705         }
706         if (irqs_disabled()) {
707             msg = "disabled interrupts";
708             local_irq_enable();
709         }
710         if (msg) {
711             printk(KERN_WARNING "initcall at 0x%p", *call);
712             print_fn_descriptor_symbol(": %s()",
713                     (unsigned long) *call);
714             printk(": returned with %s\n", msg);
715         }
716     }
717 
718     /* Make sure there is no pending stuff from the initcall sequence */
719     flush_scheduled_work();
720 }


参考资料

[1] http://blog.csdn.net/zhandoushi1982/article/details/4927579

[2] http://www.cnblogs.com/hustcat/articles/1570063.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值