当我在复制命令运行时cp
键入Ctrl+来终止该命令时,会对 ext4 文件系统产生什么后果?C
文件系统是否损坏?复制不完整的文件所占用的分区空间删除后还能用吗?
而且,最重要的是,终止cp
进程是否安全?
答案1
这样做是安全的,但自然你可能还没有完成副本。
运行该命令时cp
,它会进行系统调用,指示内核制作文件的副本。系统调用或系统调用是应用程序可用于向内核请求服务的函数,例如从磁盘读取数据或向磁盘写入数据。用户空间进程只是等待系统调用完成。如果您要跟踪来自 的调用cp ~/hello.txt /mnt
,它将如下所示:
open("/home/user/hello.txt", O_RDONLY) = 3
open("/mnt/hello.txt", O_CREAT|O_WRONLY, 0644) = 4
read(3, "Hello, world!\n", 131072) = 14
write(4, "Hello, world!\n", 14) = 14
close(3) = 0
close(4) = 0
对每个要复制的文件重复此操作。由于这些系统调用的工作方式,不会发生损坏。当输入这样的系统调用时,致命信号只有在系统调用完成后才会生效完成的,而不是在它运行时(事实上,信号仅在内核空间到用户空间上下文切换期间到达)。请注意,某些信号(例如read()
)可以提前终止。
因此,强制终止进程只会导致其在当前运行的系统调用返回后终止。这意味着文件系统驱动程序所在的内核可以自由地完成将文件系统置于正常状态所需的操作。任何此类 I/O 都不会在操作过程中终止,因此不存在文件系统损坏的风险。
答案2
由于cp
是用户空间命令,因此这不会影响文件系统的完整性。
当然,您需要做好准备,如果您终止正在运行的程序,则至少有一个文件不会被完全复制cp
。
答案3
森林的回答,尽管漂亮(并且在许多情况下是正确的)并不是您在现代系统上看到的⁰。他们是对的——在任何情况下这都不会损坏您的文件系统。但现在在任何实际情况下你都不会得到半本!
假设我这样做(只是为了生成一个大文件yesfile
,并将其复制到一个文件copy
(不必位于同一文件系统上),同时记录所有系统调用cp
):
cd /tmp
yes | head -n$((10**7)) > yesfile
strace -o strace.output cp yesfile copy
我得到了不同的图片:用户态进程cp
确实不是实际读取文件的内容,并不将其写入另一个文件;从性能角度来看,这会很糟糕:它需要至少两次上下文切换!用户层程序调用read
、切换、获取数据、调用write
、切换;如果文件大于单个读取缓冲区,则冲洗并重复。现在,这种精确的重复模型,仅读取有限大小的缓冲区,可能会导致中断时半复制文件。
相反,它使用copy_file_range
系统调用(请参见下面的跟踪1);man copy_file_range
告诉我们:
该
copy_file_range()
系统调用在两个文件描述符之间执行内核内复制,而无需将数据从内核传输到用户空间然后返回内核的额外成本。它将最多len
字节的数据从源文件描述符复制fd_in
到目标文件描述符fd_out
,覆盖目标文件请求范围内存在的任何数据。
所以,有一个原子副本此文件系统调用,即通常使用时,中断cp
不能中断复制。
如果您的源文件系统和目标文件系统相同,并且 Btrfs、CIFS、NFS 4.2、OCFS2、overlayfs 或 XFS,事情会变得更好来源(在撰写本文时,仅适用于这些 Linux 具有 reflink 功能):
ioctl(4, BTRFS_IOC_CLONE or FICLONE, 3)
成功了,系统没有需要完全复制文件内容 - 相反,仅将属于源文件的块列表复制到目标文件;每个块都有一个增加的引用计数器,因此当任何进程写入这些文件中的任何一个时,文件系统都会透明地对其进行写时复制。所以,这些东西就更加原子化了!
⁰ 至少,如果我的 GNU coreutils 8.32 与 fedora 34 向后移植copy_file_range
补丁/Linux 5.13.5 被认为是现代的。
相关 strace 输出
156 │ newfstatat(AT_FDCWD, "yesfile", {st_mode=S_IFREG|0644, st_size=20000000, ...}, 0) = 0
157 │ newfstatat(AT_FDCWD, "copy", 0x7fff982d5e70, 0) = -1 ENOENT (No such file or directory)
158 │ openat(AT_FDCWD, "yesfile", O_RDONLY) = 3
159 │ newfstatat(3, "", {st_mode=S_IFREG|0644, st_size=20000000, ...}, AT_EMPTY_PATH) = 0
160 │ openat(AT_FDCWD, "copy", O_WRONLY|O_CREAT|O_EXCL, 0644) = 4
161 │ newfstatat(4, "", {st_mode=S_IFREG|0644, st_size=0, ...}, AT_EMPTY_PATH) = 0
162 │ ioctl(4, BTRFS_IOC_CLONE or FICLONE, 3) = -1 EOPNOTSUPP (Operation not supported)
163 │ fadvise64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
164 │ mmap(NULL, 139264, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0be58ca000
165 │ uname({sysname="Linux", nodename="workhorse", ...}) = 0
166 │ copy_file_range(3, NULL, 4, NULL, 9223372035781033984, 0) = 20000000
167 │ copy_file_range(3, NULL, 4, NULL, 9223372035781033984, 0) = 0
168 │ close(4) = 0
169 │ close(3) = 0
170 │ munmap(0x7f0be58ca000, 139264) = 0