inotify–内核中文件系统的关照机制

 

转载:http://www.ibm.com/developerworks/cn/linux/l-inotifynew/index.html

一、 引言

眼看,Linux 桌面系统与 MAC 或 Windows
相相比较有过多不如人意的地点,为了精益求精这种气象,开源社区指出用户态需要内核提供部分编制,以便用户态能够登时地意识到内核或底层硬件设施爆发了怎么着,从而可以更好地保管设施,给用户提供更好的服务,如
hotplug、udev 和 inotify 就是这种要求催生的。Hotplug
是一种基本向用户态应用通报有关热插拔设备一些轩然大波暴发的体制,桌面系统能够使用它对配备开展有效的管住,udev
动态地保障 /dev 下的装置文件,inotify
是一种文件系统的变迁通告机制,如文件扩张、删除等事件可以顿时让用户态得知,该机制是知名的桌面搜索引擎项目
beagle 引入的,并在 Gamin 等品种中被运用。

实则,在 inotify 往日早已存在一连串似的编制叫
dnotify,不过它存在许多毛病:

1.
对于想监视的每一个目录,用户都亟需开辟一个文件描述符,由此假如需要监视的目录较多,将促成打开许多文本描述符,特别是,假诺被监视目录在移动介质上(如光盘和
USB 盘),将招致力不从心 umount 这么些文件系统,因为运用 dnotify
的行使打开的文本讲述符在动用该文件系统。

2. dnotify
是遵照目录的,它不得不获取目录变化事件,当然在目录内的文件的变更会潜移默化到其所在目录从而吸引目录变化事件,但是要想经过目录事件来得知哪个文件变化,需要缓存许多
stat 结构的多寡。

3. Dnotify 的接口分外不谐和,它接纳 signal。

Inotify 是为代表 dnotify 而设计的,它克制了 dnotify
的短处,提供了更好用的,简洁而强大的公文变化通告机制:

1. Inotify
不需要对被监视的靶子打开文件描述符,而且只要被监视目的在可活动介质上,那么在
umount 该介质上的文件系统后,被监视目的对应的 watch
将被机关删除,并且会暴发一个 umount 事件。

2. Inotify 既可以监视文件,也足以监视目录。

3. Inotify 施用系统调用而非 SIGIO 来通知文件系统事件。

4. Inotify 采纳文件讲述符作为接口,由此可以接纳普通的公文 I/O
操作select 和 poll 来监视文件系统的扭转。

Inotify 可以监视的文件系统事件包括:

  • IN_ACCESS,即文件被访问
  • IN_MODIFY,文件被 write
  • IN_ATTRIB,文件属性被涂改,如 chmod、chown、touch 等
  • IN_CLOSE_WRITE,可写文件被 close
  • IN_CLOSE_NOWRITE,不可写文件被 close
  • IN_OPEN,文件被 open
  • IN_MOVED_FROM,文件被移走,如 mv
  • IN_MOVED_TO,文件被移来,如 mv、cp
  • IN_CREATE,成立新文件
  • IN_DELETE,文件被剔除,如 rm
  • IN_DELETE_SELF,自删除,即一个可执行文件在执行时去除自己
  • IN_MOVE_SELF,自活动,即一个可执行文件在推行时移动自己
  • IN_UNMOUNT,宿主文件系统被 umount
  • IN_CLOSE,文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
  • IN_MOVE,文件被移动,等同于(IN_MOVED_FROM | IN_MOVED_TO)

注:下面所说的文本也包罗目录。

二、用户接口

在用户态,inotify 通过三个系统调用和在回去的文本讲述符上的公文 I/
操作来接纳,使用 inotify 的第一步是创立 inotify 实例:

int fd = inotify_init ();

每一个 inotify 实例对应一个独门的排序的队列。

文件系统的扭转事件被称做 watches 的一个目的管理,每一个 watch
是一个二元组(目标,事件掩码),目的可以是文本或目录,事件掩码表示应用希望关注的
inotify 事件,每一个位对应一个 inotify 事件。沃特ch 对象通过
watch描述符引用,watches 通过文件或目录的门道名来添加。目录 watches
将回来在该目录下的有着文件下面暴发的风波。

下面函数用于添加一个 watch:

 int wd = inotify_add_watch (fd, path, mask);

 

fd 是 inotify_init() 重返的文件描述符,path
是被监视的靶子的路线名(即文件名或目录名),mask 是事件掩码, 在头文件
linux/inotify.h
中定义了每一位代表的风波。可以使用同一的法门来修改事件掩码,即改变希望被布告的inotify
事件。Wd 是 watch 描述符。

下边的函数用于删除一个 watch:

int ret = inotify_rm_watch (fd, wd);

fd 是 inotify_init() 再次回到的文书描述符,wd 是 inotify_add_watch()重返的 watch 描述符。Ret 是函数的再次回到值。

文件事件用一个 inotify_event 结构意味着,它经过由 inotify_init()返回的文书讲述符使用普通文件读取函数 read 来得到:

struct inotify_event {
        __s32           wd;             /* watch descriptor */
        __u32           mask;           /* watch mask */
        __u32           cookie;         /* cookie to synchronize two events */
        __u32           len;            /* length (including nulls) of name */
        char            name[0];        /* stub for possible name */
};

布局中的 wd 为被监视目标的 watch 描述符,mask 为事件掩码,len 为
name字符串的尺寸,name 为被监视目的的路径名,该协会的 name
字段为一个桩,它只是为着用户方面引用文件名,文件名是变长的,它实在紧跟在该社团的后边,文件名将被
0 填充以使下一个事变社团可以 4 字节对齐。注意,len
也把填充字节数总括在内。

因而 read 调用可以一次得到六个事件,只要提供的 buf 充足大。

size_t len = read (fd, buf, BUF_LEN);

buf 是一个 inotify_event 结构的数组指针,BUF_LEN
指定要读取的总长度,buf 大小至少要不小于
BUF_LEN,该调用重返的风波数取决于 BUF_LEN 以及事件闽南语件名的长度。Len
为实在读去的字节数,即拿到的轩然大波的总长度。

可以在函数 inotify_init() 再次来到的公文讲述符 fd 上拔取 select() 或poll(),
也可以在 fd 上应用 ioctl 命令 FIONREAD
来拿到当前队列的长短。close(fd)将去除所有添加到 fd 中的 watch
并做必要的清理。

int inotify_init (void);
int inotify_add_watch (int fd, const char *path, __u32 mask);
int inotify_rm_watch (int fd, __u32 mask);

三、内核实现机理

在基本中,每一个 inotify 实例对应一个 inotify_device 结构:

struct inotify_device {
        wait_queue_head_t       wq;             /* wait queue for i/o */
        struct idr              idr;            /* idr mapping wd -> watch */
        struct semaphore        sem;            /* protects this bad boy */
        struct list_head        events;         /* list of queued events */
        struct list_head        watches;        /* list of watches */
        atomic_t                count;          /* reference count */
        struct user_struct      *user;          /* user who opened this dev */
        unsigned int            queue_size;     /* size of the queue (bytes) */
        unsigned int            event_count;    /* number of pending events */
        unsigned int            max_events;     /* maximum number of events */
        u32                     last_wd;        /* the last wd allocated */
};

wq 是伺机队列,被 read 调用阻塞的历程将挂在该等待队列上,idr 用于把
watch 描述符映射到对应的 inotify_watch,sem
用于共同对该协会的拜访,events 为该 inotify 实例上发生的轩然大波的列表,被该
inotify 实例监视的拥有事件在暴发后都将插入到这些列表,watches 是给
inotify 实例监视的 watch 列表,inotify_add_watch 将把新加上的 watch
插入到该列表,count 是引用计数,user 用于描述创设该 inotify
实例的用户,queue_size 代表该 inotify
实例的事件队列的字节数,event_count 是 events 列表的风波数,max_events
为最大允许的风波数,last_wd 是上次分红的 watch 描述符。

每一个 watch 对应一个 inotify_watch 结构:

struct inotify_watch {
        struct list_head        d_list; /* entry in inotify_device's list */
        struct list_head        i_list; /* entry in inode's list */
        atomic_t                count;  /* reference count */
        struct inotify_device   *dev;   /* associated device */
        struct inode            *inode; /* associated inode */
        s32                     wd;     /* watch descriptor */
        u32                     mask;   /* event mask for this watch */
};

d_list 指向所有 inotify_device 组成的列表的,i_list 指向所有被监视
inode 组成的列表,count 是援引计数,dev 指向该 watch 所在的 inotify
实例对应的 inotify_device 结构,inode 指向该 watch 要监视的 inode,wd
是分配给该 watch 的描述符,mask 是该 watch
的事件掩码,表示它对怎么文件系统事件感兴趣。

结构 inotify_device 在用户态调用 inotify_init() 时创建,当关闭
inotify_init()重返的文书讲述符时将被保释。结构 inotify_watch
在用户态调用 inotify_add_watch()时创立,在用户态调用
inotify_rm_watch() 或 close(fd) 时被放飞。

无论是目录仍然文件,在根本中都对应一个 inode 结构,inotify 系统在 inode
结构中追加了六个字段:

#ifdef CONFIG_INOTIFY
    struct list_head    inotify_watches; /* watches on this inode */
    struct semaphore    inotify_sem;    /* protects the watches list */
#endif

inotify_watches 是在被监视目的上的 watch 列表,每当用户调用
inotify_add_watch()时,内核就为充足的 watch 创造一个 inotify_watch
结构,并把它插入到被监视目的对应的 inode 的 inotify_watches
列表。inotify_sem 用于共同对 inotify_watches
列表的拜会。当文件系统暴发第一局部涉及的风波之一时,相应的文件系统代码将显示调用fsnotify_*
来把相应的事件报告给 inotify
系统,其中*号就是呼应的轩然大波名,近期贯彻包括:

  • fsnotify_move,文件从一个目录移动到另一个索引
  • fsnotify_nameremove,文件从目录中除去
  • fsnotify_inoderemove,自删除
  • fsnotify_create,创设新文件
  • fsnotify_mkdir,创立新目录
  • fsnotify_access,文件被读
  • fsnotify_modify,文件被写
  • fsnotify_open,文件被打开
  • fsnotify_close,文件被关门
  • fsnotify_xattr,文件的扩展属性被涂改
  • fsnotify_change,文件被改动或原数据被改动

有一个例外情形,就是 inotify_unmount_inodes,它会在文件系统被 umount
时调用来打招呼 umount 事件给 inotify 系统。

如上提到的通告函数最终都调用
inotify_inode_queue_event(inotify_unmount_inodes直接调用
inotify_dev_queue_event
),该函数首先判断相应的inode是否被监视,这通过翻看 inotify_watches
列表是否为空来实现,假若发现 inode
没有被监视,什么也不做,顿时回到,反之,遍历 inotify_watches
列表,看是否当前的文件操作事件被某个 watch 监视,假使是,调用
inotify_dev_queue_event,否则,返回。函数inotify_dev_queue_event
首先判断该事件是否是上一个事件的再次,即便是就吐弃该事件并回到,否则,它判断是否
inotify 实例即 inotify_device
的事件队列是否溢出,假诺溢出,爆发一个溢出事件,否则暴发一个脚下的文书操作事件,这些事件经过kernel_event
构建,kernel_event 将创制一个 inotify_kernel_event
结构,然后把该社团插入到相应的 inotify_device 的 events
事件列表,然后指示等待在inotify_device 结构中的 wq
指向的等候队列。想监视文件系统事件的用户态进程在inotify 实例(即
inotify_init() 重回的文书讲述符)上调用 read
时但没有事件时就挂在等待队列 wq 上。

四、使用示例

下边是一个运用 inotify 来监视文件系统事件的例证:

#include <linux/unistd.h>
#include <linux/inotify.h>
#include <errno.h>
_syscall0(int, inotify_init)
_syscall3(int, inotify_add_watch, int, fd, const char *, path, __u32, mask)
_syscall2(int, inotify_rm_watch, int, fd, __u32, mask)
char * monitored_files[] = {
    "./tmp_file",
    "./tmp_dir",
    "/mnt/sda3/windows_file"
};
struct wd_name {
    int wd;
    char * name;
};
#define WD_NUM 3
struct wd_name wd_array[WD_NUM];
char * event_array[] = {
    "File was accessed",
    "File was modified",
    "File attributes were changed",
    "writtable file closed",
    "Unwrittable file closed",
    "File was opened",
    "File was moved from X",
    "File was moved to Y",
    "Subfile was created",
    "Subfile was deleted",
    "Self was deleted",
    "Self was moved",
    "",
    "Backing fs was unmounted",
    "Event queued overflowed",
    "File was ignored"
};
#define EVENT_NUM 16
#define MAX_BUF_SIZE 1024

int main(void)
{
    int fd;
    int wd;
    char buffer[1024];
    char * offset = NULL;
    struct inotify_event * event;
    int len, tmp_len;
    char strbuf[16];
    int i = 0;

    fd = inotify_init();
    if (fd < 0) {
        printf("Fail to initialize inotify.\n");
        exit(-1);
    }
    for (i=0; i<WD_NUM; i++) {
        wd_array[i].name = monitored_files[i];
        wd = inotify_add_watch(fd, wd_array[i].name, IN_ALL_EVENTS);
        if (wd < 0) {
            printf("Can't add watch for %s.\n", wd_array[i].name);
            exit(-1);
        }
        wd_array[i].wd = wd;
    }
    while(len = read(fd, buffer, MAX_BUF_SIZE)) {
        offset = buffer;
        printf("Some event happens, len = %d.\n", len);
        event = (struct inotify_event *)buffer;
        while (((char *)event - buffer) < len) {
            if (event->mask & IN_ISDIR) {
                memcpy(strbuf, "Direcotory", 11);
            }
            else {
                memcpy(strbuf, "File", 5);
            }
            printf("Object type: %s\n", strbuf);
            for (i=0; i<WD_NUM; i++) {
                if (event->wd != wd_array[i].wd) continue;
                printf("Object name: %s\n", wd_array[i].name);
                break;
            }
            printf("Event mask: %08X\n", event->mask);
            for (i=0; i<EVENT_NUM; i++) {
                if (event_array[i][0] == '\0') continue;
                if (event->mask & (1<<i)) {
                    printf("Event: %s\n", event_array[i]);
                }
            }
            tmp_len = sizeof(struct inotify_event) + event->len;
            event = (struct inotify_event *)(offset + tmp_len); 
            offset += tmp_len;
        }
    }
}

当运行此程序的时候在另一个虚构终端履行 cat ./tmp_file,此程序的输出为:

Some event happens, len = 48.
Object type: File
Object name: ./tmp_file
Event mask: 00000020
Event: File was opened
Object type: File
Object name: ./tmp_file
Event mask: 00000001
Event: File was accessed
Object type: File
Object name: ./tmp_file
Event mask: 00000010
Event: Unwrittable file closed

 

相关文章