rtthread:"rt_thread"线程
1 微处理器系统
随着产品功能的增多,裸机系统不能够满足产品需求,引入RTOS实时操作系统的多线程管理,可以增加程序的稳定性逻辑性,便于管理;
2 rtos系统启动
正常的系统都是从main函数中启动,那么对于rtos而言。它是如何启动的呢?
rtos使用了keil编译器的打补丁功能,将thread启动之前的初始化放在了打补丁函数中来初始化,初始化完毕之后调度启动执行;
//components.c extern int $Super$$main(void); /* re-define main function */ int $Sub$$main(void) { rt_hw_interrupt_disable(); rtthread_startup(); return 0; } //这是main函数的补丁操作范围 int rtthread_startup(void) { rt_hw_interrupt_disable(); rt_hw_board_init(); //NOTE: please initialize heap inside board initialization //rt_show_version(); rt_system_timer_init(); //system:rt_timer_list rt_system_scheduler_init(); //system:rt_thread_priority_table #ifdef RT_USING_SIGNALS rt_system_signal_init(); #endif rt_application_init(); //main_thread >>main_thread_entry rt_system_timer_thread_init(); //timer_thread >>rt_thread_timer_entry rt_thread_idle_init(); //idle >>rt_thread_idle_entry rt_system_scheduler_start(); //启动调度函数switch_to汇编; /* never reach here */ return 0; }
void rt_application_init(void) { rt_thread_t tid; #ifdef RT_USING_HEAP tid = rt_thread_create("main", main_thread_entry, RT_NULL, RT_MAIN_THREAD_STACK_SIZE, RT_THREAD_PRIORITY_MAX / 3, 20); RT_ASSERT(tid != RT_NULL); #else rt_err_t result; tid = &main_thread; result = rt_thread_init(tid, "main", main_thread_entry, RT_NULL, main_stack, sizeof(main_stack), RT_THREAD_PRIORITY_MAX / 3, 20); RT_ASSERT(result == RT_EOK); /* if not define RT_USING_HEAP, using to eliminate the warning */ (void)result; #endif rt_thread_startup(tid);//将"main"线程挂载到优先级表上;优先级=RT_THREAD_PRIORITY_MAX / 3; }
void main_thread_entry(void *parameter) { extern int main(void); extern int $Super$$main(void); rt_components_init(); /* invoke system main function */ #if defined (__CC_ARM) $Super$$main(); //回调main函数; #elif defined(__ICCARM__) || defined(__GNUC__) main(); #endif }
3 线程 thread
rtos启动完毕之后,我们就可以在main函数中初始化应用层线程了;那什么是线程呢?
线程是以功能划分的程序单元,该程序单元的所有参数囊括在唯一对应的线程结构体中;thread在其他rtos中也被称为task;
3.1 线程结构体 rt_thread
线程结构体存储当前线程的所有参数;对于线程的架构理解可以以线程结构体作为线索来追踪;
//rtdef.h 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 */ #ifdef RT_USING_MODULE void *module_id; /**< id of application module */ #endif 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 */ #if RT_THREAD_PRIORITY_MAX > 32 rt_uint8_t number; rt_uint8_t high_mask; #endif rt_uint32_t number_mask; #if defined(RT_USING_EVENT) /* thread event */ rt_uint32_t event_set; rt_uint8_t event_info; #endif #if defined(RT_USING_SIGNALS) rt_sigset_t sig_pending; /**< the pending signals */ rt_sigset_t sig_mask; /**< the mask bits of signal */ void *sig_ret; /**< the return stack pointer from signal */ rt_sighandler_t *sig_vectors; /**< vectors of signal handler */ void *si_list; /**< the signal infor list */ #endif rt_ubase_t init_tick; /**< thread's initialized tick */ rt_ubase_t remaining_tick; /**< remaining tick */ struct rt_timer thread_timer; /**< built-in thread timer */ void (*cleanup)(struct rt_thread *tid); /**< cleanup function when thread exit */ rt_uint32_t user_data; /**< private user data beyond this thread */ }; typedef struct rt_thread *rt_thread_t;
3.2 对象 rt_object
对象是对线程的各类数据结构的统称;
可以通过容器对象来判断malloc对象分配;但是封装后的数据结构可以挂载到容器上进行统一管理;至于统一管理有啥用还没凸显出来先放着;
//rtdef.h struct rt_object { char name[RT_NAME_MAX]; /**< name of kernel object */ rt_uint8_t type; /**< type of kernel object */ rt_uint8_t flag; /**< flag of kernel object */ #ifdef RT_USING_MODULE void *module_id; /**< id of application module */ #endif rt_list_t list; /**< list node of kernel object */ }; typedef struct rt_object *rt_object_t; /**< Type for kernel objects. */ //rt_object_t->type的取值; enum rt_object_class_type { RT_Object_Class_Thread = 0, /* 对象是线程 */ RT_Object_Class_Semaphore, /* 对象是信号量 */ RT_Object_Class_Mutex, /* 对象是互斥量 */ RT_Object_Class_Event, /* 对象是事件 */ RT_Object_Class_MailBox, /* 对象是邮箱 */ RT_Object_Class_MessageQueue, /* 对象是消息队列 */ RT_Object_Class_MemHeap, /* 对象是内存堆 */ RT_Object_Class_MemPool, /* 对象是内存池 */ RT_Object_Class_Device, /* 对象是设备 */ RT_Object_Class_Timer, /* 对象是定时器 */ RT_Object_Class_Module, /* 对象是模块 */ RT_Object_Class_Unknown, /* 对象未知 */ RT_Object_Class_Static = 0x80 /* 对象是静态对象 */ };
3.2.1 容器
容器是一个可以寻址rtos系统全部对象的数组;
每个对象在初始化完自己的结构体之后,就顺便把自己挂载到容器对应的数据结构下;
//容器对象结构体 rtdef.h struct rt_object_information { enum rt_object_class_type type; //对象的数据类型 rt_list_t object_list; //该数据类型挂载list的链表头 rt_size_t object_size; //该object struct结构体大小 }; //容器对象类型 object.c enum rt_object_info_type { RT_Object_Info_Thread = 0, //线程对象 #ifdef RT_USING_SEMAPHORE RT_Object_Info_Semaphore, //信号量对象 #endif //省略...#ifdef ...#endif #ifdef RT_USING_MODULE RT_Object_Info_Module, //模块对象 #endif RT_Object_Info_Unknown, //对象未知 }; //容器数组 object.c //下面这行define是用自身地址初始化了容器链表头; #define _OBJ_CONTAINER_LIST_INIT(c) {&(rt_object_container[c].object_list), &(rt_object_container[c].object_list)} static struct rt_object_information rt_object_container[RT_Object_Info_Unknown] = { { RT_Object_Class_Thread, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Thread), sizeof(struct rt_thread) }, #ifdef RT_USING_SEMAPHORE { RT_Object_Class_Semaphore, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Semaphore), sizeof(struct rt_semaphore) }, #endif //#ifdef ....... #ifdef RT_USING_MODULE { RT_Object_Class_Module, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Module), sizeof(struct rt_module) }, #endif };
3.3 tlist节点
tlist节点用来将线程挂载到线程就绪优先级表上等待执行,或挂载到系统定时器链表上等待延时到期;
////rtdef.h 注意rt_list_t的第一个节点不是prev,而是next; struct rt_list_node { struct rt_list_node *next; struct rt_list_node *prev; }; typedef struct rt_list_node rt_list_t;
3.3.1 节点地址反推结构体首地址
//rtservice.h 已知一个结构体里面的节点地址,反推出该节点所在的结构体首地址 #define rt_container_of(ptr, type, member) ((type *)((char *)(ptr) - (unsigned long)(&((type *)0)->member))) #define rt_list_entry(node, type, member) rt_container_of(node, type, member) //简单整理了一下上面的宏变成下面这样方便理解;只有最外层和node的括号可以去,其他括号都有用; //#define rt_list_entry(node, struct, member) (struct *)( (char *)node - (unsigned long)( &((struct *)0)->member ) ) /*** 1. #define 默认换行符为结束define,通过\连接符来连接两行为一个define; 2. to_thread = rt_list_entry(rt_thread_priority_table[0].next, struct rt_thread, tlist); = ( (rt_thread *)((char *)rt_thread_priority_table[0].next - (unsigned long)(&((rt_thread *)0)->tlist)) ) (unsigned long)(&((rt_thread *)0)->tlist)):定义一个rt_thread结构体,初始地址赋值为0,那么&(rt_thread->tlist)就是tlist在结构体中的偏移地址; node地址&(rt_thread_priority_table[0].next) 减去node的偏移地址,就得到了该node所在的线程结构体初始地址,即存储 *sp 的地址; 3. 所有类型的指针都是32位的,用来存储地址,关于这里为什么把指针类型强制转换成char*,因为指针的自加加加的是所指向数据类型的地址宽度; ***/
3.4 线程栈 stack
线程栈存储该线程的寄存器状态、栈参数;通常是一段内存空间或全局数组;
//main.c 静态分配; 直接定义了线程栈数组; rt_uint8_t rt_flag1_thread_stack[512]; //thread.c 动态分配; 在rt_thread_create()函数中,通过RT_KERNEL_MALLOC()函数来分配线程栈的动态内存; stack_start = (void *)RT_KERNEL_MALLOC(stack_size);
3.4.1 stack_frame 通用寄存器
线程栈栈底是stack_frame通用寄存器结构体 ,存储内核通用寄存器组的参数;栈底高地址、栈顶低地址;
//cpuport.c struct stack_frame { /* r4 ~ r11 register 异常发生时需手动保存的寄存器 */ rt_uint32_t r4; rt_uint32_t r5; rt_uint32_t r6; rt_uint32_t r7; rt_uint32_t r8; rt_uint32_t r9; rt_uint32_t r10; rt_uint32_t r11; struct exception_stack_frame exception_stack_frame; }; struct exception_stack_frame { /* 异常发生时自动保存的寄存器 */ rt_uint32_t r0; rt_uint32_t r1; rt_uint32_t r2; rt_uint32_t r3; rt_uint32_t r12; rt_uint32_t lr; rt_uint32_t pc; rt_uint32_t psr; };
4 thread 初始化
对线程的初始化即对线程结构体的初始化;
本小节两种方式虽然内存分配方式不同,但是均调用_rt_thread_init( );具体见后一节;
4.1 静态内存分配
以全局变量的方式来分配线程内存,内存编译器编译的时候就存在,占用空间;
rt_thread_init( )
//main.c static rt_uint8_t led0_blink_entry_stack[RT_MAIN_THREAD_STACK_SIZE]; struct rt_thread led0_blink_thread; int main(void) { rt_thread_init(&led0_blink_thread, "led0_blink", led0_blink_entry, RT_NULL, led0_blink_entry_stack, sizeof(led0_blink_entry_stack), 2, 50); } //thread.c 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) { /* thread check */ RT_ASSERT(thread != RT_NULL); RT_ASSERT(stack_start != RT_NULL); /* init thread object */ rt_object_init((rt_object_t)thread, RT_Object_Class_Thread, name); return _rt_thread_init(thread, name, entry, parameter, stack_start, stack_size, priority, tick); } RTM_EXPORT(rt_thread_init);
4.2 动态内存分配
以动态分配的方式来分配线程内存,用完可释放,内存利用率高;
rt_thread_create ( )
//thread.c rt_thread_t rt_thread_create(const char *name, void (*entry)(void *parameter), void *parameter, rt_uint32_t stack_size, rt_uint8_t priority, rt_uint32_t tick) { struct rt_thread *thread; void *stack_start; //分配rt_object动态内存并初始化; thread = (struct rt_thread *)rt_object_allocate(RT_Object_Class_Thread, name); if (thread == RT_NULL) return RT_NULL; //分配stack frame的动态内存; stack_start = (void *)RT_KERNEL_MALLOC(stack_size); if (stack_start == RT_NULL) { /* allocate stack failure */ rt_object_delete((rt_object_t)thread); return RT_NULL; } //动态分配和静态分配的线程都是通过这个函数初始化的; _rt_thread_init(thread, name, entry, parameter, stack_start, stack_size, priority, tick); return thread; } RTM_EXPORT(rt_thread_create);
5 rt_thread 初始化
5.1 rt_object 初始化
rt_thread 的前一部分即rt_object 对象;首先对其进行初始化;
//object.c rt_object_t rt_object_allocate(enum rt_object_class_type type, const char *name) { struct rt_object *object; register rt_base_t temp; struct rt_object_information *information; RT_DEBUG_NOT_IN_INTERRUPT; #ifdef RT_USING_MODULE /* * get module object information, * module object should be managed by kernel object container */ information = (rt_module_self() != RT_NULL && (type != RT_Object_Class_Module)) ? &rt_module_self()->module_object[type] : rt_object_get_information(type); #else //通过对象的数据结构类型,返回容器数组对象; information = rt_object_get_information(type); RT_ASSERT(information != RT_NULL); #endif //通过容器获取该数据类型的结构体大小,动态分配内存; object = (struct rt_object *)RT_KERNEL_MALLOC(information->object_size); if (object == RT_NULL) { /* no memory can be allocated */ return RT_NULL; } /* initialize object's parameters */ //2:数据结构类型 object->type = type; //3:flag标志位 object->flag = 0; #ifdef RT_USING_MODULE if (rt_module_self() != RT_NULL) { object->flag |= RT_OBJECT_FLAG_MODULE; } object->module_id = (void *)rt_module_self(); #endif //1:对象字符串 rt_strncpy(object->name, name, RT_NAME_MAX); RT_OBJECT_HOOK_CALL(rt_object_attach_hook, (object)); /* lock interrupt */ temp = rt_hw_interrupt_disable(); //4:对象节点 rt_list_insert_after(&(information->object_list), &(object->list)); /* unlock interrupt */ rt_hw_interrupt_enable(temp); /* return object */ return object; } //object.c struct rt_object_information *rt_object_get_information(enum rt_object_class_type type) { int index; for (index = 0; index < RT_Object_Info_Unknown; index ++) if (rt_object_container[index].type == type) return &rt_object_container[index]; return RT_NULL; } //kservice.c 对象字符串拷贝函数 char *rt_strncpy(char *dst, const char *src, rt_ubase_t n) { if (n != 0){ char *d = dst; const char *s = src; do{ //cae20240104:赋值表达式有返回值,返回值默认为等号右边的赋值变量值;此处为*s的值; //当src值为0的时候,当n值未递减完的时候,就往dst填充0;这样的话就避免了搬运ram中的杂乱数据; if ((*d++ = *s++) == 0){ /* NUL pad the remaining n-1 bytes */ while (--n != 0) *d++ = 0; break; } } while (--n != 0); } return (dst); }
5.2 rt_thread 初始化
rt_thread在内存分配完毕之后,都是调用 _rt_thread_init( )对rt_thread进行初始化;
//thread.c 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; #if RT_THREAD_PRIORITY_MAX > 32 thread->number = 0; thread->high_mask = 0; #endif /* 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); #ifdef RT_USING_MODULE thread->module_id = RT_NULL; #endif /* initialize signal */ #ifdef RT_USING_SIGNALS thread->sig_mask = 0x00; thread->sig_pending = 0x00; thread->sig_ret = RT_NULL; thread->sig_vectors = RT_NULL; thread->si_list = RT_NULL; #endif RT_OBJECT_HOOK_CALL(rt_thread_inited_hook, (thread)); return RT_EOK; }
5.3 stack_frame 初始化
在_rt_thread_init( )中初始化rt_thread的时候,会调用rt_hw_stack_init( )对stack_frame进行初始化;
所以等会BLX回原程序的时候是执行lr中的rt_thread_exit()函数吗?那pc指针里存储的原函数地址又是什么用呢?先放着;
lr中存放的是当前entry退出的执行函数入口,pc里存放的是当前entry进入的时候执行的函数入口;。。。我怎么会问这么傻屌的问题;
//cpuport.c 传入的stack_addr = &rt_flag1_thread_stack[0] + sizeof(栈) - 4 ; rt_uint8_t *rt_hw_stack_init(void *tentry, void *parameter, rt_uint8_t *stack_addr, void *texit) { struct stack_frame *stack_frame; rt_uint8_t *stk; unsigned long i; stk = stack_addr + sizeof(rt_uint32_t); //下面这里又加了4; 这里stk指向栈底之外的地址; stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8); //8字节align,冗余字节dummy; stk -= sizeof(struct stack_frame); //这里stk移动到了&r4,返回值; stack_frame = (struct stack_frame *)stk; /* init all register */ for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++) { ((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef; } stack_frame->exception_stack_frame.r0 = (unsigned long)parameter; /* r0 : argument 线程函数形参*/ stack_frame->exception_stack_frame.r1 = 0; stack_frame->exception_stack_frame.r2 = 0; stack_frame->exception_stack_frame.r3 = 0; stack_frame->exception_stack_frame.r12 = 0; stack_frame->exception_stack_frame.lr = (unsigned long)texit; /* lr &rt_thread_exit()地址 */ stack_frame->exception_stack_frame.pc = (unsigned long)tentry; /* entry point, pc 线程函数地址*/ stack_frame->exception_stack_frame.psr = 0x01000000L; /* PSR bit24置1确保返回的是psp*/ /* return task's current stack address */ return stk; } //地址对齐,冗余地址字节舍弃; #define RT_ALIGN_DOWN(addr, align) ((addr) & ~((align) - 1)
5.4 定时器初始化
见定时器章节;
5.5 rt_thread_exit退出线程
&rt_thread_exit( ) 的地址已经放入到了lr寄存器中,当线程函数执行完返回的时候就会跳转到rt_thread_exit( )执行;
//thread.c //if是static rt_object,那么detach rt_object;所以这里并没有对static线程执行什么处理咯? //else,那么&thread->tlist插入&rt_thread_defunct,等待idle_entry清理; //当前函数是在线程退出的时候才会执行,所以线程许多都是while(1)不会执行到这里; void rt_thread_exit(void) { struct rt_thread *thread; register rt_base_t level; /* get current thread */ thread = rt_current_thread; /* disable interrupt */ level = rt_hw_interrupt_disable(); /* remove from schedule */ rt_schedule_remove_thread(thread); /* change stat */ thread->stat = RT_THREAD_CLOSE; /* remove it from timer list */ rt_timer_detach(&thread->thread_timer); if ((rt_object_is_systemobject((rt_object_t)thread) == RT_TRUE) && thread->cleanup == RT_NULL) { rt_object_detach((rt_object_t)thread); } else { /* insert to defunct thread list */ rt_list_insert_after(&rt_thread_defunct, &(thread->tlist)); } /* enable interrupt */ rt_hw_interrupt_enable(level); /* switch to next task */ rt_schedule(); }
6 注意事项
//这里的结构体对象和结构体指针都声明成了rt_xx_t的形式,不对称; /************************************************************************/ struct rt_thread{ /*...*/ }; typedef struct rt_thread *rt_thread_t; //声明 rt_thread_t 是结构体指针; /************************************************************************/ struct rt_list_node{ struct rt_list_node *next; struct rt_list_node *prev; }; typedef struct rt_list_node rt_list_t; //声明 rt_list_t 是结构体; /************************************************************************/ struct rt_object{ /*...*/ }; typedef struct rt_object *rt_object_t; //声明rt_object_t是结构体指针;
7 小结
系统是如何从补丁函数启动的;
单个线程函数是如何初始化的,其中的参数psr pc lr r0具体标识;
本文总体满意,还算结构清晰;棒棒哒;
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)