正在加载
请稍等

菜单

红楼飞雪 梦

15526773247

文章

数据结构

android 嵌入式开发, linux开发学习 by

1.模块符号

如前所述,Linux内核是一个整体结构,而模块是插入到内核中的插件。尽管内核不是一个可安装模块,但为了方便起 见,Linux把内核也看作一个模块。那么模块与模块之间如何进行交互呢,一种常用的方法就是共享变量和函数。但并不是模块中的每个变量和函数都能被共 享,内核只把各个模块中主要的变量和函数放在一个特定的区段,这些变量和函数就统称为符号。到低哪些符号可以被共享? Linux内核有自己的规定。对于内核模块,在kernel/ksyms.c中定义了从中可以“移出”的符号,例如进程管理子系统可以“移出”的符号定义如下:

/* process memory management */
EXPORT_SYMBOL(do_mmap_pgoff);
EXPORT_SYMBOL(do_munmap);
EXPORT_SYMBOL(do_brk);
EXPORT_SYMBOL(exit_mm);
EXPORT_SYMBOL(exit_files);
EXPORT_SYMBOL(exit_fs);
EXPORT_SYMBOL(exit_sighand);
EXPORT_SYMBOL(complete_and_exit);
EXPORT_SYMBOL(__wake_up);
EXPORT_SYMBOL(__wake_up_sync);
EXPORT_SYMBOL(wake_up_process);
EXPORT_SYMBOL(sleep_on);
EXPORT_SYMBOL(sleep_on_timeout);
EXPORT_SYMBOL(interruptible_sleep_on);
EXPORT_SYMBOL(interruptible_sleep_on_timeout);
EXPORT_SYMBOL(schedule);
EXPORT_SYMBOL(schedule_timeout);
EXPORT_SYMBOL(jiffies);
EXPORT_SYMBOL(xtime);
EXPORT_SYMBOL(do_gettimeofday);
EXPORT_SYMBOL(do_settimeofday);

你可能对这些变量和函数已经很熟悉。其中宏定义EXPORT_SYMBOL()本身的含义是“移出符号”。为什么说是“移出”呢?因为这些符号本来是内核内部的符号,通过这个宏放在一个公开的地方,使得装入到内核中的其他模块可以引用它们。

实际上,仅仅知道这些符号的名字是不够的,还得知道它们在内核映像中的地址才有意义。因此,内核中定义了如下结构来描述模块的符号:

struct module_symbol
{
    unsigned long value;
    /*符号在内核映像中的地址*/
    const char *name;   /*指向符号名的指针*/
};

从后面对EXPORT_SYMBOL宏的定义可以 看出,连接程序(ld)在连接内核映像时将这个结构存放在一个叫做“__ksymtab”的区段中,而这个区段中所有的符号就组成了模块对外“移出”的符 号表,这些符号可供内核及已安装的模块来引用。而其他“对内”的符号则由连接程序自行生成,并仅供内部使用。

与EXPORT_SYMBOL相关的定义在include/linux/module.h中:

#define __MODULE_STRING_1(x)    #x
#define __MODULE_STRING(x)      __MODULE_STRING_1(x)
#define __EXPORT_SYMBOL(sym, str)                       \
const char __kstrtab_##sym[]                            \
__attribute__((section(".kstrtab"))) = str;\
const struct module_symbol __ksymtab_##sym              \
__attribute__((section("__ksymtab"))) =                 \
        {(unsigned long)&sym, __kstrtab_##sym }
#if defined(MODVERSIONS) || !defined(CONFIG_MODVERSIONS)
#define EXPORT_SYMBOL(var)  __EXPORT_SYMBOL(var, __MODULE_STRING(var))

下面我们以EXPORT_SYMBOL(schedule)为例,来看一下这个宏的结果是什么。

首 先EXPORT_SYMBOL(schedule)的定义成了__EXPORT_SYMBOL(schedule, “schedule”)。而__EXPORT_SYMBOL()定义了两个语句,第一个语句定义了一个名为__kstrtab_ schedule的字符串,将字符串的内容初始化为“schedule”,并将其置于内核映像中的.kstrtab区段,注意这是一个专门存放符号名字符 串的区段。第二个语句则定义了一个名为__kstrtab_ schedule的module_symbol结构,将其初始化为{&schedule,__kstrtab_ schedule}结构,并将其置于内核映像中的__ksymtab区段。这样,module_symbol结构中的域value的值就为 schedule在内核映像中的地址,而指针name则指向字符串“schedule”。

2.模块引用(module reference)

模块引用是一个不太好理解的概念。 有些装入内核的模块必须依赖其它模块, 例如,因为VFAT文件系统是FAT文件系统或多或少的扩充集,那么,VFAT文件系统依赖(depend)于FAT文件系统,或者说,FAT模块被 VFAT模块引用,或换句话说,VFAT为“父”模块,FAT为“子”模块。其结构如下:

struct module_ref
{
    struct module *dep;     /* “父”模块指针*/
    struct module *ref;     /* “子”模块指针*/
    struct module_ref *next_ref;
    /*指向下一个子模块的指针*/
};

在这里“dep”指的是依赖,也就是引用,而“ref”指的是被引用。因为模块引用的关系可能延续下去,例如A引用B,B有引用C,因此,模块的引用形成一个链表。

3. 模块

模块的结构为module ,其定义如下:

struct module_persist; /* 待决定 */
struct module
{
    unsigned long size_of_struct;   /* 模块结构的大小,即sizeof(module) */
    struct module *next;
    /* 指向下一个模块 */
    const char *name;
    /*模块名,最长为64个字符*/
    unsigned long size;
    /*以页为单位的模块大小*/
    union
    {
        atomic_t usecount;
        /*使用计数,对其增减是原子操作*/
        long pad;
    } uc;                      /* Needs to keep its size - so says rth */
    unsigned long flags;            /* 模块的标志 */
    unsigned nsyms;        /* 模块中符号的个数 */
    unsigned ndeps;       /* 模块依赖的个数 */
    struct module_symbol *syms; /* 指向模块的符号表,表的大小为nsyms */
    struct module_ref deps;
    /*指向模块引用的数组,大小为ndeps */
    struct module_ref *refs;
    int (*init)(void);      /* 指向模块的init_module()函数 */
    void (*cleanup)(void);  /* 指向模块的cleanup_module()函数 */
    const struct exception_table_entry *ex_table_start;
    const struct exception_table_entry *ex_table_end;
    /* 以下域是在以上基本域的基础上的一种扩展,因此是可选的。可以调用
    mod_member_present()函数来检查以下域的存在与否。  */
    const struct module_persist *persist_start;
    /*尚未定义*/
    const struct module_persist *persist_end;
    int (*can_unload)(void);
    int runsize                      /*尚未使用*/
    const char *kallsyms_start;     /*用于内核调试的所有符号 */
    const char *kallsyms_end;
    const char *archdata_start;     /* 与体系结构相关的特定数据*/
    const char *archdata_end;
    const char *kernel_data;        /*保留 */
};

其中,moudle中的状态,即flags的取值定义如下:

/* Bits of module.flags.  */
#define MOD_UNINITIALIZED       0 /*模块还未初始化*/
#define MOD_RUNNING             1 /*模块正在运行*/
#define MOD_DELETED             2  /*卸载模块的过程已经启动*/
#define MOD_AUTOCLEAN           4 /*安装模块时带有此标记,表示允许自动卸载模块*/
#define MOD_VISITED             8  /*模块被访问过*/
#define MOD_USED_ONCE           16 /*模块已经使用过一次*/
#define MOD_JUST_FREED          32 /*模块刚刚被释放*/
#define MOD_INITIALIZING        64 /*正在进行模块的初始化*/-/

如 前所述,虽然内核不是可安装模块,但它也有符号表,实际上这些符号表受到其他模块的频繁引用,将内核看作可安装模块大大简化了模块设计。因此,内核也有一 个module结构,叫做kernel_module,与kernel_module相关的定义在kernel/module_c中:

#if defined(CONFIG_MODULES) || defined(CONFIG_KALLSYMS)
extern struct module_symbol __start___ksymtab[];
extern struct module_symbol __stop___ksymtab[];
extern const struct exception_table_entry __start___ex_table[];
extern const struct exception_table_entry __stop___ex_table[];
extern const char __start___kallsyms[] __attribute__ ((weak));
extern const char __stop___kallsyms[] __attribute__ ((weak));
struct module kernel_module =
{
        size_of_struct:         sizeof(struct module),
        name:                   "",
        uc:                     {ATOMIC_INIT(1)},
        flags:                  MOD_RUNNING,
        syms:                   __start___ksymtab,
        ex_table_start:         __start___ex_table,
        ex_table_end:           __stop___ex_table,
        kallsyms_start:         __start___kallsyms,
        kallsyms_end:           __stop___kallsyms,
};

首先要说明的是,内核对可安装模块的的支持是可选的。如果在编译内核代码之前的系统配置阶段选择了可安装模块,就定义了编译提示CONFIG_MODULES,使支持可安装模块的代码受到编译。同理,对用于内核调试的符号的支持也是可选的。

凡 是在以上初始值未出现的域,其值均为0或NULL。显然,内核没有init_module()和cleanup_module()函数,因为内核不是一个 真正的可安装模块。同时,内核没有deps数组,开始时也没有refs链。可是,这个结构的指针syms指向__start___ksymtab,这就是 内核符号表的起始地址。符号表的大小nsyms为0,但是在系统能初始化时会在init_module()函数中将其设置成正确的值。

在模 块映像中也可以包含对异常的处理。发生于一些特殊地址上的异常,可以通过一种描述结构exception_table_entry规定对异常的反映和处 理,这些结构在可执行映像连接时都被集中在一个数组中,内核的exception_table_entry结构数组就为 __start___ex_table[]。当异常发生时,内核的异常响应处理程序就会先搜索这个数组,看看是否对所发生的异常规定了特殊的处理,相关内 容请看第四章。

另外,从kernel_module开始,所有已安装模块的module结构都链在一起成为一条链,内核中的全局变量module_list就指向这条链:

struct module *module_list = &kernel_module;

 

27 2015-11

 

我要 分享

 

 

本文 作者

 

相关 文章