RT-Thread内核源码分析-线程栈结构分析

2024-06-16 14:18

本文主要是介绍RT-Thread内核源码分析-线程栈结构分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

 

RT-Thread提供了一套满足POSIX标准的接口,因此基于RT-Thread编写的应用程序,可以相对轻松的移植到linux平台。 pthread_create接口用来创建一个线程, 接下来我们基于pthread_create来分析动态分配的数组时如何作为线程栈来使用的。


int pthread_create(pthread_t            *tid,const pthread_attr_t *attr, void *(*start) (void *), void *parameter)
{int result;void *stack;char name[RT_NAME_MAX];static rt_uint16_t pthread_number = 0;_pthread_data_t *ptd;/* tid shall be provided */RT_ASSERT(tid != RT_NULL);/* allocate posix thread data */ptd = (_pthread_data_t*)rt_malloc(sizeof(_pthread_data_t));if (ptd == RT_NULL)return ENOMEM;/* clean posix thread data memory */rt_memset(ptd, 0, sizeof(_pthread_data_t));ptd->canceled = 0;ptd->cancelstate = PTHREAD_CANCEL_DISABLE;ptd->canceltype = PTHREAD_CANCEL_DEFERRED;ptd->magic = PTHREAD_MAGIC;if (attr != RT_NULL)ptd->attr = *attr;else {/* use default attribute */pthread_attr_init(&ptd->attr);}rt_snprintf(name, sizeof(name), "pth%02d", pthread_number ++);if (ptd->attr.stack_base == 0){stack = (void*)rt_malloc(ptd->attr.stack_size);//根据用户指定的大小动态分配栈空间}elsestack = (void*)(ptd->attr.stack_base);if (stack == RT_NULL) {rt_free(ptd);return ENOMEM;}/* pthread is a static thread object */ptd->tid = (rt_thread_t) rt_malloc(sizeof(struct rt_thread));//分配线程控制块if (ptd->tid == RT_NULL){if (ptd->attr.stack_base == 0)rt_free(stack);rt_free(ptd);return ENOMEM;}if (ptd->attr.detachstate == PTHREAD_CREATE_JOINABLE){ptd->joinable_sem = rt_sem_create(name, 0, RT_IPC_FLAG_FIFO);if (ptd->joinable_sem == RT_NULL){if (ptd->attr.stack_base != 0)rt_free(stack);rt_free(ptd);return ENOMEM;}}elseptd->joinable_sem = RT_NULL;/* set parameter */ptd->thread_entry = start;ptd->thread_parameter = parameter;/* initial this pthread to system *///调用rtthread提供的线程初始化接口if (rt_thread_init(ptd->tid, name, pthread_entry_stub, ptd, stack, ptd->attr.stack_size, 99 - ptd->attr.priority, 5) != RT_EOK){if (ptd->attr.stack_base == 0)rt_free(stack);if (ptd->joinable_sem != RT_NULL)rt_sem_delete(ptd->joinable_sem);rt_free(ptd);return EINVAL;}/* set pthread id */*tid = ptd->tid;/* set pthread cleanup function and ptd data */(*tid)->cleanup = _pthread_cleanup;(*tid)->user_data = (rt_uint32_t)ptd;/* start thread */result = rt_thread_startup(*tid);//启动该线程if (result == RT_EOK)return 0;/* start thread failed */rt_thread_detach(ptd->tid);if (ptd->attr.stack_base == 0)rt_free(stack);if (ptd->joinable_sem != RT_NULL)rt_sem_delete(ptd->joinable_sem);rt_free(ptd);return EINVAL;
}

pthread_create内部是调用rt_thread_init和rt_thread_startup来完成线程的初始化和启动工作,我们先看初始化函数:

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);
}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 = (rt_uint16_t)stack_size;/* init thread stack */rt_memset(thread->stack_addr, '#', thread->stack_size);// 传入的栈地址是: stack_addr + stack_size - 4; 之所以这么做是确保栈空间是向下增长的, 注意:这里有一个减4的操作 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;/* 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);return RT_EOK;
}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的操作, 和上文中的减4的操作配合,这个是为什么呢?stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);//8字节对齐, 不用担心stk会越界;stk -= sizeof(struct stack_frame);//stack_frame是ARM寄存器存储列表,也就是我们栈中寄存器的存储顺序, 这个操作将寄存器入栈, stk即为栈顶指针,接下来是对线程栈内容的初始化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;                        /* r1 */stack_frame->exception_stack_frame.r2  = 0;                        /* r2 */stack_frame->exception_stack_frame.r3  = 0;                        /* r3 */stack_frame->exception_stack_frame.r12 = 0;                        /* r12 */stack_frame->exception_stack_frame.lr  = (unsigned long)texit;     /* lr */stack_frame->exception_stack_frame.pc  = (unsigned long)tentry;    /* entry point, pc */stack_frame->exception_stack_frame.psr = 0x01000000L;              /* PSR *//* return task's current stack address */return stk;//返回栈顶指针
}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;
};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;
};到这里,我们就清楚了线程在创建时分配的数组是如何转化为线程栈的, 动态分配的数组位于堆上, 数组作为栈来使用时,需要从数组的末尾进行栈的初始化。

    再来看函数rt_thread_startup, 我们知道,在linux环境下,pthread_create函数创建线程后,子线程也会立即被调度执行,那么,在RT-Thead环境下,也是这样的效果吗?

/*** This function will start a thread and put it to system ready queue** @param thread the thread to be started** @return the operation status, RT_EOK on OK, -RT_ERROR on error*/
rt_err_t rt_thread_startup(rt_thread_t thread)
{/* thread check */RT_ASSERT(thread != RT_NULL);RT_ASSERT(thread->stat == RT_THREAD_INIT);/* set current priority to init priority */thread->current_priority = thread->init_priority;/* calculate priority attribute */
#if RT_THREAD_PRIORITY_MAX > 32thread->number      = thread->current_priority >> 3;            /* 5bit */thread->number_mask = 1L << thread->number;thread->high_mask   = 1L << (thread->current_priority & 0x07);  /* 3bit */
#elsethread->number_mask = 1L << thread->current_priority;
#endifRT_DEBUG_LOG(RT_DEBUG_THREAD, ("startup a thread:%s with priority:%d\n",thread->name, thread->init_priority));/* change thread stat */thread->stat = RT_THREAD_SUSPEND;/* then resume it */rt_thread_resume(thread);if (rt_thread_self() != RT_NULL)//这里是子线程是否立即被调度执行的关键{/* do a scheduling */rt_schedule();}return RT_EOK;
}/*** This function will return self thread object** @return the self thread object*/
rt_thread_t rt_thread_self(void)
{return rt_current_thread;//全局变量
}

所以,  pthread_create创建的子线程是否立即调度的关键在于全局变量rt_current_thread是否为NULL,我们分析系统启动代码:

void rtthread_startup(void)
{/* init board */rt_hw_board_init();#ifdef RT_USING_HEAP
#if STM32_EXT_SRAMrt_system_heap_init((void*)STM32_EXT_SRAM_BEGIN, (void*)STM32_EXT_SRAM_END);
#else
#ifdef __CC_ARMrt_system_heap_init((void*)&Image$$RW_IRAM1$$ZI$$Limit, (void*)STM32_SRAM_END);
#elif __ICCARM__rt_system_heap_init(__segment_end("HEAP"), (void*)STM32_SRAM_END);
#else/* init memory system */rt_system_heap_init((void*)&__bss_end, (void*)STM32_SRAM_END);
#endif
#endif  /* STM32_EXT_SRAM */
#endif /* RT_USING_HEAP *//* init scheduler system */rt_system_scheduler_init();/* initialize timer */rt_system_timer_init();/* init timer thread */rt_system_timer_thread_init();#ifdef HW_RTC/* init rtc */rt_hw_rtc_init();
#endif/* init application */rt_application_init();//应用程序线程初始化#ifndef FACTORY_TEST_MODEsetup_watchdog();
#endif/* init idle thread */rt_thread_idle_init();// 系统空闲线程初始化,/* start scheduler */rt_system_scheduler_start();//启动内核调度器/* never reach here */return ;
}int main(void)
{/* disable interrupt first */rt_hw_interrupt_disable();/* startup RT-Thread RTOS */rtthread_startup();return 0;
}/*** @ingroup SystemInit* This function will startup scheduler. It will select one thread* with the highest priority level, then switch to it.*/
void rt_system_scheduler_start(void)
{register struct rt_thread *to_thread;register rt_ubase_t highest_ready_priority;#if RT_THREAD_PRIORITY_MAX > 32register rt_ubase_t number;number = __rt_ffs(rt_thread_ready_priority_group) - 1;highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
#elsehighest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
#endif/* get switch to thread */to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,struct rt_thread,tlist);rt_current_thread = to_thread;//对全局变量进行设置, 相当于内核调度器的启动开关/* switch to new thread */rt_hw_context_switch_to((rt_uint32_t)&to_thread->sp);/* never come back */
}

所以, 在rt_system_scheduler_start调用之前, 通过pthread_create创建的线程是不会被调度的。rt_system_scheduler_start是内核调度器的启动开关,只有在内核调度器启动后,才可通过rt_schedule进行线程切换。

rt_system_scheduler_start 与 rt_schedule的区别可以参考文章https://blog.csdn.net/u011734326/article/details/90173478

 

 

 

 

这篇关于RT-Thread内核源码分析-线程栈结构分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1066671

相关文章

Java 线程池+分布式实现代码

《Java线程池+分布式实现代码》在Java开发中,池通过预先创建并管理一定数量的资源,避免频繁创建和销毁资源带来的性能开销,从而提高系统效率,:本文主要介绍Java线程池+分布式实现代码,需要... 目录1. 线程池1.1 自定义线程池实现1.1.1 线程池核心1.1.2 代码示例1.2 总结流程2. J

MyBatis Plus大数据量查询慢原因分析及解决

《MyBatisPlus大数据量查询慢原因分析及解决》大数据量查询慢常因全表扫描、分页不当、索引缺失、内存占用高及ORM开销,优化措施包括分页查询、流式读取、SQL优化、批处理、多数据源、结果集二次... 目录大数据量查询慢的常见原因优化方案高级方案配置调优监控与诊断总结大数据量查询慢的常见原因MyBAT

分析 Java Stream 的 peek使用实践与副作用处理方案

《分析JavaStream的peek使用实践与副作用处理方案》StreamAPI的peek操作是中间操作,用于观察元素但不终止流,其副作用风险包括线程安全、顺序混乱及性能问题,合理使用场景有限... 目录一、peek 操作的本质:有状态的中间操作二、副作用的定义与风险场景1. 并行流下的线程安全问题2. 顺

Java JUC并发集合详解之线程安全容器完全攻略

《JavaJUC并发集合详解之线程安全容器完全攻略》Java通过java.util.concurrent(JUC)包提供了一整套线程安全的并发容器,它们不仅是简单的同步包装,更是基于精妙并发算法构建... 目录一、为什么需要JUC并发集合?二、核心并发集合分类与详解三、选型指南:如何选择合适的并发容器?在多

MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决

《MyBatis/MyBatis-Plus同事务循环调用存储过程获取主键重复问题分析及解决》MyBatis默认开启一级缓存,同一事务中循环调用查询方法时会重复使用缓存数据,导致获取的序列主键值均为1,... 目录问题原因解决办法如果是存储过程总结问题myBATis有如下代码获取序列作为主键IdMappe

Java中最全最基础的IO流概述和简介案例分析

《Java中最全最基础的IO流概述和简介案例分析》JavaIO流用于程序与外部设备的数据交互,分为字节流(InputStream/OutputStream)和字符流(Reader/Writer),处理... 目录IO流简介IO是什么应用场景IO流的分类流的超类类型字节文件流应用简介核心API文件输出流应用文

java 恺撒加密/解密实现原理(附带源码)

《java恺撒加密/解密实现原理(附带源码)》本文介绍Java实现恺撒加密与解密,通过固定位移量对字母进行循环替换,保留大小写及非字母字符,由于其实现简单、易于理解,恺撒加密常被用作学习加密算法的入... 目录Java 恺撒加密/解密实现1. 项目背景与介绍2. 相关知识2.1 恺撒加密算法原理2.2 Ja

Nginx屏蔽服务器名称与版本信息方式(源码级修改)

《Nginx屏蔽服务器名称与版本信息方式(源码级修改)》本文详解如何通过源码修改Nginx1.25.4,移除Server响应头中的服务类型和版本信息,以增强安全性,需重新配置、编译、安装,升级时需重复... 目录一、背景与目的二、适用版本三、操作步骤修改源码文件四、后续操作提示五、注意事项六、总结一、背景与

Android实现图片浏览功能的示例详解(附带源码)

《Android实现图片浏览功能的示例详解(附带源码)》在许多应用中,都需要展示图片并支持用户进行浏览,本文主要为大家介绍了如何通过Android实现图片浏览功能,感兴趣的小伙伴可以跟随小编一起学习一... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

Vite 打包目录结构自定义配置小结

《Vite打包目录结构自定义配置小结》在Vite工程开发中,默认打包后的dist目录资源常集中在asset目录下,不利于资源管理,本文基于Rollup配置原理,本文就来介绍一下通过Vite配置自定义... 目录一、实现原理二、具体配置步骤1. 基础配置文件2. 配置说明(1)js 资源分离(2)非 JS 资