深入理解Overlayfs(二):使用与原理分析_overlayfs failed to get index nlink

6、指定的upperdir和workdir所在的基础文件系统需要支持xattr扩展属性,否则在功能方面会受到限制,例如后面的opaque目录将无法生成,并且redirect dir特性和index特性也无法使用。

7、如果upperdir和各lowerdir是来自同一个基础文件系统,那在文件触发copyup前后,用户在merge层通过ls命令或stat命令看到的Device和inode值保持不变,否则会发生改变。

删除文件和目录

删除文件和目录,看似一个简单的动作,对于overlayfs实现却需要考虑很多的场景且分很多步骤来进行。下面来分以下几个场景开分别讨论:

(1)要删除的文件或目录来自upper层,且lower层中没有同名的文件或目录

这种场景比较简单,由于upper层的文件系统是可写的,所有在overlayfs中的操作都可以直接体现在upper层所对应的文件系统中,因此直接删除upper层中对应的文件或目录即可。

示例:

这里在upper目录下创建了文件file和目录dir,然后在挂载overlayfs后从merge目录下删除它们,可见在upper目录下也同时被直接删除。

(2)要删除的文件或目录来自lower层,upper层不存在覆盖文件

由于lower层中的内容对于overlayfs来说是只读的,所以并不能像之前那样直接删除lower层中的文件或目录,因此需要进行特殊的处理,让用户在删除之后即不能正真的执行删除动作又要让用户以为删除已经成功了。

Overlayfs针对这种场景设计了一套“障眼法”——Whiteout文件。Whiteout文件在用户删除文件时创建,用于屏蔽底层的同名文件,同时该文件在merge层是不可见的,所以用户就看不到被删除的文件或目录了。whiteout文件并非普通文件,而是主次设备号都为0的字符设备(可以通过"mknod c 0 0"命令手动创建),当用户在merge层通过ls命令(将通过readddir系统调用)检查父目录的目录项时,overlayfs会自动过过滤掉和whiteout文件自身以及和它同名的lower层文件和目录,达到了隐藏文件的目的,让用户以为文件已经被删除了。

示例:

这里再lower层中创建文件file和目录dir,然后在挂载文件系统之后从merge层删除它们,然后检查lower层中的文件依然存在并没有被删除,于此同时在upper层中创建了两个同名whiteout文件,它们的文件类型为c,即表示为字符设备,同时主次设备号为0,0。

(3)要删除的文件是upper层覆盖lower层的文件,要删除的目录是上下层合并的目录

该场景就理论上来讲其实是前两个场景的合并,overlayfs即需要删除upper层对应文件系统中的文件或目录,也需要在对应位置创建同名whiteout文件,让upper层的文件被删除后不至于lower层的文件被暴露出来。

示例:

这里在upper目录和lower目录中都创建了文件file和目录dir,在挂载overlayfs后从merge目录删除它们,然后检查lower层中的文件依然不变,同时upper层中的原有文件已经被替换成两个同名的whitout文件了。

创建文件和目录

创建文件和目录同删除类似,overlayfs也需要针对不同的场景进行不同的处理。下面分以下几个场景进行讨论:

(1)全新的创建一个文件或目录

这个场景最为简单,如果在lower层中和upper层中都不存在对应的文件或目录,那直接在upper层中对应的目录下新创建文件或目录即可。

示例:

这里在一个全为空的overlayfs中,挂载后通过merge目录中创建文件file和目录dir,它们直接被创建到了upper层对应的文件系统中,而lower层不受任何影响。

(2)创建一个在lower层已经存在且在upper层有whiteout文件的同名文件

该场景对应前文中的场景2或场景3,在lower层中之前已经存在同名的文件或目录了,同时upper层也有whiteout文件将其隐藏(显然是通过merge层删除它了),所以用户在merge层看不到它们,可以新建一个同名的文件。这种场景下,overlayfs需要删除upper层中的用新建的文件替换原有的whiteout文件,这样在merge层中看到的文件就是来自upper层的新文件了。

示例:

这里先在lower目录中创建文件file,然后在upper目录中创建同名的whiteout文件用于隐藏lower层中的文件(注意:此处仅是为了演示,正常使用中用户应避免自己创建whiteout文件),挂载文件系统后通过merge目录新建文件file。新建文件后,在lower目录中的原有文件不变,upper目录中的whiteout文件已经被替换成了新创建的文件file,用户在merge中看见的也即是这个新创建的文件。

(3)创建一个在lower层已经存在且在upper层有whiteout文件的同名目录

该场景和场景2的唯一不同是将文件转换成目录,即原lower层中存在一个目录,upper层中存在一个同名whiteout文件用于隐藏它(同样的,它是之前被用户通过merge层删除了的),然后用户在merge层中又重新创建一个同名目录。依照overlayfs同名目录上下层合并的理念,如果此处不做任何特殊的处理而仅仅是在upper层中新建一个目录,那原有lower层该目录中的内容会暴露给用户。因此,overlayfs针对这种情况引入了一种属性——Opaque属性,它是通过在upper层对应的目录上设置"trusted.overlay.opaque"扩展属性值为"y"来实现(所以这也就需要upper层所在的文件系统支持xattr扩展属性),overlayfs在读取上下层存在同名目录的目录项时,如果upper层的目录被设置了opaque属性,它将忽略这个目录下层的所有同名目录中的目录项,以保证新建的目录是一个空的目录。如下图所示:

实际示例演示:

这里首先在lower目录中创建一个目录dir,并在其中创建一个文件foo,然后在upper层创建whiteout文件dir用于隐藏lower目录中的目录dir,挂载文件系统后通过merge目录新建目录dir。观察该新建的目录为空,lower层中的foo文件并没有暴露出来,然后查看upper层中的原有whiteout文件已经被替换层新建目录dir,同时它被设置了overlayfs的opaque属性。

此时如果我们删除这个opaque属性(注意需要离线删除,不能在挂载时操作所有基础文件系统目录),底层目录dir中的foo文件就会暴露出来。

写时复制(copy-up)特性

用户在写文件时,如果文件来自upper层,那直接写入即可。但是如果文件来自lower层,由于lower层文件无法修改,因此需要先复制到upper层,然后再往其中写入内容,这就是overlayfs的写时复制(copy-up)特性。

示例:

这里首先在lower目录中新建文件file,并往其中写入内容,挂载文件系统后,通过merge目录写入新的内容,观察merge目录下foo文件的内容,包含来原有的和新写入的,同时观察upper目录中,也同样存在一个新的从lower目录复制上来的文件foo,内容同merge目录中看到的一致。

当然,overlayfs的copy-up特性并不仅仅在往一个来自lower层的文件写入新内容时触发,还有很多的场景会触发,简单总结如下:

1)用户以写方式打开来自lower层的文件时,对该文件执行copyup,即open()系统调用时带有O_WRITE或O_RDWR等标识;

2)修改来自lower层文件或目录属性或者扩展属性时,对该文件或目录触发copyup,例如chmod、chown或设置acl属性等;

3)rename来自lower层文件时,对该文件执行copyup;

4)对来自lower层的文件创建硬链接时,对链接原文件执行copyup;

5)在来自lower层的目录里创建文件、目录、链接等内容时,对其父目录执行copyup;

6)对来自lower层某个文件或目录进行删除、rename、或其它会触发copy-up的动作时,其对应的父目录会至下而上递归执行copy-up。

Rename文件和目录

用户在使用mv命令移动或rename文件时,mv工具首先会尝试调用rename系统调用直接由内核完成文件的renmae操作,但对于个别文件系统内核如果不支持rename系统调用,那由mv工具代劳,它会首先复制一个一模一样的文件到目标位置,然后删除原来的文件,从而模拟达到类似的效果,但是这有一个很大的缺点就是无法保证整个rename过程的原子性。

对于overlayfs来说,文件的rename系统调用是支持的,但是目录的rename系统调用支持需要分情况讨论。前文中看到在挂载文件系统时,内核提供了一个挂载选项"redirect_dir=on/off",默认的启用情况由内核的OVERLAY_FS_REDIRECT_DIR配置选项决定。在未启用情况下,针对单纯来自upper层的目录是支持rename系统调用的,而对于来自lower层的目录或是上下层合并的目录则不支持,rename系统调用会返回-EXDEV,由mv工具负责处理;在启用的情况下,无论目录来自那一层,是否合并都将支持rename系统调用,但是该特性非向前兼容,目前内核中默认是关闭的,用户可手动开启。下面针对目录的几种场景来分别进行演示和说明:

(1)关闭redirect dir特性

在关闭redirect dir特性的情况下分别对来自lower、upper和合并的目录进行reanme操作,查看overlayfs如何进行处理。

这里在upper目录下创建单纯来自upper层的目录up_src,在lower目录下创建单纯来自lower层的目录lo_src,在lower目录和upper目录目录下分别创建目录me_src表示上下层合并的目录,各个目录下都创建了子目录和文件dir(x)和file(x)。在挂载overlay文件系统后,在merge层通过mv命令rename各个目录,完成后观察lower和merge层中的内容不变但upper层中分别创建了两个whiteout文件lo_src和me_scr用于屏蔽lower目录中的目录,然后查看lo_dst和me_dst中的内容,来自lower目录下的子目录dir、dirb和文件file、fileb都被copyup了,通过strace跟踪其流程(省略了文件属性和其他保护性的操作):

rename("merge/lo_src", "merge/lo_dst")  = -1 EXDEV (Invalid cross-device link)
mkdir("merge/lo_dst", 0700)             = 0
rename("merge/lo_src/dir", "merge/lo_dst/dir") = -1 EXDEV (Invalid cross-device link)
mkdir("merge/lo_dst/dir", 0700)         = 0
rename("merge/lo_src/file", "merge/lo_dst/file") = 0
unlinkat(4, "dir", AT_REMOVEDIR)        = 0
unlinkat(AT_FDCWD, "merge/lo_src", AT_REMOVEDIR) = 0

可以看出,mv工具首先调用rename系统调用尝试对lo_src目录进行rename,但是失败并返回-EXDEV,于是它就创建了lo_dst目录然后依次对子目录dir和文件file进行处理,子目录dir的处理方式同lo_src类似,文件file可以直接rename成功(copyup),最后删除原始的目录dir和lo_src即可。这一系列的模拟动作后,在merge层最后呈现给用户的结果是预期的,但由于是多个系统调用下发,整个过程非原子,如果操作执行过程中发生了系统奔溃,那在系统恢复后,用户就可能会发现lo_src和lo_dst同时存在的情况(这类似与跨文件系统rename)。

(2)打开redirect dir特性

打开redirect dir之后,将支持单纯来自lower层和合并目录的rename系统调用。由于目录里可能会包含很多子目录或文件,overlayfs需要保证rename系统调用的原子性,因此它不能像mv命令那样将目录里的各个子目录和文件都挨个copyup到upper层中,所以overlayfs设计了一种redirect xattr扩展属性,其内容是lower层原始目录的相对路径(相对lower层挂载根目录或当前rename目录的父目录),设置在upper层中的目标目录上,并不会copyup原始目录中的子目录或文件。用户通过merge目录扫描目录项时,overlayfs在扫描upper层目录时会检查它的redirect xattr扩展属性并找到原始lower层目录,同时将原始目录下的目录项也返回给用户。如下图所示:

如图,用户在merge层执行”mv DirA DirX“和”mv DirB DirY“之后,原始lower层中的foo文件和bar1文件并不会copyup到目标upper层中的DirX和DirY中,取而代之的时在DirX和DirY中的redirect xattr扩展属性,用户在merge层看到的DirX目录和DirY目录来自与upper层,但是其中的内容却部分来自与lower层。其实就本质上来看,redirect dir其实只是一种特殊类型的merge dir,只不过所merge的lower层目录不在是同名目录而是从redirect xattr中保存的名字而已。

实际示例如下:

同前面一样,这里创建了lo_src和me_src及其子目录和文件,然后在启用redirect dir特性的merge目录下执行mv rename操作,完成后查看upper目录中的内容,可以看到用于屏蔽lower层原始目录的两个whiteout文件同样被创建,但是不同的是lo_dst目录中并没有copyup的file文件和dir目录,me_dst目录也同样没有copyup的dirb目录和filea文件,只有原来就存在的dira目录和filea文件。最后查看lo_dst目录的redirect扩展属性和me_dst目录的扩展属性分别指向了相对同级父目录的lo_src目录和me_src目录。

原子性保证(Workdir)

前文中介绍了文件目录的创建、删除和rename等操作以及写时复制特性,描述了overlayfs处理这些操作的细节,但是有一点还没有提到,那就是overlayfs是如何保证这些操作的原子性的。例如,当用户在删除上下层都存在的文件时,overlayfs需要删除upper层的文件然后创建whiteout文件来屏蔽lower层的文件,想要创建同名文件必然需要先删除原有的文件,这删除和创建分为两个步骤,如何做到原子性以保证文件系统的一致性?我们当然不希望见到文件删除了但是whiteout文件却没有创建的情况。又例如用户在触发copyup的时候,文件并不可能在一瞬间就完整的拷贝到upper层中,如果系统崩溃,那在恢复后用户看到的就是一个被损坏的文件,也同样需要保证原子性。

对于这个问题,我们来关注前面挂载文件系统指定的workdir目录,在挂载文件系统后该目录下会创建一个为空的work目录,这个目录就是原子性保证的关键所在,下面针对不同的场景来分析overlayfs是如何使用这个目录的。

(1)删除upper层文件/目录并创建whiteout的过程

如上图所示,以文件为例,若用户删除删除文件foo,overlayfs首先(1)在workdir目录下创建用于覆盖lower层中foo文件的whiteout文件foo,然后(2)将该文件与upper中的foo文件进行rename(对于目录则为exchange rename),这样两个文件就原子的被替换了(原子性由基础文件系统保证),即使此时系统崩溃或异常掉电,磁盘上的基础文件系统中也只会是在work目录中多出了一个未被及时删除的foo文件而已(实际命名并不是foo而是一个以#开始的带有序号的文件,此处依然称之为foo是为了为了便于说明),并不影响用户看到的目录,当再次挂载overlayfs时会在挂载阶段被清除,最后(3)将work目录中的foo文件删除,这样整个upper层中的foo文件就被“原子”的删除了。

(2)在whiteout上创建同名文件/目录的过程

该过程与删除类似,只是现在在upper层中的是whiteout文件,而在work目录中是新创建的文件,workdir的使用流程基本一致,不再赘述。

(3)删除上下层合并目录的过程

由于上下层合并的目录中可能存在whiteout文件,因此在删除之前需要保证要删除的upper层目录是空的,不能有whiteout文件。

如图所示,在用户删除“空”目录Dir时,其实在upper层中Dir目录下存在一个foo的whiteout文件,因此不能直接立即通过场景1的方式进行删除。首先(1)在work目录下创建一个opaque目录,然后(2)将该目录和upper层的同名目录进行exchange rename,这样upper层中的Dir目录就变成了一个opaque目录了,它将屏蔽底层的同名Dir目录。最后(3)将workdir下的Dir目录里的whiteout文件全部清空后再删除Dir目录本身。这样就确保了Dir目录中不存在whiteout文件了,随后的步骤就同场景一一样了。需要注意的是,这一些列的流程其实对于upper层来说,包含了(1)原始目录(2)opaque目录(3)whiteout文件的这3个状态,该过程并不是原子的,但在用户看来只有两种状态,一是删除成功,此时upper层已经变成状态3,还有一种是未删除,对应upper层是状态1或状态2,所以中间的opaque目录状态并不会影响文件系统对用户的输出,依然能够保证文件系统的一致性。

(4)文件/目录copyup的过程

在件的copyup过程中由于文件没有办法在一个原子操作中完成的拷贝到upper层中的对应目录下(不仅仅是数据拷贝耗时,还包含文件属性和扩展属性的拷贝动作),所以这里同样用到了work目录作为中转站。

这里以文件copyup为例,首先(1)根据基础文件系统时候支持tempfile功能(将使用concurrent copy up来提升并发copyup的效率),若支持则在work目录下创建一个临时tmpfile,否则则创建一个真实foo文件,然后从lower层中的foo文件中拷贝数据、属性和扩展属性到这个文件中,接下来(2)若支持tempfile则将该temp文件链接到upper目录下形成正真的foo文件,否则在upper目录下创建一个空的dentry并通过rename将work目录下的文件转移到upper目录下(原子性由基础层文件系统保证),最后(3)释放这个临时dentry。至此,由于非原子部分全部在work目录下完成,所以文件系统的一致性得到保证。另外,这里还需要说明的一点是,如果基础层的文件系统支持flink,则此处的步骤1中的数据拷贝将使用cloneup功能,不用再大量复制数据块,copyup的时间可以大幅缩短。

Origin扩展属性和Impure扩展属性

Overlayfs一共有5中扩展属性,前文中已经看到了opaque和redirect dir这两种扩展属性,这里介绍origin和impure扩展属性,这两种扩展属性最初是为了解决文件的st_dev和st_ino值在copyup前后发生变化问题而设计出来的。其中origin扩展属性全称为"trusted.overlay.origin",保存的值为lower层原文件经过内核封装后的数据结构进行二进制值转换为ASCII码而成,设置在upper层的copyup之后的文件上,现在只需要知道overlayfs可以通过它获取到该文件是从哪个lower层文件copyup上来的即可。另一个impure扩展属性的全程为"trusted.overlay.impure",它仅作用于目录,设置在upper层中的目录上,用于标记该目录中的文件曾经是从底层copyup上来的,而不是一个纯粹来自upper层的目录。

下面以一个简单的示例展示它们是如何保证st_ino的一致性的:

这里首先在lower目录下创建一个文件file,并在upper目录下创建目录dir,在挂载文件系统之后使用mv命令在merge目录中将文件file rename到目录dir下,这样就触发了文件file的copyup,此后用户看到的文件将来自upper/dir目录,但它的inode值在mv前后并没有发生任何变化。这就得归功于upper/dir/file上的origin属性和upper/dir目录上的impure扩展属性了,它为下图中的两个场景做了区分:

上图中左边的场景,upper目录下的Dir目录和File文件在挂载之前就已存在,它们没有origin和impure扩展属性,在用户查询目录项时,overlayfs将直接返回upper目录下file文件的st_ino值。而上图中右边的场景就是示例中构造的场景,upper/Dir/File文件从lower目录中copyup上来,此时为了保证st_ino的一致性,所以st_ino值还必须给用户显示lower目录中file文件的st_ino,因此File文件上的origin扩展属性使得overlayfs可以通过它找到lower/File并返回它的st_ino值,而DIr文件上的impure扩展属性也会使得即使目录并不是上下层合并的,也会强制其在扫描目录项时去获取可能存在的origin st_ino值。

最后总结一下哪些场景会设置和使用origin和impure扩展属性:

1)在触发文件或目录copyup时会设置origin属性,注意文件不能为多硬链接文件(启动index特性除外,下一节细述),因为这样会导致多个不同的upper层文件的origin属性指向同一个lower层原始inode,从而导致st_ino重复的问题。

2)在启动index属性之后,在挂载文件系统时会检查并设置upper层根目录的origin扩展属性指向顶层lower根目录,同时检查并设置index目录的origin扩展属性指向upper层根目录。

3)在overlayfs查找文件(ovl_lookup)时会获取origin扩展属性,找到lower层中的原始inode并和当前inode进行绑定,以便后续保证st_ino一致性时使用。

4)在upper层目录下有文件或子目录发生copyup、rename或链接一个origined的文件,将对该目录设置impure扩展属性。

5)在遍历目录项时,如果检测到目录带有impure扩展属性,在扫描其中每一个文件时,都需要检测origin扩展属性并尝试获取和更新lower层origin文件的st_ino值。

Index特性

前文中看到overlayfs还提供了一个挂载选项“index=on/off”,可以通过勾选内核选项OVERLAY_FS_INDEX默认开启,该选项和redirect dir选项一样也不是向前兼容的。该选项在Linux-4.13正式合入内核,目前该选项的功能还在不断开发中,目前用于解决lower层硬链接copyup后断链问题,后续还会用于支持overlayfs提供NFS export和snapshot的功能。我们首先来分析index属性是如何修复硬链接断链的问题,然后再看一下开启index属性之后overlayfs会哪些变化。

(1)Hard link break问题

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年嵌入式&物联网开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

[外链图片转存中…(img-nuoWHLMf-1715759304791)]

[外链图片转存中…(img-eWhWqyaN-1715759304791)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上嵌入式&物联网开发知识点,真正体系化!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新!!

  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值