2018-06-14-玩玩inotify

Table of Contents

1 什么是inotify

DESCRIPTION
       The  inotify API provides a mechanism for monitoring filesystem events.
       Inotify can be used to monitor individual files, or to monitor directo‐
       ries.   When  a  directory is monitored, inotify will return events for
       the directory itself, and for files inside the directory.

直接翻译过来就是:提供一个机制用来监控文件系统的事件。

2 实际需求

我们设备的日志文件先是存在内存中的,现在需求是内存中的日志文件到达一 定大小Max_size时,就需要刷入flash中做持久化。那么怎么判断日志文件是 否到达Max_size呢?

最简单的方法是写一个循环一直不停地检测文件大小,但是这样很低效。

inotify在这种情况下就比较适用。具体方法就是用户进程使用inotify给内核 注册它所关心的文件。当文件发生改变时等事件时(可以在注册时选择关心的 事件),内核会通知用户程序。

这样通过inotify,上述需求可以这样完成:在每次文件修改时检测文件大小, 如果超过Max_size就刷入flash中。

3 简单的例子

这是inotify的测试程序,可以直接跟需要跟踪的目录或者文件 ./main /tmp/temp/1

#include <errno.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/inotify.h>
#include <unistd.h>

/* Read all available inotify events from the file descriptor 'fd'.
   wd is the table of watch descriptors for the directories in argv.
   argc is the length of wd and argv.
   argv is the list of watched directories.
   Entry 0 of wd and argv is unused. */

static void
handle_events(int fd, int *wd, int argc, char* argv[])
{
    /* Some systems cannot read integer variables if they are not
       properly aligned. On other systems, incorrect alignment may
       decrease performance. Hence, the buffer used for reading from
       the inotify file descriptor should have the same alignment as
       struct inotify_event. */

    char buf[4096]
        __attribute__ ((aligned(__alignof__(struct inotify_event))));
    const struct inotify_event *event;
    int i;
    ssize_t len;
    char *ptr;

    /* Loop while events can be read from inotify file descriptor. */

    for (;;)
    {

        /* Read some events. */

        len = read(fd, buf, sizeof buf);
        if (len == -1 && errno != EAGAIN) {
            perror("read");
            exit(EXIT_FAILURE);
        }

        /* If the nonblocking read() found no events to read, then
           it returns -1 with errno set to EAGAIN. In that case,
           we exit the loop. */

        if (len <= 0)
            break;

        /* Loop over all events in the buffer */

        for (ptr = buf; ptr < buf + len;
             ptr += sizeof(struct inotify_event) + event->len) {

            event = (const struct inotify_event *) ptr;

            /* Print event type */

            if (event->mask & IN_OPEN)
                printf("IN_OPEN: ");

            if (event->mask & IN_CLOSE_NOWRITE)
                printf("IN_CLOSE_NOWRITE: ");

            if (event->mask & IN_CLOSE_WRITE)
                printf("IN_CLOSE_WRITE: ");

            if (event->mask & IN_MODIFY)
                printf("IN_MODIFY: ");

            /* Print the name of the watched directory */

            for (i = 1; i < argc; ++i) {
                if (wd[i] == event->wd) {
                    printf("%s/", argv[i]);
                    break;
                }
            }

            /* Print the name of the file */

            if (event->len)
                printf("%s", event->name);

            /* Print type of filesystem object */

            if (event->mask & IN_ISDIR)
                printf(" [directory]\n");
            else
                printf(" [file]\n");
        }
    }
}

int
main(int argc, char* argv[])
{
    char           buf;
    int            fd, i, poll_num;
    int           *wd;
    nfds_t         nfds;
    struct pollfd  fds[2];

    if (argc < 2) {
        printf("Usage: %s PATH [PATH ...]\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    printf("Press ENTER key to terminate.\n");

    /* Create the file descriptor for accessing the inotify API */

    fd = inotify_init1(IN_NONBLOCK);
    if (fd == -1) {
        perror("inotify_init1");
        exit(EXIT_FAILURE);
    }

    /* Allocate memory for watch descriptors */

    wd = calloc(argc, sizeof(int));
    if (wd == NULL) {
        perror("calloc");
        exit(EXIT_FAILURE);
    }

    /* Mark directories for events
       - file was opened
       - file was closed */

    for (i = 1; i < argc; i++) {
        wd[i] = inotify_add_watch(fd, argv[i],
                                  IN_OPEN | IN_CLOSE | IN_MODIFY);
        if (wd[i] == -1) {
            fprintf(stderr, "Cannot watch '%s'\n", argv[i]);
            perror("inotify_add_watch");
            exit(EXIT_FAILURE);
        }
    }

    /* Prepare for polling */

    nfds = 2;

    /* Console input */

    fds[0].fd = STDIN_FILENO;
    fds[0].events = POLLIN;

    /* Inotify input */

    fds[1].fd = fd;
    fds[1].events = POLLIN;

    /* Wait for events and/or terminal input */

    printf("Listening for events.\n");
    while (1) {
        poll_num = poll(fds, nfds, -1);
        if (poll_num == -1) {
            if (errno == EINTR)
                continue;
            perror("poll");
            exit(EXIT_FAILURE);
        }

        if (poll_num > 0) {

            if (fds[0].revents & POLLIN) {

                /* Console input is available. Empty stdin and quit */

                while (read(STDIN_FILENO, &buf, 1) > 0 && buf != '\n')
                    continue;
                break;
            }

            if (fds[1].revents & POLLIN) {

                /* Inotify events are available */

                handle_events(fd, wd, argc, argv);
            }
        }
    }

    printf("Listening for events stopped.\n");

    /* Close inotify file descriptor */

    close(fd);

    free(wd);
    exit(EXIT_SUCCESS);
}

使用一个测试程序不停地去写 /tmp/temp/1 文件,这样上面程序中可以收 到notify了:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

int main()
{
    int fd = open("/tmp/temp/1", O_RDWR | O_APPEND | O_CREAT);

    char *buf = "hello,world\n";

    while(1)
    {
        write(fd, buf, 4);

        sleep(1);
    }

    return 0;
}

makefile文件如下:

.PHONY:a.out
a.out:
        cc -o main main.c
        cc -o test test.c
clean:
        rm -rfv *~ a.out

4 需要注意

刚开始使用的时候, main 程序老是报错。

  ➜  inotify ./a.out /tmp /tmp/temp
Press ENTER key to terminate.
Cannot watch '/tmp'
inotify_add_watch: No space left on device

搜索了一下,我理解应该是系统中,inotify有一个允计用户设置notify的最 大值。ubuntu上直接调用 sudo sysctl fs.inotify.max_user_watches 可 以查看一下当前设置值:

➜  inotify sudo sysctl fs.inotify.max_user_watches
fs.inotify.max_user_watches = 8192

如果出现这个错误,应该当前系统已经达到配置值的上限了。需要改大一点:

  ➜  inotify sudo sysctl fs.inotify.max_user_watches=81920
fs.inotify.max_user_watches = 81920

如果把这个配置持久化,需要修改 sysctl 的配置文件 /etc/sysctl.conf ,把 fs.inotify.max_user_watches = 81920 加入到 文件中即可。1

Footnotes:

1
这个我没有实际测试过,不过大致应该差不多。

Author: Peng Xie

Created: 2018-10-01 Mon 21:36