更新日期: 2023.04.08
KLite 是由个人开发者于2015年编写的嵌入式操作系统内核,并以MIT协议开放源代码。
KLite的定位是一款入门级的嵌入式实时操作系统内核,以简洁易用为设计目标,旨在降低嵌入式RTOS的入门难度。
代码干净工整、架构清晰、函数接口简单易用、不使用条件编译、移植简单、无需配置和裁减。
可能是目前最简洁易用的RTOS。
功能特性:
作者简介: 蒋晓岗,男,现居成都,毕业于成都信息工程大学,从事嵌入式软件开发十年。
KLite目前支持ARM7、ARM9、Cortex-M0、Cortex-M3、Cortex-M4、Cortex-M7内核。
以上平台如:全志F1C100S、STM32FXXX、NRF528XX等,请参考相关例程。
对于Cortex-M架构的MCU,实际上只需要修改cmsis.c里面的#include,比如#include "stm32f10x.h"
由于KLite不使用条件编译,因此可以预编译kernel源码为kernel.lib,并保留kernel.h,可以有效减少重复编译的时间。
main.c的推荐写法如下:
//只需要包含这一个头文件
#include "kernel.h"
//用于初始化应用程序的线程
void init(void *arg)
{
//在这里完成外设和驱动初始化
//并创建更多线程实现不同的功能
//thread_create(...)
}
//空闲线程,只需调用kernel_idle即可
void idle(void *arg)
{
kernel_idle();
}
//C语言程序入口
void main(void)
{
static uint8_t heap[HEAP_SIZE]; /* 定义堆内存 */
kernel_init(heap, sizeof(heap)); /* 系统初始化 */
thread_create(idle, 0, 0); /* 创建idle线程 */
thread_create(init, 0, 0); /* 创建init线程 */
kernel_start(); /* 启动系统 */
}
核心功能的源码在sources/kernel/目录,是KLite最核心的部分。
使用这些功能,只需要包含头文件
#include "kernel.h"
heap_addr
动态分配起始地址heap_size
动态分配内存大小命名规则:
主版本:架构调整,比较大的修改,与旧版本可能会不兼容
次版本:功能调整,主要涉及新增或删除功能
修订号:细节优化或BUG修复
由用户创建空闲线程是为了实现灵活配置空闲线程的stack大小,不使用宏定义来进行stack的配置。 如果使用宏定义来配置stack大小,那么代码编译成lib之后就无法修改了,失去了灵活性。
kernel_tick_count()
一起计算CPU占用率在这里把滴答时间转为毫秒单位,应用层就不必使用宏定义来进行单位转换,起到简化调用的目的。
如果硬件定时器不能产生1ms的时钟,比如RTC=32768Hz,只能产生1024Hz的中断源,周期为0.97ms,这就很尴尬!
方案一:软件修正误差,在1024个周期内,均匀地跳过24次中断。
方案二:设置中断周期为125ms,这是在32768Hz时钟下能得到的最小整数时间。
方案三:放弃毫秒为时间单位,老老实实用滴答数做为时间单位。
addr
待创建堆内存起始地址size
该堆内存的长度NULL
为系统中不同的模块创建一个独立的堆可以提高稳定性和运行效率。
heap
堆内存对象size
要申请的内存大小NULL
malloc()
一样
严格来说调用此函数应该检查返回值是否为
NULL
, 但每次heap_alloc
都要写检查返回值的代码可能有点繁琐或者容易有遗漏的地方, 所以此函数在返回NULL
之前会调用一个HOOK函数,原型void heap_fault(void);
对于嵌入式系统来说申请内存失败是严重错误,所以我们可以偷懒只在heap_fault()
函数中统一处理错误。
heap
堆内存对象mem
要释放的内存指针heap_malloc()
申请的内存,功能和标准库的free()
一样heap
堆内存对象used
输出已使用内存数量(字节),此参数可以为NULL
free
输出空闲内存数量(字节),此参数可以为NULL
entry
线程入口函数arg
线程入口函数的参数stack_size
线程的栈大小(字节),为0则使用系统默认值(1024字节)NULL
系统自动为新线程分配内存和栈空间,如果栈设置太小运行过程中可能会产生栈溢出
thread
被删除的线程标识符thread_exit()
或直接使用return退出主循环
不推荐直接删除线程,可能会造成系统不稳定,因为被删除线程可能进入了临界区未释放,需考虑清楚再使用
void thread_set_priority(thread_t thread, uint32_t prio);
参数:thread
线程标识符
参数:prio
新的优先级
THREAD_PRIORITY_HIGHEST
最高优先级
THREAD_PRIORITY_HIGHER
更高优先级
THREAD_PRIORITY_HIGH
高优先级
THREAD_PRIORITY_NORMAL
默认优先级
THREAD_PRIORITY_LOW
低优先级
THREAD_PRIORITY_LOWER
更低优先级
THREAD_PRIORITY_LOWEST
最低优先级
THREAD_PRIORITY_IDLE
空闲优先级
返回:无
描述:重新设置线程优先级,立即生效。
thread
线程标识符time
休眠时间(毫秒)void thread_suspend(void);
void thread_resume(thread_t thread);
由于直接挂起其它线程不安全,因此移除了这两个函数。 推荐使用信号量来阻塞线程,达到挂起线程和唤醒线程的目的。
这也是为什么C11标准和POSIX标准都没有定义线程挂起和恢复的接口。
thread
线程标识符可以使用此函数来监控某个线程的CPU占用率
NULL
mutex
被删除的互斥锁标识符注意:在删除互斥锁的时候不会释放等待这个锁的线程,因此在删除之前请确认没有线程在使用它
mutex
互斥锁标识符mutex
指定的互斥锁标记为锁定状态,如果mutex
已被其它线程锁定,则调用线程将会被阻塞,直到另一个线程释放这个互斥锁
参考:
C11:https://cloud.tencent.com/developer/section/1009716
POSIX:https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_mutex_lock.html
mutex
互斥锁标识符true
,失败则返回false
mutex_lock
的非阻塞版本
参考:
C11:https://cloud.tencent.com/developer/section/1009721
POSIX:https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_trylock.html
mutex
互斥锁标识符mutex
标识的互斥锁,如果有其它线程正在等待这个锁,则会唤醒优先级最高的那个线程
参考:
C11:https://cloud.tencent.com/developer/section/1009722
POSIX:https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_unlock.html
value
信号量初始值NULL
参考:
POSIX:https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_init.html WIN32:https://docs.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsemaphorea
sem
信号量标识符sem
信号量标识符参考:
POSIX:https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_post.html
WIN32:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-releasesemaphore
sem
信号量标识符参考:
POSIX:https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_wait.html
WIN32:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject
sem
信号量标识符timeout
超时时间(毫秒)timeout
指定的时间
参考:
POSIX:https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_timedwait.html
WIN32:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject
sem
信号量标识符参考:
POSIX:https://pubs.opengroup.org/onlinepubs/9699919799/functions/sem_getvalue.html
NULL
cond
条件变量标识符cond
条件变量标识符参考:
C11:https://cloud.tencent.com/developer/section/1009711
POSIX:https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_signal.html
cond
条件变量标识符参考:
C11:https://cloud.tencent.com/developer/section/1009708
POSIX:https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_broadcast.html
cond
条件变量标识符mutex
互斥锁标识符参考:
C11:https://cloud.tencent.com/developer/section/1009713
POSIX:https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_wait.html
cond
条件变量标识符mutex
互斥锁标识符timeout
超时时间(毫秒)参考:
C11:https://cloud.tencent.com/developer/section/1009712
POSIX:https://pubs.opengroup.org/onlinepubs/9699919799/functions/pthread_cond_timedwait.html
auto_reset
是否自动复位事件NULL
auto_reset
为true
时事件会在传递成功后自动复位
参考:
WIN32:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-createeventa
event
事件标识符event
事件标识符auto_reset
为true
,那么只唤醒第1个线程,并且将事件复位,
如果auto_reset
为false
,那么会唤醒所有线程,事件保持置位状态
参考:
WIN32:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-setevent
event
事件标识符参考:
WIN32:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-resetevent
event
事件标识符参考:
WIN32:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject
event
事件标识符timeout
等待时间(毫秒)timeout
设定的时间则退出等待
参考:
WIN32:https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitforsingleobject
可选功能的源码在sources/opt/
目录,每个功能对应一个c文件和h文件。
c文件是功能的具体实现,h文件是模块声明的接口。
要使用哪个功能就包含对应的头文件和源代码。
#include "event_flags.h"
NULL
event
事件组标识符event
事件组标识符bits
指定的事件标志位,并唤醒等待队列中想要获取bits
的线程event
事件组标识符bits
指定的事件标志位,此函数不会唤醒任何线程uint32_t event_flags_wait(event_flags_t event, uint32_t bits, uint32_t ops);
参数:
event
事件组标识符
bits
想要等待的标志位
ops
等待标志位的行为
EVENT_FLAGS_WAIT_ANY: 只要
bits
中的任意一位有效,函数立即返回;
EVENT_FLAGS_WAIT_ALL: 只有bits
中的所有位都有效,函数才能返回;
EVENT_FLAGS_AUTO_RESET: 函数返回时自动清零获取到的标志位;
返回:实际获取到的标志位状态
描述:等待1个或多个事件标志位
event
事件组标识符bits
想要等待的标志位ops
等待标志位的行为timeout
等待时间(毫秒)timeout
设定的时间。#include "soft_timer.h"
handler
定时器回调函数arg
回调函数的参数NULL
times
定时器标识符times
定时器标识符times
定时器标识符如果要使用软件定时器功能,需要用户创建一个线程,调用这个函数,用来承载定时器的执行。
这样做的目的是让用户控制软件定时器的线程优先级和栈空间。
#include "mpool.h"
block_size
内存块大小block_count
内存块总数NULL
mpool
标识符mpool
标识符NULL
mpool
标识符block
内存块指针#include "queue.h"
queue_t queue_create(uint32_t item_size, uint32_t queue_depth);
参数:
item_size
队列数据块大小
queue_depth
队列深度
返回:创建成功返回标识符,失败返回NULL
描述:创建一个数据队列。
void queue_delete(queue_t queue);
参数:
queue
标识符
返回:无
描述:删除数据队列。
void queue_clear(queue_t queue);
参数:
queue
标识符
返回:无
描述:清空数据队列。
bool queue_send(queue_t queue, void *item, uint32_t timeout);
参数:
queue
标识符
item
数据指针
timeout
超时时间(毫秒)
返回:成功返回true
,超时返回false
描述:向队列中发送一条数据。
bool queue_recv(queue_t queue, void *item, uint32_t timeout);
参数:
queue
标识符
item
数据指针
timeout
超时时间(毫秒)
返回:成功返回true
,超时返回false
描述:从队列中取出一条数据。
消息邮箱按照FIFO机制取出消息。取出的消息长度和发送的消息长度一致。
如果输入的buf长度小于消息长度,则丢弃超出buf长度的部分内容。
#include "mailbox.h"
mailbox_t mailbox_create(uint32_t size);
参数:
size
缓冲区长度
返回:创建成功返回标识符,失败返回NULL
描述:创建缓冲区。
void mailbox_delete(mailbox_t mbox);
参数:
mbox
标识符
返回:无
描述:删除缓冲区。
void mailbox_clear(mailbox_t mbox);
参数:
mbox
标识符
返回:无
描述:清空缓冲区。
uint32_t mailbox_post(mailbox_t mbox, void *buf, uint32_t len, uint32_t timeout);
参数:
mbox
标识符
buf
数据指针
len
数据长度
timeout
超时时间(毫秒)
返回:实际写入数据长度
描述:向缓冲区写入指定长度的数据。
uint32_t mailbox_wait(mailbox_t mbox, void *buf, uint32_t len, uint32_t timeout);
参数:
mbox
定时器标识符
buf
数据指针
len
数据长度
timeout
超时时间(毫秒)
返回:实际读出数据长度
描述:从缓冲区中读出指定长度的数据。
其它函数是与操作系统本身无关的,但在实现过程中引用的通用功能。
#include "list.h"
#include "fifo.h"
KLite核心代码完全使用C语言实现,由于不同CPU平台的差异性,有一些功能依赖于目标CPU平台,这些无法统一实现的函数,可能需要使用汇编才能实现
这些函数的实现代码放在sources/port/
目录。
kernel_init()
阶段被调用kernel_start()
阶段被调用通常用于启动滴答时钟定时器,并打开中断滴答时钟使用硬件定时器中断,中断周期通常为1ms,
也可以为其它任意值,只需要在中断服务程序中调用一次kernel_tick(1)
即可这里的参数1则代表时钟周期,如果是10ms中断一次,则应该传入10作为参数。
参数:无time
休眠的最长时间,单位毫秒stack_base
栈底指针stack_top
栈顶指针entry
线程入口函数地址arg
线程入口函数参数exit
线程出口函数地址对于ARM处理器使用的是满递减栈,因此返回的是新的栈顶指针。
核心思想是通过调度器(sched.c)维护3个TCB链表(队列):
就绪链表(m_ready_list): 此版本使用了8条就绪表,每个优先级对应一个表。
在早期的版本中,为了使代码实现简单,使用的是一条双向排序链表,高优先级在头,低优先级在尾
但这种方案排序时间会随着优先级数量和线程数量上升,一致性较差。
为了使KLite有更广泛的应用,因此现在替换为目前主流的优先级列表和位图搜索算法来调度。
理论上可以支持32级优先级(就绪表),但经过反复推敲,8个优先级已经能满足绝大多数的场景了。 因此目前的设定最多8个优先级,如果想改为32级也很简单就能实现。
睡眠链表(m_sleep_list): 当线程休眠时,会加入此表,在每一次滴答周期内都会检查有否有线程休眠结束,将其移到就绪表中。
等待链表(tcb->list_wait): 用于线程等待对象的阻塞。
每当需要切换线程时,就从就绪表中取出优先级最高的线程,将sched_tcb_next
的值修改为新线程,调用硬件接口完成上下文切换。
硬件切换接口cpu_contex_switch()
挂起指定中断,在该中断服务程序中完成线程切换。
切换过程:将当前上下文保存到sched_tcb_now->sp
栈中,并更新sp值,
然后从sched_tcb_next->sp
中取出上下文,
最后将sched_tcb_next
赋值到sched_tcb_now
。
KLite并不像其它物联网操作系统一样提供一站式的全家桶服务,它仅仅是个多线程调度内核,
但我们可以灵活选择适合自己的文件系统、网络协议栈、图形界面等更丰富的功能。
如果您在使用中发现任何BUG或者有好的改进建议,欢迎加入QQ群(317930646)或发送邮件至kerndev@foxmail.com
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。
Activity
Community
Health
Trend
Influence
:Code submit frequency
:React/respond to issue & PR etc.
:Well-balanced team members and collaboration
:Recent popularity of project
:Star counts, download counts etc.