rtthread中INIT_EXPORT与linux中__define_initcall对比解析

INIT_EXPORT的作用

宏定义INIT_EXPORT(fn, level) 对于RTT内核的初始化很重要,它指示编译器在编译的时候,将一系列初始化函数的起始地址值按照一定的顺序放在一个section中。在内核初始化阶 段,rt_components_board_init()、rt_components_init() 将按顺序从该section中以函数指针的形式取出这些函数的起始地址,来依次完成相应的初始化。由于内核某些部分的初始化需要依赖于其他某些部分的初始 化的完成,因此这个顺序排列常常非常重要。

下面将从INIT_EXPORT(fn, level) 宏定义的代码分析入手,依次分析名称为.rti_fn.的section的结构,最后分析内核初始化函数rt_components_board_init()、rt_components_init()是如何利用宏定义INIT_EXPORT(fn, level) 及其相关的衍生的7个宏宏定义,来实现内核某些部分的顺序初始化的。

1、分析INIT_EXPORT(fn, level) 宏定义

1) 这个宏的定义位于include\rtdef.h中:

#define INIT_EXPORT(fn, level)   RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn." level) = fn

其中 init_fn_t 是一个函数指针类型:

#define SECTION(x)   __attribute__((section(x)))
typedef int (*init_fn_t)(void);
而属性 SECTION(".rti_fn." level) 则表示把对象放在一个这个由括号中的名称所指代的section中。

所以这个宏定义的的含义是:

(1) 声明一个名称为__rt_init_##fn的函数指针(其中##表示替换连接);

(2) 将这个函数指针初始化为fn;

(3) 编译的时候需要把这个函数指针变量放置到名称为".rti_fn." level的section中(比如level="1",代表这个section的名称是".rti_fn.1")。

2) 举例:INIT_EXPORT(app_init, 6)上述宏调用的含义是:

声明一个函数指针__rt_init_app_init = app_init;且 这个指针变量__rt_init_app_init 需要放置到名称为".rti_fn.6"的section中( 其实质就是将 这个函数app_init的首地址放置到了这个section中)。

3) 这个宏一般并不直接使用,而是被定义成下述其他更简单的6个衍生宏这些衍生宏宏的定义也位于 inlclude\rtdef.h 中:

#define INIT_EXPORT(fn, level)                                                       \
            RT_USED const init_fn_t __rt_init_##fn SECTION(".rti_fn." level) = fn

/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn)           INIT_EXPORT(fn, "1")

/* pre/device/component/env/app init routines will be called in init_thread */
/* components pre-initialization (pure software initilization) */
#define INIT_PREV_EXPORT(fn)            INIT_EXPORT(fn, "2")
/* device initialization */
#define INIT_DEVICE_EXPORT(fn)          INIT_EXPORT(fn, "3")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn)       INIT_EXPORT(fn, "4")
/* environment initialization (mount disk, ...) */
#define INIT_ENV_EXPORT(fn)             INIT_EXPORT(fn, "5")
/* appliation initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn)             INIT_EXPORT(fn, "6")

 因此通过宏 INIT_APP_EXPORT() 来声明的函数指针,将放置到名称为".rti_fn.6"的section中,依次类推。

4) 举例:

INIT_DEVICE_EXPORT(uart_init),解释同上 1-2)。

 

2、 内核初始化函数rt_components_board_init()、 rt_components_init() 将从".rti_fn." level中,也就是这6个section中依次取出所有的函数指针,并调用这些函数指针所指向的函数,来完成内核的一些相关的初始化。这个函数的定义位于src\components.c中:

void rt_components_board_init(void)
{
    volatile const init_fn_t *fn_ptr;
    for (fn_ptr = &__rt_init_rti_board_start; fn_ptr < &__rt_init_rti_board_end; fn_ptr++)
    {
        (*fn_ptr)();
    }
}

void rt_components_init(void)
{
    volatile const init_fn_t *fn_ptr;
    for (fn_ptr = &__rt_init_rti_board_end; fn_ptr < &__rt_init_rti_end; fn_ptr ++)
    {
        (*fn_ptr)();
    }
}

 

__define_initcall 作用

宏定义_define_initcall(level,fn) 对于内核的初始化很重要,它指示编译器在编译的时候,将一系列初始化函数的起始地址值按照一定的顺序放在一个section中。在内核初始化阶 段,do_initcalls() 将按顺序从该section中以函数指针的形式取出这些函数的起始地址,来依次完成相应的初始化。由于内核某些部分的初始化需要依赖于其他某些部分的初始 化的完成,因此这个顺序排列常常非常重要。

下面将从__define_initcall(level,fn) 宏定义的代码分析入手,依次分析名称为initcall.init的section的结构,最后分析内核初始化函数 do_initcalls()是如何利用宏定义__define_initcall(level,fn)及其相关的衍生的7个宏宏定义,来实现内核某些部分的顺序初始化的。

在init.h中我们看到

#define module_init(x) __initcall(x);
#define __initcall(fn) __define_initcall(fn) 
#define __define_initcall(level,fn) \ static initcall_t __initcall_##fn __attribute_used__ __attribute__((__section__(".initcall" level ".init"))) = fn

 

1、分析 __define_initcall(level,fn) 宏定义

1) 这个宏的定义位于inlclude\linux\init.h中:

#define __define_initcall(level,fn) \ static initcall_t __initcall_##fn __attribute__((__section__(".initcall" level ".init"))) = fn

其中 initcall_t 是一个函数指针类型:

typedef int (*initcall_t)(void); 
而属性 __attribute__((__section__())) 则表示把对象放在一个这个由括号中的名称所指代的section中。

所以这个宏定义的的含义是:

(1) 声明一个名称为__initcall_##fn的函数指针(其中##表示替换连接);

(2) 将这个函数指针初始化为fn;

(3) 编译的时候需要把这个函数指针变量放置到名称为 ".initcall" level ".init"的section中(比如level="1",代表这个section的名称是 ".initcall1.init")。

2) 举例:__define_initcall(6, pci_init)上述宏调用的含义是:

声明一个函数指针__initcall_pic_init = pci_init;且 这个指针变量__initcall_pic_init 需要放置到名称为 .initcall6.init的section中( 其实质就是将 这个函数pic_init的首地址放置到了这个section中)。

3) 这个宏一般并不直接使用,而是被定义成下述其他更简单的7个衍生宏这些衍生宏宏的定义也位于 inlclude\linux\Init.h 中:

#define core_initcall(fn) __define_initcall("1",fn) 
#define postcore_initcall(fn) __define_initcall("2",fn) 
#define arch_initcall(fn) __define_initcall("3",fn) 
#define subsys_initcall(fn) __define_initcall("4",fn) 
#define fs_initcall(fn) __define_initcall("5",fn) 
#define device_initcall(fn) __define_initcall("6",fn) 
#define late_initcall(fn) __define_initcall("7",fn) 

#define module_init(x)     __initcall(x)
#define __initcall(fn)     device_initcall(fn)

因此通过宏 core_initcall() 来声明的函数指针,将放置到名称为.initcall1.init的section中,而通过宏 postcore_initcall() 来声明的函数指针, 将放置到名称为.initcall2.init的section中,依次类推。

4) 举例:

device_initcall(pci_init) 解释同上 1-2)。

 

2、与初始化调用有关section--initcall.init被分成了7个子section

1) 它们依次是.initcall1.init、.initcall2.init、...、.initcall7.init 
2) 按照先后顺序依次排列 
3) 它们的定义在文件vmlinux.lds.S中 例如 对于i386+,在i386\kernel\vmlinux.lds.S中有: __initcall_start = .; 
.initcall.init : { 
    *(.initcall1.init) 
    *(.initcall2.init) 
    *(.initcall3.init) 
    *(.initcall4.init) 
    *(.initcall5.init) 
    *(.initcall6.init) 
    *(.initcall7.init) 
}
 __initcall_end = .; 
而在makefile 中有 LDFLAGS_vmlinux += -T arch/$(ARCH)/kernel/vmlinux.lds.s 
4) 在这7个section总的开始位置被标识为__initcall_start,而在结尾被标识为__initcall_end。

3、 内核初始化函数do_basic_setup(): do_initcalls() 将从.initcall.init中,也就是这7个section中依次取出所有的函数指针,并调用这些函数指针所指向的函数,来完成内核的一些相关的初始化。

这个函数的定义位于init\main.c中:

//很简单 
static void __init do_initcalls(void) { 
    ...... 
    for (call = __initcall_start; call < __initcall_end; call++) { 
        ...... 
        result = (*call)(); 
        ...... 
        } 
    ...... 
}

 

 

 

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面我将给你提供一个使用 rt_mq_t 的例程。这个例程演示了如何创建一个消息队列、向消息队列发送消息、从消息队列接收消息,并且在接收到消息后将消息打印出来。请参考代码如下: ```c #include <rtthread.h> #define THREAD_PRIORITY 25 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 5 static rt_mq_t mq; // 定义一个消息队列 static char mq_pool[1024]; // 定义消息队列使用的内存池 static void sender_entry(void *parameter) { rt_err_t result; rt_uint8_t data = 0; while (1) { /* 发送消息 */ result = rt_mq_send(mq, &data, sizeof(data)); if (result == RT_EOK) { rt_kprintf("send data:%d\n", data); data++; } rt_thread_mdelay(1000); // 延时1秒 } } static void receiver_entry(void *parameter) { rt_err_t result; rt_uint8_t data; while (1) { /* 接收消息 */ result = rt_mq_recv(mq, &data, sizeof(data), RT_WAITING_FOREVER); if (result == RT_EOK) { rt_kprintf("received data:%d\n", data); } } } int rt_application_init(void) { rt_thread_t sender_tid, receiver_tid; /* 创建消息队列 */ mq = rt_mq_create("mq", sizeof(rt_uint8_t), 32, &mq_pool); if (mq == RT_NULL) { return -1; } /* 创建发送线程 */ sender_tid = rt_thread_create("sender", sender_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (sender_tid != RT_NULL) { rt_thread_startup(sender_tid); } /* 创建接收线程 */ receiver_tid = rt_thread_create("receiver", receiver_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (receiver_tid != RT_NULL) { rt_thread_startup(receiver_tid); } return 0; } ``` 在这个例程,我们首先定义了一个消息队列 `mq`,并且定义了一个大小为1024的内存池 `mq_pool` 用于存储消息队列的消息。然后我们创建了两个线程:`sender` 和 `receiver`,它们分别用于向消息队列发送消息和从消息队列接收消息。在 `sender_entry` 函数,我们使用 `rt_mq_send` 函数发送一个字节大小的数据,并且打印发送的数据;在 `receiver_entry` 函数,我们使用 `rt_mq_recv` 函数接收一个字节大小的数据,并且打印接收到的数据。在 `rt_application_init` 函数,我们首先创建了消息队列,然后创建了 `sender` 和 `receiver` 两个线程,并且启动它们。 希望这个例程能够帮到你。如果还有其他问题,请继续提出。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值