inotify機制監控文件系統事件原理及使用


1. 基本描述

inotify提供了一種監控文件系統事件的機制,可以用來監控單個的文件以及目錄。當一個目錄被監控,inotify會返回該目錄以及該目錄下面文件的事件。

2. 原理以及使用

2.1 內核原理

inotify機制借用了內核里面的notify通知鏈技術,針對文件系統里面的使用主要是在inode結構體里面加入了相關的字段(內核版本3.10):

linux系統中每一個常規文件都有唯一的一個inode和它對應

struct inode {
       。。。
#ifdef CONFIG_FSNOTIFY
      __u32 i_fsnotify_mask; /* all events this inode cares about */  具體可能監控的事件,事件基本上是一些位段
      struct hlist_head i_fsnotify_marks;  /* 具體的鏈表,鏈表上可以掛入多個mask結構(事件) */
#endif
  。。。
}

對於每一個監控內核都有一個fsnotify_mark和它相對應

struct fsnotify_mark {
     __u32 mask; /* mask this mark is for */
     atomic_t refcnt; /* active things looking at this mark */
     struct fsnotify_group *group; /* group this mark is for */
     struct list_head g_list; /* list of marks by group->i_fsnotify_marks */
     spinlock_t lock; /* protect group and inode */
     union {
          struct fsnotify_inode_mark i;
          struct fsnotify_vfsmount_mark m;
     };
     __u32 ignored_mask; /* events types to ignore */
     unsigned int flags; /* vfsmount or inode mark? */
     struct list_head destroy_list;
     void (*free_mark)(struct fsnotify_mark *mark); /* called on final put+free */
};

每新建一個inotify實例,內核都會分配一個fsnotify_group 

struct fsnotify_group {
  atomic_t refcnt; /* 引用次數 */

  const struct fsnotify_ops *ops; /* 操作函數指針結構體 */

  /* needed to send notification to userspace */
  struct list_head notification_list; /* 屬於這個group的需要發送到用戶控件的事件鏈表 */
  wait_queue_head_t notification_waitq; /* 讀事件阻塞時的等待隊列頭 */
  unsigned int q_len; /* events on the queue */

  unsigned int priority;

  struct list_head marks_list; /* 屬於這個group的fsnotify_mark結構體鏈表 */

  struct fasync_struct *fsn_fa; /* async notification */

  union {
    void *private;
#ifdef CONFIG_INOTIFY_USER
    struct inotify_group_private_data {
      spinlock_t idr_lock;
      struct idr idr;
      struct user_struct *user;
    } inotify_data;
#endif
  };
};

內核下面的函數執行流程為:

初始化新建一個notify實例,新建一個組

./fs/notify/inotify/inotify_user.c
inotify_init1
 group = inotify_new_group(inotify_max_queued_events);  // 新建一個組
    // 新建一個fd,名為  inotify,建立起 dentry anon_inode_inode(全局)結構
    ret = anon_inode_getfd("inotify", &inotify_fops, group, O_RDONLY | flags); 
        file = anon_inode_getfile(name, fops, priv, flags); // priv 為之前的  group
            file->private_data = priv; //  file->private_data = group

fd對應的file結構體為   file_operation為
const struct fsnotify_ops inotify_fsnotify_ops = {
        .handle_event = inotify_handle_event, // 處理事件函數
        .should_send_event = inotify_should_send_event,
        .free_group_priv = inotify_free_group_priv,
        .free_event_priv = inotify_free_event_priv,
        .freeing_mark = inotify_freeing_mark,
};

向系統中增加一個監控

SYSCALL_DEFINE3(inotify_add_watch, int, fd, const char __user *, pathname, u32, mask) /* create/update an inode mark */
    f = fdget(fd);
    ret = inotify_find_inode(pathname, &path, flags); 
    inode = path.dentry->d_inode;  // 找到需要監控的目錄或文件的 inode
    group = f.file->private_data;     // 獲取到  之前創建的group
    ret = inotify_update_watch(group, inode, mask);  // 更新新的mask參數
        /* try to update and existing watch with the new arg */
          ret = inotify_update_existing_watch(group, inode, arg);  // 
          /* no mark present, try to add a new one */
          if (ret == -ENOENT)
                ret = inotify_new_watch(group, inode, arg);   /* arg就是對應的mask(events), 沒有找到,則直接新建一個新的 */
    
ret = inotify_new_watch(group, inode, arg); /* arg就是對應的mask(events) */
    struct idr *idr = &group->inotify_data.idr;
    spinlock_t *idr_lock = &group->inotify_data.idr_lock;
    struct inotify_inode_mark tmp_i_mark = kmem_cache_alloc(inotify_inode_mark_cachep, GFP_KERNEL);
    
    fsnotify_init_mark(&tmp_i_mark->fsn_mark, inotify_free_mark);
    tmp_i_mark->fsn_mark.mask = mask;
    tmp_i_mark->wd = -1;              // 設置填充tmp_i_mark,基本的初始化
    
    ret = inotify_add_to_idr(idr, idr_lock, tmp_i_mark);   // 增加idr的一個條目
        i_mark->wd = ret(idr_alloc_cyclic(idr, i_mark, 1, 0, GFP_NOWAIT))
    
    ret = fsnotify_add_mark(&tmp_i_mark->fsn_mark, group, inode, NULL, 0);
        ret = fsnotify_add_mark_locked(mark, group, inode, mnt, allow_dups);
            mark->group = group;
            list_add(&mark->g_list, &group->marks_list);   // 把mark添加到group里面
            atomic_inc(&group->num_marks);
            fsnotify_get_mark(mark); /* for i_list and g_list */
            fsnotify_add_inode_mark(mark, group, inode, allow_dups); // 把mark和具體的監控inode掛鈎
                mark->i.inode = inode;
                hlist_add_head_rcu(&mark->i.i_list, &inode->i_fsnotify_marks); // 把mark掛入到具體的inode的i_fsnotify_marks列表上
            __fsnotify_update_child_dentry_flags(inode); // 如果是目錄,則更新目錄下面下面的
                if (!S_ISDIR(inode->i_mode))
                    return;
                if (watched)
                        child->d_flags |= DCACHE_FSNOTIFY_PARENT_WATCHED;  // 設置監控子目錄或文件
                    else
                        child->d_flags &= ~DCACHE_FSNOTIFY_PARENT_WATCHED;

    atomic_inc(&group->inotify_data.user->inotify_watches); // 增加用戶的watch號
    return ret(tmp_i_mark->wd)

當有事件被監控到了以后的執行流程

static int inotify_handle_event(struct fsnotify_group *group, struct fsnotify_mark *inode_mark, 
                                struct fsnotify_mark *vfsmount_mark,
                                struct fsnotify_event *event)
    /* 通過已有的fsnotify_mark 獲取到宿主結構 inotify_inode_mark*/
    struct inotify_inode_mark *i_mark = container_of(inode_mark, struct inotify_inode_mark, fsn_mark);
    wd = i_mark->wd;
    fsnotify_get_group(group);
    fsn_event_priv->group = group;
    event_priv->wd = wd;

    // 把事件掛入到group下面的notification_list,並且喚醒group下面的等待隊列notification_waitq;
    added_event = fsnotify_add_notify_event(group, event, fsn_event_priv, inotify_merge); 
        struct list_head *list = &group->notification_list;
        fsnotify_get_event(event);
        list_add_tail(&holder->event_list, list); // 把事件掛入到group->notification_list
        wake_up(&group->notification_waitq);      // 喚醒等待隊列
        kill_fasync(&group->fsn_fa, SIGIO, POLL_IN); // 發送信號

當監控的文件被刪除后具體的執行流程

static void destroy_inode(struct inode *inode)
    __destroy_inode(struct inode *inode)
        fsnotify_inode_delete(inode);
            __fsnotify_inode_delete(inode);
                fsnotify_clear_marks_by_inode(inode);   // fs/notify/inode_mark.c
                    struct fsnotify_mark *mark, *lmark;
                    struct hlist_node *n;
                    
                    LIST_HEAD(free_list);
                    // 遍歷inode->i_fsnotify_marks上掛的所有fsnotify_mark條目
                    hlist_for_each_entry_safe(mark, n, &inode->i_fsnotify_marks, i.i_list) {
                        // 掛入free_list鏈表,mark->i.free_i_list是在釋放mask時用的臨時的list_head
                        list_add(&mark->i.free_i_list, &free_list); 
                        // 把fsnotify_mark正式從監控文件inode的鏈表里面取出
                        hlist_del_init_rcu(&mark->i.i_list);
                    }
                    // 至此需要釋放的fsnotify_mark都已經掛載到了free_list 
                    list_for_each_entry_safe(mark, lmark, &free_list, i.free_i_list) {
                        struct fsnotify_group *group;
            
                        fsnotify_get_group(mark->group);
                        group = mark->group;
            
                        fsnotify_destroy_mark(mark, group);
                            fsnotify_destroy_mark_locked(mark, group);
                                fsnotify_destroy_inode_mark(mark);
                                    mark->i.inode = NULL; // 指向的inode為空,和具體的文件無任何關系
                                list_del_init(&mark->g_list); // 把mark從group(也就是創建的fd實例)出鏈表
                                list_add(&mark->destroy_list, &destroy_list);
                                wake_up(&destroy_waitq);
                                if (group->ops->freeing_mark)
                                    group->ops->freeing_mark(mark, group); // 釋放

                        fsnotify_put_mark(mark);
                            mark->free_mark(mark); // 釋放
                        fsnotify_put_group(group); // 如果group的引用為0,則釋放group
                    }

具體的關系結構圖如圖所示:

 圖1 inotify機制內核下面各結構體關系圖

2.2 用戶態接口原理

用戶態接口的基本思路是初始化一個具體的inotify實例,並設置實例以及具體監控哪些事件。當有具體的事件以后可以讀取對應的

結構體來解析。事件結構體為:

struct inotify_event {
     int wd; /* Watch descriptor */ 監控描述符
     uint32_t mask; /* Mask of events */  具體的事件(文件創建、刪除、屬性修改等)
     uint32_t cookie; /* Unique cookie associating related
                                events (for rename(2)) */
     uint32_t len; /* Size of name field */
     char name[]; /* Optional null-terminated name */  具體的文件名
};

具體可以監控的事件主要有:(注釋比較清晰了)

IN_ACCESS File was accessed (read) (*).
IN_ATTRIB Metadata changed, e.g., permissions, timestamps, extended attributes, link count (since Linux 2.6.25), UID, GID, etc. (*).
IN_CLOSE_WRITE File opened for writing was closed (*).
IN_CLOSE_NOWRITE File not opened for writing was closed (*).
IN_CREATE File/directory created in watched directory (*).
IN_DELETE File/directory deleted from watched directory (*).
IN_DELETE_SELF Watched file/directory was itself deleted.
IN_MODIFY File was modified (*).
IN_MOVE_SELF Watched file/directory was itself moved.
IN_MOVED_FROM Generated for the directory containing the old filename when a file is renamed (*).
IN_MOVED_TO Generated for the directory containing the new filename when a file is renamed (*).
IN_OPEN File was opened (*).

主要的用戶態接口函數:

int inotify_init(void);
int inotify_init1(int flags);

初始化一個inotify實例並且返回一個文件描述符,inotify實例和一個evnet隊列掛鈎,失敗返回-1。

int inotify_add_watch(int fd, const char *pathname, uint32_t mask);

添加一個watch對象,fd為具體的inotify實例描述符,pathname為監控的目錄或者文件,mask為具體的事件,成功返回非負整數,失敗返

回-1.

int inotify_rm_watch(int fd, int wd);

刪除一個watch,fd為inotify實例描述符,wd為watch描述符。成功返回0,失敗返回-1.

2.3 幾個相關的參數

1  /proc/sys/fs/inotify/max_user_instances   // 默認是128

   This specifies an upper limit on the number of inotify instances that can be created per real user ID.

   最多可以創建的實例,也就是最多可以消耗的fd個數。也是inotify_init這個函數調用的最大次數!

2  /proc/sys/fs/inotify/max_user_watches // 默認8192

    This specifies an upper limit on the number of watches that can be created per real user ID.

    可以具體監控的目錄(文件)的最大值, 也就是inotify_add_watch這個函數可以監控文件或目錄的最

    大值,該函數每執行一次,需要指定具體監控的目錄或者文件路徑。

3  /proc/sys/fs/inotify/max_queued_events // 默認是16384

    The value in this file is used when an application calls inotify_init(2) to set an upper limit on the number

    of events that can  be  queued  to the corresponding inotify instance.  Events in excess of this limit are

    dropped, but an IN_Q_OVERFLOW event is always generated.

    可以監控事件的最大值。

3. 測試程序

#include <sys/inotify.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/select.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/stat.h>

/*
 * struct inotify_event {
 *     int wd;          // Watch descriptor
 *     uint32_t mask;   // Mask of events
 *     uint32_t cookie; // Unique cookie associating related events (for rename(2))
 *     uint32_t len;    // Size of name field
 *     char name[];     // Optional null-terminated name
 * };
 *
 **/

int giNotifyFd;
int giaWds[20];
int giCount;

int watch_inotify_events(int fd)
{
    char event_buf[512];
    int ret;
    int event_pos = 0;
    int event_size = 0;
    struct inotify_event *event;
    time_t tNow;
    struct tm *pTimeNow;

    /* 讀事件是否發生,沒有發生就會阻塞 */
    ret = read(fd, event_buf, sizeof(event_buf));

    /* 如果read的返回值,小於inotify_event大小出現錯誤 */
    if (ret < (int)sizeof(struct inotify_event)) {
        printf("counld not get event!\n");
        return -1;
    }
    /* 因為read的返回值存在一個或者多個inotify_event對象,需要一個一個取出來處理 */
    while (ret >= (int)sizeof(struct inotify_event)) {
        event = (struct inotify_event*)(event_buf + event_pos);
        if (event->len) {
            /* 這三行可以注釋掉,之前出現過加了這三行執行出現core dump的問題 */
            // time(&tNow);    
            // pTimeNow = localtime(&tNow);
            // printf("Local time is:%s", asctime(pTimeNow));

            if(event->mask & IN_CREATE) {
                printf("watch is %d, create file: %s\n", event->wd, event->name);
            } else {
                printf("watch is %d, delete file: %s\n", event->wd, event->name);
            }
            if (event->mask & IN_ATTRIB) {
                printf("watch is %d, modify file attribute: %s\n", event->wd, event->name);
            }
        }
        /* event_size就是一個事件的真正大小 */
        event_size = sizeof(struct inotify_event) + event->len;
        ret -= event_size;
        /* 指向下一個事件 */
        event_pos += event_size;
    }
    return 0;
}

/* 遞歸處理目錄 */

void init_all_iwds(char *pcName)
{
    int iWd;
    struct stat tStat;
    DIR *pDir;
    struct dirent *ptDirent;
    char caNametmp[100]; // 存儲目錄名字

    iWd = inotify_add_watch(giNotifyFd, pcName, IN_CREATE|IN_DELETE|IN_ATTRIB|IN_MODIFY);
    giaWds[giCount] = iWd;
    giCount++;

    if (-1 == stat(pcName, &tStat)) {
        printf("stat %s error\n", pcName);
        return;
    }
    if (!S_ISDIR(tStat.st_mode))
        return;
    /* 處理子目錄 */
    pDir = opendir(pcName);
    if (NULL == pDir) {
        printf("opendir %s error\n", pcName);
        return;
    }

    // 循環讀目錄下面的子項
    while (NULL != (ptDirent = readdir(pDir))) {
        if ((0 == strcmp(ptDirent->d_name, ".")) || (0 == strcmp(ptDirent->d_name, "..")))
            continue; // 跳過當前目錄和上一級父目錄
        // printf("sub name is %s, d_type is 0x%x\n", ptDirent->d_name, ptDirent->d_type);
        sprintf(caNametmp, "%s/%s", pcName, ptDirent->d_name); //獲取子目錄或文件名字
        if (-1 == stat(caNametmp, &tStat)) {
            printf("stat error:%s\n", caNametmp); // 獲取統計數據
            return;
        }
        if (!S_ISDIR(tStat.st_mode)) //看是否是子目錄,原則只處理目錄
            continue;

        printf("sub absoulte dir name is %s\n", caNametmp);
        // iWd = inotify_add_watch(giNotifyFd, caNametmp, IN_CREATE|IN_DELETE|IN_ATTRIB|IN_MODIFY);
        init_all_iwds(caNametmp); //處理子目錄
    }

    // 關閉
    closedir(pDir);
}

int main(int argc, char** argv)
{
    int iNotifyRet;
    fd_set fds;
    int iaWd[10];
    int icount = 0;

    if (argc != 2) {
        printf("Usage: %s <dir>\n", argv[0]);
        return -1;
    }

    /* inotify初始化 */
    iNotifyFd = inotify_init();
    if (iNotifyFd == -1) {
        printf("inotify_init error!\n");
        return -1;
    }

    /* 遞歸處理具體的目錄,添加watch對象 */
    init_all_iwds(argv[1]);

    /* 處理事件 */
    while (1) {
        FD_ZERO(&fds);
        FD_SET(iNotifyFd, &fds);

        if (select(iNotifyFd+1, &fds, NULL, NULL, NULL) > 0) {
            iNotifyRet = watch_inotify_events(iNotifyFd);
            if (-1 == iNotifyRet)
                break;
        }
    }

    /* 刪除inotify的watch對象 */
    for (icount = 0; icount < giCount; icount++) {

        if (inotify_rm_watch(iNotifyFd, giaWds[icount ]) == -1) {
            printf("notify_rm_watch %d error!\n", giaWds[icount]);
            return -1;
        }

    }

    /* 關閉inotify描述符 */
    close(iNotifyFd);

    return 0;
}

文件命名為:inotify.c

編譯: gcc -o inotify inotify.c    生成可執行文件

執行: ./inotify輸出  Usage: ./inotify <dir>  提示需要輸入具體監控的目錄或者文件。

執行: ./inotify /home/work/0604_inotify/ &

        創建 aaa 文件,打印出  watch is 1, create file: aaa

        修改aaa文件屬性,打印出  watch is 1, modify file attribute: aaa


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM