RockPI 4A Linux内核模块

Linux内核模块除代码实现部分外还需关注:模块定义、链接位置、模块加载和模块优先级。

一、模块定义

Linux内核使用xx_initcall_xx(fn)的宏来定义内核模块。

宏定义文件:include/linux/init.h,定义如下:

/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)      __define_initcall(fn, early)

/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 * Keep main.c:initcall_level_names[] in sync.
 */
#define pure_initcall(fn)       __define_initcall(fn, 0)

#define core_initcall(fn)       __define_initcall(fn, 1)
#define core_initcall_sync(fn)      __define_initcall(fn, 1s)
#define postcore_initcall(fn)       __define_initcall(fn, 2)
#define postcore_initcall_sync(fn)  __define_initcall(fn, 2s)
#define arch_initcall(fn)       __define_initcall(fn, 3)
#define arch_initcall_sync(fn)      __define_initcall(fn, 3s)
#define subsys_initcall(fn)     __define_initcall(fn, 4)
#define subsys_initcall_sync(fn)    __define_initcall(fn, 4s)
#define fs_initcall(fn)         __define_initcall(fn, 5)
#define fs_initcall_sync(fn)        __define_initcall(fn, 5s)
#define rootfs_initcall(fn)     __define_initcall(fn, rootfs)
#define device_initcall(fn)     __define_initcall(fn, 6)
#define device_initcall_sync(fn)    __define_initcall(fn, 6s)
#define late_initcall(fn)       __define_initcall(fn, 7)
#define late_initcall_sync(fn)      __define_initcall(fn, 7s)

其中:early_initcall(fn)只针对内核的核心代码,不能描述模块。

从上面代码可以看出,每个宏的实现都是__define_initcall(),其定义如下:

#define __define_initcall(fn, id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" #id ".init"))) = fn; \
    LTO_REFERENCE_INITCALL(__initcall_##fn##id)
    
typedef int (*initcall_t)(void);

对于上面定义,需要关注以下几点:

1、initcall_t:是一个函数指针类型,定义__initcall_##fn##id,指向fn

2、__used:在文件include/linux/compiler-gcc.h中定义为:# define __used __attribute__((__used__)),通知编译器在目标文件中保留一个静态函数,即使该函数未被使用。

3、__attribute__((__section__(".initcall" #id ".init"))):定义的函数指针位于.initcall*.init段中。

二、链接位置

在文件include/asm-generic/vmlinux.lds.h中,定义了宏INIT_CALLS,定义如下:

#define INIT_CALLS_LEVEL(level)                     \
        VMLINUX_SYMBOL(__initcall##level##_start) = .;      \
        *(.initcall##level##.init)              \
        *(.initcall##level##s.init)             \

#define INIT_CALLS                          \
        VMLINUX_SYMBOL(__initcall_start) = .;           \
        *(.initcallearly.init)                  \
        INIT_CALLS_LEVEL(0)                 \
        INIT_CALLS_LEVEL(1)                 \
        INIT_CALLS_LEVEL(2)                 \
        INIT_CALLS_LEVEL(3)                 \
        INIT_CALLS_LEVEL(4)                 \
        INIT_CALLS_LEVEL(5)                 \
        INIT_CALLS_LEVEL(rootfs)                \
        INIT_CALLS_LEVEL(6)                 \
        INIT_CALLS_LEVEL(7)                 \
        VMLINUX_SYMBOL(__initcall_end) = .;

在文件arch/arm64/kernel/vmlinux.lds.S 中,设置了宏INIT_CALLS.init.data段中的分布位置,内容如下:

    .init.data : {
        INIT_DATA
        INIT_SETUP(16)
        INIT_CALLS
        CON_INITCALL
        SECURITY_INITCALL
        INIT_RAM_FS
    }

在Linux内核编译链接后,会生成文件arch/arm64/kernel/vmlinux.lds。在该文件中将展开宏INIT_CALLS,并分配到.init.data段中,内容如下:

 .init.data : {
...
  __initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;
...
 }

注:在__initcall_start__initcall_end之间是按照从0到7的顺序进行排列,对应pure_initcall(fn)late_initcall_sync(fn)

三、模块加载

Linux内核模块可以直接编译到内核映像,在内核启动时加载;也可以在系统启动后,通过insmod命令加载到内核。

Linux内核启动时,kernel_init线程会实现静态编译的内核模块加载。

1、程序调用流程

## kernel/init/main.c
start_kernel()->
    rest_init()->
        kernel_thread(kernel_init, NULL, CLONE_FS) ## 1.创建内核线程

kernel_init()->
    kernel_init_freeable()->
        do_basic_setup()->
            do_initcalls()->      ## 2.模块加载过程
                do_initcall_level()->
                    do_one_initcall()

2、do_initcalls()函数

函数功能:按顺序扫描.init.data段中的每个等级,即:从__initcall0_start__initcall_end

## __initdata中的定义在
static initcall_t *initcall_levels[] __initdata = {
    __initcall0_start, 
    __initcall1_start,
    __initcall2_start,
    __initcall3_start,
    __initcall4_start,
    __initcall5_start,
    __initcall6_start,
    __initcall7_start,
    __initcall_end,
};

static void __init do_initcalls(void)
{
    int level;

    for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
        do_initcall_level(level);
}

3、do_initcall_level()函数

函数功能:在同一个等级中,按顺序扫描.initcall*.init.initcall*s.init

static void __init do_initcall_level(int level)
{
    ...
    for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
        do_one_initcall(*fn);
}

4、do_one_initcall()函数

函数功能:调用每个定义的模块函数。

int __init_or_module do_one_initcall(initcall_t fn)
{
    ...
    if (initcall_debug)
        ret = do_one_initcall_debug(fn);
    else
        ret = fn(); ##调用某个定义的initcall函数
    ...
    return ret;
}

注:每个fn()对应第一部分模块定义中的fn,例:device_initcall(fn)

四、模块优先级

从上面函数的执行流程可以看出内核模块加载优先级如下:

early_initcall(fn)               ## 优先级最高,后续优先级依次降低
pure_initcall(fn)
core_initcall(fn)
core_initcall_sync(fn)
postcore_initcall(fn)
postcore_initcall_sync(fn)
arch_initcall(fn)
arch_initcall_sync(fn)
subsys_initcall(fn)
subsys_initcall_sync(fn)
fs_initcall(fn)
fs_initcall_sync(fn)
rootfs_initcall(fn)
device_initcall(fn)
device_initcall_sync(fn)
late_initcall(fn)
late_initcall_sync(fn)          ## 优先级最低

Linux内核模块的优先级决定了模块的加载顺序,在驱动开发时,需要关注有启动顺序要求的模块定义。

注:下一篇会通过一个例子对上面部分进行解释。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,386评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,142评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,704评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,702评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,716评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,573评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,314评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,230评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,680评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,873评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,991评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,706评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,329评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,910评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,038评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,158评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,941评论 2 355

推荐阅读更多精彩内容