Linux INotif 메커니즘 및 예제 코드에 대한 자세한 설명

高洛峰
풀어 주다: 2023-03-05 14:34:02
원래의
1327명이 탐색했습니다.

Linux INotif 메커니즘

1. 서문:

우리 모두 알고 있듯이 Linux 데스크톱 시스템은 이러한 상황을 개선하기 위해 MAC 또는 Windows에 비해 만족스럽지 못한 측면이 많습니다. 오픈 소스 커뮤니티는 사용자 상태가 커널이나 기본 하드웨어 장치에 발생한 일을 적시에 알 수 있도록 커널이 몇 가지 메커니즘을 제공해야 장치를 더 잘 관리하고 사용자에게 더 나은 서비스를 제공할 수 있다고 제안했습니다. hotplug, udev 및 inotify와 같은 것은 이러한 필요성에서 탄생했습니다. 핫플러그는 커널이 핫플러그 장치에서 일부 이벤트 발생을 알리는 메커니즘입니다. 데스크탑 시스템은 이를 사용하여 장치 파일을 /dev에서 동적으로 관리할 수 있습니다. 파일 추가, 삭제 및 기타 이벤트와 같은 변경 알림 메커니즘은 사용자 상태에 즉시 알릴 수 있습니다. 이 메커니즘은 유명한 데스크톱 검색 엔진 프로젝트인 비글에 의해 도입되었으며 Gamin과 같은 프로젝트에서 사용됩니다.

사실 inotify 이전에도 dnotify라는 비슷한 메커니즘이 있었지만 단점이 많았습니다.

1. 모니터링해야 하는 각 디렉터리에 대해 사용자는 파일 설명자를 열어야 합니다. 따라서 모니터링해야 할 디렉터리가 많으면 특히 모니터링되는 디렉터리가 이동식 미디어(예: CD)에 있는 경우 많은 파일 설명자가 열립니다. 및 USB 디스크), dnotify를 사용하여 응용 프로그램에서 연 파일 설명자가 파일 시스템을 사용하고 있기 때문에 이러한 파일 시스템을 마운트 해제할 수 없게 됩니다.

2. dnotify는 디렉터리를 기반으로 하며 디렉터리 변경 이벤트만 가져올 수 있습니다. 물론 디렉터리에 있는 파일을 변경하면 해당 파일이 있는 디렉터리에 영향을 미치고 디렉터리 변경 이벤트가 발생합니다. 이벤트를 사용하려면 많은 통계 구조적 데이터를 캐시해야 합니다.

3. Dnotify의 인터페이스는 매우 비우호적이며 신호를 사용합니다.

Inotify는 dnotify의 단점을 극복하고 보다 유용하고 간결하며 강력한 파일 변경 알림 메커니즘을 제공합니다.

1. Inotify는 모니터링 대상에 대한 파일 설명자를 열 필요가 없으며 모니터링 대상이 이동식 미디어에 있는 경우 미디어에서 파일 시스템을 마운트 해제한 후 모니터링 대상에 해당하는 watch가 자동으로 삭제되고 umount 이벤트가 발생합니다. .

2. Inotify는 파일과 디렉터리를 모두 모니터링할 수 있습니다.

3. Inotify는 SIGIO 대신 시스템 호출을 사용하여 파일 시스템 이벤트를 알립니다.

4. Inotify는 파일 설명자를 인터페이스로 사용하므로 일반적인 파일 I/O 작업 선택 및 폴링을 사용하여 파일 시스템 변경 사항을 모니터링할 수 있습니다.

Inotify는 다음을 포함한 파일 시스템 이벤트를 모니터링할 수 있습니다.

IN_ACCESS, 즉 파일이
IN_MODIFY에 액세스되고, 파일이
IN_ATTRIB에 기록되고, chmod, chown, touch 등과 같은 파일 속성이 수정됩니다.
IN_CLOSE_WRITE, 쓰기 가능한 파일이 닫힙니다.
IN_CLOSE_NOWRITE, 쓰기 불가능한 파일이 닫힙니다.
IN_OPEN, 파일이 열립니다.
IN_MOVED_FROM, 파일이 이동됩니다. , 예: mv
IN_MOVED_TO, 파일 이동(예: mv, cp
IN_CREATE, 새 파일 생성
IN_DELETE, 파일이 삭제됨, 예: rm
IN_DELETE_SELF, 자체 삭제, 즉, 실행 파일이 실행될 때 스스로 삭제됩니다.
IN_MOVE_SELF, 자체 이동, 즉 실행 파일이 실행 중에 스스로 이동합니다.
IN_UNMOUNT, 호스트 파일 시스템이 마운트 해제됩니다.
IN_CLOSE, 파일이 닫힙니다. (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)
IN_MOVE와 동일하며 파일은 Move이며 (IN_MOVED_FROM | IN_MOVED_TO)

과 동일합니다. 참고: 위에 언급된 파일에는 디렉터리도 포함됩니다.

2. 사용자 인터페이스

사용자 모드에서는 반환된 파일 설명자에 대한 세 가지 시스템 호출과 파일 I/작업을 통해 inotify를 사용합니다. inotify 인스턴스를 생성하려면:

int fd = inotify_init ();
로그인 후 복사

각 inotify 인스턴스는 독립적으로 정렬된 대기열에 해당합니다.

파일 시스템 변경 이벤트는 시계라는 개체 관리입니다. 각 시계는 튜플(대상, 이벤트 마스크)일 수 있으며 이벤트 마스크는 inotify 이벤트를 나타냅니다. 애플리케이션이 주의를 기울이고자 하는 각 비트는 inotify 이벤트에 해당합니다. Watch 개체는 Watch 설명자에 의해 참조되며 Watch는 파일이나 디렉터리의 경로 이름으로 추가됩니다. 디렉터리 감시는 해당 디렉터리의 모든 파일에서 발생한 이벤트를 반환합니다.

다음 함수는 watch를 추가하는 데 사용됩니다.

int wd = inotify_add_watch (fd, path, mask);
로그인 후 복사

fd는 inotify_init()에서 반환된 파일 설명자이고, path는 watch의 경로 이름입니다. 모니터링 대상(예: 파일 이름 또는 디렉터리 이름), 마스크는 이벤트 마스크이며 각 비트로 표시되는 이벤트는 헤더 파일 linux/inotify.h에 정의되어 있습니다. 알림을 받으려는 inotify 이벤트를 변경하여 동일한 방식으로 이벤트 마스크를 수정할 수 있습니다. Wd는 시계 설명자입니다.
다음 함수는 시계를 삭제하는 데 사용됩니다.

int ret = inotify_rm_watch (fd, wd);
로그인 후 복사

fd는 inotify_init()가 반환한 파일 설명자이고, wd는 inotify_add_watch()가 반환한 시계 설명자입니다. Ret는 함수의 반환 값입니다.
파일 이벤트는 inotify_init()에 의해 반환된 파일 설명자를 통해 읽은 일반적인 파일 읽기 함수를 사용하여 얻은 inotify_event 구조로 표시됩니다.

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&#39;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] == &#39;\0&#39;) 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;
    }
  }
}
로그인 후 복사


该程序将监视发生在当前目录下的文件 tmp_file 与当前目录下的目录 tmp_dir 上的所有文件系统事件, 同时它也将监视发生在文件 /mnt/sda3/windows_file 上的文件系统事件,注意,/mnt/sda3 是 SATA 硬盘分区 3 的挂接点。

细心的读者可能注意到,该程序首部使用 _syscallN 来声明 inotify 系统调用,原因是这些系统调用是在最新的稳定内核 2.6.13 中引入的,glibc 并没有实现这些系统调用的库函数版本,因此,为了能在程序中使用这些系统调用,必须通过 _syscallN 来声明这些新的系统,其中的 N 为要声明的系统调用实际的参数数。还有需要注意的地方是系统的头文件必须与被启动的内核匹配,为了让上面的程序能够成功编译,必须让 2.6.13 的内核头文件(包括 include/linux/*, include/asm/* 和 include/asm-generic/*)在头文件搜索路径内,并且是第一优先搜索的头文件路径,因为 _syscallN 需要用到这些头文件中的 linux/unistd.h 和 asm/unistd.h,它们包含了 inotify 的三个系统调用的系统调用号 __NR_inotify_init、__NR_inotify_add_watch 和 __NR_inotify_rm_watch。

因此,要想成功编译此程序,只要把用户编译好的内核的头文件拷贝到该程序所在的路径,并使用如下命令编译即可:

$gcc -o inotify_example -I. inotify_example.c
로그인 후 복사

注意:当前目录下应当包含 linux、asm 和 asm-generic 三个已编译好的 2.6.13 内核的有文件目录,asm 是一个链接,因此拷贝 asm 头文件的时候需要拷贝 asm 与 asm-ARCH(对于 x86 平台应当是 asm-i386)。 然后,为了运行该程序,需要在当前目录下创建文件 tmp_file 和目录 tmp_dir,对于/mnt/sda3/windows_file 文件,用户需要依自己的实际情况而定,可能是/mnt/dosc/windows_file,即 /mnt/dosc 是一个 FAT32 的 windows 硬盘,因此用户在编译该程序时需要根据自己的实际情况来修改 /mnt/sda3。Windows_file 是在被 mount 硬盘上创建的一个文件,为了运行该程序,它必须被创建。
以下是作者在 redhat 9.0 上运行此程序得到的一些结果:

当运行此程序的时候在另一个虚拟终端执行 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
로그인 후 복사

以上事件清楚地说明了 cat 指令执行了文件 open 和 close 操作,当然 open 和 close操作都属于 access 操作,任何对文件的操作都是 access 操作。

此外,运行 vi ./tmp_file,发现 vi实际在编辑文件时复制了一个副本,在未保存之前是对副本进行操作。 运行 vi ./tmp_file, 修改并保存退出时,发现 vi 实际在保存修改时删除了最初的文件并把那个副本文件名更改为最初的文件的名称。注意,事件"File was ignored"表示系统把该文件对应的 watch 从 inotify 实例的 watch 列表中删除,因为文件已经被删除。 读者可以自己分别执行命令:echo "abc" > ./tmp_file 、rm -f tmp_file、 ls tmp_dir、 cd tmp_dir;touch c.txt、 rm c.txt 、 umount /mnt/sda3(实际用户需要使用自己当时的 mount 点路径名),然后分析一下结果。Umount 触发两个事件,一个表示文件已经被删除或不在存在,另一个表示该文件的 watch被从 watch 列表中删除。

읽어주셔서 감사합니다. 모든 사람에게 도움이 되기를 바랍니다. 이 사이트를 지원해 주셔서 감사합니다!

리눅스 INotif 메커니즘에 대한 더 자세한 설명과 예제 코드 관련 글은 PHP 중국어 홈페이지를 참고해주세요!


관련 라벨:
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿