启动文件startup_stm32f10x_hd.s
$Sub$$main
调用main()函数之前调用此函数,使用到了MDK编译器的特性,具体可见ARM® Compiler v5.06 for µVision®armlink User Guide。意思是调用main()之前,先调用$Sub$$main,在$Sub$$main里面再通过函数调用main()。
//components.c
int $Sub$$main(void)
{
rt_hw_interrupt_disable();
rtthread_startup();
return 0;
}
rtthread_startup() 函数
//components.c
int rtthread_startup(void)
{
rt_hw_interrupt_disable();
/* board level initalization
* NOTE: please initialize heap inside board initialization.
*/
rt_hw_board_init();
/* show RT-Thread version */
rt_show_version();
/* timer system initialization */
rt_system_timer_init();
/* scheduler system initialization */
rt_system_scheduler_init();
#ifdef RT_USING_SIGNALS
/* signal system initialization */
rt_system_signal_init();
#endif
/* create init_thread */
rt_application_init();
/* timer thread initialization */
rt_system_timer_thread_init();
/* idle thread initialization */
rt_thread_idle_init();
/* start scheduler */
rt_system_scheduler_start();
/* never reach here */
return 0;
}
rt_hw_interrupt_disable()在context_rvds.s文件里面,作用是屏蔽中断。
rt_hw_interrupt_disable PROC
EXPORT rt_hw_interrupt_disable
MRS r0, PRIMASK ;读PRIMASK值到r0
CPSID I ;关中断
BX LR
ENDP
PRIMASK是Cortex-M3的一个特殊功能寄存器,属于中断屏蔽寄存器组(PRIMASK, FAULTMASK, BASEPRI)。关闭所有可以屏蔽的异常,只有NMI和硬件fault可以响应。
特殊功能寄存器只能被专用的 MSR/MRS 指令访问,而且它们也没有与之相关联的访问地址。如:
CPSID I ;PRIMASK=1, ;关中断
CPSIE I ;PRIMASK=0, ;开中断
CPSID F ;FAULTMASK=1, ;关异常 '
Y]\,
CPSIE F ;FAULTMASK=0 ;开异常
最后跳转LR寄存器(即R14)指向的地址,LR寄存器存储了子程序调用的返回地址。
rt_hw_board_init():板级初始化,board.c文件中定义
void rt_hw_board_init()
{
//配置System Tick
SysTick_Config( SystemCoreClock / RT_TICK_PER_SECOND );
//外设硬件配置(用户)
LED_GPIO_Config();
/* Call components board initial (use INIT_BOARD_EXPORT()) */
#ifdef RT_USING_COMPONENTS_INIT
rt_components_board_init();
#endif
#if defined(RT_USING_CONSOLE) && defined(RT_USING_DEVICE)
rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
#endif
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
rt_system_heap_init(rt_heap_begin_get(), rt_heap_end_get());
#endif
}
//SysTick中断时间处理函数
void SysTick_Handler(void)
{
/* enter interrupt */
rt_interrupt_enter();
rt_tick_increase();//通知内核tick加1
/* leave interrupt */
rt_interrupt_leave();
}
//在board.c中
#if defined(RT_USING_USER_MAIN) && defined(RT_USING_HEAP)
#define RT_HEAP_SIZE 1024
static uint32_t rt_heap[RT_HEAP_SIZE]; // heap default size: 4K(1024 * 4)
RT_WEAK void *rt_heap_begin_get(void)
{
return rt_heap;
}
RT_WEAK void *rt_heap_end_get(void)
{
return rt_heap + RT_HEAP_SIZE;
}
#endif
rt_application_init();//此处创建主线程
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);//启动主线程
}
Q:为什么主线程的优先级设置为(RT_THREAD_PRIORITY_MAX / 3)?
A:
main_thread_entry()为主线程函数,其中在main_thread_entry()函数中调用main()函数。
void main_thread_entry(void *parameter)
{
extern int main(void);
//在MDK中,使用$Super$$main()区分main()和$Sub$$main(),调用$Super$$main()从而进入main()
extern int $Super$$main(void);
/* RT-Thread components initialization */
rt_components_init();//主要是一些组件的初始化及应用初始化
/* invoke system main function */
#if defined (__CC_ARM)
$Super$$main(); /* for ARMCC. */
#elif defined(__ICCARM__) || defined(__GNUC__)
main();
#endif
}
rt_components_init()的目的和实现参考RT-Thread 自动初始化详解。
/* board init routines will be called in board_init() function */
#define INIT_BOARD_EXPORT(fn) INIT_EXPORT(fn, "1")
/* pre/device/component/env/app init routines will be called in init_thread */
/* components pre-initialization (pure software initilization) */
#define INIT_PREV_EXPORT(fn) INIT_EXPORT(fn, "2")
/* device initialization */
#define INIT_DEVICE_EXPORT(fn) INIT_EXPORT(fn, "3")
/* components initialization (dfs, lwip, ...) */
#define INIT_COMPONENT_EXPORT(fn) INIT_EXPORT(fn, "4")
/* environment initialization (mount disk, ...) */
#define INIT_ENV_EXPORT(fn) INIT_EXPORT(fn, "5")
/* appliation initialization (rtgui application etc ...) */
#define INIT_APP_EXPORT(fn) INIT_EXPORT(fn, "6")
补充:section含义?
rt_system_scheduler_start();//调度具体实现之后再学习
线程重要属性
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 */
};
RT-Thread 线程具有独立的栈,当进行线程切换时,会将当前线程的上下文存在栈中,当线程要恢复运行时,再从栈中读取上下文信息,进行恢复。
线程栈还用来存放函数中的局部变量:函数中的局部变量从线程栈空间中申请;函数中局部变量初始时从寄存器中分配(ARM 架构),当这个函数再调用另一个函数时,这些局部变量将放入栈中。
如简单的顺序语句、do while() 或 for()循环等,此类线程不会循环或不会永久循环,可谓是 “一次性” 线程,一定会被执行完毕。在执行完毕后,线程将被系统自动删除。
RT-Thread 最大支持 256 个线程优先级 (0~255),数值越小的优先级越高,0 为最高优先级。在一些资源比较紧张的系统中,可以根据实际情况选择只支持 8 个或 32 个优先级的系统配置;对于 ARM Cortex-M 系列,普遍采用 32 个优先级。最低优先级默认分配给空闲线程使用,用户一般不使用。在系统中,当有比当前线程优先级更高的线程就绪时,当前线程将立刻被换出,高优先级线程抢占处理器运行。
每个线程都有时间片这个参数,但时间片仅对优先级相同的就绪态线程有效。系统对优先级相同的就绪态线程采用时间片轮转的调度方式进行调度时,时间片起到约束线程单次运行时长的作用,其单位是一个系统节拍(OS Tick)。
常用线程操作
线程创建(删除)/初始化(脱离)
动态线程是系统自动从动态内存堆上分配栈空间与线程句柄(初始化 heap 之后才能使用 create 创建动态线程),静态线程是由用户分配栈空间与线程句柄。
启动线程 rt_thread_startup();
挂起/恢复线程
挂起:线程挂起有三个原因:
线程自己阻塞自己,使其进入睡眠(挂起态)。以下三个函数作用一样。
rt_err_t rt_thread_sleep(rt_tick_t tick);
rt_err_t rt_thread_delay(rt_tick_t tick);
rt_err_t rt_thread_mdelay(rt_int32_t ms);
因为等待资源挂起
rt_sem_take()
rt_mb_recv()
被别的线程挂起。调用rt_thread_suspend()可以挂起别的函数,也可以自己挂起自己。如果线程A尝试调用rt_thread_suspend()挂起线程B的时候,如果线程B不处于就绪态,rt_thread_suspend()就会返回错误码。
注:一个线程尝试挂起另一个线程是一个非常危险的行为,因此RT-Thread对此函数有严格的使用限制:该函数只能使用来挂起当前线程(即自己挂起自己),不可以在线程A中尝试挂起线程B。而且在挂起线程自己后,需要立刻调用 rt_schedule() 函数进行手动的线程上下文切换。这是因为A线程在尝试挂起B线程时,A线程并不清楚B线程正在运行什么程序,一旦B线程正在使用例如互斥量、信号量等影响、阻塞其他线程(如C线程)的内核对象,如果此时其他线程也在等待这个内核对象,那么A线程尝试挂起B线程的操作将会引发其他线程(如C线程)的饥饿,严重危及系统的实时性。
if ((thread->stat & RT_THREAD_STAT_MASK) != RT_THREAD_READY)
{
RT_DEBUG_LOG(RT_DEBUG_THREAD, ("thread suspend: thread disorder, 0x%2x\n",
thread->stat));
return -RT_ERROR;
}
恢复 rt_err_t rt_thread_resume (rt_thread_t thread);
钩子函数
钩子函数感觉和回调函数差不多,在触发了某个事件或者进入某种状态,钩子函数就会被执行。
空闲钩子函数是空闲线程的钩子函数,如果设置了空闲钩子函数,就可以在系统执行空闲线程时,自动执行空闲钩子函数来做一些其他事情,比如系统指示灯。设置 / 删除空闲钩子的接口如下:
rt_err_t rt_thread_idle_sethook(void (*hook)(void));
rt_err_t rt_thread_idle_delhook(void (*hook)(void));
注:空闲线程是一个线程状态永远为就绪态的线程,因此设置的钩子函数必须保证空闲线程在任何时刻都不会处于挂起状态,例如 rt_thread_delay(),rt_sem_take() 等可能会导致线程挂起的函数都不能使用。并且,由于 malloc、free 等内存相关的函数内部使用了信号量作为临界区保护,因此在钩子函数内部也不允许调用此类函数!
void rt_scheduler_sethook(void (*hook)(struct rt_thread* from, struct rt_thread* to));
注:请仔细编写你的钩子函数,稍有不慎将很可能导致整个系统运行不正常(在这个钩子函数中,基本上不允许调用系统 API,更不应该导致当前运行的上下文挂起)。
线程同步概念
同步是指按预定的先后次序进行运行,线程同步是指多个线程通过特定的机制(如互斥量,事件对象,临界区)来控制线程之间的执行顺序,也可以说是在线程之间通过同步建立起执行顺序的关系,如果没有同步,那线程之间将是无序的。
线程的同步方式有很多种,其核心思想都是:在访问临界区的时候只允许一个 (或一类) 线程运行。进入 / 退出临界区的方式有很多种:
- 调用 rt_hw_interrupt_disable() 进入临界区,调用 rt_hw_interrupt_enable() 退出临界区;详见《中断管理》的全局中断开关内容。(注:屏蔽中断后,因为时基中断也被屏蔽,所以相当于系统事件停止,线程不可能发生调度)
- 调用 rt_enter_critical() 进入临界区,调用 rt_exit_critical() 退出临界区。
信号量(semaphore)
计数型信号量:信号量的值可以表示信号量对象的实例数目、资源数目。
数据结构
//rtdef.h
struct rt_semaphore
{
struct rt_ipc_object parent; /* 继承自 rt_ipc_object 类 */
rt_uint16_t value; /* 信号量的值 */
};
/* rt_sem_t 是指向 semaphore 结构体的指针类型 */
typedef struct rt_semaphore* rt_sem_t;
rt_ipc_object数据结构
struct rt_ipc_object
{
struct rt_object parent;/* 继承自 rt_object */
rt_list_t suspend_thread;/* 在此IPC对象阻塞的线程 */
};
rt_object数据结构
struct rt_object
{
char name[RT_NAME_MAX]; /* 内核对象的名字 */
rt_uint8_t type; /* 内核对象类型 */
rt_uint8_t flag; /* 内核对象标志 */
#ifdef RT_USING_MODULE
void *module_id;
#endif
rt_list_t list; /* 内核对象链表节点,记录双向链表的前后节点指针 */
};
typedef struct rt_object *rt_object_t;
内核对象类由一个枚举结构给出
enum rt_object_class_type
{
RT_Object_Class_Thread = 0, /**< The object is a thread. */
RT_Object_Class_Semaphore, /**< The object is a semaphore. */
RT_Object_Class_Mutex, /**< The object is a mutex. */
RT_Object_Class_Event, /**< The object is a event. */
RT_Object_Class_MailBox, /**< The object is a mail box. */
RT_Object_Class_MessageQueue, /**< The object is a message queue. */
RT_Object_Class_MemHeap, /**< The object is a memory heap */
RT_Object_Class_MemPool, /**< The object is a memory pool. */
RT_Object_Class_Device, /**< The object is a device */
RT_Object_Class_Timer, /**< The object is a timer. */
RT_Object_Class_Module, /**< The object is a module. */
RT_Object_Class_Unknown, /**< 此类型之前的内核对象类型是已经定义的 */
RT_Object_Class_Static = 0x80 /**< 说明这个内核对象是静态的,即其内存不是由系统在堆上分配的,删除内核对象时,不用回收内存。可以和其它类型一起使用 */
};
IPC(Inter-Process Communication,进程间同步与通信)对象的flag,flag的作用是决定在此IPC对象挂起的线程以什么方式排队获取此IPC对象。RT_IPC_FLAG_FIFO表示使用先来后到的方式,RT_IPC_FLAG_PRIO表示按照线程的优先级排队。
#define RT_IPC_FLAG_FIFO 0x00 /**< FIFOed IPC. @ref IPC. */
#define RT_IPC_FLAG_PRIO 0x01 /**< PRIOed IPC. @ref IPC. */
内核设置了容器来管理容器内核对象,对象容器在RTT以静态结构体数组的形式存在。结构体数组包含所定义的内核对象,数组初始化记录了内核对象的类型以及对象链表指针。当创建一个内核对象时,内核对象需要插入链表管理。
//object.c
static struct rt_object_information rt_object_container[RT_Object_Info_Unknown] =
{
/* initialize object container - thread */
{RT_Object_Class_Thread, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Thread), sizeof(struct rt_thread)},
#ifdef RT_USING_SEMAPHORE
/* initialize object container - semaphore */
{RT_Object_Class_Semaphore, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Semaphore), sizeof(struct rt_semaphore)},
#endif
#ifdef RT_USING_MUTEX
/* initialize object container - mutex */
{RT_Object_Class_Mutex, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Mutex), sizeof(struct rt_mutex)},
#endif
#ifdef RT_USING_EVENT
/* initialize object container - event */
{RT_Object_Class_Event, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Event), sizeof(struct rt_event)},
#endif
#ifdef RT_USING_MAILBOX
/* initialize object container - mailbox */
{RT_Object_Class_MailBox, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MailBox), sizeof(struct rt_mailbox)},
#endif
#ifdef RT_USING_MESSAGEQUEUE
/* initialize object container - message queue */
{RT_Object_Class_MessageQueue, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MessageQueue), sizeof(struct rt_messagequeue)},
#endif
#ifdef RT_USING_MEMHEAP
/* initialize object container - memory heap */
{RT_Object_Class_MemHeap, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MemHeap), sizeof(struct rt_memheap)},
#endif
#ifdef RT_USING_MEMPOOL
/* initialize object container - memory pool */
{RT_Object_Class_MemPool, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_MemPool), sizeof(struct rt_mempool)},
#endif
#ifdef RT_USING_DEVICE
/* initialize object container - device */
{RT_Object_Class_Device, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Device), sizeof(struct rt_device)},
#endif
/* initialize object container - timer */
{RT_Object_Class_Timer, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Timer), sizeof(struct rt_timer)},
#ifdef RT_USING_MODULE
/* initialize object container - module */
{RT_Object_Class_Module, _OBJ_CONTAINER_LIST_INIT(RT_Object_Info_Module), sizeof(struct rt_module)},
#endif
};
Q: RTT设置内核对象链表的意义?
A:可以通过宏rt_list_entry获取内核对象的地址,从而管理内核对象。
用户函数
二值信号量:二值信号量和计数型信号量在RTT中并没有本质区别,二值信号量初始值为1,通常用于临界区的互斥访问。
锁,单一的锁常应用于多个线程间对同一共享资源(即临界区)的访问。信号量在作为锁来使用时,通常应将信号量资源实例初始化成 1,代表系统默认有一个资源可用,因为信号量的值始终在 1 和 0 之间变动,所以这类锁也叫做二值信号量。如下图所示,当线程需要访问共享资源时,它需要先获得这个资源锁。当这个线程成功获得资源锁时,其他打算访问共享资源的线程会由于获取不到资源而挂起,这是因为其他线程在试图获取这个锁时,这个锁已经被锁上(信号量值是 0)。当获得信号量的线程处理完毕,退出临界区时,它将会释放信号量并把锁解开,而挂起在锁上的第一个等待线程将被唤醒从而获得临界区的访问权。
互斥量(mutex)
互斥量和二值信号量的功能相类似,但是使用二值信号量可能导致几个问题:
线程死锁
线程如果递归的持有信号量,最终会导致线程被挂起。而由于线程持有了信号量(所有的值),不能释放信号量,所以线程一直被挂起,形成死锁。
线程优先级翻转
优先级翻转的意思是:高优先级的线程得不到及时的运行,但是比其低优先级的线程却得以充分运行。
所谓优先级翻转,即当一个高优先级线程试图通过信号量机制访问共享资源时,如果该信号量已被一低优先级线程持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证。如下图所示:有优先级为 A、B 和 C 的三个线程,优先级 A> B > C。线程 A,B 处于挂起状态,等待某一事件触发,线程 C 正在运行,此时线程 C 开始使用某一共享资源 M。在使用过程中,线程 A 等待的事件到来,线程 A 转为就绪态,因为它比线程 C 优先级高,所以立即执行。但是当线程 A 要使用共享资源 M 时,由于其正在被线程 C 使用,因此线程 A 被挂起切换到线程 C 运行。如果此时线程 B 等待的事件到来,则线程 B 转为就绪态。由于线程 B 的优先级比线程 C 高,且线程B没有用到共享资源 M ,因此线程 B 开始运行,直到其运行完毕,线程 C 才开始运行。只有当线程 C 释放共享资源 M 后,线程 A 才得以执行。在这种情况下,优先级发生了翻转:线程 B 先于线程 A 运行。这样便不能保证高优先级线程的响应时间。
互斥量
支持递归访问,不会引起死锁
不会引起线程优先级翻转问题:RTT使用优先级继承协议解决优先级翻转问题。
优先级继承是通过在线程 A 尝试获取共享资源而被挂起的期间内,将线程 C 的优先级提升到线程 A 的优先级别,从而解决优先级翻转引起的问题。这样能够防止 C(间接地防止 A)被 B 抢占。优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,然后执行,而当这个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。
数据结构
struct rt_mutex
{
struct rt_ipc_object parent; /**< inherit from ipc_object */
rt_uint16_t value; /**< value of mutex */
rt_uint8_t original_priority; /**< priority of last thread hold the mutex */
rt_uint8_t hold; /**< numbers of thread hold the mutex */
struct rt_thread *owner; /**< current owner of mutex */
};
typedef struct rt_mutex *rt_mutex_t;
original_priority是持有互斥量线程的原优先级,设置此变量是因为RTT使用了优先级继承协议,当线程不再持有此互斥量时,线程的优先级需要保持原来的优先级。owner表示持有此互斥量的线程。hold用来支持互斥量的递归持有:如果持有此互斥量的线程再次尝试持有此互斥量,hold加1,value值不改变;如果持有互斥量的线程释放此互斥量,hold减1,当hold为0,此互斥量不在被此线程持有,互斥量被释放。
操作函数
事件集(event)
事件集主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。即一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件。这种多个事件的集合可以用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件,线程通过 “逻辑与” 或“逻辑或”将一个或多个事件关联起来,形成事件组合。事件的 “逻辑或” 也称为是独立型同步,指的是线程与任何事件之一发生同步;事件 “逻辑与” 也称为是关联型同步,指的是线程与若干事件都发生同步。
struct rt_event
{
struct rt_ipc_object parent; /* 继承自 ipc_object 类 */
/* 事件集合,每一 bit 表示 1 个事件,bit 位的值可以标记某事件是否发生 */
rt_uint32_t set;
};
/* rt_event_t 是指向事件结构体的指针类型 */
typedef struct rt_event* rt_event_t;
邮箱
消息队列
信号
RTT时钟
定时器:定时器的作用是定时执行任务。
RTT定时器:RTT提供的定时器有两种模式:HARD_TIMER以及SOFT_TIMER模式。两种模式下,定时器的最小时间单位都是一个tick,其区别在于定时器超时函数的执行环境。HARD_TIMER模式下,定时器超时函数在系统中断的上下文环境执行;SOFT_TIMER模式下,超时函数在timer线程的上下文环境执行。
HARD_TIMER:在int rtthread_startup(void) 中,调用rt_system_timer_init()初始化定时器链表,设置为HARD_TIMER模式的定时器启动后其节点将插入此链表。硬件定时器链表表头是一个静态数组:static rt_list_t rt_timer_list[RT_TIMER_SKIP_LIST_LEVEL]。定时器链表是一个跳表,用来加速链表的查询。如下图,head就是rt_timer_list,跳表的层数由RT_TIMER_SKIP_LIST_LEVEL设置,RT_TIMER_SKIP_LIST_LEVEL-1是最底层。检索时,从0层开始检索。
SOFT_TIMER:通过定义RT_USING_TIMER_SOFT宏来启用SOFT_TIMER模式,当启用此模式之后,系统在初始化阶段(rtthread_startup())通过rt_system_timer_thread_init()创建timer线程。SOFT_TIMER模式的定时器超时函数都是在timer线程中执行。
RTT定时器数据结构
struct rt_timer
{
struct rt_object parent; //继承内核对象
rt_list_t row[RT_TIMER_SKIP_LIST_LEVEL]; //链表节点,启动定时器时插入定时器链表
void (*timeout_func)(void *parameter); //超时函数
void *parameter; //超时函数参数
rt_tick_t init_tick; //设定超时时间
rt_tick_t timeout_tick; //加上目前的时间后的超时时间
};
typedef struct rt_timer *rt_timer_t;
init_tick和timeout_tick区分:假设目前的时钟节拍current_tick = 10,10后定时器函数执行,则init_tick = 10,timeout_tick = 20。
RTT定时器操作
rt_err_t rt_timer_start(rt_timer_t timer)
{
unsigned int row_lvl;
rt_list_t *timer_list;
register rt_base_t level;
rt_list_t *row_head[RT_TIMER_SKIP_LIST_LEVEL];
unsigned int tst_nr;
static unsigned int random_nr;
/* timer check */
RT_ASSERT(timer != RT_NULL);
//将定时器从定时器链表移除(如果定时器已经启动)
level = rt_hw_interrupt_disable();
/* remove timer from list */
_rt_timer_remove(timer);
/* change status of timer */
timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
rt_hw_interrupt_enable(level);
RT_OBJECT_HOOK_CALL(rt_object_take_hook, (&(timer->parent)));
//定时时间须小于RT_TICK_MAX / 2
RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2);
timer->timeout_tick = rt_tick_get() + timer->init_tick;
/* disable interrupt */
level = rt_hw_interrupt_disable();
#ifdef RT_USING_TIMER_SOFT
if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
{
/* insert timer to soft timer list */
timer_list = rt_soft_timer_list;
}
else
#endif
{
/* insert timer to system timer list */
timer_list = rt_timer_list;
}
//此段代码的作用是找出跳表各层中,定时器应该插入的节点
//从跳表的顶层开始检索
row_head[0] = &timer_list[0];
for (row_lvl = 0; row_lvl < RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
{
for (; row_head[row_lvl] != timer_list[row_lvl].prev;
row_head[row_lvl] = row_head[row_lvl]->next)
{
struct rt_timer *t;
rt_list_t *p = row_head[row_lvl]->next;
//rt_list_entry的作用是取得节点p对应的结构体的地址
//这里获取节点p所对应定时器控制块的地址
//思想是利用p地址减去(rt_timer *)(0)->row[row_lvl]的地址
//得到p所在结构体地址
t = rt_list_entry(p, struct rt_timer, row[row_lvl]);
//如果遇到一个相同的init_tick的定时器,则按照先来后到的原则
//将定时器插入此定时器之后
if ((t->timeout_tick - timer->timeout_tick) == 0)
{
continue;
}
else if ((t->timeout_tick - timer->timeout_tick) < RT_TICK_MAX / 2)
{
break;
}
}
//调表下一层起始节点
if (row_lvl != RT_TIMER_SKIP_LIST_LEVEL - 1)
row_head[row_lvl + 1] = row_head[row_lvl] + 1;
}
/* Interestingly, this super simple timer insert counter works very very
* well on distributing the list height uniformly. By means of "very very
* well", I mean it beats the randomness of timer->timeout_tick very easily
* (actually, the timeout_tick is not random and easy to be attacked). */
random_nr++;
tst_nr = random_nr;
//此段代码作用是将定时器插入跳表各层对应的位置
//将定时器插入最底层节点
rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - 1],
&(timer->row[RT_TIMER_SKIP_LIST_LEVEL - 1]));
for (row_lvl = 2; row_lvl <= RT_TIMER_SKIP_LIST_LEVEL; row_lvl++)
{
//这里意思大概是random_nr决定每层应不应该插入定时器
//RT_TIMER_SKIP_LIST_MASK若为0x03,则下一层每隔3个节点,即每新增4个节点,此层就插入一个节点
if (!(tst_nr & RT_TIMER_SKIP_LIST_MASK))
rt_list_insert_after(row_head[RT_TIMER_SKIP_LIST_LEVEL - row_lvl],
&(timer->row[RT_TIMER_SKIP_LIST_LEVEL - row_lvl]));
else
break;
/* Shift over the bits we have tested. Works well with 1 bit and 2
* bits. */
//以random_nr的1/2个bit表示一层的节点增加数,利用进位来决定一层是否应该增加节点
tst_nr >>= (RT_TIMER_SKIP_LIST_MASK + 1) >> 1;
}
timer->parent.flag |= RT_TIMER_FLAG_ACTIVATED;
/* enable interrupt */
rt_hw_interrupt_enable(level);
#ifdef RT_USING_TIMER_SOFT
if (timer->parent.flag & RT_TIMER_FLAG_SOFT_TIMER)
{
/* check whether timer thread is ready */
if ((timer_thread.stat & RT_THREAD_STAT_MASK) != RT_THREAD_READY)
{
/* resume timer thread to check soft timer */
rt_thread_resume(&timer_thread);
rt_schedule();
}
}
#endif
return -RT_EOK;
}
RTM_EXPORT(rt_timer_start);
启动定时器步骤:
rt_err_t rt_timer_stop(rt_timer_t timer)
{
register rt_base_t level;
/* timer check */
RT_ASSERT(timer != RT_NULL);
if (!(timer->parent.flag & RT_TIMER_FLAG_ACTIVATED))
return -RT_ERROR;
RT_OBJECT_HOOK_CALL(rt_object_put_hook, (&(timer->parent)));
/* disable interrupt */
level = rt_hw_interrupt_disable();
_rt_timer_remove(timer);
/* enable interrupt */
rt_hw_interrupt_enable(level);
/* change stat */
timer->parent.flag &= ~RT_TIMER_FLAG_ACTIVATED;
return RT_EOK;
}
RTM_EXPORT(rt_timer_stop);
停止定时器相比启动定时器简单:主要是将定时器节点从跳表中移除。Cortex-M3中断机制
寄存器
操作模式、特权级别
NVIC:嵌套向量中断控制器。
PendSV系统调用:一个系统异常,通常用来辅助操作系统进行上下文切换。
RTT中断接口:install\mask\unmask在 Cortex-M0/M3/M4 的移植分支中没有。
Cortex-M3中运行RTT,中断实现和裸机上差不多。当中断触发,进入中断函数,中断函数内会通知相关的线程执行相关任务(上半部分),当中断函数返回后,相关线程就会启动(底半部分)。
void rt_schedule(void)
{
rt_base_t level;
struct rt_thread *to_thread;
struct rt_thread *from_thread;
/* disable interrupt */
level = rt_hw_interrupt_disable();
/* check the scheduler is enabled or not */
if (rt_scheduler_lock_nest == 0)
{
register rt_ubase_t highest_ready_priority;
/* 从就绪优先级列表找出最高优先级 */
#if RT_THREAD_PRIORITY_MAX <= 32
highest_ready_priority = __rt_ffs(rt_thread_ready_priority_group) - 1;
#else
register 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;
#endif
/* get switch to thread */
to_thread = rt_list_entry(rt_thread_priority_table[highest_ready_priority].next,
struct rt_thread,
tlist);
/* if the destination thread is not the same as current thread */
if (to_thread != rt_current_thread)
{
rt_current_priority = (rt_uint8_t)highest_ready_priority;
from_thread = rt_current_thread;
rt_current_thread = to_thread;
RT_OBJECT_HOOK_CALL(rt_scheduler_hook, (from_thread, to_thread));
/* switch to new thread */
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER,
("[%d]switch to priority#%d "
"thread:%.*s(sp:0x%p), "
"from thread:%.*s(sp: 0x%p)\n",
rt_interrupt_nest, highest_ready_priority,
RT_NAME_MAX, to_thread->name, to_thread->sp,
RT_NAME_MAX, from_thread->name, from_thread->sp));
#ifdef RT_USING_OVERFLOW_CHECK
_rt_scheduler_stack_check(to_thread);
#endif
if (rt_interrupt_nest == 0)
{
extern void rt_thread_handle_sig(rt_bool_t clean_state);
rt_hw_context_switch((rt_uint32_t)&from_thread->sp,
(rt_uint32_t)&to_thread->sp);
/* enable interrupt */
rt_hw_interrupt_enable(level);
#ifdef RT_USING_SIGNALS
/* check signal status */
rt_thread_handle_sig(RT_TRUE);
#endif
}
else
{
RT_DEBUG_LOG(RT_DEBUG_SCHEDULER, ("switch in interrupt\n"));
rt_hw_context_switch_interrupt((rt_uint32_t)&from_thread->sp,
(rt_uint32_t)&to_thread->sp);
/* enable interrupt */
rt_hw_interrupt_enable(level);
}
}
else
{
/* enable interrupt */
rt_hw_interrupt_enable(level);
}
}
else
{
/* enable interrupt */
rt_hw_interrupt_enable(level);
}
}
number = __rt_ffs(rt_thread_ready_priority_group) - 1;
highest_ready_priority = (number << 3) + __rt_ffs(rt_thread_ready_table[number]) - 1;
移植相关接口:libcpu的抽象层
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sbuMYNIh-1657031522733)(img/RT-thread_2022-07-04-18-56-41.png)]
开关全局中断:两个函数需要配对使用
;context-rvds.S
;/*
; * rt_base_t rt_hw_interrupt_disable();
; */
rt_hw_interrupt_disable PROC
EXPORT rt_hw_interrupt_disable
MRS r0, PRIMASK ;返回PRIMASK寄存器的原始值
CPSID I ;关中断
BX LR
ENDP
;/*
; * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable PROC
EXPORT rt_hw_interrupt_enable
MSR PRIMASK, r0 ;恢复PRIMASK寄存器的原始值,即开中断
BX LR
ENDP
初始化线程的栈空间
//cpuport.c
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);
//地址向下圆整,须是8的倍数
stk = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);
//满递减堆栈
stk -= sizeof(struct stack_frame);
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;
}
切换上下文
;context-rvds.S
;/*
; * void rt_hw_context_switch(rt_uint32 from, rt_uint32 to);
; * r0 --> from
; * r1 --> to
; */
rt_hw_context_switch_interrupt
EXPORT rt_hw_context_switch_interrupt
rt_hw_context_switch PROC
EXPORT rt_hw_context_switch
; set rt_thread_switch_interrupt_flag to 1
LDR r2, =rt_thread_switch_interrupt_flag
LDR r3, [r2]
CMP r3, #1
BEQ _reswitch
MOV r3, #1
STR r3, [r2]
LDR r2, =rt_interrupt_from_thread ; set rt_interrupt_from_thread
STR r0, [r2]
_reswitch
LDR r2, =rt_interrupt_to_thread ; set rt_interrupt_to_thread
STR r1, [r2]
LDR r0, =NVIC_INT_CTRL ; trigger the PendSV exception (causes context switch)
LDR r1, =NVIC_PENDSVSET
STR r1, [r0]
BX LR
ENDP
PendSV中切换上下文
;context-rvds.S
; r0 --> switch from thread stack
; r1 --> switch to thread stack
; psr, pc, lr, r12, r3, r2, r1, r0 are pushed into [from] stack
PendSV_Handler PROC
EXPORT PendSV_Handler
; disable interrupt to protect context switch
MRS r2, PRIMASK
CPSID I
; get rt_thread_switch_interrupt_flag
LDR r0, =rt_thread_switch_interrupt_flag
LDR r1, [r0]
CBZ r1, pendsv_exit ; pendsv already handled
; clear rt_thread_switch_interrupt_flag to 0
MOV r1, #0x00
STR r1, [r0]
LDR r0, =rt_interrupt_from_thread
LDR r1, [r0]
CBZ r1, switch_to_thread ; skip register save at the first time
MRS r1, psp ; get from thread stack pointer
STMFD r1!, {r4 - r11} ; push r4 - r11 register
LDR r0, [r0]
STR r1, [r0] ; update from thread stack pointer
switch_to_thread
LDR r1, =rt_interrupt_to_thread
LDR r1, [r1]
LDR r1, [r1] ; load thread stack pointer
LDMFD r1!, {r4 - r11} ; pop r4 - r11 register
MSR psp, r1 ; update stack pointer
pendsv_exit
; restore interrupt
MSR PRIMASK, r2
ORR lr, lr, #0x04
BX lr
ENDP