LabHub
学习 学习路径 课程

操作系统

删掉名字,文件还在

在 LabHub 中继续学习

目标

在阅读材料中,我们学到‘名称与实体彼此分离’。现在通过亲手操作,确认这种分离 实际上会产生怎样的行为。

完成八个步骤后,你将能回答这些问题:df 明明显示还有空间, 为什么写入会失败?日志已经删除,为什么空间没有释放?怎样替换配置文件, 才能让读取方不会看到只写了一半的文件?

工作目录

mkdir -p /root/fs && cd /root/fs

所有产物都在这里创建。

使用的工具

stat -c '%i %h %d %n' 파일   # 아이노드, 링크 수, 장치 번호, 이름
ls -i                        # 아이노드 번호를 함께 보기
ls -l /proc/<PID>/fd         # 그 프로세스가 열어 둔 파일들

stat%i 是实体的身份证。如果两个名称的 %i 相同,它们就是同一个文件。 这和大小、内容相同并不是一回事。

评分方式

其中三个步骤,评分器会直接运行你编写的脚本。

hold.py      채점기가 만든 파일을 넘겨 주고, 이름을 뗀 뒤에도
             읽어 내는지 봅니다
findleak.sh  채점기가 직접 유출을 만들어 놓고, 그것을 찾아내는지
             봅니다. 유출이 없어진 뒤에도 계속 보고하면 실패입니다
swap.sh      채점기가 대상 파일을 열어 둔 채로 여러분의 교체를
             시킵니다. 열어 둔 디스크립터로 새 내용이 보이면
             대상에 직접 쓴 것이므로 실패입니다

文字说明不计分,只检查实际是否可以正常运行。

如何判断是不是同一个文件

创建 orig.txt,再创建指向同一实体的第二个名称 hard.txt,以及仅内容相同的副本 copy.txt。查看三者的 inode 编号,并记录到 identity.txt 中。

硬链接使用 ln 원본 새이름,副本使用 cp 원본 새이름

使用 stat -c '%i %h %n' orig.txt hard.txt copy.txt 验证。

**内容相同与是同一个文件并不是一回事。**用 diff 查看时,三者看起来都一样。只有 inode 编号能说明实体究竟是一个还是多个。

链接数统计的是什么

创建指向同一实体的第三个名称 third.txt。把 orig.txt 的链接数如何变化,以及该值降到多少时数据块才会被回收,记录到 nlink.txt 中。

stat -c %h orig.txt 显示的就是链接数。有三个名称时,链接数就是 3。

**rm 并不是删除文件的命令,而是移除名称的命令。**系统调用之所以叫 unlink,原因就在这里。只有一个名称都不剩,并且也没有进程打开该文件时,数据块才会真正被回收。

读取已删除的文件

创建 hold.py。它要先打开通过参数传入的文件,然后移除文件名,再通过该描述符读取并输出内容。同时输出该描述符此时仍指向什么。

在 Python 中,移除名称使用 os.unlink(경로)。顺序就是一切——先打开,再移除,最后读取。

可以用 os.readlink('/proc/self/fd/%d' % f.fileno()) 查看描述符指向什么。如果文件名已经被移除,内核会在后面加上 (deleted)

**先把内容全部读入再删除并不是一回事。**那只是在复制;这里要观察的是,即使名称消失,实体仍然存在这一事实。

找出占着空间不放的元凶

创建 findleak.sh。找出当前系统中所有名称已消失但仍处于打开状态的文件,并输出占用这些文件的进程及路径。为脚本添加执行权限。

/proc/<PID>/fd 会用符号链接显示该进程打开的文件。名称已被移除的文件末尾会带有 (deleted)

ls -l /proc/[0-9]*/fd 2>/dev/null | grep deleted 开始。没有权限的进程无法读取,因此需要丢弃错误输出。

**不能把结果写死。**评分器会先制造泄漏再将其清除,并检查脚本在泄漏消失后是否仍然继续报告。

移动原文件后什么会失效

创建 base.txt,并为它创建硬链接 keep.txt 和符号链接 soft.txt。然后把 base.txt 移动moved.txt。在 links.txt 中写明哪一个链接失效了,以及原因。(保留第 1~2 步创建的文件。)

符号链接使用 ln -s,硬链接则使用不带 -sln

移动后分别尝试 cat soft.txtcat keep.txt。再用 ls -l soft.txt 查看它指向什么。

**硬链接保存的是 inode,符号链接保存的是路径字符串。**每次访问符号链接时,系统都会重新沿着该路径查找。

安全替换配置文件

创建 swap.sh <새내용파일> <대상파일>。替换目标文件时,要确保读取方绝不会看到只写了一半的文件。为脚本添加执行权限,并且不要留下临时文件。

不能直接写入目标文件。先把新内容写入同一目录中的临时文件,再用 mv 替换名称。

**在同一个文件系统内,mv 只修改一个目录项,因此是原子操作。**读取方看到的要么是旧实体,要么是新实体,不存在中间状态。

评分器会在保持目标文件打开的状态下运行你的脚本。如果确实替换了名称,那么通过原有描述符应该仍能看到旧内容。

名称替换的边界

查看工作目录与 /dev/shm 的设备编号。亲自尝试跨越这个边界替换名称,并把出现的错误以及此时 mv 实际会做什么记录到 exdev.txt 中。

设备编号使用 stat -c %d 경로 查看。值不同就表示位于不同的文件系统。

使用 python3 -c "import os; os.rename('a.txt', '/dev/shm/a.txt')" 亲自尝试。mv 遇到这个错误后会静默地复制文件并删除原文件,因此若要看到失败过程,必须直接调用 rename

**这就是安全替换必须在同一个文件系统内完成的原因。**复制需要时间,如果中途被打断,就会留下只写了一半的文件。

当 df 看起来像在说谎时

report.md 中撰写诊断报告。报告应包含:df 明明显示还有空间却无法写入的两种情况、分别用于确认问题的命令、回收空间的方法,以及这些事故通常发生在哪里。

前面的步骤已经介绍过这两种情况。一种是耗尽的并非数据块,而是其他资源(df -i);另一种就是第 4 步中亲自找出的对象。

**如果只找到原因就结束,事故还会继续发生。**请分别写明立即回收空间的方法,以及防止问题再次发生的方法。

目标是写出一份让下一位读者可以直接照着操作的文档。