diff --git a/zh-cn/device-dev/kernel/Readme-CN.md b/zh-cn/device-dev/kernel/Readme-CN.md index 4f32b8e9e9822ea707a23a10f9e378c5f9b97e69..fdeb7b452e6580d5db0f55d9959528bacc4e740a 100755 --- a/zh-cn/device-dev/kernel/Readme-CN.md +++ b/zh-cn/device-dev/kernel/Readme-CN.md @@ -6,10 +6,7 @@ - 基础内核 - [中断管理](kernel-mini-basic-interrupt.md) - [任务管理](kernel-mini-basic-task.md) - - 内存管理 - - [基本概念](kernel-mini-basic-memory-basic.md) - - [静态内存](kernel-mini-basic-memory-static.md) - - [动态内存](kernel-mini-basic-memory-dynamic.md) + - [内存管理](kernel-mini-basic-memory.md) - 内核通信机制 - [事件](kernel-mini-basic-ipc-event.md) - [互斥锁](kernel-mini-basic-ipc-mutex.md) @@ -21,23 +18,16 @@ - [C++支持](kernel-mini-extend-support.md) - [CPU占用率](kernel-mini-extend-cpup.md) - [动态加载](kernel-mini-extend-dynamic-loading.md) - - 文件系统 - - [FAT](kernel-mini-extend-file-fat.md) - - [LittleFS](kernel-mini-extend-file-lit.md) + - [文件系统](kernel-mini-extend-file.md) - 内核调测 - - 内存调测 - - [内存信息统计](kernel-mini-memory-debug-mes.md) - - [内存泄漏检测](kernel-mini-memory-debug-det.md) - - [踩内存检测](kernel-mini-memory-debug-cet.md) + - [内存调测](kernel-mini-memory-debug.md) - [异常调测](kernel-mini-memory-exception.md) - [Trace调测](kernel-mini-memory-trace.md) - [LMS调测](kernel-mini-memory-lms.md) - 附录 - [内核编码规范](kernel-mini-appx-code.md) - [双向链表](kernel-mini-appx-data-list.md) - - 标准库支持 - - [CMSIS支持](kernel-mini-appx-lib-cmsis.md) - - [POSIX支持](kernel-mini-appx-lib-posix.md) + - [标准库支持](kernel-mini-appx-lib.md) - 小型系统内核 - [内核概述](kernel-small-overview.md) - 内核启动 @@ -71,13 +61,9 @@ - [虚拟动态共享库](kernel-small-bundles-share.md) - [轻量级进程间通信](kernel-small-bundles-ipc.md) - 文件系统 + - [文件系统概述](kernel-small-bundles-fs.md) - [虚拟文件系统](kernel-small-bundles-fs-virtual.md) - - 支持的文件系统 - - [FAT](kernel-small-bundles-fs-support-fat.md) - - [JFFS2](kernel-small-bundles-fs-support-jffs2.md) - - [NFS](kernel-small-bundles-fs-support-nfs.md) - - [Ramfs](kernel-small-bundles-fs-support-ramfs.md) - - [Procfs](kernel-small-bundles-fs-support-procfs.md) + - [支持的文件系统](kernel-small-bundles-fs-support.md) - [适配新的文件系统](kernel-small-bundles-fs-new.md) - 调测与工具 - Shell @@ -155,15 +141,7 @@ - [内存信息统计](kernel-small-debug-memory-info.md) - [内存泄漏检测](kernel-small-debug-memory-leak.md) - [踩内存检测](kernel-small-debug-memory-corrupt.md) - - 用户态内存调测 - - [基本概念](kernel-small-debug-user-concept.md) - - [运行机制](kernel-small-debug-user-function.md) - - 使用指导 - - [接口说明](kernel-small-debug-user-guide-api.md) - - [使用说明](kernel-small-debug-user-guide-use.md) - - [接口调用方式](kernel-small-debug-user-guide-use-api.md) - - [命令行参数方式](kernel-small-debug-user-guide-use-cli.md) - - [常见问题](kernel-small-debug-user-faqs.md) + - [用户态内存调测](kernel-small-debug-user.md) - 其他内核调测手段 - [临终遗言](kernel-small-debug-trace-other-lastwords.md) - [常见问题](kernel-small-debug-trace-other-faqs.md) diff --git a/zh-cn/device-dev/kernel/kernel-mini-appx-lib.md b/zh-cn/device-dev/kernel/kernel-mini-appx-lib.md index 8ce3db5c735a90579bb5693248740808c0664419..d2f4b54be58063e23b8f7fc145e96c555b4e27ca 100644 --- a/zh-cn/device-dev/kernel/kernel-mini-appx-lib.md +++ b/zh-cn/device-dev/kernel/kernel-mini-appx-lib.md @@ -1,7 +1,500 @@ # 标准库支持 +## CMSIS支持 -- **[CMSIS支持](kernel-mini-appx-lib-cmsis.md)** -- **[POSIX支持](kernel-mini-appx-lib-posix.md)** \ No newline at end of file +### 基本概念 + +[CMSIS](https://developer.arm.com/tools-and-software/embedded/cmsis)是Cortex Microcontroller Software Interface Standard(Cortex微控制器软件接口标准)的缩写,是对于那些基于ARM Cortex处理器的微控制器独立于供应商的硬件抽象层。它包含多个组件层,其中之一是RTOS层,该层定义了一套通用及标准化的RTOS API接口,减少了应用开发者对特定RTOS的依赖,方便用户软件的移植重用。该套API有2个版本,分别为版本1(CMSIS-RTOS v1)和版本2(CMSIS-RTOS v2),OpenHarmony LiteOS-M仅提供其版本2的实现。 + + +### 开发指导 + + +#### 接口说明 + +CMSIS-RTOS v2提供下面几种功能,接口详细信息可以查看API参考。 + + **表1** 内核信息与控制 + +| 接口名 | 接口描述 | +| -------- | -------- | +| osKernelGetInfo | 获取RTOS内核信息。 | +| osKernelGetState | 获取当前的RTOS内核状态。 | +| osKernelGetSysTimerCount | 获取RTOS内核系统计时器计数。 | +| osKernelGetSysTimerFreq | 获取RTOS内核系统计时器频率。 | +| osKernelInitialize | 初始化RTOS内核。 | +| osKernelLock | 锁定RTOS内核调度程序。 | +| osKernelUnlock | 解锁RTOS内核调度程序。 | +| osKernelRestoreLock | 恢复RTOS内核调度程序锁定状态。 | +| osKernelResume | 恢复RTOS内核调度程序。(暂未实现) | +| osKernelStart | 启动RTOS内核调度程序。 | +| osKernelSuspend | 挂起RTOS内核调度程序。(暂未实现) | +| osKernelGetTickCount | 获取RTOS内核滴答计数。 | +| osKernelGetTickFreq | 获取RTOS内核滴答频率。 | + + **表2** 线程管理 + +| 接口名 | 接口描述 | +| -------- | -------- | +| osThreadDetach | 分离线程(线程终止时可以回收线程存储)。(暂未实现) | +| osThreadEnumerate | 枚举活动线程。(暂未实现) | +| osThreadExit | 终止当前正在运行的线程的执行。 | +| osThreadGetCount | 获取活动线程的数量。 | +| osThreadGetId | 返回当前正在运行的线程的线程ID。 | +| osThreadGetName | 获取线程的名称。 | +| osThreadGetPriority | 获取线程的当前优先级。 | +| osThreadGetStackSize | 获取线程的堆栈大小。 | +| osThreadGetStackSpace | 根据执行期间的堆栈水印记录获取线程的可用堆栈空间。 | +| osThreadGetState | 获取线程的当前线程状态。 | +| osThreadJoin | 等待指定线程终止。(暂未实现) | +| osThreadNew | 创建一个线程并将其添加到活动线程中。 | +| osThreadResume | 恢复线程的执行。 | +| osThreadSetPriority | 更改线程的优先级。 | +| osThreadSuspend | 暂停执行线程。 | +| osThreadTerminate | 终止线程的执行。 | +| osThreadYield | 将控制权传递给处于就绪状态的下一个线程。 | + + **表3** 线程标志 + +| 接口名 | 接口描述 | +| -------- | -------- | +| osThreadFlagsSet | 设置线程的指定线程标志。(暂未实现) | +| osThreadFlagsClear | 清除当前正在运行的线程的指定线程标志。(暂未实现) | +| osThreadFlagsGet | 获取当前正在运行的线程的当前线程标志。(暂未实现) | +| osThreadFlagsWait | 等待当前正在运行的线程的一个或多个线程标志发出信号。(暂未实现) | + + **表4** 事件标志 + +| 接口名 | 接口描述 | +| -------- | -------- | +| osEventFlagsGetName | 获取事件标志对象的名称。(暂未实现) | +| osEventFlagsNew | 创建并初始化事件标志对象。 | +| osEventFlagsDelete | 删除事件标志对象。 | +| osEventFlagsSet | 设置指定的事件标志。 | +| osEventFlagsClear | 清除指定的事件标志。 | +| osEventFlagsGet | 获取当前事件标志。 | +| osEventFlagsWait | 等待一个或多个事件标志被发出信号。 | + + **表5** 通用等待函数 + +| 接口名 | 接口描述 | +| -------- | -------- | +| osDelay | 等待超时(时间延迟)。 | +| osDelayUntil | 等到指定时间。 | + + **表6** 计时器管理 + +| 接口名 | 接口描述 | +| -------- | -------- | +| osTimerDelete | 删除计时器。 | +| osTimerGetName | 获取计时器的名称。(暂未实现) | +| osTimerIsRunning | 检查计时器是否正在运行。 | +| osTimerNew | 创建和初始化计时器。 | +| osTimerStart | 启动或重新启动计时器。 | +| osTimerStop | 停止计时器。 | + + **表7** 互斥管理 + +| 接口名 | 接口描述 | +| -------- | -------- | +| osMutexAcquire | 获取互斥或超时(如果已锁定)。 | +| osMutexDelete | 删除互斥对象。 | +| osMutexGetName | 获取互斥对象的名称。(暂未实现) | +| osMutexGetOwner | 获取拥有互斥对象的线程。 | +| osMutexNew | 创建并初始化Mutex对象。 | +| osMutexRelease | 释放由osMutexAcquire获取的Mutex。 | + + **表8** 信号量 + +| 接口名 | 接口描述 | +| -------- | -------- | +| osSemaphoreAcquire | 获取信号量令牌或超时(如果没有可用的令牌)。 | +| osSemaphoreDelete | 删除一个信号量对象。 | +| osSemaphoreGetCount | 获取当前信号量令牌计数。 | +| osSemaphoreGetName | 获取信号量对象的名称。(暂未实现) | +| osSemaphoreNew | 创建并初始化一个信号量对象。 | +| osSemaphoreRelease | 释放信号量令牌,直到初始最大计数。 | + + **表9** 内存池 + +| 接口名 | 接口描述 | +| -------- | -------- | +| osMemoryPoolAlloc | 从内存池分配一个内存块。 | +| osMemoryPoolDelete | 删除内存池对象。 | +| osMemoryPoolFree | 将分配的内存块返回到内存池。 | +| osMemoryPoolGetBlockSize | 获取内存池中的内存块大小。 | +| osMemoryPoolGetCapacity | 获取内存池中最大的内存块数。 | +| osMemoryPoolGetCount | 获取内存池中使用的内存块数。 | +| osMemoryPoolGetName | 获取内存池对象的名称。 | +| osMemoryPoolGetSpace | 获取内存池中可用的内存块数。 | +| osMemoryPoolNew | 创建并初始化一个内存池对象。 | + + **表10** 消息队列 + +| 接口名 | 接口描述 | +| -------- | -------- | +| osMessageQueueDelete | 删除消息队列对象。 | +| osMessageQueueGet | 从队列获取消息,或者如果队列为空,则从超时获取消息。 | +| osMessageQueueGetCapacity | 获取消息队列中的最大消息数。 | +| osMessageQueueGetCount | 获取消息队列中排队的消息数。 | +| osMessageQueueGetMsgSize | 获取内存池中的最大消息大小。 | +| osMessageQueueGetName | 获取消息队列对象的名称。(暂未实现) | +| osMessageQueueGetSpace | 获取消息队列中消息的可用插槽数。 | +| osMessageQueueNew | 创建和初始化消息队列对象。 | +| osMessageQueuePut | 如果队列已满,则将消息放入队列或超时。 | +| osMessageQueueReset | 将消息队列重置为初始空状态。(暂未实现) | + + +#### 开发流程 + +CMSIS-RTOS2组件可以作为库或源代码提供(下图显示了库)。通过添加CMSIS-RTOS2组件(通常是一些配置文件),可以将基于CMSIS的应用程序扩展为具有RTOS功能。只需包含cmsis_os2.h头文件就可以访问RTOS API函数,这使用户应用程序能够处理RTOS内核相关事件,而在更换内核时无需重新编译源代码。 + +静态对象分配需要访问RTOS对象控制块定义。特定于实现的头文件(下图中的os_xx .h)提供对此类控制块定义的访问。对于OpenHarmony LiteOS-M内核,由文件名以los_开头的头文件提供,这些文件包含OpenHarmony LiteOS-M内核的这些定义。 + +![zh-cn_image_0000001153834574](figures/zh-cn_image_0000001153834574.png) + + +#### 编程实例 + + +``` +#include ... +#include "cmsis_os2.h" + +/*---------------------------------------------------------------------------- + * 应用程序主线程 + *---------------------------------------------------------------------------*/ +void app_main (void *argument) { + // ... + for (;;) {} +} + +int main (void) { + // 系统初始化 + MySystemInit(); + // ... + + osKernelInitialize(); // 初始化CMSIS-RTOS + osThreadNew(app_main, NULL, NULL); // 创建应用程序主线程 + osKernelStart(); // 开始执行线程 + for (;;) {} +} +``` + +## POSIX支持 + + +### 基本概念 + +OpenHarmony内核使用**musl libc**库以及自研接口,支持部分标准POSIX接口,开发者可基于POSIX标准接口开发内核之上的组件及应用。 + + +### 开发指导 + + +#### 接口说明 + + **表1** process + +| 需要包含的头文件 | 接口名 | 描述 | +| -------- | -------- | -------- | +| \#include <stdlib.h> | void abort(void); | 中止线程执行 | +| \#include <assert.h> | void assert(scalar expression); | 断言为假终止线程 | +| \#include <pthread.h> | int pthread_cond_destroy(pthread_cond_t \*cond); | 销毁条件变量 | +| \#include <pthread.h> | int pthread_cond_init(pthread_cond_t \*restrict co
nd, const pthread_condattr_t \*restrict attr); | 初始化条件变量 | +| \#include <pthread.h> | int pthread_cond_timedwait(pthread_cond_t \*restr
ict cond, pthread_mutex_t \*restrict mutex, const st
ruct timespec \*restrict abstime); | 等待条件 | +| \#include <pthread.h> | int pthread_condattr_init(pthread_condattr_t \*attr); | 初始化条件变量属性对象 | +| \#include <pthread.h> | int pthread_mutex_unlock(pthread_mutex_t \*mutex); | 解锁互斥锁 | +| \#include <pthread.h> | int pthread_create(pthread_t \*thread, const pthread_
attr_t \*attr, void \*(\*start_routine)(void \*), void \*arg); | 创建一个新的线程 | +| \#include <pthread.h> | int pthread_join(pthread_t thread, void \*\*retval); | 等待指定的线程结束 | +| \#include <pthread.h> | pthread_t pthread_self(void); | 获取当前线程的ID | +| \#include <pthread.h> | int pthread_getschedparam(pthread_t thread, int \*
policy, struct sched_param \*param); | 获取线程的调度策略和参数 | +| \#include <pthread.h> | int pthread_setschedparam(pthread_t thread, int
policy, const struct sched_param \*param); | 设置线程的调度策略和参数 | +| \#include <pthread.h> | int pthread_mutex_init(pthread_mutex_t \*__restrict m
, const pthread_mutexattr_t \*__restrict a); | 初始化互斥锁 | +| \#include <pthread.h> | int pthread_mutex_lock(pthread_mutex_t \*m); | 互斥锁加锁操作 | +| \#include <pthread.h> | int pthread_mutex_trylock(pthread_mutex_t \*m); | 互斥锁尝试加锁操作 | +| \#include <pthread.h> | int pthread_mutex_destroy(pthread_mutex_t \*m); | 销毁互斥锁 | +| \#include <pthread.h> | int pthread_attr_init(pthread_attr_t \*attr); | 初始化线程属性对象 | +| \#include <pthread.h> | int pthread_attr_destroy(pthread_attr_t \*attr); | 销毁线程属性对象 | +| \#include <pthread.h> | int pthread_attr_getstacksize(const pthread_attr
_t \*attr, size_t \*stacksize); | 获取线程属性对象的堆栈大小 | +| \#include <pthread.h> | int pthread_attr_setstacksize(pthread_attr_t \*attr
, size_t stacksize); | 设置线程属性对象的堆栈大小 | +| \#include <pthread.h> | int pthread_attr_getschedparam(const pthread_
attr_t \*attr, struct sched_param \*param); | 获取线程属性对象的调度参数属性 | +| \#include <pthread.h> | int pthread_attr_setschedparam(pthread_attr_t \*
attr, const struct sched_param \*param); | 设置线程属性对象的调度参数属性 | +| \#include <pthread.h> | int pthread_getname_np(pthread_t pthread, char
\*name, size_t len); | 获取线程名称 | +| \#include <pthread.h> | int pthread_setname_np(pthread_t pthread, const
char \*name); | 设置线程名称 | +| \#include <pthread.h> | int pthread_cond_broadcast(pthread_cond_t \*c); | 解除若干已被等待条件阻塞的线程 | +| \#include <pthread.h> | int pthread_cond_signal(pthread_cond_t \*c); | 解除被阻塞的线程 | +| \#include <pthread.h> | int pthread_cond_wait(pthread_cond_t \*__restrict
c, pthread_mutex_t \*__restrict m); | 等待条件 | + + **表2** fs + +| 需要包含的头文件 | 接口名 | 描述 | +| -------- | -------- | -------- | +| \#include <libgen.h> | char \*dirname(char \*path); | 获取目录名 | +| \#include <dirent.h> | struct dirent \*readdir(DIR \*dirp); | 读目录 | +| \#include <sys/stat.h> | int stat(const char \*restrict path, struct stat \*restrict buf); | 获取文件信息 | +| \#include <unistd.h> | int unlink(const char \*pathname); | 删除文件 | +| \#include <fcntl.h | int open(const char \*path, int oflags, ...); | 用于打开文件,如文件不存在,创建文件并打开 | +| \#include <nistd.h> | int close(int fd); | 关闭文件 | +| \#include <stdio.h> | int rename(const char \*oldpath, const char \*newpath); | 重命名指定的文件 | +| \#include <dirent.h> | DIR  \*opendir(const char \*dirname); | 打开指定目录 | +| \#include <dirent.h> | int closedir(DIR \*dir); | 关闭指定目录 | +| \#include <sys/mount.h> | int mount(const char \*source, const char \*target, con
st char \*filesystemtype, unsigned long mountflags, c
onst void \*data); | 挂载文件系统 | +| \#include <sys/mount.h> | int umount(const char \*target); | 卸载文件系统 | +| \#include <sys/mount.h> | int umount2(const char \*target, int flag); | 卸载文件系统 | +| \#include <sys/stat.h> | int fsync(int fd); | 将与指定文件描述符关联的文件同步到存储设备 | +| \#include <sys/stat.h> | int mkdir(const char \*pathname, mode_t mode); | 创建目录 | +| \#include <unistd.h> | int rmdir(const char \*path); | 删除目录 | +| \#include <sys/stat.h> | int fstat(int fd, struct stat \*buf); | 获取文件状态信息 | +| \#include <sys/statfs.h> | int statfs(const char \*path, struct statfs \*buf); | 获取指定路径下文件的文件系统信息 | + + **表3** time + +| 需要包含的头文件 | 接口名 | 描述 | +| -------- | -------- | -------- | +| \#include <sys/time.h> | int gettimeofday(struct timeval \*tv, struct timezone \*tz); | 获取时间。当前暂无时区概念,tz返回为空 | +| \#include <time.h> | struct tm \*gmtime(const time_t \*timep); | 将日期和时间转换为细分时间或ASCII | +| \#include <time.h> | struct tm \*localtime(const time_t \*timep); | 获取时间 | +| \#include <time.h> | struct tm \*localtime_r(const time_t \*timep, struct tm \*result); | 获取时间 | +| \#include <time.h> | time_t mktime(struct tm \*tm); | 将日期和时间转换为细分时间或ASCII | +| \#include <time.h> | size_t strftime(char \*s, size_t max, const char \*
format,const struct tm \*tm); | 格式化日期和时间字符串 | +| \#include <time.h> | time_t time(time_t \*tloc); | 获得日历时间 | +| \#include <sys/times.h> | clock_t times(struct tms \*buf); | 获取线程时间 | +| \#include <unistd.h> | int usleep(useconds_t usec); | 休眠(微秒单位) | +| \#include <time.h> | int nanosleep(const struct timespec \*tspec1, struct
timespec \*tspec2); | 暂停当前线程直到指定的时间到达 | +| \#include <time.h> | int clock_gettime(clockid_t id, struct timespec \*tspec); | 获取时钟的时间 | +| \#include <time.h> | int timer_create(clockid_t id, struct sigevent \*__
restrict evp, timer_t \*__restrict t); | 为线程创建计时器 | +| \#include <time.h> | int timer_delete(timer_t t); | 为线程删除计时器 | +| \#include <time.h> | int timer_settime(timer_t t, int flags, const struct
itimerspec \*__restrict val, struct itimerspec \*__restrict old); | 为线程设置计时器 | +| \#include <time.h> | time_t time (time_t \*t); | 获取时间 | +| \#include <time.h> | char \*strptime(const char \*s, const char \*format, struct tm \*tm); | 将时间的字符串表示形式转换为时间tm结构 | + + **表4** util + +| 需要包含的头文件 | 接口名 | 描述 | +| -------- | -------- | -------- | +| \#include <stdlib.h> | int atoi(const char \*nptr); | 字符串转换整型(int) | +| \#include <stdlib.h> | long atol(const char \*nptr); | 字符串转换整型(long) | +| \#include <stdlib.h> | long long atoll(const char \*nptr); | 字符串转换整型(long long) | +| \#include <ctype.h> | int isalnum(int c); | 检查字母数字字符 | +| \#include <ctype.h> | int isascii(int c); | 检查ASCII | +| \#include <ctype.h> | int isdigit(int c); | 检查数字字符 | +| \#include <ctype.h> | int islower(int c); | 检查小写字符 | +| \#include <ctype.h> | int isprint(int c); | 检查任何可打印字符,包括空格 | +| \#include <ctype.h> | int isspace(int c); | 检查空格字符 | +| \#include <ctype.h> | int isupper(int c); | 检查所传的字符是否是大写字母 | +| \#include <ctype.h> | int isxdigit(int c); | 判断字符是否为十六进制数 | +| \#include <stdlib.h> | long int random (void); | 生成伪随机数 | +| \#include <stdlib.h> | void srandom(unsigned int seed); | 初始化随机数生成器 | +| \#include <ctype.h> | int tolower(int c); | 字母转换成小写 | +| \#include <ctype.h> | int toupper(int c); | 字母转换成大写 | +| \#include <stdarg.h> | type va_arg(va_list ap, type); | 获取可变参数的当前参数,返回指定类型并将指针指向下一参数 | +| \#include <stdarg.h> | void va_copy(va_list dest, va_list src); | 复制参数 | +| \#include <stdarg.h> | void va_end(va_list ap); | 清空va_list可变参数列表 | +| \#include <stdarg.h> | void va_start(va_list ap, last); | 定义变长参数列表的起始位置 | +| \#include <string.h> | char \*strchr(const char \*s, int c); | 在字符串中定位字符 | +| \#include <string.h> | int strcmp(const char \*s1, const char \*s2); | 比较字符串 | +| \#include <string.h> | size_t strcspn(const char \*s, const char \*reject); | 获取前缀子串的长度 | +| \#include <string.h> | char \*strdup(const char \*s); | 字符串拷贝到新建的位置处 | +| \#include <string.h> | size_t strlen(const char \*s); | 计算字符串长度 | +| \#include <strings.h> | int strncasecmp(const char \*s1, const char \*s2, size_t n); | 比较固定长度字符串(忽略大小写) | +| \#include <strings.h> | int strcasecmp(const char \*s1, const char \*s2); | 比较字符串(忽略大小写) | +| \#include <string.h> | int strncmp(const char \*s1, const char \*s2, size_t n); | 比较字符串(指定长度) | +| \#include <string.h> | char \*strrchr(const char \*s, int c); | 在字符串中定位字符 | +| \#include <string.h> | char \*strstr(const char \*haystack, const char \*needle); | 寻找指定的子串 | +| \#include <stdlib.h> | long int strtol(const char \*nptr, char \*\*endptr, int base); | 将字符串转换为long型整数 | +| \#include <stdlib.h> | unsigned long int strtoul(const char \*nptr, char
\*\*endptr, int base); | 将字符串转换为unsigned long型整数 | +| \#include <stdlib.h> | unsigned long long int strtoull(const char \*nptr,
char \*\*endptr,int base); | 将字符串转换为unsigned long long型整数 | +| \#include <regex.h> | int regcomp(regex_t \*preg, const char \*regex,
int cflags); | 编译正则表达式 | +| \#include <regex.h> | int regexec(const regex_t \*preg, const char \*
string, size_t nmatch,regmatch_t pmatch[], int eflags); | 匹配正则表达式 | +| \#include <regex.h> | void regfree(regex_t \*preg); | 释放正则表达式 | +| \#include <string.h> | char \*strerror(int errnum); | 返回描述错误号的字符串 | + + **表5** math + +| 需要包含的头文件 | 接口名 | 描述 | +| -------- | -------- | -------- | +| \#include <stdlib.h> | int abs(int i); | 取绝对值 | +| \#include <math.h> | double log(double x); | 自然对数函数 | +| \#include <math.h> | double pow(double x, double y); | 求x的指数y次幂 | +| \#include <math.h> | double round(double x); | 从零开始,舍入到最接近的整数 | +| \#include <math.h> | double sqrt(double x); | 平方根 | + + **表6** IO + +| 需要包含的头文件 | 接口名 | 描述 | +| -------- | -------- | -------- | +| \#include <stdio.h> | void clearerr(FILE \*stream); | 清除流的文件结尾和错误指示 | +| \#include <stdio.h> | int fclose(FILE \*stream); | 关闭文件流 | +| \#include <stdio.h> | FILE \*fdopen(int fd, const char \*mode); | 通过文件描述符打开文件流 | +| \#include <stdio.h> | int feof(FILE \*stream); | 检测返回文件末尾指示位 | +| \#include <stdio.h> | int fflush(FILE \*stream); | 刷新流 | +| \#include <stdio.h> | char \*fgets(char \*s, int size, FILE \*stream); | 读取流的下一行 | +| \#include <stdio.h> | int fileno(FILE \*stream); | 返回流的文件描述符 | +| \#include <stdio.h> | FILE \*fopen(const char \*path, const char \*mode); | 打开流 | +| \#include <stdio.h> | int fputs(const char \*s, FILE \*stream); | 向指定流写入一行 | +| \#include <stdio.h> | size_t fread(void \*ptr, size_t size, size_t nmemb,
FILE \*stream); | 读一个流 | +| \#include <stdio.h> | int fseek(FILE \*stream, long offset, int whence); | 设置流指针的位置 | +| \#include <stdio.h> | long ftell(FILE \*stream); | 获取流指针的位置 | +| \#include <stdio.h> | size_t fwrite(const void \*ptr, size_t size, size_t
nmemb,FILE \*stream); | 向流写入 | +| \#include <stdio.h> | void perror(const char \*s); | 打印系统错误信息 | +| \#include <stdio.h> | void rewind(FILE \*stream); | 重新定位流 | +| \#include <unistd.h> | ssize_t write(int fd, const void \*buf, size_t size); | 写文件内容 | +| \#include <unistd.h> | ssize_t read(int fd, void \*buf, size_t size); | 读文件内容 | + + **表7** net + +| 需要包含的头文件 | 接口名 | 描述 | +| -------- | -------- | -------- | +| \#include <sys/socket.h> | void freeaddrinfo(struct addrinfo \*res); | 释放调用getaddrinfo所分配的动态内存 | +| \#include <sys/socket.h> | int getaddrinfo(const char \*restrict nodename,const
char \*restrict servname,const struct addrinfo \*restrict
hints,struct addrinfo \*\*restrict res); | 网络地址和服务转换 | +| \#include <sys/socket.h> | int getnameinfo(const struct sockaddr \*restrict sa,
socklen_t salen,char \*restrict node, socklen_t nodelen
, char \*restrict service,socklen_t servicelen, int flags); | 以协议无关的方式进行地址到名称的转换 | +| \#include <net/if.h> | unsigned int if_nametoindex(const char \*ifname); | 通过网络接口名得到索引 | +| \#include <arpa/inet.h> | in_addr_t inet_addr(const char \*cp); | 网络主机地址点分十进制形式转换位二进制形式 | +| \#include <arpa/inet.h> | char \*inet_ntoa(struct in_addr in); | 网络主机地址二进制形式转换位点分十进制形式 | +| \#include <arpa/inet.h> | const char \*inet_ntop(int af, const void \*src,char \*dst,
socklen_t size); | 网络地址转换 | +| \#include <arpa/inet.h> | int inet_pton(int af, const char \*src, void \*dst); | 网络地址转换 | +| \#include <sys/socket.h> | int listen(int sockfd, int backlog); | 监听套接字 | +| \#include <sys/socket.h> | ssize_t recvmsg(int sockfd, struct msghdr \*msg, int flags); | 从套接字接收消息.只支持iov大小为1的场景,且不支持ancillary消息 | +| \#include <sys/socket.h> | ssize_t send(int sockfd, const void \*buf, size_t len, int flags); | 从socket发送消息 | +| \#include <sys/socket.h> | ssize_t sendmsg(int sockfd, const struct msghdr \*msg, int flags); | 从socket发送消息。不支持ancillary消息 | +| \#include <sys/socket.h> | ssize_t sendto(int sockfd, const void \*buf, size_t len, int
flags,const struct sockaddr \*dest_addr, socklen_t addrlen); | 从socket发送消息 | +| \#include <sys/socket.h> | int setsockopt(int sockfd, int level, int optname,const
void \*optval, socklen_t optlen); | 设置与套接字关联的选项 | + + **表8** mem + +| 需要包含的头文件 | 接口名 | 描述 | +| -------- | -------- | -------- | +| \#include <string.h> | int memcmp(const void \*s1, const void \*s2, size_t n); | 内存比较 | +| \#include <string.h> | void \*memcpy(void \*dest, const void \*src, size_t n); | 内存拷贝 | +| \#include <string.h> | void \*memset(void \*s, int c, size_t n); | 内存初始化 | +| \#include <stdlib.h> | void \*realloc(void \*ptr, size_t size); | 重分配内存 | +| \#include <stdlib.h> | void \*malloc(size_t size); | 动态分配内存块大小 | +| \#include <stdlib.h> | void free(void \*ptr); | 释放ptr所指向的内存空间 | + + **表9** IPC + +| 需要包含的头文件 | 接口名 | 描述 | +| -------- | -------- | -------- | +| \#include <semaphore.h> | int sem_timedwait(sem_t \*sem, const struct
 timespec \*abs_timeout); | 计时锁定信号量 | +| \#include <semaphore.h> | int sem_destroy(sem_t \*sem); | 销毁指定的无名信号量 | +| \#include <semaphore.h> | int sem_init(sem_t \*sem, int pshared
, unsigned int value); | 创建并初始化一个无名信号量 | +| \#include <semaphore.h> | int sem_post(sem_t \*sem); | 增加信号量计数 | +| \#include <semaphore.h> | int sem_wait(sem_t \*sem); | 获取信号量 | +| \#include <mqueue.h> | mqd_t mq_open(const char \*mqName,
 int openFlag, ...); | 此API用于打开一个具有指定名称的已有消息队列或创建一个新的消息队列 | +| \#include <mqueue.h> | int mq_close(mqd_t personal); | 此API用于关闭具有指定描述符的消息队列 | +| \#include <mqueue.h> | int mq_unlink(const char \*mqName); | 此API用于删除具有指定名称的消息队列 | +| \#include <mqueue.h> | int mq_send(mqd_t personal, const
 char \*msg,size_t msgLen, unsigned int msgPrio); | 此API用于将具有指定内容和长度的消息放入具有指定描述符的消息队列中 | +| \#include <mqueue.h> | ssize_t mq_receive(mqd_t personal, char \*msg,
size_t msgLen, unsigned int \*msgPrio); | 此API用于从具有指定描述符的消息队列中删除最老的消息,并将其放入msg_ptr所指向的缓冲区中 | +| \#include <mqueue.h> | int mq_timedsend(mqd_t personal, const char
\*msg, size_t msgLen, unsigned int msgPrio, c
onst struct timespec \*absTimeout) | 此API用于在预定时间将具有指定内容和长度的消息放入具有描述符的消息队列中 | +| \#include <mqueue.h> | ssize_t mq_timedreceive(mqd_t personal, char
\*msg, size_t msgLen, unsigned int \*msgPrio,
const struct timespec \*absTimeout); | 此API用于从具有指定描述符的消息队列消息中获取具有指定消息内容和长度的消息 | +| \#include <mqueue.h> | int mq_setattr(mqd_t mqdes, const struct mq_
attr \*__restrict newattr, struct mq_attr \*__restrict oldattr); | 设置描述符指定的消息队列属性 | +| \#include <libc.h> | const char \*libc_get_version_string(void); | 获取libc版本字符串 | +| \#include <libc.h> | int libc_get_version(void); | 获取libc版本号 | + + +#### 注意事项 + +常用错误码对照表: + +| 错误码 | 值 | 描述 | 含义 | +| -------- | -------- | -------- | -------- | +| ENOERR | 0 | Success | 成功 | +| EPERM | 1 | Operation not permitted | 操作不允许 | +| ENOENT | 2 | No such file or directory | 没有这样的文件或目录 | +| ESRCH | 3 | No such process | 没有这样的进程(暂不支持) | +| EINTR | 4 | Interrupted system call | 系统调用被中断 | +| EIO | 5 | I/O error | I/O错误 | +| ENXIO | 6 | No such device or address | 没有这样的设备或地址 | +| E2BIG | 7 | Arg list too long | 参数列表太长 | +| ENOEXEC | 8 | Exec format error | 执行格式错误 | +| EBADF | 9 | Bad file number | 坏的文件描述符 | +| ECHILD | 10 | No child processes | 没有子进程(暂不支持) | +| EAGAIN | 11 | Try again | 资源暂时不可用 | +| ENOMEM | 12 | Out of memory | 内存溢出 | +| EACCES | 13 | Permission denied | 拒绝许可 | +| EFAULT | 14 | Bad address | 错误的地址 | +| ENOTBLK | 15 | Block device required | 块设备请求 | +| EBUSY | 16 | Device or resource busy | 设备或资源忙 | +| EEXIST | 17 | File exists | 文件存在 | +| EXDEV | 18 | Cross-device link | 无效的交叉链接 | +| ENODEV | 19 | No such device | 设备不存在 | +| ENOTDIR | 20 | Not a directory | 不是一个目录 | +| EISDIR | 21 | Is a directory | 是一个目录 | +| EINVAL | 22 | Invalid argument | 无效的参数 | +| ENFILE\* | 23 | File table overflow | 打开太多的文件系统 | +| EMFILE | 24 | Too many open files | 打开的文件过多 | +| EFBIG | 27 | File too large | 文件太大 | +| ENOSPC | 28 | No space left on device | 设备上没有空间 | +| ESPIPE | 29 | Illegal seek | 非法移位 | +| EROFS | 30 | Read-only file system | 只读文件系统 | +| EMLINK | 31 | Too many links | 太多的链接 | +| EDOM | 33 | Math argument out of domain | 数值结果超出范围 | +| ERANGE | 34 | Math result not representable | 数值结果不具代表性 | +| EDEADLK | 35 | Resource deadlock would occur | 资源死锁错误 | +| ENAMETOOLONG | 36 | Filename too long | 文件名太长 | +| ENOLCK | 37 | No record locks available | 没有可用锁 | +| ENOSYS | 38 | Function not implemented | 功能没有实现 | +| ENOTEMPTY | 39 | Directory not empty | 目录不空 | +| ELOOP | 40 | Too many symbolic links encountered | 符号链接层次太多 | +| ENOMSG | 42 | No message of desired type | 没有期望类型的消息 | +| EIDRM | 43 | Identifier removed | 标识符删除 | +| ELNRNG | 48 | Link number out of range | 链接数超出范围 | +| EBADR | 53 | Invalid request descriptor | 请求描述符无效 | +| EBADRQC | 56 | Invalid request code | 无效的请求代码 | +| ENOSTR | 60 | Device not a stream | 设备不是字符流 | +| ENODATA | 61 | No data available | 无可用数据 | +| ETIME | 62 | Timer expired | 计时器过期 | +| EPROTO | 71 | Protocol error | 协议错误 | +| EBADMSG | 74 | Not a data message | 非数据消息 | +| EOVERFLOW | 75 | Value too large for defined data type | 值太大,对于定义数据类型 | +| EMSGSIZE | 90 | Message too long | 消息太长 | + + +#### 编程实例 + +demo功能: + +创建一个线程并将父线程中的信息传递给子线程,在子线程中打印传递过来的信息和自身线程id值。 + + +``` +#include +#include + +pthread_t ntid; + +void *ThreadFn(void *arg) +{ + pthread_t tid; + while(1) { + tid = pthread_self(); + printf("\n++++++++++++++ %s %s tid = %d ++++++++++++++\n", (char*)arg, __FUNCTION__, tid); + } + return ((void *)0); +} + +void DemoForTest() +{ + int err; + char* str = "Hello world"; + err = pthread_create(&ntid, NULL, ThreadFn, (void*)str); + if(err != 0) { + printf("can't create thread\n"); + } +} + +``` + +执行DemoForTest运行结果如下: + + +``` +++++++++++++++ Hello world ThreadFn tid = 48 ++++++++++++++ + +++++++++++++++ Hello world ThreadFn tid = 48 ++++++++++++++ + +++++++++++++++ Hello world ThreadFn tid = 48 ++++++++++++++ +``` diff --git a/zh-cn/device-dev/kernel/kernel-mini-basic-memory.md b/zh-cn/device-dev/kernel/kernel-mini-basic-memory.md index afc518c52ce6037e598cad7b54a883c0f8c7e6eb..5071ddf325680c7d939907b5bb2fd8d8f1368e77 100644 --- a/zh-cn/device-dev/kernel/kernel-mini-basic-memory.md +++ b/zh-cn/device-dev/kernel/kernel-mini-basic-memory.md @@ -1,9 +1,322 @@ # 内存管理 +## 基本概念 +内存管理模块管理系统的内存资源,它是操作系统的核心模块之一,主要包括内存的初始化、分配以及释放。 -- **[基本概念](kernel-mini-basic-memory-basic.md)** -- **[静态内存](kernel-mini-basic-memory-static.md)** +在系统运行过程中,内存管理模块通过对内存的申请/释放来管理用户和OS对内存的使用,使内存的利用率和使用效率达到最优,同时最大限度地解决系统的内存碎片问题。 -- **[动态内存](kernel-mini-basic-memory-dynamic.md)** \ No newline at end of file + +OpenHarmony LiteOS-M的内存管理分为静态内存管理和动态内存管理,提供内存初始化、分配、释放等功能。 + + +- 动态内存:在动态内存池中分配用户指定大小的内存块。 + - 优点:按需分配。 + - 缺点:内存池中可能出现碎片。 + +- 静态内存:在静态内存池中分配用户初始化时预设(固定)大小的内存块。 + - 优点:分配和释放效率高,静态内存池中无碎片。 + - 缺点:只能申请到初始化预设大小的内存块,不能按需申请。 +## 静态内存 + + +### 运行机制 + +静态内存实质上是一个静态数组,静态内存池内的块大小在初始化时设定,初始化后块大小不可变更。 + +静态内存池由一个控制块LOS_MEMBOX_INFO和若干相同大小的内存块LOS_MEMBOX_NODE构成。控制块位于内存池头部,用于内存块管理,包含内存块大小uwBlkSize,内存块数量uwBlkNum,已分配使用的内存块数量uwBlkCnt和空闲内存块链表stFreeList。内存块的申请和释放以块大小为粒度,每个内存块包含指向下一个内存块的指针pstNext。 + + **图1** 静态内存示意图 + ![zh-cn_image_0000001199352039](figures/zh-cn_image_0000001199352039.png) + + +### 开发指导 + + +#### 使用场景 + +当用户需要使用固定长度的内存时,可以通过静态内存分配的方式获取内存,一旦使用完毕,通过静态内存释放函数归还所占用内存,使之可以重复使用。 + + +#### 接口说明 + +OpenHarmony LiteOS-M的静态内存管理主要为用户提供以下功能,接口详细信息可以查看API参考。 + + **表1** 静态内存模块接口 + +| 功能分类 | 接口名 | +| -------- | -------- | +| 初始化静态内存池 | LOS_MemboxInit:初始化一个静态内存池,根据入参设定其起始地址、总大小及每个内存块大小。 | +| 清除静态内存块内容 | LOS_MemboxClr:清零从静态内存池中申请的静态内存块的内容。 | +| 申请、释放静态内存 | - LOS_MemboxAlloc:从指定的静态内存池中申请一块静态内存块。
- LOS_MemboxFree:释放从静态内存池中申请的一块静态内存块。 | +| 获取、打印静态内存池信息 | - LOS_MemboxStatisticsGet:获取指定静态内存池的信息,包括内存池中总内存块数量、已经分配出去的内存块数量、每个内存块的大小。
- LOS_ShowBox:打印指定静态内存池所有节点信息(打印等级是LOS_INFO_LEVEL),包括内存池起始地址、内存块大小、总内存块数量、每个空闲内存块的起始地址、所有内存块的起始地址。 | + +> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** +> 初始化后的内存池的内存块数量,不等于总大小除于内存块大小,因为内存池的控制块和每个内存块的控制头,都存在内存开销,设置总大小时,需要将这些因素考虑进去。 + + +#### 开发流程 + +本节介绍使用静态内存的典型场景开发流程。 + +1. 规划一片内存区域作为静态内存池。 + +2. 调用LOS_MemboxInit初始化静态内存池。 + 初始化会将入参指定的内存区域分割为N块(N值取决于静态内存总大小和块大小),将所有内存块挂到空闲链表,在内存起始处放置控制头。 + +3. 调用LOS_MemboxAlloc接口分配静态内存。 + 系统将会从空闲链表中获取第一个空闲块,并返回该内存块的起始地址。 + +4. 调用LOS_MemboxClr接口。 + 将入参地址对应的内存块清零。 + +5. 调用LOS_MemboxFree接口。 + 将该内存块加入空闲链表。 + + +#### 编程实例 + +本实例执行以下步骤: + +1. 初始化一个静态内存池。 + +2. 从静态内存池中申请一块静态内存。 + +3. 在内存块存放一个数据。 + +4. 打印出内存块中的数据。 + +5. 清除内存块中的数据。 + +6. 释放该内存块。 + 示例代码如下: + + +``` +#include "los_membox.h" + +VOID Example_StaticMem(VOID) +{ + UINT32 *mem = NULL; + UINT32 blkSize = 10; + UINT32 boxSize = 100; + UINT32 boxMem[1000]; + UINT32 ret; + + /*内存池初始化*/ + ret = LOS_MemboxInit(&boxMem[0], boxSize, blkSize); + if(ret != LOS_OK) { + printf("Membox init failed!\n"); + return; + } else { + printf("Membox init success!\n"); + } + + /*申请内存块*/ + mem = (UINT32 *)LOS_MemboxAlloc(boxMem); + if (NULL == mem) { + printf("Mem alloc failed!\n"); + return; + } + printf("Mem alloc success!\n"); + + /*赋值*/ + *mem = 828; + printf("*mem = %d\n", *mem); + + /*清除内存内容*/ + LOS_MemboxClr(boxMem, mem); + printf("Mem clear success \n *mem = %d\n", *mem); + + /*释放内存*/ + ret = LOS_MemboxFree(boxMem, mem); + if (LOS_OK == ret) { + printf("Mem free success!\n"); + } else { + printf("Mem free failed!\n"); + } + + return; +} +``` + + +#### 结果验证 + +输出结果如下: + + +``` +Membox init success! +Mem alloc success! +*mem = 828 +Mem clear success +*mem = 0 +Mem free success! +``` +## 动态内存 + + +### 运行机制 + +动态内存管理,即在内存资源充足的情况下,根据用户需求,从系统配置的一块比较大的连续内存(内存池,也是堆内存)中分配任意大小的内存块。当用户不需要该内存块时,又可以释放回系统供下一次使用。与静态内存相比,动态内存管理的优点是按需分配,缺点是内存池中容易出现碎片。 + +OpenHarmony LiteOS-M动态内存在TLSF算法的基础上,对区间的划分进行了优化,获得更优的性能,降低了碎片率。动态内存核心算法框图如下: + + **图1** 轻量系统动态内存核心算法 + ![zh-cn_image_0000001199352445](figures/zh-cn_image_0000001199352445.png) + +根据空闲内存块的大小,使用多个空闲链表来管理。根据内存空闲块大小分为两个部分:[4, 127]和[27, 231],如上图size class所示: + +1. 对[4,127]区间的内存进行等分,如上图下半部分所示,分为31个小区间,每个小区间对应内存块大小为4字节的倍数。每个小区间对应一个空闲内存链表和用于标记对应空闲内存链表是否为空的一个比特位,值为1时,空闲链表非空。[4,127]区间的31个小区间内存对应31个比特位进行标记链表是否为空。 + +2. 大于127字节的空闲内存块,按照2的次幂区间大小进行空闲链表管理。总共分为24个小区间,每个小区间又等分为8个二级小区间,见上图上半部分的Size Class和Size SubClass部分。每个二级小区间对应一个空闲链表和用于标记对应空闲内存链表是否为空的一个比特位。总共24\*8=192个二级小区间,对应192个空闲链表和192个比特位进行标记链表是否为空。 + +例如,当有40字节的空闲内存需要插入空闲链表时,对应小区间[40,43],第10个空闲链表,位图标记的第10比特位。把40字节的空闲内存挂载第10个空闲链表上,并判断是否需要更新位图标记。当需要申请40字节的内存时,根据位图标记获取存在满足申请大小的内存块的空闲链表,从空闲链表上获取空闲内存节点。如果分配的节点大于需要申请的内存大小,进行分割节点操作,剩余的节点重新挂载到相应的空闲链表上。当有580字节的空闲内存需要插入空闲链表时,对应二级小区间[2^9,2^9+2^6],第31+2\*8=47个空闲链表,并使用位图的第47个比特位来标记链表是否为空。把580字节的空闲内存挂载第47个空闲链表上,并判断是否需要更新位图标记。当需要申请580字节的内存时,根据位图标记获取存在满足申请大小的内存块的空闲链表,从空闲链表上获取空闲内存节点。如果分配的节点大于需要申请的内存大小,进行分割节点操作,剩余的节点重新挂载到相应的空闲链表上。如果对应的空闲链表为空,则向更大的内存区间去查询是否有满足条件的空闲链表,实际计算时,会一次性查找到满足申请大小的空闲链表。 + +内存管理结构如下图所示: + + **图2** 轻量系统动态内存管理结构图 + ![zh-cn_image_0000001153313284](figures/zh-cn_image_0000001153313284.png) + +- 内存池池头部分 + 内存池池头部分包含内存池信息、位图标记数组和空闲链表数组。内存池信息包含内存池起始地址及堆区域总大小,内存池属性。位图标记数组有7个32位无符号整数组成,每个比特位标记对应的空闲链表是否挂载空闲内存块节点。空闲内存链表包含223个空闲内存头节点信息,每个空闲内存头节点信息维护内存节点头和空闲链表中的前驱、后继空闲内存节点。 + +- 内存池节点部分 + 包含3种类型节点:未使用空闲内存节点,已使用内存节点和尾节点。每个内存节点维护一个前序指针,指向内存池中上一个内存节点,还维护内存节点的大小和使用标记。空闲内存节点和已使用内存节点后面的内存区域是数据域,尾节点没有数据域。 + +一些芯片片内RAM大小无法满足要求,需要使用片外物理内存进行扩充。对于这样的多段非连续性内存, LiteOS-M内核支持把多个非连续性内存逻辑上合一,用户不感知底层的多段非连续性内存区域。 LiteOS-M内核内存模块把不连续的内存区域作为空闲内存结点插入到空闲内存节点链表,把不同内存区域间的不连续部分标记为虚拟的已使用内存节点,从逻辑上把多个非连续性内存区域实现为一个统一的内存池。下面通过示意图说明下多段非连续性内存的运行机制: + + **图3** 非连续性内存合一示意图 + ![zh-cn_image_0000001198253551](figures/zh-cn_image_0000001198253551.png) + +结合上述示意图,非连续性内存合并为一个统一的内存池的步骤如下: + +1. 把多段非连续性内存区域的第一块内存区域通过调用LOS_MemInit接口进行初始化。 + +2. 获取下一个内存区域的开始地址和长度,计算该内存区域和上一块内存区域的间隔大小gapSize。 + +3. 把内存区域间隔部分视为虚拟的已使用节点,使用上一个内存区域的尾节点,设置其大小为gapSize+ OS_MEM_NODE_HEAD_SIZE。 + +4. 把当前内存区域划分为一个空闲内存节点和一个尾节点,把空闲内存节点插入到空闲链表,并设置各个节点的前后链接关系。 + +5. 如果有更多的非连续内存区域,重复上述步骤2-4。 + + +### 开发指导 + + +#### 使用场景 + +动态内存管理的主要工作是动态分配并管理用户申请到的内存区间。动态内存管理主要用于用户需要使用大小不等的内存块的场景,当用户需要使用内存时,可以通过操作系统的动态内存申请函数索取指定大小的内存块,一旦使用完毕,通过动态内存释放函数归还所占用内存,使之可以重复使用。 + + +#### 接口说明 + +OpenHarmony LiteOS-M的动态内存管理主要为用户提供以下功能,接口详细信息可以查看API参考。 + + **表1** 动态内存模块接口 + +| 功能分类 | 接口描述 | +| -------- | -------- | +| 初始化和删除内存池 | - LOS_MemInit:初始化一块指定的动态内存池,大小为size。
- LOS_MemDeInit:删除指定内存池,仅打开LOSCFG_MEM_MUL_POOL时有效。 | +| 申请、释放动态内存 | - LOS_MemAlloc:从指定动态内存池中申请size长度的内存。
- LOS_MemFree:释放从指定动态内存中申请的内存。
- LOS_MemRealloc:释放从指定动态内存中申请的内存。 | +| 获取内存池信息 | - LOS_MemPoolSizeGet:获取指定动态内存池的总大小。
- LOS_MemTotalUsedGet:获取指定动态内存池的总使用量大小。
- LOS_MemInfoGet:获取指定内存池的内存结构信息,包括空闲内存大小、已使用内存大小、空闲内存块数量、已使用的内存块数量、最大的空闲内存块大小。
- LOS_MemPoolList:打印系统中已初始化的所有内存池,包括内存池的起始地址、内存池大小、空闲内存总大小、已使用内存总大小、最大的空闲内存块大小、空闲内存块数量、已使用的内存块数量。仅打开LOSCFG_MEM_MUL_POOL时有效。 | +| 获取内存块信息 | - LOS_MemFreeNodeShow:打印指定内存池的空闲内存块的大小及数量。
- LOS_MemUsedNodeShow:打印指定内存池的已使用内存块的大小及数量。 | +| 检查指定内存池的完整性 | LOS_MemIntegrityCheck:对指定内存池做完整性检查,仅打开LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK时有效。 | +| 增加非连续性内存区域 | LOS_MemRegionsAdd:支持多段非连续性内存区域,把非连续性内存区域逻辑上整合为一个统一的内存池。仅打开LOSCFG_MEM_MUL_REGIONS时有效。如果内存池指针参数pool为空,则使用多段内存的第一个初始化为内存池,其他内存区域,作为空闲节点插入;如果内存池指针参数pool不为空,则把多段内存作为空闲节点,插入到指定的内存池。 | + +> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** +> - 由于动态内存管理需要管理控制块数据结构来管理内存,这些数据结构会额外消耗内存,故实际用户可使用内存总量小于配置项OS_SYS_MEM_SIZE的大小。 +> +> - 对齐分配内存接口LOS_MemAllocAlign/LOS_MemMallocAlign因为要进行地址对齐,可能会额外消耗部分内存,故存在一些遗失内存,当系统释放该对齐内存时,同时回收由于对齐导致的遗失内存。 +> +> - 非连续性内存区域接口LOS_MemRegionsAdd的LosMemRegion数组参数传入的非连续性内存区域需要按各个内存区域的内存开始地址升序,且内存区域不能重叠。 + + +#### 开发流程 + +本节介绍使用动态内存的典型场景开发流程。 + +1. 初始化LOS_MemInit。 + 初始一个内存池后生成一个内存池控制头、尾节点EndNode,剩余的内存被标记为FreeNode内存节点。注:EndNode作为内存池末尾的节点,size为0。 + +1. 申请任意大小的动态内存LOS_MemAlloc。 + 判断动态内存池中是否存在大于申请量大小的空闲内存块空间,若存在,则划出一块内存块,以指针形式返回,若不存在,返回NULL。如果空闲内存块大于申请量,需要对内存块进行分割,剩余的部分作为空闲内存块挂载到空闲内存链表上。 + +1. 释放动态内存LOS_MemFree。 + 回收内存块,供下一次使用。调用LOS_MemFree释放内存块,则会回收内存块,并且将其标记为FreeNode。在回收内存块时,相邻的FreeNode会自动合并。 + + +#### 编程实例 + +本实例执行以下步骤: + +1. 初始化一个动态内存池。 + +2. 从动态内存池中申请一个内存块。 + +3. 在内存块中存放一个数据。 + +4. 打印出内存块中的数据。 + +5. 释放该内存块。 + +示例代码如下: + + +``` +#include "los_memory.h" +#define TEST_POOL_SIZE (2*1024) +__attribute__((aligned(4))) UINT8 g_testPool[TEST_POOL_SIZE]; +VOID Example_DynMem(VOID) +{ + UINT32 *mem = NULL; + UINT32 ret; + + /*初始化内存池*/ + ret = LOS_MemInit(g_testPool, TEST_POOL_SIZE); + if (LOS_OK == ret) { + printf("Mem init success!\n"); + } else { + printf("Mem init failed!\n"); + return; + } + + /*分配内存*/ + mem = (UINT32 *)LOS_MemAlloc(g_testPool, 4); + if (NULL == mem) { + printf("Mem alloc failed!\n"); + return; + } + printf("Mem alloc success!\n"); + + /*赋值*/ + *mem = 828; + printf("*mem = %d\n", *mem); + + /*释放内存*/ + ret = LOS_MemFree(g_testPool, mem); + if (LOS_OK == ret) { + printf("Mem free success!\n"); + } else { + printf("Mem free failed!\n"); + } + + return; +} +``` + + +#### 结果验证 + +输出结果如下: + + +``` +Mem init success! +Mem alloc success! +*mem = 828 +Mem free success! +``` diff --git a/zh-cn/device-dev/kernel/kernel-mini-extend-file.md b/zh-cn/device-dev/kernel/kernel-mini-extend-file.md index 65e95f800c497f322f28bc990c0665978e7580bf..e0df347da27479ec128062a02c44d458cf6cd9b7 100644 --- a/zh-cn/device-dev/kernel/kernel-mini-extend-file.md +++ b/zh-cn/device-dev/kernel/kernel-mini-extend-file.md @@ -40,7 +40,292 @@ M核的文件系统子系统当前支持的文件系统有FATFS与LittleFS。同 | umount2 | 分区卸载,可通过MNT_FORCE参数进行强制卸载 | 支持 | 不支持 | | statfs | 获取分区信息 | 支持 | 不支持 | +## FAT -- **[FAT](kernel-mini-extend-file-fat.md)** -- **[LittleFS](kernel-mini-extend-file-lit.md)** \ No newline at end of file +### 基本概念 + +FAT文件系统是File Allocation Table(文件配置表)的简称,主要包括DBR区、FAT区、DATA区三个区域。其中,FAT区各个表项记录存储设备中对应簇的信息,包括簇是否被使用、文件下一个簇的编号、是否文件结尾等。FAT文件系统有FAT12、FAT16、FAT32等多种格式,其中,12、16、32表示对应格式中FAT表项的比特数。FAT文件系统支持多种介质,特别在可移动存储介质(U盘、SD卡、移动硬盘等)上广泛使用,使嵌入式设备和Windows、Linux等桌面系统保持很好的兼容性,方便用户管理操作文件。 + +OpenHarmony内核支持FAT12、FAT16与FAT32三种格式的FAT文件系统,具有代码量小、资源占用小、可裁切、支持多种物理介质等特性,并且与Windows、Linux等系统保持兼容,支持多设备、多分区识别等功能。OpenHarmony内核支持硬盘多分区,可以在主分区以及逻辑分区上创建FAT文件系统。 + + +### 开发指导 + + +#### 驱动适配 + +FAT文件系统的使用需要底层MMC相关驱动的支持。在一个带MMC存储设备的板子上运行FATFS,需要: + +1、适配板端EMMC驱动,实现disk_status、disk_initialize、disk_read、disk_write、disk_ioctl接口; + +2、新增fs_config.h文件,配置FS_MAX_SS(存储设备最大sector大小)、FF_VOLUME_STRS(分区名)等信息,例如: + + +``` +#define FF_VOLUME_STRS "system", "inner", "update", "user" +#define FS_MAX_SS 512 +#define FAT_MAX_OPEN_FILES 50 +``` + + +#### 开发流程 + +> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** +> - FATFS文件与目录操作: +> - 单个文件大小不超过4G。 +> - 支持同时打开的文件数最大为FAT_MAX_OPEN_FILES,文件夹数最大为FAT_MAX_OPEN_DIRS。 +> - 暂不支持根目录管理,文件/目录名均以分区名开头,例如“user/testfile”就是在“user”分区下名为“testfile”的文件或目录。 +> - 若需要同时多次打开同一文件,必须全部使用只读方式(O_RDONLY)。以可写方式(O_RDWR、O_WRONLY等)只能打开一次。 +> - 读写指针未分离,例如以O_APPEND(追加写)方式打开文件后,读指针也在文件尾,从头读文件前需要用户手动置位。 +> - 暂不支持文件与目录的权限管理。 +> - stat及fstat接口暂不支持查询修改时间、创建时间和最后访问时间。微软FAT协议不支持1980年以前的时间。 +> +> - FATFS分区挂载与卸载: +> - 支持以只读属性挂载分区。当mount函数的入参为MS_RDONLY时,所有的带有写入的接口,如write、mkdir、unlink,以及非O_RDONLY属性的open,将均被拒绝。 +> - mount支持通过MS_REMOUNT标记修改已挂载分区的权限。 +> - 在umount操作前,需确保所有目录及文件全部关闭。 +> - umount2支持通过MNT_FORCE参数强制关闭所有文件与文件夹并umount,但可能造成数据丢失,请谨慎使用。 +> +> - FATFS支持重新划分存储设备分区、格式化分区,对应接口为fatfs_fdisk与fatfs_format: +> - 在fatfs_format操作之前,若需要格式化的分区已挂载,需确保分区中的所有目录及文件全部关闭,并且分区umount。 +> - 在fatfs_fdisk操作前,需要该设备中的所有分区均已umount。 +> - fatfs_fdisk与fatfs_format会造成设备数据丢失,请谨慎使用。 + + +### 编程实例 + + +#### 实例描述 + +本实例实现以下功能: + +1. 创建目录“user/test” + +2. 在“user/test”目录下创建文件“file.txt” + +3. 在文件起始位置写入“Hello OpenHarmony!” + +4. 将文件内容刷入设备中 + +5. 设置偏移到文件起始位置 + +6. 读取文件内容 + +7. 关闭文件 + +8. 删除文件 + +9. 删除目录 + + +#### 示例代码 + + **前提条件:** + + 系统已将MMC设备分区挂载到user目录 + + **代码实现如下:** + + ``` + #include + #include + #include "sys/stat.h" + #include "fcntl.h" + #include "unistd.h" + + #define LOS_OK 0 + #define LOS_NOK -1 + + int FatfsTest(void) + { + int ret; + int fd = -1; + ssize_t len; + off_t off; + char dirName[20] = "user/test"; + char fileName[20] = "user/test/file.txt"; + char writeBuf[20] = "Hello OpenHarmony!"; + char readBuf[20] = {0}; + + /* 创建目录“user/test” */ + ret = mkdir(dirName, 0777); + if (ret != LOS_OK) { + printf("mkdir failed.\n"); + return LOS_NOK; + } + + /* 创建可读写文件"user/test/file.txt" */ + fd = open(fileName, O_RDWR | O_CREAT, 0777); + if (fd < 0) { + printf("open file failed.\n"); + return LOS_NOK; + } + + /* 将writeBuf中的内容写入文件 */ + len = write(fd, writeBuf, strlen(writeBuf)); + if (len != strlen(writeBuf)) { + printf("write file failed.\n"); + return LOS_NOK; + } + + /* 将文件内容刷入存储设备中 */ + ret = fsync(fd); + if (ret != LOS_OK) { + printf("fsync failed.\n"); + return LOS_NOK; + } + + /* 将读写指针偏移至文件头 */ + off = lseek(fd, 0, SEEK_SET); + if (off != 0) { + printf("lseek failed.\n"); + return LOS_NOK; + } + + /* 将文件内容读出至readBuf中,读取长度为readBuf大小 */ + len = read(fd, readBuf, sizeof(readBuf)); + if (len != strlen(writeBuf)) { + printf("read file failed.\n"); + return LOS_NOK; + } + printf("%s\n", readBuf); + + /* 关闭文件 */ + ret = close(fd); + if (ret != LOS_OK) { + printf("close failed.\n"); + return LOS_NOK; + } + + /* 删除文件"user/test/file.txt" */ + ret = unlink(fileName); + if (ret != LOS_OK) { + printf("unlink failed.\n"); + return LOS_NOK; + } + + /* 删除目录“user/test” */ + ret = rmdir(dirName); + if (ret != LOS_OK) { + printf("rmdir failed.\n"); + return LOS_NOK; + } + + return LOS_OK; + } + ``` + + +#### 结果验证 + +编译运行得到的结果为: + + +``` +Hello OpenHarmony! +``` +## LittleFS + + +### 基本概念 + +LittleFS是一个小型的Flash文件系统,它结合日志结构(log-structured)文件系统和COW(copy-on-write)文件系统的思想,以日志结构存储元数据,以COW结构存储数据。这种特殊的存储方式,使LittleFS具有强大的掉电恢复能力(power-loss resilience)。分配COW数据块时LittleFS采用了名为统计损耗均衡的动态损耗均衡算法,使Flash设备的寿命得到有效保障。同时LittleFS针对资源紧缺的小型设备进行设计,具有极其有限的ROM和RAM占用,并且所有RAM的使用都通过一个可配置的固定大小缓冲区进行分配,不会随文件系统的扩大占据更多的系统资源。 + +当在一个资源非常紧缺的小型设备上,寻找一个具有掉电恢复能力并支持损耗均衡的Flash文件系统时,LittleFS是一个比较好的选择。 + + +### 开发指导 + +移植LittleFS到新硬件设备上,需要申明lfs_config: + + +``` +const struct lfs_config cfg = { + // block device operations + .read = user_provided_block_device_read, + .prog = user_provided_block_device_prog, + .erase = user_provided_block_device_erase, + .sync = user_provided_block_device_sync, + + // block device configuration + .read_size = 16, + .prog_size = 16, + .block_size = 4096, + .block_count = 128, + .cache_size = 16, + .lookahead_size = 16, + .block_cycles = 500, +}; +``` + +其中.read,.prog,.erase,.sync分别对应该硬件平台上的底层的读写\擦除\同步等接口。 + +read_size 每次读取的字节数,可以比物理读单元大以改善性能,这个数值决定了读缓存的大小,但值太大会带来更多的内存消耗。 + +prog_size 每次写入的字节数,可以比物理写单元大以改善性能,这个数值决定了写缓存的大小,必须是read_size的整数倍,但值太大会带来更多的内存消耗。 + +block_size 每个擦除块的字节数,可以比物理擦除单元大,但此数值应尽可能小因为每个文件至少会占用一个块。必须是prog_size的整数倍。 + +block_count 可以被擦除的块数量,这取决于块设备的容量及擦除块的大小。 + + +### 示例代码 + + 代码实现如下: + +``` +#include "lfs.h" +#include "stdio.h" +lfs_t lfs; +lfs_file_t file; +const struct lfs_config cfg = { + // block device operations + .read = user_provided_block_device_read, + .prog = user_provided_block_device_prog, + .erase = user_provided_block_device_erase, + .sync = user_provided_block_device_sync, + // block device configuration + .read_size = 16, + .prog_size = 16, + .block_size = 4096, + .block_count = 128, + .cache_size = 16, + .lookahead_size = 16, + .block_cycles = 500, +}; +int main(void) { + // mount the filesystem + int err = lfs_mount(&lfs, &cfg); + // reformat if we can't mount the filesystem + // this should only happen on the first boot + if (err) { + lfs_format(&lfs, &cfg); + lfs_mount(&lfs, &cfg); + } + // read current count + uint32_t boot_count = 0; + lfs_file_open(&lfs, &file, "boot_count", LFS_O_RDWR | LFS_O_CREAT); + lfs_file_read(&lfs, &file, &boot_count, sizeof(boot_count)); + // update boot count + boot_count += 1; + lfs_file_rewind(&lfs, &file); + lfs_file_write(&lfs, &file, &boot_count, sizeof(boot_count)); + // remember the storage is not updated until the file is closed successfully + lfs_file_close(&lfs, &file); + // release any resources we were using + lfs_unmount(&lfs); + // print the boot count + printf("boot_count: %d\n", boot_count); +} +``` + + + **结果验证** + +首次编译运行得到的结果为: + + +``` +Say hello 1 times. +``` \ No newline at end of file diff --git a/zh-cn/device-dev/kernel/kernel-mini-memory-debug.md b/zh-cn/device-dev/kernel/kernel-mini-memory-debug.md index 625990100c08618f1beef05b71bf496e67f2d42a..fab4a6266dabae8db627379f1ffb8073961e6d37 100644 --- a/zh-cn/device-dev/kernel/kernel-mini-memory-debug.md +++ b/zh-cn/device-dev/kernel/kernel-mini-memory-debug.md @@ -4,8 +4,336 @@ 内存调测方法旨在辅助定位动态内存相关问题,提供了基础的动态内存池信息统计手段,向用户呈现内存池水线、碎片率等信息;提供了内存泄漏检测手段,方便用户准确定位存在内存泄漏的代码行,也可以辅助分析系统各个模块内存的使用情况;提供了踩内存检测手段,可以辅助定位越界踩内存的场景。 -- **[内存信息统计](kernel-mini-memory-debug-mes.md)** +## 内存信息统计 -- **[内存泄漏检测](kernel-mini-memory-debug-det.md)** -- **[踩内存检测](kernel-mini-memory-debug-cet.md)** \ No newline at end of file +### 基础概念 + +内存信息包括内存池大小、内存使用量、剩余内存大小、最大空闲内存、内存水线、内存节点数统计、碎片率等。 + +- 内存水线:即内存池的最大使用量,每次申请和释放时,都会更新水线值,实际业务可根据该值,优化内存池大小; + +- 碎片率:衡量内存池的碎片化程度,碎片率高表现为内存池剩余内存很多,但是最大空闲内存块很小,可以用公式(fragment=100-100\*最大空闲内存块大小/剩余内存大小)来度量; + +- 其他参数:通过调用接口(详见[内存管理](../kernel/kernel-mini-basic-memory-basic.md)章节接口说明),扫描内存池的节点信息,统计出相关信息。 + + +### 功能配置 + +LOSCFG_MEM_WATERLINE:开关宏,默认打开;若关闭这个功能,在target_config.h中将这个宏定义为0。如需获取内存水线,需要打开该配置。 + + +### 开发指导 + + +#### 开发流程 + +关键结构体介绍: + + +``` +typedef struct { + UINT32 totalUsedSize; // 内存池的内存使用量 + UINT32 totalFreeSize; // 内存池的剩余内存大小 + UINT32 maxFreeNodeSize; // 内存池的最大空闲内存块大小 + UINT32 usedNodeNum; // 内存池的非空闲内存块个数 + UINT32 freeNodeNum; // 内存池的空闲内存块个数 +#if (LOSCFG_MEM_WATERLINE == 1) // 默认打开,如需关闭,在target_config.h中将该宏设置为0 + UINT32 usageWaterLine; // 内存池的水线值 +#endif +} LOS_MEM_POOL_STATUS; +``` + +- 内存水线获取:调用LOS_MemInfoGet接口,第1个参数是内存池首地址,第2个参数是LOS_MEM_POOL_STATUS类型的句柄,其中字段usageWaterLine即水线值。 + +- 内存碎片率计算:同样调用LOS_MemInfoGet接口,可以获取内存池的剩余内存大小和最大空闲内存块大小,然后根据公式(fragment=100-100\*最大空闲内存块大小/剩余内存大小)得出此时的动态内存池碎片率。 + + +#### 编程实例 + +本实例实现如下功能: + +1.创建一个监控任务,用于获取内存池的信息; + +2.调用LOS_MemInfoGet接口,获取内存池的基础信息; + +3.利用公式算出使用率及碎片率。 + + +#### 示例代码 + + 代码实现如下: + +``` +#include +#include +#include "los_task.h" +#include "los_memory.h" +#include "los_config.h" + + +void MemInfoTaskFunc(void) +{ + LOS_MEM_POOL_STATUS poolStatus = {0}; + + /* pool为要统计信息的内存地址,此处以OS_SYS_MEM_ADDR为例 */ + void *pool = OS_SYS_MEM_ADDR; + LOS_MemInfoGet(pool, &poolStatus); + /* 算出内存池当前的碎片率百分比 */ + unsigned char fragment = 100 - poolStatus.maxFreeNodeSize * 100 / poolStatus.totalFreeSize; + /* 算出内存池当前的使用率百分比 */ + unsigned char usage = LOS_MemTotalUsedGet(pool) * 100 / LOS_MemPoolSizeGet(pool); + printf("usage = %d, fragment = %d, maxFreeSize = %d, totalFreeSize = %d, waterLine = %d\n", usage, fragment, poolStatus.maxFreeNodeSize, + poolStatus.totalFreeSize, poolStatus.usageWaterLine); +} + +int MemTest(void) +{ + unsigned int ret; + unsigned int taskID; + TSK_INIT_PARAM_S taskStatus = {0}; + taskStatus.pfnTaskEntry = (TSK_ENTRY_FUNC)MemInfoTaskFunc; + taskStatus.uwStackSize = 0x1000; + taskStatus.pcName = "memInfo"; + taskStatus.usTaskPrio = 10; + ret = LOS_TaskCreate(&taskID, &taskStatus); + if (ret != LOS_OK) { + printf("task create failed\n"); + return -1; + } + return 0; +} +``` + + +#### 结果验证 + +编译运行输出的结果如下: + + +``` +usage = 22, fragment = 3, maxFreeSize = 49056, totalFreeSize = 50132, waterLine = 1414 +``` +## 内存泄漏检测 + + +### 基础概念 + +内存泄漏检测机制作为内核的可选功能,用于辅助定位动态内存泄漏问题。开启该功能,动态内存机制会自动记录申请内存时的函数调用关系(下文简称LR)。如果出现泄漏,就可以利用这些记录的信息,找到内存申请的地方,方便进一步确认。 + + +### 功能配置 + +1. LOSCFG_MEM_LEAKCHECK:开关宏,默认关闭;若打开这个功能,在target_config.h中将这个宏定义为1。 + +2. LOSCFG_MEM_RECORD_LR_CNT:记录的LR层数,默认3层;每层LR消耗sizeof(void \*)字节数的内存。 + +3. LOSCFG_MEM_OMIT_LR_CNT:忽略的LR层数,默认4层,即从调用LOS_MemAlloc的函数开始记录,可根据实际情况调整。为啥需要这个配置?有3点原因如下: + - LOS_MemAlloc接口内部也有函数调用; + - 外部可能对LOS_MemAlloc接口有封装; + - LOSCFG_MEM_RECORD_LR_CNT 配置的LR层数有限; + +正确配置这个宏,将无效的LR层数忽略,就可以记录有效的LR层数,节省内存消耗。 + + +### 开发指导 + + +#### 开发流程 + +该调测功能可以分析关键的代码逻辑中是否存在内存泄漏。开启这个功能,每次申请内存时,会记录LR信息。在需要检测的代码段前后,调用LOS_MemUsedNodeShow接口,每次都会打印指定内存池已使用的全部节点信息,对比前后两次的节点信息,新增的节点信息就是疑似泄漏的内存节点。通过LR,可以找到具体申请的代码位置,进一步确认是否泄漏。 + +调用LOS_MemUsedNodeShow接口输出的节点信息格式如下:每1行为一个节点信息;第1列为节点地址,可以根据这个地址,使用GDB等手段查看节点完整信息;第2列为节点的大小,等于节点头大小+数据域大小;第3~5列为函数调用关系LR地址,可以根据这个值,结合汇编文件,查看该节点具体申请的位置。 + + +``` +node size LR[0] LR[1] LR[2] +0x10017320: 0x528 0x9b004eba 0x9b004f60 0x9b005002 +0x10017848: 0xe0 0x9b02c24e 0x9b02c246 0x9b008ef0 +0x10017928: 0x50 0x9b008ed0 0x9b068902 0x9b0687c4 +0x10017978: 0x24 0x9b008ed0 0x9b068924 0x9b0687c4 +0x1001799c: 0x30 0x9b02c24e 0x9b02c246 0x9b008ef0 +0x100179cc: 0x5c 0x9b02c24e 0x9b02c246 0x9b008ef0 +``` + +> ![icon-caution.gif](public_sys-resources/icon-caution.gif) **注意:** +> 开启内存检测会影响内存申请的性能,且每个内存节点都会记录LR地址,内存开销也加大。 + + +#### 编程实例 + +本实例实现如下功能:构建内存泄漏代码段。 + +1. 调用LOS_MemUsedNodeShow接口,输出全部节点信息打印; + +2. 申请内存,但没有释放,模拟内存泄漏; + +3. 再次调用LOS_MemUsedNodeShow接口,输出全部节点信息打印; + +4. 将两次log进行对比,得出泄漏的节点信息; + +5. 通过LR地址,找出泄漏的代码位置; + + +#### 示例代码 + +代码实现如下: + + +``` +#include +#include +#include "los_memory.h" +#include "los_config.h" + +void MemLeakTest(void) +{ + LOS_MemUsedNodeShow(LOSCFG_SYS_HEAP_ADDR); + void *ptr1 = LOS_MemAlloc(LOSCFG_SYS_HEAP_ADDR, 8); + void *ptr2 = LOS_MemAlloc(LOSCFG_SYS_HEAP_ADDR, 8); + LOS_MemUsedNodeShow(LOSCFG_SYS_HEAP_ADDR); +} +``` + + +#### 结果验证 + +编译运行输出log如下: + + +``` +node size LR[0] LR[1] LR[2] +0x20001b04: 0x24 0x08001a10 0x080035ce 0x080028fc +0x20002058: 0x40 0x08002fe8 0x08003626 0x080028fc +0x200022ac: 0x40 0x08000e0c 0x08000e56 0x0800359e +0x20002594: 0x120 0x08000e0c 0x08000e56 0x08000c8a +0x20002aac: 0x56 0x08000e0c 0x08000e56 0x08004220 + +node size LR[0] LR[1] LR[2] +0x20001b04: 0x24 0x08001a10 0x080035ce 0x080028fc +0x20002058: 0x40 0x08002fe8 0x08003626 0x080028fc +0x200022ac: 0x40 0x08000e0c 0x08000e56 0x0800359e +0x20002594: 0x120 0x08000e0c 0x08000e56 0x08000c8a +0x20002aac: 0x56 0x08000e0c 0x08000e56 0x08004220 +0x20003ac4: 0x1d 0x08001458 0x080014e0 0x080041e6 +0x20003ae0: 0x1d 0x080041ee 0x08000cc2 0x00000000 +``` + +对比两次log,差异如下,这些内存节点就是疑似泄漏的内存块: + + +``` +0x20003ac4: 0x1d 0x08001458 0x080014e0 0x080041e6 +0x20003ae0: 0x1d 0x080041ee 0x08000cc2 0x00000000 +``` + +部分汇编文件如下: + + +``` + MemLeakTest: + 0x80041d4: 0xb510 PUSH {R4, LR} + 0x80041d6: 0x4ca8 LDR.N R4, [PC, #0x2a0] ; g_memStart + 0x80041d8: 0x0020 MOVS R0, R4 + 0x80041da: 0xf7fd 0xf93e BL LOS_MemUsedNodeShow ; 0x800145a + 0x80041de: 0x2108 MOVS R1, #8 + 0x80041e0: 0x0020 MOVS R0, R4 + 0x80041e2: 0xf7fd 0xfbd9 BL LOS_MemAlloc ; 0x8001998 + 0x80041e6: 0x2108 MOVS R1, #8 + 0x80041e8: 0x0020 MOVS R0, R4 + 0x80041ea: 0xf7fd 0xfbd5 BL LOS_MemAlloc ; 0x8001998 + 0x80041ee: 0x0020 MOVS R0, R4 + 0x80041f0: 0xf7fd 0xf933 BL LOS_MemUsedNodeShow ; 0x800145a + 0x80041f4: 0xbd10 POP {R4, PC} + 0x80041f6: 0x0000 MOVS R0, R0 +``` + +其中,通过查找0x080041ee,就可以发现该内存节点是在MemLeakTest接口里申请的且是没有释放的。 + +## 踩内存检测 + + +### 基础概念 + +踩内存检测机制作为内核的可选功能,用于检测动态内存池的完整性。通过该机制,可以及时发现内存池是否发生了踩内存问题,并给出错误信息,便于及时发现系统问题,提高问题解决效率,降低问题定位成本。 + + +### 功能配置 + +LOSCFG_BASE_MEM_NODE_INTEGRITY_CHECK:开关宏,默认关闭;若打开这个功能,在target_config.h中将这个宏定义为1。 + +1. 开启这个功能,每次申请内存,会实时检测内存池的完整性。 + +2. 如果不开启该功能,也可以调用LOS_MemIntegrityCheck接口检测,但是每次申请内存时,不会实时检测内存完整性,而且由于节点头没有魔鬼数字(开启时才有,省内存),检测的准确性也会相应降低,但对于系统的性能没有影响,故根据实际情况开关该功能。 + +由于该功能只会检测出哪个内存节点被破坏了,并给出前节点信息(因为内存分布是连续的,当前节点最有可能被前节点破坏)。如果要进一步确认前节点在哪里申请的,需开启内存泄漏检测功能,通过LR记录,辅助定位。 + +> ![icon-caution.gif](public_sys-resources/icon-caution.gif) **注意:** +> 开启该功能,节点头多了魔鬼数字字段,会增大节点头大小。由于实时检测完整性,故性能影响较大;若性能敏感的场景,可以不开启该功能,使用LOS_MemIntegrityCheck接口检测。 + + +### 开发指导 + + +#### 开发流程 + +通过调用LOS_MemIntegrityCheck接口检测内存池是否发生了踩内存,如果没有踩内存问题,那么接口返回0且没有log输出;如果存在踩内存问题,那么会输出相关log,详见下文编程实例的结果输出。 + + +#### 编程实例 + +本实例实现如下功能: + +1. 申请两个物理上连续的内存块; + +2. 通过memset构造越界访问,踩到下个节点的头4个字节; + +3. 调用LOS_MemIntegrityCheck检测是否发生踩内存。 + + +#### 示例代码 + +代码实现如下: + + +``` +#include +#include +#include "los_memory.h" +#include "los_config.h" + +void MemIntegrityTest(void) +{ + /* 申请两个物理连续的内存块 */ + void *ptr1 = LOS_MemAlloc(LOSCFG_SYS_HEAP_ADDR, 8); + void *ptr2 = LOS_MemAlloc(LOSCFG_SYS_HEAP_ADDR, 8); + /* 第一个节点内存块大小是8字节,那么12字节的清零,会踩到第二个内存节点的节点头,构造踩内存场景 */ + memset(ptr1, 0, 8 + 4); + LOS_MemIntegrityCheck(LOSCFG_SYS_HEAP_ADDR); +} +``` + + +#### 结果验证 + +编译运行输出log如下: + + +``` +[ERR][OsMemMagicCheckPrint], 2028, memory check error! +memory used but magic num wrong, magic num = 0x00000000 /* 提示信息,检测到哪个字段被破坏了,用例构造了将下个节点的头4个字节清零,即魔鬼数字字段 */ + + broken node head: 0x20003af0 0x00000000 0x80000020, prev node head: 0x20002ad4 0xabcddcba 0x80000020 +/* 被破坏节点和其前节点关键字段信息,分别为其前节点地址、节点的魔鬼数字、节点的sizeAndFlag;可以看出被破坏节点的魔鬼数字字段被清零,符合用例场景 */ + + broken node head LR info: /* 节点的LR信息需要开启内存检测功能才有有效输出 */ + LR[0]:0x0800414e + LR[1]:0x08000cc2 + LR[2]:0x00000000 + + pre node head LR info: /* 通过LR信息,可以在汇编文件中查找前节点是哪里申请,然后排查其使用的准确性 */ + LR[0]:0x08004144 + LR[1]:0x08000cc2 + LR[2]:0x00000000 +[ERR]Memory interity check error, cur node: 0x20003b10, pre node: 0x20003af0 /* 被破坏节点和其前节点的地址 */ +``` diff --git a/zh-cn/device-dev/kernel/kernel-small-bundles-fs-support.md b/zh-cn/device-dev/kernel/kernel-small-bundles-fs-support.md index 5f2ea549f7be468f4780dd7c4db0c96a8690b4e0..427cc10737c384630f2d0235131c5e7b7c8e5115 100644 --- a/zh-cn/device-dev/kernel/kernel-small-bundles-fs-support.md +++ b/zh-cn/device-dev/kernel/kernel-small-bundles-fs-support.md @@ -1,13 +1,424 @@ # 支持的文件系统 +## FAT +### 基本概念 -- **[FAT](kernel-small-bundles-fs-support-fat.md)** +FAT文件系统是File Allocation Table(文件配置表)的简称,主要包括DBR区、FAT区、DATA区三个区域。其中,FAT区各个表项记录存储设备中对应簇的信息,包括簇是否被使用、文件下一个簇的编号、是否文件结尾等。FAT文件系统有FAT12、FAT16、FAT32等多种格式,其中,12、16、32表示对应格式中FAT表项的比特数,它们同时也限制了文件系统中的最大文件大小。FAT文件系统支持多种介质,特别在可移动存储介质(U盘、SD卡、移动硬盘等)上广泛使用,使嵌入式设备和Windows、Linux等桌面系统保持很好的兼容性,方便用户管理操作文件。 -- **[JFFS2](kernel-small-bundles-fs-support-jffs2.md)** +OpenHarmony内核支持FAT12、FAT16与FAT32三种格式的FAT文件系统,具有代码量小、资源占用小、可裁切、支持多种物理介质等特性,并且与Windows、Linux等系统保持兼容,支持多设备、多分区识别等功能。OpenHarmony内核支持硬盘多分区,可以在主分区以及逻辑分区上创建FAT文件系统。 -- **[NFS](kernel-small-bundles-fs-support-nfs.md)** -- **[Ramfs](kernel-small-bundles-fs-support-ramfs.md)** +### 运行机制 -- **[Procfs](kernel-small-bundles-fs-support-procfs.md)** \ No newline at end of file +FAT文件系统设计与物理布局的相关文档在互联网上非常丰富,请开发者自行搜索查看。 + +OpenHarmony LiteOS-A内核通过Bcache提升FAT文件系统性能,Bcache是block cache的简称。当发生读写时,Bcache会缓存读写扇区附近的扇区,以减少I/O次数,提高性能。Bcache的基本缓存单位为block,每个block大小一致(默认有28个block,每个block缓存64个扇区的数据)。当Bcache脏块率(脏扇区数/总扇区数)达到阈值时,会触发写回;如果脏块率未达到阈值,则不会将缓存数据写回磁盘。如果需要保证数据写回,开发者应当调用sync和fsync触发写回。FAT文件系统的部分接口也会触发写回操作(如close、umount等),但开发者不应当基于这些接口触发写回。 + + +### 开发指导 + + + **开发流程** + +基本使用流程为挂载→操作→卸载。 + +SD卡或MMC的设备名为mmcblk[x]p[y],文件系统类型为“vfat”。 + +示例: + + +``` +mount("/dev/mmcblk0p0", "/mnt", "vfat", 0, NULL); +``` + +> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** +> - FAT文件系统中,单个文件不能大于4 GiB。 +> +> - 当有两个SD卡插槽时,卡0和卡1不固定,先插上的为卡0,后插上的为卡1。 +> +> - 当多分区功能打开,存在多分区的情况下,卡0注册的设备节点/dev/mmcblk0(主设备)和/dev/mmcblk0p0(次设备)是同一个设备,禁止对主设备进行操作。 +> +> - 为避免SD卡使用异常或内存泄漏,SD卡使用过程中拔卡,用户必须先关闭正处于打开状态的文件和目录,并且卸载挂载节点。 +> +> - 在format操作之前,需要首先umount挂载点。 +> +> - 当Bcache功能生效时,需要注意: +> - 当mount函数的入参为MS_NOSYNC时,FAT不会主动将cache的内容写回存储器件。FAT的如下接口(open、close、 unlink、rename、mkdir、rmdir、truncate)不会自动进行sync操作,速度可以提升,但是需要上层主动调用sync来进行数据同步,否则可能会数据丢失。 +> +> - Bcache有定时写回功能。在menuconfig中开启LOSCFG_FS_FAT_CACHE_SYNC_THREAD选项,打开后系统会创建一个任务定时写回Bcache中的数据,默认每隔5秒检查Bcache中脏数据块比例,超过80%时进行sync操作,将Bcache中的脏数据全部写回磁盘。任务优先级、刷新时间间隔以及脏数据块比例的阈值可分别通过接口LOS_SetSyncThreadPrio、 LOS_SetSyncThreadInterval和LOS_SetDirtyRatioThreshold设置。 +> - 当前cache的默认大小为28个块,每个块64个扇区。 + +## JFFS2 + + +### 基本概念 + +JFFS2是Journalling Flash File System Version 2(日志文件系统)的缩写,是针对MTD设备的日志型文件系统。 + +OpenHarmony内核的JFFS2主要应用于NOR FLASH闪存,其特点是:可读写、支持数据压缩、提供了崩溃/掉电安全保护、提供“写平衡”支持等。闪存与磁盘介质有许多差异,直接将磁盘文件系统运行在闪存设备上,会导致性能和安全问题。为解决这一问题,需要实现一个特别针对闪存的文件系统,JFFS2就是这样一种文件系统。 + + +### 运行机制 + +关于JFFS2文件系统的在存储设备上的实际物理布局,及文件系统本身的规格说明,请参考JFFS2的[官方规格说明文档](https://sourceware.org/jffs2/)。 + +这里仅列举几个对开发者和使用者会有一定影响的JFFS2的重要机制/特征: + +1. Mount机制及速度问题:按照JFFS2的设计,所有的文件会按照一定的规则,切分成大小不等的节点,依次存储到flash设备上。在mount流程中,需要获取到所有的这些节点信息并缓存到内存里。因此,mount速度和flash设备的大小和文件数量的多少成线性比例关系。这是JFFS2的原生设计问题,对于mount速度非常介意的用户,可以在内核编译时开启“Enable JFFS2 SUMMARY”选项,可以极大提升mount的速度。这个选项的原理是将mount需要的信息提前存储到flash上,在mount时读取并解析这块内容,使得mount的速度变得相对恒定。这个实际是空间换时间的做法,会消耗8%左右的额外空间。 + +2. 写平衡的支持:由于flash设备的物理属性,读写都只能基于某个特定大小的“块”进行,为了防止某些特定的块磨损过于严重,在JFFS2中需要对写入的块进行“平衡”的管理,保证所有的块的写入次数都是相对平均的,进而保证flash设备的整体寿命。 + +3. GC(garbage collection)机制:在JFFS2里发生删除动作,实际的物理空间并不会立即释放,而是由独立的GC线程来做空间整理和搬移等GC动作,和所有的GC机制一样,在JFFS2里的GC会对瞬时的读写性能有一定影响。另外,为了有空间能被用来做空间整理,JFFS2会对每个分区预留3块左右的空间,这个空间是用户不可见的。 + +4. 压缩机制:当前使用的JFFS2,底层会自动的在每次读/写时进行解压/压缩动作,实际IO的大小和用户请求读写的大小并不会一样。特别在写入时,不能通过写入大小来和flash剩余空间的大小来预估写入一定会成功或者失败。 + +5. 硬链接机制:JFFS2支持硬链接,底层实际占用的物理空间是一份,对于同一个文件的多个硬连接,并不会增加空间的占用;反之,只有当删除了所有的硬链接时,实际物理空间才会被释放。 + + +### 开发指导 + +对于基于JFFS2和nor flash的开发,总体而言,与其他文件系统非常相似,因为都有VFS层来屏蔽了具体文件系统的差异,对外接口体现也都是标准的POSIX接口。 + +对于整个裸nor flash设备而言,没有集中的地方来管理和记录分区的信息。因此,需要通过其他的配置方式来传递这部分信息(当前使用的方式是在烧写镜像的时候,使用bootargs参数配置的),然后在代码中调用相应的接口来添加分区,再进行挂载动作。 + +**制作JFFS2文件系统镜像** + +使用mkfs.jffs2工具,制作镜像默认命令如下。页大小默认为4KiB,eraseblock大小默认64KiB。若实际参数与下面不同时,修改相应参数。 + + +``` +./mkfs.jffs2 -d rootfs/ -o rootfs.jffs2 +``` + + **表1** 指令含义表(更详细的介绍可以通过mkfs.jffs2 --help来查看) + +| 指令 | 含义 | +| -------- | -------- | +| -s | 页大小,不指定默认为4KiB | +| -e | eraseblock大小,不指定默认为64KiB | +| -p | 镜像大小。在镜像文件后面,用0xFF填充至指定大小,不指定则用0xFF填充至eraseblock对齐。 | +| -d | 要制作成文件系统镜像的源目录 | +| -o | 要制成的镜像名称 | + +**挂载JFFS2分区** + +调用int mount(const char \*source, const char \*target, const char \*filesystemtype, unsigned long mountflags, const void \*data)函数实现设备节点和挂载点的挂载。 + +该函数有五个参数,第一个参数const char \*source,表示设备节点,第二个参数const char \*target表示挂载点。第三个参数 const char \*filesystemtype,表示文件系统类型。 + +最后两个参数unsigned long mountflags和const void \*data表示挂载标志和数据,默认为0和NULL;这一操作也可以在Shell中使用mount命令实现,最后两个参数不需要用户给出。 + +运行命令: + + +``` +OHOS # mount /dev/spinorblk1 /jffs1 jffs2 +``` + +将从串口得到如下回应信息,表明挂载成功。 + + +``` +OHOS # mount /dev/spinorblk1 /jffs1 jffs2 +mount OK +``` + +挂载成功后,用户就能对norflash进行读写操作。 + +**卸载JFFS2分区** + +调用int umount(const char \*target)函数卸载分区,只需要正确给出挂载点即可。 + +运行命令: + + +``` +OHOS # umount /jffs1 +``` + +将从串口得到如下回应信息,表明卸载成功。 + + +``` +OHOS # umount /jffs1 +umount ok +``` +## NFS + + +### 基本概念 + +NFS是Network File System(网络文件系统)的缩写。它最大的功能是可以通过网络,让不同的机器、不同的操作系统彼此分享其他用户的文件。因此,用户可以简单地将它看做是一个文件系统服务,在一定程度上相当于Windows环境下的共享文件夹。 + + +### 运行机制 + +OpenHarmony LiteOS-A内核的NFS文件系统指的是NFS的客户端,NFS客户端能够将远程的NFS服务端分享的目录挂载到本地的机器中,运行程序和共享文件,但不占用当前系统的存储空间,在本地端的机器看起来,远程服务端的目录就好像是自己的一个磁盘一样。 + + +### 开发指导 + +1. 搭建NFS服务器 + + 这里以Ubuntu操作系统为例,说明服务器端设置步骤。 + + - 安装NFS服务器软件。 + + 设置好Ubuntu系统的下载源,保证网络连接好的情况下执行: + + + ``` + sudo apt-get install nfs-kernel-server + ``` + + - 创建用于挂载的目录并设置完全权限 + + + ``` + mkdir -p /home/sqbin/nfs + sudo chmod 777 /home/sqbin/nfs + ``` + + - 设置和启动NFS server。 + + 修改NFS配置文件/etc/exports,添加如下一行: + + + ``` + /home/sqbin/nfs *(rw,no_root_squash,async) + ``` + + 其中/home/sqbin/nfs是NFS共享的根目录。 + + 执行以下命令启动NFS server: + + + ``` + sudo /etc/init.d/nfs-kernel-server start + ``` + + 执行以下命令重启NFS server: + + + ``` + sudo /etc/init.d/nfs-kernel-server restart + ``` + +2. 设置单板为NFS客户端 + + 本指导中的NFS客户端指运行OpenHarmony内核的设备。 + + - 硬件连接设置。 + + OpenHarmony内核设备连接到NFS服务器的网络。设置两者IP,使其处于同一网段。比如,设置NFS服务器的IP为10.67.212.178/24,设置OpenHarmony内核设备IP为 + 10.67.212.3/24,注意:此IP为内网私有IP地址,用户使用时有差异,以用户实际IP为准。 + + OpenHarmony内核设备上的IP信息可通过ifconfig命令查看。 + + - 启动网络,确保单板到NFS服务器之间的网络通畅。 + + 启动以太网或者其他类型网络,使用ping命令检查到服务器的网络是否通畅。 + + + ``` + OHOS # ping 10.67.212.178 + [0]Reply from 10.67.212.178: time=1ms TTL=63 + [1]Reply from 10.67.212.178: time=0ms TTL=63 + [2]Reply from 10.67.212.178: time=1ms TTL=63 + [3]Reply from 10.67.212.178: time=1ms TTL=63 + --- 10.67.212.178 ping statistics --- + packets transmitted, 4 received, 0 loss + + 客户端NFS初始化,运行命令: + + + ``` + OHOS # mkdir /nfs + OHOS # mount 10.67.212.178:/home/sqbin/nfs /nfs nfs 1011 1000 + ``` + + 将从串口得到如下回应信息,表明初始化NFS客户端成功。 + + + ``` + OHOS # mount 10.67.212.178:/home/sqbin/nfs /nfs nfs 1011 1000 + Mount nfs on 10.67.212.178:/home/sqbin/nfs, uid:1011, gid:1000 + Mount nfs finished. + ``` + + 该命令将服务器10.67.212.178上的/home/sqbin/nfs目录挂载到OpenHarmony内核设备上的/nfs上。 + + > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** + > 本例默认nfs server已经配置可用,即示例中服务器10.67.212.178上的/home/sqbin/nfs已配置可访问。 + > + > mount命令的格式为: + > + > + > ``` + > mount nfs + > ``` + > + > 其中“SERVER_IP”表示服务器的IP地址;“SERVER_PATH”表示服务器端NFS共享目录路径;“CLIENT_PATH”表示设备上的NFS路径,“nfs”表示客户端要挂载的路径,可以根据自己需要替换。 + > + > 如果不想有NFS访问权限限制,可以在Linux命令行将NFS根目录权限设置成777: + > + > + > ``` + > chmod -R 777 /home/sqbin/nfs + > ``` + > + > 至此,NFS客户端设置完毕。NFS文件系统已成功挂载。 + +3. 利用NFS共享文件 + + 在NFS服务器下新建目录dir,并保存。在OpenHarmony内核下运行ls命令: + + ``` + OHOS # ls /nfs + ``` + + 则可从串口得到如下回应: + + + ``` + OHOS # ls /nfs + Directory /nfs: + drwxr-xr-x 0 u:0 g:0 dir + ``` + + 可见,刚刚在NFS服务器上新建的dir目录已同步到客户端(OpenHarmony内核系统)的/nfs目录,两者保持同步。 + + 同样地,在客户端(OpenHarmony内核系统)上创建文件和目录,在NFS服务器上也可以访问,读者可自行体验。 + + > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** + > 目前,NFS客户端仅支持NFS v3部分规范要求,因此对于规范支持不全的服务器,无法完全兼容。在开发测试过程中,建议使用Linux的NFS server,其对NFS支持很完善。 + +## Ramfs + + +### 基本概念 + +RAMFS是一个可动态调整大小的基于RAM的文件系统。RAMFS没有后备存储源。向RAMFS中进行的文件写操作也会分配目录项和页缓存,但是数据并不写回到任何其他存储介质上,掉电后数据丢失。 +### 运行机制 +RAMFS文件系统把所有的文件都放在 RAM 中,所以读/写操作发生在RAM中,可以用RAMFS来存储一些临时性或经常要修改的数据,例如/tmp和/var目录,这样既避免了对存储器的读写损耗,也提高了数据读写速度。 +### 开发指导 +挂载: +``` +mount(NULL, "/dev/shm", "ramfs", 0, NULL) +``` +创建目录: +``` +mkdir(pathname, mode) +``` +创建文件: +``` +open(pathname, O_NONBLOCK | O_CREAT | O_RDWR, mode) +``` +读取目录: +``` +dir = opendir(pathname) +ptr = readdir(dir) +closedir(dir) +``` +删除文件: +``` +unlink(pathname) +``` +删除目录: +``` +rmdir(pathname) +``` +去挂载: +``` +umount("/dev/shm") +``` +> ![icon-caution.gif](public_sys-resources/icon-caution.gif) **注意:** +> - RAMFS只能挂载一次,一次挂载成功后,后面不能继续挂载到其他目录。 +> +> - RAMFS属于调测功能,默认配置为关闭,正式产品中不要使用该功能。 + +## Procfs + + +### 基本概念 + +procfs是进程文件系统的简称,是一种虚拟文件系统,他用文件的形式,展示进程或其他系统信息。相比调用接口的方式获取信息,以文件操作的方式获取系统信息更为方便。 + + +### 运行机制 + +OpenHarmony内核中,procfs在开机时会自动挂载到/proc目录下,仅支持内核模块创建文件节点来提供查询服务。 + + +### 开发指导 + +procfs文件的创建无法使用一般的文件系统接口,需要使用ProcMkdir接口创建目录,使用CreateProcEntry接口创建文件。文件节点功能的开发就是实现read和write函数的钩子挂到CreateProcEntry创建的文件中。当用户使用读写procfs的文件时,就会调用到钩子函数来实现自定义的功能。 + + +编程实例 + +下面我们以创建/proc/hello/world文件为例,实现如下功能: + +1.在/proc/hello/world位置创建一个文件 + +2.当读文件内容时,返回"HelloWorld!" + +3.当写文件内容时,打印写入的内容 + + +``` +#include "proc_fs.h" + +static int TestRead(struct SeqBuf *buf, void *arg) +{ + LosBufPrintf(buf, "Hello World!\n"); /* 将数据打印到buffer中,这个buffer中的数据会返回到read的结果中 */ + return 0; +} + +static int TestWrite(struct ProcFile *pf, const char *buffer, size_t buflen, loff_t *ppos) +{ + if ((buffer == NULL) || (buflen <= 0)) { + return -EINVAL; + } + + PRINTK("your input is: %s\n", buffer); /* 注意和上面的read接口区别,这是对write接口输入命令的反馈,这个打印只会打印到控制台 */ + return buflen; +} +static const struct ProcFileOperations HELLO_WORLD_OPS = { + .read = TestRead, + .write = TestWrite, +}; + +void HelloWorldInit(void) +{ + /* 创建hello目录 */ + struct ProcDirEntry *dir = ProcMkdir("hello", NULL); + if (dir == NULL) { + PRINT_ERR("create dir failed!\n"); + return; + } + + /* 创建world文件 */ + struct ProcDirEntry *entry = CreateProcEntry("world", 0, dir); + if (entry == NULL) { + PRINT_ERR("create entry failed!\n"); + return; + } + + /* 将自定义的read和write钩子挂到文件中 */ + entry->procFileOps = &HELLO_WORLD_OPS; +} +``` + +**结果验证** + +启动后在shell输入如下命令 + + +``` +OHOS # cat /proc/hello/world +OHOS # Hello World! +OHOS # echo "yo" > /proc/hello/world +OHOS # your input is: yo +``` diff --git a/zh-cn/device-dev/kernel/kernel-small-debug-user.md b/zh-cn/device-dev/kernel/kernel-small-debug-user.md index 3a57c7c48ce1b8719a750f8b53bf2a01351b8e4f..cb61d4fe832b15e30df9d1f19963be0a811eb68d 100644 --- a/zh-cn/device-dev/kernel/kernel-small-debug-user.md +++ b/zh-cn/device-dev/kernel/kernel-small-debug-user.md @@ -1,11 +1,552 @@ # 用户态内存调测 +## 基本概念 +Debug版本的musl-libc库为用户提供内存泄漏检测、堆内存统计、踩内存分析以及backtrace功能等维测手段,可以提高用户态内存相关问题的定位效率。 -- **[基本概念](kernel-small-debug-user-concept.md)** -- **[运行机制](kernel-small-debug-user-function.md)** +采用了对malloc/free接口进行插桩,保存关键节点信息,然后程序在申请和释放内存时进行内存节点完整性校验,最后在程序结束时通过统计节点信息得到内存统计信息并根据统计信息判断内存是否泄漏的设计思想 -- **[使用指导](kernel-small-debug-user-guide.md)** +## 运行机制 + + +### 内存泄漏检查 + +对于每个进程,内存调测模块维护了128个链表(当前系统的线程最大数量为128个),每个链表的索引为线程ID。 + +申请内存时:保存关键信息到内存节点控制块,根据当前线程ID将内存节点控制块挂到对应链表; + +释放内存时:根据需要释放的内存地址匹配内存节点控制块并将该控制块删除。 + + **图1** 堆内存节点信息链表 + + ![zh-cn_image_0000001165890158](figures/zh-cn_image_0000001165890158.png) + +申请内存时,返回地址会被保存到LR寄存器中。进程运行过程中,系统会在内存节点控制块中添加疑似泄漏点对应的lr等信息。如下图所示: + + **图2** 堆内存节点信息 + + ![zh-cn_image_0000001165890518](figures/zh-cn_image_0000001165890518.png) + +其中,TID表示线程ID;PID表示进程ID;ptr表示申请的内存地址;size表示申请的内存大小;lr[n]表示函数调用栈地址,变量n可以根据具体场景的需要进行配置。 + +释放内存时,将free等接口的入参指针与node的ptr字段进行匹配,如果相同则删除该内存节点控制块信息。 + +用户通过串口或文件等方式,将各个进程内存调测信息导出,利用addr2line工具将导出的信息转换成导致内存泄漏的代码行,便可以解决内存泄露问题。 + + **图3** 泄漏点代码行定位流程 + + ![zh-cn_image_0000001165730464](figures/zh-cn_image_0000001165730464.png) + + +### 堆内存统计 + +用户态线程堆内存使用统计具有一定的实际意义,统计线程申请的堆内存占比,为用户程序的内存使用优化提供数据支持。用户态堆内存统计模块主要涉及的接口为malloc和free。如上图所示,每个进程维护128个链表,链表索引即线程ID,申请内存时系统将ptr和size信息记录在内存节点控制块中并将节点控制块挂在以线程ID为头信息的链表上,堆内存释放时根据ptr从对应的链表上移除相应的堆内存块信息;同时计算出当前线程所持有的堆内存总的使用量,并更新当前进程的堆内存使用量和堆内存使用峰值。 + + +### 内存完整性检查 + +- 使用malloc申请内存(小于等于0x1c000bytes时通过堆分配算法分配) + 用户程序申请堆内存时,在堆内存节点处添加校验值等信息,如果校验值异常,则很有可能是前一块堆内存使用越界导致的(目前无法识别校验值被野指针破坏的场景)。在内存申请、释放时校验内存节点校验值的正确性,若内存节点被破坏,校验失败时则输出tid、pid及当前被踩节点前一块堆内存申请时保存的调用栈信息,通过addr2line工具可获得具体的代码行信息,辅助用户解决问题。 + + **图4** node节点头信息添加校验值 + + ![zh-cn_image_0000001211449151](figures/zh-cn_image_0000001211449151.png) + + free堆内存时,不会立即把该内存块释放掉,而是在内存中写入魔术数字0xFE,并放到free队列中(保证在一定时间内不会再被malloc函数分配),当有野指针或use-after-free的情况对该内存进行读取的操作时,能够发现数据异常,但是对于写操作则无法判断出来。 + + **图5** free流程图 + + ![zh-cn_image_0000001165890904](figures/zh-cn_image_0000001165890904.png) + +- 使用malloc申请内存(大于0x1c000bytes时通过mmap申请) + 当malloc通过mmap申请大块内存时,在返回给用户使用的内存区间头和尾分别多申请一个页,一个页PAGE_SIZE当前为0x1000,这两个页分别通过mprotect接口设置权限为PROT_NONE(无可读可写权限),可以有效防止内存越界读写问题:越界读写数据时由于无读写权限而导致用户程序异常,根据异常调用栈信息可找到相应的代码逻辑。 + + **图6** malloc通过mmap机制申请内存的内存布局 + + ![zh-cn_image_0000001211130993](figures/zh-cn_image_0000001211130993.png) + +### 使用指导 +#### 接口说明 + + + **表1** 内存调测功能 + +| 接口名 | 描述 | +| -------- | -------- | +| mem_check_init | 初始化内存检测模块。 | +| watch_mem | 获取线程级堆内存使用信息。 | +| check_leak | 检查是否有堆内存泄漏。 | +| check_heap_integrity | 检查堆内存的完整性。 | +| backtrace | 获取调用栈地址信息。 | +| backtrace_symbols | 根据地址信息获取符号信息。 | +| print_trace | 输出函数调用栈信息。 | + + + **表2** 调用栈回溯功能 + +| 接口名 | 描述 | +| -------- | -------- | +| backtrace | 获取调用栈地址信息。 | +| backtrace_symbols | 根据地址信息获取符号信息。 | +| print_trace | 输出函数调用栈信息。 | + +### 使用说明 + + +编译OpenHarmony工程时默认编译的是debug版本,即libc库已经集成内存调测相关的接口实现,用户可以根据具体需要决定是否使能内存调测功能。 + + +堆内存调测功能提供两种方式供用户使用:接口调用及命令行参数。 + + +- 接口调用:优点是可以较精确的检查某一段代码逻辑的堆内存相关信息,缺点是需要修改用户代码。 + +- 命令行参数:优点是无需修改用户代码,缺点是无法精确的校验某一段逻辑的堆内存信息。 + + +> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** +> 内存调测功能使能后,进程退出时会默认进行一次堆内存泄漏和堆内存完整性检查。内存调测功能未使能时,堆内存统计、堆内存泄漏检查、堆内存完整性校验功能不会开启,调用相关调测接口无响应。 + + +- **[接口调用方式](kernel-small-debug-user-guide-use-api.md)** + +- **[命令行参数方式](kernel-small-debug-user-guide-use-cli.md)** + + + + +#### 接口调用方式 + + +##### 示例代码 + +代码功能:显式调用调测模块的相关接口对用户代码进行内存校验。 + + +``` +#include +#include +#include +#include // 包含提供内存调测接口声明的头文件 + +#define MALLOC_LEAK_SIZE 0x300 + +void func(void) { + char *ptr = malloc(MALLOC_LEAK_SIZE); + memset(ptr, '3', MALLOC_LEAK_SIZE); +} + +int main() +{ + mem_check_init(NULL); // 通过串口输出内存调测信息,必须在用户程序第一次申请堆内存之前调用(一般在main函数入口调用),否则调测信息不准确。 + // mem_check_init("/storage/mem_debug.txt"); // 内存调测信息输出到/storage/mem_debug.txt文件中,如果该文件创建失败,则信息通过串口输出。 + char *ptr = malloc(MALLOC_LEAK_SIZE); + memset(ptr, '1', MALLOC_LEAK_SIZE); + + watch_mem(); // 在当前代码逻辑处查看线程级内存统计信息。 + func(); + check_heap_integrity(); // 检查堆内存节点完整性。 + check_leak(); // 在当前代码逻辑处检查堆内存是否泄漏(一般在程序退出之前校验比较准确,若在malloc和free调用逻辑之间做校验,则结果不准确)。 + return 0; +} +``` + + +##### 编译 + + +``` +$ clang -o mem_check mem_check.c -funwind-tables -rdynamic -g -mfloat-abi=softfp -mcpu=cortex-a7 -mfpu=neon-vfpv4 -target arm-liteos --sysroot=/home//directory/out/hispark_taurus/ipcamera_hispark_taurus/sysroot $(clang -mfloat-abi=softfp -mcpu=cortex-a7 -mfpu=neon-vfpv4 -target arm-liteos -print-file-name=libunwind.a) +``` + + +> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** +> - 本编译示例基于将编译器的路径写入环境变量中,即.bashrc文件中。 +> +> - 编译用户程序及所需的lib库时,需要添加编译选项-funwind-tables,-rdynamic,-g,用于栈回溯。 +> +> - -mfloat-abi=softfp,-mcpu=cortex-a7,-mfpu=neon-vfpv4编译选项用于指定具体的芯片架构、浮点数计算优化、fpu,与具体的libc库使用的编译选项保持一致,否则链接时可能出现找不到libc库文件。 +> +> - -target arm-liteos用于指定编译器相关库文件路径。 +> +> - --sysroot=/home/<user-name>/directory/out/hispark_taurus/ipcamera_hispark_taurus/sysroot用于指定编译器库文件搜索根目录,假设OpenHarmony工程代码存放路径为/home/<user-name>/directory。其中out/hispark_taurus/ipcamera_hispark_taurus路径为在编译时,hb set命令指定的具体产品,本示例选择的是ipcamera_hispark_taurus产品。 +> +> - $(clang -mfloat-abi=softfp -mcpu=cortex-a7 -mfpu=neon-vfpv4 -target arm-liteos -print-file-name=libunwind.a)用于指定相应的unwind库的路径。 + + +##### 调测信息 + + +``` +OHOS # ./mem_check +OHOS # +==PID:4== Heap memory statistics(bytes): // 堆内存统计信息 + [Check point]: // check点调用栈 + #00: [0x86c] -> mem_check + #01: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so + + [TID: 18, Used: 0x320] // 18号线程堆内存占用,当前进程仅一个线程 + +==PID:4== Total heap: 0x320 byte(s), Peak: 0x320 byte(s) + +Check heap integrity ok! // 堆内存完整性检查 + +==PID:4== Detected memory leak(s): // 内存泄漏信息及调用栈 + [Check point]: + #00: [0x2da4c] -> /lib/libc.so + #01: [0x878] -> mem_check + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x850] -> mem_check + #01: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x810] -> mem_check + #01: [0x870] -> mem_check + #02: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so + +==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s). + +==PID:4== Detected memory leak(s): + [Check point]: + #00: [0x2da4c] -> /lib/libc.so + #01: [0x111ec] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x850] -> mem_check + #01: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x810] -> mem_check + #01: [0x870] -> mem_check + #02: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so + +==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s). + +Check heap integrity ok! +``` + + +##### 调用栈解析 + +提供parse_mem_info.sh脚本可以对调用栈进行解析,解析脚本存放的路径:kernel/liteos_a/tools/scripts/parse_memory/parse_mem_info.sh。利用脚本可以将相应的调测信息转换成具体的源码行号,如下命令所示,mem_debug.txt保存的是内存调测信息,elf1、elf2等文件是需要解析的elf文件。 + + +``` +$ ./parse_mem_info.sh mem_debug.txt elf1 elf2 elf3 ... +``` + +例如: + + +``` +$ ./parse_mem_info.sh mem_debug.txt mem_check +Compiler is [gcc/llvm]: llvm +Now using addr2line ... + +==PID:4== Heap memory statistics(bytes): + [Check point]: + #00: [0x86c] at /usr1/xxx/TEST_ELF/mem_check.c:22 + #01: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so + + [TID: 18, Used: 0x320] + +==PID:4== Total heap: 0x320 byte(s), Peak: 0x320 byte(s) + +Check heap integrity ok! + +==PID:4== Detected memory leak(s): + [Check point]: + #00: [0x2da4c] -> /lib/libc.so + #01: [0x878] at /usr1/xxx/TEST_ELF/mem_check.c:28 + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x850] at /usr1/xxx/TEST_ELF/mem_check.c:17 + #01: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x810] at /usr1/xxx/TEST_ELF/mem_check.c:9 + #01: [0x870] at /usr1/xxx/TEST_ELF/mem_check.c:24 + #02: <(null)+0x24baf9dc>[0x219dc] -> /lib/libc.so + +==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s). +``` + +#### 命令行参数方式 + + +对用户态进程进行内存相关的检查时,除了接口调用方式还可以通过命令行方式进行内存统计、内存泄漏或内存完整性检查。 + +``` +--mwatch:初始化内存调测功能,注册信号。内存调测信息将从串口输出; +--mrecord :初始化内存调测功能,注册信号。内存调测信息将保存至f_path文件,若f_path创建失败,则内存调测信息将从串口输出 +``` + + +在待调测的进程未退出时可使用信号机制获取对应信息: + +``` +kill -35 # 查看线程级堆内存占用 +kill -36 # 检查是否存在堆内存泄漏 +kill -37 # 检查堆内存头节点是否完整 +``` + + +##### 示例代码 + +代码功能:构造内存问题利用命令行方式进行内存调测。 + + +``` +#include +#include +#include + +#define MALLOC_LEAK_SIZE 0x300 + +void func(void) { + char *ptr = malloc(MALLOC_LEAK_SIZE); + memset(ptr, '3', MALLOC_LEAK_SIZE); +} + +int main() +{ + char *ptr = malloc(MALLOC_LEAK_SIZE); + memset(ptr, '1', MALLOC_LEAK_SIZE); + func(); + while (1); +} +``` + + +##### 编译 + +参考[接口调用一节](../kernel/kernel-small-debug-user-guide-use-api.md#编译)。 + + +##### 使用mwatch参数命令 + + +``` +OHOS # ./mem_check --mwatch // 利用task命令可以查到mem_check进程的pid为4 +OHOS # +OHOS # kill -35 4 // 查看堆内存统计信息 +OHOS # +==PID:4== Heap memory statistics(bytes): + [Check point]: + #00: [0x58dfc] -> /lib/libc.so + + [TID: 18, Used: 0x640] + +==PID:4== Total heap: 0x640 byte(s), Peak: 0x640 byte(s) + +OHOS # kill -36 4 // 检查是否存在堆内存泄漏 +OHOS # +==PID:4== Detected memory leak(s): + [Check point]: + #00: [0x2da4c] -> /lib/libc.so + #01: [0x58dfc] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x724] -> mem_check + #01: <(null)+0x2555a9dc>[0x219dc] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x6ec] -> mem_check + #01: [0x740] -> mem_check + #02: <(null)+0x2555a9dc>[0x219dc] -> /lib/libc.so + +==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s). + +OHOS # kill -37 4 // 检查堆内存头节点的完整性 +OHOS # +Check heap integrity ok! +``` + + +##### 调用栈解析 + +将调测信息保存至test.txt文件中,利用脚本进行解析,获取内存泄漏的具体行号。 + + +``` +$ ./parse_mem_info.sh test.txt mem_check +Compiler is [gcc/llvm]: llvm +Now using addr2line ... + +==PID:4== Detected memory leak(s): + [Check point]: + #00: [0x2da4c] -> /lib/libc.so + #01: [0x58dfc] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x724] at /usr1/xxx/TEST_ELF/mem_check.c:14 + #01: <(null)+0x2555a9dc>[0x219dc] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x6ec] at /usr1/xxx/TEST_ELF/mem_check.c:8 + #01: [0x740] at /usr1/xxx/TEST_ELF/mem_check.c:19 + #02: <(null)+0x2555a9dc>[0x219dc] -> /lib/libc.so + +==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s). +``` + + +##### 使用mrecord参数命令 + +1. 执行用户程序并指定记录内存调测信息的文件路径 + + ``` + OHOS # ./mem_check --mrecord /storage/check.txt + ``` + +2. 利用kill -35 <pid>统计内存信息,该信息将会输出到文件中,使用cat命令查看 + + ``` + OHOS # kill -35 4 + OHOS # Memory statistics information saved in /storage/pid(4)_check.txt + + OHOS # cat /storage/pid(4)_check.txt + + ==PID:4== Heap memory statistics(bytes): + [Check point]: + #00: [0x5973c] -> /lib/libc.so + + [TID: 18, Used: 0x640] + + ==PID:4== Total heap: 0x640 byte(s), Peak: 0x640 byte(s) + ``` + +3. 利用kill -36 <pid>校验内存完整性,该信息将会输出到文件中,使用cat命令查看 + + ``` + OHOS # kill -36 4 + OHOS # Leak check information saved in /storage/pid(4)_check.txt + + OHOS # cat /storage/pid(4)_check.txt + + ==PID:4== Heap memory statistics(bytes): + [Check point]: + #00: [0x5973c] -> /lib/libc.so + + [TID: 18, Used: 0x640] + + ==PID:4== Total heap: 0x640 byte(s), Peak: 0x640 byte(s) + + ==PID:4== Detected memory leak(s): + [Check point]: + #00: [0x2e38c] -> /lib/libc.so + #01: [0x5973c] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x724] -> mem_check + #01: <(null)+0x1fdd231c>[0x2231c] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x6ec] -> mem_check + #01: [0x740] -> mem_check + #02: <(null)+0x1fdd231c>[0x2231c] -> /lib/libc.so + + ==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s). + ``` + +4. 利用kill -9 <pid>杀掉当前进程,进程退出后会默认校验内存完整性,该信息将会输出到文件中,使用cat命令查看 + + ``` + OHOS # kill -9 4 + OHOS # Leak check information saved in /storage/pid(4)_check.txt + + Check heap integrity ok! + + OHOS # cat /storage/pid(4)_check.txt + OHOS # + ==PID:4== Heap memory statistics(bytes): + [Check point]: + #00: [0x5973c] -> /lib/libc.so + + [TID: 18, Used: 0x640] + + ==PID:4== Total heap: 0x640 byte(s), Peak: 0x640 byte(s) + + ==PID:4== Detected memory leak(s): + [Check point]: + #00: [0x2e38c] -> /lib/libc.so + #01: [0x5973c] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x724] -> mem_check + #01: <(null)+0x1fdd231c>[0x2231c] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x6ec] -> mem_check + #01: [0x740] -> mem_check + #02: <(null)+0x1fdd231c>[0x2231c] -> /lib/libc.so + + ==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s). + + ==PID:4== Detected memory leak(s): + [Check point]: + #00: [0x2e38c] -> /lib/libc.so + #01: [0x11b2c] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x724] -> mem_check + #01: <(null)+0x1fdd231c>[0x2231c] -> /lib/libc.so + + [TID:18 Leak:0x320 byte(s)] Allocated from: + #00: [0x6ec] -> mem_check + #01: [0x740] -> mem_check + #02: <(null)+0x1fdd231c>[0x2231c] -> /lib/libc.so + + ==PID:4== SUMMARY: 0x640 byte(s) leaked in 2 allocation(s). + ``` + +> ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** +> 上述连续记录的信息会逐步追加到初始化时所指定的文件中,故最后cat文件时,文件中还包含历史记录的信息内容。 +## 常见问题 + + +### UAF(Use after free) + +- 申请小块内存(不大于0x1c000字节) + free之后: + + 读操作:读取free之后的内存大概率是魔术数字(0xFEFEFEFE) + + > ![icon-note.gif](public_sys-resources/icon-note.gif) **说明:** + > free之后的堆内存不会立即释放进堆内存池,会先放至固定长度的队列中,并置魔术数字0xFE,队列满后会将先放至队列中的内存块释放进堆内存池 + + 写操作:无法校验。 + + +- 申请大块内存(大于0x1c000) + 堆内存由malloc通过调用mmap接口申请,free之后若仍访问该内存,则用户程序异常(该内存区间已被unmap)。 + + +### Double free + +Double free时,用户程序将会异常退出。 + + +### 堆内存节点被踩 + +- 申请小块内存(不大于0x1c000) + 堆内存节点被踩时,用户程序将会异常退出,并输出破坏被踩节点的可能的堆内存申请调用栈,对于野指针踩内存情况无法校验出来。例如用户程序mem_check中存在堆内存越界踩的情况,利用命令行方式可以获得踩内存的可能的具体位置。 + + + ``` + OHOS # ./mem_check --mwatch + OHOS # + ==PID:6== Memory integrity information: + [TID:28 allocated addr: 0x272e1ea0, size: 0x120] The possible attacker was allocated from: + #00: [0x640e8] -> /lib/libc.so + #01: [0x21d0] -> mem_check + ``` + + 可以通过调用栈解析脚本对调用栈信息进行解析。 + +- 申请大块内存(大于0x1c000) + + 堆内存由malloc通过mmap接口申请,申请得到的堆内存块前后各置一个size为PAGE_SIZE大小的区间,设置无读写权限,读写操作会触发用户程序异常。 -- **[常见问题](kernel-small-debug-user-faqs.md)** \ No newline at end of file diff --git a/zh-cn/device-dev/porting/Readme-CN.md b/zh-cn/device-dev/porting/Readme-CN.md index ae54c69dc6597106a1d5e8a3cc23d0235bdba6da..8d2c217aee92178f6eb562066e5bcac3465bb7f5 100644 --- a/zh-cn/device-dev/porting/Readme-CN.md +++ b/zh-cn/device-dev/porting/Readme-CN.md @@ -67,3 +67,5 @@ repo init -u https://gitee.com/openharmony-sig/manifest.git -b master -m devboar - [Combo解决方案之W800芯片移植案例](porting-w800-combo-demo.md) - 小型系统芯片移植案例 - [小型设备STM32MP1芯片移植案例](porting-stm32mp15xx-on-smallsystem.md) +- 标准系统芯片移植案例 + - [标准系统方案之瑞芯微RK3568移植案例](porting-dayu200-on_standard-demo.md) diff --git a/zh-cn/device-dev/subsystems/Readme-CN.md b/zh-cn/device-dev/subsystems/Readme-CN.md index 4f55d86f3a01808c7631404f134d6c8af3a7f7e8..d93db2c101cf19d4e158414e257abb588ee14953 100755 --- a/zh-cn/device-dev/subsystems/Readme-CN.md +++ b/zh-cn/device-dev/subsystems/Readme-CN.md @@ -74,7 +74,13 @@ - [设备安全等级管理开发指导](subsys-security-devicesecuritylevel.md) - 启动恢复 - [启动恢复子系统概述](subsys-boot-overview.md) - - [init启动引导组件](subsys-boot-init.md) + - init启动引导组件 + - [引导启动配置文件](subsys-boot-init-cfg.md) + - [jobs管理](subsys-boot-init-jobs.md) + - [服务管理](subsys-boot-init-service.md) + - [系统参数](subsys-boot-init-sysparam.md) + - [沙盒管理](subsys-boot-init-sandbox.md) + - [插件](subsys-boot-init-plugin.md) - [appspawn应用孵化组件](subsys-boot-appspawn.md) - [bootstrap服务启动组件](subsys-boot-bootstrap.md) - [常见问题](subsys-boot-faqs.md)