首页 > 系统相关 >linux下文件与inode的关系

linux下文件与inode的关系

时间:2024-11-09 17:32:05浏览次数:3  
标签:文件 tar st linux txt inode FDCWD

最近忽然被问到一个问题:程序A打开了文件a.txt,程序B覆盖了a.txt,那这时候程序A读取到的内容是怎么样的?是读取到旧内容,还是新内容,或者是半新半旧?

为了解答这个问题,得先明白系统的文件管理机制。以Linux为例,文件属于一种资源,它是由系统内核统一管理的。操作文件也只能是通过内核的系统调用去间接操作文件。简单来说,硬盘上的每一个文件,系统都会给它分配一个inode。当一个程序去读写这个文件时,内核会把这个inode对应的内容加载到内存中,然后该程序通过系统调用去操作这块内存。

回到最开始的问题,当程序A打开文件a.txt时,a.txt对应的inode的内容将会被加载到内存中。程序B覆盖了a.txt时,程序A读取到的内容,就取决于这个inode对应的内容。那问题就转变为:程序B覆盖a.txt时,它会修改a.txt的inode对应的内容吗?

“覆盖”是一个比较模糊的概念,这里先把它明确为cpmvtar解压这三种常见的操作,现在使用strace来查看一下这几个指令到底对文件进行了什么操作。

strace cp a.txt b.txt

openat(AT_FDCWD, "b.txt", O_RDONLY|O_PATH|O_DIRECTORY) = -1 ENOTDIR (不是目录)
newfstatat(AT_FDCWD, "a.txt", {st_mode=S_IFREG|0644, st_size=5, ...}, 0) = 0
newfstatat(AT_FDCWD, "b.txt", {st_mode=S_IFREG|0644, st_size=5, ...}, 0) = 0
openat(AT_FDCWD, "a.txt", O_RDONLY)     = 3
newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=5, ...}, AT_EMPTY_PATH) = 0
openat(AT_FDCWD, "b.txt", O_WRONLY|O_TRUNC) = 4
ioctl(4, BTRFS_IOC_CLONE or FICLONE, 3) = -1 EOPNOTSUPP (不支持的操作)
newfstatat(4, "", {st_mode=S_IFREG|0644, st_size=0, ...}, AT_EMPTY_PATH) = 0
fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
copy_file_range(3, NULL, 4, NULL, 9223372035781033984, 0) = 5
copy_file_range(3, NULL, 4, NULL, 9223372035781033984, 0) = 0
close(4)                                = 0
close(3)                                = 0

从上面的日志可以看到cp a.txt b.txt是分别打开a.txtb.txt,然后用copy_file_rangea.txt的内容复制到b.txt,这个过程中,b.txt的inode将不会变化,但inode对应的内容将会被修改。

strace mv a.txt b.txt

ioctl(0, TCGETS, {c_iflag=ICRNL|IXON, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
renameat2(AT_FDCWD, "a.txt", AT_FDCWD, "b.txt", RENAME_NOREPLACE) = -1 EEXIST (文件已存在)
openat(AT_FDCWD, "b.txt", O_RDONLY|O_PATH|O_DIRECTORY) = -1 ENOTDIR (不是目录)
newfstatat(AT_FDCWD, "a.txt", {st_mode=S_IFREG|0644, st_size=5, ...}, AT_SYMLINK_NOFOLLOW) = 0
newfstatat(AT_FDCWD, "b.txt", {st_mode=S_IFREG|0644, st_size=5, ...}, AT_SYMLINK_NOFOLLOW) = 0
geteuid()                               = 1000
faccessat2(AT_FDCWD, "b.txt", W_OK, AT_EACCESS) = 0
renameat(AT_FDCWD, "a.txt", AT_FDCWD, "b.txt") = 0
lseek(0, 0, SEEK_CUR)                   = -1 ESPIPE (非法 seek 操作)

从日志可以看到,一开始mv a.txt b.txt尝试直接把a.txt重命名为b.txt,但发现b.txt已存在。接着判断b.txt是否为目录,发现不是目录,再检测是否对b.txt有操作权限,发现有权限。于是接下来直接强制把a.txt重命名为b.txt。在这种情况下,b.txt对应的inode会直接变为a.txt的inode(因为a.txt已经不存在了)。

接下来先把b.txt压缩tar -zcvf b.tar.gz b.txt,再解压strace tar -zxvf b.tar.gz,那b.txt将会被覆盖

newfstatat(1, "", {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0), ...}, AT_EMPTY_PATH) = 0
write(1, "b.txt\n", 6b.txt
)                  = 6
openat(AT_FDCWD, "b.txt", O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_NONBLOCK|O_CLOEXEC, 0644) = -1 EEXIST (文件已存在)
unlinkat(AT_FDCWD, "b.txt", 0)          = 0
openat(AT_FDCWD, "b.txt", O_WRONLY|O_CREAT|O_EXCL|O_NOCTTY|O_NONBLOCK|O_CLOEXEC, 0644) = 4
write(4, "\37\213\10\0\0\0\0\0\0\3\355\301\1\r\0\0\0\302\240\367Om\0167\240\0\0\0\0\0\0\0"..., 45) = 45
utimensat(4, NULL, [UTIME_OMIT, {tv_sec=1731033001, tv_nsec=0} /* 2024-11-08T10:30:01+0800 */], 0) = 0
close(4)                                = 0
close(3)

从日志可以看出,tar尝试直接创建b.txt,结果发现文件已存在,接着调用unlinkat先删除旧文件,再创建一个新的同名文件。在这种情况下,新文件肯定拥有新的inode,而旧b.txt对应的inode则看情况。Linux,当一个文件被打开时,在内核那边会有一个引用计数,此时如果该文件被删除,那文件将会从硬盘上消失,但它对应的inode还保留在内存中直到引用计数为0。这就是为什么在Linux下一个文件被打开后,还可以删掉文件并且不影响对已打开文件的读写的原因。

通过ls -i可以查看文件的inode

xzc@debian12:~/test$ ls -i
2098870 a.txt  2098858 b.tar.gz  2098857 b.txt  2097242 test  2098869 test.cpp

通过测试可以发现,cp是不会修改inode的,mv的话,b.txt的inode会变成a.txt的inode。而tar则是会更新b.txt的inode。意外的发现,tar覆盖b.txt时,新的inode居然是打包时b.txt的inode。需要写个程序测试一下inode的复用。如果旧的inode一直被占用,那b.txt的inode是全新的吗?

那么,怎么去验证这个问题呢?首先,有很多命令可以查看文件的inode,比如ls -i

$ ls -i
785817 a.out  785815 a.txt  785816 b.txt  785820 test.cpp

然后,再写一个小程序来辅助测试

#include <iostream>
#include <fstream>
#include <unistd.h>

int main() {
    // 打开文件
    std::ifstream file("b.txt");
    if (!file) return 1;
    
    std::cout << "sleep now" << std::endl;
    sleep(20);
    
    // 读取文件
    std::string line;
    while (std::getline(file, line)) {
        std::cout << line << std::endl;
    }
    
    return 0;
}

这个文件打开b.txt会,会sleep一段时间,在这段时间里,可以用cpmv等指令对b.txt进行操作,最后看程序读取到的是哪些内容,即可验证上面的说法。

最终的结论是:程序A打开了文件a.txt,程序B使用cp指令时进行操作,则会直接影响程序A读取的内容,使用mvtar等指令时,是不会影响程序A的。同样的,当代码里使用fopen之类的函数读写文件时,也是直接对inode的内容进行操作,所以多线程、多进程对同一个文件读写是不安全的。

另外,这里我们还可以看出,inode像文件描述符一样,只要没被占用,也是会复用。

$ ls -i
785817 a.out  785815 a.txt  785816 b.txt  785820 test.cpp
$ mv a.txt b.txt
$ ls -i
785817 a.out  785818 b.tar.gz  785815 b.txt  785820 test.cpp
$ echo 'a' > a.txt
$ tar -zxvf b.tar.gz
$ ls -i
785817 a.out  785815 a.txt  785818 b.tar.gz  785816 b.txt  785820 test.cpp

标签:文件,tar,st,linux,txt,inode,FDCWD
From: https://www.cnblogs.com/coding-my-life/p/18537002

相关文章

  • 词典编译配置文件概述
    概述《汉文博士》允许使用者自己编写词典文件。本文简要讲述了词典编译过程和相关配置文件的编写方法。读者需具备XML和正则表达式的基础知识。词典编译器《汉文博士》的词典编译器可在“文件”菜单中点击“词典编译器”调出。编译前,需点击“加载”按钮指定配置文件。选定配......
  • rocky linux 重启网卡命令
    通用的命令 ifdown ens33关闭网卡名叫ens33的网卡ifup ens33  开启网卡名叫ens33的网卡查看IP地址ip aCentos8和RockyLinux 管理网卡新命令 nmcli connection和c都可以 1、重载网卡,重启网卡之前一定要重新载入一下配置文件,不然不能......
  • 解压缩支持文件时出错:灾难性故障处理方式
    电脑系统WIN10,在反复安装卸载文件后,再安装软件时出现,解压缩支持文件时出错:灾难性故障  在解决之前,卸载软件出现报错提示。手动删除软件所在目录,打算重装,也是出现同样的错误提示。解决方法:在此电脑--C:\ProgramFiles(x86)\InstallShieldInstallationInformation路......
  • 如何在 Linux 中按名称终止进程?
    在Linux系统中,进程是指正在执行的程序或任务的实例。每个程序在运行时会创建一个或多个进程,并且这些进程在后台或前台执行。虽然大部分进程是正常运行的,但有时候系统中可能会出现一些故障进程,这些进程可能会导致系统资源浪费或系统变得缓慢。在这种情况下,终止这些不正常的......
  • 2024 年 10 个最佳 Linux 服务器发行版
    对于系统管理员和网络工程师来说,选择正确的Linux发行版尤为关键,因为它直接影响到服务器的性能、维护成本及长期使用的稳定性。虽然Linux系统有上百种发行版,但不同的发行版在功能、社区支持、企业级支持等方面有所不同。因此,了解并选择一个适合自己需求的Linux发行版显......
  • 将URDF模型文件导入Issac_Gym系列【1】
    1在solidworks中导出URDF文件1这里按照古月居老师的要求进行基本的配置https://www.bilibili.com/video/BV1Tx411o7rH/?vd_source=fcddcf87e97b17fd530dc88db643aab3关于catkin_ws这种ROS的工作环境的配置,具体可以参考我的这篇博客https://www.cnblogs.com/myleaf/p/1846629......
  • Python代码文件不只是“.py”
       今天同事给我扔了一个.pyd文件,说让我跑个数据。然后我就傻了。。不知道多少粉丝小伙伴会run.pyd代码文件?如果你也懵懵的,请继续往下读吧。。今天科普下各类Python代码文件的后缀,给各位Python开发“扫扫盲”。.py最常见的Python代码文件后缀名,官方称Python源代码文......
  • SpringBoot项目编译报错 类文件具有错误的版本 61.0, 应为 52.0
    springboot项目在编译时报错:/Users/Apple/Developer/art/caicai/cai-api/dubbo-samples/1-basic/dubbo-samples-spring-boot/dubbo-samples-spring-boot-provider/src/main/java/org/apache/dubbo/springboot/demo/provider/ProviderApplication.java:22:32java:无法访问......
  • linux搭建大数据环境
    前期准备工作友情提醒提前安装好vmware软件,准备好连接虚拟机的客户端一.基础环境1.配置ip地址修改ip配置文件[root@node1/]#vim/etc/sysconfig/network-scripts/ifcfg-ens33TYPE="Ethernet"PROXY_METHOD="none"BROWSER_ONLY="no"#1.把dhcp修改成staticBOOTP......
  • Qt 学习第 天:文件和事件
    一、创建widget对象(文件)二、设计ui界面放一个label标签上去,设置成box就可以显示边框了三、新建Mylabel类四、提升ui界面的label标签为Mylabel五、修改mylabel.h,mylabel.cpp#ifndefMYLABEL_H#defineMYLABEL_H#include<QLabel>classMylabel:publicQLabel{......