【RT-Thread读书笔记】4. RT-Thread 学习6章读后感(一)
[复制链接]
本帖最后由 传媒学子 于 2019-4-27 21:54 编辑
【RT-Thread读书笔记】4. RT-Thread 学习6章读后感 此次分享对应的是《RT-thread内核实现与应用开发实战指南》的第六章中6.1-6.3的内容。
线程是RTOS的基础,非常重要,线程的切换是用汇编代码来实现的,应该就是汇编直接编写arm指令代码,实现线程切换,这样做的好处,相对于C来讲应该就是切换的速度更快。
为什么用汇编来实现线程切换? 可能就是用汇编更接近与底层,相对于C来讲,速度可能更快,因为在RTOS中,线程切换是一个非常频繁的操作,线程切换会使用栈或者堆等存储或者读取上下文。
1.线程的定义: 在裸机系统中,系统就一个线程,即main函数中的大循环;在RTOS中,有很多这样的大循环,每个大循环简称为一个线程。通过任务调度器等完成县城切换。
2.创建线程: 在裸机系统中,会有全局变量,子函数调用,以及中断发生,在系统运行时,这些东西存放在哪里,我们不用十分关注。但在RTOS中,存在很多线程,我们必须弄清楚这些东西的存放位置,这样才能调用它们,保证RTOS的正确运行。
通常这些参数会被存放在栈中,栈一般是RAM中的一段空间。在RTOS中,线程之间是相互独立的,互不干扰的,因此,我们要为每个线程分配自己独立的栈空间。因此,我们会发现为什么RTOS会占用比裸机系统要大一些的RAM空间。
定义线程栈: - [/align]rt_uint8_t rt_flag1_thread_stack[512];
- rt_uint8_t rt_flag2_thread_stack[512];
复制代码
在RT-Thread中,所有涉及到数据类型的地方,都会在rtdef.h中重新定义: - /* RT-Thread basic data type definitions */
- typedef signed char rt_int8_t; /**< 8bit integer type */
- typedef signed short rt_int16_t; /**< 16bit integer type */
- typedef signed long rt_int32_t; /**< 32bit integer type */
- typedef unsigned char rt_uint8_t; /**< 8bit unsigned integer type */
- typedef unsigned short rt_uint16_t; /**< 16bit unsigned integer type */
- typedef unsigned long rt_uint32_t; /**< 32bit unsigned integer type */
- typedef int rt_bool_t; /**< boolean type */
- /* 32bit CPU */
- typedef long rt_base_t; /**< Nbit CPU related date type */
- typedef unsigned long rt_ubase_t; /**< Nbit unsigned CPU related data type */
- typedef rt_base_t rt_err_t; /**< Type for error number */
- typedef rt_uint32_t rt_time_t; /**< Type for time stamp */
- typedef rt_uint32_t rt_tick_t; /**< Type for tick count */
- typedef rt_base_t rt_flag_t; /**< Type for flags */
- typedef rt_ubase_t rt_size_t; /**< Type for size number */
- typedef rt_ubase_t rt_dev_t; /**< Type for device */
- typedef rt_base_t rt_off_t; /**< Type for offset */
- /* boolean type definitions */
- #define RT_TRUE 1 /**< boolean true */
- #define RT_FALSE 0 /**< boolean fails */
复制代码
3.定义线程函数:每个线程都是一个无限循环且不能返回的函数,其实就是以前裸机系统的main函数。
4.定义线程控制块:线程控制块是保存每个线程运行所需的所有信息的地方,例如线程的栈指针,线程名称,线程的各种参数等。系统可以通过线程控制块,实现对线程的调度。
rt-thread中声明如下:
- /*
- *************************************************************************
- * 线程结构体
- *************************************************************************
- */
- struct rt_thread
- {
- void *sp; /* 线程栈指针 */
- void *entry; /* 线程入口地址 */
- void *parameter; /* 线程形参 */
- void *stack_addr; /* 线程起始地址 */
- rt_uint32_t stack_size; /* 线程栈大小,单位为字节 */
-
- rt_list_t tlist; /* 线程链表节点 */
- };
- typedef struct rt_thread *rt_thread_t;
复制代码
我们看到,这个结构体中,定义中使用了指针,其实指针这个东西真的很简单,就是一个tag,因为实际的物理地址比较长,一般是0xXXXXXXXXX, 为了方便,我们定义了一个指针,这个指针就代表这个实际的地址,而指针也是一个变量,它也会占用内存,因此会有指针的指针。建议好好理解一下指针, 因为有些老师在教书的时候,给大家灌输一种指针很难学,指针容易出错的错误理念,造成我们谈指色变,反正我是深受其害。希望有些老师在教书的时候,还是要客观的讲述一些东西,指针很重要,而不是指针很难用。这里多说了一点东西,有些地方可能理解有误,大家还是以书本讲述为准。
5.线程创建函数的实现:
首先,是初始化函数:
- #include <rtthread.h>
- #include <rthw.h>
- rt_err_t rt_thread_init(struct rt_thread *thread,
- void (*entry)(void *parameter),
- void *parameter,
- void *stack_start,
- rt_uint32_t stack_size)
- {
- rt_list_init(&(thread->tlist));
-
- thread->entry = (void *)entry;
- thread->parameter = parameter;
- thread->stack_addr = stack_start;
- thread->stack_size = stack_size;
-
- /* 初始化线程栈,并返回线程栈指针 */
- thread->sp = (void *)rt_hw_stack_init( thread->entry,
- thread->parameter,
- (void *)((char *)thread->stack_addr + thread->stack_size - 4) );
-
- return RT_EOK;
- }
复制代码
5.1实现链表相关函数
rt_list_init(&(thread->tlist)); 初始化线程链表节点,我们把线程控制块挂在链表中。 - /*
- *************************************************************************
- * 双向链表结构体
- *************************************************************************
- */
- struct rt_list_node
- {
- struct rt_list_node *next; /* 指向后一个节点 */
- struct rt_list_node *prev; /* 指向前一个节点 */
- };
- typedef struct rt_list_node rt_list_t;
复制代码
初始化链表节点:就是将节点中的next和pre这两个节点指针指向节点本身。
双向链表的插入,删除 对于实现任务控制是很重要的知识,建议大家认真掌握和理解这些内容,这些知识数据结构上都会有详细介绍,在这本书中也有很多介绍,这里就不多说了。
5.2 rt_hw_stack_init()函数
下面是错误代码的定义:
- /*
- *************************************************************************
- * 错误码定义
- *************************************************************************
- */
- /* RT-Thread 错误码重定义 */
- #define RT_EOK 0 /**< There is no error */
- #define RT_ERROR 1 /**< A generic error happens */
- #define RT_ETIMEOUT 2 /**< Timed out */
- #define RT_EFULL 3 /**< The resource is full */
- #define RT_EEMPTY 4 /**< The resource is empty */
- #define RT_ENOMEM 5 /**< No memory */
- #define RT_ENOSYS 6 /**< No system */
- #define RT_EBUSY 7 /**< Busy */
- #define RT_EIO 8 /**< IO error */
- #define RT_EINTR 9 /**< Interrupted system call */
- #define RT_EINVAL 10 /**< Invalid argument */
复制代码
下面在主函数中初始化线程:
- int main(void)
- {
- /* 硬件初始化 */
- /* 将硬件相关的初始化放在这里,如果是软件仿真则没有相关初始化代码 */
-
- /* 调度器初始化 */
- rt_system_scheduler_init();
-
-
- /* 初始化线程 */
- rt_thread_init( &rt_flag1_thread, /* 线程控制块 */
- flag1_thread_entry, /* 线程入口地址 */
- RT_NULL, /* 线程形参 */
- &rt_flag1_thread_stack[0], /* 线程栈起始地址 */
- sizeof(rt_flag1_thread_stack) ); /* 线程栈大小,单位为字节 */
- /* 将线程插入到就绪列表 */
- rt_list_insert_before( &(rt_thread_priority_table[0]),&(rt_flag1_thread.tlist) );
-
- /* 初始化线程 */
- rt_thread_init( &rt_flag2_thread, /* 线程控制块 */
- flag2_thread_entry, /* 线程入口地址 */
- RT_NULL, /* 线程形参 */
- &rt_flag2_thread_stack[0], /* 线程栈起始地址 */
- sizeof(rt_flag2_thread_stack) ); /* 线程栈大小,单位为字节 */
- /* 将线程插入到就绪列表 */
- rt_list_insert_before( &(rt_thread_priority_table[1]),&(rt_flag2_thread.tlist) );
-
- /* 启动系统调度器 */
- rt_system_scheduler_start();
- }
复制代码
由于这一章内容较多,而且重要,我这边分为多期来学习这一章,今天先介绍到这里。
总结:
本文主要对RT-Thread的线程定义以及如何创建线程进行了梳理,创建线程又包括: 定义线程栈,定义线程函数,定义线程控制块,实现线程创建函数等内容,涵盖了数据结构的指针、栈、双向链表的知识,涉及C语言结构体等内容。
同时,结合rt-thread源码,学习了rt-thread关于这些内容的设计和实现。
下面会就书中“6.4实现就绪列表、6.5实现调度器、6.6实现main函数”等内容进行学习和梳理。
此内容由EEWORLD论坛网友传媒学子原创,如需转载或用于商业用途需征得作者同意并注明出处
|