驱动开发第一步,入门,最简单的驱动代码编写

本文介绍了Linux驱动开发的基础,包括module结构体、initcall调用级别和模块的生命周期。通过示例解释了如何使用module_init和module_exit注册初始化和退出函数,并探讨了编译时链接脚本的作用。驱动开发涉及struct module、initcall调用链及内核加载模块的流程。

结构体struct module内核的其中之一的模块,多个模块形成的链表是模块的集合。通过insmod(实际执行module_init系统调用)把本模块插入集合,相当于插入链表当中。

如上所述会逐步替换,最终会替换成如下:(所以说以上的宏使用都是等价的,都可以使用)

至于如下的这个是什么意思? 我还没弄明白。

      static initcall_t __initcall_##fn##id __used /

      __attribute__((__section__(".initcall" level ".init"))) = fn

 

其中level参数即0~7(实际是0~7s)共14个级别,

 

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

 

这样内核在编译时会生产14个名为”.initcall.(0~7s).init”section,例如.initcall0.init.initcall2s.init等。被不同宏修饰的函数被放到对应的section中,例如上例的fs_initcall(acpi_event_init)最终会被放到.initcall5.init

 


    我们向内核注册一个模块,一般会使用命令insmod,该命令实际上调用了系统调用module_init(),在该系统调用函数中,首先调用module_init(),参数传递过来的初始化函数.

如:

static int __init init_function(){…..}

module_init(init_function);

在模块自动加载或手动注册的时候,会自动调用init_function函数,切记init_function的函数原型(static int __init init_function(){…..}),一定不可以修改,必须这么写,这是内核的规定。

 

 __init __initdata等属性标志, 是要把这种属性的代码放入目标文件的.init.text节, 数据放入.init.data──这一过程是通过编译内核时为相关目标平台提供了xxx.lds链接脚本, 来指导ld完成的。 对模s3c2410来说, 可以参考arch/s3c2410/kernel/vmlinux.lds.S文件。

对编译成module的代码和数据来说, 当模块加载时, __init属性的函数就被执行;
对静态编入内核的代码和数据来说, 当内核引导时, do_basic_setup()函数调用do_initcalls()函数, 后者负责所有.init节函数的执行。

module_init(init_function); 对应的函数是module_exit(init_function);

如:

static void __exit exit_function(){…..}

module_exit(exit_function);

 

__exit修饰词标记函数只在模块卸载时使用。
  如果模块被直接编进内核则该函数就不会被调用。如果内核编译时没有包含该模块,则此标记的函数将被简单地丢弃。

 

下面是一个简单的例子

include <linux/init.h>

#include <linux/module.h>

//“Dual BSD/GPL”或者 “Dual MPL/GPL”或者 “GPL ,GPL v2,”其中之一

MODULE_LICENSE(*****);

static int __init init_function(void)

{

 //加载模块时调用

    return 0;

}

static void  __exit exit_function(void)

{  

//模块卸载时调用

}

//切记一定要记得写下面2个方法。最好要在函数的最后写,避免不必要的函数声明

module_init(init_function);

module_exit(exit_function);

 

 

 

 

 

 

 

 

 

链接脚本的如下代码:

  .initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
      __initcall_start = .;
       INITCALLS
      __initcall_end = .;
  }

 

生成了initcall表,起始地址和结束地址存在__initcall_start__initcall_end中。很多资料说根据level参数的值不同,内核在不同阶段调用这些函数。但从代码来看,我认为并非如此,内核只分两个阶段调用,即early initcall阶段和剩余阶段(level = 0~7s)。感兴趣的朋友可以看看上面INITCALLS宏的展开以及do_initcalls()do_pre_smp_initcalls()两个函数,很容易就能明白。
在构建data segment的最后部分,我们看到如下代码:
  .bss : AT(ADDR(.bss) - LOAD_OFFSET) {
       __init_end = .;
       __bss_start = .;              /* BSS */
       *(.bss.page_aligned)
       *(.bss)
       . = ALIGN(4);
       __bss_stop = .;
      _end = . ;
       /* This is where the kernel creates the early boot page tables */
       . = ALIGN(PAGE_SIZE);
       pg0 = . ;
  }
它告诉我们.bss section位于data segment的最后,变量pg0存放的是“Provisional kernel Page Tables”的地址,

 模块便与一个struct module结构体相关联,并成为内核的一部分。

下面是结构体struct module的完整定义,接下来会逐个解释:
    struct module
    {
        enum module_state state;
        struct list_head list;
        char name[MODULE_NAME_LEN];

        struct module_kobject mkobj;
        struct module_param_attrs *param_attrs;
        const char *version;
        const char *srcversion;

        const struct kernel_symbol *syms;
        unsigned int num_syms;
        const unsigned long *crcs;

        const struct kernel_symbol *gpl_syms;
        unsigned int num_gpl_syms;
        const unsigned long *gpl_crcs;

        unsigned int num_exentries;
        const struct exception_table_entry *extable;

        int (*init)(void);
        void *module_init;
        void *module_core;
        unsigned long init_size, core_size;
        unsigned long init_text_size, core_text_size;
        struct mod_arch_specific arch;
        int unsafe;
        int license_gplok;

#ifdef CONFIG_MODULE_UNLOAD
        struct module_ref ref[NR_CPUS];
        struct list_head modules_which_use_me;
        struct task_struct *waiter;
        void (*exit)(void);
#endif

#ifdef CONFIG_KALLSYMS
        Elf_Sym *symtab;
        unsigned long num_symtab;
        char *strtab;
        struct module_sect_attrs *sect_attrs;
#endif
        void *percpu;
        char *args;
    };

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值