mv与rm命令

mv、rm命令究竟在linux系统底层完成了哪些工作?

 

预备知识概要

在linux 系统中,磁盘通常被格式化为 ext3 或 ext4 格式,这两种文件系统对文件的存储和访问是通过一种被称为 inode 即 i 节点的机制来实现的。

维基百科中对inode有如下解释:

每个文件都对应一个 i 节点,i 节点存储了除文件名和文件内容之外的所有信息。

这里做一个简单描述,详细 inode 内容见传送门。

        当我们读写文件时,通常是以流的形式,即认为文件的内容是连续的。

        但是在磁盘上,一个文件的内容通常是由多个固定大小的数据块即block构成的,并且这些数据块通常是不连续的。这时就需要一个额外的数据结构来保存各数据块的位置、数据块之间的顺序关系、文件大小、文件访问权限、文件的拥有者及修改时间等信息,即文件的元信息,而维护这些元信息的数据结构就被称为 i 结点。可以说,一个i 节点中包含了进程访问文件时所需要的所有信息。

        由于i节点存储了文件的元信息,因而 i节点本身也是要占用磁盘空间的。 i 节点的内容独立于文件内容,这里有必要区分“更改文件本身”的内容和更改“文件对应的i节点” 的内容。当对文件执行写入、编辑后保存等更改文件内容的操作时,我们更改的是文件内容本身。而更改i节点信息通常有如下情形:

        更改文件内容后,导致文件元信息发生了变化,例如文件的大小、文件的访问时间等,这时文件对应i 节点的信息也会发生变化。文件的拥有者、访问权限发生了变化,例如对文件执行了 chown、chgrp、chmod 等命令。因而,inode 内容发生了变化,对应的文件内容不一定发生了变化。在 linux 系统中用 i 节点号来标识一个i 节点。 i 节点号是一个整数,在单个磁盘分区中是唯一的。linux 中挂载多个磁盘分区时,不同的分区中可能有相同的 i 节点号,本文只考虑单一的磁盘分区,因而认为 i 节点号是唯一的。

 

硬链接

一般情况下一个文件名对应一个 i 节点,但是存在一种硬链接的情况,硬链接文件与源文件是同一个文件,只是有两个名字,类似于C++中的引用,当一个文件有多个链接时候,删除其中一个文件名,不会删除文件本身,只有当链接数为0时,文件内容才会真正被删除。

 

符号链接

除了硬链接,linux 系统还提供了一种符号链接。符号链接并不增加目标文件 i 节点的链接数。符号链接本身也是一个文件,其中存储了目标文件的完整路径,类似于windows系统中的快捷方式。符号链接与硬链接的另一个区别是符号链接可以对目录建立链接,而硬链接不能对目录建立链接。因为如果允许对目录建立硬链接,有可能形成链接环。

 

unlink命令

unlink 命令的 man page 中说明其调用了 unlink(file) 系统调用,查看 unlink 系统调用的man page,得到了以下信息:

 

unlink() deletes a name from the filesystem. If that name was the last link to a file and no processes have the file open the file is deleted and the space it was using is made available for reuse.
If the name was the last link to a file but any processes still have the file open the file will remain in existence until the last file descriptor referring to it is closed.
If the name referred to a symbolic link the link is removed.
If the name referred to a socket, fifo or device the name for it is removed but processes which have the object open may continue to use it.

unlink 用于删除文件名。删除文件名是指在原目录下不再含有此文件名。要注意的是,这里的表述是删除文件名,并不一定删除磁盘上文件的内容。只有在文件的链接数为1,即当前文件名是文件的最后一个链接并且没有进程打开此文件的时候,unlink() 才会真正删除文件内容。用 unlink真正的删除一个文件内容,必须同时满足以上两个条件。

 

注意:

1. 如果文件硬链接数为1,但是仍然有进程打开这一文件,那么 unlink 后,虽然在原目录中已经没有了被删除文件的名字,但是实际上系统还是保留了这一文件,直到打开这一文件的所有进程全部关闭此文件后,系统才会真正删除磁盘上的文件内容。由此可见,用unlink直接删除打开的文件是安全的。删除已经打开的文件,对使用此文件的进程,不会有任何影响,也不会导致进程崩溃(注意这里讨论的是删除已被打开的文件,通常是数据文件,并未讨论删除正在运行的可执行文件)。

2.对于符号链接,unlink 删除的是符号链接本身,而不是其指向的文件。

 

正文

rm

rm 命令作用也是删除文件,为了查看rm 与 unlink 的区别,用 strace 跟踪执行 rm 命令时使用的系统调用:

1
2
3
4
5
6
# strace rm data.txt 2>&1 | grep 'data.txt'
execve("/bin/rm", ["rm""data.txt"], [/* 13 vars */]) = 0
lstat("data.txt", {st_mode=S_IFREG|0644, st_size=10, ...}) = 0
stat("data.txt", {st_mode=S_IFREG|0644, st_size=10, ...}) = 0
access("data.txt", W_OK)                = 0
unlink("data.txt")                      = 0

 

跟踪 unlink 命令的结果:

1
2
3
# strace unlink data.txt 2>&1 | grep 'data.txt'
execve("/bin/unlink", ["unlink""data.txt"], [/* 13 vars */]) = 0
unlink("data.txt")

由上述追踪来看:

在linux 中,rm 命令比 unlink 命令多了一些权限的检查,之后也是调用了 unlink() 系统调用。

在文件允许删除的情况下,rm 命令和 unlink 命令其实是没有区别的。

 

mv

mv 命令通常用于重命名文件。当目标文件不存在时,跟踪其执行过程:

1
2
3
4
5
6
# strace mv data.txt  dest_file 2>&1 | egrep  'data.txt|dest_file'
execve("/bin/mv", ["mv""data.txt""dest_file"], [/* 13 vars */]) = 0
stat("dest_file", 0x7ffe1b4aab50)       = -1 ENOENT (No such file or directory)
lstat("data.txt", {st_mode=S_IFREG|0644, st_size=726, ...}) = 0
lstat("dest_file", 0x7ffe1b4aa900)      = -1 ENOENT (No such file or directory)
rename("data.txt""dest_file")         = 0

 

当目标文件存在时:

1
2
3
4
5
6
7
8
# strace mv src_data data.txt 2>&1 | egrep 'src_data|data.txt'
execve("/bin/mv", ["mv""src_data""data.txt"], [/* 13 vars */]) = 0
stat("data.txt", {st_mode=S_IFREG|0644, st_size=726, ...}) = 0
lstat("src_data", {st_mode=S_IFREG|0644, st_size=726, ...}) = 0
lstat("data.txt", {st_mode=S_IFREG|0644, st_size=726, ...}) = 0
stat("data.txt", {st_mode=S_IFREG|0644, st_size=726, ...}) = 0
access("data.txt", W_OK)                = 0
rename("src_data""data.txt")          = 0

从追踪结果看:

mv 的主要功能就是检查初始文件和目标文件是否存在及是否有访问权限,之后执行 rename 系统调用。

因而,当目标文件存在时,mv 的行为由 rename() 系统调用决定,即类似于删除文件后再重建一个同名文件。

posted @ 2020-04-07 16:39  lidowson  阅读(12)  评论(1)    收藏  举报