Table of Contents

  1. 背景
  2. poc
    1. 删除前
    2. 删除后
    3. 恢复删除的文件
  3. POC结论
  4. 文件系统
    1. 创建文件&目录
      1. 文件
      2. 目录
  5. Reference:

背景

项目中用k8s的livesness check 功能做容器的健康探测,主要分3中方式:1.http request 探测2.exec command 3.tcp 探测。我负责的模块不是一个sevice,所以没有选择1,感觉2好像更符合我的应用场景。简单来说就是k8s可以定期执行一个命令或者一个shell文件,功能可能就是查看一个文件是否存在来测试当前的容器是否健康。而我的实现就是查看文件是否存在,若存在表明健康并删除,模块中有个定时器会定时再创建这个文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env bash
#check health file is exist and delete it
HEALTHYFILE = "/tmp/healthy"
if [ ! -f "${HEALTHYFILE}" ];then
exit 1
else
#delete healthy file
rm "${HEALTHYFILE}"
exit 0
fi
livenessProbe:
exec:
command:
- /bin/sh
- /usr/local/src/sandboxapi/src/healthCheck.sh
initialDelaySeconds: 60
periodSeconds: 120
setInterval(() => {
if(msgProcessCount !== 0 && !fs.existsSync(HEALTHYFile)){
fs.writeFileSync(HEALTHYFile, '');//mode Default: 0o666,flag Default:'w'
}
msgProcessCount=0;
}, HEALTHYTIME*1000);

command shell里面如果文件占用会删除失败吗?遂研究了一下,在linux环境下,如果文件正在被占用(读写),删除操作会发生什么?

poc

死循环读data文件中的内容,并打印出来pid,fd和文件内容(helloWorld)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#define BUFFER_SIZE 30
int main(void){
int fd;
int i=0;
char buffer[BUFFER_SIZE];
if((fd=open("data",O_RDONLY))==-1){
printf("Open file Error\n");
exit(1);
}
int pid=getpid();
int n=0;
while(1){
++i;
n=read(fd,buffer,BUFFER_SIZE-1);
if(n==-1){
printf("read Error\n");
exit(1);
}
buffer[n]='\0';
printf(" %d pid:%d, fd:%d, content:%s\n",i,pid,fd,buffer);
sleep(10);
lseek(fd,0L,SEEK_SET);
}
close(fd);
exit(0);
}

删除前

1
2
3
4
5
6
7
winter@ubuntu in ~/Dev/test_delete_file ❯ ./delete_file
1 pid:16395, fd:3, content:helloWorld
2 pid:16395, fd:3, content:helloWorld
3 pid:16395, fd:3, content:helloWorld
4 pid:16395, fd:3, content:helloWorld
5 pid:16395, fd:3, content:helloWorld
6 pid:16395, fd:3, content:helloWorld

我们查看delete_file进程打开的文件句柄的情况,使用ls -ilL:
-i, –inode 印出每个文件的 inode 号
-L, –dereference 当显示符号链接的文件信息时,显示符号链接所指示的对象而并非符号链接本身的信息。

1
2
3
4
5
6
7
8
9
winter@ubuntu in ~/Dev/test_delete_file ❯ ll /proc/21594/fd/
total 0
dr-x------ 2 winter winter 0 Jul 25 00:11 .
dr-xr-xr-x 9 winter winter 0 Jul 25 00:11 ..
lrwx------ 1 winter winter 64 Jul 25 00:11 0 -> /dev/pts/0
lrwx------ 1 winter winter 64 Jul 25 00:11 1 -> /dev/pts/0
lrwx------ 1 winter winter 64 Jul 25 00:11 2 -> /dev/pts/0
lr-x------ 1 winter winter 64 Jul 25 00:11 3 -> /home/winter/Dev/test_delete_file/data

发现fd下面是符号链接文件3是指向我们的data文件,所以查看一下转向对象的信息。

1
2
3
4
winter@ubuntu in ~/Dev/test_delete_file ❯ ls -ilL /proc/21594/fd/3
1054025 -rw-rw-r-- 1 winter winter 12 Jul 25 00:09 /proc/21594/fd/3
winter@ubuntu in ~/Dev/test_delete_file ❯ ls -ilL data
1054025 -rw-rw-r-- 1 winter winter 12 Jul 25 00:09 data

意料之中data文件和fd/3的node号是一样的。因为L链接文件指向的是指向对象的信息而非本身。

删除后

查看原进程的输出:

1
2
3
4
5
6
7
8
9
10
7 pid:21594, fd:3, content:helloWorld
8 pid:21594, fd:3, content:helloWorld
9 pid:21594, fd:3, content:helloWorld
10 pid:21594, fd:3, content:helloWorld
11 pid:21594, fd:3, content:helloWorld
12 pid:21594, fd:3, content:helloWorld
13 pid:21594, fd:3, content:helloWorld
14 pid:21594, fd:3, content:helloWorld
15 pid:21594, fd:3, content:helloWorld
16 pid:21594, fd:3, content:helloWorld

我们发现原来进程的输出内容没有变化,但使用ls已经查看不到该文件了。再次查看进程下的fd状况:

1
2
3
4
5
6
winter@ubuntu in ~/Dev/test_delete_file ❯ ls -il /proc/21594/fd/
total 0
198671 lrwx------ 1 winter winter 64 Jul 25 00:11 0 -> /dev/pts/0
198672 lrwx------ 1 winter winter 64 Jul 25 00:11 1 -> /dev/pts/0
198673 lrwx------ 1 winter winter 64 Jul 25 00:11 2 -> /dev/pts/0
198674 lr-x------ 1 winter winter 64 Jul 25 00:11 3 -> '/home/winter/Dev/test_delete_file/data (deleted)'

可以发现删除后,fd3指向了一个已经删除的文件。我们使用-L参数查看一下它指向的内容。

1
2
winter@ubuntu in ~/Dev/test_delete_file ❯ ls -ilL /proc/21594/fd/3
1054025 -rw-rw-r-- 0 winter winter 12 Jul 25 00:09 /proc/21594/fd/3

我们再次发现,链接指向的已删除文件的i节点号与已被删除的data文件的节点号一致。这说明虽然 data.txt 在相应目录中已经被删除,但是由于有其他进程打开这一文件后还未关闭,操作系统其实还为这些进程保留了磁盘上被打开的文件内容。
重新创建DATA文件,并改变内容:
重新创建data文件后,进程输出的内容与以前并无不同。查看下进程fd:

1
2
3
4
5
6
winter@ubuntu in ~/Dev/test_delete_file ❯ ls -il /proc/21594/fd/3
198674 lr-x------ 1 winter winter 64 Jul 25 00:11 /proc/21594/fd/3 -> '/home/winter/Dev/test_delete_file/data (deleted)'
winter@ubuntu in ~/Dev/test_delete_file ❯ ls -ilL data
1054026 -rw-rw-r-- 1 winter winter 13 Jul 25 21:48 data
winter@ubuntu in ~/Dev/test_delete_file ❯ ls -ilL /proc/21594/fd/3
1054025 -rw-rw-r-- 0 winter winter 12 Jul 25 00:09 /proc/21594/fd/3

由此看出,即使重新创建了同名文件,进程fd3依旧指向原来被删除的文件。因而可以认为,新建的同名文件与原文件及原进程无任何关系,新建的同名文件未被原进程使用,并且新建的文件使用了新的 i 节点,与原文件即使同名也没有产生任何联系。

恢复删除的文件

当用 cp 命令不加任何选项复制一个符号链接时,复制的是符号链接指向的目标文件。文件描述符 3 指向的内容就是被删除文件的内容,这一内容是可以通过 cp 命令复原的内容的(data 文件)这进一步说明了原 data.txt 的内容没有被删除。

1
2
3
4
5
winter@ubuntu in ~/Dev/test_delete_file ❯ cp /proc/21594/fd/3 recovery_data
winter@ubuntu in ~/Dev/test_delete_file ❯ ll -ilL recovery_data
1054022 -rw-rw-r-- 1 winter winter 12 Jul 25 22:00 recovery_data
winter@ubuntu in ~/Dev/test_delete_file ❯ ls -ilL /proc/21594/fd/3
1054025 -rw-rw-r-- 0 winter winter 12 Jul 25 00:09 /proc/21594/fd/3

复原只是内容的重新dump,i节点号是新的。

POC结论

由此可知,当进程打开一个文件后,如果我们在磁盘上删除这个文件,虽然表面上看在目录中已经成功删除了这个文件名,但是实际上系统依然保留了文件内容,直至所有进程都关闭了这一文件。这种行为与 unlink() 的 man page 描述一致,linux 系统的健壮性可见一斑。所以rm只是删除了文件名,不会因为文件正在被占用而导致rm失败。

文件系统

unix文件系统基本结构:

我们可以把一个磁盘分成一个或多个分区。每个分区包含一个文件系统。

  • 超级块:存放文件系统本省的结构信息。比如,每个区域的大小,未使用的磁盘块的信息。总之,它是是一个文件系统的概括。

  • i节点表:每个文件都有一些属性,如文件的大小、文件所有者、和创建时间等,这些性质被记录在一个称为i-节点的结构中。所有i-节点都有相同的大小,并且i-节点表是这些结构的一个列表,文件系统中每个文件在该表中都有一个i-节点。

  • 数据块:文件的内容保存在这个区域。磁盘上所有块的大小都一样。如果文件包含了超过一个块的内容,则文件内容会存放在多个块中。一个较大的文件很容易分布上千个独立的块中.
    接下来,我们详细看下i节点表和数据块。

  • 目录块:是一系列目录项的列表。每个目录项,由两部分构成:文件名以及对应的i节点号。

  • i节点:包含了文件有关的所有信息,文件类型、文件访问权限、长度和指向文件数据块的指针等。通过stat查看到的文件信息大部分取自i节点。

  • 数据块:系统通常一次会读取一个数据块的大小(4k,4096bytes)。

  1. stat命令输出的Blocks单位通常是512bytes,也就是一个扇区。
  2. 一个文件假设只有几个字节,其实也会占用一个文件块(Block size)大小,通常是4096bytes。
  3. 系统通常一次会读取一个Block size大小,而不是一个扇区大小。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    winter@ubuntu in ~/Dev/test_delete_file ❯ stat data 
    File: data
    Size: 13 Blocks: 8 IO Block: 4096 regular file
    Device: 801h/2049d Inode: 1054026 Links: 1
    Access: (0664/-rw-rw-r--) Uid: ( 1000/ winter) Gid: ( 1000/ winter)
    Access: 2020-07-25 21:48:55.057772045 -0700
    Modify: 2020-07-25 21:48:55.057772045 -0700
    Change: 2020-07-25 21:48:55.057772045 -0700
    Birth: -

    创建文件&目录

    文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    winter@ubuntu in ~/Dev ❯ touch mk_file.txt
    winter@ubuntu in ~/Dev ❯ stat mk_file.txt
    File: mk_file.txt
    Size: 0 Blocks: 0 IO Block: 4096 regular empty file
    Device: 801h/2049d Inode: 1054023 Links: 1
    Access: (0664/-rw-rw-r--) Uid: ( 1000/ winter) Gid: ( 1000/ winter)
    Access: 2020-07-26 01:43:25.589866702 -0700
    Modify: 2020-07-26 01:43:25.589866702 -0700
    Change: 2020-07-26 01:43:25.589866702 -0700
    Birth: -

Links为硬链接。硬链接直接指向文件i节点。link为1来自该文件的目录项。

目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
winter@ubuntu in ~/Dev ❯ mkdir mk_dir
winter@ubuntu in ~/Dev ❯ stat mk_dir
File: mk_dir
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: 801h/2049d Inode: 1090530 Links: 2
Access: (0775/drwxrwxr-x) Uid: ( 1000/ winter) Gid: ( 1000/ winter)
Access: 2020-07-26 01:49:17.297681146 -0700
Modify: 2020-07-26 01:49:17.297681146 -0700
Change: 2020-07-26 01:49:17.297681146 -0700
Birth: -
###################################
winter@ubuntu in ~/Dev/mk_dir ❯ mkdir mk_dir2
winter@ubuntu in ~/Dev/mk_dir ❯ stat mk_dir2
File: mk_dir2
Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: 801h/2049d Inode: 1090531 Links: 2
Access: (0775/drwxrwxr-x) Uid: ( 1000/ winter) Gid: ( 1000/ winter)
Access: 2020-07-26 02:04:19.016754520 -0700
Modify: 2020-07-26 02:04:19.016754520 -0700
Change: 2020-07-26 02:04:19.016754520 -0700
Birth: -

编号为1090531 ,其类型字段表示它是个目录,链接数为2,任何一个页目录链接数#总是2,数值2来自命名该目录的目录项(1090530)和在该目录项中的‘.’。1090530,链接数为3:一个是命名它的目录项,第二个是该目录项中的‘.’第三个是子目录中的‘..’。

Reference:

https://zhuanlan.zhihu.com/p/25650525
《UNIX环境高级编程》(第三版)4.14文件系统