LiteOS-A内核中的procfs文件系统分析

news2024/10/6 2:23:22

一、 procfs介绍

procfs是类UNIX操作系统中进程文件系统(process file system)的缩写,主要用于通过内核访问进程信息和系统信息,以及可以修改内核参数改变系统行为。需要注意的是,procfs文件系统是一个虚拟文件系统,不存在硬盘当中,而是系统启动时动态生成的文件系统,储存在内存中。procfs文件系统通常挂载在/proc目录下。

LiteOS-A是OpenAtom OpenHarmony(以下简称“OpenHarmony”)系统中使用的轻量系统内核,实现了procfs文件系统。本文主要对LiteOS-A内核中的procfs文件系统的设计、实现和使用进行介绍和分析。

procfs文件系统是LiteOS-A内核文件系统的一个案例,通过了解procfs文件系统,能够熟悉LiteOS-A的文件系统框架,并很好地将内核信息通过文件系统反馈给使用者。

1. Linux系统中的procfs文件系统包含的内容

Ubuntu 20.04中的/proc文件信息如下:

图1:Ubuntu proc目录信息

2. OS-A系统的命令以及procfs文件系统的内容

LiteOS-A的命令集:

LiteOS-A的proc目录信息如下:

图2:liteOS-A proc目录信息

二、 procfs文件系统的设计

LiteOS-A中使用VFS作为各个文件系统的粘合层,而VFS在OpenHarmony内核中采用树结构实现,树中的每一个节点都是Vnode结构体。VFS提供统一的抽象接口用于屏蔽文件系统之间的差异,其提供三大操作接口用于统一不同文件系统调用不同接口的现状。

VFS提供的三大操作接口:

• VnodeOps

• MountOps

• file_operations_vfs

VnodeOps用于控制Vnode节点,MountOps控制挂载点,file_operations_vfs提供常用的文件接口。

文件系统各自需要实现VFS提供的这三大接口,即实现系统本身需要的接口方法,让VFS能够调用这些接口即可。procfs文件系统虽作为一个伪文件系统pseudo-file system,但其仍旧需要实现上述接口。

1. VFS提供的重要接口

(1) Vnode 结构体:

struct Vnode {
    enum VnodeType type;                /* Vnode节点类型 */
    int useCount;                       /* 节点链接数 */
    uint32_t hash;                      /* 哈希值 */
    uint uid;                           /* 文件拥有者的user id */
    uint gid;                           /* 文件群组id */
    mode_t mode;                        /* 文件读写执行权限 */
    LIST_HEAD parentPathCaches;         /* 指向父节点的路径缓存 */
    LIST_HEAD childPathCaches;          /* 指向儿子节点的路径缓存 */
    struct Vnode *parent;               /* vnode父节点 */
    struct VnodeOps *vop;               /* vnode操作接口 */
    struct file_operations_vfs *fop;    /* 文件操作接口,即指定文件系统 */
    void *data;                         /* 数据,指向内部数据的指针 */
    uint32_t flag;                      /* 节点标签 */
    LIST_ENTRY hashEntry;               /* 挂入v_vnodeHashEntry[i]中 */
    LIST_ENTRY actFreeEntry;            /* 通过本节点挂载到空闲和使用链表中 */
    struct Mount *originMount;          /* 所在文件系统挂载信息 */
    struct Mount *newMount;             /* 其他挂载在这个节点中的文件系统信息 */
    char *filePath;                     /* Vnode的路径信息 */
    struct page_mapping mapping;        /* page mapping of the vnode */
};

图3:Vnode structure

Vnode功能接口定义:

struct VnodeOps {
    int (*Create)(struct Vnode *parent, const char *name, int mode, struct Vnode **vnode);// 创建节点
    int (*Lookup)(struct Vnode *parent, const char *name, int len, struct Vnode **vnode);// 查询节点
    int (*Open)(struct Vnode *vnode, int fd, int mode, int flags);// 打开节点
    ssize_t (*ReadPage)(struct Vnode *vnode, char *buffer, off_t pos);
    ssize_t (*WritePage)(struct Vnode *vnode, char *buffer, off_t pos, size_t buflen);
    int (*Close)(struct Vnode *vnode);// 关闭节点
    int (*Reclaim)(struct Vnode *vnode);// 回收节点
    int (*Unlink)(struct Vnode *parent, struct Vnode *vnode, const char *fileName);// 取消硬链接
    int (*Rmdir)(struct Vnode *parent, struct Vnode *vnode, const char *dirName);// 删除目录节点
    int (*Mkdir)(struct Vnode *parent, const char *dirName, mode_t mode, struct Vnode **vnode);// 创建目录节点
    int (*Readdir)(struct Vnode *vnode, struct fs_dirent_s *dir);// 读目录节点信息
    int (*Opendir)(struct Vnode *vnode, struct fs_dirent_s *dir);// 打开目录节点
    int (*Rewinddir)(struct Vnode *vnode, struct fs_dirent_s *dir);// 定位目录节点
    int (*Closedir)(struct Vnode *vnode, struct fs_dirent_s *dir);// 关闭目录节点
    int (*Getattr)(struct Vnode *vnode, struct stat *st);// 获取节点属性
    int (*Setattr)(struct Vnode *vnode, struct stat *st);// 设置节点属性
    int (*Chattr)(struct Vnode *vnode, struct IATTR *attr);// 改变节点属性
    int (*Rename)(struct Vnode *src, struct Vnode *dstParent, const char *srcName, const char *dstName);
    int (*Truncate)(structVnode*vnode,off_t len);// 缩小或扩大
int(*Truncate64)(structVnode*vnode,off64_t len);
int(*Fscheck)(structVnode*vnode,structfs_dirent_s*dir);
int(*Link)(structVnode*src,structVnode*dstParent,structVnode**dst,constchar*dstName);
int(*Symlink)(structVnode*parentVnode,structVnode**newVnode,constchar*path,constchar*target);
ssize_t(*Readlink)(structVnode*vnode,char*buffer,size_t bufLen);
};

Vnode根节点的初始化操作:

将全局Vnode表进行初始化,开始节点指向根目录/,全局节点g_rootVnode。

int VnodesInit(void)
{
    int retval = LOS_MuxInit(&g_vnodeMux, NULL);
    if (retval != LOS_OK) {
        PRINT_ERR("Create mutex for vnode fail, status: %d", retval);
        return retval;
    }
 
    LOS_ListInit(&g_vnodeFreeList);
    LOS_ListInit(&g_vnodeVirtualList);
    LOS_ListInit(&g_vnodeActiveList);
    retval = VnodeAlloc(NULL, &g_rootVnode);
    if (retval != LOS_OK) {
        PRINT_ERR("VnodeInit failed error %d\n", retval);
        return retval;
    }
    g_rootVnode->mode = S_IRWXU | S_IRWXG | S_IRWXO | S_IFDIR;
    g_rootVnode->type = VNODE_TYPE_DIR;
    g_rootVnode->filePath = "/";
 
    return LOS_OK;
}

(2) Mount结构体:

struct Mount {
    LIST_ENTRY mountList;              /* 全局Mount链表 */
    const struct MountOps *ops;        /* Mount的功能函数 */
    struct Vnode *vnodeBeCovered;      /* 要被挂载的节点 */
    struct Vnode *vnodeCovered;        /* 要挂载的节点 */
    struct Vnode *vnodeDev;            /* 设备vnode */
    LIST_HEAD vnodeList;               /* Vnode表的表头 */
    int vnodeSize;                     /* Vnode表的节点数量 */
    LIST_HEAD activeVnodeList;         /* 激活的节点链表 */
    int activeVnodeSize;               /* 激活的节点数量 */
    void *data;                        /* 数据,指向内部数据的指针 */
    uint32_t hashseed;                 /* Random seed for vfshash */
    unsigned long mountFlags;          /* 挂载标签 */
    char pathName[PATH_MAX];           /* 挂载点路径 */
    char devName[PATH_MAX];            /* 设备名称 /dev/sdb-1 */
};

图4:Mount structure

挂载点的接口定义:

struct MountOps {
    int (*Mount)(struct Mount *mount, struct Vnode *vnode, const void *data);
    int (*Unmount)(struct Mount *mount, struct Vnode **blkdriver);
    int (*Statfs)(struct Mount *mount, struct statfs *sbp);//统计文件系统的信息,类型、大小等
    int (*Sync)(struct Mount *mount);
};

(3)文件结构定义:

struct file
{
  unsigned int         f_magicnum;  /* file magic number. -- to be deleted */
  int                  f_oflags;    /* Open mode flags */
  struct Vnode         *f_vnode;    /* Driver interface */
  loff_t               f_pos;       /* File position */
  unsigned long        f_refcount;  /* reference count */
  char                 *f_path;     /* File fullpath */
  void                 *f_priv;     /* Per file driver private data */
  const char           *f_relpath;  /* realpath.  -- to be deleted */
  struct page_mapping  *f_mapping;  /* mapping file to memory */
  void                 *f_dir;      /* DIR struct for iterate the directory if open a directory */
  const struct file_operations_vfs *ops;
  int fd;
};

文件接口功能定义:

struct file_operations_vfs
{
  /* The device driver open method differs from the mountpoint open method */
 
  int     (*open)(struct file *filep);
  int     (*close)(struct file *filep);
  ssize_t (*read)(struct file *filep, char *buffer, size_t buflen);
  ssize_t (*write)(struct file *filep, const char *buffer, size_t buflen);
  off_t   (*seek)(struct file *filep, off_t offset, int whence);
  int     (*ioctl)(struct file *filep, int cmd, unsigned long arg);
  int     (*mmap)(struct file* filep, struct VmMapRegion *region);
  /* The two structures need not be common after this point */
 
  int     (*poll)(struct file *filep, poll_table *fds);
  int     (*stat)(struct file *filep, struct stat* st);
  int     (*fallocate)(struct file* filep, int mode, off_t offset, off_t len);
  int     (*fallocate64)(struct file *filep, int mode, off64_t offset, off64_t len);
  int     (*fsync)(struct file *filep);
  ssize_t (*readpage)(struct file *filep, char *buffer, size_t buflen);
  int     (*unlink)(struct Vnode *vnode);
};

2.文件系统的重要接口设计

procfs文件系统中每个目录或文件都是一个Vnode,也可以理解为一个entry。ProcDirEntry中的subdir指向的目录中的一个子项,其本质是一个单向链表的形式,并且采用头插法的形式进行节点的插入。

图5:DirEntry

图6:ProcFile

图7:ProcData

图8: ProcFileOperations

三、 procfs文件系统的实现

1. Procfs的注册过程

(1)向系统注册文件系统入口函数:

LOS_MODULE_INIT(ProcFsInit, LOS_INIT_LEVEL_KMOD_EXTENDED);

(2)向VFS文件系统表注册系统名以及实现的接口等:

const struct MountOps procfs_operations = {
    .Mount = VfsProcfsMount,
    .Unmount = NULL,
    .Statfs = VfsProcfsStatfs,
};
 
static struct VnodeOps g_procfsVops = {
    .Lookup = VfsProcfsLookup,
    .Getattr = VfsProcfsStat,
    .Readdir = VfsProcfsReaddir,
    .Opendir = VfsProcfsOpendir,
    .Closedir = VfsProcfsClosedir,
    .Truncate = VfsProcfsTruncate
};
 
static struct file_operations_vfs g_procfsFops = {
    .read = VfsProcfsRead,
    .write = VfsProcfsWrite,
    .open = VfsProcfsOpen,
    .close = VfsProcfsClose
};
 
// 注册文件系统名字以及实现的接口方法等
FSMAP_ENTRY(procfs_fsmap, "procfs", procfs_operations, FALSE, FALSE);

2. Procfs的初始化初始化

需要做的工作主要包括向OS注册procfs文件系统,生成procfs文件目录中的文件初始项,在liteOS-A具体包含目录power、mounts等。

procfs文件系统的初始化流程大致如下:

// 系统的入口函数
main(VOID)
|-> OsMain() // ./liteos/kernel/liteos_a/kernel/common/main.c
    | // 进行系统的相关初始化工作
    | -> EarliestInit()
    | -> ...
    |
    | -> KModInit()
         |-> ...
         |
         |-> OsInitCall(LOS_INIT_LEVEL_KMOD_EXTENDED) // 生成procfs文件系统并挂载到/proc目录
          |-> InitLevelCall(level)//根据不同的级别进行相关初始化工作,procfs的级别是8,其级别是文件系统向OS注册的
           | // ./liteos/kernel/liteos_a/fs/proc/os_adapt/proc_init.c
           |
           |-> ProcFsInit() // 进行procfs文件系统的具体初始化工作
           | |-> mkdir(PROCFS_MOUNT_POINT, PROCFS_DEFAULT_MODE) // 先生成/proc目录,之后需要将procfs文件系统挂载到该目录下
           | |-> mount(NULL, PROCFS_MOUNT_POINT, "procfs", 0, NULL)
           | | // 生成mount文件,包括分配Vnode和挂载Vnode
           | |
           | |-> ProcMountsInit()
           | | | // procfs具体项的初始化都写在一个独立的文件中,例如mounts在./liteos/kernel/liteos_a/fs/proc/os_adapt/mounts_proc.c
           | | |
           | | |-> ProcMountsInit(void)
           | | | // 创建mounts节点并挂载到该目录下,NULL位parent为父节点,若parent为NULL,则默认父节点为/proc
           | | |
           | | |-> CreateProcEntry("mounts", 0, NULL)
           | |  | // 先判断节点是文件属性还是目录属性,后选择具体的节点创建函数,在这选择File节点
           | |  |
           | |  |-> ProcCreateFile(parent, name, NULL, mode)
           | |   |-> struct ProcDirEntry *pn = NULL
           | |   |-> ProcAllocNode(&parent, name, S_IFREG | mode) // 具体的分配节点
           | |    |-> struct ProcDirEntry *pn = NULL;
           | |    | // 首先对节点的合法性进行相关检查,例如parent是否NULL,name是否NULL等
           | |    |
           | |    |-> pn = (struct ProcDirEntry *)malloc(sizeof(struct ProcDirEntry));//分配一个struct ProcDirEntry内存地址
           | |    | // 对生成的节点赋予一些属性,例如节点名字长度,权限,名字等,每个ProcDirEntry都需要指定一个ProcFile成员,里面含有具体信息
           | |    |
           | |    |-> pn->nameLen = strlen(lastName);
           | |    |-> pn->mode = mode;
             | |    |-> ret = memcpy_s(pn->name, sizeof(pn->name), lastName, strlen(lastName) + 1);
             | |    |-> pn->pf = (struct ProcFile *)malloc(sizeof(struct ProcFile));
             | |    |-> pn->pf->pPDE = pn;// ProcFile的parent是生成的pn节点
             | |   | // 生成对应的节点,对节点指定相应的函数接口后,需要挂载的父节点中
             | |   |
             | |   |-> ProcAddNode(parent, pn)
             | |    |// 先判断parent是否为NULL以及pn是否已经有parent,即判断是否已挂载
|||
|||// 在这里可知一个目录下的子目录以及文件都是以一个单链表的形式存储的,且采用的是头插法,即最先生成的在最后面
|||-> pn->parent = parent;
|||-> pn->next = parent->subdir;
|||-> parent->subdir = pn;
||->...
||
||->ProcPmInit()// 目录初始化工作
|||// power目录下含有子目录,但是目录生成的过程都一样,在这以power文件夹为例
|||->structProcDirEntry*power =CreateProcEntry("power", S_IFDIR | S_IRWXU | S_IRWXG | S_IROTH,NULL);
||||->CreateProcEntry("power", S_IFDIR | S_IRWXU | S_IRWXG | S_IROTH,NULL)
|||||->// 先判断节点是文件属性还是目录属性,后选择具体的节点创建函数,在这选择目录节点
|||||
|||||->ProcCreateDir(parent, name,NULL, mode)
||||||// 这里节点创建和挂载和上述文件节点创建一样,不再赘述
||||||
||||||->ProcAllocNode(&parent, name, S_IFREG | mode)// 具体的分配节点
||||||->ProcAddNode(parent, pn)
|||||
||||
|||->...
||
|...

四、procfs业务分析

1. procfs挂载过程分析

在procfs文件系统的挂载过程中,若使用qemu进行调试,则挂载的命令大致如下: mount -R -t procfs [Dir_Path]

mount的系统调用间接调用procfs的mount接口。

用户输入挂载命令后,引发系统调用SysMount开始逐层调用:

-> ...
-> SysMount(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags,const void *data)
|--|-> 将路径,文件系统等转化之后调用mount
|  |-> mount(sourceRet, targetRet, (filesystemtype ? fstypeRet : NULL), mountflags, dataRet)
|  |   |-> //找到指定的文件系统
|  |   |-> fsmap = mount_findfs(filesystemtype)
|  |   |-> mops = fsmap->fs_mops // 为mount节点指定mount的接口函数
|  |   |-> //找到挂载目录对应的Vnode并且设置文件系统相关信息
|  |   |-> VnodeLookup(target, &mountpt_vnode, 0)
|  |   |   |->VnodeLookupAt(path, vnode, flags, NULL)
|  |   |   |  |-> //对目录变成绝对路径并且从全局Vnode链表中开始找
|  |   |   |  |-> PreProcess(path, &startVnode, &normalizedPath)
|  |   |   |  |  |-> vfs_normalize_path(NULL, originPath, &absolutePath)
|  |   |-> mnt = MountAlloc(mountpt_vnode, (struct MountOps*)mops)
|  |   |-> mops->Mount(mnt, device, data)//进入具体的procfs文件系统的mount函数
|  |   |  |-> VfsProcfsMount(struct Mount *mnt, struct Vnode *device, const void *data)
|  |   |  |   |-> VnodeAlloc(&g_procfsVops, &vp);//生成一个Vnode用于挂载mount节点和procfs文件系统的root节点
|  |   |  |   |-> root = GetProcRootEntry(); //获取procfs文件系统的根节点
|  |   |  |   |-> vp->data = root; // 
|  |   |  |   |-> vp->originMount = mnt;// 将vp挂载在挂载目录所对应的mount节点上
|  |   |  |   |-> mnt->data = NULL;
|  |   |  |   |-> mnt->vnodeCovered = vp;// mount节点挂载的Vnode是该文件系统,方便后续在mount链表中找挂载点
|  |   |  |   |-> vp->type = root->type;
|  |   |...

2. 节点的增加过程分析

关键代码如下:

temp = ProcFindNode(parent, pn->name);
 if (temp != NULL) {
     PRINT_ERR("Error!ProcDirEntry '%s/%s' already registered\n", parent->name, pn->name);
     spin_unlock(&procfsLock);
     return -EEXIST;
 }
 pn->parent = parent;
 pn->next = parent->subdir;
 parent->subdir = pn;

为了更好地说明,假设目前已经在系统中生成了proc/和mounts节点,proc/节点就是该文件系统的根节点,此时两者的关系可以用下图表示:

图9:层级目录的关系

此时若需要在两者在插入一个power节点,则首先需要先生成一个power节点如下,再改变相应的指向即可,具体可以参考图10,给出三者之间的关系,最终的节点效果如图11。

图10:生成一个新节点

图11:重新组合

3、writeproc shell命令的创建

liteOS-A中含有一个叫writeproc的shell命令,使用格式如下:

writeproc value >> path

shell命令的创建方式主要有两种,分静态注册和动态注册,writeproc命令使用静态注册方式进行注册,在本文中也主要介绍静态注册。

shell开发的流程如下:

① 定义一个新增命令所要调用的执行函数xxx;

② 使用SHELLCMD_ENTRY函数添加新增命令项;

③ 在链接选项liteos_tables_ldflags.mk中添加链接该新增命令项参数;

④ 重新编译代码后运行。

writeproc的注册如下:

// 定义一个具体的执行函数
int OsShellCmdWriteProc(int argc, char **argv);
// 新增命令项
SHELLCMD_ENTRY(writeproc_shellcmd, CMD_TYPE_EX, "writeproc", XARGS, (CmdCallBackFunc)OsShellCmdWriteProc);

writeproc的具体流程分析:

①首先由用户按照命令格式进行输入;

②OsShellCmdWriteProc函数对输入的命令进行分析,并采取相关的动作。

-> ...
 -> // 使用shell命令唤起writeproc注册函数
 -> writeproc value >> path
  |-> // 进行初始化工作,主要用于判断输入路径是否合法,节点是否存在
  |-> struct ProcDirEntry *handle = NULL;
  |-> const char *rootProcDir = "/proc/";
  |-> handle = OpenProcFile(realPath, O_TRUNC) // 若路径合法则找到对应的Vnode
  |   |-> pn = ProcFindEntry(fileName)
  |   |   |-> int leveltotal = 0;// leveltotal用于判定文件所对应的层级,一个/表示一层
  |   |   | // 遍历Vnode找到对应的Vnode并返回
  |   |-> pn->flags = (unsigned int)(pn->flags) | (unsigned int)flags// 设置节点相应的权限
  |   |-> ...
  |   WriteProcFile(handle, value, len) // 找到文件句柄之后开始写入数据
  |   | // 使用Vnode的文件接口对ProcFile数据成员进行写入
  |   |-> result = pde->procFileOps->write(pde->pf, (const char *)buf, len, &(pde->pf->fPos))
  |...

根据文件名查找Vnode的关键代码:

pn = &g_procRootDirEntry;
while ((pn != NULL) && (levelcount < leveltotal)) {
 levelcount++;
 isfoundsub = 0;
 while (pn != NULL) {
      next = strchr(path, '/');
      if (next == NULL) {
           while (pn != NULL) {
               if (strcmp(path, pn->name) == 0) {
                    spin_unlock(&procfsLock);
                    return pn;
               }
               pn = pn->next;
           }
           pn = NULL;
           spin_unlock(&procfsLock);
           return pn;
      }
  
      len = next - path;
      if (pn == &g_procRootDirEntry) {
           if (levelcount == leveltotal) {
               spin_unlock(&procfsLock);
               return pn;
           }
           len = g_procRootDirEntry.nameLen;
      }
      if (ProcMatch(len, path, pn)) {
           isfoundsub = 1;
           path += len + 1;
           break;
      }
  
      pn = pn->next;
 }
}

五、总结

本文介绍了LiteOS-A内核下proc相关目录信息,并且对LiteOS-A内核中procfs文件系统的原理和实现,结合源码进行了分析。同时,通过writeproc shell命令介绍了procfs的使用。希望读者可以掌握LiteOS-A文件系统的基本知识,更好地运用于基于LiteOS-A内核的系统移植工作。

为了帮助到大家能够更有效的学习OpenHarmony 开发的内容,下面特别准备了一些相关的参考学习资料:

OpenHarmony 开发环境搭建:https://qr18.cn/CgxrRy

《OpenHarmony源码解析》:https://qr18.cn/CgxrRy

  • 搭建开发环境
  • Windows 开发环境的搭建
  • Ubuntu 开发环境搭建
  • Linux 与 Windows 之间的文件共享
  • ……

系统架构分析:https://qr18.cn/CgxrRy

  • 构建子系统
  • 启动流程
  • 子系统
  • 分布式任务调度子系统
  • 分布式通信子系统
  • 驱动子系统
  • ……

OpenHarmony 设备开发学习手册:https://qr18.cn/CgxrRy

在这里插入图片描述

OpenHarmony面试题(内含参考答案):https://qr18.cn/CgxrRy

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AI副业:3天涨粉10w+,这些人凭什么这么火? (附教程案例)

大家好&#xff0c;我是向阳 今天给大家分享一下免费的 AI 插画工具 & 某书图文涨粉项目 某书上有这么一类笔记&#xff1a;精美的插话 几句提供情绪价值的文案 这类笔记可以说几乎是发一篇爆一篇&#xff01;获赞涨粉如喝水&#xff0c;并且评论区有不少人求图和求教程…

利用Axure模板快速设计,可视化大屏信息大屏,含近200例资源和各类部件

模板类别&#xff1a; **通用模板&#xff1a;**提供基础的布局和设计元素&#xff0c;适用于各种场景。 **行业特定模板&#xff1a;**如农业、医院、销售、能源、物流、政府机关等&#xff0c;针对不同行业提供专业模板。 **数据展示模板&#xff1a;**包括大数据驾驶舱、统…

正点原子LWIP学习笔记(一)lwIP入门

lwIP入门 一、lwIP简介&#xff08;了解&#xff09;二、lwIP结构框图&#xff08;了解&#xff09;三、如何学习lwIP&#xff08;熟悉&#xff09; 一、lwIP简介&#xff08;了解&#xff09; lwIP是一个小型开源的TCP/IP协议栈 阉割的TCP/IP协议 TCP/IP协议栈结构&#xff0…

什么是 UUID,uuid

文章目录 一、是什么二、为什么三、怎么用 标题&#xff1a;深入探讨UUID&#xff1a;全球唯一标识符的秘密 一、是什么 在当今数字化时代&#xff0c;唯一标识符&#xff08;UUID&#xff09;在计算机科学领域扮演着重要的角色。UUID是一种用于标识信息的唯一字符串&#xff0…

【数据结构】树、森林与二叉树的转换 |树的存储 |双亲表示法 |孩子表示法 |孩子兄弟表示法

&#x1f4d6;专栏文章&#xff1a;数据结构学习笔记 &#x1faaa;作者主页&#xff1a;格乐斯 前言 树的存储 双亲表示法孩子表示法孩子兄弟表示法 树、森林与二叉树的转换 树的存储 双亲表示法 首先给每个结点编号&#xff0c;再将这些结点的双亲结点的序号存储起来&a…

从零开始实现自己的串口调试助手(1) - ui界面搭建

UI 界面搭建 ui界面整体演示 ui对象拆分 更多的细节就不方便展开了&#xff0c;下面有提示完成ui设计的提示 在创建工程前 记得把编码改为utf-8 ui设计技巧: ctrl 鼠标左键实现拖动实现复制粘贴 groupBox &#xff1a; 带标题的文本框 栅格布局 -- 只有一个控件的时候会铺满…

C++使用范围for语句处理多维数组

循环访问二维数组时出现如下问题&#xff1a; error: invalid range expression of type int *; no viable begin function availablefor (auto col : row){^ ~~~ 1 error generated.问题复现 初始化数组 constexpr size_t rowCnt 3, colCnt 4; int ia[rowCnt][colCnt]; fo…

【网站项目】SpringBoot380百天公司财务管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

AD162A低功耗语音解码芯片,助眠耳机方案推荐—云信通讯

睡眠一直是很多人所追寻的目标&#xff0c;无论是因为工作压力过大&#xff0c;还是日常烦扰的思绪&#xff0c;一个好的睡眠质量对每个人来说都至关重要。为了解决这个问题&#xff0c;一些科技公司开发了高品质的助眠耳机&#xff0c;通过音乐和声音来帮助入睡&#xff0c;为…

618好物推荐,每一款都是心头好

618买什么&#xff1f;&#xff01;是不是又要开始剁手了&#xff1f;&#xff01;买贵不如买对&#xff0c;收下这份好物推荐&#xff0c;无论你是有购物需求还是观望中&#xff0c;无论是日常好物还是智能家电&#xff0c;无论你是追求平价还是实用&#xff0c;这一篇好物推荐…

eNSP-集线器(hub)连接局域网

一、拓扑结构搭建 二、主机配置 pc1、pc2、pc3 三、测试 Hub相当于大家共享一条线路(类似于电线搭电)&#xff0c;线路上的所有的设备都会接收同样的信息。

【MySQL02】【 InnoDB 记录存储结构】

文章目录 一、前言二、InnoDB 行格式1. COMPACT 行格式1.1 记录的额外信息1.2 记录的真实数据1.3 综上 2. REDUNDANT 行格式2.1 字段长度偏移列表2.2 记录头信息 3. DYNAMIC 行格式和 COMPPESED 行格式 三、InnoDB 数据页结构1. File Header (文件头部)2. Page Header (页面头部…

搭建淘宝扭蛋机小程序:技术选型与最佳实践

随着移动互联网的快速发展&#xff0c;小程序作为一种轻量级应用&#xff0c;以其无需安装、即用即走的特点&#xff0c;受到了广大用户的喜爱。在电商领域&#xff0c;淘宝作为国内最大的电商平台之一&#xff0c;也积极拥抱小程序技术&#xff0c;为用户提供更加便捷、个性化…

纯CSS画浮动卡通蓝天白云草坪动画效果

文章目录 效果展示 背景效果实现效果展示HTML结构CSS样式 云效果实现效果展示HTML结构CSS样式 草效果实现单颗小草效果展示HTML结构CSS样式 组合小草效果展示HTML结构CSS样式 完整代码 在网页设计中&#xff0c;添加动态元素如浮动云朵可以为用户带来更加生动和自然的体验。虽然…

BeanDefinition作用

BeanDefinition接口 BeanDefinition 描述一个 Bean 实例&#xff0c;这个实例有哪些属性值、构造函数以及一些其他信息&#xff0c;就是描述Bean实例的信息。 BeanDefinition是一个接口&#xff0c;允许BeanFactoryPostProcessor 内省和修改属性值和其他 Bean 元数据。 点击了…

【simple-admin】simple-admin-core 首次服务启动 如何配置mysql数据库表 | 如何docker启动core

一、下载启动S-A 1、下载源码 https://github.com/suyuan32/simple-admin-core.git git clone https://github.com/suyuan32/simple-admin-core.git2、修改etc下yaml配置 需要对RPC和API 分别2个文件夹下的etc下的yaml进行修改 替换成我们的数据库 3、初始化数据库 核心代…

芯课堂 | UI Creator 物理键盘移植指南

LVGL提供输入设备的种类一共有5种&#xff0c;分别是&#xff1a;touchpad&#xff08;触摸板&#xff09;、mouse&#xff08;鼠标&#xff09;、keypad&#xff08;键盘&#xff09;、encoder&#xff08;编码器&#xff09;、button&#xff08;外部按键&#xff09;。而基于…

抖音直播预告|换新·升级 Aigtek安泰电子2024新产品发布会

多系列功放产品大升级&#xff01; 深耕电子测试仪器领域&#xff0c;攻坚行业尖端技术&#xff0c;不断自主研发与创新&#xff0c;是安泰电子一如既往的坚持&#xff01;通过长久技术积淀&#xff0c;Aigtek安泰电子多系列功放也在2024年迎来了全新升级&#xff01; 本次我们…

青年精英大会笔记

2024年5月16日上午 大会主席致辞【郑纬民】 郑纬民【清华大学教授、中国工程院院士】——78岁【看着精神抖擞】&#xff01;牛哇 学件初步探索【周志华】 南京大学 zhouzhnju.edu.cn Preliminary Exploration to Learnware 土生土长的 听完介绍感觉这个研究工作很不错&…

go语言中的一个特别的语法 //go:embed 可将将静态文件内容读取到string, []byte和 embed.FS 变量并直接打包到exe包中

go语言中的一个特别的语法 //go:embed 看上去像是注释&#xff0c;实则是golang中的一个内置的语法&#xff0c;而且是仅在你的go代码编译时生效的语法&#xff0c; 借助他我们可以将我们的静态资源文件读取到FS直接打包到我们的exe执行文件中。 同时他还支持文件的模式匹配…