rt-thread的moudle源码分析

1 前言

大家都知道在windows操作系统中,可以运行一个个程序,这些程序大都是exe可执行文件,它是一个编译好的二进制文件,在windows下,在一个程序中还可以使用dll动态链接库dll文件,它同样也是编译好的二进制文件。相对的在Linux下也有可执行文件可动态链库.so文件,那么在rt-thread中是否也存在一种这样的文件,可以在系统的运行过程中,由用户启动并执行它,答案是有。在rt-thead中这样的可执行文件或是动态链表库文件,这种文件是elf格式的文件。本文将从内核的角度来看rt-thead是如何将这样一个elf格式的文件装载到内存并执行它。

2 模块控制块

在/include/rtdef.h文件中有模块控制块的定义,如下:

struct rt_module
{
    struct rt_object             parent;              //内核对象控制块

    rt_uint8_t                  *module_space;        //模块空间,保存装载elf文件后的内存空间

    void                        *module_entry;          //此模块对应的线程入口
    rt_thread_t                  module_thread;       //此模块对应的线程
    rt_uint32_t                  stack_size;            //此模块对应的线程栈
    rt_uint32_t                  thread_priority;    //此模块对应的线程优先级

#ifdef RT_USING_SLAB
    /* module memory allocator */
    void                        *mem_list;              //此模块对应的内存堆链表
    void                        *page_array;            //内存页
    rt_uint32_t                  page_cnt;              //内存页数量
#endif

    rt_uint32_t                  nsym;                  //此模块包含的符号个数
    struct rt_module_symtab     *symtab;                //此模块包含的符号表
    rt_uint32_t                  nref;                  //此模块被关联的次数
    
    /* object in this module, module object is the last basic object type */
    struct rt_object_information module_object[RT_Object_Class_Unknown];//此模块包含的内核对象容器
};
typedef struct rt_module *rt_module_t;

一般来说,rt-thread在装一个elf格式文件时,会自动为它创建一个线程来执行它,这个线程就是指上面的模块对应的线程。

下现来看看 rt-thread是如何装一个so文件(elf格式),并执行它的。

3 装载so文件

在/components/libdl/dlfcn.h头文件中存在dlopen函数声明:

void *dlopen (const char *filename, int flag);

就是这个dlopen函数负责打开so文件并装载进内存(保存到rt_moudule结构体中),那么下面来分析一下这个dlopen函数:

void* dlopen(const char *filename, int flags)
{
	rt_module_t module;
	char *fullpath;
	const char*def_path = MODULE_ROOT_DIR;

	/* check parameters */
	RT_ASSERT(filename != RT_NULL);
        //处理相对路径
	if (filename[0] != '/') /* it's a absolute path, use it directly */
	{
		fullpath = rt_malloc(strlen(def_path) + strlen(filename) + 2);

		/* join path and file name */
		rt_snprintf(fullpath, strlen(def_path) + strlen(filename) + 2, 
			"%s/%s", def_path, filename);
	}
	
	/* find in module list */
	module = rt_module_find(fullpath);//在内核对象容器中尝试查找此模块
	
	if(module != RT_NULL) module->nref++;//如果找到,那么将它的被关联次数加1
	else module = rt_module_open(fullpath);//打开模块

	rt_free(fullpath);
	return (void*)module;
}
上面代码只是处理了一下路径问题,最终还是调用rt_module_open来处理,从源码中可知,其实形参flags是没有用到的.rt_module_open函数如下:
rt_module_t rt_module_open(const char *path)
{
    int fd, length;
    struct rt_module *module;
    struct stat s;
    char *buffer, *offset_ptr;
    char *name;

    RT_DEBUG_NOT_IN_INTERRUPT;

    /* check parameters */
    RT_ASSERT(path != RT_NULL);
   //读取输入的文件信息
    if (stat(path, &s) !=0)
    {
        rt_kprintf("Module: access %s failed\n", path);

        return RT_NULL;
    }
    //以下代码是将输入的文件打开并读入内存缓冲区
    buffer = (char *)rt_malloc(s.st_size);
    if (buffer == RT_NULL)
    {
        rt_kprintf("Module: out of memory\n");

        return RT_NULL;
    }

    offset_ptr = buffer;
    fd = open(path, O_RDONLY, 0);
    if (fd < 0)
    {
        rt_kprintf("Module: open %s failed\n", path);
        rt_free(buffer);

        return RT_NULL;
    }

    do
    {
        length = read(fd, offset_ptr, 4096);
        if (length > 0)
        {
            offset_ptr += length;
        }
    }while (length > 0);

    /* close fd */
    close(fd);

    if ((rt_uint32_t)offset_ptr - (rt_uint32_t)buffer != s.st_size)
    {
        rt_kprintf("Module: read file failed\n");
        rt_free(buffer);

        return RT_NULL;
    }
    //获取文件名
    name   = _module_name(path);
   //将保存到内存中的so文件内容解释并执行
    module = rt_module_load(name, (void *)buffer);
    rt_free(buffer);
    rt_free(name);

    return module;
}

从以上代码可以,到这里还只是将so文件读入内存,真正解析so文件(elf格式)并执行的是在rt_module_load函数,其定义如下代码:

rt_module_t rt_module_load(const char *name, void *module_ptr)
{
    rt_module_t module;

    RT_DEBUG_NOT_IN_INTERRUPT;

    RT_DEBUG_LOG(RT_DEBUG_MODULE, ("rt_module_load: %s ,", name));

    /* check ELF header */
    if (rt_memcmp(elf_module->e_ident, RTMMAG, SELFMAG) != 0 &&//判断elf格式文件的头部信息的文件标志是否为"\177RTM"或"\177ELF",否则将返回空,也就是说,此函数只能处理这两种文件标识的elf文件
        rt_memcmp(elf_module->e_ident, ELFMAG, SELFMAG) != 0)
    {
        rt_kprintf("Module: magic error\n");

        return RT_NULL;
    }

    /* check ELF class */
    if (elf_module->e_ident[EI_CLASS] != ELFCLASS32)//检查此elf格式文件是否为32位机器码
    {
        rt_kprintf("Module: ELF class error\n");

        return RT_NULL;
    }
    //判断elf文件的类型,这里处理可重定位文件和共享目标文件
    if (elf_module->e_type == ET_REL)
    {
        module = _load_relocated_object(name, module_ptr);//装载可重定位文件
    }
    else if (elf_module->e_type == ET_DYN)
    {
        module = _load_shared_object(name, module_ptr);//装载共享目标文件
    }
    else
    {
        rt_kprintf("Module: unsupported elf type\n");

        return RT_NULL;
    }

    if (module == RT_NULL)
        return RT_NULL;

    /* init module object container */
    rt_module_init_object_container(module);//在模块内部的内核对象容器

    /* increase module reference count */
    module->nref ++;//模块的被索引次数加1
   //如果模块的入口地址存在
    if (elf_module->e_entry != 0)
    {
        rt_uint32_t *stack_size;
        rt_uint8_t  *priority;

#ifdef RT_USING_SLAB
        /* init module memory allocator */
        module->mem_list = RT_NULL;

        /* create page array */
        module->page_array = 
            (void *)rt_malloc(PAGE_COUNT_MAX * sizeof(struct rt_page_info));//为模块创建内存页
        module->page_cnt = 0;
#endif

        /* get the main thread stack size */
        module->stack_size = 2048;//模块的线程栈设为2K
        module->thread_priority = RT_THREAD_PRIORITY_MAX - 2;//模块的线程优先级设为最大优先级-2

        /* create module thread *///创建线程,将模块入口地址设置为此线程的入口函数
        module->module_thread =
            rt_thread_create(name,
                             (void(*)(void *))module->module_entry,
                             RT_NULL,
                             module->stack_size,
                             module->thread_priority,
                             10);

        RT_DEBUG_LOG(RT_DEBUG_MODULE, ("thread entry 0x%x\n",
                                       module->module_entry));

        /* set module id */
        module->module_thread->module_id = (void *)module;//设置模块线程的模块ID为本模块
        module->parent.flag = RT_MODULE_FLAG_WITHENTRY;

        /* startup module thread */
        rt_thread_startup(module->module_thread);//启动模块线程
    }
    else//如果模块无入口地址
    {
        /* without entry point */
        module->parent.flag |= RT_MODULE_FLAG_WITHOUTENTRY;//设置标志为无入口地址
    }

#ifdef RT_USING_HOOK
    if (rt_module_load_hook != RT_NULL)
    {
        rt_module_load_hook(module);//使用勾子函数
    }
#endif

    return module;
}

elf文件是一种标准的二进制文件格式,它有好几种类型,除了上面代码是提供到可重定位文件(ET_REL)和共享目标文件(ET_DYN)之外,还存在可执行文件(ET_EXEC),当然还存在一些其它的类型,这里就不具体说明.当前rt-thread的最新版本是V1.1.0,从源码可知,当前版本只支持可重定位文件和共享目标文件这两种文件.

上述代码大致流程如下,首先从传入的elf文件内存获取elf头部信息,判断一些信息是否满足条件,之后判断elf文件类型,是否为可重定位文件或共享目标文件,如果是,则分别用不用的代码解析它,并将它装进rt_module_t对应的内存结构体中,然后创建一个线程并启动它.就这样,一个so的执行过程就完了,它是通过为一个模块创建一个线程来执行的.

再次回到模块控制块结构定义中,这里特别说明一个,模块控制块成员module_space保存了elf文件中表示程序的内容,而module_entry指向modul_space中的入口.

有关elf文件格式的具体说明请参考:http://blog.csdn.net/flydream0/article/details/8719036

或参考:http://en.wikipedia.org/wiki/Elf_format

这里就不做具体说明了,另_load_relocated_object函数(处理可重定位文件)和函数_load_shared_object(处理共享目标文件),这两个函数由于与elf文件格式密切相关,不建议在没有深刻理解elf文件格式之前去看这两个函数的实现,这里暂且将理解为分别解析可重定位文件和共享目标文件即可.

因此这里先建议了解elf文件格式再进一步去看这两个函数的实现,后续我将再写博文来讨论,敬请关注!

OK,先这样!


  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
好的,我会尽力回答你的问题。首先,RT-Thread是一个实时操作系统,它提供了一些基本的实时操作系统功能,例如线程、信号量、消息队列、定时器等。其中,定时器是一种非常重要的实时操作系统功能,它可以用来在指定的时间间隔内执行一些操作。 在RT-Thread中,定时器的实现是基于软件定时器,即在内核中维护一个定时器列表,每个定时器都有一个超时时间。当定时器到期时,内核会调用定时器回调函数来执行相应的操作。 下面是RT-Thread定时器的源码分析: 1. 定时器结构体 在RT-Thread中,定时器的结构体定义如下: ```c struct rt_timer { char name[RT_NAME_MAX]; /* 定时器名称 */ rt_list_t list; /* 定时器列表 */ rt_tick_t timeout_tick; /* 定时器超时时间 */ rt_tick_t init_tick; /* 定时器初始时间 */ rt_uint8_t flag; /* 定时器标志 */ rt_uint8_t state; /* 定时器状态 */ void (*timeout_func)(void* parameter); /* 定时器回调函数 */ void* parameter; /* 回调函数参数 */ }; ``` 可以看到,定时器结构体包含了定时器的名称、超时时间、标志、状态、回调函数等信息。 2. 定时器创建 在RT-Thread中,定时器的创建函数是rt_timer_create(),它的函数原型如下: ```c rt_err_t rt_timer_create(rt_timer_t *timer, const char *name, void (*timeout_func)(void* parameter), void* parameter, rt_tick_t time, rt_uint8_t flag); ``` 其中,timer表示定时器指针,name表示定时器名称,timeout_func表示定时器回调函数,parameter表示回调函数参数,time表示定时器超时时间,flag表示定时器标志。 rt_timer_create()函数会在内核中创建一个定时器,并将定时器添加到定时器列表中。如果创建成功,函数返回RT_EOK,否则返回错误码。 3. 定时器启动 在RT-Thread中,定时器的启动函数是rt_timer_start(),它的函数原型如下: ```c rt_err_t rt_timer_start(rt_timer_t timer); ``` rt_timer_start()函数会启动指定的定时器,并将其状态设置为RT_TIMER_FLAG_ACTIVATED。如果启动成功,函数返回RT_EOK,否则返回错误码。 4. 定时器停止 在RT-Thread中,定时器的停止函数是rt_timer_stop(),它的函数原型如下: ```c rt_err_t rt_timer_stop(rt_timer_t timer); ``` rt_timer_stop()函数会停止指定的定时器,并将其状态设置为RT_TIMER_FLAG_DEACTIVATED。如果停止成功,函数返回RT_EOK,否则返回错误码。 5. 定时器删除 在RT-Thread中,定时器的删除函数是rt_timer_delete(),它的函数原型如下: ```c rt_err_t rt_timer_delete(rt_timer_t timer); ``` rt_timer_delete()函数会删除指定的定时器,并释放相应的资源。如果删除成功,函数返回RT_EOK,否则返回错误码。 以上就是RT-Thread定时器的源码分析,希望能对你有所帮助。如果你有其他问题,可以继续问我。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值