手搓Docker-Image-Creator(DIC)工具(04):DIC的代码实现

此系列的前 3 篇主要是介绍了 Docker 的应用、Docker 编排文件 Dockerfile 的常用命令、以及 Docker 镜像的构建过程等都进行简单介绍。尤其在第 3 篇,讲述了 Docker 运行时、安装用等资源,并在文末提出了存在的不足和改进的方向,本篇就直接从代码开始介绍如何使用 DIC 工具来创建 Docker 镜像。

1 了解框架目录结构

1.1 框架根目录 docker-image-creator(目录可以随意取)

图1  简洁的第一层目录结构

图1 简洁的第一层目录结构

第一级很简洁,只有 3 个文件和 1 个文件夹,分别是:

  • framework 文件夹,是整个工具的基础,用于存放 DIC 工具的资源和工具脚本
  • createApp.sh 文件,根据相关参数信息创建一个应用项目
  • build.sh 文件,根据 Dockerfile 文件的设定,对应用项目进行构建,最终生成 Docker 镜像
  • run.sh 文件,将制定的镜像在 Docker 里以容器的形式运行

1.2 framework 目录结构

既然 framework 是基础,那就重点看看此目录下的内容,如下图:
图2  framework 子目录构成

图2 framework 子目录构成

framework 目录下包含了三个目录,分别是:

  • install - 用来存放最终拷入到镜像里的安装包(安装结束后,最好从镜像里删除)
  • runtime - 用来存放最终考入到镜像里的运行时文件,不能从镜像里删除
  • versions - 用来存放不同版本的安装安装工具包,就是脚本文件,实现多版本的支持。这个版本号需要在创建项目时选择好,并记录在应用的配置文件 config.sh 文件里

下图展示 framework 的目录结构全貌:
图3  framework 的目录结构全貌

图3 framework 的目录结构全貌

在此实例中:

  • install目录下有 1 个安装包 glibc,并区分版本目录,便于存取
  • runtime目录下有 1 个运行时文件,并区分版本目录,便于存取
  • versions目录下有 1 个安装工具包,并区分版本目录,便于选择不同版本的管理脚本

2 创建应用项目

使用该框架的第一步就是要创建一个应用项目,由 createApp.sh 文件来完成,该文件会根据输入的参数信息创建一个应用项目。

2.1 createApp.sh 文件

目前的脚本并不复杂,所以没有使用函数,直接将脚本内容写在 createApp.sh 文件里。

#!/bin/bash

# 用法:./createApp.sh [creatorVersion] [appName] [appVersion]
# 参数
# creatorVersion=$1 - 生成器版本号
# appName=$2 - 应用名称
# appVersion=$3 - 应用版本号

set -e # 遇到错误时退出脚本

# 生成器版本号
creatorVersion=$1
if [ -z "$creatorVersion" ]; then
  echo "请输入生成器版本号"
  exit 1
fi

# 应用名称
appName=$2
if [ -z "$appName" ]; then
  echo "请输入应用名称"
  exit 2
fi

# 应用版本号
appVersion=$3
if [ -z "$appVersion" ]; then
  echo "请输入应用版本号"
  exit 3
fi

# 检查版本号在 framework 目录下是否存在
if [ ! -d "framework/versions/$creatorVersion" ]; then
  echo "生成器版本 $creatorVersion 不存在!创建应用失败!!"
  exit 4
fi

# 调用指定版本下的 _createApp.sh 脚本,需要根据 _createApp.sh 脚本要求传参
. ./framework/versions/$creatorVersion/tools/_createApp.sh $1 $2 $3

从createApp.sh脚本的内容分析,其核心作用在于接收并验证用户传递的参数,然后根据指定的版本信息调用位于 framework/versions/ 目录下对应版本的 _createApp.sh 子脚本来执行实际的项目创建操作。换言之,createApp.sh扮演了引导和参数预处理的角色,而具体的项目构建逻辑实现在各个版本的 _createApp.sh 中。

同样的模式也体现在其他相关脚本中,如build.sh和run.sh。build.sh脚本负责调用对应的 _build.sh 子脚本进行构建任务,而run.sh脚本则是通过调用 _run.sh 子脚本来执行项目的运行操作。这些主脚本与子脚本之间的关系构成了一个模块化的执行结构,每个主脚本作为对外接口统一处理输入和调度,而具体功能的实现则分散在各版本的子脚本中。

这样设计的好处是,可以充分发挥各个版本的功能模块化,每个版本都可以独立扩展和修改,而不会影响其他版本。

2.2 _createApp.sh 文件

_createApp.sh 文件的代码如下:

#!/bin/bash

# 用法:./_createApp.sh [creatorVersion] [appName] [appVersion]

set -e # 遇到错误时退出脚本

# 创建应用目录
mkdir -p apps/$appName # 创建应用目录

# 如果 Dockfile 文件不存在,则创建一个模版
if [ ! -f "apps/$appName/Dockerfile" ]; then
  echo "FROM busybox:latest" > apps/$appName/Dockerfile
fi

# 生成配置文件
echo "#!/bin/bash" > apps/$appName/config.sh
echo "appName=$appName # 应用名称" >> apps/$appName/config.sh
echo "appVersion=$appVersion # 应用版本号" >> apps/$appName/config.sh
echo "imageName=$appName # 镜像名称" >> apps/$appName/config.sh
echo "containerName=$appName # 容器名称" >> apps/$appName/config.sh
echo "creatorVersion=$creatorVersion # 生成器版本号" >> apps/$appName/config.sh

# 修改 config.sh 的可执行权限
chmod 755 apps/$appName/config.sh

echo ""
echo "*********************************************************"
echo "项目【 $appName 】创建成功!"
echo "*********************************************************"
echo ""

_createApp.sh脚本的主要执行流程如下:

  • 初始化应用目录:它会创建一个新的应用目录
  • 生成 Dockerfile 模版:如果在应用根目录下未找到 Dockerfile 文件,则自动生成一个标准的 Dockerfile 模版
  • 配置信息写入 config.sh:在应用根目录下创建并填充 config.sh 文件,其中包含了诸如应用名称、版本号、镜像名称、容器名称以及生成器版本号等关键信息,同时赋予该文件可执行权限

至此,项目的基本结构已经创建完毕。

3 创建实例程序

运行 macOS 的终端,或者 Windows 里的 Git Bash 程序(下同)。

3.1 进入 createApp.sh 文件所在的目录,运行:

./createApp.sh 0.0.1 app1 1.0.0
  • 0.0.1 为生成器版本号,由于比较“低调”就取了个“小号”,但需要在 framework/versions/ 下能找到这个版本号才可以运行
  • app1 为应用名称
  • 1.0.0 为应用版本号

运行并成功创建应用项目后,目录结构改变如下图:
图4  创建后的应用项目目录结构

图4 创建后的应用项目目录结构

从上图可以看到,创建了一个 apps 目录,改目录是所有应用的根目录,目前包含了刚创建的项目目录 app1 ,该目录下包含了 config.sh、Dockerfile 两个文件。

3.2 config.sh 文件

#!/bin/bash

appName=app1 # 应用名称
appVersion=1.0.0 # 应用版本号
imageName=app1 # 镜像名称
containerName=app1 # 容器名称
creatorVersion=0.0.1 # 生成器版本号

3.3 Dockerfile 文件(默认模板)

FROM busybox:latest

就一句话!可以理解为 Dockerfile 生成镜像时创建的 “Hello World”。

#!/bin/bash

set -e # 遇到错误时退出脚本

# 判断是否运行过配置文件 config.sh,如果未运行过,则运行配置文件
if [ -z "$configured" ]; then
  . ./config.sh
  configured=true # 设置一个标志,表示已经运行过配置文件
fi

# 检查 Docker 是否在运行
if ! docker info > /dev/null 2>&1; then
  echo "Docker 没有运行。请启动 Docker 再试!"
  exit 1
fi

# 查看指定名称和TAG的镜像是否存在
set +e
rslt=`docker images | grep -E "^$appName\s+$appVersion\s+"`
set -e
# 判断字符串是否为空
if [ -z "$rslt" ]; then
  echo "镜像不存在,请先构建镜像!"
  exit 2
fi

# 检查容器是否存在
if docker ps -a --filter "name=$containerName" | grep -q "$containerName"; then
  echo "容器存在,先停止,再删除..."
  # 容器存在,先停止,再删除
  docker stop $containerName > /dev/null 2>&1
  docker rm $containerName > /dev/null 2>&1
fi

docker run -itd --name $containerName $imageName:$appVersion
echo ""
echo "*********************************************************"
echo "成功创建容器:【 $containerName 】!"
echo "*********************************************************"
echo ""

上面的代码注释很详细,而且脚本功能也不复杂,就不多说了。

4 编译和运行脚本介绍

4.1 build.sh

#!/bin/bash

# 用法:./build.sh [appName]

# 参数
# appName=$1 - 应用名称

set -e # 遇到错误时退出脚本

# 判断是否带有参数 $1
appName=$1
if [ -z "$appName" ]; then
  echo "请输入应用名称"
  exit 1
fi

# 根据 appName 查找应用目录 appPath
appPath="apps/$appName"
echo $appPath
if [ ! -d "$appPath" ]; then
  echo "应用 $appName 不存在!"
  exit 2
fi

# 判断是否运行过配置文件 config.sh,如果未运行过,则运行配置文件
if [ -z "$configured" ]; then
  # 检查 config.sh 文件是否存在
  if [ ! -f "$appPath/config.sh" ]; then
    echo "配置文件 $appPath/config.sh 不存在!请创新创建项目或创建 config.sh 配置文件!"
    exit 3
  fi
  . $appPath/config.sh
  configured=true # 设置一个标志,表示已经运行过配置文件
fi

toolsPath=./framework/versions/$creatorVersion/tools
. $toolsPath/_build.sh # 调用 framwork 下对应版本的 _build.sh 脚本

4.2 _build.sh

#!/bin/bash

set -e # 遇到错误时退出脚本

# 判断是否运行过配置文件 config.sh,如果未运行过,则运行配置文件
if [ -z "$configured" ]; then
  . ./config.sh
  configured=true # 设置一个标志,表示已经运行过配置文件
fi

# 检查 Docker 是否在运行
if ! docker info > /dev/null 2>&1; then
  echo "Docker 没用运行。请启动 Docker 再试!"
  exit 1
fi

# 判断 Dockerfile 文件是否存在
if [ ! -f "$appPath/Dockerfile" ]; then
  echo "Dockerfile 文件不存在。请检查!"
  exit 2
fi

# 构建镜像,注意此处用到了
docker build -f $appPath/Dockerfile -t $imageName:$appVersion .
echo ""
echo "*********************************************************"
echo "成功生成 Docker 镜像:【 $imageName:$appVersion 】!"
echo "*********************************************************"
echo ""

4.3 run.sh

#!/bin/bash

# 用法:./run.sh [appName]
# 参数
# appName=$1 - 应用名称

set -e # 遇到错误时退出脚本

# 根据 appName 查找应用目录
appName=$1
if [ -z "$appName" ]; then
  echo "请输入应用名称"
  exit 1
fi
appPath="apps/$appName"
echo $appPath
if [ ! -d "$appPath" ]; then
  echo "应用 $appName 不存在!"
  exit 2
fi

# 判断是否运行过配置文件 config.sh,如果未运行过,则运行配置文件
if [ -z "$configured" ]; then
  . $appPath/config.sh
  configured=true # 设置一个标志,表示已经运行过配置文件
fi

echo $creatorVersion
toolsPath=./framework/versions/$creatorVersion/tools
echo $toolsPath

. $toolsPath/_run.sh

4.4 _run.sh

#!/bin/bash

set -e # 遇到错误时退出脚本

# 判断是否运行过配置文件 config.sh,如果未运行过,则运行配置文件
if [ -z "$configured" ]; then
  . ./config.sh
  configured=true # 设置一个标志,表示已经运行过配置文件
fi

# 检查 Docker 是否在运行
if ! docker info > /dev/null 2>&1; then
  echo "Docker 没有运行。请启动 Docker 再试!"
  exit 1
fi

# 查看指定名称和TAG的镜像是否存在
set +e
rslt=`docker images | grep -E "^$appName\s+$appVersion\s+"`
set -e
# 判断字符串是否为空
if [ -z "$rslt" ]; then
  echo "镜像不存在,请先构建镜像!"
  exit 2
fi

# 检查容器是否存在
if docker ps -a --filter "name=$containerName" | grep -q "$containerName"; then
  echo "容器存在,先停止,再删除..."
  # 容器存在,先停止,再删除
  docker stop $containerName > /dev/null 2>&1
  docker rm $containerName > /dev/null 2>&1
fi

docker run -itd --name $containerName $imageName:$appVersion
echo ""
echo "*********************************************************"
echo "成功创建容器:【 $containerName 】!"
echo "*********************************************************"
echo ""

5 运行实例程序

运行 macOS 的终端,或者 Windows 里的 Git Bash 程序,进入 DIC 框架根目录,运行 build.sh 脚本。

5.1 编译镜像

运行:

./build.sh

运行成功后,输入镜像查看命令:

docker images

会看到列举出 app1 镜像。

5.2 运行容器

运行:

./run.sh

运行成功后,输入容器查看命令:

docker ps -a

会看到列举出 app1 容器。
创建单个最简应用的测试就完成了。接下来我们来实现一个 alpine+jre 镜像。

6 再一次实现 alpine+jre 镜像

6.1 创建一个新的应用

终端进入 DCI 工具所在的目录,运行:

./createApp.sh 0.0.1 alpine-jre 1.0.0

轻松创建应用项目:alpine-jre,如下图:
图5  alpine-jre 镜像应用目录

图5 alpine-jre 镜像应用目录

6.2 修改 Dockerfile 文件

内容如下:

FROM busybox:latest
# Dockerfile
# 导入的镜像源
FROM alpine:3.10

# 作者 LABEL
LABEL maintainer="tanbushi@qq.com"

# 设置工作目录
WORKDIR /opt/app

# 更新源
RUN apk update && apk upgrade

# 拷贝 jre 到镜像中
COPY framework/runtime/JRE/jre1.8.0_401 /opt/app/runtime/JRE/jre1.8.0_401/

# 拷贝 glibc 到镜像中
COPY framework/install/glibc/2.29-r0 /opt/app/install/glibc/2.29-r0/

# 安装glibc
RUN apk --no-cache add ca-certificates wget 
RUN wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
RUN apk add /opt/app/install/glibc/2.29-r0/glibc-2.29-r0.apk
RUN apk add /opt/app/install/glibc/2.29-r0/glibc-bin-2.29-r0.apk
RUN apk add /opt/app/install/glibc/2.29-r0/glibc-i18n-2.29-r0.apk

# 删除缓存文件及 install 目录
RUN rm -rf /var/cache/apk/* /opt/app/install

# 配置环境变量
ENV JAVA_HOME=/opt/app/runtime/JRE/jre1.8.0_401
ENV CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
ENV PATH=$JAVA_HOME/bin:$PATH

6.3 创建和镜像和运行容器

终端进入 DIC 框架根目录,运行:

./build.sh alpine-jre 

成功创建镜像:alpine-jre:1.0.0

继续运行:

./run.sh alpine-jre

成功创建容器:alpine-jre

使用 docker exec 命令直接进入容器测试:

docker exec -it alpine-jre /bin/sh

出现下面的显示即表示已进入容器内部:

/opt/app #

在此窗口输入 java 命令:

java -version 

可以看到 JRE 版本:

java version "1.8.0_401"
Java(TM) SE Runtime Environment (build 1.8.0_401-b10)
Java HotSpot(TM) 64-Bit Server VM (build 25.401-b10, mixed mode)

alpine-jre 镜像生成和测试成功。退出容器:

exit

7 总结

在此阶段,我们成功完成了DIC(Docker Image Creation)工具的开发与初步测试工作,证明了其能够有效地用于生成各类定制化镜像。用户可以根据需求灵活运用这一工具来构建其他所需的Docker镜像。

为进一步提升DIC工具的用户体验和功能性,我们可以考虑以下优化方向:

  • 图形用户界面集成:引入GUI界面,方便用户通过直观的操作选择所需资源文件,简化繁琐的命令行操作步骤。例如,用户可以直接在界面上拖拽或选择要打包进镜像的文件和目录,降低使用门槛。

  • 增强复杂操作支持:扩展DIC工具的能力,使其能够处理更为复杂的场景,如添加多阶段构建、自动依赖检测与安装、环境变量配置等功能。通过智能分析和自动化处理,减少人工编写Dockerfile时的工作量和出错率。

  • 自动化Dockerfile生成:基于用户在界面上的选择和配置,DIC工具能够自动生成规范且高效的Dockerfile,不仅提升了工作效率,还确保了Dockerfile的质量和一致性。

  • 总之,尽管目前DIC工具已达到基本可用状态,但仍存在许多潜在的改进空间。我们鼓励开发者们根据实际应用场景不断对其进行迭代升级,共同推动DIC工具向着更加便捷、强大的方向发展。期待未来能见证一个功能全面且易用性出色的DIC工具诞生!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/510778.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

安卓Android 架构模式及UI布局设计

文章目录 一、Android UI 简介1.1 在手机UI设计中,坚持的原则是什么1.2 安卓中的架构模式1.2.1 MVC (Model-View-Controller)设计模式优缺点 1.2.2 MVP(Model-View-Presenter)设计模式MVP与MVC关系: 1.2.3 MVVM(Model—View—ViewModel ) 设计模式1.2.4 …

Go项目结构整洁实现|GitHub 3.5k

一、前言 hi,大家好,这里是白泽。今天给大家分享一个GitHub 🌟 3.5k 的 Go项目:go-backend-clean-arch https://github.com/amitshekhariitbhu/go-backend-clean-architecture 这个项目是一位老外写的,通过一个 HTT…

【MySQL】DML的表操作详解:添加数据&修改数据&删除数据(可cv例题语句)

前言 大家好吖,欢迎来到 YY 滴MySQL系列 ,热烈欢迎! 本章主要内容面向接触过C Linux的老铁 主要内容含: 欢迎订阅 YY滴C专栏!更多干货持续更新!以下是传送门! YY的《C》专栏YY的《C11》专栏YY的…

FastAPI Web框架教程 第6章 表单和上传文件

6-1 什么是Form表单 需求场景 很多网站都支持上传文件,比如说:注册时上传头像;填写问卷时上传附件等等。 那么FastAPI是如何来解决文件上传的需求呢? 其实,这个需求不是FastAPI要解决的问题,这是很常见…

阿赵UE学习笔记——23、动画蒙太奇

阿赵UE学习笔记目录   大家好,我是阿赵。   继续学习虚幻引擎的使用方法。上一篇介绍了动画合成功能,这次介绍的动画蒙太奇,和动画合成有很多类似的东西,但本质上却又不同。   蒙太奇是法语“剪接”的意思。所以动画蒙太奇&…

ARM FVP平台的terminal窗口大小如何设置

当启动ARM FVP平台时,terminal窗口太小怎么办?看起来非常累眼睛,本博客来解决这个问题。 首先看下ARM FVP平台对Host主机的需求: 通过上图可知,UART默认使用的是xterm。因此,我们需要修改xterm的默认字体设…

STM32 M3内核寄存器概念

内容主要来自<<M3内核权威指南>> 汇编程序中的最低有效位&#xff08;Least Significant Bit&#xff09;。LSB是二进制数中最右边的位&#xff0c;它代表了数值中的最小单位。在汇编程序中&#xff0c;LSB通常用于表示数据的最小精度或者作为标志位。 ---------…

element-ui 修改el-form-item样式

文章目录 form结构修改el-form-item所有样式只修改label只修改content只修改input只修改button form结构 <el-form :model"formData" label-width"80px"> <el-form-item label"label1"> <el-input v-model"formData.valu…

新手如何用Postman做接口自动化测试

1、什么是自动化测试 把人对软件的测试行为转化为由机器执行测试行为的一种实践。 例如GUI自动化测试&#xff0c;模拟人去操作软件界面&#xff0c;把人从简单重复的劳动中解放出来&#xff0c;本质是用代码去测试另一段代码&#xff0c;属于一种软件开发工作&#xff0c;已…

二叉树 - 栈 - 计数 - leetcode 331. 验证二叉树的前序序列化 | 中等难度

题目 - 点击直达 leetcode 331. 验证二叉树的前序序列化 | 中等难度1. 题目详情1. 原题链接2. 基础框架 2. 解题思路1. 题目分析2. 算法原理方法1&#xff1a;栈方法2&#xff1a;计数 3. 时间复杂度 3. 代码实现方法1&#xff1a;栈方法2&#xff1a;计数 leetcode 331. 验证二…

免费Linux系统和生信宝典原创学习教程

免费Linux系统和生信宝典原创学习教程 生物信息的学习离不开Linux系统&#xff0c;不管自己写命令处理数据&#xff0c;还是使用现有的工具。Linux对我们来讲最重要的是它强大的命令行功能&#xff0c;可以快速、批量、灵活的处理数据的提取、统计和整理等耗时耗力的重复性工作…

CTF wed安全 (攻防世界)练习题

一、disabled_button 步骤一&#xff1a;进入网站发现按钮按不了 步骤二&#xff1a;按F12会查看源代码&#xff0c;会发现disabled disable属性 在HTML中&#xff0c; disabled 属性只有两个值&#xff1a;一个是不带值&#xff08;例如&#xff1a;disabled&#xff09;&…

4.2学习总结

一.java学习总结 (本次java学习总结,主要总结了抽象类和接口的一些知识,和它们之间的联系和区别) 一.抽象类 1.1定义: 抽象类主要用来抽取子类的通用特性&#xff0c;作为子类的模板&#xff0c;它不能被实例化&#xff0c;只能被用作为子类的超类。 2.概括: 有方法声明&…

【隐私计算实训营008——SCQL】

1.SCQL使用/集成最佳实践 目前SCQL只开放API供用户使用/集成 使用SCDBClient上手体验可以基于SCQL API开发封装白屏产品&#xff0c;或集成到业务链路中 1.1 部署系统 环境配置&#xff1a; 机器配置&#xff1a;CPU/MEM最低8C16G机构之间的网络互通 镜像&#xff1a;secret…

Redis实现高可用之持久化介绍

前言 在生产环境中&#xff0c;为了实现Redis的高可用性&#xff0c;可以采用持久化、主从复制、哨兵模式和 Cluster集群的方法确保数据的持久性和可靠性。这里首先介绍一下使用持久化实现服务器的高可用。 目录 一、Redis 高可用方法 1. 持久化 2. 主从复制 3. 哨兵 4.…

二轮电动自行车充电桩开源系统

文章目录 一、产品功能部分截图1.手机端&#xff08;小程序、安卓、ios&#xff09;2.PC端 二、小程序体验账号以及PC后台体验账号1.小程序体验账号2.PC后台体验账号关注公众号获取最新资讯 三、产品简介&#xff1f;1. 充电桩云平台&#xff08;含硬件充电桩&#xff09;&…

曲线降采样之道格拉斯-普克算法Douglas–Peucker

曲线降采样之道格拉斯-普克算法Douglas–Peucker 该算法的目的是&#xff0c;给定一条由线段构成的曲线&#xff0c;找到一条点数较少的相似曲线&#xff0c;来近似描述原始的曲线&#xff0c;达到降低时间、空间复杂度和平滑曲线的目的。 附赠自动驾驶学习资料和量产经验&…

【与C++的邂逅】---- 函数重载与引用

关注小庄 顿顿解馋(▿) 喜欢的小伙伴可以多多支持小庄的文章哦 &#x1f4d2; 数据结构 &#x1f4d2; C 引言 : 上一篇博客我们了解了C入门语法的一部分&#xff0c;今天我们来了解函数重载&#xff0c;引用的技术&#xff0c;请放心食用 ~ 文章目录 一. &#x1f3e0; 函数重…

windows搭建ftp实现局域网共享文件

一、开启ftp服务 1.使用 win Q 键&#xff0c;快捷打开搜索框 2.搜索框内搜索 “控制面板” 3. 进入控制面板内选择 ”程序“ 4. 单击进入 “启用或关闭windows功能” 5. 找到并展开“internet information services”、 6. 建议展开后全选 “FTP服务器” 和 “web管理工…

OpenHarmony实战:轻量系统芯片移植

本文从芯片适配的端到端视角&#xff0c;为芯片/模组制造商提供基于OpenHarmony的芯片适配指导。典型的芯片架构&#xff0c;例如cortex-m、risc-v系列都可以按照本文档进行适配移植。 约束与限制 本文档适用于OpenHarmony LTS 3.0.1及之前版本的轻量系统的适配。 说明&#…