1 前言
大家都知道在windows操作系统中,可以运行一个个程序,这些程序大都是exe可执行文件,它是一个编译好的二进制文件,在windows下,在一个程序中还可以使用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://wenku.baidu.com/view/3ed18ae9856a561252d36ff3.html
或参考:http://en.wikipedia.org/wiki/Elf_format
这里就不做具体说明了,另_load_relocated_object函数(处理可重定位文件)和函数_load_shared_object(处理共享目标文件),这两个函数由于与elf文件格式密切相关,不建议在没有深刻理解elf文件格式之前去看这两个函数的实现,这里暂且将理解为分别解析可重定位文件和共享目标文件即可.
因此这里先建议了解elf文件格式再进一步去看这两个函数的实现,后续我将再写博文来讨论,敬请关注!
OK,先这样!