为什么设备的 stat::st_size 0 但同时 lseek 正确定义了设备大小?

Why is stat::st_size 0 for devices but at the same time lseek defines the device size correctly?

我注意到,当我使用 open + lseek 查询设备大小时,一切正常,但是当我 stat 设备时,我得到的是零而不是实际设备尺寸。该设备是干净的,没有任何文件系统,并且设备的第一个字节以一些文本开头,例如“1234567890ABC”。怎么了?

代码:

#include <sys/stat.h>
#include <dirent.h>

bool
GetFileSize(const char* pPath, uint64_t& Size)
{
    pPath = "/home/sw/.bashrc";
    pPath = "/dev/sda";

    struct stat buffer;
    if (stat(pPath, &buffer))
    {
        printf("Failed to stat file. Error: %s. FilePath: %s\n", strerror(errno), pPath);
        return false;
    }

    printf("File size by stat: %" PRIu64 " WTF?\n", buffer.st_size);

    //
    // Note: It's strange, but stat::st_size from the stat call is zero for devices
    //

    int File = open(pPath, O_RDONLY);
    if (File < 0)
    {
        printf("Failed to open file. Error: %s. FilePath: %s\n", strerror(errno), pPath);
        return false;
    }

    long off = lseek(File, 0, SEEK_END);
    if (off == (off_t)-1)
    {
        printf("Failed to get file size. Error: %s. FilePath: %s\n", strerror(errno), pPath);
        close(File);
        return false;
    }
    close(File);

    printf("File size by lseek: %" PRIu64 "\n", off);
    fflush(stdout);

    Size = off;
    return true;
}

输出:

File size by stat: 0 Huh?
File size by lseek: 34359738368

如果我对常规文件使用 stat,则一切正常(注释掉带有“/dev/sda”的行):

File size by stat: 4019 Huh?
File size by lseek: 4019

来自这个Unix Stack Exchange问题:

Device files are not files per se. They're an I/O interface to use the devices in Unix-like operating systems. They use no space on disk, however, they still use an inode as reported by the stat command:

$ stat /dev/sda
      File: /dev/sda
      Size: 0               Blocks: 0          IO Block: 4096   block special file
Device: 6h/6d   Inode: 14628       Links: 1     Device type: 8,0

这解决了 stat 部分。

你可以在这个"file"中寻找的事实与此无关。这不是真正的文件,但您可以 open 它并从中读取。你也可以寻求它。它允许在最低级别读取磁盘,所以查找是必要的(这就是它工作的原因,为什么它不像任何 "real" 文件那样 return 新位置?)。

根据this other UnixSE answer,您可以通过阅读此/dev/sda/size文件来获取设备大小。

一个"device"的长度如/dev/sda没有被the POSIX struct stat指定:

off_t st_size       For regular files, the file size in bytes. 

                    For symbolic links, the length in bytes of the 
                    pathname contained in the symbolic link. 

                    For a shared memory object, the length in bytes. 

                    For a typed memory object, the length in bytes. 

                    For other file types, the use of this field is 
                    unspecified. 

所以POSIX对磁盘设备"size"没有要求

Linux同样does not specify that stat()应return磁盘设备的大小:

st_size

This field gives the size of the file (if it is a regular file or a symbolic link) in bytes. The size of a symbolic link is the length of the pathname it contains, without a terminating null byte.

细节决定成败...对于初学者来说,有一个 Unix 设计的基本原则:一切都是文件,很好 explained here.

第二个是 stat(2) 调用为您提供 inode 存储在文件系统上的关于 device-special file 的统计信息它的大小为零(将其视为 lstat(2))。如果你的块设备上有文件系统,你可以使用 statfs(2)getfsstat(2)statvfs(2) 以 filesystem/device 独立的方式获取有关它的信息。

处理特殊文件(通常驻留在 /dev 中)一直是系统特定的,手册页位于第 4 节。因此,如果您想直接操作设备,您应该阅读那里的细节。例如,在 Linux 中,man 4 hd 将向您展示如何以编程方式与 IDE 块设备进行交互。而 man 4 sd 将告诉您如何与 scsi 光盘进行交互等

第三,系统调用的功能不应该不一致也不它们的局限性。

希望对您有所帮助。

在 Linux 上,记录的获取可打开的原始磁盘设备大小的方法是使用 BLKGETSIZE ioctl。请参阅 sd(4) 联机帮助页。

请注意,此 returns 设备的大小在 个扇区 中。您可能会认为,对于以字节为单位的大小,您必须乘以 BLKSSZGET ioctl 返回的值,但是 if I'm reading the source code correctly,实际上您必须乘以 512,无论 BLKSSZGET returns.

lseek 是 C 的 fseek 的 backbone,因此它具有相似的语义,匹配 fseek - 并且与 Unix 的其他领域完全分离 API.出处方面,您会期望 lseek 的行为类似于获取文件句柄 fseek,而 fseek 是一个 C 库接口,它并不是特定于 Unix 的。

但是,

stat 是特定于 Unix 的,并且有自己的作用。如果你考虑出处,这是一个合理的差异。当然,问题是 C API 的类型模型非常薄弱,因为 C 距离实现真正的类型安全还差一步。

为什么这很重要?因为,从根本上说,seekable_sizefile_object_size 是两个根本不同的概念,并且需要不同的类型——甚至C++ 标准库弄错了。

但是在 C++ 和现代编译器中,它现在完全是一个无端的遗留缺陷,在 C 中确实没有办法在不降低性能和代码可读性的情况下有效地将整数包装到不兼容的类型中。因此,您最终会得到类似 offs_tlong 的东西被用于完全不兼容的概念。这就是混淆的根源:仅仅因为您从与文件相关的函数中得到与大小相关的数字并不意味着该数字将具有相同的含义。含义通常在类型中被捕获……long 固有的唯一含义是“嘿,我是一个数字,你可以和我一起做数字的事情”……:(