05
30

強大的檔案控管機制-Inotify

在講到inotify這個機制前,可能要說一下它的使用限制,如果OS不是用LINUX,請按上一頁,再者如果Linux版本不是2.6.13以上,也請按上一頁,如果OS條件都符合以上兩項,請繼續往下看

什麼是inotify
我截錄一段inotify創作人Rober Love在Linux Journal上所說的一段話
inotify is a file change notification system—a kernel feature that allows applications to request the monitoring of a set of files against a list of events. When the event occurs, the application is notified. To be useful, such a feature must be simple to use, lightweight with little overhead and flexible. It should be easy to add new watches and painless to receive notification of events.
上面那段話簡單的來說,inotify可以幫助你即時的觀測檔案的改變,並回送通知給你,而inotify把檔案狀態的變化分為以下十類

1.IN_ACCESS:File was read from.
2.IN_MODIFY:File was written to.
3.IN_ATTRIB:File's metadata (inode or xattr) was changed.
4.IN_CLOSE_WRITE:File was closed (and was open for writing).
5.IN_CLOSE_NOWRITE:File was closed (and was not open for writing).
6.IN_OPEN:File was opened.
7.IN_MOVED_FROM:File was moved away from watch.
8.IN_MOVED_TO:File was moved to watch.
9.IN_DELETE File:was deleted.
10.IN_DELETE_SELF:The watch itself was deleted.

所以不論讀,寫,開啟,刪除,或attribute改變,都可以從inotify收到通知,使用這樣的機制好處是,你再也不用去polling你所有要監測的檔案了,這對embedded device來說是十分重要的功能,因為它可以簡化你所有監測的動作並且節省device的資源

而使用inotify的方法也非常簡單,只需呼叫幾個單純的API即可,比如說我想監測/home/jemic/test目錄下的所有檔案,就寫下面的code
int fd;
    
queue_t q;
    
int *wd;
    
    
q = queue_create (128);
    
fd=inotify_init();
    
wd = inotify_add_watch (fd, "/home/jemic/test/",IN_ATTRIB);
    
process_inotify_events (q,fd);
    
    
queue_destroy (q);
    
close(fd);
    
return 0;

從上面的程式碼可看到,一開始我們先initial一個queue儲存inotify傳遞給我們的資料,並呼叫inotify_init取得inotify的file descriptor,接著我們把取得的file descriptor,和要監測的目錄及監測項目當作參數傳遞給inotify_add_watch這個函式,接著我們只要用process_inotify_events觀查檔案是否有變化,結束程式前記得把queue毀掉和把inotify file descriptor關閉

在/proc/sys/fs/inotify下有一些選項可供使用者調整
max_queued_events:最大可queue的事件
max_user_instances:inotify的最大使用量
max_user_watches:可觀測多少檔案

底下是完整的demo code
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/file.h>
#include <sys/sysinfo.h>
#include <sys/epoll.h>
#include <signal.h>
#include <linux/inotify.h>
#include <net/if.h>
#include <netinet/in.h>
 
struct queue_struct
{
    
int capacity;
    
int front;
    
int rear;
    
int size;
    
void **array;
};
 
struct queue_struct;
typedef struct queue_struct *queue_t;
 
 
 
int queue_empty (queue_t q)
{
    
return q->size == 0;
}
 
int queue_full (queue_t q)
{
    
return q->size == q->capacity;
}
 
queue_t queue_create (int num_elements)
{
    
queue_t q;
 
    
q = malloc (sizeof (struct queue_struct));
 
    
if (q == NULL)
        
exit (-1);
 
    
q->array = malloc(sizeof (void *) * num_elements);
 
    
if (q->array == NULL)
        
exit (-1);
 
    
q->capacity = num_elements;
 
    
queue_make_empty (q);
 
    
return q;
}
 
void queue_destroy (queue_t q)
{
    
if (q != NULL)
    
{
        
if (q->array)
            
free (q->array);
 
        
free (q);
    
}
}
 
void queue_make_empty (queue_t q)
{
    
q->size = 0;
    
q->front = 1;
    
q->rear = 0;
}
 
static int next_position (int v, queue_t q)
{
    
if (++v == q->capacity) {
        
v = 0;
    
}
 
    
return v;
}
 
void queue_enqueue (void *d, queue_t q)
{
    
if (queue_full (q))
    
{
        
return;
    
}
 
    
q->size++;
    
q->rear = next_position (q->rear, q);
    
q->array[q->rear] = d;
}
 
void *queue_front (queue_t q)
{
    
if (!queue_empty(q))
        
return q->array [q->front];
 
    
return NULL;
}
 
void queue_dequeue (queue_t q)
{
    
if (!queue_empty (q))
    
{
        
q->size--;
        
q->front = next_position (q->front, q);
    
}
}
 
 
/* This method does the dirty work of determining what happened,
then allows us to act appropriately
*/

void handle_event (struct inotify_event *event)
{
    
/* If the event was associated with a filename, we will store it here */
    
char * cur_event_filename = NULL;
    
/* This is the watch descriptor the event occurred on */
    
int cur_event_wd = event->wd;
    
if (event->len)
    
{
        
cur_event_filename = event->name;
    
}
    
printf("FILENAME=%s\n", cur_event_filename);
    
printf("watch descriptor=%d\n",cur_event_wd);
    
printf("\n");
    
/* Perform event dependent handler routines */
    
/* The mask is the magic that tells us what file operation occurred */
    
switch (event->mask)
    
{
    
/* File was accessed */
    
case IN_ACCESS:
        
printf("ACCESS EVENT OCCURRED: File \"%s\" on WD #%i\n",
        
cur_event_filename, cur_event_wd);
    
break;
    
/* File was modified */
    
case IN_MODIFY:
        
printf("MODIFY EVENT OCCURRED: File \"%s\" on WD #%i\n",
        
cur_event_filename, cur_event_wd);
    
break;
    
/* File changed attributes */
    
case IN_ATTRIB:
        
printf("ATTRIB EVENT OCCURRED: File \"%s\" on WD #%i\n",
        
cur_event_filename, cur_event_wd);
    
break;
    
/* File was closed */
    
case IN_CLOSE:
        
printf("CLOSE EVENT OCCURRED: File \"%s\" on WD #%i\n",
    
cur_event_filename, cur_event_wd);
    
break;
    
/* File was opened */
    
case IN_OPEN:
    
printf("OPEN EVENT OCCURRED: File \"%s\" on WD #%i\n",
    
cur_event_filename, cur_event_wd);
    
break;
    
/* File was moved from X */
    
case IN_MOVED_FROM:
        
printf("MOVE_FROM EVENT OCCURRED: File \"%s\" on WD #%i\n",
        
cur_event_filename, cur_event_wd);
    
break;
    
/* File was moved to X */
    
case IN_MOVED_TO:
        
printf("MOVE_TO EVENT OCCURRED: File \"%s\" on WD #%i\n",
        
cur_event_filename, cur_event_wd);
    
break;
    
/* Watched entry was deleted */
    
case IN_DELETE_SELF:
        
printf("DELETE_SELF EVENT OCCURRED: File \"%s\" on WD #%i\n",
        
cur_event_filename, cur_event_wd);
    
break;
    
/* Backing FS was unmounted */
    
case IN_UNMOUNT:
        
printf("UNMOUNT EVENT OCCURRED: File \"%s\" on WD #%i\n",
        
cur_event_filename, cur_event_wd);
    
break;
    
/* Too many FS events were received without reading them
    some event notifications were potentially lost. */

    
case IN_Q_OVERFLOW:
        
printf("Warning: AN OVERFLOW EVENT OCCURRED: \n");
        
break;
    
case IN_IGNORED:
        
printf("IGNORED EVENT OCCURRED: \n");
    
break;
    
/* Some unknown message received */
    
default:
    
printf ("UNKNOWN EVENT OCCURRED for file \"%s\" on WD #%i\n",
    
cur_event_filename, cur_event_wd);
    
break;
    
}
}
 
void handle_events (queue_t q)
{
    
struct inotify_event *event;
    
while (!queue_empty (q))
    
{
        
event = queue_front (q);
        
queue_dequeue (q);
        
handle_event (event);
        
free (event);
    
}
}
 
int event_check (int fd)
{
    
struct timeval timeout;
    
int r;
    
fd_set rfds;
 
    
timeout.tv_sec = 4;
    
timeout.tv_usec = 0;
 
    
FD_ZERO(&rfds);
    
FD_SET(fd, &rfds);
 
    
r = select (fd+1, &rfds, NULL, NULL, &timeout);
 
    
return r;
}
 
int read_events (queue_t q, int fd)
{
    
char buffer[16384];
    
size_t buffer_i;
    
struct inotify_event *pevent, *event;
    
ssize_t r;
    
size_t event_size;
    
int count = 0;
 
    
r = read (fd, buffer, 16384);
 
    
if (r <= 0)
        
return r;
 
    
buffer_i = 0;
    
while (buffer_i < r)
    
{
        
/* Parse events and queue them ! */
        
pevent = (struct inotify_event *)&buffer[buffer_i];
        
event_size = sizeof(struct inotify_event) + pevent->len;
        
event = malloc(event_size);
        
memmove(event, pevent, event_size);
        
queue_enqueue(event, q);
        
buffer_i += event_size;
        
count++;
    
}
 
    
return count;
}
 
 
int process_inotify_events (queue_t q, int fd)
{
    
while (1)
    
{
        
if (!queue_empty(q))
        
{
            
handle_events (q);
        
}
        
if (event_check(fd) > 0)
        
{
            
int r;
 
            
r = read_events (q, fd);
 
            
if (r < 0)
                
break;
        
}
    
}
 
    
return 0;
}
 
 
int main()
{
    
int fd;
    
queue_t q;
    
int *wd;
    
    
q = queue_create (128);
    
fd=inotify_init();
    
wd = inotify_add_watch (fd, "/home/jemic/test/",IN_ATTRIB);
    
process_inotify_events (q,fd);
    
    
queue_destroy (q);
    
close(fd);
    
return 0;
}


標籤: linux
評論: 4 | 引用: 0 | 閱讀: 15848
  • 1 
門外漢 [ 2009-07-08 19:38 | 回覆 | 編輯 刪除 ]
安,你好,

逛你的blog學到不少東西,
我剛入門沒多久 ^^"
想請問一個問題:
如果沒有用
cat xxx >> /home/jemic/test/
echo xxx >> /home/jemic/test/
vim
等類的方法來修改被監控的檔案,是不是inotify就不會有所動作@@,
我拿你的code改了一個地方
wd = inotify_add_watch (fd, "/home/jemic/test/",IN_ATTRIB)
改成
wd = inotify_add_watch (fd, "/home/jemic/test/",IN_ALL_EVENTS)
( 看不太懂IIN_ATTRIB的意思,可以請幫我解說一下嗎?3Q )

我想請問,如果我想監控 /proc/acpi/battery/BAT1/state (筆電的電池容量)
因為筆電沒充電的話,大慨每隔幾秒,cat 這個檔案就會更動,
想知道可以用inotify這個方法來監控嗎?謝謝你
Joey [ 回復於2009-07-16 15:23 郵箱 | 編輯 刪除 ]
Hi
我記得in_attrib是檔案的屬性諸如日期,讀寫狀態...等

監控/proc下面的資料可能有點難,因為proc運作的原理是
"當你去讀proc裡面的資料時, 資料才會更新",proc資料並不是一有變動
就會自動更新,所以inotify用在proc似乎不太適合
門外漢 [ 2009-07-08 19:40 | 回覆 | 編輯 刪除 ]
你的code我compiler時有些地方有問題(warning),不知道是不是我的問題 ^^"
我改了下面的地方
1. int *wd -> int wd
2. void queue_make_empty (queue_t q) -> 這個function往上移一點

謝謝
Joey [ 回復於2009-07-16 15:24 郵箱 | 編輯 刪除 ]
我常被software leader k的原因都是太粗心...如果有warning,煩您多包函...
  • 1 
發表評論
暱 稱: 密 碼:
網 址: E - mail:
驗證碼: 驗證碼圖片 選 項:
頭 像:
內 容: