OpenHarmony设备开发小型系统内核(LiteOS-A) 附录
版本:V3.2Beta
基本数据结构
双向链表
基本概念
双向链表是指含有往前和往后两个方向的链表,即每个结点中除存放下一个节点指针外,还增加一个指向前一个节点的指针。其头指针head是唯一确定的。从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点,这种数据结构形式使得双向链表在查找时更加方便,特别是大量数据的遍历。由于双向链表具有对称性,能方便地完成各种插入、删除等操作,但需要注意前后方向的操作。
功能说明
双向链表模块为用户提供下面几种功能,接口详细信息可以查看API参考。
功能分类 | 接口名 |
初始化链表 | - LOS_ListInit:将指定节点初始化为双向链表节点 - LOS_DL_LIST_HEAD:定义一个节点并初始化为双向链表节点 |
增加节点 | - LOS_ListAdd:将指定节点插入到双向链表头端 - LOS_ListHeadInsert:将指定节点插入到双向链表头端,同LOS_ListAdd - LOS_ListTailInsert:将指定节点插入到双向链表尾端 |
增加链表 | - LOS_ListAddList:将指定链表的头端插入到双向链表头端 - LOS_ListHeadInsertList:将指定链表的头端插入到双向链表头端 - LOS_ListTailInsertList:将指定链表的尾端插入到双向链表头端 |
删除节点 | - LOS_ListDelete:将指定节点从链表中删除 - LOS_ListDelInit:将指定节点从链表中删除,并使用该节点初始化链表 |
判断双向链表 | - LOS_ListEmpty:判断链表是否为空 - LOS_DL_LIST_IS_END:判断指定链表节点是否为链表尾端 - LOS_DL_LIST_IS_ON_QUEUE:判断链表节点是否在双向链表里 |
获取结构体信息 | - LOS_OFF_SET_OF:获取指定结构体内的成员相对于结构体起始地址的偏移量 - LOS_DL_LIST_ENTRY:获取双向链表中第一个链表节点所在的结构体地址,接口的第一个入参表示的是链表中的头节点,第二个入参是要获取的结构体名称,第三个入参是链表在该结构体中的名称 - LOS_ListPeekHeadType:获取双向链表中第一个链表节点所在的结构体地址,接口的第一个入参表示的是链表中的头节点,第二个入参是要获取的结构体名称,第三个入参是链表在该结构体中的名称。如果链表为空,返回NULL。 - LOS_ListRemoveHeadType:获取双向链表中第一个链表节点所在的结构体地址,并把第一个链表节点从链表中删除。接口的第一个入参表示的是链表中的头节点,第二个入参是要获取的结构体名称,第三个入参是链表在该结构体中的名称。如果链表为空,返回NULL。 - LOS_ListNextType:获取双向链表中指定链表节点的下一个节点所在的结构体地址。接口的第一个入参表示的是链表中的头节点,第二个入参是指定的链表节点,第三个入参是要获取的结构体名称,第四个入参是链表在该结构体中的名称。如果链表节点下一个为链表头结点为空,返回NULL。 |
遍历双向链表 | - LOS_DL_LIST_FOR_EACH:遍历双向链表 - LOS_DL_LIST_FOR_EACH_SAFE:遍历双向链表,并存储当前节点的后继节点用于安全校验 |
遍历包含双向链表的结构体 | - LOS_DL_LIST_FOR_EACH_ENTRY:遍历指定双向链表,获取包含该链表节点的结构体地址 - LOS_DL_LIST_FOR_EACH_ENTRY_SAFE:遍历指定双向链表,获取包含该链表节点的结构体地址,并存储包含当前节点的后继节点的结构体地址 |
开发流程
双向链表的典型开发流程:
- 调用LOS_ListInit/LOS_DL_LIST_HEAD初始双向链表。
- 调用LOS_ListAdd向链表插入节点。
- 调用LOS_ListTailInsert向链表尾部插入节点。
- 调用LOS_ListDelete删除指定节点。
- 调用LOS_ListEmpty判断链表是否为空。
- 调用LOS_ListDelInit删除指定节点并以此节点初始化链表。
说明:
- 需要注意节点指针前后方向的操作。
- 链表操作接口,为底层接口,不对入参进行判空,需要使用者确保传参合法。
- 如果链表节点的内存是动态申请的,删除节点时,要注意释放内存。
编程实例
实例描述
本实例实现如下功能:
- 初始化双向链表。
- 增加节点。
- 删除节点。
- 测试操作是否成功。
编程示例
本演示代码在./kernel/liteos_a/testsuites/kernel/src/osTest.c中编译验证,在TestTaskEntry中调用验证入口函数ListSample
示例代码如下:
#include "stdio.h"
#include "los_list.h"
static UINT32 ListSample(VOID)
{
LOS_DL_LIST listHead = {NULL,NULL};
LOS_DL_LIST listNode1 = {NULL,NULL};
LOS_DL_LIST listNode2 = {NULL,NULL};
//首先初始化链表
PRINTK("Initial head\n");
LOS_ListInit(&listHead);
//添加节点1和节点2,并校验他们的相互关系
LOS_ListAdd(&listHead, &listNode1);
if (listNode1.pstNext == &listHead && listNode1.pstPrev == &listHead) {
PRINTK("Add listNode1 success\n");
}
LOS_ListTailInsert(&listHead, &listNode2);
if (listNode2.pstNext == &listHead && listNode2.pstPrev == &listNode1) {
PRINTK("Tail insert listNode2 success\n");
}
//删除两个节点
LOS_ListDelete(&listNode1);
LOS_ListDelete(&listNode2);
//确认链表为空
if (LOS_ListEmpty(&listHead)) {
PRINTK("Delete success\n");
}
return LOS_OK;
}
结果验证
编译运行得到的结果为:
Initial head
Add listNode1 success
Tail insert listNode2 success
Delete success
位操作
基本概念
位操作是指对二进制数的bit位进行操作。程序可以设置某一变量为状态字,状态字中的每一bit位(标志位)可以具有自定义的含义。
功能说明
系统提供标志位的置1和清0操作,可以改变标志位的内容,同时还提供获取状态字中标志位为1的最高位和最低位的功能。用户也可以对系统的寄存器进行位操作。位操作模块为用户提供下面几种功能,接口详细信息可以查看API参考。
表1 位操作模块接口
功能分类 | 接口描述 |
置1/清0标志位 | - LOS_BitmapSet:对状态字的某一标志位进行置1操作 - LOS_BitmapClr:对状态字的某一标志位进行清0操作 |
获取标志位为1的bit位 | - LOS_HighBitGet:获取状态字中为1的最高位 - LOS_LowBitGet:获取状态字中为1的最低位 |
连续bit位操作 | - LOS_BitmapSetNBits:对状态字的连续标志位进行置1操作 - LOS_BitmapClrNBits:对状态字的连续标志位进行清0操作 - LOS_BitmapFfz:获取从最低有效位开始的第一个0的bit位 |
编程实例
实例描述
对数据实现位操作,本实例实现如下功能:
- 某一标志位置1。
- 获取标志位为1的最高bit位。
- 某一标志位清0。
- 获取标志位为1的最低bit位。
编程示例
本演示代码在./kernel/liteos_a/testsuites/kernel/src/osTest.c中编译验证,在TestTaskEntry中调用验证入口函数BitSample。
#include "los_bitmap.h"
#include "los_printf.h"
static UINT32 BitSample(VOID)
{
UINT32 flag = 0x10101010;
UINT16 pos;
PRINTK("\nBitmap Sample!\n");
PRINTK("The flag is 0x%8x\n", flag);
pos = 8;
LOS_BitmapSet(&flag, pos);
PRINTK("LOS_BitmapSet:\t pos : %d, the flag is 0x%0+8x\n", pos, flag);
pos = LOS_HighBitGet(flag);
PRINTK("LOS_HighBitGet:\t The highest one bit is %d, the flag is 0x%0+8x\n", pos, flag);
LOS_BitmapClr(&flag, pos);
PRINTK("LOS_BitmapClr:\t pos : %d, the flag is 0x%0+8x\n", pos, flag);
pos = LOS_LowBitGet(flag);
PRINTK("LOS_LowBitGet:\t The lowest one bit is %d, the flag is 0x%0+8x\n\n", pos, flag);
return LOS_OK;
}
结果验证
编译运行得到的结果为:
Bitmap Sample!
The flag is 0x10101010
LOS_BitmapSet: pos : 8, the flag is 0x10101110
LOS_HighBitGet:The highest one bit is 28, the flag is 0x10101110
LOS_BitmapClr: pos : 28, the flag is 0x00101110
LOS_LowBitGet: The lowest one bit is 4, the flag is 0x00101110
标准库
OpenHarmony内核使用musl libc库,支持标准POSIX接口,开发者可基于POSIX标准接口开发内核之上的组件及应用。
标准库接口框架
图1 POSIX接口框架
musl libc库支持POSIX标准,涉及的系统调用相关接口由OpenHarmony内核适配支持 ,以满足接口对外描述的功能要求。
标准库支持接口的详细情况请参考C库的API文档,其中也涵盖了与POSIX标准之间的差异说明。
编程实例
实例描述
在本示例中,主线程创建了THREAD_NUM个子线程,每个子线程启动后等待被主线程唤醒,主线程成功唤醒所有子线程后,子线程继续执行直至生命周期结束,同时主线程通过pthread_join方法等待所有线程执行结束。
编程示例
本演示代码在./kernel/liteos_a/testsuites/kernel/src/osTest.c中编译验证,在TestTaskEntry中调用验证入口函数ExamplePosix。
示例代码如下:
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define THREAD_NUM 3
int g_startNum = 0; /* 启动的线程数 */
int g_wakenNum = 0; /* 唤醒的线程数 */
struct testdata {
pthread_mutex_t mutex;
pthread_cond_t cond;
} g_td;
/* 子线程入口函数 */
static VOID *ChildThreadFunc(VOID *arg)
{
int rc;
pthread_t self = pthread_self();
/* 获取mutex锁 */
rc = pthread_mutex_lock(&g_td.mutex);
if (rc != 0) {
dprintf("ERROR:take mutex lock failed, error code is %d!\n", rc);
goto EXIT;
}
/* g_startNum计数加一,用于统计已经获得mutex锁的子线程个数 */
g_startNum++;
/* 等待cond条件变量 */
rc = pthread_cond_wait(&g_td.cond, &g_td.mutex);
if (rc != 0) {
dprintf("ERROR: pthread condition wait failed, error code is %d!\n", rc);
(void)pthread_mutex_unlock(&g_td.mutex);
goto EXIT;
}
/* 尝试获取mutex锁,正常场景,此处无法获取锁 */
rc = pthread_mutex_trylock(&g_td.mutex);
if (rc == 0) {
dprintf("ERROR: mutex gets an abnormal lock!\n");
goto EXIT;
}
/* g_wakenNum计数加一,用于统计已经被cond条件变量唤醒的子线程个数 */
g_wakenNum++;
/* 释放mutex锁 */
rc = pthread_mutex_unlock(&g_td.mutex);
if (rc != 0) {
dprintf("ERROR: mutex release failed, error code is %d!\n", rc);
goto EXIT;
}
EXIT:
return NULL;
}
static int ExamplePosix(VOID)
{
int i, rc;
pthread_t thread[THREAD_NUM];
/* 初始化mutex锁 */
rc = pthread_mutex_init(&g_td.mutex, NULL);
if (rc != 0) {
dprintf("ERROR: mutex init failed, error code is %d!\n", rc);
goto ERROROUT;
}
/* 初始化cond条件变量 */
rc = pthread_cond_init(&g_td.cond, NULL);
if (rc != 0) {
dprintf("ERROR: pthread condition init failed, error code is %d!\n", rc);
goto ERROROUT;
}
/* 批量创建THREAD_NUM个子线程 */
for (i = 0; i < THREAD_NUM; i++) {
rc = pthread_create(&thread[i], NULL, ChildThreadFunc, NULL);
if (rc != 0) {
dprintf("ERROR: pthread create failed, error code is %d!\n", rc);
goto ERROROUT;
}
}
dprintf("pthread_create ok\n");
/* 等待所有子线程都完成mutex锁的获取 */
while (g_startNum < THREAD_NUM) {
usleep(100);
}
/* 获取mutex锁,确保所有子线程都阻塞在pthread_cond_wait上 */
rc = pthread_mutex_lock(&g_td.mutex);
if (rc != 0) {
dprintf("ERROR: mutex lock failed, error code is %d\n", rc);
goto ERROROUT;
}
/* 释放mutex锁 */
rc = pthread_mutex_unlock(&g_td.mutex);
if (rc != 0) {
dprintf("ERROR: mutex unlock failed, error code is %d!\n", rc);
goto ERROROUT;
}
for (int j = 0; j < THREAD_NUM; j++) {
/* 在cond条件变量上广播信号 */
rc = pthread_cond_signal(&g_td.cond);
if (rc != 0) {
dprintf("ERROR: pthread condition failed, error code is %d!\n", rc);
goto ERROROUT;
}
}
sleep(1);
/* 检查是否所有子线程都已被唤醒 */
if (g_wakenNum != THREAD_NUM) {
dprintf("ERROR: not all threads awaken, only %d thread(s) awaken!\n", g_wakenNum);
goto ERROROUT;
}
dprintf("all threads awaked\n");
/* join所有子线程,即等待其结束 */
for (i = 0; i < THREAD_NUM; i++) {
rc = pthread_join(thread[i], NULL);
if (rc != 0) {
dprintf("ERROR: pthread join failed, error code is %d!\n", rc);
goto ERROROUT;
}
}
dprintf("all threads join ok\n");
/* 销毁cond条件变量 */
rc = pthread_cond_destroy(&g_td.cond);
if (rc != 0) {
dprintf("ERROR: pthread condition destroy failed, error code is %d!\n", rc);
goto ERROROUT;
}
return 0;
ERROROUT:
return -1;
}
验证结果
输出结果如下:
pthread_create ok
all threads awaked
all threads join ok
与Linux标准库差异
本节描述了OpenHarmony内核承载的标准库与Linux标准库之间存在的关键差异。更多差异详见C库API文档说明。
进程
- OpenHarmony用户态进程优先级只支持静态优先级且用户态可配置的优先级范围为10(最高优先级)-31(最低优先级)。
- OpenHarmony用户态线程优先级只支持静态优先级且用户态可配置的优先级范围为0(最高优先级)-31(最低优先级)。
- OpenHarmony进程调度策略只支持SCHED_RR, 线程调度策略支持SCHED_RR和SCHED_FIFO。
内存
与Linux mmap的差异
mmap接口原型为:void *mmap (void *addr, size_t length, int prot, int flags, int fd, off_t offset)。
其中,参数fd的生命周期实现与Linux glibc存在差异。具体体现在,glibc在成功调用mmap进行映射后,可以立即释放fd句柄。在OpenHarmony内核中,不允许用户在映射成功后立即关闭相关fd,只允许在取消映射munmap后再进行fd的close操作。如果用户不进行fd的close操作,操作系统将在进程退出时对该fd进行回收。
代码举例
Linux目前支持的情况如下:
int main(int argc, char *argv[])
{
int fd;
void *addr = NULL;
...
fd = open(argv[1], O_RDONLY);
if (fd == -1){
perror("open");
exit(EXIT_FAILURE);
}
addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, offset);
if (addr == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
close(fd); /* OpenHarmony does not support closing fd immediately after the mapping is successful. */
...
exit(EXIT_SUCCESS);
}
OpenHarmony支持的情况如下:
int main(int argc, char *argv[])
{
int fd;
void *addr = NULL;
...
fd = open(argv[1], O_RDONLY);
if (fd == -1) {
perror("open");
exit(EXIT_FAILURE);
}
addr = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd, offset);
if (addr == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
...
munmap(addr, length);
close(fd); /* Close fd after the munmap is canceled. */
exit(EXIT_SUCCESS);
}
文件系统
系统目录:用户无权限修改系统目录和设备挂载目录。包含/dev,/proc,/app,/bin,/data,/etc,/lib,/system,/usr目录。
用户目录:用户可以在该目录下进行文件创建、读写,但不能进行设备挂载。用户目录指/storage目录。
除系统目录与用户目录之外,用户可以自行创建文件夹进行设备的挂载。但是要注意,已挂载的文件夹及其子文件夹不允许重复或者嵌套挂载,非空文件夹不允许挂载。
信号
- 信号默认行为不支持STOP、CONTINUE、COREDUMP功能。
- 无法通过信号唤醒正在睡眠状态(举例:进程调用sleep函数进入睡眠)的进程。原因:信号机制无唤醒功能,当且仅当进程被CPU调度运行时才能处理信号内容。
- 进程退出后会发送SIGCHLD给父进程,发送动作无法取消。
- 信号仅支持1-30号信号,接收方收到多次同一信号,仅执行一次回调函数。
Time
OpenHarmony当前时间精度以tick计算,系统默认10ms/tick。sleep、timeout系列函数时间误差<=20ms。
文章转载自:https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/device-dev/kernel/kernel-small-apx-dll.md/