0

后端面试之Linux-cp和mv命令的区别

 1 year ago
source link: https://www.leftpocket.cn/post/linux/cp/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

原文地址:码农在新加坡的个人博客

如果你突然被面试官问:cp和mv这两个linux的命令有什么区别?
你会不会一脸懵逼,cp不就是复制,mv不就是移动吗,还能有什么区别?
如果你也是这么想,那么这篇文章适合你。

inode

了解文件操作命令例如cpmvrm的底层原理时,需要先了解 linux 中文件系统的基本原理。

在linux系统中,文件系统对文件的存储和访问是通过一种被称为inode即i节点的机制来实现的。

为什么需要inode呢?
文件数据存储在硬盘上,硬盘的最小存储单位叫做"扇区"(512Bytes)。OS读取硬盘的时候,为了提高效率会一次性读取一个"块"(8*扇区=4K)。
所以一个大文件的数据内容在磁盘上可能不是连续空间的,就需要inode来把各个Block串联起来。

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

inode(index node)表中包含文件系统所有文件列表,一个节点 (索引节点)是在一个表项,包含有关文件的信息( 元数据 ),包括:文件类型,权限,UID,GID、链接数(指向这个文件名路径名称个数)、该文件的大小和不同的时间戳、指向磁盘上文件的数据块指针、有关文件的其他数据。

inode

了解inode的基本信息之后,我们再看看cp, mv有什么区别。

目标文件不存在时

dest.txt不存在时,执行cp src.txt dest.txt
可以发现dest.txtsrc.txt的inode不一样,也就是用open()新建一个文件dest.txt,然后读取src.txt的数据再写入dest.txt

cp前:

  • src.txt: Inode: 34643179

cp后:

  • dest.txt: Inode: 34257722
[root@instance-1 blog]# strace cp source.txt destination.txt 2>&1 | egrep 'source.txt|destination.txt'
execve("/bin/cp", ["cp", "source.txt", "destination.txt"], 0x7ffd8f1f3ea0 /* 23 vars */) = 0
stat("destination.txt", 0x7fff021b0040) = -1 ENOENT (No such file or directory)
stat("source.txt", {st_mode=S_IFREG|0644, st_size=13, ...}) = 0
stat("destination.txt", 0x7fff021afda0) = -1 ENOENT (No such file or directory)
open("source.txt", O_RDONLY)            = 3
open("destination.txt", O_WRONLY|O_CREAT|O_EXCL, 0644) = 4

目标文件存在时

此时dest.txt已经存在,再次执行cp src.txt dest.txt
可以发现dest.txt跟上次执行的dest.txt的inode没有变化,同时看open()的参数也可以看出:先清空了dest.txt的内容,再把新的内容写入目标文件。没有文件的删除和创建,所以inode没有变化。

cp前:

  • src.txt: Inode: 34643179
  • dest.txt: Inode: 34257722

cp后:

  • dest.txt: Inode: 34257722
[root@instance-1 blog]# strace cp source.txt destination.txt 2>&1 | egrep 'source.txt|destination.txt'
execve("/bin/cp", ["cp", "source.txt", "destination.txt"], 0x7ffd4bfd93e0 /* 23 vars */) = 0
stat("destination.txt", {st_mode=S_IFREG|0644, st_size=13, ...}) = 0
stat("source.txt", {st_mode=S_IFREG|0644, st_size=13, ...}) = 0
stat("destination.txt", {st_mode=S_IFREG|0644, st_size=13, ...}) = 0
open("source.txt", O_RDONLY)            = 3
open("destination.txt", O_WRONLY|O_TRUNC) = 4
  • cp调用open系统函数,只会复制文件数据,不会复制inode索引节点的元数据。

目标文件不存在时

当目标文件dest.txt不存在时,执行mv src.txt dest.txt
可以发现dest.txtsrc.txt的inode一样,底层调用了rename(),inode信息与src.txt的索引节点保持一致。
注:centos 7.5以上的版本,调用renameat2函数,7.5及以下的函数,依旧调用rename函数,没有本质的区别。

mv前:

  • src.txt: Inode: 34643179

mv后:

  • dest.txt: Inode: 34643179
[root@instance-1 blog]# strace mv src.txt dest.txt 2>&1 | egrep 'src.txt|dest.txt'
execve("/bin/mv", ["mv", "src.txt", "dest.txt"], 0x7ffd71529810 /* 23 vars */) = 0
stat("dest.txt", 0x7ffdb24b0fe0)        = -1 ENOENT (No such file or directory)
lstat("src.txt", {st_mode=S_IFREG|0644, st_size=13, ...}) = 0
lstat("dest.txt", 0x7ffdb24b0c90)       = -1 ENOENT (No such file or directory)
renameat2(AT_FDCWD, "src.txt", AT_FDCWD, "dest.txt", 0) = 0

目标文件存在时

当目标文件dest.txt存在时,执行mv src.txt dest.txt
可以发现dest.txtsrc.txt的inode一样(之前的dest.txt的inode不见了),底层调用了rename(),inode变为src.txt的索引节点。

mv前:

  • src.txt: Inode: 34643179
  • dest.txt: Inode: 34257722

mv后:

  • dest.txt: Inode: 34643179
[root@instance-1 blog]# strace mv src.txt dest.txt 2>&1 | egrep 'src.txt|dest.txt'
execve("/bin/mv", ["mv", "src.txt", "dest.txt"], 0x7ffeef1ed240 /* 23 vars */) = 0
stat("dest.txt", {st_mode=S_IFREG|0644, st_size=13, ...}) = 0
lstat("src.txt", {st_mode=S_IFREG|0644, st_size=13, ...}) = 0
lstat("dest.txt", {st_mode=S_IFREG|0644, st_size=13, ...}) = 0
renameat2(AT_FDCWD, "src.txt", AT_FDCWD, "dest.txt", 0) = 0
  • mv调用rename系统调用,把src.txt重命名为目标文件,会将存储于inode索引节点上的文件元信息也移动到新文件中。

在Linux中,要真正删除一个文件,需要满足两个条件:

  • 链接数为0
  • 没有进程打开该文件

系统调用unlink()是移除目标文件的一个链接。可以发现rm底层调用的其实就是unlink()

[root@instance-1 blog]# strace rm src.txt 2>&1 | egrep 'src.txt'
execve("/bin/rm", ["rm", "src.txt"], 0x7fffbf900e58 /* 23 vars */) = 0
newfstatat(AT_FDCWD, "src.txt", {st_mode=S_IFREG|0644, st_size=13, ...}, AT_SYMLINK_NOFOLLOW) = 0
unlinkat(AT_FDCWD, "src.txt", 0)        = 0

unlink系统调用

从文件系统中删除一个名称。如果名称是文件的最后一个连接,并且没有其它进程将文件打开,名称对应的文件会实际被删除。
如果文件仍旧是打开的,或者是被进程占用,其内容不会被删除。只有当进程关闭该文件或终止时(这种情况下,内核关闭该进程所打开的全部文件),该文件的内容才会被删除。

所以你可能会遇到,一个进程在读写文件时,你发现磁盘空间不足,使用rm删除文件,却发现磁盘空间却没有释放的情况。 使用lsof | grep deleted可以查看占用的进程。kill进程之后,文件才能真正的被删除。

  • rm调用unlink系统调用,只有当所有的进程都不占用此文件的时候,才会真正的从磁盘删除。

替换可执行程序

不知道你还记不记得你是怎么替换可执行文件的,一般来说:

可当你使用

cp new_backend_server backend_server

的时候,提示Text file busy。 为什么呢,因为你这个文件正在被使用,当你清空并写入的时候,它能感知到修改,修改文件内容很可能导致程序逻辑错误甚至崩溃。所以禁止你对正在使用的文件执行cp替换。

当你执行:

rm backend_server
mv new_backend_server backend_server
service restart backend_server

就可以成功替换新的文件。
这又是为什么呢,因为当你使用rm&mv的时候是直接unlink旧的文件,由于文件被进程占用,实际上并没有删除,当你把新的文件mv到当前文件的时候,直接进行rename。并不会影响当前被进程占用的那个文件(新旧的inode不同,只是名字一样)。当你重启的时候,才会释放旧的文件,使用新的文件。

最后总结一下:

  • cp调用open系统函数,只会复制文件数据,不会复制inode索引节点的元数据。(不改变inode)
  • mv调用rename系统调用,把src.txt重命名为目标文件,会将存储于inode索引节点上的文件元信息也移动到新文件中。(改变inode)
  • rm调用unlink系统调用,只有当所有的进程都不占用此文件的时候,才会真正的从磁盘删除。

<全文完>

欢迎关注我的微信公众号:码农在新加坡,有更多好的技术分享。

pic

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK