inotify提供了一種監控文件系統事件的機制,能夠用來監控單個的文件以及目錄。當一個目錄被監控,inotify會返回該目錄以及該目錄下面文件的事件。node
inotify機制借用了內核裏面的notify通知鏈技術,針對文件系統裏面的使用主要是在inode結構體裏面加入了相關的字段(內核版本3.10):linux
linux系統中每個常規文件都有惟一的一個inode和它對應cookie
struct inode {
。。。
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */ 具體可能監控的事件,事件基本上是一些位段
struct hlist_head i_fsnotify_marks; /* 具體的鏈表,鏈表上能夠掛入多個mask結構(事件) */
#endif
。。。
}app
對於每個監控內核都有一個fsnotify_mark和它相對應async
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; /* 引用次數 */ui
const struct fsnotify_ops *ops; /* 操做函數指針結構體 */this
/* 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 */atom
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機制內核下面各結構體關係圖
用戶態接口的基本思路是初始化一個具體的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.
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.
能夠監控事件的最大值。
#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