1. 程式人生 > >epoll模型新增inotify事件的程式碼實現

epoll模型新增inotify事件的程式碼實現

#include <sys/inotify.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <string.h>
struct inotify_data_type{
    int fd;
    char self_type[16];
};

int main(int argc,char* argv[]){
    if(argc < 2) {
        return 1;
    }
    int inotify_fd = inotify_init();
    if(inotify_fd < 0){
        printf("Create inotify descriptor failed.");
        return 1;
    }
    int *wd = malloc(sizeof(int)*(argc-1));
    int i;
    for(i = 1; i < argc; i++){
        wd[i-1] = inotify_add_watch(inotify_fd,argv[i],IN_CLOSE_WRITE);
        if(wd[i-1] < 0){
            printf("Could not watch dirctory : %s");
            return 1;
        }
    }
    int nb = 1;
    ioctl(inotify_fd,FIONBIO,&nb);

    int epoll_fd = epoll_create(1024);
    if(epoll_fd < 0){
        printf("Create epoll descriptor failed");
        return 1;
    }

    struct inotify_data_type inotify_data;
    inotify_data.fd = inotify_fd;
    strcpy(inotify_data.self_type,"inotify");


    struct epoll_event inotify_event;
    int option = EPOLL_CTL_ADD;
    inotify_event.events = EPOLLIN|EPOLLET;
    inotify_event.data.ptr = &inotify_data;

    int result = epoll_ctl(epoll_fd, option, inotify_fd, &inotify_event);
    if(result < 0){
        printf("Could not add Inotify event in EPOLL");
        return 1;
    }

    int running = 1;
    struct epoll_event event_list[10];
    while(running){
        int events_num = epoll_wait(epoll_fd, event_list,10,0);
        if(events_num < 0){
            printf("Epoll_wait failed!");
            return 1;
        }

        if(events_num > 0){
        //  int i = 0;
            for(i = 0; i < events_num; i++){
                struct inotify_data_type *inotify_data_backup = event_list[i].data.ptr;
                if(strcmp(inotify_data_backup->self_type,"inotify") == 0){
                    int revents = event_list[i].events;
                    if(revents & (EPOLLERR|EPOLLHUP)){
                        continue;
                    }
                    if(revents & EPOLLIN){
                        char inotify_event_buf[1024];
                        bzero(inotify_event_buf,1024);
                        int length = read(inotify_data_backup->fd,inotify_event_buf,1024);
                        //理論上,下面的程式碼不需要使用迴圈
                        char *tmp;
                        int tmp_len;
                        for(tmp = inotify_event_buf,tmp_len = 0; (tmp-inotify_event_buf) < length; tmp += tmp_
len){
                            struct inotify_event *iev = (struct inotify_event*)tmp;
                            int j = 0;
                            for(j = 0; j < argc-1; j++){
                                if(wd[j] == iev->wd){
                                    if(iev->mask & IN_CLOSE_WRITE){
                                        printf("The inotify event referred to File=%s, whose length=%d\n",iev-
>name,iev->len);//顯示觸發事件的檔名
                                    }
                                }
                            }
                            tmp_len = sizeof(struct inotify_event)+iev->len;
                        }
                    }

                }
            }

        }

    }
close(epoll_fd);

}

1. inotify 簡介

inotify是linux提供的一款監視檔案系統的工具,其允許監控程式開啟一個獨立檔案描述符,針對事件集監控一個或者多個檔案,例如開啟、關閉、移動/重新命名、刪除、建立或者改變屬性。由於其實用檔案描述符,因此可以和select和epoll函式等結合使用,上面的程式碼就是一個例子。

2.介面說明

int inotify_init();
建立一個inotify例項的系統呼叫,並返回一個指向該例項的檔案描述符。
int inotify_add_watch(int fd , char *path, uint32_t mask);
增加對檔案或者目錄的監控。檔案系統的變化一個叫watches的物件管理,每個watch是一個二元組(目標,事件掩碼)。

fd:inotify 檔案描述符

path:要監控的檔案或者目錄的路徑

mask:事件掩碼,具體的操作根據掩碼不同而不同,讓我們看一下

/*  events suitable for MASK parameter of INOTIFY_ADD_WATCH.  */
#define IN_ACCESS    0x00000001 /* File was accessed.  */
#define IN_MODIFY    0x00000002 /* File was modified.  */
#define IN_ATTRIB    0x00000004 /* Metadata changed.  */
#define IN_CLOSE_WRITE   0x00000008 /* Writtable file was closed.  */
#define IN_CLOSE_NOWRITE 0x00000010 /* Unwrittable file closed.  */
#define IN_CLOSE     (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE) /* Close.  */
#define IN_OPEN      0x00000020 /* File was opened.  */
#define IN_MOVED_FROM    0x00000040 /* File was moved from X.  */
#define IN_MOVED_TO      0x00000080 /* File was moved to Y.  */
#define IN_MOVE      (IN_MOVED_FROM | IN_MOVED_TO) /* Moves.  */
#define IN_CREATE    0x00000100 /* Subfile was created.  */
#define IN_DELETE    0x00000200 /* Subfile was deleted.  */
#define IN_DELETE_SELF   0x00000400 /* Self was deleted.  */
#define IN_MOVE_SELF     0x00000800 /* Self was moved.  */

/* Events sent by the kernel.  */
#define IN_UNMOUNT   0x00002000 /* Backing fs was unmounted.  */
#define IN_Q_OVERFLOW    0x00004000 /* Event queued overflowed.  */
#define IN_IGNORED   0x00008000 /* File was ignored.  */

/* Helper events.  */
#define IN_CLOSE     (IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)    /* Close.  */
#define IN_MOVE      (IN_MOVED_FROM | IN_MOVED_TO)      /* Moves.  */

/* Special flags.  */
#define IN_ONLYDIR   0x01000000 /* Only watch the path if it is a
                       directory.  */
#define IN_DONT_FOLLOW   0x02000000 /* Do not follow a sym link.  */
#define IN_MASK_ADD  0x20000000 /* Add to the mask of an already
                       existing watch.  */
#define IN_ISDIR     0x40000000 /* Event occurred against dir.  */
#define IN_ONESHOT   0x80000000 /* Only send event once.  */

/* All events which a program can wait on.  */
#define IN_ALL_EVENTS    (IN_ACCESS | IN_MODIFY | IN_ATTRIB | IN_CLOSE_WRITE  \
              | IN_CLOSE_NOWRITE | IN_OPEN | IN_MOVED_FROM        \
              | IN_MOVED_TO | IN_CREATE | IN_DELETE           \
              | IN_DELETE_SELF | IN_MOVE_SELF)

int inotify_rm_watch(fd,wd);
刪除一個watch

fd:inotify_init的返回

wd:inotify_add_watch的返回

3. inotify的事件結構

struct inotify_event{
   int wd;            //watch描述符
   uint32_t mask;     //watch掩碼
   uint32_t cookie;   //用於同步兩個事件的cookie
   unit32_t len;      //name的長度
   char name[0] //name指標

};
wd 可用於判斷事件是否屬於我們所監聽的檔案系統物件,mask返回事件型別。當監控物件是目錄,且事件與目錄內的檔案有關時才會使用到name儲存觸發事件的檔名。

我們可以通過read一次獲得多個事件,具體返回事件多少要看提供多達的buf
size_t len = read(fd,buf,BUFLEN);

4.核心實現原理(轉)

系統為每個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 */
};
該結構在使用者呼叫inotify_init時建立,當關閉對應的描述符時被釋放。

同時,系統為每一個watch例項分配一個inotify_watch結構,其在呼叫inotify_add_watch時建立,並在呼叫inotify_rm_watch或者close(fd)時銷燬:

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 指向對應的inotify_device例項

inode 標明要監控的inode

wd 分配給watch的描述符

mask事件掩碼

系統為了支援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:

A. 判斷inode是否被監控

B. 遍歷inotify_watch列表,看是否是某個watch所關係的事件,如果是 inotify_dev_queue_event,不是則返回

而 inotify_dev_queue_event主要做一下工作:

A. 是否重複事件,是的話丟棄,不是繼續

B. 判斷inotify例項即inotify_device的事件佇列是否溢位,溢位則返回一個當前的檔案操作事件,繼續

C. 由kernel_event函式構建inotify_kernel_event結構,然後把結構插入對應inotify_device的events事件列表,喚醒wq指向的等待佇列。

整個inotify的核心工作流程就到這裡了,後面我們通過read方式讀取相應的inotify_event就可以進行進一步的操作了。