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

相关文章

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

usaco 1.3 Mixing Milk (结构体排序 qsort) and hdu 2020(sort)

到了这题学会了结构体排序 于是回去修改了 1.2 milking cows 的算法~ 结构体排序核心: 1.结构体定义 struct Milk{int price;int milks;}milk[5000]; 2.自定义的比较函数,若返回值为正,qsort 函数判定a>b ;为负,a<b;为0,a==b; int milkcmp(const void *va,c

Java ArrayList扩容机制 (源码解读)

结论:初始长度为10,若所需长度小于1.5倍原长度,则按照1.5倍扩容。若不够用则按照所需长度扩容。 一. 明确类内部重要变量含义         1:数组默认长度         2:这是一个共享的空数组实例,用于明确创建长度为0时的ArrayList ,比如通过 new ArrayList<>(0),ArrayList 内部的数组 elementData 会指向这个 EMPTY_EL

如何在Visual Studio中调试.NET源码

今天偶然在看别人代码时,发现在他的代码里使用了Any判断List<T>是否为空。 我一般的做法是先判断是否为null,再判断Count。 看了一下Count的源码如下: 1 [__DynamicallyInvokable]2 public int Count3 {4 [__DynamicallyInvokable]5 get

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、