本帖最后由 永不停息 于 2018-12-25 09:34 编辑 【RT-Thread内核实现与应用开发实战指南】本书总共分为两个部分,第一部分主要剖析 RT-Thread 内核的实现机理,通过代码从操作系统最基本的线程创建和线程切换开始,一步一步增加代码功能(线程阻塞、时间片、优先级、临界区等)由浅入深从开发者的角度教读者怎么从零实现基本的操作系统内核。虽然内容不是很细致,但是能够帮助读者抓住重点,让读者能够最快速的了解操作系统的核心,读完这部分然后再去阅读源码会比直接阅读源码事半功倍。第二部分内容着重RT-Thread 的应用,分别介绍操作系统各种机制(信号量、互斥量、时间、邮箱等)的工作机理以及相关的接口函数,读者可以通过这部分去熟悉 RT-Thread 的应用。由于本人之前对一些操作系统(FreeRTOS)也有所学习和应用,但是没有深入去学习源码,对于操作系统内核的实现机制并不是很了解,因此这次也将学习重点放在第一部分,下面针对第一部分的线程创建和切换这部分进行总结。对于操作系统而言,线程是最基本的概念,什么是线程呢?线程是程序执行的最小单元,每个线程都拥有独立的栈空间,是系统调度的基本单元。操作系统最核心的地方也在于线程的调度,系统为了方便线程调度,为每个线程都额外定义了线程控制块,线程控制块的定义如下:- /**
- * Thread structure
- */
- struct rt_thread
- {
- /* rt object */
- char name[RT_NAME_MAX]; /**< the name of thread */
- rt_uint8_t type; /**< type of object */
- rt_uint8_t flags; /**< thread's flags */
- rt_list_t list; /**< the object list */
- rt_list_t tlist; /**< the thread list */
- /* stack point and entry */
- void *sp; /**< stack point */
- void *entry; /**< entry */
- void *parameter; /**< parameter */
- void *stack_addr; /**< stack address */
- rt_uint32_t stack_size; /**< stack size */
- /* error code */
- rt_err_t error; /**< error code */
- rt_uint8_t stat; /**< thread status */
- /* priority */
- rt_uint8_t current_priority; /**< current priority */
- rt_uint8_t init_priority; /**< initialized priority */
复制代码 其中 tlist 成员是线程的链表节点,后面要把线程插入到各种双向链表中便是通过该链表节点实现,该链表节点定义如下:- struct rt_list_node
- {
- struct rt_list_node *next; /**< point to next node. */
- struct rt_list_node *prev; /**< point to prev node. */
- };
- typedef struct rt_list_node rt_list_t; /**< Type for lists. */
复制代码 线程初始化通过函数 rt_thread_init(struct rt_thread *thread) 实现,主要进行线程控制块相关成员的初始化以及初始化线程栈具体实现如下所示:- static rt_err_t _rt_thread_init(struct rt_thread *thread,
- const char *name,
- void (*entry)(void *parameter),
- void *parameter,
- void *stack_start,
- rt_uint32_t stack_size,
- rt_uint8_t priority,
- rt_uint32_t tick)
- {
- /* init thread list */
- rt_list_init(&(thread->tlist));
- thread->entry = (void *)entry;
- thread->parameter = parameter;
- /* stack init */
- thread->stack_addr = stack_start;
- thread->stack_size = stack_size;
- /* init thread stack */
- rt_memset(thread->stack_addr, '#', thread->stack_size);
- thread->sp = (void *)rt_hw_stack_init(thread->entry, thread->parameter,
- (void *)((char *)thread->stack_addr + thread->stack_size - 4),
- (void *)rt_thread_exit);
- /* priority init */
- RT_ASSERT(priority < RT_THREAD_PRIORITY_MAX);
- thread->init_priority = priority;
- thread->current_priority = priority;
- thread->number_mask = 0;
- /* tick init */
- thread->init_tick = tick;
- thread->remaining_tick = tick;
- /* error and flags */
- thread->error = RT_EOK;
- thread->stat = RT_THREAD_INIT;
- /* initialize cleanup function and user data */
- thread->cleanup = 0;
- thread->user_data = 0;
- /* init thread timer */
- rt_timer_init(&(thread->thread_timer),
- thread->name,
- rt_thread_timeout,
- thread,
- 0,
- RT_TIMER_FLAG_ONE_SHOT);
- RT_OBJECT_HOOK_CALL(rt_thread_inited_hook, (thread));
- return RT_EOK;
- }
复制代码 从上面的函数中可以看出线程栈的初始化由函数 rt_hw_stack_init() 来完成,每个线程的栈由三部分组成:自动加载到 CPU 寄存器的部分、手动加载到 CPU 寄存器的部分以及空闲栈。如下图所示:
初始化后除了空闲栈部分其他值都变成 0xdeadbeef,线程第一次运行时,自动加载到 CPU 部分的内容要预先设置好,包括 PC 指针、R0任务形参,其它设置为 0。线程初始化后要插入到就绪列表中,通过函数 rt_list_insert_before() 实现,最后要运行线程则需要启动调度器,启用调度器通过函数 rt_system_scheduler_start() 实现,具体函数实现如下:- <blockquote>void rt_system_scheduler_start(void)
复制代码 函数中 rt_hw_context_switch_to() 用于启动第一次线程切换,该函数通过触发 PendSV 中断来实现上下文切换,后面的上下文切换则通过函数 rt_hw_context_switch() 实现。最后系统调度通过 rt_scheduer() 函数实现,其核心内容也是通过获取下一个要执行的线程然后产生上下文切换,在此不详细说明。第一次分享帖子,写得不好多多见谅。帖子内容有限,只是说明一下我个人对于其中的一点理解,各位有兴趣的还是得亲自去看书,个人觉得书是挺好的,由浅入深,循序渐进,但是看完书后最重要的还是得多实践,要想深入内核原理的还得慢慢啃源码。 |