xv6 文件系统(上)

〇、前言

本文将会结合 xv6 源码讨论文件系统的工作原理。

一、文件系统实现概述

xv6 文件系统可以用下面的图来表示:
在这里插入图片描述
按照分层的方式进行理解:

  • 在最底层是磁盘,也就是一些实际保存数据的存储设备,正是这些设备提供了持久化存储。
  • 在这之上是buffer cache或者说 block cache,这些cache可以避免频繁的读写磁盘。这里我们将磁盘中的数据保存在了内存中。
  • 为了保证持久性,再往上通常会有一个logging层
  • 在logging层之上,xv6有inode cache,这主要是为了同步(synchronization)。inode通常小于一个disk block,所以多个inode通常会打包存储在一个disk block中。为了向单个inode提供同步操作,xv6维护了inode cache
  • 再往上就是inode本身了。它实现了read/write。
  • 再往上,就是文件名&文件描述符操作。

二、文件系统怎么使用硬盘

有些术语有点让人困惑,它们是 sectorsblocks

  • sector 通常是磁盘驱动可以读写的最小单元,它过去通常是512字节;
  • block 通常是操作系统或者文件系统视角的数据。它由文件系统定义,在xv6中它是1024字节。所以 xv6 中一个block对应两个sector。通常来说一个block对应了一个或者多个sector。

有的时候,人们也将磁盘上的 sector 称为 block,所以这里的术语也不是很精确。

从文件系统的角度来看磁盘还是很直观的。因为对于磁盘就是读写 block 或者 sector,我们可以将磁盘看作是一个巨大的block数组,数组从0开始,一直增长到磁盘的最后。

在这里插入图片描述

而文件系统的工作就是将所有的数据结构以一种能够在重启之后重新构建文件系统的方式,存放在磁盘上。虽然有不同的方式,但是 xv6 使用了一种非常简单,但是还挺常见的布局结构。通常来说:

  • block0 要么没有用,要么被用作boot sector来启动操作系统。
  • block1 通常被称为super block,它描述了文件系统。它可能包含磁盘上有多少个block共同构成了文件系统这样的信息。我们之后会看到XV6在里面会存更多的信息,你可以通过block1构造出大部分的文件系统信息。
  • xv6 中,logblock2 开始,到 block32 结束。实际上 log 的大小可能不同,这里在super block中会定义log就是30个block
  • 接下来在 block32block45 之间,xv6 存储了 inode
  • 之后是 bitmap block,这是我们构建文件系统的默认方法,它只占据一个block。它记录了数据block是否空闲。
  • 之后就全是数据block了,数据block存储了文件的内容和目录的内容(目录也是文件)。

通常来说,bitmap block,inode blocks和log blocks被统称为metadata block。它们虽然不存储实际的数据,但是它们存储了能帮助文件系统完成工作的元数据。

假设 inode64 字节,如果你想要读取 inode10,那么你应该按照下面的公式去对应的 block 读取 inode

在这里插入图片描述
所以 inode0block32inode17 会在 block33。只要有 inode 的编号,我们总是可以找到 inode 在磁盘上存储的位置。

三、inode 结构

inode 结构体在内存中的表示如下:

#define NDIRECT 12

// in-memory copy of an inode
struct inode {
  uint dev;           // Device number
  uint inum;          // Inode number
  int ref;            // Reference count
  struct sleeplock lock; // protects everything below here
  int valid;          // inode has been read from disk?

  short type;         // copy of disk inode
  short major;
  short minor;
  short nlink;
  uint size;
  uint addrs[NDIRECT+1];
};

在硬盘中的表示如下:

// On-disk inode structure
struct dinode {
  short type;           // File type
  short major;          // Major device number (T_DEVICE only)
  short minor;          // Minor device number (T_DEVICE only)
  short nlink;          // Number of links to inode in file system
  uint size;            // Size of file (bytes)
  uint addrs[NDIRECT+1];   // Data block addresses
};

我们先研究在硬盘的 inode 结构:

  • 通常来说它有一个 type 字段,表明 inode 是文件还是目录。
  • nlink字段,也就是 link计数器,用来跟踪究竟有多少文件名指向了当前的inode
  • size字段,表明了文件数据有多少个字节。
  • 不同文件系统中的表达方式可能不一样,不过在 xv6 中接下来是一些 block 的编号,例如编号0,编号1,等等。xv6inode 中总共有12block编号。这些被称为direct block number。这 12block 编号指向了构成文件的前 12block。举个例子,如果文件只有2个字节,那么只会有一个block编号0,它包含的数字是磁盘上文件前2个字节的block的位置。
  • 之后还有一个 indirect block number,它对应了磁盘上一个 block,这个 block 包含了256block number,这 256block number 包含了文件的数据。所以 inodeblock number0block number11 都是direct block number,而 block number12保存的 indirect block number 指向了另一个block。所以数组 addrs[NDIRECT+1] 的长度为 13

如果一个 block号4 个字节,那么一个block 就可以存储 1024/4 = 256block号。所以我们很轻易地计算出 xv6 中一个文件的大小上限:(256+12)*1KB。因为一个块号占 4 个字节,那么 xv6 一共可以寻址 2^32个 block,一个 block 大小为 1KB,那么 xv6 一共可以安装的硬盘大小为 2^42B,也就是 4TB

四、目录

文件系统的酷炫特性就是层次化的命名空间hierarchical namespace),你可以在文件系统中保存对用户友好的文件名。大部分 Unix文件系统 有趣的点在于,一个目录本质上是一个文件加上一些文件系统能够理解的结构。在 xv6 中,这里的结构极其简单。每一个目录包含了directory entries,每一条 entry 都有固定的格式:

  • 2 个字节包含了目录中文件或者子目录的 inode 编号;
  • 接下来的 14 个字节包含了文件或者子目录名。

所以每个 entry 总共是16个字节。

对于实现路径名查找,这里的信息就足够了。假设我们要查找路径名 “/y/x”,我们该怎么做呢?

从路径名我们知道,应该从 root inode 开始查找。通常 root inode 会有固定的 inode 编号,在 xv6 中,这个编号是1。我们该如何根据编号找到 root inode 呢?

从前一节我们可以知道,inodeblock32 开始,如果是 inode1,那么必然在block32 中的 64128 字节的位置。所以文件系统可以直接读到 root inode 的内容。

由于 inodein-memory copy of an inode) 中包含对于路径名查找程序,接下来就是扫描 root inode 包含的所有 block,就是为了找到 “y” 。该怎么找到 root inode 所有对应的 block 呢?根据前一节的内容就是读取所有的 direct block numberindirect block number

如果找到了,那么 目录y 也会有一个 inode 编号,假设是 251。我们可以继续从inode251查找,先用公式计算出 block号,然后再根据偏移量读取 inode251 的内容,之后再扫描 inode 所有对应的block,以找到 “x”并得到文件x对应的inode编号,最后将其作为路径名查找的结果返回。

这么简单的一件事,上面的过程一共查询了三次 inode(两次目录node,一次文件xinode ),两次 扫描block 操作。

很明现,这里的结构不是很有效。为了找到一个目录名,需要线性扫描。实际的文件系统会使用更复杂的数据结构来使得查找更快,当然这又是设计数据结构的问题,而不是设计操作系统的问题。出于简单和更容易解释的目的,xv6 使用了这里这种非常简单的数据结构。

五、文件系统工作示例

首先启动 xv6,启动 xv6 的过程中,调用了makefs指令,来创建一个文件系统。

所以makefs创建了一个全新的磁盘镜像,在这个磁盘镜像中包含了我们在指令中传入的一些文件。makefs为你创建了一个包含这些文件的新的文件系统

xv6 总是会打印文件系统的一些信息,所以从指令的下方可以看出有46meta block,其中包括了:

  • boot block
  • super block
  • 30个log block
  • 13个inode block
  • 1个bitmap block

之后是954data block。所以这是一个袖珍级的文件系统,总共就包含了1000block

稍微修改了一下 xv6之后,使得任何时候写入 block 都会打印出 block 的编号:

void
log_write(struct buf *b)
{
  ...
  printf("write: %d\n",b->blockno);     // 打印使用的 block 号
  log.lh.block[i] = b->blockno;
  if (i == log.lh.n) {  // Add new block to log?
    bpin(b);
    log.lh.n++;
  }
  release(&log.lock);
}

接下来我们运行一个命令 echo hi > x,来看一下特定的命令对哪些 block 做了写操作,并理解为什么要对这些 block 写入数据。

我们通过echo hi > x,来创建一个文件x,并写入字符 hi,这会打印以下信息:

$ echo hi > x
1	write: 33
2	write: 33
3	write: 46
4	write: 32
5	write: 33
6	write: 45
7	write: 908
8	write: 908
9	write: 33
10	write: 908
11	write: 33

这里会有几个阶段:

  • 第一阶段是创建文件x
  • 第二阶段将 hi写入文件;
  • 第三阶段将 \n换行符写入到文件;

看一下user/echo.c的代码实现,基本就是这3个阶段:

#include "kernel/types.h"
#include "kernel/stat.h"
#include "user/user.h"

int
main(int argc, char *argv[])
{
  int i;

  for(i = 1; i < argc; i++){
    write(1, argv[i], strlen(argv[i]));
    if(i + 1 < argc){
      write(1, " ", 1);
    } else {
      write(1, "\n", 1);
    }
  }
  exit(0);
}

首先它将 hi写入到标准输出(我们在此处已经重定向了,对于 echo()argc==2 ),然后继续写入 \n

1、创建文件x

我们已经了解到,x文件是创建在根目录下的。既然是创建文件,第一步肯定是分配一个 inode。因此要在空闲 inode 列表中找一个空闲的 inode,然后打上标记。

然后我们要在此 inode 中写入一些信息,比如 inode 的引用计数、addrs等内容。

以上两步对应于:

1	write: 33
2	write: 33

由于我们在根目录下创建了一个文件x,因此根目录的 inodeinode 映射的addrs中的内容都要被修改。首先修改的是根目录中的第一个 block文件x 是根目录下的一个 entry,自然要修改这个 block。然后根目录的 inode 也要修改,因为根目录大小变了。之后,又修改了一次文件xinode

以上两步对应于:

3	write: 46
4	write: 32
5	write: 33

2、写入文件x hi

因为要引用某个块,所以要更新 bitmap。之后要往某个 direct block 中写两个字符。最后还需要更改文件x inode中的size字段,而且还会将 inodeblock 关联起来,即修改 addrs 字段。这对应于:

6	write: 45
7	write: 908
8	write: 908
9	write: 33

这意味着文件系统挑选了 block 908

3、写入\n

这就很简单了,写文件,修改 inode

10	write: 908
11	write: 33

4、再执行一条命令

通过再执行一条命令 echo hii > x:这条命令会将 文件x 中内容覆盖,并且重新写 hii\n(注意这是分开写的)。因为文件x 已经创建过了,因此这个写入block 的记录和上面的那个命令是不同的:

$ echo hii > x
1	write: 45
2	write: 33
3	write: 45
4	write: 908
5	write: 908
6	write: 33
7	write: 908
8	write: 33

首先修改bitmap,然后修改 inode。这是为了将 x文件中的内容全部清空,那么这个block 自然就不用了,需要修改 inode 中 的 sizeaddrs字段等。这对应于:

1	write: 45
2	write: 33

之后又要继续写 hii。这意味着要重新启用这个 block,因此要修改 bitmap。之后写入 hii,写完后,继续更新 inode,这对应于:

3	write: 45
4	write: 908
5	write: 908
6	write: 33

因为写入了 3 个字符,但是这里的 block 被写了两次,目前还找不到原因。事实上,我尝试写了几十个字符,也只有两次写入 block

$ echo asdfsdfasdf > x
write: 45
write: 33
write: 45
write: 908
write: 908
write: 33
write: 908
write: 33

然后就是写入\n了:

7	write: 908
8	write: 33

以上就是文件系统的工作示例了。

五、代码

sysfile.c中包含了所有与文件系统相关的函数,分配inode发生在sys_open()函数中,这个函数会负责创建文件:

uint64
sys_open(void)
{
  char path[MAXPATH];
  int fd, omode;
  struct file *f;
  struct inode *ip;
  int n;

  argint(1, &omode);
  if((n = argstr(0, path, MAXPATH)) < 0)
    return -1;

  begin_op();

  if(omode & O_CREATE){
    ip = create(path, T_FILE, 0, 0);
    if(ip == 0){
      end_op();
      return -1;
    }
  } 
  ...
}

superblock 中存储着各种各样的信息,包括 inode 的数量 ninodes

// Disk layout:
// [ boot block | super block | log | inode blocks |
//                                          free bit map | data blocks]
//
// mkfs computes the super block and builds an initial file system. The
// super block describes the disk layout:
struct superblock {
  uint magic;        // Must be FSMAGIC
  uint size;         // Size of file system image (blocks)
  uint nblocks;      // Number of data blocks
  uint ninodes;      // Number of inodes.
  uint nlog;         // Number of log blocks
  uint logstart;     // Block number of first log block
  uint inodestart;   // Block number of first inode block
  uint bmapstart;    // Block number of first free map block
};

创建的过程中,它会调用 create()函数:

static struct inode*
create(char *path, short type, short major, short minor)
{
  struct inode *ip, *dp;
  char name[DIRSIZ];

  if((dp = nameiparent(path, name)) == 0)
    return 0;

  ilock(dp);

  if((ip = dirlookup(dp, name, 0)) != 0){
    iunlockput(dp);
    ilock(ip);
    if(type == T_FILE && (ip->type == T_FILE || ip->type == T_DEVICE))
      return ip;
    iunlockput(ip);
    return 0;
  }

  if((ip = ialloc(dp->dev, type)) == 0){
    iunlockput(dp);
    return 0;
  }

  ilock(ip);
  ip->major = major;
  ip->minor = minor;
  ip->nlink = 1;
  iupdate(ip);

  if(type == T_DIR){  // Create . and .. entries.
    // No ip->nlink++ for ".": avoid cyclic ref count.
    if(dirlink(ip, ".", ip->inum) < 0 || dirlink(ip, "..", dp->inum) < 0)
      goto fail;
  }

  if(dirlink(dp, name, ip->inum) < 0)
    goto fail;

  if(type == T_DIR){
    // now that success is guaranteed:
    dp->nlink++;  // for ".."
    iupdate(dp);
  }

  iunlockput(dp);

  return ip;
}

create()函数中首先会解析路径名并找到最后一个目录,之后会查看文件是否存在,如果存在的话会返回错误。之后就会调用ialloc(),这个函数会为 文件x 分配inodeialloc函数位于fs.c文件中:

// Allocate an inode on device dev.
// Mark it as allocated by  giving it type type.
// Returns an unlocked but allocated and referenced inode,
// or NULL if there is no free inode.
struct inode*
ialloc(uint dev, short type)
{
  int inum;
  struct buf *bp;
  struct dinode *dip;

  for(inum = 1; inum < sb.ninodes; inum++){
    bp = bread(dev, IBLOCK(inum, sb));
    dip = (struct dinode*)bp->data + inum%IPB;
    if(dip->type == 0){  // a free inode
      memset(dip, 0, sizeof(*dip));
      dip->type = type;
      log_write(bp);   // mark it allocated on the disk
      brelse(bp);
      return iget(dev, inum);
    }
    brelse(bp);
  }
  printf("ialloc: no inodes\n");
  return 0;
}

它会遍历所有可能的 inode编号IBLOCK(inum, sb)可以将通过inum找到 superblock 对应的 block numberbread()可以通过设备名和 blockno 可以得到硬盘中的 block 在内存中的缓存,记为 bp。由于 bpdblock 中的一个拷贝,因此接下来就直接在 bp 中操作了。

找到inode所在的block,再看位于block中的inode数据的type字段。如果这是一个空闲的 inode,那么将其type字段设置为文件,这会将inode标记为已被分配。函数中的log_write() 就是我们之前看到在console中有关写block的输出。这里的 log_write() 是我们看到的整个输出的第一个

以上就是第一次写磁盘涉及到的函数调用。

这里有个有趣的问题,如果有多个进程同时调用create函数会发生什么? 对于一个多核的计算机,进程可能并行运行,两个进程可能同时会调用到 ialloc函数,然后进而调用 bread() 函数。所以必须要有一些机制确保这两个进程不会互相影响。

这个已经在上一个实验中讨论了,就不再赘述了。

六、总结(大模型总结)

这篇文章深入探讨了文件系统在 xv6 环境下的工作原理,分层次地介绍了文件系统的实现概述、硬盘使用、inode 结构、目录、以及文件系统的工作示例。文章详细解释了inodeblock在磁盘上的分布方式,以及文件的创建、写入和元数据修改的过程。它提供了清晰的代码示例和相关函数的解释,为理解文件系统的内部机制提供了深入而全面的认识。

文章非常详尽地描述了 xv6 文件系统的内部机制和数据结构,从底层的磁盘操作到高层的文件操作都有涉及。它还着重强调了在文件系统设计中的一些限制和简化,尤其是对于路径查找等操作的效率问题。这样的详细解释对于想要深入了解文件系统工作原理的读者来说是非常有帮助的。

全文完,感谢阅读。

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

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

相关文章

mysql函数()之常见字符串函数

MySQL中常见的字符串函数有以下几种&#xff1a; CONCAT()&#xff1a;将两个或多个字符串连接在一起。 用法&#xff1a;CONCAT(string1, string2, …) 效果图&#xff1a; LENGTH()&#xff1a;返回字符串的长度。 用法&#xff1a;LENGTH(string) 效果图&#xff1a; UP…

7款不容错过的前端特效源码分享(附源码及演示效果)

分享7款非常不错炫酷的前端特效源码 其中包含css动画特效、js原生特效、svg特效等 下面我会列出核心的代码 但你也可以点击预览获取查看该源码的最终展示效果及下载该源码资源 图像网格动画 纯js和css实现的多方位多样式的图像网格动画 点击预览获取 核心代码 <div class…

基于JSP+Servlet+JavaBean+JDBC+DAO的Web架构设计图书管理系统

系统实现如下的基本管理功能: (1)用户分为两类:系统管理员&#xff0c;一般用户。 (2)提供用户注册和用户登录验证功能:其中一个登录用户的信息有:登录用户名&#xff0c;登录密码。 (3)管理员可以实现对注册用户的管理(删除)&#xff0c;并实现对图书的创建、查询、修改和删…

Java算法(十二):【数据结构与算法】 十大排序 之 二分查法 二分查法实现详细流程图分析 实现源码实例

二分查找 二分查找 二分查找就是返回有序序列中&#xff0c;需要查找的元素索引&#xff0c;无则-1。 需求&#xff1a;二分查找&#xff1a;手写实现数组元素的查找&#xff0c;存在返回索引&#xff0c;无则返回 -1&#xff1b;实现思路&#xff1a;&#xff08;前提是有序…

“源味河南县 牛羊天下鲜”河南县有机农畜产品发布会在博鳌召开

2023年12月17日&#xff0c;由中共青海省黄南州河南蒙古族自治县委员会、县人民政府主办的“源味河南县 牛羊天下鲜”绿色有机农畜产品发布会在海南博鳌举办。原农业部党组成员、中国农产品市场协会会长张玉香&#xff0c;原农业部党组成员、中国奶业协会战略发展委员会名誉副主…

VueStu03-插值表达式

插值表达式是 vue 的一种模板语法。 语法&#xff1a;{{ 表达式 }} 总结&#xff1a;

材料论文阅读/中文记录:Scaling deep learning for materials discovery

Merchant A, Batzner S, Schoenholz S S, et al. Scaling deep learning for materials discovery[J]. Nature, 2023: 1-6. 文章目录 摘要引言生成和过滤概述GNoME主动学习缩放法则和泛化 发现稳定晶体通过实验匹配和 r 2 S C A N r^2SCAN r2SCAN 进行验证有趣的组合家族 扩大…

WEB渗透—PHP反序列化(四)

Web渗透—PHP反序列化 课程学习分享&#xff08;课程非本人制作&#xff0c;仅提供学习分享&#xff09; 靶场下载地址&#xff1a;GitHub - mcc0624/php_ser_Class: php反序列化靶场课程&#xff0c;基于课程制作的靶场 课程地址&#xff1a;PHP反序列化漏洞学习_哔哩…

日本服务器:确保其稳定性的几个要点

​  在租用日本服务器时&#xff0c;用户们大多一定会关注它的稳定性&#xff0c;其实这些顾及都是正常的。毕竟&#xff0c;网站要想正常运行&#xff0c;保障服务器稳定是关键。本文将讨论有关如何保障日本服务器稳定性的一些有用技巧&#xff0c;希望对您有所帮助。 1.注重…

矩阵式键盘实现的电子密码锁

#include<reg51.h> //包含51单片机寄存器定义的头文件 sbit P14P1^4; //将P14位定义为P1.4引脚 sbit P15P1^5; //将P15位定义为P1.5引脚 sbit P16P1^6; //将P16位定义为P1.6引脚 sbit P17P1^7; //将P17位定义为P1.7引脚 sbit soundP3^7; //将so…

Spring Boot 3.2 + CRaC = 王炸!

前段时间发布了 Spring 6.1 和 SpringBoot 3.2&#xff0c;它们都完全支持 CRaC&#xff08;检查点协调恢复&#xff09;。 如果你想了解有关 CRaC 的更多信息&#xff0c;请随时阅读此处&#xff1a; https://docs.azul.com/core/crac/crac-introduction CRaC 是一个 OpenJDK…

MySQL锁机制

MySQL的锁机制用于管理事务对共享资源的并发访问&#xff0c;实现事务的隔离级别。 MySQL的锁比较多&#xff0c;下面我们按照四个维度来介绍相关的锁。 图 MySQL 锁的分类 1 加锁机制 悲观锁 操作数据时&#xff0c;认为其他线程也会对该数据进行更改。于是在获取数据时会先…

Gem5 checkkpoint使用: checkpoint恢复并运行parsec benchmark,运行和checkpoint时不同的新script

简介 Gem5 checkkpoint使用&#xff1a; 如何保存checkpoint&#xff0c;从checkpoint恢复&#xff0c;使用哪一层级的文件夹作为输入&#xff0c;-r 1制定检查点 顺序&#xff0c;并运行parsec benchmark。尤其提供运行和checkpoint时不同的新script的方法。不然每一次restor…

FinalShell的安装与使用

FinalShell是一款集成了SSH远程登录、SFTP文件传输、MySQL数据库管理、VPS服务器监控等多种功能的全能型服务器管理工具。 以下是在Linux上安装和使用FinalShell的基本步骤 1、下载FinalShell 访问FinalShell的官方网站下载对应的系统的版本。 FinalShell SSH工具,服务器管…

flask 接口处理带有图片和json数据的请求 发送图片到前端的实现

1.flask的request 从flask的源码可以看到flask的可用属性很多&#xff0c;包括data,form,files&#xff0c;header,host等&#xff0c;在我们接收文件传参时需要用到的属性就是form和files。不过具体的使用方式有两种&#xff0c;即&#xff1a;postman发送的和requests模拟发…

ES查询流程

在ES中查询分为两类&#xff1a;1.基于文档ID查询&#xff0c;2.按照非文档ID查询。 基于文档id查询 1.基于文档ID查询 当执行如下查询时&#xff1a; GET /megacorp/employee/1ES在执行上述查询的具体过程如下&#xff1a; 1、客户端向 Node 1 发送获取请求&#xff0c;此…

RocketMQ 顺序消息收发实践

目录 概述局部有序创建 Topic配置代码测试 结束 概述 顺序消息 全局有序&#xff1a;适用于性能不是特别高的场景&#xff0c;但是又要求消息又严格一致的概念。局部有序&#xff1a;适用于性能要求高的场景&#xff0c;想办法通过在设计层面处理有序的消息尽量发送至同一个 T…

Python自动化操作:简单、有趣、高效!解放你的工作流程!

今天跟大家分享一套自动化操作流程解决方案&#xff0c;基于Python语言&#xff0c;涉及pyautogui、pyperclip、pythoncom、win32com依赖包。安装命令为&#xff1a; pip install pyautoguipip install pyperclippip install pythoncompip install win32compyautogui 是一个自…

二级教师属于什么职称

教师的职称评定对于他们的职业发展具有重要意义。教育体系中&#xff0c;教师的职称分为多个等级&#xff0c;其中二级教师是其中的一个重要级别。那么&#xff0c;二级教师属于什么职称呢&#xff1f; 职称的定义。职称是指根据工作性质、职责、难度、能力等因素&#xff0c;对…

I Doc View 多个高危漏洞复现

I Doc View在线文档预览是一款在线文档预览系统。近期出现了多个高危漏洞&#xff0c;因此集中复现一下&#xff0c;有兴趣的童鞋可以收藏一下。#头条首发挑战赛# 1.Upload接口任意文件读取漏洞 1.1 漏洞级别 高危 1.2 漏洞描述 I Doc View存在代码执行漏洞&#xff0c;使…