使用inotify和read函数

15

我一直在学习inotify调用,但是当涉及到读取接口时仍然有些困惑。以下是我找到的关于如何使用read(2)正确地与inotify进行交互的最相关资源:

它们都以相同的方式实现它,首先定义以下大小:

#define EVENT_SIZE  ( sizeof (struct inotify_event) )
#define BUF_LEN     ( 1024 * ( EVENT_SIZE + 16 )

然后他们以这种方式使用它们:

length = read( fd, buffer, BUF_LEN );  

if ( length < 0 ) {
    perror( "read" );
}  

while ( i < length ) {
    struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
    /* some processing */
    i += EVENT_SIZE + event->len;
}

现在,我们知道名称是struct inotify_event的一部分,并且它具有可变长度。那么,缓冲区中的最后一个inotify_event可能会被截断吗?

假设有1023个具有16字节路径和1个具有32字节路径的inotify_events,那么会发生什么?那么后者会被截断吗?还是内核会看到它不适合于缓冲区并完全留下它?

1个回答

9

基本用法

根据 inotify(7),您可以使用 FIONREAD ioctl 查找可读取的数据量并相应地调整缓冲区大小。下面是一些(非常粗略的)代码可实现此操作:

unsigned int avail;
ioctl(inotify_fd, FIONREAD, &avail);

char buffer[avail];
read(fd, buffer, avail);

int offset = 0;
while (offset < avail) {
    struct inotify_event *event = (inotify_event*)(buffer + offset);

    // Insert logic here
    my_process_inotify_event(event);

    offset = offset + sizeof(inotify_event) + event->len;
}

更强大的使用方式

inotify-tools 提供了一个更高层次的接口来使用 inotify。您可以使用它代替直接访问 inotify,也可以查看它如何实现 inotifytools_next_events 来安全、可靠地读取所有可用事件。

部分事件和截断

针对您关于截断的问题,我认为内核不会返回部分的 inotify_event 或者在缓冲区无法容纳所有事件时截断 inotify_event。以下是 inotify(7) 手册中的一段话:

当传递给 read(2) 的缓冲区过小以至于无法返回下一个事件的信息时,read(2) 的行为取决于内核版本:在 2.6.21 版本之前的内核中,read(2) 返回0;自从内核 2.6.21 起,read(2) 函数会报 EINVAL 错误。

以下是inotifytools.c中的注释:

// oh... no.  this can't be happening.  An incomplete event.
// Copy what we currently have into first element, call self to
// read remainder.
// oh, and they BETTER NOT overlap.
// Boy I hope this code works.
// But I think this can never happen due to how inotify is written.

1
我已经向#kernelnewbies咨询了有关短读/截断的问题。手册说明:“每个成功的read(2)返回一个包含一个或多个以下结构的缓冲区[...]”。文件名是这些结构的一部分。中断在这里并不重要,信号不能在内核上下文中发生。输出缓冲区只包含完整的结构或什么都没有。在内核code中,从read()返回的值为sizeof(struct inotify_event)+round(name)。 - wodny
“哦...不要啊。这不可能发生的。...希望这段代码能正常工作。”- 强壮,呵呵? - user253751
不要使用 avail 来确定缓冲区的大小。avail 只是当前可用数据的量,当您首次启动时它将为零。而且每次循环都设置缓冲区大小也不是一个好主意。 - Ariel
@Ariel - 我不明白你的担忧。这段代码只会在select或类似函数报告有可读数据时被调用,因此我们知道avail是非零的。而且,由于buffer[avail]是一个在堆栈上分配的C99变长数组,每次循环设置缓冲区大小的成本微不足道。 - Josh Kelley
是的,你可以这样做,但是在不同的函数中放置read()poll()对我来说没有意义。实际上,一个合理的上限是4MB,缓冲区的合理大小约为1K到64K。由于在堆栈上处理4MB有些棘手,因此将其放在堆栈上真的不值得 - 你并没有获得任何好处。 - Ariel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接