【问题标题】:Why does Files.readAllBytes first read with a bufsize of 1?为什么 Files.readAllBytes 首先读取 bufsize 为 1?
【发布时间】:2016-10-04 17:57:21
【问题描述】:

我正在编写一个简单的 Linux USB 字符驱动程序,允许从它创建的设备节点读取一个短字符串。

它工作正常,但我注意到使用cat 从设备节点读取和使用Files.readAllBytes 从Java 程序读取之间存在差异。

使用cat读取,在第一次调用file_operations.read函数时传入大小为131072的缓冲区,并复制5字节字符串:

kernel: [46863.186331] usbtherm: Device was opened
kernel: [46863.186407] usbtherm: buffer: 131072, read: 5, offset: 5
kernel: [46863.186444] usbtherm: done, returning 0
kernel: [46863.186481] usbtherm: Device was released

Files.readAllBytes读取,第一次调用时传入一个大小为1的缓冲区,然后传入一个大小为8191的缓冲区并复制剩余的4个字节:

kernel: [51442.728879] usbtherm: Device was opened
kernel: [51442.729032] usbtherm: buffer: 1, read: 1, offset: 1
kernel: [51442.729102] usbtherm: buffer: 8191, read: 4, offset: 5
kernel: [51442.729140] usbtherm: done, returning 0
kernel: [51442.729158] usbtherm: Device was released

file_operations.read函数(包括调试printk的)是:

static ssize_t device_read(struct file *filp, char *buffer, size_t length,
        loff_t *offset)
{
    int err = 0;
    size_t msg_len = 0;
    size_t len_read = 0;

    msg_len = strlen(message);

    if (*offset >= msg_len)
    {
        printk(KERN_INFO "usbtherm: done, returning 0\n");
        return 0;
    }

    len_read = msg_len - *offset;
    if (len_read > length)
    {
        len_read = length;
    }

    err = copy_to_user(buffer, message + *offset, len_read);
    if (err)
    {
        err = -EFAULT;
        goto error;
    }

    *offset += len_read;

    printk(KERN_INFO "usbtherm: buffer: %ld, read: %ld, offset: %lld\n", 
            length, len_read, *offset);

    return len_read;

error:
    return err;
}

两种情况下读取的字符串是相同的,所以我想没关系,我只是想知道为什么会有不同的行为?

【问题讨论】:

  • 你能看到size()为你的设备节点返回什么吗?

标签: java linux kernel driver


【解决方案1】:

GNU cat

cat的源码中,

      insize = io_blksize (stat_buf);

你可以看到缓冲区的大小是由coreutils的io_bliksize()决定的,在这方面有一个相当的interesting comment

/* 截至 2014 年 5 月,128KiB 被确定为最小 blksize 最大限度地减少系统调用开销。

这可以用cat 解释结果,因为 128KiB 是 131072 字节,而 GNUrus 认为这是最小化系统调用开销的最佳方法。

Files.readAllBytes

有点难以掌握,至少对于像我这样的简单灵魂来说。 source of readAllBytes

public static byte[] readAllBytes(Path path) throws IOException {
    try (SeekableByteChannel sbc = Files.newByteChannel(path);
         InputStream in = Channels.newInputStream(sbc)) {
        long size = sbc.size();
        if (size > (long)MAX_BUFFER_SIZE)
            throw new OutOfMemoryError("Required array size too large");

        return read(in, (int)size);
    }
}

表明它只是调用read(InputStream, initialSize),其中初始大小由字节通道的大小决定。 size() 方法还有一个有趣的注释,

不是isRegularFile()文件的文件大小是实现 特定的,因此未指定。

最后,read(InputStream, initialSize) 调用 InputStream.read(byteArray, offset, length) 进行读取(源代码中的 cmets 来自原始源,并且自 capacity - nread = 0 以来令人困惑,所以第一次到达 while 循环时,它确实 不是 读到 EOF):

private static byte[] read(InputStream source, int initialSize)
        throws IOException {
    int capacity = initialSize;
    byte[] buf = new byte[capacity];
    int nread = 0;
    int n;
    for (;;) {
        // read to EOF which may read more or less than initialSize (eg: file
        // is truncated while we are reading)
        while ((n = source.read(buf, nread, capacity - nread)) > 0)
            nread += n;

        // if last call to source.read() returned -1, we are done
        // otherwise, try to read one more byte; if that failed we're done too
        if (n < 0 || (n = source.read()) < 0)
            break;

        // one more byte was read; need to allocate a larger buffer
        if (capacity <= MAX_BUFFER_SIZE - capacity) {
            capacity = Math.max(capacity << 1, BUFFER_SIZE);
        } else {
            if (capacity == MAX_BUFFER_SIZE)
                throw new OutOfMemoryError("Required array size too large");
            capacity = MAX_BUFFER_SIZE;
        }
        buf = Arrays.copyOf(buf, capacity);
        buf[nread++] = (byte)n;
    }
    return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
}

BUFFER_SIZEFiles的声明

    // buffer size used for reading and writing
    private static final int BUFFER_SIZE = 8192;

InputStream.read(byteArray, offset, length) 的文档/来源包含相关评论,

如果长度为零,则不读取字节,返回0;

由于size() 为您的设备节点返回 0 字节,因此read(InputStream source, int initialSize) 中发生的情况如下:

for (;;)循环的第一轮:

  • capacity=0nread=0。所以while ((n = source.read(buf, nread, capacity - nread)) > 0)中的source.read将0字节读入buf并返回0:while循环的条件为假,它所做的只是n = 0作为条件的副作用。

  • 1234563这会导致您的“buffer: 1, read: 1, offset: 1
  • 缓冲区的capacity设置为BUFFER_SIZE,读取的单个字节放入buf[0]nread递增。

for (;;) 循环的第二轮

  • 因此有capacity=8192nread=1,这使得while ((n = source.read(buf, nread, capacity - nread)) &gt; 0) nread += n; 从偏移量1 读取8191 个字节,直到source.read 返回-1:EOF!这在读取剩余的 4 个字节后发生。这将导致您的“缓冲区:8191,读取:4,偏移量:5”。

  • 从现在开始n = -1if (n &lt; 0 || (n = source.read()) &lt; 0) break; 中的表达式在n &lt; 0 上短路,这使得我们的for 循环退出而不读取更多字节。

最后,该方法返回Arrays.copyOf(buf, nread):缓冲区中放置读取字节的那部分的副本。

【讨论】:

  • 嗯,您提供的read()方法实际上是逐字节读取的,其中没有it tries to read the rest of the bytes.。第一个字节的特殊处理用于区分情况,在错误/异常之前是否已读取 任何字节。根据方法规范的要求,它会影响返回值/异常。
  • @Tsyvarev 同意,​​这是对第一个字节处理的更好解释。我也很困惑size() 是从哪里获得“未指定”值的,也许这就是它的根源。
  • size() 为设备节点返回 0,而它返回常规文件的实际大小,例如 ls -l
  • @TorstenRömer size() of 0 使read(inputStream, initialSize) 调用read(byte b[], int off, int len) 方法三次,一次读取0 字节,一次读取1 字节,一次读取BUFFER_SIZE-1 字节。所以我们要做点什么:) 我将删除答案的最后一部分,并首先将read(inputStream, initialSize) 的完整源代码编辑到其中。
  • @TorstenRömer 是的。简而言之,它读取 0 字节,然后读取 1 字节以查看是否有要读取的内容,然后读取 BUFFER_SIZE-1 字节直到 EOF。我得看看我如何编辑答案以一种可读的方式解释它:P
猜你喜欢
  • 1970-01-01
  • 2012-02-12
  • 2019-05-30
  • 2017-07-09
  • 2019-08-19
  • 1970-01-01
  • 1970-01-01
  • 2016-07-03
  • 2019-09-25
相关资源
最近更新 更多