因同步导致的注解覆盖还原.

    百万汉字注解 + 百篇博客分析 => 挖透鸿蒙内核源码
    博客输出站点(国内):https://weharmonyos.comhttp://8.134.122.205
    博客输出站点(国外):https://weharmony.github.io
    注解文件系统:https://gitee.com/weharmony/third_party_NuttX
    注解协议栈:https://gitee.com/weharmony/third_party_lwip
上级 f334e015
../../../../third_party/FreeBSD/sys/sys/bus.h
\ No newline at end of file
../../../../third_party/FreeBSD/sys/sys/bus.h
......@@ -37,7 +37,7 @@
#include "mqueue.h"
#include "semaphore.h"
//杂项
/*
* Supply some suitable values for constants that may not be present
* in all configurations.
......@@ -134,7 +134,7 @@ long sysconf(int name)
return -1;
}
}
//获取进程ID ? @note_thinking 这里取taskID 不对吧,
pid_t getpid(void)
{
return ((LosTaskCB *)(OsCurrTaskGet()))->taskID;
......
......@@ -104,11 +104,11 @@ static int QuickstartDevUnlink(struct Vnode *node)
(void)node;
return unregister_driver(QUICKSTART_NODE);
}
//快速开始控制器
static ssize_t QuickstartIoctl(struct file *filep, int cmd, unsigned long arg)
{
ssize_t ret;
if (cmd == QUICKSTART_NOTIFY) {
if (cmd == QUICKSTART_NOTIFY) {//通知模式
return QuickstartNotify(arg);
}
......@@ -117,7 +117,7 @@ static ssize_t QuickstartIoctl(struct file *filep, int cmd, unsigned long arg)
return -EACCES;
}
switch (cmd) {
case QUICKSTART_LISTEN:
case QUICKSTART_LISTEN://监听模式
ret = QuickstartListen(arg);
break;
default:
......@@ -136,7 +136,7 @@ static const struct file_operations_vfs g_quickstartDevOps = {
int QuickstartDevRegister(void)
{
LOS_EventInit(&g_qsEvent);
LOS_EventInit(&g_qsEvent);//初始化 g_qsEvent
return register_driver(QUICKSTART_NODE, &g_quickstartDevOps, 0644, 0); /* 0644: file mode */
}
......@@ -47,7 +47,7 @@ extern "C" {
int DevRandomRegister(void);
int DevUrandomRegister(void);
typedef struct {
typedef struct {//随机数操作集
int (*support)(void); /* Whether hard random numbers are supported */
void (*init)(void); /* Initializing the hard random number generator */
void (*deinit)(void); /* Deinitializing the hard random number generator */
......
......@@ -33,10 +33,14 @@
#include "fcntl.h"
#include "linux/kernel.h"
#include "fs/driver.h"
/*****************************************************************
/dev/random在类UNIX系统中是一个特殊的设备文件,可以用作随机数发生器或伪随机数发生器。
*****************************************************************/
static unsigned long g_randomMax = 0x7FFFFFFF;
static unsigned long g_randomMax = 0x7FFFFFFF;//随机数范围
//生成随机数
static long DoRand(unsigned long *value)
{
long quotient, remainder, t;
......@@ -54,8 +58,8 @@ static unsigned long g_seed = 1;
int RanOpen(struct file *filep)
{
g_seed = (unsigned long)(LOS_CurrNanosec() & 0xffffffff);
return 0;
g_seed = (unsigned long)(LOS_CurrNanosec() & 0xffffffff);//取当前纳秒数
return 0;//返回成功,目的是为了给g_seed取赋值
}
static int RanClose(struct file *filep)
......@@ -111,7 +115,7 @@ static const struct file_operations_vfs g_ranDevOps = {
#endif
NULL, /* unlink */
};
//注册生成随机数驱动
int DevRandomRegister(void)
{
return register_driver("/dev/random", &g_ranDevOps, 0666, 0); /* 0666: file mode */
......
......@@ -35,8 +35,13 @@
#include "linux/kernel.h"
#include "fs/driver.h"
/*****************************************************************
硬件随机数发生器,
/dev/urandom在类UNIX系统中是一个特殊的设备文件,可以用作随机数发生器或伪随机数发生器。
*****************************************************************/
static RandomOperations g_randomOp;
void RandomOperationsInit(const RandomOperations *r)
void RandomOperationsInit(const RandomOperations *r)//随机数操作集初始化
{
if (r != NULL) {
(void)memcpy_s(&g_randomOp, sizeof(RandomOperations), r, sizeof(RandomOperations));
......
......@@ -32,28 +32,50 @@
#define __MTD_DEV_H__
#include "los_typedef.h"
/************************************************
MTD,Memory Technology Device即内存技术设备
Linux系统中采用MTD来管理不同类型的Flash芯片,包括NandFlash和NorFlash
NAND型和NOR型Flash在进行写入和擦除时都需要MTD(Memory Technology Drivers,MTD已集成在Flash芯片内部,
它是对Flash进行操作的接口),这是它们的共同特点;但在NOR型Flash上运行代码不需要任何的软件支持,
而在NAND型Flash上进行同样操作时,通常需要驱动程序,即内存技术驱动程序MTD。
NOR型Flash采用的SRAM接口,提供足够的地址引脚来寻址,可以很容易的存取其片内的每一个字节;
NAND型Flash使用复杂的I/O口来串行的存取数据,各个产品或厂商的方法可能各不相同,通常是采用8个I/O引脚
来传送控制、地址、数据信息。
NAND型Flash具有较高的单元密度,容量可以做得比较大,加之其生产过程更为简单,价格较低;NOR型Flash占据了容量
为1~16MB闪存市场的大部分,而NAND型Flash只是用在8~128MB的产品中,这也说明NOR主要用在代码存储介质中,
NAND适合数据存储。
************************************************/
#define MTD_NORFLASH 3
#define MTD_NORFLASH 3 //存储空间一般比较小,但它可以不用初始化,可以在其内部运行程序,一般在其存储一些初始化内存的固件代码;
#define MTD_NANDFLASH 4
#define MTD_DATAFLASH 6
#define MTD_MLCNANDFLASH 8
struct MtdNorDev {
unsigned long blockSize;
unsigned long blockStart;
unsigned long blockEnd;
};
/*********************************************
扇区是对硬盘而言,而块是对文件系统而言
文件系统不是一个扇区一个扇区的来读数据,一个扇区512个字节,太慢了,所以有了block(块)的概念,
文件系统是一个块一个块的读取的,block才是文件存取的最小单位。
*********************************************/
struct MtdNorDev {//一个block是4K,即:文件系统中1个块是由连续的8个扇区组成
unsigned long blockSize; //块大小,不用猜也知道,是4K,和内存的页等同,如此方便置换
unsigned long blockStart; //开始块索引
unsigned long blockEnd; //结束块索引
};
struct MtdDev {
VOID *priv;
UINT32 type;
UINT64 size;
UINT32 eraseSize;
struct MtdDev {//flash MTD 层 描述符
VOID *priv;
UINT32 type;
UINT64 size;
UINT32 eraseSize;//4K, 跟PAGE_CACHE_SIZE对应
//三个主要动作
int (*erase)(struct MtdDev *mtd, UINT64 start, UINT64 len, UINT64 *failAddr);//擦除flash操作
int (*read)(struct MtdDev *mtd, UINT64 start, UINT64 len, const char *buf);//读操作
int (*write)(struct MtdDev *mtd, UINT64 start, UINT64 len, const char *buf);//写操作
};
int (*erase)(struct MtdDev *mtd, UINT64 start, UINT64 len, UINT64 *failAddr);
int (*read)(struct MtdDev *mtd, UINT64 start, UINT64 len, const char *buf);
int (*write)(struct MtdDev *mtd, UINT64 start, UINT64 len, const char *buf);
};
#endif /* __MTD_DEV_H__ */
......@@ -47,36 +47,107 @@ extern "C" {
#endif /* __cplusplus */
#endif /* __cplusplus */
#define SPIBLK_NAME "/dev/spinorblk"
#define SPICHR_NAME "/dev/spinorchr"
#define NANDBLK_NAME "/dev/nandblk"
#define NANDCHR_NAME "/dev/nandchr"
typedef struct mtd_node {
UINT32 start_block;
UINT32 end_block;
UINT32 patitionnum;
CHAR *blockdriver_name;
CHAR *chardriver_name;
CHAR *mountpoint_name;
VOID *mtd_info; /* Driver used by a partition */
LOS_DL_LIST node_info;
LosMux lock;
UINT32 user_num;
} mtd_partition;
typedef struct par_param {
mtd_partition *partition_head;
struct MtdDev *flash_mtd;
const struct block_operations *flash_ops;
const struct file_operations_vfs *char_ops;
CHAR *blockname;
CHAR *charname;
UINT32 block_size;
} partition_param;
#define CONFIG_MTD_PATTITION_NUM 20
/***************************************************************
* flash按照内部存储结构不同,分为两种:nor flash和nand flash
* Nor FLASH使用方便,易于连接,可以在芯片上直接运行代码,稳定性出色,传输速率高,
在小容量时有很高的性价比,这使其很适合应于嵌入式系统中作为 FLASH ROM。
Nor Flash架构提供足够的地址线来映射整个存储器范围。
* NandFLASH强调更高的性能,更低的成本,更小的体积,更长的使用寿命。
这使Nand FLASH很擅于存储纯资料或数据等,在嵌入式系统中用来支持文件系统。
缺点包括较慢的读取熟读和I/O映射类型或间接接口
* 在通信方式上Nor Flash 分为两种类型:CFI Flash和 SPI Flash。即采用的通信协议不同.
SPI Flash(serial peripheral interface)串行外围设备接口,是一种常见的时钟同步串行通信接口。
有4线(时钟,两个数据线,片选线)或者3线(时钟,两个数据线)通信接口,由于它有两个数据线能实现全双工通信,
读写速度上较快。拥有独立的数据总线和地址总线,能快速随机读取,允许系统直接从Flash中读取代码执行;
可以单字节或单字编程,但不能单字节擦除,必须以Sector为单位或对整片执行擦除操作,在对存储器进行重新编程之前需要对Sector
或整片进行预编程和擦除操作。
CFI Flash
英文全称是common flash interface,也就是公共闪存接口,是由存储芯片工业界定义的一种获取闪存芯片物理参数
和结构参数的操作规程和标准。CFI有许多关于闪存芯片的规定,有利于嵌入式对FLASH的编程。现在的很多NOR FLASH 都支持CFI,
但并不是所有的都支持。
CFI接口,相对于串口的SPI来说,也被称为parallel接口,并行接口;另外,CFI接口是JEDEC定义的,
所以,有的又成CFI接口为JEDEC接口。所以,可以简单理解为:对于Nor Flash来说,
CFI接口=JEDEC接口=Parallel接口 = 并行接口
特点在于支持的容量更大,读写速度更快。
缺点由于拥有独立的数据线和地址总线,会浪费电路电子设计上的更多资源。
SPI Flash : 每次传输一个bit位的数据,传输速度慢,但是价格便宜,任意地址读数据,擦除按扇区进行
CFI Flash : 每次传输一个字节 ,速度快,任意地址读数据,擦除按扇区进行
Nand Flash:芯片操作是以“块”为基本单位.NAND闪存的块比较小,一般是8KB,然后每块又分成页,
页大小一般是512字节.要修改NandFlash芯片中一个字节,必须重写整个数据块,读和写都是按照扇区进行的。
参考:https://blog.csdn.net/zhejfl/article/details/78544796
***************************************************************/
#define SPIBLK_NAME "/dev/spinorblk" //nor spi flash 块设备
#define SPICHR_NAME "/dev/spinorchr" //nor spi flash 字符设备
#define NANDBLK_NAME "/dev/nandblk" //nand flash 块设备
#define NANDCHR_NAME "/dev/nandchr" //nand flash 字符设备
/***************************************************************
存储器技术设备(英语:Memory Technology Device,缩写为 MTD),是Linux系统中设备文件系统的一个类别,
主要用于闪存的应用,是一种闪存转换层(Flash Translation Layer,FTL)。创造MTD子系统的主要目的是提供
一个介于闪存硬件驱动程序与高端应用程序之间的抽象层。
因为具备以下特性,所以 MTD 设备和硬盘相较之下,处理起来要复杂许多:
* 具有 eraseblocks 的特微,而不是像硬盘一样使用集群。
* eraseblocks (32KiB ~ 128KiB) 跟硬盘的 sector size(512 到 1024 bytes)比起来要大很多。
* 操作上主要分作三个动作: 从 eraseblock 读取、写入 eraseblock 、还有就是清除 eraseblock 。
* 坏掉的 eraseblocks 无法隐藏,需要软件加以处理。
* eraseblocks 的寿命大约会在 104 到 105 的清除动作之后结束。
像U盘、多媒体记忆卡(MMC)、SD卡、CF卡等其他流行的可移动存储器要和MTD区分开来,虽然它们也叫"flash",但它们不是使用MTD技术的存储器[1]。
[1] http://www.linux-mtd.infradead.org/faq/general.html#L_ext2_mtd
http://www.linux-mtd.infradead.org/
在Linux内核中,引入MTD层为
NOR FLASH和NAND FLASH设备提供统一接口。MTD将文件系统与底层FLASH存储器进行了隔离。
字符设备和块设备的区别在于前者只能被顺序读写,后者可以随机访问;同时,两者读写数据的基本单元不同。
字符设备,以字节为基本单位,在Linux中,字符设备实现的比较简单,不需要缓冲区即可直接读写,
内核例程和用户态API一一对应,用户层的Read函数直接对应了内核中的Read例程,这种映射关系由字符设备的
file_operations维护。
块设备,则以块为单位接受输入和返回输出。对这种设备的读写是按块进行的,其接口相对于字符设备复杂,
read、write API没有直接到块设备层,而是直接到文件系统层,然后再由文件系统层发起读写请求。
同时,由于块设备的IO性能与CPU相比很差,因此,块设备的数据流往往会引入文件系统的Cache机制。
MTD设备既非块设备也不是字符设备,但可以同时提供字符设备和块设备接口来操作它。
MTD设备通常可分为四层
这四层从上到下依次是:设备节点、MTD设备层、MTD原始设备层和硬件驱动层。
参考: https://blog.csdn.net/lwj103862095/article/details/21545791
***************************************************************/
typedef struct mtd_node {//通过mknod在/dev子目录下建立MTD块设备节点(主设备号为31)和MTD字符设备节点(主设备号为90)
UINT32 start_block; //开始块索引
UINT32 end_block; //结束块索引
UINT32 patitionnum; //分区编号
CHAR *blockdriver_name; //块设备驱动名称 例如: /dev/spinorblk0p0
CHAR *chardriver_name; //字符设备驱动名称 例如: /dev/nandchr0p2
CHAR *mountpoint_name; //挂载点名称 例如: /
VOID *mtd_info; /* Driver used by a partition *///分区使用的驱动程序
LOS_DL_LIST node_info;//双循环节点,挂在首个分区节点上
LosMux lock; //每个分区都有自己的互斥锁
UINT32 user_num; //使用数量
} mtd_partition;
typedef struct par_param {//分区参数描述符,一个分区既可支持按块访问也可以支持按字符访问,只要有驱动程序就阔以
mtd_partition *partition_head; //首个分区,其他分区都挂在.node_info节点上
struct MtdDev *flash_mtd; //flash设备描述符,属于硬件驱动层
const struct block_operations *flash_ops; //块方式的操作数据
const struct file_operations_vfs *char_ops; //字符方式的操作数据
CHAR *blockname; //块设备名称
CHAR *charname; //字符设备名称
UINT32 block_size; //块单位(4K),对文件系统而言是按块读取数据,方便和内存页置换
} partition_param;
#define CONFIG_MTD_PATTITION_NUM 20 //分区数量的上限
#define ALIGN_ASSIGN(len, startAddr, startBlk, endBlk, blkSize) do { \
(len) = (((len) + ((blkSize) - 1)) & ~((blkSize) - 1)); \
......
../../../../../third_party/NuttX/include/nuttx/fs/dirent_fs.h
\ No newline at end of file
../../../../../third_party/NuttX/include/nuttx/fs/dirent_fs.h
......@@ -76,20 +76,21 @@ typedef LOS_DL_LIST LIST_ENTRY;
#define CHG_MTIME 32
#define CHG_CTIME 64
struct IATTR
struct IATTR //此结构用于记录 vnode 的属性
{
/* This structure is used for record vnode attr. */
unsigned int attr_chg_valid;
unsigned int attr_chg_flags;
unsigned attr_chg_mode;
unsigned attr_chg_uid;
unsigned attr_chg_gid;
unsigned attr_chg_size;
unsigned attr_chg_atime;
unsigned attr_chg_mtime;
unsigned attr_chg_ctime;
unsigned int attr_chg_valid;//节点改变有效性 (CHG_MODE | CHG_UID | ... )
unsigned int attr_chg_flags;//额外的系统与用户标志(flag),用来保护该文件
unsigned attr_chg_mode; //确定了文件的类型,以及它的所有者、它的group、其它用户访问此文件的权限 (S_IWUSR | ...)
unsigned attr_chg_uid; //用户ID
unsigned attr_chg_gid; //组ID
unsigned attr_chg_size; //节点大小
unsigned attr_chg_atime; //节点最近访问时间
unsigned attr_chg_mtime; //节点对应的文件内容被修改时间
unsigned attr_chg_ctime; //节点自身被修改时间
};
/******************************************************************
Linux系统使用struct inode作为数据结构名称。BSD派生的系统,使用vnode名称,其中v表示“virtual file system”
......
......@@ -35,9 +35,9 @@
LIST_HEAD g_vnodeFreeList; /* free vnodes list */ //空闲节点链表
LIST_HEAD g_vnodeVirtualList; /* dev vnodes list */ //虚拟设备节点链表,暂无实际的文件系统
LIST_HEAD g_vnodeActiveList; /* inuse vnodes list */
LIST_HEAD g_vnodeActiveList; /* inuse vnodes list */ //正在使用的虚拟节点链表
static int g_freeVnodeSize = 0; /* system free vnodes size */ //剩余节点数量
static int g_totalVnodeSize = 0; /* total vnode size */ //总节点数量
static int g_totalVnodeSize = 0; /* total vnode size */ //已分配的总节点数量
static LosMux g_vnodeMux; //操作链表互斥量
static struct Vnode *g_rootVnode = NULL;//根节点
......@@ -93,7 +93,7 @@ int VnodesInit(void)
LOS_ListInit(&g_vnodeFreeList); //初始化空闲的节点链表
LOS_ListInit(&g_vnodeVirtualList); //初始化虚拟节点链表
LOS_ListInit(&g_vnodeActiveList);
LOS_ListInit(&g_vnodeActiveList); //初始化活动虚拟节点链表
retval = VnodeAlloc(NULL, &g_rootVnode);//分配根节点
if (retval != LOS_OK) {
PRINT_ERR("VnodeInit failed error %d\n", retval);
......@@ -214,21 +214,20 @@ int VnodeFree(struct Vnode *vnode)
LOS_ListDelete(&(vnode->hashEntry));//将自己从当前哈希链表上摘出来,此时vnode通过hashEntry挂在 g_vnodeHashEntrys
LOS_ListDelete(&vnode->actFreeEntry);//将自己从当前链表摘出来,此时vnode通过actFreeEntry挂在 g_vnodeCurrList
if (vnode->vop->Reclaim) {
vnode->vop->Reclaim(vnode);
if (vnode->vop->Reclaim) {//资源的回收操作
vnode->vop->Reclaim(vnode);//先回收资源
}
if (vnode->vop == &g_devfsOps) {
if (vnode->vop == &g_devfsOps) {//对于设备文件系统的回收
/* for dev vnode, just free it */
free(vnode->data);
free(vnode->data);//
free(vnode);
g_totalVnodeSize--;
} else {
/* for normal vnode, reclaim it to g_VnodeFreeList */
memset_s(vnode, sizeof(struct Vnode), 0, sizeof(struct Vnode));
LOS_ListAdd(&g_vnodeFreeList, &vnode->actFreeEntry);//通过 actFreeEntry链表节点挂到 空闲链表上.
g_freeVnodeSize++;
memset_s(vnode, sizeof(struct Vnode), 0, sizeof(struct Vnode));//节点再利用
LOS_ListAdd(&g_vnodeFreeList, &vnode->actFreeEntry);//actFreeEntry换个地方挂,从活动链接换到空闲链表上.
g_freeVnodeSize++;//空闲链表节点数量增加
}
VnodeDrop();//释放互斥锁
......@@ -257,7 +256,7 @@ BOOL VnodeInUseIter(const struct Mount *mount)
{
struct Vnode *vnode = NULL;
LOS_DL_LIST_FOR_EACH_ENTRY(vnode, &g_vnodeActiveList, struct Vnode, actFreeEntry) {
LOS_DL_LIST_FOR_EACH_ENTRY(vnode, &g_vnodeActiveList, struct Vnode, actFreeEntry) {//遍历活动链表
if (vnode->originMount == mount) {//找到一致挂载点
if ((vnode->useCount > 0) || (vnode->flag & VNODE_FLAG_MOUNT_ORIGIN)) {//还在被使用
return TRUE;
......@@ -632,7 +631,7 @@ int VnodeDevLookup(struct Vnode *parentVnode, const char *path, int len, struct
/* dev node must in pathCache. */
return -ENOENT;
}
//虚拟设备 默认节点操作
//设备文件系统节点操作
static struct VnodeOps g_devfsOps = {
.Lookup = VnodeDevLookup,
.Getattr = VnodeGetattr,
......
......@@ -31,6 +31,20 @@
#include "shcmd.h"
/*
help命令用于显示当前操作系统内所有操作指令
OHOS # help
*******************shell commands:*************************
arp cat cd chgrp chmod chown cp cpup
date dhclient dmesg dns format free help hwi
ifconfig ipdebug kill log ls lsfd memcheck mkdir
mount netstat oom partinfo partition ping ping6 pwd
reset rm rmdir sem statfs su swtmr sync
systeminfo task telnet tftp touch umount uname watch
writeproc
*/
UINT32 OsShellCmdHelp(UINT32 argc, const CHAR **argv)
{
UINT32 loop = 0;
......@@ -44,7 +58,7 @@ UINT32 OsShellCmdHelp(UINT32 argc, const CHAR **argv)
}
PRINTK("*******************shell commands:*************************\n");
LOS_DL_LIST_FOR_EACH_ENTRY(curCmdItem, &(cmdInfo->cmdList.list), CmdItemNode, list) {
LOS_DL_LIST_FOR_EACH_ENTRY(curCmdItem, &(cmdInfo->cmdList.list), CmdItemNode, list) {//遍历命令链表
if ((loop & (8 - 1)) == 0) { /* 8 - 1:just align print */
PRINTK("\n");
}
......
......@@ -274,26 +274,26 @@ int SysOpen(const char *path, int oflags, ...)
}
}
procFd = AllocProcessFd();
procFd = AllocProcessFd();//分配进程描述符
if (procFd < 0) {
ret = -EMFILE;
goto ERROUT;
}
if ((unsigned int)oflags & O_DIRECTORY) {
ret = do_opendir(pathRet, oflags);
if ((unsigned int)oflags & O_DIRECTORY) {//目录标签
ret = do_opendir(pathRet, oflags);//打开目录
} else {
#ifdef LOSCFG_FILE_MODE //文件权限开关
va_list ap;
va_start(ap, oflags);
mode = va_arg(ap, int);
mode = va_arg(ap, int);//可变参数读取mode值
va_end(ap);
#endif
//当fd参数的值是AT_FDCWD,并且path参数是一个相对路径名,fstatat会计算相对于当前目录的path参数。
//如果path是一个绝对路径,fd参数就会被忽略
ret = do_open(AT_FDCWD, pathRet, oflags, mode);
ret = do_open(AT_FDCWD, pathRet, oflags, mode);//打开文件,返回系统描述符
}
if (ret < 0) {
......@@ -301,18 +301,18 @@ int SysOpen(const char *path, int oflags, ...)
goto ERROUT;
}
AssociateSystemFd(procFd, ret);
AssociateSystemFd(procFd, ret);//进程<->系统描述符 关联/绑定
if (pathRet != NULL) {
LOS_MemFree(OS_SYS_MEM_ADDR, pathRet);
}
return procFd;
return procFd;//返回进程描述符
ERROUT:
if (ret >= 0) {
AssociateSystemFd(procFd, ret);//进程FD 关联 系统FD
ret = procFd;//正常情况下返回的是进程Fd
} else {
FreeProcessFd(procFd);
FreeProcessFd(procFd);//
}
ERROUT_PATH_FREE:
if (pathRet != NULL) {
......
git add -A
git commit -m ' 同步官方代码 `2021/6/27` -- 本次官方对文件系统/设备驱动改动较大,目录结构进行了重新整理.
git commit -m ' 因同步导致的注解覆盖还原.
百万汉字注解 + 百篇博客分析 => 挖透鸿蒙内核源码
博客输出站点(国内):https://weharmonyos.com 或 http://8.134.122.205
博客输出站点(国外):https://weharmony.github.io
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册