日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
監(jiān)聽(tīng)風(fēng)云|Inotify實(shí)現(xiàn)原理

本文轉(zhuǎn)載自微信公眾號(hào)「Linux內(nèi)核那些事」,作者songsong001。轉(zhuǎn)載本文請(qǐng)聯(lián)系Linux內(nèi)核那些事公眾號(hào)。

創(chuàng)新互聯(lián)從2013年開(kāi)始,公司以成都網(wǎng)站設(shè)計(jì)、網(wǎng)站建設(shè)、系統(tǒng)開(kāi)發(fā)、網(wǎng)絡(luò)推廣、文化傳媒、企業(yè)宣傳、平面廣告設(shè)計(jì)等為主要業(yè)務(wù),適用行業(yè)近百種。服務(wù)企業(yè)客戶(hù)1000多家,涉及國(guó)內(nèi)多個(gè)省份客戶(hù)。擁有多年網(wǎng)站建設(shè)開(kāi)發(fā)經(jīng)驗(yàn)。為企業(yè)提供專(zhuān)業(yè)的網(wǎng)站建設(shè)、創(chuàng)意設(shè)計(jì)、宣傳推廣等服務(wù)。 通過(guò)專(zhuān)業(yè)的設(shè)計(jì)、獨(dú)特的風(fēng)格,為不同客戶(hù)提供各種風(fēng)格的特色服務(wù)。

重要的數(shù)據(jù)結(jié)構(gòu)

魯迅先生說(shuō)過(guò):程序 = 數(shù)據(jù)結(jié)構(gòu) + 算法

想想如果讓我們來(lái)設(shè)計(jì) inotify 應(yīng)該如何實(shí)現(xiàn)呢?下面來(lái)分析一下:

  • 我們知道,inotify 是用來(lái)監(jiān)控文件或目錄的變動(dòng)事件,所以應(yīng)該定義一個(gè)對(duì)象來(lái)存儲(chǔ)被監(jiān)聽(tīng)的文件或目錄列表和它們所發(fā)生的事件列表(在內(nèi)核中定義了 inotify_device 對(duì)象來(lái)存儲(chǔ)被監(jiān)聽(tīng)的文件列表和事件列表)。
  • 另外,當(dāng)對(duì)被監(jiān)聽(tīng)的文件或目錄進(jìn)行讀寫(xiě)操作時(shí)會(huì)觸發(fā)相應(yīng)的事件產(chǎn)生。所以,應(yīng)該在讀寫(xiě)操作相關(guān)的系統(tǒng)調(diào)用中嵌入產(chǎn)生事件的動(dòng)作(在內(nèi)核中由 inotify_dev_queue_event 函數(shù)產(chǎn)生事件)。

在介紹 inotify 的實(shí)現(xiàn)前,我們先來(lái)了解下其原理。inotify 的原理如下:

當(dāng)用戶(hù)調(diào)用 read 或者 write 等系統(tǒng)調(diào)用對(duì)文件進(jìn)行讀寫(xiě)操作時(shí),內(nèi)核會(huì)把事件保存到 inotify_device 對(duì)象的事件隊(duì)列中,然后喚醒等待 inotify 事件的進(jìn)程。正所謂一圖勝千言,所以我們通過(guò)下圖來(lái)描述此過(guò)程:

從上圖可知,當(dāng)應(yīng)用程序調(diào)用 read 函數(shù)讀取文件的內(nèi)容時(shí),最終會(huì)調(diào)用 inotify_dev_queue_event 函數(shù)來(lái)觸發(fā)事件,調(diào)用棧如下:

 
 
 
  1. read() 
  2. └→ sys_read() 
  3.    └→ vfs_read() 
  4.       └→ fsnotify_access() 
  5.          └→ inotify_inode_queue_event() 
  6.             └→ inotify_dev_queue_event() 

inotify_dev_queue_event 函數(shù)主要完成兩個(gè)工作:

  • 創(chuàng)建一個(gè)表示事件的 inotify_kernel_event 對(duì)象,并且把其插入到 inotify_device 對(duì)象的 events 列表中。
  • 喚醒正在等待 inotify 發(fā)生事件的進(jìn)程,等待的進(jìn)程放置在 inotify_device 對(duì)象的 wq 字段中。

上面主要涉及到兩個(gè)對(duì)象,inotify_device 和 inotify_kernel_event,我們先來(lái)介紹一下這兩個(gè)對(duì)象的作用。

  • inotify_device:內(nèi)核使用此對(duì)象來(lái)描述一個(gè) inotify,是 inotify 的核心對(duì)象。
  • intoify_kernel_event:內(nèi)核使用此對(duì)象來(lái)描述一個(gè)事件。

我們來(lái)看看這兩個(gè)對(duì)象的定義。

1. inotify_device對(duì)象

內(nèi)核使用 inotify_device 來(lái)管理 inotify 監(jiān)聽(tīng)的對(duì)象和發(fā)生的事件,其定義如下:

 
 
 
  1. 1struct inotify_device { 
  2. 2    wait_queue_head_t       wq; 
  3. 3    ... 
  4. 4    struct list_head        events; 
  5. 5    ... 
  6. 6    struct inotify_handle   *ih; 
  7. 7    unsigned int            event_count; 
  8. 8    unsigned int            max_events; 
  9. 9}; 

下面我們介紹一下各個(gè)字段的作用:

  • wq:正在等待當(dāng)前 inotify 發(fā)生事件的進(jìn)程列表。
  • events:保存由 inotify 監(jiān)聽(tīng)的文件或目錄所發(fā)生的事件。
  • ih:內(nèi)核用來(lái)存儲(chǔ) inotify 監(jiān)聽(tīng)的文件或目錄,下面會(huì)介紹。
  • event_count:inotify 監(jiān)聽(tīng)的文件或目錄所發(fā)生的事件數(shù)量。
  • max_events:inotify 能夠保存最大的事件數(shù)量。

下圖描述了 inotify_device 對(duì)象中兩個(gè)比較重要的隊(duì)列(等待隊(duì)列 和 事件隊(duì)列):

當(dāng)事件隊(duì)列中有數(shù)據(jù)時(shí),就可以通過(guò)調(diào)用 read 函數(shù)來(lái)讀取這些事件。

2. inotify_kernel_event對(duì)象

內(nèi)核使用 inotify_kernel_event 對(duì)象來(lái)存儲(chǔ)一個(gè)事件,其定義如下:

 
 
 
  1. struct inotify_kernel_event { 
  2.    struct inotify_event    event; 
  3.     struct list_head        list; 
  4.     char                    *name; 
  5. }; 

可以看出,inotify_kernel_event 對(duì)象只是對(duì) inotify_event 對(duì)象進(jìn)行擴(kuò)展而已,而我們?cè)凇侗O(jiān)聽(tīng)風(fēng)云 - inotify介紹》一文中已經(jīng)介紹過(guò) inotify_event 對(duì)象。

inotify_kernel_event 對(duì)象在 inotify_event 對(duì)象的基礎(chǔ)上增加了 list 字段和 name 字段:

  • list:用于把所有由 inotify 監(jiān)聽(tīng)的文件或目錄所發(fā)生的事件連接起來(lái),
  • name:用于記錄發(fā)生事件的文件名或目錄名。

3. inotify_handle對(duì)象

在 inotify_device 對(duì)象中,有個(gè)類(lèi)型為 inotify_handle 的字段 ih,這個(gè)字段主要用來(lái)存儲(chǔ) inotify 監(jiān)聽(tīng)的文件或目錄。我們來(lái)看看 inotify_handle 對(duì)象的定義:

 
 
 
  1. struct inotify_handle { 
  2.     struct idr          idr; 
  3.     ... 
  4.     struct list_head    watches; 
  5.     ... 
  6.     const struct inotify_operations *in_ops; 
  7. }; 

下面來(lái)介紹一下 inotify_handle 對(duì)象的各個(gè)字段作用:

  • idr:ID生成器,用于生成被監(jiān)聽(tīng)對(duì)象(文件或目錄)的ID。
  • watches:inotify 監(jiān)聽(tīng)的對(duì)象(文件或目錄)列表。
  • in_ops:當(dāng)事件發(fā)生時(shí),被 inotify 回調(diào)的函數(shù)列表。

4. inotify_watch對(duì)象

內(nèi)核使用 inotify_handle 來(lái)存儲(chǔ)被監(jiān)聽(tīng)的對(duì)象列表,那么被監(jiān)聽(tīng)對(duì)象是個(gè)什么東西呢?內(nèi)核中使用 inotify_watch 對(duì)象來(lái)表示一個(gè)被監(jiān)聽(tīng)的對(duì)象。其定義如下:

 
 
 
  1. struct inotify_watch { 
  2.     struct list_head        h_list; 
  3.     struct list_head        i_list; 
  4.     ... 
  5.     struct inotify_handle   *ih; 
  6.     struct inode            *inode; 
  7.     __s32                   wd; 
  8.     __u32                   mask; 
  9. }; 

下面介紹一下 inotify_watch 對(duì)象各個(gè)字段的作用:

  • h_list:用于把屬于同一個(gè) inotify 監(jiān)聽(tīng)的對(duì)象連接起來(lái)。
  • i_list:由于同一個(gè)文件或目錄可以被多個(gè) inotify 監(jiān)聽(tīng),所以使用此字段來(lái)把所有監(jiān)聽(tīng)同一個(gè)文件的 inotify_handle 對(duì)象連接起來(lái)。
  • ih:指向其所屬的 inotify_handle 對(duì)象。
  • inode:由于在 Linux 內(nèi)核中,每個(gè)文件或目錄都由一個(gè) inode 對(duì)象來(lái)描述,這個(gè)字段就是指向被監(jiān)聽(tīng)的文件或目錄的 inode 對(duì)象。
  • wd:被監(jiān)聽(tīng)對(duì)象的ID(或稱(chēng)為描述符)。
  • mask:被監(jiān)聽(tīng)的事件類(lèi)型(在《監(jiān)聽(tīng)風(fēng)云 - inotify介紹》一文中已經(jīng)介紹)。

現(xiàn)在,我們通過(guò)下圖來(lái)描述一下 inotify_device、inotify_handle 和 inotify_watch 三者的關(guān)系:

inotify功能實(shí)現(xiàn)

上面我們把 inotify 功能涉及的所有數(shù)據(jù)結(jié)構(gòu)都介紹了,有上面的基礎(chǔ),現(xiàn)在我們可以開(kāi)始分析 inotify 功能的實(shí)現(xiàn)了。

1. inotify_init 函數(shù)

在《監(jiān)聽(tīng)風(fēng)云 - inotify介紹》一文中介紹過(guò),要使用 inotify 功能,首先要調(diào)用 inotify_init 函數(shù)創(chuàng)建一個(gè) inotify 的句柄,而 inotify_init 函數(shù)最終會(huì)調(diào)用內(nèi)核函數(shù) sys_inotify_init。我們來(lái)分析一下 sys_inotify_init 的實(shí)現(xiàn):

 
 
 
  1. long sys_inotify_init(void) 
  2.     struct inotify_device *dev; 
  3.     struct inotify_handle *ih; 
  4.     struct user_struct *user; 
  5.     struct file *filp; 
  6.     int fd, ret; 
  7.  
  8.     // 1. 獲取一個(gè)沒(méi)用被占用的文件描述符 
  9.     fd = get_unused_fd(); 
  10.     ... 
  11.     // 2. 獲取一個(gè)文件對(duì)象 
  12.     filp = get_empty_filp(); 
  13.     ... 
  14.     // 3. 創(chuàng)建一個(gè) inotify_device 對(duì)象 
  15.     dev = kmalloc(sizeof(struct inotify_device), GFP_KERNEL); 
  16.     ... 
  17.     // 4. 創(chuàng)建一個(gè) inotify_handle 對(duì)象 
  18.     ih = inotify_init(&inotify_user_ops); 
  19.     ... 
  20.     // 5. 把 inotify_handle 對(duì)象與 inotify_device 對(duì)象進(jìn)行綁定 
  21.     dev->ih = ih; 
  22.     // 6. 設(shè)置文件對(duì)象的操作函數(shù)列表為:inotify_fops 
  23.     filp->f_op = &inotify_fops; 
  24.     ... 
  25.     // 7. 將 inotify_device 對(duì)象綁定到文件對(duì)象的 private_data 字段中 
  26.     filp->private_data = dev; 
  27.     ... 
  28.     // 8. 把文件句柄與文件對(duì)象進(jìn)行映射 
  29.     fd_install(fd, filp); 
  30.  
  31.     return fd; 

sys_inotify_init 函數(shù)主要完成以下幾個(gè)工作:

  • 調(diào)用 get_unused_fd 函數(shù)從進(jìn)程中獲取一個(gè)沒(méi)被使用的文件描述符(句柄)。
  • 調(diào)用 get_empty_filp 獲取一個(gè)文件對(duì)象。
  • 調(diào)用 kmalloc 函數(shù)申請(qǐng)一個(gè) inotify_device 對(duì)象。
  • 調(diào)用 inotify_init 函數(shù)創(chuàng)建并初始化一個(gè) inotify_handle 對(duì)象。
  • 把 inotify_handle 對(duì)象與 inotify_device 對(duì)象進(jìn)行綁定。
  • 設(shè)置文件對(duì)象的操作函數(shù)列表為:inotify_fops,主要提供 read 和 poll 等接口的實(shí)現(xiàn)。
  • 將 inotify_device 對(duì)象綁定到文件對(duì)象的 private_data 字段中。
  • 把文件描述符與文件對(duì)象進(jìn)行映射。
  • 返回文件描述符給應(yīng)用層。

從上面的實(shí)現(xiàn)可以看出,sys_inotify_init 函數(shù)主要是創(chuàng)建 inotify_device 對(duì)象和 inotify_handle 對(duì)象,并且將它們與文件對(duì)象關(guān)聯(lián)起來(lái)。

另外需要注意的是,在 sys_inotify_init 函數(shù)中,還把文件對(duì)象的操作函數(shù)集設(shè)置為 inotify_fops,主要提供了 read 和 poll 等接口的實(shí)現(xiàn),其定義如下:

 
 
 
  1. static const struct file_operations inotify_fops = { 
  2.     .poll           = inotify_poll, 
  3.     .read           = inotify_read, 
  4.     .release        = inotify_release, 
  5.     ... 
  6. }; 

所以,當(dāng)調(diào)用 read 函數(shù)讀取 inotify 的句柄時(shí),就會(huì)觸發(fā)調(diào)用 inotify_read 函數(shù)讀取 inotify 事件隊(duì)列中的事件。

2. inotify_add_watch 函數(shù)

當(dāng)調(diào)用 inotify_init 函數(shù)創(chuàng)建好 inotify 句柄后,就可以通過(guò)調(diào)用 inotify_add_watch 函數(shù)向 inotify 句柄添加要監(jiān)控的文件或目錄。inotify_add_watch 函數(shù)的實(shí)現(xiàn)如下:

 
 
 
  1. long sys_inotify_add_watch(int fd, const char __user *path, u32 mask) 
  2.     struct inode *inode; 
  3.     struct inotify_device *dev; 
  4.     struct nameidata nd; 
  5.     struct file *filp; 
  6.     int ret, fput_needed; 
  7.     unsigned flags = 0; 
  8.  
  9.     // 通過(guò)文件句柄獲取文件對(duì)象 
  10.     filp = fget_light(fd, &fput_needed); 
  11.     ... 
  12.     // 獲取文件或目錄對(duì)應(yīng)的 inode 對(duì)象 
  13.     ret = find_inode(path, &nd, flags); 
  14.     ... 
  15.     inode = nd.dentry->d_inode; 
  16.     // 從文件對(duì)象的 private_data 字段獲取對(duì)應(yīng)的 inotify_device 對(duì)象 
  17.     dev = filp->private_data; 
  18.     ... 
  19.     // 創(chuàng)建一個(gè)新的 inotify_watch 對(duì)象 
  20.     if (ret == -ENOENT) 
  21.         ret = create_watch(dev, inode, mask); 
  22.     ... 
  23.     return ret; 

sys_inotify_add_watch 函數(shù)主要完成以下幾個(gè)工作:

  • 調(diào)用 fget_light 函數(shù)獲取 inotify 句柄對(duì)應(yīng)的文件對(duì)象。
  • 調(diào)用 find_inode 函數(shù)獲取 path 路徑對(duì)應(yīng)的 inode 對(duì)象,也就是獲取要監(jiān)聽(tīng)的文件或目錄所對(duì)應(yīng)的 inode 對(duì)象。
  • 從 inotify 文件對(duì)象的 private_data 字段中,獲取對(duì)應(yīng)的 inotify_device 對(duì)象。
  • 調(diào)用 create_watch 函數(shù)創(chuàng)建一個(gè)新的 inotify_watch 對(duì)象,并且把這個(gè) inotify_watch 對(duì)象添加到 inotify_handle 對(duì)象的 watches 列表和 inode 對(duì)象的 inotify_watches 列表中。

事件通知

到了 inotify 最關(guān)鍵的部分,就是 inotify 的事件是怎么產(chǎn)生的。

在本文的第一部分中介紹過(guò),當(dāng)用戶(hù)調(diào)用 read 系統(tǒng)調(diào)用讀取文件內(nèi)容時(shí),最終會(huì)調(diào)用 inotify_dev_queue_event 函數(shù)來(lái)產(chǎn)生一個(gè)事件,我們先來(lái)回顧一下 read 系統(tǒng)調(diào)用的調(diào)用棧:

 
 
 
  1. read() 
  2. └→ sys_read() 
  3.    └→ vfs_read() 
  4.       └→ fsnotify_access() 
  5.          └→ inotify_inode_queue_event() 
  6.             └→ inotify_dev_queue_event() 

下面我們來(lái)分析一下 inotify_dev_queue_event 函數(shù)的實(shí)現(xiàn):

 
 
 
  1. static void 
  2. inotify_dev_queue_event(struct inotify_watch *w, u32 wd,   
  3.     u32 mask, u32 cookie, const char *name, struct inode *ignored) 
  4.     struct inotify_user_watch *watch; 
  5.     struct inotify_device *dev; 
  6.     struct inotify_kernel_event *kevent, *last; 
  7.  
  8.     watch = container_of(w, struct inotify_user_watch, wdata); 
  9.    dev = watch->dev; 
  10.    ... 
  11.    // 1. 申請(qǐng)一個(gè) inotify_kernel_event 事件對(duì)象 
  12.    if (unlikely(dev->event_count == dev->max_events)) 
  13.        kevent = kernel_event(-1, IN_Q_OVERFLOW, cookie, NULL); 
  14.    else 
  15.        kevent = kernel_event(wd, mask, cookie, name); 
  16.    ... 
  17.    // 2. 增加 inotify 事件隊(duì)列的計(jì)數(shù)器 
  18.    dev->event_count++; 
  19.    // 3. 增加 inotify 事件隊(duì)列所占用的內(nèi)存大小 
  20.    dev->queue_size += sizeof(struct inotify_event) + kevent->event.len; 
  21.  
  22.    // 4. 把事件對(duì)象添加到 inotify 的事件隊(duì)列中 
  23.    list_add_tail(&kevent->list, &dev->events); 
  24.  
  25.    // 5. 喚醒正在等待讀取事件的進(jìn)程 
  26.    wake_up_interruptible(&dev->wq); 
  27.    ... 

我們先來(lái)介紹一下 inotify_dev_queue_event 函數(shù)各個(gè)參數(shù)的意義:

  • w:被監(jiān)聽(tīng)對(duì)象,用于描述被監(jiān)聽(tīng)的文件或目錄。
  • wd:被監(jiān)聽(tīng)對(duì)象的ID。
  • mask:發(fā)生的事件類(lèi)型,可以參考《監(jiān)聽(tīng)風(fēng)云 - inotify介紹》一文。
  • cookie:比較少使用,忽略。
  • name:發(fā)生事件的文件或目錄名稱(chēng)。
  • ignored:發(fā)生事件的文件或目錄的 inode 對(duì)象,在本函數(shù)中沒(méi)有使用。

inotify_dev_queue_event 函數(shù)主要完成以下幾個(gè)工作:

  • 通過(guò)調(diào)用 kernel_event 函數(shù)申請(qǐng)一個(gè) inotify_kernel_event 事件對(duì)象。
  • 增加 inotify 事件隊(duì)列的計(jì)數(shù)器。
  • 增加 inotify 事件隊(duì)列所占用的內(nèi)存大小。
  • 把第一步創(chuàng)建的事件對(duì)象添加到 inotify 的事件隊(duì)列中。
  • 喚醒正在等待讀取事件的進(jìn)程(因?yàn)橐呀?jīng)有事件發(fā)生了)。

從上面的分析可以看出,inotify_dev_queue_event 函數(shù)只負(fù)責(zé)創(chuàng)建一個(gè)事件對(duì)象,并且添加到 inotify 的事件隊(duì)列中。但發(fā)生了什么事件是由哪個(gè)步驟指定的呢?

我們可以通過(guò)分析 read 系統(tǒng)調(diào)用的調(diào)用棧,會(huì)發(fā)現(xiàn)在 fsnotify_access 函數(shù)中指定了事件的類(lèi)型,我們來(lái)看看 fsnotify_access 函數(shù)的實(shí)現(xiàn):

 
 
 
  1. static inline void fsnotify_access(struct dentry *dentry) 
  2.     struct inode *inode = dentry->d_inode; 
  3.     u32 mask = IN_ACCESS; // 指定事件類(lèi)型為 IN_ACCESS 
  4.  
  5.     if (S_ISDIR(inode->i_mode)) 
  6.         mask |= IN_ISDIR; // 如果是目錄, 增加 IN_ISDIR 標(biāo)志 
  7.     ... 
  8.     // 創(chuàng)建事件 
  9.     inotify_inode_queue_event(inode, mask, 0, NULL, NULL);  

從上面的分析可知,當(dāng)發(fā)生讀事件時(shí),由 fsnotify_access 函數(shù)指定事件類(lèi)型為 IN_ACCESS。在 include/linux/fsnotify.h 文件中還實(shí)現(xiàn)了其他事件的觸發(fā)函數(shù),有興趣的可以自行查閱此文件 。

總結(jié)

inotify 的實(shí)現(xiàn)過(guò)程總結(jié)為以下兩點(diǎn):

當(dāng)用戶(hù)調(diào)用讀寫(xiě)、創(chuàng)建或刪除文件的系統(tǒng)調(diào)用時(shí),內(nèi)核會(huì)注入相應(yīng)的事件觸發(fā)函數(shù)來(lái)產(chǎn)生一個(gè)事件,并且添加到 inotify 的事件隊(duì)列中。

喚醒等待讀取事件的進(jìn)程,當(dāng)進(jìn)程被喚醒后,就可以通過(guò)調(diào)用 read 函數(shù)來(lái)讀取 inotify 事件隊(duì)列中的事件。


網(wǎng)站欄目:監(jiān)聽(tīng)風(fēng)云|Inotify實(shí)現(xiàn)原理
標(biāo)題URL:http://www.dlmjj.cn/article/dppohgi.html