为什么会这样:“cp image.bin /dev/mapper/loop0p1”?

为什么会这样:“cp image.bin /dev/mapper/loop0p1”?

为什么这有效?

cp image.bin /dev/mapper/loop0p1

image.bin是分区映像。

我已经尝试过并且有效,但是为什么呢?不应该dd用a吗?

答案1

GNU coreutilscp之所以有效,是因为它就是这样编写的。写入块设备并不复杂,本质上与写入常规文件的操作相同。

但是,您不应该使用cp这种方式。如果您确定 GNU coreutils 可以工作,您就可以这样做。但还有其他风格cp,例如busybox cp根本不支持写入设备;它取消链接(删除)设备节点并在其位置创建一个新文件。

dd在这方面更安全。它是为写入设备而设计的,并提供了处理设备时经常需要的工具(bs、seek、skip、count...)。它应该在所有风格中完成预期的事情dd

答案2

您的期望与程序/系统设计不同。对此我们该怎么说呢? :-)

“一切都是文件”...

您可以运行 cp 和/或 ddstrace -e trace=open并会看到常规文件和块设备的系统调用是相同的。如果系统调用不能区分它们,那么 cp 为什么要关心呢?

答案3

据我所知,所做cp的就是

  • 以写入模式打开目标文件
  • 将数据从源文件写入目标文件(虽然不确定块大小,但这只是细节)

对于普通文件,这可能会导致

  • 创建一个新文件,该文件将随着每次写入调用而增长
  • 覆盖将被覆盖的现有文件:
    第一次写入调用将擦除文件的内容并将新数据放入其中。从那时起,文件就会像新文件一样增长。

现在 /dev/mapper/* 是块设备(或者具体来说,是块设备的符号链接)。这些确实有静态文件大小。因此,如果您打开这些文件,每次write()调用都会简单地覆盖您发送给它的目标文件的 n 个字节(假设任何地方都没有fseek()调用)。

那么,我们来写一下我们自己的穷人cp:

#include <stdlib.h>
#include <stdio.h>

void usage() {fprintf(stderr, "Usage: cp <srcFile> <tgtFile>\n"); exit(1);}
void error(const char *msg) {fprintf(stderr, "Error: %s\n", msg); exit(2);}

void printPosAndSize(FILE *f) {
    off_t curPos = ftello(f);
    fseeko(f, 0, SEEK_END);
    off_t size = ftell(f);
    fseeko(f, curPos, SEEK_SET);
    printf("Pos: %llu,\tSize: %llu\n", curPos, size);
}

int main(int argc, const char *argv[]) {
    if (argc != 3) usage();

    const char *srcPath = argv[1];
    const char *tgtPath = argv[2];

    FILE *inFile = fopen(srcPath, "rb");
    FILE *outFile = fopen(tgtPath, "wb");

    printf("inFile: %s, outfile: %s\n", srcPath, tgtPath);

    if (!inFile) error("Couldn't open source file!");
    if (!outFile) error("Couldn't open target file!");

    while (!feof(inFile)) {
        char buff[2048];
        size_t count = fread(buff, 1, sizeof(buff), inFile);
        fwrite(buff, 1, count, outFile);
        printPosAndSize(outFile);
    }

    fclose(inFile);
    fclose(outFile);

    return 0;
}

此 cp 会将 2048 字节块从 file1 写入 file2。如果您只是复制常规文件,输出将如下所示:

# copying to new file:
$ sudo ./cp /var/log/syslog /tmp/foo.txt 
inFile: /var/log/syslog, outfile: /tmp/foo.txt
Pos: 2048,      Size: 2048
Pos: 4096,      Size: 4096
Pos: 4949,      Size: 4949

# overwriting existing file:
$ sudo ./cp /var/log/syslog /tmp/foo.txt 
inFile: /var/log/syslog, outfile: /tmp/foo.txt
Pos: 2048,      Size: 2048
Pos: 4096,      Size: 4096
Pos: 4949,      Size: 4949

我运行它两次是为了向您展示在以写入模式打开目标文件之前目标文件是否存在并不重要。一旦你写入它,它的内容就会被完全覆盖,文件大小反映了这一点。

所以,让我们尝试一些不同的东西: $ sudo ./cp /var/log/syslog /dev/null inFile: /var/log/syslog, outfile: /dev/null Pos: 0, Size: 0 Pos: 2048, Size: 0 位置:974,尺寸:0

/dev/null是一个字符设备。它们的大小始终为 0。输出只是写入它们(例如写入串行端口),然后就忘记了。

但让我们回答你的问题:如果你写入块设备(警告!执行此操作将使设备上的所有数据都无法读取,因为它会破坏驱动器的元信息!我在这个演示中使用了旧的 USB 驱动器)

$ sudo ./cp /var/log/syslog /dev/sdb
inFile: /var/log/syslog, outfile: /dev/sdb
Pos: 2048,      Size: 2003828736
Pos: 4096,      Size: 2003828736
Pos: 5306,      Size: 2003828736

块文件只是在位置 0 处打开并逐字节覆盖(不触及所有其他数据)。

相关内容