RT-Thread 目录 [TOC]
内核简介 自动初始化机制 初始化顺序 宏接口 描述 1 INIT_BOARD_EXPORT(fn) 非常早期的初始化,此时调度器还未启动 2 INIT_PREV_EXPORT(fn) 主要是用于纯软件的初始化、没有太多依赖的函数 3 INIT_DEVICE_EXPORT(fn) 外设驱动初始化相关,比如网卡设备 4 INIT_COMPONENT_EXPORT(fn) 组件初始化,比如文件系统或者 LWIP 5 INIT_ENV_EXPORT(fn) 系统环境初始化,比如挂载文件系统 6 INIT_APP_EXPORT(fn) 应用初始化,比如 GUI 应用
内核对象管理架构 派生和继承
内核rtconfig.h配置 (1)RT-Thread 内核部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #define RT_NAME_MAX 8 #define RT_ALIGN_SIZE 4 #define RT_THREAD_PRIORITY_MAX 32 #define RT_TICK_PER_SECOND 100 #define RT_USING_OVERFLOW_CHECK #define RT_DEBUG #define RT_DEBUG_INIT 0 #define RT_DEBUG_THREAD 0 #define RT_USING_HOOK #define IDLE_THREAD_STACK_SIZE 256
(2)线程间同步与通信部分,该部分会使用到的对象有信号量、互斥量、事件、邮箱、消息队列、信号等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #define RT_USING_SEMAPHORE #define RT_USING_MUTEX #define RT_USING_EVENT #define RT_USING_MAILBOX #define RT_USING_MESSAGEQUEUE #define RT_USING_SIGNALS
(3)内存管理部分
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #define RT_USING_MEMPOOL #define RT_USING_MEMHEAP #define RT_USING_SMALL_MEM #define RT_USING_HEAP
(4)内核设备对象
1 2 3 4 5 6 7 8 9 #define RT_USING_DEVICE #define RT_USING_CONSOLE #define RT_CONSOLEBUF_SIZE 128 #define RT_CONSOLE_DEVICE_NAME "uart1"
(5)自动初始化方式
1 2 3 4 5 6 7 #define RT_USING_COMPONENTS_INIT #define RT_USING_USER_MAIN #define RT_MAIN_THREAD_STACK_SIZE 2048
(6)FinSH
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #define RT_USING_FINSH #define FINSH_THREAD_NAME "tshell" #define FINSH_USING_HISTORY #define FINSH_HISTORY_LINES 5 #define FINSH_USING_SYMTAB #define FINSH_THREAD_PRIORITY 20 #define FINSH_THREAD_STACK_SIZE 4096 #define FINSH_CMD_SIZE 80 #define FINSH_USING_MSH #define FINSH_USING_MSH_DEFAULT #define FINSH_USING_MSH_ONLY
(7)关于 MCU
1 2 3 4 5 6 7 8 #define STM32F103ZE #define RT_HSE_VALUE 8000000 #define RT_USING_UART1
常见宏定义说明 1)rt_inline,定义如下,static 关键字的作用是令函数只能在当前的文件中使用;inline 表示内联,用 static 修饰后在调用函数时会建议编译器进行内联展开。
1 #define rt_inline static __inline
2)RT_USED,定义如下,该宏的作用是向编译器说明这段代码有用,即使函数中没有调用也要保留编译。例如 RT-Thread 自动初始化功能使用了自定义的段,使用 RT_USED 会将自定义的代码段保留。
1 #define RT_USED __attribute__((used))
3)RT_UNUSED,定义如下,表示函数或变量可能不使用,这个属性可以避免编译器产生警告信息。
1 #define RT_UNUSED __attribute__((unused))
4)RT_WEAK,定义如下,常用于定义函数,编译器在链接函数时会优先链接没有该关键字前缀的函数,如果找不到则再链接由 weak 修饰的函数。
5)ALIGN(n),定义如下,作用是在给某对象分配地址空间时,将其存放的地址按照 n 字节对齐,这里 n 可取 2 的幂次方。字节对齐的作用不仅是便于 CPU 快速访问,同时合理的利用字节对齐可以有效地节省存储空间。
1 #define ALIGN(n) __attribute__((aligned(n)))
6)RT_ALIGN(size,align),定义如下,作用是将 size 提升为 align 定义的整数的倍数,例如,RT_ALIGN(13,4) 将返回 16。
1 #define RT_ALIGN(size, align) (((size) + (align) - 1) & ~((align) - 1))
线程管理 线程状态 状态 描述 初始状态 当线程刚开始创建还没开始运行时就处于初始状态; 在初始状态下,线程不参与调度。 此状态在 RT-Thread 中的宏定义为 RT_THREAD_INIT 就绪状态 在就绪状态下,线程按照优先级排队,等待被执行; 一旦当前线程运行完毕让出处理器,操作系统会马上寻找最高优先级的就绪态线程运行。 此状态在 RT-Thread 中的宏定义为 RT_THREAD_READY 运行状态 线程当前正在运行。在单核系统中,只有 rt_thread_self() 函数返回的线程处于运行状态; 在多核系统中,可能就不止这一个线程处于运行状态。 此状态在 RT-Thread 中的宏定义为 RT_THREAD_RUNNING 挂起状态 也称阻塞态。它可能因为资源不可用而挂起等待,或线程主动延时一段时间而挂起。 在挂起状态下,线程不参与调度。 此状态在 RT-Thread 中的宏定义为 RT_THREAD_SUSPEND 关闭状态 当线程运行结束时将处于关闭状态。 关闭状态的线程不参与线程的调度。 此状态在 RT-Thread 中的宏定义为 RT_THREAD_CLOSE
线程状态的切换
线程的管理API
创建删除-create 系统会从动态堆内存中分配一个线程句柄以及按照参数中指定的栈大小从动态堆内存中分配相应的空间。分配出来的栈空间是按照 rtconfig.h 中配置的 RT_ALIGN_SIZE 方式对齐
1 2 3 4 5 6 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) ;
参数 描述 name 线程的名称; 线程名称的最大长度由 rtconfig.h 中的宏 RT_NAME_MAX 指定,多余部分会被自动截掉 entry 线程入口函数 parameter 线程入口函数参数 stack_size 线程栈大小,单位是字节 priority 线程的优先级。 优先级范围根据系统配置情况(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义),如果支持的是 256 级优先级,那么范围是从 0~255,数值越小优先级越高,0 代表最高优先级 tick 线程的时间片大小。 时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行 返回 —— thread 线程创建成功,返回线程句柄 RT_NULL 线程创建失败
1 rt_err_t rt_thread_delete (rt_thread_t thread) ;
参数 描述 thread 要删除的线程句柄 返回 —— RT_EOK 删除线程成功 -RT_ERROR 删除线程失败
初始化和脱离-init 1 2 3 4 5 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) ;
静态线程的线程句柄(或者说线程控制块指针)、线程栈由用户提供。静态线程是指线程控制块、线程运行栈一般都设置为全局变量,在编译时就被确定、被分配处理,内核不负责动态分配内存空间。需要注意的是,用户提供的栈首地址需做系统对齐 (例如 ARM 上需要做 4 字节对齐)。
参数 描述 thread 线程句柄。线程句柄由用户提供出来,并指向对应的线程控制块内存地址 name 线程的名称;线程名称的最大长度由 rtconfig.h 中定义的 RT_NAME_MAX 宏指定,多余部分会被自动截掉 entry 线程入口函数 parameter 线程入口函数参数 stack_start 线程栈起始地址 stack_size 线程栈大小,单位是字节。在大多数系统中需要做栈空间地址对齐(例如 ARM 体系结构中需要向 4 字节地址对齐) priority 线程的优先级。优先级范围根据系统配置情况(rtconfig.h 中的 RT_THREAD_PRIORITY_MAX 宏定义),如果支持的是 256 级优先级,那么范围是从 0 ~ 255,数值越小优先级越高,0 代表最高优先级 tick 线程的时间片大小。时间片(tick)的单位是操作系统的时钟节拍。当系统中存在相同优先级线程时,这个参数指定线程一次调度能够运行的最大时间长度。这个时间片运行结束时,调度器自动选择下一个就绪态的同优先级线程进行运行 返回 —— RT_EOK 线程创建成功 -RT_ERROR 线程创建失败
1 rt_err_t rt_thread_detach (rt_thread_t thread) ;
参数 描述 thread 线程句柄,它应该是由 rt_thread_init 进行初始化的线程句柄。 返回 —— RT_EOK 线程脱离成功 -RT_ERROR 线程脱离失败
线程启动-startup 1 rt_err_t rt_thread_startup (rt_thread_t thread) ;
参数 描述 thread 线程句柄 返回 —— RT_EOK 线程启动成功 -RT_ERROR 线程起动失败
获取当前线程-self 在程序的运行过程中,相同的一段代码可能会被多个线程执行,在执行的时候可以通过下面的函数接口获得当前执行的线程句柄:
1 rt_thread_t rt_thread_self (void ) ;
返回 描述 thread 当前运行的线程句柄 RT_NULL 失败,调度器还未启动
使线程让出处理器资源-yield (如果当前优先级只有这一个线程,则这个线程继续执行,不进行上下文切换动作)。
1 rt_err_t rt_thread_yield (void ) ;
rt_thread_yield() 和 rt_schedule() 比较相像,有相同优先级的其他就绪态线程存在时其行为有所差异 。
使线程睡眠(延时)-sleep 1 2 3 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) ;
参数 描述 tick/ms 线程睡眠的时间: sleep/delay 的传入参数 tick 以 1 个 OS Tick 为单位 ; mdelay 的传入参数 ms 以 1ms 为单位; 返回 —— RT_EOK 操作成功
挂起和恢复线程-suspend 通常不应该使用这个函数来挂起线程本身,如果确实需要采用 rt_thread_suspend() 函数挂起当前任务,需要在调用 rt_thread_suspend() 函数后立刻调用 rt_schedule() 函数进行手动的线程上下文切换。用户只需要了解该接口的作用,不推荐使用该接口。
1 rt_err_t rt_thread_suspend (rt_thread_t thread) ;
参数 描述 thread 线程句柄 返回 —— RT_EOK 线程挂起成功 -RT_ERROR 线程挂起失败,因为该线程的状态并不是就绪状态
恢复线程就是让挂起的线程重新进入就绪状态,并将线程放入系统的就绪队列中;
1 rt_err_t rt_thread_resume (rt_thread_t thread) ;
参数 描述 thread 线程句柄 返回 —— RT_EOK 线程恢复成功 -RT_ERROR 线程恢复失败,因为该个线程的状态并不是 RT_THREAD_SUSPEND 状态
控制线程-control 1 rt_err_t rt_thread_control (rt_thread_t thread, rt_uint8_t cmd, void * arg) ;
线程控制接口 rt_thread_control() 的参数和返回值见下表:
函数参数 描述 thread 线程句柄 cmd 指示控制命令 arg 控制参数 返回 —— RT_EOK 控制执行正确 -RT_ERROR 失败
指示控制命令 cmd 当前支持的命令包括:
RT_THREAD_CTRL_CHANGE_PRIORITY:动态更改线程的优先级;
RT_THREAD_CTRL_STARTUP:开始运行一个线程,等同于 rt_thread_startup() 函数调用;
RT_THREAD_CTRL_CLOSE:关闭一个线程,等同于 rt_thread_delete() 函数调用。
设置和删除空闲钩子-idle 空闲钩子函数是空闲线程的钩子函数,如果设置了空闲钩子函数,就可以在系统执行空闲线程时,自动执行空闲钩子函数来做一些其他事情,比如系统指示灯。钩子函数必须保证空闲线程在任何时刻都不会处于挂起状态。
1 2 rt_err_t rt_thread_idle_sethook (void (*hook)(void )) ;rt_err_t rt_thread_idle_delhook (void (*hook)(void )) ;
设置空闲钩子函数 rt_thread_idle_sethook()
函数参数 描述 hook 设置的钩子函数 返回 —— RT_EOK 设置成功 -RT_EFULL 设置失败
删除空闲钩子函数 rt_thread_idle_delhook()
函数参数 描述 hook 删除的钩子函数 返回 —— RT_EOK 删除成功 -RT_ENOSYS 删除失败
设置调度器钩子-scheduler 有时用户可能会想知道在一个时刻发生了什么样的线程切换,可以通过调用下面的函数接口设置一个相应的钩子函数。在系统线程切换时,这个钩子函数将被调用:请仔细编写你的钩子函数,稍有不慎将很可能导致整个系统运行不正常(在这个钩子函数中,基本上不允许调用系统 API,更不应该导致当前运行的上下文挂起)。
1 void rt_scheduler_sethook (void (*hook)(struct rt_thread* from, struct rt_thread* to)) ;
钩子函数 hook() 的声明如下:
1 void hook (struct rt_thread* from, struct rt_thread* to) ;
函数参数 描述 from 表示系统所要切换出的线程控制块指针 to 表示系统所要切换到的线程控制块指针
API应用示例 线程 2 计数到一定值会执行完毕,线程 2 被系统自动删除,计数停止。线程 1 一直打印计数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 #include <rtthread.h> #define THREAD_PRIORITY 25 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 5 static rt_thread_t tid1 = RT_NULL;static void thread1_entry (void *parameter) { rt_uint32_t count = 0 ; while (1 ) { rt_kprintf("thread1 count: %d\n" , count ++); rt_thread_mdelay(500 ); } } ALIGN(RT_ALIGN_SIZE)static char thread2_stack[1024 ];static struct rt_thread thread2 ;static void thread2_entry (void *param) { rt_uint32_t count = 0 ; for (count = 0 ; count < 10 ; count++) { rt_kprintf("thread2 count: %d\n" , count); } rt_kprintf("thread2 exit\n" ); }int thread_sample (void ) { tid1 = rt_thread_create("thread1" , thread1_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (tid1 != RT_NULL) rt_thread_startup(tid1); rt_thread_init(&thread2, "thread2" , thread2_entry, RT_NULL, &thread2_stack[0 ], sizeof (thread2_stack), THREAD_PRIORITY - 1 , THREAD_TIMESLICE); rt_thread_startup(&thread2); return 0 ; } MSH_CMD_EXPORT(thread_sample, thread sample);
仿真运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 \ | / - RT - Thread Operating System / | \ 3.1 .0 build Aug 24 2018 2006 - 2018 Copyright by rt-thread team msh >thread_sample msh >thread2 count: 0 thread2 count: 1 thread2 count: 2 thread2 count: 3 thread2 count: 4 thread2 count: 5 thread2 count: 6 thread2 count: 7 thread2 count: 8 thread2 count: 9 thread2 exit thread1 count: 0 thread1 count: 1 thread1 count: 2 thread1 count: 3 …
笔记 若某线程运行完毕,系统将自动删除线程:自动执行 rt_thread_exit() 函数,先将该线程从系统就绪队列中删除,再将该线程的状态更改为关闭状态,不再参与系统调度,然后挂入 rt_thread_defunct 僵尸队列(资源未回收、处于关闭状态的线程队列)中,最后空闲线程会回收被删除线程的资源。 时钟管理 获取时钟节拍API 由于全局变量 rt_tick 在每经过一个时钟节拍时,值就会加 1,通过调用 rt_tick_get 会返回当前 rt_tick 的值,即可以获取到当前的时钟节拍值。此接口可用于记录系统的运行时间长短,或者测量某任务运行的时间。
1 rt_tick_t rt_tick_get (void ) ;
软件定时器管理 RT-Thread 操作系统提供软件实现的定时器,以时钟节拍(OS Tick)的时间长度为单位,即定时数值必须是 OS Tick 的整数倍。它构建在硬件定时器基础之上,使系统能够提供不受数目限制的定时器服务。
RT-Thread 的定时器提供两类定时器机制:
第一类是单次触发定时器,这类定时器在启动后只会触发一次定时器事件,然后定时器自动停止。 第二类是周期触发定时器,这类定时器会周期性的触发定时器事件,直到用户手动的停止,否则将永远持续执行下去。 另外,根据超时函数执行时所处的上下文环境,RT-Thread 的定时器可以分为 HARD_TIMER 模式与 SOFT_TIMER 模式,如下图。使用RT_TIMER_FLAG_HARD_TIMER 和 **RT_TIMER_FLAG_SOFT_TIMER ** 来决定使用的模式
系统新创建并激活的定时器都会按照以超时时间排序的方式插入到 rt_timer_list 链表中。
rt_tick(当前系统tick) 从 20 增长到 70,与 Timer1 的 timeout 值相等,这时会触发与 Timer1 定时器相关联的超时函数,同时将 Timer1 从 rt_timer_list 链表上删除。
如果系统当前定时器状态在 10 个 tick 以后(rt_tick=30)有一个任务新创建了一个 tick 值为 300 的 Timer4 定时器,由于 Timer4 定时器的 timeout=rt_tick+300=330, 因此它将被插入到 Timer2 和 Timer3 定时器中间如下图,所示:
定时器API
在系统启动时需要初始化定时器管理系统。可以通过下面的函数接口完成:
1 void rt_system_timer_init (void ) ;
如果需要使用 SOFT_TIMER,则系统初始化时,应该调用下面这个函数接口:
1 void rt_system_timer_thread_init (void ) ;
创建和删除-create 1 2 3 4 5 rt_timer_t rt_timer_create (const char * name, void (*timeout)(void * parameter), void * parameter, rt_tick_t time, rt_uint8_t flag) ;
参数 描述 name 定时器的名称 void (timeout) (void parameter) 定时器超时函数指针(当定时器超时时,系统会调用这个函数) parameter 定时器超时函数的入口参数(当定时器超时时,调用超时回调函数会把这个参数做为入口参数传递给超时函数) time 定时器的超时时间,单位是时钟节拍 flag 定时器创建时的参数,支持的值包括单次定时、周期定时、硬件定时器、软件定时器等(可以用 “或” 关系取多个值) 返回 —— RT_NULL 创建失败(通常会由于系统内存不够用而返回 RT_NULL) 定时器的句柄 定时器创建成功
如下2 组值可以以 “或” 逻辑的方式赋给 flag。
1 2 3 4 5 #define RT_TIMER_FLAG_ONE_SHOT 0x0 #define RT_TIMER_FLAG_PERIODIC 0x2 #define RT_TIMER_FLAG_HARD_TIMER 0x0 #define RT_TIMER_FLAG_SOFT_TIMER 0x4
1 rt_err_t rt_timer_delete (rt_timer_t timer) ;
参数 描述 timer 定时器句柄,指向要删除的定时器 返回 —— RT_EOK 删除成功(如果参数 timer 句柄是一个 RT_NULL,将会导致一个 ASSERT 断言)
初始化和脱离定时器-init 1 2 3 4 5 void rt_timer_init (rt_timer_t timer, const char * name, void (*timeout)(void * parameter), void * parameter, rt_tick_t time, rt_uint8_t flag) ;
参数 描述 timer 定时器句柄,指向要初始化的定时器控制块 name 定时器的名称 void (timeout) (void parameter) 定时器超时函数指针(当定时器超时时,系统会调用这个函数) parameter 定时器超时函数的入口参数(当定时器超时时,调用超时回调函数会把这个参数做为入口参数传递给超时函数) time 定时器的超时时间,单位是时钟节拍 flag 定时器创建时的参数,支持的值包括单次定时、周期定时、硬件定时器、软件定时器(可以用 “或” 关系取多个值),详见上面 创建和删除
1 rt_err_t rt_timer_detach (rt_timer_t timer) ;
参数 描述 timer 定时器句柄,指向要脱离的定时器控制块 返回 —— RT_EOK 脱离成功
启动和停止定时器-start 1 rt_err_t rt_timer_start (rt_timer_t timer) ;
调用接口后,定时器的状态将更改为激活状态(RT_TIMER_FLAG_ACTIVATED),并按照超时顺序插入到 rt_timer_list 队列链表中。
参数 描述 timer 定时器句柄,指向要启动的定时器控制块 返回 —— RT_EOK 启动成功
1 rt_err_t rt_timer_stop (rt_timer_t timer) ;
当一个(周期性)定时器超时时,可以调用这个函数接口停止这个(周期性)定时器本身。
参数 描述 timer 定时器句柄,指向要停止的定时器控制块 返回 —— RT_EOK 成功停止定时器 - RT_ERROR timer 已经处于停止状态
控制定时器-control 1 rt_err_t rt_timer_control (rt_timer_t timer, rt_uint8_t cmd, void * arg) ;
参数 描述 timer 定时器句柄,指向要停止的定时器控制块 cmd 用于控制定时器的命令,当前支持四个命令,分别是设置定时时间,查看定时时间,设置单次触发,设置周期触发 arg 与 cmd 相对应的控制命令参数 比如,cmd 为设定超时时间时,就可以将超时时间参数通过 arg 进行设定 返回 —— RT_EOK 成功
函数参数 cmd 支持的命令:
1 2 3 4 #define RT_TIMER_CTRL_SET_TIME 0x0 #define RT_TIMER_CTRL_GET_TIME 0x1 #define RT_TIMER_CTRL_SET_ONESHOT 0x2 #define RT_TIMER_CTRL_SET_PERIODIC 0x3
API应用示例 这是一个创建定时器的例子,这个例程会创建两个动态定时器,一个是单次定时,一个是周期性定时并让周期定时器运行一段时间后停止运行,如下所示:周期性定时器 1 的超时函数,每 10 个 OS Tick 运行 1 次,共运行 10 次(10 次后调用 rt_timer_stop 使定时器 1 停止运行);单次定时器 2 的超时函数在第 30 个 OS Tick 时运行一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 #include <rtthread.h> static rt_timer_t timer1;static rt_timer_t timer2;static int cnt = 0 ;static void timeout1 (void *parameter) { rt_kprintf("periodic timer is timeout %d\n" , cnt); if (cnt++>= 9 ) { rt_timer_stop(timer1); rt_kprintf("periodic timer was stopped! \n" ); } }static void timeout2 (void *parameter) { rt_kprintf("one shot timer is timeout\n" ); }int timer_sample (void ) { timer1 = rt_timer_create("timer1" , timeout1, RT_NULL, 10 , RT_TIMER_FLAG_PERIODIC); if (timer1 != RT_NULL) rt_timer_start(timer1); timer2 = rt_timer_create("timer2" , timeout2, RT_NULL, 30 , RT_TIMER_FLAG_ONE_SHOT); if (timer2 != RT_NULL) rt_timer_start(timer2); return 0 ; } MSH_CMD_EXPORT(timer_sample, timer sample);
仿真运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 \ | / - RT - Thread Operating System / | \ 3.1 .0 build Aug 24 2018 2006 - 2018 Copyright by rt-thread team msh >timer_sample msh >periodic timer is timeout 0 periodic timer is timeout 1 one shot timer is timeout periodic timer is timeout 2 periodic timer is timeout 3 periodic timer is timeout 4 periodic timer is timeout 5 periodic timer is timeout 6 periodic timer is timeout 7 periodic timer is timeout 8 periodic timer is timeout 9 periodic timer was stopped!
初始化定时器的例子与创建定时器的例子类似,这个程序会初始化 2 个静态定时器,一个是单次定时,一个是周期性的定时,如下代码所示:周期性定时器 1 的超时函数,每 10 个 OS Tick 运行 1 次,共运行 10 次(10 次后调用 rt_timer_stop 使定时器 1 停止运行);单次定时器 2 的超时函数在第 30 个 OS Tick 时运行一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <rtthread.h> static struct rt_timer timer1 ;static struct rt_timer timer2 ;static int cnt = 0 ;static void timeout1 (void * parameter) { rt_kprintf("periodic timer is timeout\n" ); if (cnt++>= 9 ) { rt_timer_stop(&timer1); } }static void timeout2 (void * parameter) { rt_kprintf("one shot timer is timeout\n" ); }int timer_static_sample (void ) { rt_timer_init(&timer1, "timer1" , timeout1, RT_NULL, 10 , RT_TIMER_FLAG_PERIODIC); rt_timer_init(&timer2, "timer2" , timeout2, RT_NULL, 30 , RT_TIMER_FLAG_ONE_SHOT); rt_timer_start(&timer1); rt_timer_start(&timer2); return 0 ; } MSH_CMD_EXPORT(timer_static_sample, timer_static sample);
仿真运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 \ | / - RT - Thread Operating System / | \ 3.1 .0 build Aug 24 2018 2006 - 2018 Copyright by rt-thread team msh >timer_static_sample msh >periodic timer is timeout periodic timer is timeout one shot timer is timeout periodic timer is timeout periodic timer is timeout periodic timer is timeout periodic timer is timeout periodic timer is timeout periodic timer is timeout periodic timer is timeout periodic timer is timeout
低于一个Tick的延时 入口参数 us 指示出需要延时的微秒数目,这个函数只能支持低于 1 OS Tick 的延时,否则 SysTick 会出现溢出而不能够获得指定的延时时间。
1 2 3 4 5 6 7 8 9 10 11 #include <board.h> void rt_hw_us_delay (rt_uint32_t us) { rt_uint32_t delta; us = us * (SysTick->LOAD/(1000000 /RT_TICK_PER_SECOND)); delta = SysTick->VAL; while (delta - SysTick->VAL< us); }
笔记 时钟节拍是特定的周期性中断,这个中断可以看做是系统心跳,中断之间的时间间隔取决于不同的应用,一般是 1ms–100ms,时钟节拍率越快,系统的额外开销就越大,从系统启动开始计数的时钟节拍数称为系统时间。 线程间同步 线程的同步方式有很多种,其核心思想都是:在访问临界区的时候只允许一个 (或一类) 线程运行。 进入 / 退出临界区的方式有很多种:
1)调用 rt_hw_interrupt_disable() 进入临界区,调用 rt_hw_interrupt_enable() 退出临界区;详见《中断管理》的全局中断开关内容。
2)调用 rt_enter_critical() 进入临界区,调用 rt_exit_critical() 退出临界区。
信号量Semaphore 信号量的值对应了信号量对象的实例数目、资源数目,假如信号量值为 5,则表示共有 5 个信号量实例(资源)可以被使用,当信号量实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)。
可以形成锁 、同步 、资源计数 等关系,也能方便的用于线程与线程 、中断与线程 间的同步中。
Semaphore API
创建和删除信号量-create 1 2 3 rt_sem_t rt_sem_create (const char *name, rt_uint32_t value, rt_uint8_t flag) ;
当选择 RT_IPC_FLAG_FIFO(先进先出)方式时,那么等待线程队列将按照先进先出的方式排队,先进入的线程将先获得等待的信号量;
当选择 RT_IPC_FLAG_PRIO(优先级等待)方式时,等待线程队列将按照优先级进行排队,优先级高的等待线程将先获得等待的信号量。
参数 描述 name 信号量名称 value 信号量初始值 flag 信号量标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO 返回 —— RT_NULL 创建失败 信号量的控制块指针 创建成功
1 rt_err_t rt_sem_delete (rt_sem_t sem) ;
如果删除该信号量时,有线程正在等待该信号量,那么删除操作会先唤醒等待在该信号量上的线程(等待线程的返回值是 - RT_ERROR),然后再释放信号量的内存资源。
参数 描述 sem rt_sem_create() 创建的信号量对象 返回 —— RT_EOK 删除成功
初始化和脱离信号量-init 1 2 3 4 rt_err_t rt_sem_init (rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag)
flag见上↑
参数 描述 sem 信号量对象的句柄 name 信号量名称 value 信号量初始值 flag 信号量标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO 返回 —— RT_EOK 初始化成功
1 rt_err_t rt_sem_detach (rt_sem_t sem) ;
使用该函数后,内核先唤醒所有挂在该信号量等待队列上的线程,然后将该信号量从内核对象管理器中脱离。原来挂起在信号量上的等待线程将获得 - RT_ERROR 的返回值。
参数 描述 sem 信号量对象的句柄 返回 —— RT_EOK 脱离成功
获取信号量-take 1 rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time) ;
参数 描述 sem 信号量对象的句柄 time 指定的等待时间,单位是操作系统时钟节拍(OS Tick),或者RT_WAITING_FOREVER永远等待 返回 —— RT_EOK 成功获得信号量 -RT_ETIMEOUT 超时依然未获得信号量 -RT_ERROR 其他错误
无等待获取信号量-trytake 当用户不想在申请的信号量上挂起线程进行等待时,可以使用无等待方式获取信号量
1 rt_err_t rt_sem_trytake (rt_sem_t sem) ;
这个函数与 rt_sem_take(sem, 0) 的作用相同,即当线程申请的信号量资源实例不可用的时候,它不会等待在该信号量上,而是直接返回 - RT_ETIMEOUT。
参数 描述 sem 信号量对象的句柄 返回 —— RT_EOK 成功获得信号量 -RT_ETIMEOUT 获取失败
释放信号量-release 1 rt_err_t rt_sem_release (rt_sem_t sem) ;
例如当信号量的值等于零时,并且有线程等待这个信号量时,释放信号量将唤醒等待在该信号量线程队列中的第一个线程,由它获取信号量;否则将把信号量的值加 1。
参数 描述 sem 信号量对象的句柄 返回 —— RT_EOK 成功释放信号量
信号量应用示例 动态信号量的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 #include <rtthread.h> #define THREAD_PRIORITY 25 #define THREAD_TIMESLICE 5 static rt_sem_t dynamic_sem = RT_NULL; ALIGN(RT_ALIGN_SIZE)static char thread1_stack[1024 ];static struct rt_thread thread1 ;static void rt_thread1_entry (void *parameter) { static rt_uint8_t count = 0 ; while (1 ) { if (count <= 100 ) { count++; } else return ; if (0 == (count % 10 )) { rt_kprintf("t1 release a dynamic semaphore.\n" ); rt_sem_release(dynamic_sem); } } } ALIGN(RT_ALIGN_SIZE)static char thread2_stack[1024 ];static struct rt_thread thread2 ;static void rt_thread2_entry (void *parameter) { static rt_err_t result; static rt_uint8_t number = 0 ; while (1 ) { result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER); if (result != RT_EOK) { rt_kprintf("t2 take a dynamic semaphore, failed.\n" ); rt_sem_delete(dynamic_sem); return ; } else { number++; rt_kprintf("t2 take a dynamic semaphore. number = %d\n" ,number); } } }int semaphore_sample (void ) { dynamic_sem = rt_sem_create("dsem" , 0 , RT_IPC_FLAG_FIFO); if (dynamic_sem == RT_NULL) { rt_kprintf("create dynamic semaphore failed.\n" ); return -1 ; } else { rt_kprintf("create done. dynamic semaphore value = 0.\n" ); } rt_thread_init(&thread1, "thread1" , rt_thread1_entry, RT_NULL, &thread1_stack[0 ], sizeof (thread1_stack), THREAD_PRIORITY, THREAD_TIMESLICE); rt_thread_startup(&thread1); rt_thread_init(&thread2, "thread2" , rt_thread2_entry, RT_NULL, &thread2_stack[0 ], sizeof (thread2_stack), THREAD_PRIORITY-1 , THREAD_TIMESLICE); rt_thread_startup(&thread2); return 0 ; } MSH_CMD_EXPORT(semaphore_sample, semaphore sample);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 \ | / - RT - Thread Operating System / | \ 3.1 .0 build Aug 27 2018 2006 - 2018 Copyright by rt-thread team msh >semaphore_samplecreate done. dynamic semaphore value = 0. msh >t1 release a dynamic semaphore. t2 take a dynamic semaphore. number = 1 t1 release a dynamic semaphore. t2 take a dynamic semaphore. number = 2 t1 release a dynamic semaphore. t2 take a dynamic semaphore. number = 3 t1 release a dynamic semaphore. t2 take a dynamic semaphore. number = 4 t1 release a dynamic semaphore. t2 take a dynamic semaphore. number = 5 t1 release a dynamic semaphore. t2 take a dynamic semaphore. number = 6 t1 release a dynamic semaphore. t2 take a dynamic semaphore. number = 7 t1 release a dynamic semaphore. t2 take a dynamic semaphore. number = 8 t1 release a dynamic semaphore. t2 take a dynamic semaphore. number = 9 t1 release a dynamic semaphore. t2 take a dynamic semaphore. number = 10
信号量锁的作用,生产者消费者例程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 #include <rtthread.h> #define THREAD_PRIORITY 6 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 5 #define MAXSEM 5 rt_uint32_t array [MAXSEM];static rt_uint32_t set , get;static rt_thread_t producer_tid = RT_NULL;static rt_thread_t consumer_tid = RT_NULL;struct rt_semaphore sem_lock ;struct rt_semaphore sem_empty , sem_full ;void producer_thread_entry (void *parameter) { int cnt = 0 ; while (cnt < 10 ) { rt_sem_take(&sem_empty, RT_WAITING_FOREVER); rt_sem_take(&sem_lock, RT_WAITING_FOREVER); array [set % MAXSEM] = cnt + 1 ; rt_kprintf("the producer generates a number: %d\n" , array [set % MAXSEM]); set ++; rt_sem_release(&sem_lock); rt_sem_release(&sem_full); cnt++; rt_thread_mdelay(20 ); } rt_kprintf("the producer exit!\n" ); }void consumer_thread_entry (void *parameter) { rt_uint32_t sum = 0 ; while (1 ) { rt_sem_take(&sem_full, RT_WAITING_FOREVER); rt_sem_take(&sem_lock, RT_WAITING_FOREVER); sum += array [get % MAXSEM]; rt_kprintf("the consumer[%d] get a number: %d\n" , (get % MAXSEM), array [get % MAXSEM]); get++; rt_sem_release(&sem_lock); rt_sem_release(&sem_empty); if (get == 10 ) break ; rt_thread_mdelay(50 ); } rt_kprintf("the consumer sum is: %d\n" , sum); rt_kprintf("the consumer exit!\n" ); }int producer_consumer (void ) { set = 0 ; get = 0 ; rt_sem_init(&sem_lock, "lock" , 1 , RT_IPC_FLAG_FIFO); rt_sem_init(&sem_empty, "empty" , MAXSEM, RT_IPC_FLAG_FIFO); rt_sem_init(&sem_full, "full" , 0 , RT_IPC_FLAG_FIFO); producer_tid = rt_thread_create("producer" , producer_thread_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY - 1 , THREAD_TIMESLICE); if (producer_tid != RT_NULL) { rt_thread_startup(producer_tid); } else { rt_kprintf("create thread producer failed" ); return -1 ; } consumer_tid = rt_thread_create("consumer" , consumer_thread_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY + 1 , THREAD_TIMESLICE); if (consumer_tid != RT_NULL) { rt_thread_startup(consumer_tid); } else { rt_kprintf("create thread consumer failed" ); return -1 ; } return 0 ; } MSH_CMD_EXPORT(producer_consumer, producer_consumer sample);
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 \ | / - RT - Thread Operating System / | \ 3.1 .0 build Aug 27 2018 2006 - 2018 Copyright by rt-thread team msh >producer_consumerthe producer generates a number : 1 the consumer[0 ] get a number : 1 msh >the producer generates a number : 2 the producer generates a number : 3 the consumer[1 ] get a number : 2 the producer generates a number : 4 the producer generates a number : 5 the producer generates a number : 6 the consumer[2 ] get a number : 3 the producer generates a number : 7 the producer generates a number : 8 the consumer[3 ] get a number : 4 the producer generates a number : 9 the consumer[4 ] get a number : 5 the producer generates a number : 10 the producer exit!the consumer[0 ] get a number : 6 the consumer[1 ] get a number : 7 the consumer[2 ] get a number : 8 the consumer[3 ] get a number : 9 the consumer[4 ] get a number : 10 the consumer sum is: 55 the consumer exit!
互斥量 Mutex 互斥量又叫相互排斥的信号量,是一种特殊的二值信号量。
互斥量和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。
线程优先级翻转如下。
互斥量API
创建和删除互斥量-create 1 rt_mutex_t rt_mutex_create (const char * name, rt_uint8_t flag) ;
互斥量的 flag 标志设置为 RT_IPC_FLAG_PRIO,表示在多个线程等待资源时,将由优先级高的线程优先获得资源。flag 设置为 RT_IPC_FLAG_FIFO,表示在多个线程等待资源时,将按照先来先得的顺序获得资源。
参数 描述 name 互斥量的名称 flag 互斥量标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO 返回 —— 互斥量句柄 创建成功 RT_NULL 创建失败
1 rt_err_t rt_mutex_delete (rt_mutex_t mutex) ;
当删除一个互斥量时,所有等待此互斥量的线程都将被唤醒,等待线程获得的返回值是 - RT_ERROR。然后系统将该互斥量从内核对象管理器链表中删除并释放互斥量占用的内存空间。
参数 描述 mutex 互斥量对象的句柄 返回 —— RT_EOK 删除成功
初始化和脱离互斥量-init 1 rt_err_t rt_mutex_init (rt_mutex_t mutex, const char * name, rt_uint8_t flag) ;
flag见上
参数 描述 mutex 互斥量对象的句柄,它由用户提供,并指向互斥量对象的内存块 name 互斥量的名称 flag 互斥量标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO 返回 —— RT_EOK 初始化成功
1 rt_err_t rt_mutex_detach (rt_mutex_t mutex) ;
使用该函数接口后,内核先唤醒所有挂在该互斥量上的线程(线程的返回值是 -RT_ERROR),然后系统将该互斥量从内核对象管理器中脱离。
参数 描述 mutex 互斥量对象的句柄 返回 —— RT_EOK 成功
获取互斥量-take 线程获取了互斥量,那么线程就有了对该互斥量的所有权,即某一个时刻一个互斥量只能被一个线程持有。
1 rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time) ;
如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。
如果互斥量已经被当前线程线程控制,则该互斥量的持有计数加 1,当前线程也不会挂起等待。
如果互斥量已经被其他线程占有,则当前线程在该互斥量上挂起等待,直到其他线程释放它或者等待时间超过指定的超时时间。
参数 描述 mutex 互斥量对象的句柄 time 指定等待的时间 返回 —— RT_EOK 成功获得互斥量 -RT_ETIMEOUT 超时 -RT_ERROR 获取失败
释放互斥量-release 当线程完成互斥资源的访问后,应尽快释放它占据的互斥量,使得其他线程能及时获取该互斥量。
1 rt_err_t rt_mutex_release (rt_mutex_t mutex) ;
只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的持有计数就减 1。 当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该信号量上的线程将被唤醒。 参数 描述 mutex 互斥量对象的句柄 返回 —— RT_EOK 成功
互斥量应用示例 这是一个互斥量的应用例程,互斥锁是一种保护共享资源的方法。当一个线程拥有互斥锁的时候,可以保护共享资源不被其他线程破坏。下面用一个例子来说明,有两个线程:线程 1 和线程 2,线程 1 对 2 个 number 分别进行加 1 操作;线程 2 也对 2 个 number 分别进行加 1 操作,使用互斥量保证线程改变 2 个 number 值的操作不被打断。如下代码所示:
互斥量例程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 #include <rtthread.h> #define THREAD_PRIORITY 8 #define THREAD_TIMESLICE 5 static rt_mutex_t dynamic_mutex = RT_NULL;static rt_uint8_t number1,number2 = 0 ; ALIGN(RT_ALIGN_SIZE)static char thread1_stack[1024 ];static struct rt_thread thread1 ;static void rt_thread_entry1 (void *parameter) { while (1 ) { rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); number1++; rt_thread_mdelay(10 ); number2++; rt_mutex_release(dynamic_mutex); } } ALIGN(RT_ALIGN_SIZE)static char thread2_stack[1024 ];static struct rt_thread thread2 ;static void rt_thread_entry2 (void *parameter) { while (1 ) { rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER); if (number1 != number2) { rt_kprintf("not protect.number1 = %d, mumber2 = %d \n" ,number1 ,number2); } else { rt_kprintf("mutex protect ,number1 = mumber2 is %d\n" ,number1); } number1++; number2++; rt_mutex_release(dynamic_mutex); if (number1>=50 ) return ; } }int mutex_sample (void ) { dynamic_mutex = rt_mutex_create("dmutex" , RT_IPC_FLAG_FIFO); if (dynamic_mutex == RT_NULL) { rt_kprintf("create dynamic mutex failed.\n" ); return -1 ; } rt_thread_init(&thread1, "thread1" , rt_thread_entry1, RT_NULL, &thread1_stack[0 ], sizeof (thread1_stack), THREAD_PRIORITY, THREAD_TIMESLICE); rt_thread_startup(&thread1); rt_thread_init(&thread2, "thread2" , rt_thread_entry2, RT_NULL, &thread2_stack[0 ], sizeof (thread2_stack), THREAD_PRIORITY-1 , THREAD_TIMESLICE); rt_thread_startup(&thread2); return 0 ; } MSH_CMD_EXPORT(mutex_sample, mutex sample);
线程 1 与线程 2 中均使用互斥量保护对 2 个 number 的操作(倘若将线程 1 中的获取、释放互斥量语句注释掉,线程 1 将对 number 不再做保护),仿真运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 \ | / - RT - Thread Operating System / | \ 3.1 .0 build Aug 24 2018 2006 - 2018 Copyright by rt-thread team msh >mutex_sample msh >mutex protect ,number1 = mumber2 is 1 mutex protect ,number1 = mumber2 is 2 mutex protect ,number1 = mumber2 is 3 mutex protect ,number1 = mumber2 is 4 … mutex protect ,number1 = mumber2 is 48 mutex protect ,number1 = mumber2 is 49
线程使用互斥量保护对两个 number 的操作,使 number 值保持一致。
互斥量的另一个例子见下面的代码,这个例子将创建 3 个动态线程以检查持有互斥量时,持有的线程优先级是否被调整到等待线程优先级中的最高优先级。
防止优先级翻转特性例程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 #include <rtthread.h> static rt_thread_t tid1 = RT_NULL;static rt_thread_t tid2 = RT_NULL;static rt_thread_t tid3 = RT_NULL;static rt_mutex_t mutex = RT_NULL;#define THREAD_PRIORITY 10 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 5 static void thread1_entry (void *parameter) { rt_thread_mdelay(100 ); if (tid2->current_priority != tid3->current_priority) { rt_kprintf("the priority of thread2 is: %d\n" , tid2->current_priority); rt_kprintf("the priority of thread3 is: %d\n" , tid3->current_priority); rt_kprintf("test failed.\n" ); return ; } else { rt_kprintf("the priority of thread2 is: %d\n" , tid2->current_priority); rt_kprintf("the priority of thread3 is: %d\n" , tid3->current_priority); rt_kprintf("test OK.\n" ); } }static void thread2_entry (void *parameter) { rt_err_t result; rt_kprintf("the priority of thread2 is: %d\n" , tid2->current_priority); rt_thread_mdelay(50 ); result = rt_mutex_take(mutex, RT_WAITING_FOREVER); if (result == RT_EOK) { rt_mutex_release(mutex); } }static void thread3_entry (void *parameter) { rt_tick_t tick; rt_err_t result; rt_kprintf("the priority of thread3 is: %d\n" , tid3->current_priority); result = rt_mutex_take(mutex, RT_WAITING_FOREVER); if (result != RT_EOK) { rt_kprintf("thread3 take a mutex, failed.\n" ); } tick = rt_tick_get(); while (rt_tick_get() - tick < (RT_TICK_PER_SECOND / 2 )) ; rt_mutex_release(mutex); }int pri_inversion (void ) { mutex = rt_mutex_create("mutex" , RT_IPC_FLAG_FIFO); if (mutex == RT_NULL) { rt_kprintf("create dynamic mutex failed.\n" ); return -1 ; } tid1 = rt_thread_create("thread1" , thread1_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY - 1 , THREAD_TIMESLICE); if (tid1 != RT_NULL) rt_thread_startup(tid1); tid2 = rt_thread_create("thread2" , thread2_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (tid2 != RT_NULL) rt_thread_startup(tid2); tid3 = rt_thread_create("thread3" , thread3_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY + 1 , THREAD_TIMESLICE); if (tid3 != RT_NULL) rt_thread_startup(tid3); return 0 ; } MSH_CMD_EXPORT(pri_inversion, prio_inversion sample);
仿真运行结果如下:
1 2 3 4 5 6 7 8 9 10 \ | / - RT - Thread Operating System / | \ 3.1.0 build Aug 27 2018 2006 - 2018 Copyright by rt-thread team msh >pri_inversion the priority of thread2 is: 10 the priority of thread3 is: 11 the priority of thread2 is: 10 the priority of thread3 is: 10test OK.
事件集Event 一个事件集可以包含多个事件,利用事件集可以完成一对多 ,多对多 ,多对一 的线程间同步。
这种多个事件的集合可以用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件,线程通过 “逻辑与” 或“逻辑或”将一个或多个事件关联起来,形成事件组合。
事件的 “逻辑或” 也称为是独立型同步,指的是线程与任何事件之一发生同步; 事件 “逻辑与” 也称为是关联型同步,指的是线程与若干事件都发生同步。 事件只与线程相关,事件间相互独立:每个线程可拥有 32 个事件标志,采用一个 32 bit 无符号整型数进行记录,每一个 bit 代表一个事件; 事件仅用于同步,不提供数据传输功能; 事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。
线程 #1 的事件标志中第 1 位和第 30 位被置位,如果事件信息标记位设为逻辑与,则表示线程 #1 只有在事件 1 和事件 30 都发生以后才会被触发唤醒,如果事件信息标记位设为逻辑或,则事件 1 或事件 30 中的任意一个发生都会触发唤醒线程 #1。如果信息标记同时设置了清除标记位,则当线程 #1 唤醒后将主动把事件 1 和事件 30 清为零,否则事件标志将依然存在(即置 1)。
事件集API
创建和删除事件集-create 1 rt_event_t rt_event_create (const char * name, rt_uint8_t flag) ;
参数 描述 name 事件集的名称 flag 事件集的标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO 返回 —— RT_NULL 创建失败 事件对象的句柄 创建成功
1 rt_err_t rt_event_delete (rt_event_t event) ;
在删除前会唤醒所有挂起在该事件集上的线程(线程的返回值是 - RT_ERROR),然后释放事件集对象占用的内存块。
参数 描述 event 事件集对象的句柄 返回 —— RT_EOK 成功
初始化和脱离事件集-init 1 rt_err_t rt_event_init (rt_event_t event, const char * name, rt_uint8_t flag) ;
参数 描述 event 事件集对象的句柄 name 事件集的名称 flag 事件集的标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO 返回 —— RT_EOK 成功
1 rt_err_t rt_event_detach (rt_event_t event) ;
用户调用这个函数时,系统首先唤醒所有挂在该事件集等待队列上的线程(线程的返回值是 - RT_ERROR),然后将该事件集从内核对象管理器中脱离。
参数 描述 event 事件集对象的句柄 返回 —— RT_EOK 成功
发送事件-send 1 rt_err_t rt_event_send (rt_event_t event, rt_uint32_t set ) ;
使用该函数接口时,通过参数 set 指定的事件标志来设定 event 事件集对象的事件标志值,然后遍历等待在 event 事件集对象上的等待线程链表,判断是否有线程的事件激活要求与当前 event 对象事件标志值匹配,如果有,则唤醒该线程。
参数 描述 event 事件集对象的句柄 set 发送的一个或多个事件的标志值,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO 返回 —— RT_EOK 成功
接收事件-recv 内核使用 32 位的无符号整数来标识事件集,它的每一位代表一个事件,因此一个事件集对象可同时等待接收 32 个事件.
1 2 3 4 5 rt_err_t rt_event_recv (rt_event_t event, rt_uint32_t set , rt_uint8_t option, rt_int32_t timeout, rt_uint32_t * recved) ;
当用户调用这个接口时,系统首先根据 set 参数和接收选项 option 来判断它要接收的事件是否发生,如果已经发生,则根据参数 option 上是否设置有 RT_EVENT_FLAG_CLEAR 来决定是否重置事件的相应标志位,然后返回(其中 recved 参数返回接收到的事件);如果没有发生,则把等待的 set 和 option 参数填入线程本身的结构中,然后把线程挂起在此事件上,直到其等待的事件满足条件或等待时间超过指定的超时时间。如果超时时间设置为零,则表示当线程要接受的事件没有满足其要求时就不等待,而直接返回 - RT_ETIMEOUT。
参数 描述 event 事件集对象的句柄 set 接收线程感兴趣的事件 option 接收选项 可取值见下 timeout 指定超时时间 recved 指向接收到的事件 返回 —— RT_EOK 成功 -RT_ETIMEOUT 超时 -RT_ERROR 错误
option 的值可取:
1 2 3 4 5 6 RT_EVENT_FLAG_OR RT_EVENT_FLAG_AND RT_EVENT_FLAG_CLEAR
事件集应用示例 这是事件集的应用例程,例子中初始化了一个事件集,两个线程。一个线程等待自己关心的事件发生,另外一个线程发送事件,如代码清单 6-5 例所示:
事件集的使用例程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #include <rtthread.h> #define THREAD_PRIORITY 9 #define THREAD_TIMESLICE 5 #define EVENT_FLAG3 (1 << 3) #define EVENT_FLAG5 (1 << 5) static struct rt_event event ; ALIGN(RT_ALIGN_SIZE)static char thread1_stack[1024 ];static struct rt_thread thread1 ;static void thread1_recv_event (void *param) { rt_uint32_t e; if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5), RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &e) == RT_EOK) { rt_kprintf("thread1: OR recv event 0x%x\n" , e); } rt_kprintf("thread1: delay 1s to prepare the second event\n" ); rt_thread_mdelay(1000 ); if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5), RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR, RT_WAITING_FOREVER, &e) == RT_EOK) { rt_kprintf("thread1: AND recv event 0x%x\n" , e); } rt_kprintf("thread1 leave.\n" ); } ALIGN(RT_ALIGN_SIZE)static char thread2_stack[1024 ];static struct rt_thread thread2 ;static void thread2_send_event (void *param) { rt_kprintf("thread2: send event3\n" ); rt_event_send(&event, EVENT_FLAG3); rt_thread_mdelay(200 ); rt_kprintf("thread2: send event5\n" ); rt_event_send(&event, EVENT_FLAG5); rt_thread_mdelay(200 ); rt_kprintf("thread2: send event3\n" ); rt_event_send(&event, EVENT_FLAG3); rt_kprintf("thread2 leave.\n" ); }int event_sample (void ) { rt_err_t result; result = rt_event_init(&event, "event" , RT_IPC_FLAG_FIFO); if (result != RT_EOK) { rt_kprintf("init event failed.\n" ); return -1 ; } rt_thread_init(&thread1, "thread1" , thread1_recv_event, RT_NULL, &thread1_stack[0 ], sizeof (thread1_stack), THREAD_PRIORITY - 1 , THREAD_TIMESLICE); rt_thread_startup(&thread1); rt_thread_init(&thread2, "thread2" , thread2_send_event, RT_NULL, &thread2_stack[0 ], sizeof (thread2_stack), THREAD_PRIORITY, THREAD_TIMESLICE); rt_thread_startup(&thread2); return 0 ; } MSH_CMD_EXPORT(event_sample, event sample);
仿真运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 \ | / - RT - Thread Operating System / | \ 3.1 .0 build Aug 24 2018 2006 - 2018 Copyright by rt-thread team msh >event_sample thread2: send event3 thread1: OR recv event 0x8 thread1: delay 1 s to prepare the second event msh >thread2: send event5 thread2: send event3 thread2 leave. thread1: AND recv event 0x28 thread1 leave.
笔记 Semaphore:值、数值
Mutex:二值性、只能持有进程释放
Event:32Bit、进行’’或’’和’’与’’操作
在获得互斥量后,请尽快释放互斥量,并且在持有互斥量的过程中,不得再行更改持有互斥量线程的优先级。 互斥量不能在中断服务例程中使用。 线程间通信 邮箱Mailbox 邮箱用于线程间通信,特点是开销比较低,效率较高。邮箱中的每一封邮件只能容纳固定的 4 字节内容(针对 32 位处理系统,指针的大小即为 4 个字节,所以一封邮件恰好能够容纳一个指针)。
如下图所示,线程或中断服务例程把一封 4 字节长度的邮件发送到邮箱中,而一个或多个线程可以从邮箱中接收这些邮件并进行处理。
邮箱API
创建和删除邮箱-create 1 rt_mailbox_t rt_mb_create (const char * name, rt_size_t size, rt_uint8_t flag) ;
参数 描述 name 邮箱名称 size 邮箱容量 flag 邮箱标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO 返回 —— RT_NULL 创建失败 邮箱对象的句柄 创建成功
1 rt_err_t rt_mb_delete (rt_mailbox_t mb) ;
删除邮箱时,如果有线程被挂起在该邮箱对象上,内核先唤醒挂起在该邮箱上的所有线程(线程返回值是 - RT_ERROR),然后再释放邮箱使用的内存,最后删除邮箱对象。
参数 描述 mb 邮箱对象的句柄 返回 —— RT_EOK 成功
初始化和脱离邮箱-init 1 2 3 4 5 rt_err_t rt_mb_init (rt_mailbox_t mb, const char * name, void * msgpool, rt_size_t size, rt_uint8_t flag)
参数 描述 mb 邮箱对象的句柄 name 邮箱名称 msgpool 缓冲区指针 size 邮箱容量 flag 邮箱标志,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO 返回 —— RT_EOK 成功
即如果 msgpool 指向的缓冲区的字节数是 N,那么邮箱容量应该是 N/4。
1 rt_err_t rt_mb_detach (rt_mailbox_t mb) ;
使用该函数接口后,内核先唤醒所有挂在该邮箱上的线程(线程获得返回值是 - RT_ERROR),然后将该邮箱对象从内核对象管理器中脱离。
参数 描述 mb 邮箱对象的句柄 返回 —— RT_EOK 成功
发送邮件-send 1 rt_err_t rt_mb_send (rt_mailbox_t mb, rt_uint32_t value) ;
发送的邮件可以是 32 位任意格式的数据,一个整型值或者一个指向缓冲区的指针。当邮箱中的邮件已经满时,发送邮件的线程或者中断程序会收到 -RT_EFULL 的返回值。
参数 描述 mb 邮箱对象的句柄 value 邮件内容 返回 —— RT_EOK 发送成功 -RT_EFULL 邮箱已经满了
等待方式发送邮件-wait send 1 2 3 rt_err_t rt_mb_send_wait (rt_mailbox_t mb, rt_uint32_t value, rt_int32_t timeout) ;
rt_mb_send_wait() 与 rt_mb_send() 的区别在于有等待时间,如果邮箱已经满了,那么发送线程将根据设定的 timeout 参数等待邮箱中因为收取邮件而空出空间。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒并返回错误码。
参数 描述 mb 邮箱对象的句柄 value 邮件内容 timeout 超时时间 返回 —— RT_EOK 发送成功 -RT_ETIMEOUT 超时 -RT_ERROR 失败,返回错误
接收邮件-recv 1 rt_err_t rt_mb_recv (rt_mailbox_t mb, rt_uint32_t * value, rt_int32_t timeout) ;
参数 描述 mb 邮箱对象的句柄 value 邮件内容 timeout 超时时间 返回 —— RT_EOK 发送成功 -RT_ETIMEOUT 超时 -RT_ERROR 失败,返回错误
邮箱应用示例 这是一个邮箱的应用例程,初始化 2 个静态线程,一个静态的邮箱对象,其中一个线程往邮箱中发送邮件,一个线程往邮箱中收取邮件。如下代码所示:
邮箱的使用例程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 #include <rtthread.h> #define THREAD_PRIORITY 10 #define THREAD_TIMESLICE 5 static struct rt_mailbox mb ;static char mb_pool[128 ];static char mb_str1[] = "I'm a mail!" ;static char mb_str2[] = "this is another mail!" ;static char mb_str3[] = "over" ; ALIGN(RT_ALIGN_SIZE)static char thread1_stack[1024 ];static struct rt_thread thread1 ;static void thread1_entry (void *parameter) { char *str; while (1 ) { rt_kprintf("thread1: try to recv a mail\n" ); if (rt_mb_recv(&mb, (rt_uint32_t *)&str, RT_WAITING_FOREVER) == RT_EOK) { rt_kprintf("thread1: get a mail from mailbox, the content:%s\n" , str); if (str == mb_str3) break ; rt_thread_mdelay(100 ); } } rt_mb_detach(&mb); } ALIGN(RT_ALIGN_SIZE)static char thread2_stack[1024 ];static struct rt_thread thread2 ;static void thread2_entry (void *parameter) { rt_uint8_t count; count = 0 ; while (count < 10 ) { count ++; if (count & 0x1 ) { rt_mb_send(&mb, (rt_uint32_t )&mb_str1); } else { rt_mb_send(&mb, (rt_uint32_t )&mb_str2); } rt_thread_mdelay(200 ); } rt_mb_send(&mb, (rt_uint32_t )&mb_str3); }int mailbox_sample (void ) { rt_err_t result; result = rt_mb_init(&mb, "mbt" , &mb_pool[0 ], sizeof (mb_pool) / 4 , RT_IPC_FLAG_FIFO); if (result != RT_EOK) { rt_kprintf("init mailbox failed.\n" ); return -1 ; } rt_thread_init(&thread1, "thread1" , thread1_entry, RT_NULL, &thread1_stack[0 ], sizeof (thread1_stack), THREAD_PRIORITY, THREAD_TIMESLICE); rt_thread_startup(&thread1); rt_thread_init(&thread2, "thread2" , thread2_entry, RT_NULL, &thread2_stack[0 ], sizeof (thread2_stack), THREAD_PRIORITY, THREAD_TIMESLICE); rt_thread_startup(&thread2); return 0 ; } MSH_CMD_EXPORT(mailbox_sample, mailbox sample);
仿真运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 \ | / - RT - Thread Operating System / | \ 3.1 .0 build Aug 27 2018 2006 - 2018 Copyright by rt-thread team msh >mailbox_samplethread1: try to recv a mailthread1: get a mail from mailbox, the content:I msh >thread1: try to recv a mailthread1: get a mail from mailbox, the content:this is another mail! …thread1: try to recv a mailthread1: get a mail from mailbox, the content:this is another mail!thread1: try to recv a mailthread1: get a mail from mailbox, the content:over
消息队列Messagequeue 消息队列是另一种常用的线程间通讯方式,是邮箱的扩展(消息队列:不固定长度的消息)。可以应用在多种场合:线程间的消息交换、使用串口接收不定长数据等。
线程或中断服务例程可以将一条或多条消息放入消息队列中。同样,一个或多个线程也可以从消息队列中获得消息。当有多个消息发送到消息队列时,通常将先进入消息队列的消息先传给线程,也就是说,线程先得到的是最先进入消息队列的消息,即先进先出原则 (FIFO)。
消息队列和邮箱的明显不同是消息的长度并不限定在 4 个字节以内;另外,消息队列也包括了一个发送紧急消息的函数接口。但是当创建的是一个所有消息的最大长度是 4 字节的消息队列时,消息队列对象将蜕化成邮箱。
消息队列API
创建和删除消息队列-create 1 2 rt_mq_t rt_mq_create (const char * name, rt_size_t msg_size, rt_size_t max_msgs, rt_uint8_t flag) ;
消息队列内存的大小 =[消息大小+消息头(用于链表连接)的大小]X消息队列最大个数
参数 描述 name 消息队列的名称 msg_size 消息队列中一条消息的最大长度,单位字节 max_msgs 消息队列的最大个数 flag 消息队列采用的等待方式,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO 返回 —— RT_EOK 发送成功 消息队列对象的句柄 成功 RT_NULL 失败
1 rt_err_t rt_mq_delete (rt_mq_t mq) ;
删除消息队列时,如果有线程被挂起在该消息队列等待队列上,则内核先唤醒挂起在该消息等待队列上的所有线程(线程返回值是 - RT_ERROR),然后再释放消息队列使用的内存,最后删除消息队列对象。
参数 描述 mq 消息队列对象的句柄 返回 —— RT_EOK 成功
初始化和脱离消息队列-init 1 2 3 rt_err_t rt_mq_init (rt_mq_t mq, const char * name, void *msgpool, rt_size_t msg_size, rt_size_t pool_size, rt_uint8_t flag) ;
参数 描述 mq 消息队列对象的句柄 name 消息队列的名称 msgpool 指向存放消息的缓冲区的指针 msg_size 消息队列中一条消息的最大长度,单位字节 pool_size 存放消息的缓冲区大小 flag 消息队列采用的等待方式,它可以取如下数值: RT_IPC_FLAG_FIFO 或 RT_IPC_FLAG_PRIO 返回 —— RT_EOK 成功
1 rt_err_t rt_mq_detach (rt_mq_t mq) ;
使用该函数接口后,内核先唤醒所有挂在该消息等待队列对象上的线程(线程返回值是 -RT_ERROR),然后将该消息队列对象从内核对象管理器中脱离。
参数 描述 mq 消息队列对象的句柄 返回 —— RT_EOK 成功
发送消息-send 线程 或者中断服务程序 都可以给消息队列发送消息。当发送消息时,消息队列对象先从空闲消息链表上取下一个空闲消息块,把线程或者中断服务程序发送的消息内容复制到消息块上,然后把该消息块挂到消息队列的尾部。当且仅当空闲消息链表上有可用的空闲消息块时,发送者才能成功发送消息;当空闲消息链表上无可用消息块,说明消息队列已满,此时,发送消息的的线程或者中断程序会收到一个错误码(-RT_EFULL)。
1 rt_err_t rt_mq_send (rt_mq_t mq, void * buffer, rt_size_t size) ;
发送消息时,发送者需指定发送的消息队列的对象句柄(即指向消息队列控制块的指针),并且指定发送的消息内容以及消息大小。如下图所示,在发送一个普通消息之后,空闲消息链表上的队首消息被转移到了消息队列尾。
参数 描述 mq 消息队列对象的句柄 buffer 消息内容 size 消息大小 返回 —— RT_EOK 成功 -RT_EFULL 消息队列已满 -RT_ERROR 失败,表示发送的消息长度大于消息队列中消息的最大长度
等待方式发送消息-wait 1 2 3 4 rt_err_t rt_mq_send_wait (rt_mq_t mq, const void *buffer, rt_size_t size, rt_int32_t timeout) ;
rt_mq_send_wait() 与 rt_mq_send() 的区别在于有等待时间,如果消息队列已经满了,那么发送线程将根据设定的 timeout 参数进行等待。如果设置的超时时间到达依然没有空出空间,这时发送线程将被唤醒并返回错误码。
参数 描述 mq 消息队列对象的句柄 buffer 消息内容 size 消息大小 timeout 超时时间 返回 —— RT_EOK 成功 -RT_EFULL 消息队列已满 -RT_ERROR 失败,表示发送的消息长度大于消息队列中消息的最大长度
发送紧急消息-urgent 发送紧急消息的过程与发送消息几乎一样,唯一的不同是,当发送紧急消息时,从空闲消息链表上取下来的消息块不是挂到消息队列的队尾,而是挂到队首,这样,接收者就能够优先接收到紧急消息,从而及时进行消息处理。发送紧急消息的函数接口如下:
1 rt_err_t rt_mq_urgent (rt_mq_t mq, void * buffer, rt_size_t size) ;
参数 描述 mq 消息队列对象的句柄 buffer 消息内容 size 消息大小 返回 —— RT_EOK 成功 -RT_EFULL 消息队列已满 -RT_ERROR 失败
接收消息-recv 1 2 rt_err_t rt_mq_recv (rt_mq_t mq, void * buffer, rt_size_t size, rt_int32_t timeout) ;
接收消息时,接收者需指定存储消息的消息队列对象句柄,并且指定一个内存缓冲区,接收到的消息内容将被复制到该缓冲区里。此外,还需指定未能及时取到消息时的超时时间。
参数 描述 mq 消息队列对象的句柄 buffer 消息内容 size 消息大小 timeout 指定的超时时间 返回 —— RT_EOK 成功收到 -RT_ETIMEOUT 超时 -RT_ERROR 失败,返回错误
消息队列应用示例 这是一个消息队列的应用例程,例程中初始化了 2 个静态线程,一个线程会从消息队列中收取消息;另一个线程会定时给消息队列发送普通消息和紧急消息,如下代码所示:
消息队列的使用例程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 #include <rtthread.h> static struct rt_messagequeue mq ;static rt_uint8_t msg_pool[2048 ]; ALIGN(RT_ALIGN_SIZE)static char thread1_stack[1024 ];static struct rt_thread thread1 ;static void thread1_entry (void *parameter) { char buf = 0 ; rt_uint8_t cnt = 0 ; while (1 ) { if (rt_mq_recv(&mq, &buf, sizeof (buf), RT_WAITING_FOREVER) == RT_EOK) { rt_kprintf("thread1: recv msg from msg queue, the content:%c\n" , buf); if (cnt == 19 ) { break ; } } cnt++; rt_thread_mdelay(50 ); } rt_kprintf("thread1: detach mq \n" ); rt_mq_detach(&mq); } ALIGN(RT_ALIGN_SIZE)static char thread2_stack[1024 ];static struct rt_thread thread2 ;static void thread2_entry (void *parameter) { int result; char buf = 'A' ; rt_uint8_t cnt = 0 ; while (1 ) { if (cnt == 8 ) { result = rt_mq_urgent(&mq, &buf, 1 ); if (result != RT_EOK) { rt_kprintf("rt_mq_urgent ERR\n" ); } else { rt_kprintf("thread2: send urgent message - %c\n" , buf); } } else if (cnt>= 20 ) { rt_kprintf("message queue stop send, thread2 quit\n" ); break ; } else { result = rt_mq_send(&mq, &buf, 1 ); if (result != RT_EOK) { rt_kprintf("rt_mq_send ERR\n" ); } rt_kprintf("thread2: send message - %c\n" , buf); } buf++; cnt++; rt_thread_mdelay(5 ); } }int msgq_sample (void ) { rt_err_t result; result = rt_mq_init(&mq, "mqt" , &msg_pool[0 ], 1 , sizeof (msg_pool), RT_IPC_FLAG_FIFO); if (result != RT_EOK) { rt_kprintf("init message queue failed.\n" ); return -1 ; } rt_thread_init(&thread1, "thread1" , thread1_entry, RT_NULL, &thread1_stack[0 ], sizeof (thread1_stack), 25 , 5 ); rt_thread_startup(&thread1); rt_thread_init(&thread2, "thread2" , thread2_entry, RT_NULL, &thread2_stack[0 ], sizeof (thread2_stack), 25 , 5 ); rt_thread_startup(&thread2); return 0 ; } MSH_CMD_EXPORT(msgq_sample, msgq sample);
仿真运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 \ | / - RT - Thread Operating System / | \ 3.1 .0 build Aug 24 2018 2006 - 2018 Copyright by rt-thread team msh > msgq_sample msh >thread2: send message - Athread1: recv msg from msg queue, the content:Athread2: send message - Bthread2: send message - Cthread2: send message - Dthread2: send message - Ethread1: recv msg from msg queue, the content:Bthread2: send message - Fthread2: send message - Gthread2: send message - Hthread2: send urgent message - Ithread2: send message - Jthread1: recv msg from msg queue, the content:Ithread2: send message - Kthread2: send message - Lthread2: send message - Mthread2: send message - Nthread2: send message - Othread1: recv msg from msg queue, the content:Cthread2: send message - Pthread2: send message - Qthread2: send message - Rthread2: send message - Sthread2: send message - Tthread1: recv msg from msg queue, the content:D message queue stop send, thread2 quitthread1: recv msg from msg queue, the content:Ethread1: recv msg from msg queue, the content:Fthread1: recv msg from msg queue, the content:G …thread1: recv msg from msg queue, the content:Tthread1: detach mq
信号Signal 信号(又称为软中断信号),在软件层次上是对中断机制的一种模拟,在原理上,一个线程收到一个信号与处理器收到一个中断请求可以说是类似的。
应用程序(用户)能够使用的信号为 SIGUSR1(10)和 SIGUSR2(12)。
信号本质是软中断 ,用来通知线程发生了异步事件,用做线程之间的异常通知、应急处理。一个线程不必通过任何操作来等待信号的到达,事实上,线程也不知道信号到底什么时候到达,线程之间可以互相通过调用 rt_thread_kill() 发送软中断信号。
收到信号的线程对各种信号有不同的处理方法,在信号安装时设定 handler 参数,处理方法可以分为三类:
第一种是类似中断的处理程序,对于需要处理的信号,线程可以指定处理函数,由该函数来处理。
第二种方法是,参数设为 SIG_IGN,忽略某个信号,对该信号不做任何处理,就像未发生过一样。
第三种方法是,参数设为 SIG_DFL,系统会调用默认的处理函数_signal_default_handler()。
如下图所示,假设线程 1 需要对信号进行处理,首先线程 1 安装一个信号并解除阻塞,并在安装的同时设定了对信号的异常处理方式;然后其他线程可以给线程 1 发送信号,触发线程 1 对该信号的处理。
当信号被传递给线程 1 时,如果它正处于挂起状态,那会把状态改为就绪状态去处理对应的信号。如果它正处于运行状态,那么会在它当前的线程栈基础上建立新栈帧空间去处理对应的信号,需要注意的是使用的线程栈大小也会相应增加。
信号API
安装信号-install 如果线程要处理某一信号,那么就要在线程中安装该信号。安装信号主要用来确定信号值及线程针对该信号值的动作之间的映射关系,即线程将要处理哪个信号,该信号被传递给线程时,将执行何种操作。
1 rt_sighandler_t rt_signal_install (int signo, rt_sighandler_t [] handler) ;
其中 rt_sighandler_t 是定义信号处理函数的函数指针类型。
参数 描述 signo 信号值(只有 SIGUSR1 和 SIGUSR2 是开放给用户使用的,下同) handler 设置对信号值的处理方式 (函数指针) 返回 —— SIG_ERR 错误的信号 安装信号前的 handler 值 成功
屏蔽信号-mask 信号阻塞,也可以理解为屏蔽信号。如果该信号被阻塞,则该信号将不会递达给安装此信号的线程,也不会引发软中断处理。
1 void rt_signal_mask (int signo) ;
解除信号屏蔽-unmask 1 void rt_signal_unmask (int signo) ;
发送信号-kill 1 int rt_thread_kill (rt_thread_t tid, int sig) ;
参数 描述 tid 接收信号的线程 sig 信号值 返回 —— RT_EOK 发送成功 -RT_EINVAL 参数错误
等待信号-wait 等待 set 信号的到来,如果没有等到这个信号,则将线程挂起,直到等到这个信号或者等待时间超过指定的超时时间 timeout。如果等到了该信号,则将指向该信号体的指针存入 si,如下是等待信号的函数。
1 2 int rt_signal_wait (const rt_sigset_t *set , rt_siginfo_t [] *si, rt_int32_t timeout) ;
参数 描述 set 指定等待的信号 si 指向存储等到信号信息的指针 timeout 指定的等待时间 返回 —— RT_EOK 等到信号 -RT_ETIMEOUT 超时 -RT_EINVAL 参数错误
信号应用示例 这是一个信号的应用例程,如下代码所示。此例程创建了 1 个线程,在安装信号时,信号处理方式设为自定义处理,定义的信号的处理函数为 thread1_signal_handler()。待此线程运行起来安装好信号之后,给此线程发送信号。此线程将接收到信号,并打印信息。
信号使用例程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 #include <rtthread.h> #define THREAD_PRIORITY 25 #define THREAD_STACK_SIZE 512 #define THREAD_TIMESLICE 5 static rt_thread_t tid1 = RT_NULL;void thread1_signal_handler (int sig) { rt_kprintf("thread1 received signal %d\n" , sig); }static void thread1_entry (void *parameter) { int cnt = 0 ; rt_signal_install(SIGUSR1, thread1_signal_handler); rt_signal_unmask(SIGUSR1); while (cnt < 10 ) { rt_kprintf("thread1 count : %d\n" , cnt); cnt++; rt_thread_mdelay(100 ); } }int signal_sample (void ) { tid1 = rt_thread_create("thread1" , thread1_entry, RT_NULL, THREAD_STACK_SIZE, THREAD_PRIORITY, THREAD_TIMESLICE); if (tid1 != RT_NULL) rt_thread_startup(tid1); rt_thread_mdelay(300 ); rt_thread_kill(tid1, SIGUSR1); return 0 ; } MSH_CMD_EXPORT(signal_sample, signal sample);
仿真运行结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 \ | / - RT - Thread Operating System / | \ 3.1 .0 build Aug 24 2018 2006 - 2018 Copyright by rt-thread team msh >signal_sample thread1 count : 0 thread1 count : 1 thread1 count : 2 msh >thread1 received signal 10 thread1 count : 3 thread1 count : 4 thread1 count : 5 thread1 count : 6 thread1 count : 7 thread1 count : 8 thread1 count : 9
笔记 Mailbox:开销较小,4字节内容,
Message Queue:长度不固定、需要进行内存分配、
Signal:软件中断
内存管理 RT-Thread 操作系统在内存管理上,根据上层应用及系统资源的不同,有针对性地提供了不同的内存分配管理算法。总体上可分为两类:内存堆管理与内存池管理,而内存堆管理又根据具体内存设备划分为三种情况:
第一种是针对小内存块的分配管理(小内存管理算法);
第二种是针对大内存块的分配管理(slab 管理算法);
第三种是针对多内存堆的分配情况(memheap 管理算法)
内存堆Heap RT-Thread 将 “ZI 段结尾处” 到内存尾部的空间用作内存堆。
内存堆可以在当前资源满足的情况下,根据用户的需求分配任意大小的内存块。而当用户不需要再使用这些内存块时,又可以释放回堆中供其他应用分配使用。RT-Thread 系统为了满足不同的需求,提供了不同的内存管理算法,分别是小内存管理算法 、Slab 管理算法和 memheap 管理算法 。
小内存管理算法 主要针对系统资源比较少,一般用于小于 2MB 内存空间的系统;
slab 内存管理算法 则主要是在系统资源比较丰富时,提供了一种近似多内存池管理算法的快速算法。
memheap 管理算法 。memheap 方法适用于系统存在多个内存堆的情况,它可以将多个内存 “粘贴” 在一起,形成一个大的内存堆,用户使用起来会非常方便。
小内存管理算法
每个内存块(不管是已分配的内存块还是空闲的内存块)都包含一个数据头,其中包括:
1)magic :变数(或称为幻数),它会被初始化成 0x1ea0(即英文单词 heap),用于标记这个内存块是一个内存管理用的内存数据块;变数不仅仅用于标识这个数据块是一个内存管理用的内存数据块,实质也是一个内存保护字:如果这个区域被改写,那么也就意味着这块内存块被非法改写(正常情况下只有内存管理器才会去碰这块内存)。
2)used :指示出当前内存块是否已经分配。
内存管理的表现主要体现在内存的分配与释放上,小型内存管理算法可以用以下例子体现出来。
如下图所示的内存分配情况,空闲链表指针 lfree 初始指向 32 字节的内存块。当用户线程要再分配一个 64 字节的内存块时,但此 lfree 指针指向的内存块只有 32 字节并不能满足要求,内存管理器会继续寻找下一内存块,当找到再下一块内存块,128 字节时,它满足分配的要求。因为这个内存块比较大,分配器将把此内存块进行拆分,余下的内存块(52 字节)继续留在 lfree 链表中,如下图分配 64 字节后的链表结构所示。
另外,在每次分配内存块前,都会留出 12 字节数据头用于 magic、used 信息及链表节点使用。返回给应用的地址实际上是这块内存块 12 字节以后的地址,前面的 12 字节数据头是用户永远不应该碰的部分(注:12 字节数据头长度会与系统对齐差异而有所不同)。
释放时则是相反的过程,但分配器会查看前后相邻的内存块是否空闲,如果空闲则合并成一个大的空闲内存块。
Slab管理算法 slab 分配器会根据对象的大小分成多个区(zone),也可以看成每类对象有一个内存池,如下图所示:
一个 zone 的大小在 32K 到 128K 字节之间,分配器会在堆初始化时根据堆的大小自动调整。系统中的 zone 最多包括 72 种对象,一次最大能够分配 16K 的内存空间,如果超出了 16K 那么直接从页分配器中分配。每个 zone 上分配的内存块大小是固定的,能够分配相同大小内存块的 zone 会链接在一个链表中,而 72 种对象的 zone 链表则放在一个数组(zone_array[])中统一管理。
(1)内存分配
假设分配一个 32 字节的内存,slab 内存分配器会先按照 32 字节的值,从 zone array 链表表头数组中找到相应的 zone 链表。如果这个链表是空的,则向页分配器分配一个新的 zone,然后从 zone 中返回第一个空闲内存块。如果链表非空,则这个 zone 链表中的第一个 zone 节点必然有空闲块存在(否则它就不应该放在这个链表中),那么就取相应的空闲块。如果分配完成后,zone 中所有空闲内存块都使用完毕,那么分配器需要把这个 zone 节点从链表中删除。
(2)内存释放
分配器需要找到内存块所在的 zone 节点,然后把内存块链接到 zone 的空闲内存块链表中。如果此时 zone 的空闲链表指示出 zone 的所有内存块都已经释放,即 zone 是完全空闲的,那么当 zone 链表中全空闲 zone 达到一定数目后,系统就会把这个全空闲的 zone 释放到页面分配器中去。
Memheap管理算法 memheap 功能就可以很方便地把多个 memheap(地址可不连续)粘合起来用于系统的 heap 分配。
memheap 工作机制如下图所示,首先将多块内存加入 memheap_item 链表进行粘合。当分配内存块时,会先从默认内存堆去分配内存,当分配不到时会查找 memheap_item 链表,尝试从其他的内存堆上分配内存块。应用程序不用关心当前分配的内存块位于哪个内存堆上,就像是在操作一个内存堆。
内存池Pool 创建/初始化 rt_mp_create()/rt_mp_init() 分配内存块 rt_mp_alloc() 释放内存块 rt_mp_free() 删除/脱离 rt_mp_delete()/rt_mp_detach()
创建/删除-create 1 2 3 rt_mp_t rt_mp_create (const char * name, rt_size_t block_count, rt_size_t block_size) ;
参数 描述 name 内存池名 block_count 内存块数量 block_size 内存块容量 返回 —— 内存池的句柄 创建内存池对象成功 RT_NULL 创建失败
1 rt_err_t rt_mp_delete (rt_mp_t mp) ;
参数 描述 mp rt_mp_create 返回的内存池对象句柄 返回 —— RT_EOK 删除成功
初始化/剥离-init 内存池块个数 = size / (block_size + 4 链表指针大小),计算结果取整数。
例如:内存池数据区总大小 size 设为 4096 字节,内存块大小 block_size 设为 80 字节;则申请的内存块个数为 4096/ (80+4)= 48 个。
1 2 3 4 rt_err_t rt_mp_init (rt_mp_t mp, const char * name, void *start, rt_size_t size, rt_size_t block size) ;
参数 描述 mp 内存池对象 name 内存池名 start 内存池的起始位置 size 内存池数据区域大小 block_size 内存块容量 返回 —— RT_EOK 初始化成功 - RT_ERROR 失败
1 rt_err_t rt_mp_detach (rt_mp_t mp) ;
分配-alloc 1 void *rt_mp_alloc (rt_mp_t mp, rt_int32_t time) ;
参数 描述 mp 内存池对象 time 超时时间 返回 —— 分配的内存块地址 成功 RT_NULL 失败
释放-free 1 void rt_mp_free (void *block) ;
笔记 因为内存堆管理器要满足多线程情况下的安全分配,会考虑多线程间的互斥问题,所以请不要在中断服务例程中分配或释放动态内存块。因为它可能会引起当前上下文被挂起等待。 RT-Thread的中断管理 中断工作机制 中断处理过程 RT-Thread 中断管理中,将中断处理程序分为中断前导程序、用户中断服务程序、中断后续程序三部分
中断前导 保存 CPU 中断现场,这部分跟 CPU 架构相关,不同 CPU 架构的实现方式有差异。
对于 Cortex-M 来说,该工作由硬件自动完成。当一个中断触发并且系统进行响应时,处理器硬件会将当前运行部分的上下文寄存器自动压入中断栈中,这部分的寄存器包括 PSR、PC、LR、R12、R3-R0 寄存器。
通知内核进入中断状态,调用 rt_interrupt_enter() 函数,作用是把全局变量 rt_interrupt_nest 加 1,用它来记录中断嵌套的层数,代码如下所示。
1 2 3 4 5 6 7 8 void rt_interrupt_enter (void ) { rt_base_t level; level = rt_hw_interrupt_disable(); rt_interrupt_nest ++; rt_hw_interrupt_enable(level); }
中断处理 中断完后不进行 线程切换,这种情况下用户中断服务程序和中断后续程序运行完毕后退出中断模式,返回被中断的线程。
中断完后进行 线程切换,这种情况会调用 rt_hw_context_switch_interrupt() 函数进行上下文切换,该函数跟 CPU 架构相关,不同 CPU 架构的实现方式有差异。
rt_hw_context_switch_interrupt() 函数会触发PendSV异常,PendSV 异常被触发后,不会立即进行 PendSV 异常中断处理程序,因为此时还在中断处理中,只有当中断后续程序运行完毕,真正退出中断处理后,才进入 PendSV 异常中断处理程序。
中断后续 通知内核离开中断状态,通过调用 rt_interrupt_leave() 函数,将全局变量 rt_interrupt_nest 减 1,代码如下所示。
1 2 3 4 5 6 7 8 void rt_interrupt_leave (void ) { rt_base_t level; level = rt_hw_interrupt_disable(); rt_interrupt_nest --; rt_hw_interrupt_enable(level); }
恢复中断前的 CPU 上下文,中断过程发生了线程切换 和没发生线程切换 的CPU上下文是不一样的。
中断嵌套 如果需要进行线程调度,线程的上下文切换将在所有中断处理程序都运行结束时才发生,如下图所示。
中断栈 中断栈可以保存在打断线程的栈中,当从中断中退出时,返回相应的线程继续执行。 中断栈也可以与线程栈完全分离开来,即每次进入中断时,在保存完打断线程上下文后,切换到新的中断栈中独立运行。在中断退出时,再做相应的上下文恢复。 使用独立中断栈相对来说更容易实现,并且对于线程栈使用情况也比较容易了解和掌握(否则必须要为中断栈预留空间,如果系统支持中断嵌套,还需要考虑应该为嵌套中断预留多大的空间)。
中断的底半处理 发生中断后,中断一般读取硬件状态或者数据,然后发送一个通知(信号量、事件、邮箱、消息队列等),接下来的相关线程收到通知对着数据进行进一步处理,这个处理的过程就叫底半处理 。
当一个中断发生时,中断服务程序需要取得相应的硬件状态或者数据。如果中断服务程序接下来要对状态或者数据进行简单处理,比如 CPU 时钟中断,中断服务程序只需对一个系统时钟变量进行加一操作,然后就结束中断服务程序。这类中断需要的运行时间往往都比较短。但对于另外一些中断,中断服务程序在取得硬件状态或数据以后,还需要进行一系列更耗时的处理过程,通常需要将该中断分割为两部分,即上半部分 (Top Half)和底半部分 (Bottom Half)。在上半部分中,取得硬件状态和数据后,打开被屏蔽的中断,给相关线程发送一条通知(可以是 RT-Thread 所提供的信号量、事件、邮箱或消息队列等方式),然后结束中断服务程序;而接下来,相关的线程在接收到通知后,接着对状态或数据进行进一步的处理,这一过程称之为底半处理 。
中断管理API
请注意!!!装载中断服务例程、中断源管理——的API 并不会出现在每一个移植分支中,例如通常 Cortex-M0/M3/M4 的移植分支中就没有这些 API。
装载中断服务例程 系统把用户的中断服务程序 (handler) 和指定的中断号关联起来,可调用如下的接口挂载一个新的中断服务程序:
1 2 3 4 rt_isr_handler_t rt_hw_interrupt_install (int vector , rt_isr_handler_t handler, void *param, char *name) ;
参数 描述 vector vector 是挂载的中断号 handler 新挂载的中断服务程序 param param 会作为参数传递给中断服务程序 name 中断的名称 返回 —— return 挂载这个中断服务程序之前挂载的中断服务程序的句柄
中断源管理 屏蔽中断源 通常在 ISR 准备处理某个中断信号之前,我们需要先屏蔽该中断源,在 ISR 处理完状态或数据以后,及时的打开之前被屏蔽的中断源。
屏蔽中断源可以保证在接下来的处理过程中硬件状态或者数据不会受到干扰,可调用下面这个函数接口:
1 void rt_hw_interrupt_mask (int vector ) ;
打开被屏蔽的中断源 1 void rt_hw_interrupt_umask (int vector ) ;
全局中断开关 全局中断开关也称为 中断锁,是禁止多线程访问临界区最简单的一种方式,即通过关闭中断的方式,来保证当前线程不会被其他事件打断(因为整个系统已经不再响应那些可以触发线程重新调度的外部事件),也就是当前线程不会被抢占,除非这个线程主动放弃了处理器控制权。
失能全局中断 1 rt_base_t rt_hw_interrupt_disable (void ) ;
返回 描述 中断状态 rt_hw_interrupt_disable 函数运行前的中断状态 2. 使能全局中断
1 void rt_hw_interrupt_enable (rt_base_t level) ;
参数 描述 level 前一次 rt_hw_interrupt_disable 返回的中断状态
中断通知 1 2 void rt_interrupt_enter (void ) ;void rt_interrupt_leave (void ) ;
这两个接口分别用在中断前导程序和中断后续程序中,均会对 rt_interrupt_nest(中断嵌套深度)的值进行修改。
使用 rt_interrupt_enter/leave() 的作用是,在中断中释放了一个信号量,唤醒了某线程,但通过判断发现当前系统处于中断上下文环境中,那么在进行线程切换时应该采取中断中线程切换的策略(等中断结束再切换),而不是立即进行切换(正常情况下立即进行切换)。
(不建议)但如果中断服务程序不会调用内核相关的函数(释放信号量等操作),这个时候,也可以不调用 rt_interrupt_enter/leave() 函数。
在上层应用中,在内核需要知道当前已经进入到中断状态或当前嵌套的中断深度 时,可调用 rt_interrupt_get_nest() 接口,它会返回 rt_interrupt_nest。如下:
1 rt_uint8_t rt_interrupt_get_nest (void ) ;
返回 描述 0 当前系统不处于中断上下文环境中 1 当前系统处于中断上下文环境中 大于 1 当前中断嵌套层次
中断与轮询 当驱动外设工作时,其编程模式到底采用中断模式触发还是轮询模式触发往往是驱动开发人员首先要考虑的问题,并且这个问题在实时操作系统与分时操作系统中差异还非常大。
在实时系统中轮询模式可能会出现非常大问题,因为在实时操作系统中,当一个程序持续地执行时(轮询时),它所在的线程会一直运行,比它优先级低的线程都不会得到运行。而分时系统中,这点恰恰相反,几乎没有优先级之分,可以在一个时间片运行这个程序,然后在另外一段时间片上运行另外一段程序。
所以通常情况下,实时系统中更多采用的是中断模式来驱动外设。当数据达到时,由中断唤醒相关的处理线程,再继续进行后续的动作。例如一些携带 FIFO(包含一定数据量的先进先出队列)的串口外设,其写入过程可以是这样的,如下图所示:
对于低速设备来说,运用这种模式非常好,而对于高速设备,数据量又小的情况下,线程的切换时间(几个us)会很明显的影响数据吞吐量和带宽利用率。
发送数据量越小,发送速度越快,对于数据吞吐量的影响也将越大。归根结底,取决于系统中产生中断的频度如何。当一个实时系统想要提升数据吞吐量时,可以考虑的几种方式:
1)增加每次数据量发送的长度,每次尽量让外设尽量多地发送数据;
2)必要情况下更改中断模式为轮询模式。同时为了解决轮询方式一直抢占处理机,其他低优先级线程得不到运行的情况,可以把轮询线程的优先级适当降低。
内核移植 参考官方文档
CPU架构移植 BSP移植 线程切换 cortex-m3中上下文切换统一是使用PendSV来实现的。