• attribute section 属性 Hello


    一、__attribute__((__section__(section_name))) 简介

    1. __attribute__((section("name"))) 是gcc编译器支持的一个编译特性(arm编译器也支持此特性),实现在编译时把某个函数/数据放到名为name的数据段中。原理如下:
    (1) 模块通过 __attribute__((section("name"))),会构建初始化函数表,放到你命名为的name数据段中。
    (2) 而默认链接脚本缺少自定义的数据段的声明,需要在链接脚本添加你定义的数据段的声明。
    (3) main在执行初始化时,只需要把name数据段中的所有初始化接口执行一遍,即可实现不同模块间的隔离效果。

    那么这里有两个问题 :
    (1) 如何再链接脚本中添加自己定义的数据段的声明.
    (2) main是如何将放入数据段的模块接口都执行了一遍.

    2. 链接脚本处理
    内核是根据不同的架构,调用内核自己写的对应的链接脚本。ld链接命令有两个关键的选项如下:

    ld -T <script> //指定链接时的链接脚本
    ld --verbose //打印出默认的链接脚本

    内核最终其实用了 ld -T arch/$(SRCARCH)/kernel/vmlinux.lds.S 指定架构对应的链接脚本,对于ARM64架构使用的就是 arch/arm64/kernel/vmlinux.lds.S。我们以”ARCH=arm“ 为例,查看链接脚本:arch/arm/kernel/vmlinux.lds,可以发现其实就在 .bss 数据段前添加了自己定义数据段的声明,如下:

    //arch/arm64/include/asm/module.lds.h:
    SECTIONS {
        #define INIT_CALLS_LEVEL(level)             \
            KEEP(*(.initcall##level##.init*))     \
            KEEP(*(.initcall##level##s.init*))
    
        .initcalls : {
            *(.initcalls._start)
            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)
            *(.initcalls._end)
        }
    }

    3. 举个内核中的例子

    //include/linux/module.h
    #define module_init(x)    __initcall(x);
    
    //include/linux/init.h
    #define __initcall(fn)    device_initcall(fn)
    #define device_initcall(fn)    __define_initcall(fn, 6)
    #define __define_initcall(fn, id) \
        static initcall_t __initcall_##fn##id \
        __used __attribute__((__section__(".initcall" #id ".init"))) = fn;
    
    //module_init(ip_tables_init)展开为:
    static initcall_t  _initcall_ip_tables_init_6    __attribute__((unused, section(".initcall6.init"))) = ip_tables_init;

    main 执行初始化函数:

    typedef int (*initcall_t)(void);
    
    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);
    }
    
    static void __init do_initcall_level(int level)
    {
        ......
        for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
            do_one_initcall(*fn);
    }
    
    int __init_or_module do_one_initcall(initcall_t fn)
    {
        ......
        if (initcall_debug)
            ret = do_one_initcall_debug(fn);
        else
            ret = fn();
        ......
    }

    通过上述的代码追踪,我们发现 module_init 的实现有以下关键步骤:

    (1) 通过 module_init 的宏,在编译时,把初始化函数放到了数据段:.initcall6.init。
    (2) 在内核自定义的链接,申明了.initcall6.init 的数据段存放的位置,以及指向数据段地址的变量 _initcall6_start。
    (3) 在init/main.c中的for循环,通过_initcall6_start 的指针,调用了所有注册的驱动模块的初始化接口。
    (4) 最后通过 Kconfig/Makefile 选择编译的驱动,实现只要编译了驱动代码,则自动把驱动的初始化函数构建到统一的驱动初始化函数表。

    二、移植内核代码测试

    1. 在看内核中thermal驱动的代码时发现,各个governor就使用了 __attribute__ __section__ 属性,于是将其精简后移植到用户空间:

    #include <stdio.h>
    
    
    struct thermal_governor {
            char name[32];
            int (*bind_to_tz)(void);
            //struct list_head      governor_list;
    };
    
    //gcc 编译属性定义
    #define __section(section)    __attribute__((__section__(section)))
    //也就是向编译器说明这段代码有用,即使在没有用到的情况下编译器也不会警告,实测,这个不定义也行
    #define __used   __attribute__((__used__))
    
    //编译器自动生成的变量
    extern struct thermal_governor *__governor_thermal_table[];
    extern struct thermal_governor *__governor_thermal_table_end[];
    
    #define THERMAL_TABLE_ENTRY(table, name)                        \
            static typeof(name) *__thermal_table_entry_##name       \
            __used __section("__" #table "_thermal_table") = &name
    
    #define THERMAL_GOVERNOR_DECLARE(name)  THERMAL_TABLE_ENTRY(governor, name)
    
    //定义遍历函数
    #define for_each_governor_table(__governor)             \
        for (__governor = __governor_thermal_table; __governor < __governor_thermal_table_end; __governor++)
    
    
    //各个模块的使用,分布在不同模块中,这里写在一起了
    static struct thermal_governor thermal_gov_power_allocator = {
        .name = "power_allocator",
    };
    THERMAL_GOVERNOR_DECLARE(thermal_gov_power_allocator);
    
    static struct thermal_governor thermal_gov_bang_bang = {
        .name = "bang_bang",
    };
    THERMAL_GOVERNOR_DECLARE(thermal_gov_bang_bang);
    
    static struct thermal_governor thermal_gov_user_space = {
        .name = "user_space",
    };
    THERMAL_GOVERNOR_DECLARE(thermal_gov_user_space);
    
    static struct thermal_governor thermal_gov_step_wise = {
        .name = "step_wise",
    };
    THERMAL_GOVERNOR_DECLARE(thermal_gov_step_wise);
    
    static struct thermal_governor thermal_gov_fair_share = {
        .name = "fair_share",
    };
    THERMAL_GOVERNOR_DECLARE(thermal_gov_fair_share);
    
    
    int main()
    {
        struct thermal_governor **gov;
    
        for_each_governor_table(gov) {
            printf("%s\n", (*gov)->name);
        }
    
        return 0;
    }

    编译报错如下:

    attribute_section$ gcc attribute_section_test.c -o pp
    /tmp/cc2awnku.o: In function `main':
    attribute_section_test.c:(.text+0x2a): undefined reference to `__governor_thermal_table_end'
    collect2: error: ld returned 1 exit status

    看来用户空间默认的链接器脚本帮我们定义了指针数组 __governor_thermal_table,却没有定义 __governor_thermal_table_end。内核的连接器脚本中两个都会定义。

    通过命令”ld --verbose”获取默认链接脚本,然后拷贝"============================"中间的内容到 lds_my.lds 中,并在"__bss_start" 前面添加:

    __governor_thermal_table = .;
    __governor_thermal_table : { *(__governor_thermal_table) }
    __governor_thermal_table_end = .;

    然后,可以看到已经正常遍历所有的模块了:

    attribute_section$ gcc attribute_section_test.c -o pp -T lds_my.lds 
    attribute_section$ ./pp
    power_allocator
    bang_bang
    user_space
    step_wise
    fair_share

    2. 补充:模仿内核,创建 lds_my.lds.h 文件,并输入如下内容,然后在 lds_my.lds 中 #include <lds_my.lds.h>,实测不行,不能使用#include。

    SECTIONS {
        __governor_thermal_table : ALIGN(8) {
            __governor_thermal_table = .;
            __governor_thermal_table : { *(__governor_thermal_table) }
            __governor_thermal_table_end = .;
        }
    }

    三、使用函数指针使用__section__属性

    1. 对函数指针使用__section__属性

    (1) 同理,在链接脚本 lds_my_funcp.lds 中加入如下链接定义

    __my_init_call_start = .;
        .my.init : {*(.my.init)}
    __my_init_call_end = .;

    (2) 测试代码

    #include <stdio.h>
    
    typedef int (*init_call_type)(void *data);
    
    extern init_call_type __my_init_call_start;
    extern init_call_type __my_init_call_end;
    
    #define DECLARE_INIT_CALL(func) \
        static init_call_type init_call_##func __attribute__((used, section(".my.init"))) = func
    
    #define for_each_init_module(fn) \
        for (fn = &__my_init_call_start;    fn < &__my_init_call_end; fn++)
    
    
    //modules define
    int led_module_init(void *data)
    {
        printf("%s\n", __func__);
    }
    DECLARE_INIT_CALL(led_module_init);
    
    int video_module_init(void *data)
    {
        printf("%s\n", __func__);
    }
    DECLARE_INIT_CALL(video_module_init);
    
    int camera_module_init(void *data)
    {
        printf("%s\n", __func__);
    }
    DECLARE_INIT_CALL(camera_module_init);
    
    
    int main()
    {
        init_call_type *fn;
        
        for_each_init_module(fn) {
            (*fn)(NULL);
        }
        
        return 0;
    }
    
    /*
    attribute_section$ gcc attribute_section_funcp_test.c -o pp -T lds_my_funcp.lds 
    attribute_section$ ./pp
    led_module_init
    video_module_init
    camera_module_init
    */

    2. 总结

    (1) 链接后,自己定义的 __my_init_call_start 和 __my_init_call_end 只是两个地址常量,而且应该是没有类型的地址常量,可以将其当做指针或指针数组,将其赋值给一级指针或二级指针都不会报warning。这两个常量之间存的内容是函数指针的地址,指针指向的是函数,因此需要两次解引用才能调用到原来的函数,因此变量变量需要是一个二级指针。

    (2) 下面两种格式都行

    __attribute__((__section__("section_name")))
    __attribute__((section(".my.init")))
  • 相关阅读:
    在redhat 6.6上安装Docker
    Linux操作系统各版本ISO镜像下载(包括oracle linux edhatcentosu
    UML时序图(Sequence Diagram)学习笔记
    eureka 和zookeeper 区别 优势【转】
    HttpClient实现远程调用
    Java 1.8 Stream 用例测试
    ZooKeeper下载安装(Windows版本)
    Java1.8新特性
    mysql大数据量表索引与非索引对比
    druid监控mysql程序
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/15721408.html
Copyright © 2020-2023  润新知