当前位置: 首页 > news >正文

RT-Thread 内核移植

内核移植

内核移植就是将RTT内核在不同的芯片架构、不同的板卡上运行起来,能够具备线程管理和调度,内存管理,线程间同步等功能。

移植可分为CPU架构移植和BSP(Board support package,板级支持包)移植两部分。

CPU架构移植

在嵌入式领域有多种不同CPU架构,例如Cortex-M,ARM920T、MIPS32、RISC-V等等。

为了使RTT能够在不同CPU架构的芯片上运行,RT-Thread提供了一个libcpu抽象层适配不同的CPU架构
libcpu层向上对内核提供统一的接口,包括全局中断的开关,线程栈的初始化,上下文切换等等。

libcpu抽象层向下提供了一套统一的CPU架构移植接口,这部分接口包含了全局中断开关函数、线程上下文切换函数、时钟节拍的配置和中断函数、Cache 等等内容。下表是 CPU 架构移植需要实现的接口和变量。

libcpu移植相关API

  1. rt_base_t rt_hw_interrupt_disable(void);关闭全局中断
  2. void rt_hw_interrupt_enbale(rt_base_t level);打开全局中断
  3. rt_uint8_t *rt_hw_stack_init(void *tentery, void *parameter,void *stack_addr, void *texit);线程栈的初始化,内核在线程创建和线程初始化里面会调用这个函数。
  4. void rt_hw_context_switch_to(rt_uint32_t to);没有来源线程的上下文切换,在调度器启动第一个线程的时候会调用,以及在signal里面会调用。
  5. void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to);从from线程切换到to线程,用于线程和线程之间的切换。
  6. void rt_hw_context_switch_interrupt(rt_uint32_t from, rt_uint32_t to);从from线程切换到to线程,用于中断里面进行切换的时候使用。
  7. rt_uint32_t rt_thread_switch_interrupt_flag; 表示需要再中断里面进行切换的标志。
  8. rt_uint32_t rt_interrupt_from_thread, rt_interrupt_to_thread;在线程进行上下文切换时,保存from线程和to线程。

实现全局中断开关

无论内核代码还是用户的代码,都可能存在一些变量,需要在多个线程或者中断里面使用,如果没有相应的保护机制,那就可能导致临界区问题。RT-Thread 里为了解决这个问题,提供了一系列的线程间同步和通信机制来解决。但是这些机制都需要用到 libcpu 里提供的全局中断开关函数。它们分别是:

/* 关闭全局中断 */
rt_base_t rt_hw_interrupt_disable(void);/* 打开全局中断 */
void rt_hw_interrupt_enable(rt_base_t level);

下面介绍在Cortex-M架构上实现这两个函数,为了快速开关中断,实现了CPS指令,可以用在此处。

CPSID I ;PRIMASK=1; 关中断
CPSIE I ;PRIMASK=0; 开中断

关闭全局中断

在rt_hw_interrupt_disable()函数里面需要完成的功能是:

  1. 保存当前的全局中断状态,并把状态作为函数的返回值。
  2. 关闭全局中断。
;/*
; * rt_base_t rt_hw_interrupt_disable(void);
; */
rt_hw_interrupt_disable    PROC      ;PROC 伪指令定义函数EXPORT  rt_hw_interrupt_disable  ;EXPORT输出定义的函数,类似于C语言externMRS     r0, PRIMASK              ; 读取 PRIMASK 寄存器的值到 r0 寄存器CPSID   I                        ; 关闭全局中断BX      LR                       ; 函数返回ENDP                             ;ENDP 函数结束

上面代码首先使用MRS指令将PRIMASK寄存器的值保存到r0寄存器里,然后使用CPSID I指令关闭全局中断,最后使用BX指令返回。r0存储的数据就是函数的返回值。
中断可以发生在 “MRS r0, PRIMASK” 指令和 “CPSID I” 之间,这并不会导致全局中断状态的错乱。

打开全局中断

在 rt_hw_interrupt_enable(rt_base_t level) 里,将变量 level 作为需要恢复的状态,覆盖芯片的全局中断状态。

基于 MDK,在 Cortex-M 内核上的实现打开全局中断,如下代码所示:

打开全局中断

;/*
; * void rt_hw_interrupt_enable(rt_base_t level);
; */
rt_hw_interrupt_enable    PROC      ; PROC 伪指令定义函数EXPORT  rt_hw_interrupt_enable  ; EXPORT 输出定义的函数,类似于 C 语言 externMSR     PRIMASK, r0             ; 将 r0 寄存器的值写入到 PRIMASK 寄存器BX      LR                      ; 函数返回ENDP                            ; ENDP 函数结束

使用MSR指令将r0寄存器的值写入到PRIMASK寄存器,从而恢复之前的中断状态。

实现线程栈初始化

在动态创建线程和初始化线程的时候,会使用内部的线程初始化函数_rt__thread_init(),_rt_thread_init()函数会调用栈初始化函数rt_hw_stack_init(),在栈初始化函数里会手动构造一个上下文内容,这个上下文内容被作为每个线程第一次执行的初始值。
在这里插入图片描述

rt_uint8_t *rt_hw_stack_init(void       *tentry,void       *parameter,rt_uint8_t *stack_addr,void       *texit)
{struct stack_frame *stack_frame;rt_uint8_t         *stk;unsigned long       i;/* 对传入的栈指针做对齐处理 */stk  = stack_addr + sizeof(rt_uint32_t);stk  = (rt_uint8_t *)RT_ALIGN_DOWN((rt_uint32_t)stk, 8);stk -= sizeof(struct stack_frame);/* 得到上下文的栈帧的指针 */stack_frame = (struct stack_frame *)stk;/* 把所有寄存器的默认值设置为 0xdeadbeef */for (i = 0; i < sizeof(struct stack_frame) / sizeof(rt_uint32_t); i ++){((rt_uint32_t *)stack_frame)[i] = 0xdeadbeef;}/* 根据 ARM  APCS 调用标准,将第一个参数保存在 r0 寄存器 */stack_frame->exception_stack_frame.r0  = (unsigned long)parameter;/* 将剩下的参数寄存器都设置为 0 */stack_frame->exception_stack_frame.r1  = 0;                 /* r1 寄存器 */stack_frame->exception_stack_frame.r2  = 0;                 /* r2 寄存器 */stack_frame->exception_stack_frame.r3  = 0;                 /* r3 寄存器 *//* 将 IP(Intra-Procedure-call scratch register.) 设置为 0 */stack_frame->exception_stack_frame.r12 = 0;                 /* r12 寄存器 *//* 将线程退出函数的地址保存在 lr 寄存器 */stack_frame->exception_stack_frame.lr  = (unsigned long)texit;/* 将线程入口函数的地址保存在 pc 寄存器 */stack_frame->exception_stack_frame.pc  = (unsigned long)tentry;/* 设置 psr 的值为 0x01000000L,表示默认切换过去是 Thumb 模式 */stack_frame->exception_stack_frame.psr = 0x01000000L;/* 返回当前线程的栈地址       */return stk;
}

实现上下文切换

在不同的CPU架构里,线程之间的上下文切换和中断到线程的上下文切换,上下文的寄存器部分可能是有差异的,也可能是一样的。在 Cortex-M 里面上下文切换都是统一使用 PendSV 异常来完成,切换部分并没有差异。但是为了能适应不同的 CPU 架构,RT-Thread 的 libcpu 抽象层还是需要实现三个线程切换相关的函数:

1) rt_hw_context_switch_to():没有来源线程,切换到目标线程,在调度器启动第一个线程的时候被调用。

2) rt_hw_context_switch():在线程环境下,从当前线程切换到目标线程。

3) rt_hw_context_switch_interrupt ():在中断环境下,从当前线程切换到目标线程。

在线程环境下进行切换和在中断环境进行切换是存在差异的。
线程环境下,如果调用rt_hw_context_switch()函数,那么可以马上进行上下文切换;而在中断环境下,需要等待中断处理函数完成之后才能进行切换。

在中断处理程序里如果触发了线程的调度,调度函数里会调用rt_hw_context_switch_interrupt()触发上下文切换。中断处理程序里处理完中断事务之后,中断退出之前检查rt_thread_switch_interrupt_flag变量,如果该变量的值为1,就根据rt_interrupt_from_thread 变量和 rt_interrupt_to_thread 变量,完成线程的上下文切换。

在Cortex-M处理器架构里,基于自动部分压栈PendSV的特性,上下文切换可以实现地更加简洁。

在这里插入图片描述
硬件在进入PendSV中断之前自动保存了from线程的PSR、PC、LR、R12、R3R0寄存器,然后在PendSV里保存from线程的R11R4寄存器,最后硬件在退出PendSV中断之后,自动恢复 to 线程的 R0~R3、R12、LR、PC、PSR 寄存器。

在这里插入图片描述
硬件在进入中断之前自动保存了 from 线程的 PSR、PC、LR、R12、R3-R0 寄存器,然后触发了PendSV异常,在 PendSV 异常处理函数里保存 from 线程的 R11~R4 寄存器,以及恢复 to 线程的 R4~R11 寄存器,最后硬件在退出 PendSV 中断之后,自动恢复 to 线程的 R0~R3、R12、PSR、PC、LR 寄存器。

显然,在Cortex-M内核里rt_hw_context_switch() 和 rt_hw_context_switch_interrupt() 功能一致,都是在PendSV里完成剩余上下文的保存和恢复。所以我们仅仅需要实现一份代码,简化移植的工作。

实现 rt_hw_context_switch_to()

rt_hw_context_switch_to() 只有目标线程,没有来源线程。这个函数里实现切换到指定线程的功能,下图是流程图:

  1. 将参数to保存到rt_interrupt_to_thread变量
  2. 将rt_interrupt_from_thread变量设置为0
  3. 将flag变量设置为1
  4. 设置PendSV异常优先级、触发PendSV中断
  5. 恢复MSP的默认值
  6. 使能全局中断
;/*
; * void rt_hw_context_switch_to(rt_uint32_t to);
; * r0 --> to
; * this fucntion is used to perform the first thread switch
; */
rt_hw_context_switch_to    PROCEXPORT rt_hw_context_switch_to; r0的值是一个指针,该指针指向to线程的线程控制块的SP成员; 将r0寄存器的值保存到rt_interrupt_to_thread变量里LDR     r1, =rt_interrupt_to_threadSTR     r0, [r1]; 设置from线程为空,表示不需要保存from的上下文LDR     r1, =rt_interrupt_from_threadMOV     r0, #0x0STR     r0, [r1]; 设置标志为1,表示需要切换,这个变量会在PendSV异常处理函数里切换的时候被清零LDR     r1, =rt_thread_switch_interrupt_flagMOV     r0, #1STR     r0, [r1]; 设置PendSV异常优先级为最低优先级LDR     r0, =NVIC_SYSPRI2LDR     r1, =NVIC_PENDSV_PRILDR.W   r2, [r0,#0x00]       ; readORR     r1,r1,r2             ; modifySTR     r1, [r0]             ; write-back; 触发PendSV异常(执行PendSV异常处理程序)LDR     r0, =NVIC_INT_CTRLLDR     r1, =NVIC_PENDSVSETSTR     r1, [r0]; 放弃芯片启动到第一次上下文切换之前的栈内容,将 MSP 设置启动时的值LDR     r0, =SCB_VTORLDR     r0, [r0]LDR     r0, [r0]MSR     msp, r0; 使能全局中断和全局异常,使能之后将进入 PendSV 异常处理函数CPSIE   FCPSIE   I; 不会执行到这里ENDP

实现 rt_hw_context_switch()/ rt_hw_context_switch_interrupt()

函数 rt_hw_context_switch() 和函数 rt_hw_context_switch_interrupt() 都有两个参数,分别是 from 线程和 to 线程。它们实现从 from 线程切换到 to 线程的功能。

在这里插入图片描述

;/*
; * void rt_hw_context_switch(rt_uint32_t from, rt_uint32_t to);
; * r0 --> from
; * r1 --> to
; */
rt_hw_context_switch_interruptEXPORT rt_hw_context_switch_interrupt
rt_hw_context_switch    PROCEXPORT rt_hw_context_switch; 检查 rt_thread_switch_interrupt_flag 变量是否为 1; 如果变量为 1 就跳过更新 from 线程的内容LDR     r2, =rt_thread_switch_interrupt_flagLDR     r3, [r2]CMP     r3, #1BEQ     _reswitch; 设置 rt_thread_switch_interrupt_flag 变量为 1MOV     r3, #1STR     r3, [r2]; 从参数 r0 里更新 rt_interrupt_from_thread 变量LDR     r2, =rt_interrupt_from_threadSTR     r0, [r2]_reswitch; 从参数 r1 里更新 rt_interrupt_to_thread 变量LDR     r2, =rt_interrupt_to_threadSTR     r1, [r2]; 触发 PendSV 异常,将进入 PendSV 异常处理函数里完成上下文切换LDR     r0, =NVIC_INT_CTRLLDR     r1, =NVIC_PENDSVSETSTR     r1, [r0]BX      LR

实现PendSV中断

在 Cortex-M3 里,PendSV 中断处理函数是 PendSV_Handler()。在 PendSV_Handler() 里完成线程切换的实际工作。
在这里插入图片描述

实现时钟节拍

有了开关全局中断和上下文切换功能的基础,RTOS 就可以进行线程的创建、运行、调度等功能了。有了时钟节拍支持,RT-Thread 可以实现对相同优先级的线程采用时间片轮转的方式来调度,实现定时器功能,实现 rt_thread_delay() 延时函数等等。

libcpu的移植需要完成的工作,就是确保rt_tick_increase()函数会在时钟节拍的中断里被周期性的调用,调用周期取决于 rtconfig.h 的宏 RT_TICK_PER_SECOND 的值。

BSP移植

相同的CPU架构在实际项目中,不同的板卡上可能使用相同的CPU架构,搭载不同的外设资源,完成不同的产品,所以也需要针对板卡做适配工作。

RTT提供了BSP抽象层来适配常见的板卡。如果希望在一个板卡上使用RTT内核,除了需要有相应的芯片架构的移植,还需要有针对板卡的移植,也就是实现一个基本的BSP。

主要任务是建立让操作系统运行的基本环境,需要完成的工作是:

  1. 初始化CPU内部寄存器,设定RAM工作时序。
  2. 实现时钟驱动及中断控制器驱动,完善中断管理。
  3. 实现串口和GPIO驱动。
  4. 初始化动态内存堆,实现动态堆内存管理。

相关文章:

RT-Thread 内核移植

内核移植 内核移植就是将RTT内核在不同的芯片架构、不同的板卡上运行起来&#xff0c;能够具备线程管理和调度&#xff0c;内存管理&#xff0c;线程间同步等功能。 移植可分为CPU架构移植和BSP&#xff08;Board support package&#xff0c;板级支持包&#xff09;移植两部…...

springboot中entity层、dto层、vo层通俗理解三者的区别

entity&#xff1a;这个类的属性是跟数据库字段一模一样的&#xff08;驼峰命名&#xff09;&#xff0c;当我们使用MyBatis-Plus的时候经常用得到。 dto&#xff1a;用于后端接收前端返回的数据&#xff0c;一般是post请求&#xff0c;前端会给我们返回一个json对象&#xff…...

TypeScript_队列结构-链表

队列 队列&#xff08;Queue&#xff09;&#xff0c;它是一种受限的线性表&#xff0c;先进先出&#xff08;FIFO First In First Out&#xff09; 受限之处在于它只允许在队列的前端&#xff08;front&#xff09;进行删除操作而在队列的后端&#xff08;rear&#xff09;进…...

STM32G0 定时器PWM DMA输出驱动WS2812配置 LL库

通过DMA方式输出PWM模拟LED数据信号 优点&#xff1a;不消耗CPU资源 缺点&#xff1a;占用内存较大 STM32CUBEMX配置 定时器配置 定时器通道&#xff1a;TIM3 CH2 分频&#xff1a;0 重装值&#xff1a;79&#xff0c;芯片主频64Mhz&#xff0c;因此PWM输出频率&#xff1a…...

记录错误:Access denied for user ‘root‘@‘localhost‘ (using password:No) 解决方案

他说我没输入密码&#xff0c;但是我输入了啊&#xff1f;&#xff1f;于是&#xff0c;我试了试这儿&#xff0c;password 一改就好了。。。 他原来是是我打的很快&#xff0c;快速生成的。。。。...

python爬虫实战(5)--获取小破站热榜

1. 分析地址 打开小破站热榜首页&#xff0c;查看响应找到如下接口地址 2. 编码 定义请求头 拿到标头 复制粘贴&#xff0c;处理成json 处理请求头代码如下: def format_headers_to_json():f open("data.txt", "r", encoding"utf-8") # 读…...

单目标应用:基于麻雀搜索算法SSA的微电网优化调度MATLAB

一、微网系统运行优化模型 参考文献&#xff1a; [1]李兴莘,张靖,何宇,等.基于改进粒子群算法的微电网多目标优化调度[J].电力科学与工程, 2021, 37(3):7 二、麻雀搜索算法简介 麻雀搜索算法 (Sparrow Search Algorithm, SSA) 是一种新型的群智能优化算法&#xff0c;于2020…...

C# easymodbus

库介绍 EasyModbus是用于 .NET 和 Java 平台上的Modbus TCP/UDP/RTU通讯协议库&#xff0c;支持多种编程语言&#xff0c;如C#、VB.NET、Java、C 与更多C#的变体&#xff0c;如Unity、Mono、.NET Core等等。 EasyModbus的Java版本至少需要Java 7&#xff0c;而C#版本兼容 .NE…...

HikariCP源码修改,使其连接池支持Kerberos认证

HikariCP-4.0.3 修改HikariCP源码,使其连接池支持Kerberos认证 修改后的Hikari源码地址:https://github.com/Raray-chuan/HikariCP-4.0.3 Springboot使用hikari连接池并进行Kerberos认证访问Impala的demo地址:https://github.com/Raray-chuan/springboot-kerberos-hikari-im…...

5分钟看明白rust mod use

rust把mod简单的事没说清&#xff0c;一片混乱&#xff0c;似懂非懂. mod语句查找只有一条规则&#xff1a;先找mod名1.rs,没有就我同名文件夹下的mod名1.rs&#xff0c;如果没有&#xff0c;就同名文件夹下的mod名1/mod.rs,再没有就error. 在mod.rs中&#xff0c;pub mod 文件…...

【Java核心知识】ThreadLocal相关知识

ThreadLocal 什么是ThreadLocal ThreadLoacal类可以为每个线程保存一份独有的变量&#xff0c;该变量对于每个线程都是独占的。实现原理为每个Thread类中包含一个ThreadHashMap&#xff0c;key为变量的对应的ThreadLocal对象&#xff0c;value为变量的值。 在日常使用中&…...

《Python基础教程(第三版)》阅读笔记 1

目录 1 快速上手&#xff1a;基础知识2 列表和元组3 字符串4 字典5 条件、循环及其他6 抽象7 再谈抽象8 异常9 魔法方法、特性和迭代器10 开箱即用 本文参考自《Beginning Python: from novice to professional》&#xff0c;中文版为《Python基础教程&#xff08;第三版&#…...

坦克400 Hi4-T预售价28.5万元起,越野新能源好理解

8月25日&#xff0c;在以“智享蓉城&#xff0c;驭见未来”为主题的成都国际车展上&#xff0c;坦克品牌越野新能源再启新程&#xff0c;首次以全Hi4-T新能源阵容亮相展台&#xff0c;释放坦克品牌加速布局越野新能源的强烈信号。 Hi4-T架构首款落地车型坦克500 Hi4-T上市至今斩…...

我的Vim学习笔记(不定期更新)

2023年9月3日&#xff0c;周日上午 学到了啥就写啥&#xff0c;不定期更新 目录 字体 文件 标签页 分屏 调用系统命令 字体 设置字体大小 :set guifont字体:h字体大小 例如&#xff0c;:set guifontMonospace:h20 查询当前使用的字体和字体大小 :set guifont? 查看…...

spring boot项目生成容器并运行

一个安静的周末&#xff0c;shigen又睡懒觉了&#xff0c;上次说的拖延症的惩罚来了&#xff1a;早晚各100个健腹轮练习&#xff0c;早上的已经完成了。今天的文章来的有点晚&#xff0c;但是依旧保持质量。 springboot项目生成容器并运行 背景 将springboot项目打包成jar包&…...

Vue之html中特殊符号的展示

Vue之html中特殊符号的展示 在html中使用特殊字符时直接展示会报错&#xff0c;需要使用实体名称或者实体编号才能展示。 最常用的字符实体 显示结果 描述 实体名称 实体编号空格 < 小于号 < &…...

数据结构1 -- leetcode练习

三. 练习 3.1 时间复杂度 用函数 f ( n ) f(n) f(n) 表示算法效率与数据规模的关系&#xff0c;假设每次解决问题需要 1 微秒&#xff08; 1 0 − 6 10^{-6} 10−6 秒&#xff09;&#xff0c;进行估算&#xff1a; 如果 f ( n ) n 2 f(n) n^2 f(n)n2 那么 1 秒能解决多…...

Java设计模式:四、行为型模式-05:备忘录模式

文章目录 一、定义&#xff1a;备忘录模式二、模拟场景&#xff1a;备忘录模式三、改善代码&#xff1a;备忘录模式3.1 工程结构3.2 备忘录模式模型结构图3.3 备忘录模式定义3.3.1 配置信息类3.3.2 备忘录类3.3.3 记录者类3.3.4 管理员类 3.4 单元测试 四、总结&#xff1a;备忘…...

MongoDB实验——MongoDB配置用户的访问控制

MongoDB 配置用户的访问控制 一、 实验原理 理解admin数据库&#xff1a;安装MongoDB时&#xff0c;会自动创建admin数据库&#xff0c;这是一个特殊数据库&#xff0c;提供了普通数据库没有的功能&#xff0c;例如&#xff0c;有些账户角色赋予用户操作多个数据库的权限&…...

golang逃逸技术分析

“ 申请到栈内存好处&#xff1a;函数返回直接释放&#xff0c;不会引起垃圾回收&#xff0c;对性能没有影响。 申请到堆上面的内存才会引起垃圾回收。 func F() { a : make([]int, 0, 20) b : make([]int, 0, 20000) l : 20 c : make([]int, 0, l)} “ a和b代码一样&#xff0…...

说说你了解的 Nginx

分析&回答 nginx性能数据 高并发连接: 官方称单节点支持5万并发连接数&#xff0c;实际生产环境能够承受2-3万并发。内存消耗少: 在3万并发连接下&#xff0c;开启10个nginx进程仅消耗150M内存 (15M10150M) 1. 正向、反向代理 所谓“代理”&#xff0c;是指在内网边缘 …...

SpringWeb(SpringMVC)

目录 SpringWeb介绍 搭建 SpringWeb SpringWeb介绍 Spring Web是一个基于 Servlet API 构建的原始 web 框架&#xff0c;用于构建基于MVC模式的Web应用程序。在 web 层框架历经 Strust1&#xff0c;WebWork&#xff0c;Strust2 等诸多产品的历代更选 之后&#xff0c;目前业界普…...

Mysql 语句

数据库管理 SQL语言分类 DDL 数据定义语言&#xff0c;用于创建数据库对象&#xff0c;如库、表、索引等 create 创建 create database/table; 数据库/表 create table 表名 &#xff08;括号内添加类型和字段&#xff09;;drop 删除 drop database/table; 数据库/表…...

软考高级架构师——6、软件架构设计

像学写文章一样&#xff0c;在学会字、词、句之后&#xff0c;就应上升到段落&#xff0c;就应追求文章的“布局谋 篇”&#xff0c;这就是架构。通俗地讲&#xff0c;软件架构设计就是软件系统的“布局谋篇”。 人们在软件工程实践中&#xff0c;逐步认识到了软件架构的重要性…...

虚拟内存相关笔记

虚拟内存是计算机系统内存管理的一个功能&#xff0c;它允许程序认为它们有比实际物理内存更多的可用内存。它使用硬盘来模拟额外的RAM。当物理内存不足时&#xff0c;操作系统将利用磁盘空间作为虚拟内存来存储数据。这种机制提高了资源的利用率并允许更大、更复杂的应用程序的…...

【linux】定时任务讲解

文章目录 一. 在某时刻只执行一次&#xff1a;at1. 设置定时任务2. 查看和删除定时任务 二. 周期性执行任务&#xff1a;cron1. 启动crond进程2. 编辑定时任务3. 查看和删除4. 用户权限4.1. 黑名单4.2指定用户 三. /etc/crontab的管理 一. 在某时刻只执行一次&#xff1a;at 1…...

安卓10创建文件夹失败

最近在做拍照录像功能&#xff0c;已经有了文件读写权限&#xff0c;却发现在9.0手机上正常使用&#xff0c;但是在安卓12系统上根本没有创建文件夹。经过研究发现&#xff0c;创建名称为“DCIM”的文件夹可以&#xff0c;别的又都不行。而且是getExternalStorageDirectory和ge…...

文件操作(c/c++)

文件操作可以概括为几步&#xff1a; 打开文件&#xff0c;写入文件&#xff0c;读取文件&#xff0c;关闭文件 FILE FILE 是一个在C语言中用于文件操作的库函数&#xff0c;它提供了一系列函数来实现文件的创建、打开、读取、写入、关闭等操作。FILE 库函数可以帮助开发者处理…...

设计模式-适配器

文章目录 一、简介二、适配器模式基础1. 适配器模式定义与分类2. 适配器模式的作用与优势3.UML图 三、适配器模式实现方式1. 类适配器模式2. 对象适配器模式3.类适配器模式和对象适配器模式对比 四、适配器模式应用场景1. 继承与接口的适配2. 跨平台适配 五、适配器模式与其他设…...

C. Queries for the Array - 思维

分析&#xff1a; 分析出现矛盾的地方&#xff0c;也就是可能遇到0&#xff0c;并且已有字符串的长度小于等于1&#xff0c;另一种情况就是&#xff0c;遇到了1并且已有字符串不是排好序的&#xff0c;或者遇到了0已有字符串是排好序的&#xff0c;那么可以遍历字符串&#xff…...