ffxz 发表于 2013-6-22 22:47:52

RT-Thread 1.2.0 beta新功能预览 [组件初始化]

这个问题源于老的代码:
static void rt_init_thread_entry(void *parameter)
{
#ifdef RT_USING_PM
    efm32gg_pm_hw_init();
    rt_hw_serial_pm_init();
#endif /* RT_USING_PM */

    rt_hw_userled_init();

#ifdef RT_USING_MTD_NAND
    rt_hw_mtd_nand_init();
#endif /* RT_USING_MTD_NAND */

#ifdef RT_USING_LOGTRACE
    /* initialize log trace component */
    log_trace_init();
    log_trace_set_device(RT_CONSOLE_DEVICE_NAME);
#ifdef LOG_TRACE_NAND
    /* set exception handler */
    rt_hw_exception_install(exception_handle);
#endif

    log_trace(LOG_TRACE_INFO"log trace initialized!\n");
#endif /* RT_USING_LOGTRACE */

    /* File System Initialization */
#ifdef RT_USING_DFS
    {
      /* initialize the device file system */
      dfs_init();

#ifdef RT_USING_DFS_ELMFAT
      /* initialize the elm chan FatFs file system*/
      elm_init();
#endif /* RT_USING_DFS_ELMFAT */

#ifdef RT_USING_NFTL
      nftl_attach("nand0");
      if (dfs_mount("nand0", "/", "elm", 0, 0) == 0)
      {
            rt_kprintf("Mount FatFs file system to root, Done!\n");
      }
      else
      {
            rt_kprintf("Mount FatFs file system failed.\n");
      }
#endif

#ifdef RT_USING_RAMFS
      dfs_ramfs_init();
      {
            struct dfs_ramfs* ramfs;
            ramfs = dfs_ramfs_create((rt_uint8_t*)EFM32_RAMFS_BEGIN, EFM32_RAMFS_SIZE);
            if (ramfs != RT_NULL)
            {
                if (dfs_mount(RT_NULL, "/ramfs", "ram", 0, ramfs) == 0)
                {
                  rt_kprintf("Mount RAMDisk done!\n");
                }
                else
                {
                  rt_kprintf("Mount RAMDisk failed.\n");
                }
            }
      }
#endif
    }
#endif /* RT_USING_DFS */

    /* Initialize I2C bus */
#ifdef RT_USING_I2C
    rt_i2c_core_init();
    rt_hw_iicbus_init();
#endif /* RT_USING_I2C */

    /* Initialize RTC */
#ifdef RT_USING_RTC
#ifdef RT_USING_ALARM
    rt_alarm_system_init();
#endif

#ifdef SH3H_USING_RX8025
    rt_hw_rx8025_init(EFM32GG_IICBUS1_NAME);
#endif

#ifdef EFM32GG_USING_CHIP_RTC
    rt_hw_rtc_init();
#endif

#ifdef SH3H_USING_RX8564
    rt_hw_rx8564_init(EFM32GG_IICBUS0_NAME);
#endif
#endif /* RT_USING_RTC */

    /* Initialize Watchdog */
#ifdef RT_USING_WDT
#ifdef EFM32GG_USING_CHIP_WDT
    rt_hw_wdt_init();
#endif

#ifdef SH3H_USING_X4043_WDT
    rt_hw_x4043_init(EFM32GG_IICBUS1_NAME);
#endif

#ifdef SH3H_USING_STM6822_WDT
    rt_hw_stm6822_init();
#endif

    wdtmgr_init(SH3H_STM6822_WDT_NAME);
#endif /* RT_USING_WDT */

#ifdef RT_USING_LWIP
    /* initialize lwip system */
    eth_system_device_init();
    lwip_system_init();
    rt_kprintf("TCP/IP initialized!\n");

#ifdef RT_LWIP_PPP
    /* initialize ppp protocol */
    pppInit();

    modem_system_init();
    modem_mg323_init();
    modem_mc323_init();
    modem_gl868_init();
#endif /* RT_LWIP_PPP */

#endif /* RT_USING_LWIP */

#ifdef RT_USING_USB_DEVICE
    rt_hw_usbd_init();

    rt_usb_device_init("usbd");

#if defined(RT_USING_LWIP) && defined(RT_USB_DEVICE_RNDIS)
    {
      extern void dhcpd_start(void);
      extern void ssdp_start(void);
      extern void telnet_srv(void);

      dhcpd_start();
      ssdp_start();
      telnet_srv();
    }

#ifdefRT_USING_WEBNET
    {
      extern void httpd_init(void);

      httpd_init();
    }
#endif /* RT_USING_WEBNET */
#endif // RT_USING_LWIP && RT_USB_DEVICE_RNDIS

#endif /* RT_USING_USB_DEVICE */

#ifdef RT_USING_PM
    {
      rt_pm_release(PM_RUNNING_MODE);
      rt_pm_release(PM_SLEEP_MODE);
      rt_pm_request(PM_TIMER_MODE);

      rt_hw_plug_init();
    }
#endif /* RT_USING_PM */

    /* init finsh */
#ifdef RT_USING_FINSH
    finsh_system_init();
    finsh_set_device(RT_CONSOLE_DEVICE_NAME);
#endif /* RT_USING_FINSH */
}
其中的宏条件、宏定义够多够乱!为了实现不同组件的独立性,不得不加无数的宏条件,“貌似”没什么好办法,但其实这些初始化是很规律性的,或者最简单的一点:编译进代码中的,需要初始化,不编译的自然不需要初始化。

原来有一个components.c,期望它可以进行各个组件的初始化,期望它能够做到:
#include <components.h>

static void rt_init_thread_entry(void *parameter)
{
    rt_components_init();
}
然后用户的代码自然也就清晰了,再乱也只是乱在rt_components_init()的实现里:
/**
* RT-Thread Components Initialization
*/
void rt_components_init(void)
{
#ifdef RT_USING_MODULE
    rt_system_module_init();
#endif

#ifdef RT_USING_FINSH
    /* initialize finsh */
    finsh_system_init();
    finsh_set_device(RT_CONSOLE_DEVICE_NAME);
#endif

#ifdef RT_USING_LWIP
    /* initialize lwip stack */
    /* register ethernetif device */
    eth_system_device_init();

    /* initialize lwip system */
    lwip_system_init();
#endif

#ifdef RT_USING_DFS
    /* initialize the device file system */
    dfs_init();

    #ifdef RT_USING_DFS_ELMFAT
    /* initialize the elm chan FatFS file system*/
    elm_init();
    #endif

    #if defined(RT_USING_DFS_NFS) && defined(RT_USING_LWIP)
    /* initialize NFSv3 client file system */
    nfs_init();
    #endif

    #ifdef RT_USING_DFS_YAFFS2
    dfs_yaffs2_init();
    #endif

    #ifdef RT_USING_DFS_UFFS
    dfs_uffs_init();
    #endif

    #ifdef RT_USING_DFS_JFFS2
    dfs_jffs2_init();
    #endif

    #ifdef RT_USING_DFS_ROMFS
    dfs_romfs_init();
    #endif

    #ifdef RT_USING_DFS_DEVFS
    devfs_init();
    #endif
#endif /* end of RT_USING_DFS */

#ifdef RT_USING_NEWLIB
    libc_system_init(RT_CONSOLE_DEVICE_NAME);
#else
    /* the pthread system initialization will be initiallized in libc */
    #ifdef RT_USING_PTHREADS
    pthread_system_init();
    #endif
#endif

#ifdef RT_USING_RTGUI
    rtgui_system_server_init();
#endif

#ifdef RT_USING_USB_HOST
    rt_usb_host_init();
#endif

    return;
}
一样的宏条件,一样的糟糕的代码。特别是,一个巨大的问题:components.c属于rt-thread里的代码,如果每个用户都需要自己再去修改里面的代码,那么和把初始化代码放在application.c中没有本质的区别。

components.c的缺陷在于,依然不能摆脱宏条件的实现方式,另一个,用户不能够很方便的加入到初始化的序列中。

不过finsh shell中灵活地把一个函数输出到命令行中,为我们提供一丝灵感。既然能够让用户的程序输出到命令行中,那么也自然能够让用户初始化函数输出到 组件初始化 序列中。

finsh shell的实现方式是把函数入口放到了一个独立的section中,然后再通过编译+链接过程中,获取这个section首地址、尾地址的形式,从而实现系统中所有输出符号的序列。同时为了能够在shell中按照函数名称来查找,在这个独立的section中实际填写的是:
/* system call table */
struct finsh_syscall
{
        const char*                name;                /* the name of system call */
#if defined(FINSH_USING_DESCRIPTION) && defined(FINSH_USING_SYMTAB)
        const char*                desc;                /* description of system call */
#endif
        syscall_func func;                /* the function address of system call */
};
这样一个结构体。记得论坛上好像有人提及到FINSH_USING_DESCRIPTION和FINSH_USING_SYMTAB的意义来着,其实就是决定shell中列出的命令函数列表,是否有帮助、描述信息。

name给出了函数的名称(所以也能够使用FINSH_FUNCTION_EXPORT_ALIAS宏把一个函数名更改成一个别名),shell中就是使用这个名称来查找对应的func函数指针。section FSymTab存放的则是这一个个struct finsh_syscall结构体。

好像有些扯远了,回过头来说组件初始化。

类似的这样的方式,Linux也提供了一些借鉴,把一个函数的地址(注意是函数地址,而不是函数本身)输出到一个独立的section中,同时按照一定顺序进行排列,例如:
.rti_fn.0
.rti_fn.1
.rti_fn.2
...
.rti_fn.7
这样几个section(这样几个不同的section也给出了排列的顺序)。同时把.rti_fn.0和.rti_fn.7保留给系统使用,分别定义出两个桩放置在这两个点上。也可以按照RT-Thread的形式定义简化的宏:
typedef int (*init_fn_t)(void);
#define INIT_EXPORT(fn, level)        \
        const init_fn_t __rt_init_##fn SECTION(".rti_fn."level) = fn

#define INIT_BOARD_EXPORT(fn)                INIT_EXPORT(fn, "1")
#define INIT_CPU_EXPORT(fn)                        INIT_EXPORT(fn, "2")
#define INIT_DEVICE_EXPORT(fn)                INIT_EXPORT(fn, "3")
#define INIT_COMPONENT_EXPORT(fn)        INIT_EXPORT(fn, "4")
#define INIT_FS_EXPORT(fn)                        INIT_EXPORT(fn, "5")
#define INIT_APP_EXPORT(fn)                        INIT_EXPORT(fn, "6")
INIT_EXPORT宏用于输出一个函数到初始化序列中,相应的可以定义一些更简化的宏。

这样两个桩可以定义成:
static int rti_start(void)
{
        return 0;
}
INIT_EXPORT(rti_start, "0");

static int rti_end(void)
{
        return 0;
}
INIT_EXPORT(rti_end,"7");
根据这两个桩的位置,简化的rt_components_init()函数就可以变成:
void rt_components_init(void)
{
        const init_fn_t* fn_ptr;

        for (fn_ptr = &__rt_init_rti_start; fn_ptr < &__rt_init_rti_end; )
        {
                (*fn_ptr)();
                fn_ptr ++;
        }
}
世界清静了!

electrlife 发表于 2013-6-22 23:07:25

本帖最后由 electrlife 于 2013-6-22 23:09 编辑

呵呵,这利用编译器段一般按字母排序的特性。uboot等也是这样处理的。

McuPlayer 发表于 2013-6-22 23:33:18

本帖最后由 McuPlayer 于 2013-6-22 23:34 编辑

支持一把RTT
去年一个PRJ打算用,后来因为计划变动项目被无限期Delay了
只做了驱动模块,基于RTT的多任务和事件,用DMA实现低速外设模块的异步读写操作,既不占用CPU时间又可以使得APP简洁

geniuscode 发表于 2013-6-22 23:43:54

本帖最后由 geniuscode 于 2013-6-22 23:45 编辑

{:lol:} 巧妙

ffxz 发表于 2013-6-23 08:08:06

这段代码已经上库了,以后的项目可以用这个初始化方式,不用再写一堆的大段初始化代码了。

后面再加log trace、crc模块,这些也是很有用的模块,特别是log trace

amwox 发表于 2013-6-23 17:32:35

惯性思维,所以用宏理解简单{:sweat:},放开了,就当是跳出井口的青蛙...

amwox 发表于 2013-6-24 18:55:33

下班了,做了个测试工程编译试试
工程编译后,从map文件找到相关部分内容:
InitFuncSym$$Base                        0x00000e18   Number         0init_1.o(InitFuncSym)
    __rt_init_init_1                         0x00000e18   Data         4init_1.o(InitFuncSym)
    __rt_init_init_2                         0x00000e1c   Data         4init_2.o(InitFuncSym)
    __rt_init_init_3                         0x00000e20   Data         4init_3.o(InitFuncSym)
    __rt_init_init_4                         0x00000e24   Data         4init_4.o(InitFuncSym)
    __rt_init_init_5                         0x00000e28   Data         4init_5.o(InitFuncSym)
    __rt_init_init_6                         0x00000e2c   Data         4init_6.o(InitFuncSym)
    InitFuncSym$$Limit                     0x00000e30   Number         0init_6.o(InitFuncSym)
尽管数据存放在不同的文件,但从这里这可以看到是空间连续分配的
楼主设定的桩
static int rti_start(void)
{
        return 0;
}
INIT_EXPORT(rti_start, "0");

static int rti_end(void)
{
        return 0;
}
INIT_EXPORT(rti_end,"7");
可由InitFuncSym$$Base和InitFuncSym$$Limit 代替
最后,初始化过程可以写成
      extern int InitFuncSym$$Base;
        extern int InitFuncSym$$Limit;
        init_fn_t* fn;
        for (fn = (init_fn_t *)&InitFuncSym$$Base; fn < (init_fn_t *)&InitFuncSym$$Limit; fn ++ ) {
                (*fn)();
        }

aozima 发表于 2013-6-24 20:04:13

amwox 发表于 2013-6-24 18:55 static/image/common/back.gif
下班了,做了个测试工程编译试试
工程编译后,从map文件找到相关部分内容:
InitFuncSym$$Base                ...

这个只能适用于MDK吧。

amwox 发表于 2013-6-25 09:10:20

增加了个IAR工程
参考RTT下的IAR工程,在ICF文件增加一行
keep { section InitFuncSym };
这行关系到以下信息是否生成.
在Debug/List下的map中有这样的描述
.text            ro code0x00000658   0x16xprout.o
InitFuncSym               0x00000670   0x14<Block>
    InitFuncSym      const    0x00000670    0x4init_1.o
    InitFuncSym      const    0x00000674    0x4init_2.o
    InitFuncSym      const    0x00000678    0x4init_4.o
    InitFuncSym      const    0x0000067c    0x4init_5.o
    InitFuncSym      const    0x00000680    0x4init_6.o
.rodata            const    0x00000684   0x10init_1.o
......
InitFuncSym$$Base   0x00000670         DataGb- Linker created -
InitFuncSym$$Limit    0x00000684         DataGb- Linker created -
IAR不太熟,模拟仿真一下


没有GCC,请有GCC的同学帮忙测试一下

ffxz 发表于 2013-6-25 10:29:30

IAR也支持$$?印象中,IAR支持的很不友好的

为了避免各种编译器的情况,所以在这里是用函数桩的形式,同时也避免如果系统中没定义相应的section,导致这个section是空的情况。

jiangkehong 发表于 2013-6-27 13:24:11

请问ffxz这次文档方面更新的多不多,现在git上的好像比之前的没多多少内容。

haigerl 发表于 2013-6-27 17:21:35

1. 这样做,初始化函数是不能带参数的,这个函数libc_system_init(RT_CONSOLE_DEVICE_NAME)是否有问题。
2. 能不能定义一个const的初始化函数指针的数组,如果没有编译的,其函数指针设为空指针,循环执行时不执行,这样是不是也可以,用户的初始化程序可以添加在后面。

eye 发表于 2013-6-28 01:24:48

还可以把系统任务和系统资源用这种形式来创建,
开发者只需定义需要的资源就好了, 不用去关心如何创建的.
RT_TASK_DEFINE("this task", thisTask, stackSize, prio, ....)
RT_MUTEX_DEFINE("this mutex", thisMutex, .....)
这样各个模块的应用工程师都可以很独立的开发,合并工作就是把代码放到一起编译就OK了,类似于依赖注入一样。

superyongzhe 发表于 2013-6-30 16:46:21

linux内核其实也是这么干的。

tianqing324 发表于 2014-3-7 15:23:08

pppInit();

    modem_system_init();
    modem_mg323_init();
    modem_mc323_init();
    modem_gl868_init();
这些函数的实体定义在哪里呢?

aozima 发表于 2014-3-8 14:45:20

tianqing324 发表于 2014-3-7 15:23
pppInit();

    modem_system_init();


除了pppInit(),其它都是具体项目的代码。

tianqing324 发表于 2014-3-8 18:10:23

aozima 发表于 2014-3-8 14:45
除了pppInit(),其它都是具体项目的代码。

没有源码是吧,最近在搞gprs ppp
页: [1]
查看完整版本: RT-Thread 1.2.0 beta新功能预览 [组件初始化]