attribute section 屬性


一、__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")))

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM