Inotify --2.6内核中的文件系统变化通知机制

转摘

Inotify --2.6内核中的文件系统变化通知机制

一、 引言

    众所周知,Linux 桌面系统与 MAC 或 Windows 相比有许多不如人意的地方,为了改善这种状况,开源社区提出用户态需要内核提供一些机制,以便用户态能够及时地得知内核或底层硬件设备发生了什么,从而能够更好地管理设备,给用户提供更好的服务,如 hotplug、udev 和 inotify 就是这种需求催生的。Hotplug 是一种内核向用户态应用通报关于热插拔设备一些事件发生的机制,桌面系统能够利用它对设备进行有效的管理,udev 动态地维护 /dev 下的设备文件,inotify 是一种文件系统的变化通知机制,如文件增加、删除等事件可以立刻让用户态得知,该机制是著名的桌面搜索引擎项目 beagle 引入的,并在 Gamin 等项目中被应用。

    事实上,在 inotify 之前已经存在一种类似的机制叫 dnotify,但是它存在许多缺陷:

    1. 对于想监视的每一个目录,用户都需要打开一个文件描述符,因此如果需要监视的目录较多,将导致打开许多文件描述符,特别是,如果被监视目录在移动介质上(如光盘和 USB 盘),将导致无法 umount 这些文件系统,因为使用 dnotify 的应用打开的文件描述符在使用该文件系统。

    2. dnotify 是基于目录的,它只能得到目录变化事件,当然在目录内的文件的变化会影响到其所在目录从而引发目录变化事件,但是要想通过目录事件来得知哪个文件变化,需要缓存许多 stat 结构的数据。

    3. Dnotify 的接口非常不友好,它使用 signal.

    Inotify 是为替代 dnotify 而设计的,它克服了 dnotify 的缺陷,提供了更好用的,简洁而强大的文件变化通知机制:

    1. Inotify 不需要对被监视的目标打开文件描述符,而且如果被监视目标在可移动介质上,那么在 umount 该介质上的文件系统后,被监视目标对应的 watch 将被自动删除,并且会产生一个 umount 事件。

    2. Inotify 既可以监视文件,也可以监视目录。

    3. Inotify 使用系统调用而非 SIGIO 来通知文件系统事件。

    4. Inotify 使用文件描述符作为接口,因而可以使用通常的文件 I/O 操作select 和 poll 来监视文件系统的变化。

    Inotify 可以监视的文件系统事件包括:

    IN_ACCESS,即文件被访问IN_MODIFY,文件被 write IN_ATTRIB,文件属性被修改,如 chmod、chown、touch 等IN_CLOSE_WRITE,可写文件被 close IN_CLOSE_NOWRITE,不可写文件被 close IN_OPEN,文件被 open IN_MOVED_FROM,文件被移走,如 mv IN_MOVED_TO,文件被移来,如 mv、cp IN_CREATE,创建新文件IN_DELETE,文件被删除,如 rm IN_DELETE_SELF,自删除,即一个可执行文件在执行时删除自己IN_MOVE_SELF,自移动,即一个可执行文件在执行时移动自己IN_UNMOUNT,宿主文件系统被 umount IN_CLOSE,文件被关闭,等同于(IN_CLOSE_WRITE | IN_CLOSE_NOWRITE)

    IN_MOVE,文件被移动,等同于(IN_MOVED_FROM | IN_MOVED_TO)

    注:上面所说的文件也包括目录。

    二、用户接口

    在用户态,inotify 通过三个系统调用和在返回的文件描述符上的文件 I/ 操作来使用,使用 inotify 的第一步是创建 inotify 实例:

 

 

 


 

                int fd = inotify_init ();
       
 

 

    每一个 inotify 实例对应一个独立的排序的队列。

    文件系统的变化事件被称做 watches 的一个对象管理,每一个 watch 是一个二元组(目标,事件掩码),目标可以是文件或目录,事件掩码表示应用希望关注的 inotify 事件,每一个位对应一个 inotify 事件。Watch 对象通过 watch描述符引用,watches 通过文件或目录的路径名来添加。目录 watches 将返回在该目录下的所有文件上面发生的事件。

    下面函数用于添加一个 watch:


                int wd = inotify_add_watch (fd, path, mask);
       
 

 

    fd 是 inotify_init() 返回的文件描述符,path 是被监视的目标的路径名(即文件名或目录名),mask 是事件掩码, 在头文件 linux/inotify.h 中定义了每一位代表的事件。可以使用同样的方式来修改事件掩码,即改变希望被通知的inotify 事件。Wd 是 watch 描述符。

    下面的函数用于删除一个 watch:


        int ret = inotify_rm_watch (fd, wd);
       
 

 

    fd 是 inotify_init() 返回的文件描述符,wd 是 inotify_add_watch() 返回的 watch 描述符。Ret 是函数的返回值。

    文件事件用一个 inotify_event 结构表示,它通过由 inotify_init() 返回的文件描述符使用通常文件读取函数 read 来获得


struct inotify_event {
        __s32           wd;             /* watch descriptor */
        __u32           mask;           /* watch mask */
        __u32           cookie;         /* cookie to synchronize two events */
        __u32           len;            /* length (including nulls) of name */
        char            name[0];        /* stub for possible name */
};

 

 

    结构中的 wd 为被监视目标的 watch 描述符,mask 为事件掩码,len 为 name字符串的长度,name 为被监视目标的路径名,该结构的 name 字段为一个桩,它只是为了用户方面引用文件名,文件名是变长的,它实际紧跟在该结构的后面,文件名将被 0 填充以使下一个事件结构能够 4 字节对齐。注意,len 也把填充字节数统计在内。

    通过 read 调用可以一次获得多个事件,只要提供的 buf 足够大。


                size_t len = read (fd, buf, BUF_LEN);
       
 

 

    buf 是一个 inotify_event 结构的数组指针,BUF_LEN 指定要读取的总长度,buf 大小至少要不小于 BUF_LEN,该调用返回的事件数取决于 BUF_LEN 以及事件中文件名的长度。Len 为实际读去的字节数,即获得的事件的总长度。

    可以在函数 inotify_init() 返回的文件描述符 fd 上使用 select() 或poll(), 也可以在 fd 上使用 ioctl 命令 FIONREAD 来得到当前队列的长度。close(fd)将删除所有添加到 fd 中的 watch 并做必要的清理。


                int inotify_init (void);
        int inotify_add_watch (int fd, const char *path, __u32 mask);
        int inotify_rm_watch (int fd, __u32 mask);
       
 
三、内核实现机理
    在内核中,每一个 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 */
};

 


    d_list 指向所有 inotify_device 组成的列表的,i_list 指向所有被监视 inode 组成的列表,count 是引用计数,dev 指向该 watch 所在的 inotify 实例对应的 inotify_device 结构,inode 指向该 watch 要监视的 inode,wd 是分配给该 watch 的描述符,mask 是该 watch 的事件掩码,表示它对哪些文件系统事件感兴趣。

    结构 inotify_device 在用户态调用 inotify_init() 时创建,当关闭 inotify_init()返回的文件描述符时将被释放。结构 inotify_watch 在用户态调用 inotify_add_watch()时创建,在用户态调用 inotify_rm_watch() 或 close(fd) 时被释放。

    无论是目录还是文件,在内核中都对应一个 inode 结构,inotify 系统在 inode 结构中增加了两个字段:

 

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 指向该 watch 所在的 inotify 实例对应的 inotify_device 结构,inode 指向该 watch 要监视的 inode,wd 是分配给该 watch 的描述符,mask 是该 watch 的事件掩码,表示它对哪些文件系统事件感兴趣。

    结构 inotify_device 在用户态调用 inotify_init() 时创建,当关闭 inotify_init()返回的文件描述符时将被释放。结构 inotify_watch 在用户态调用 inotify_add_watch()时创建,在用户态调用 inotify_rm_watch() 或 close(fd) 时被释放。

    无论是目录还是文件,在内核中都对应一个 inode 结构,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(inotify_unmount_inodes直接调用 inotify_dev_queue_event ),该函数首先判断对应的inode是否被监视,这通过查看 inotify_watches 列表是否为空来实现,如果发现 inode 没有被监视,什么也不做,立刻返回,反之,遍历 inotify_watches 列表,看是否当前的文件操作事件被某个 watch 监视,如果是,调用 inotify_dev_queue_event,否则,返回。函数inotify_dev_queue_event 首先判断该事件是否是上一个事件的重复,如果是就丢弃该事件并返回,否则,它判断是否 inotify 实例即 inotify_device 的事件队列是否溢出,如果溢出,产生一个溢出事件,否则产生一个当前的文件操作事件,这些事件通过kernel_event 构建,kernel_event 将创建一个 inotify_kernel_event 结构,然后把该结构插入到对应的 inotify_device 的 events 事件列表,然后唤醒等待在inotify_device 结构中的 wq 指向的等待队列。想监视文件系统事件的用户态进程在inotify 实例(即 inotify_init() 返回的文件描述符)上调用 read 时但没有事件时就挂在等待队列 wq 上。

四、使用示例
    下面是一个使用 inotify 来监视文件系统事件的例子:
 

#include
#include
#include

_syscall0(int, inotify_init)
_syscall3(int, inotify_add_watch, int, fd, const char *, path, __u32, mask)
_syscall2(int, inotify_rm_watch, int, fd, __u32, mask)

char * monitored_files[] = {
 "./tmp_file",
 "./tmp_dir",
 "/mnt/sda3/windows_file"
};

struct wd_name {
 int wd;
 char * name;
};

#define WD_NUM 3
struct wd_name wd_array[WD_NUM];

char * event_array[] = {
 "File was accessed",
 "File was modified",
 "File attributes were changed",
 "writtable file closed",
 "Unwrittable file closed",
 "File was opened",
 "File was moved from X",
 "File was moved to Y",
 "Subfile was created",
 "Subfile was deleted",
 "Self was deleted",
 "Self was moved",
 "",
 "Backing fs was unmounted",
 "Event queued overflowed",
 "File was ignored"
};
#define EVENT_NUM 16
#define MAX_BUF_SIZE 1024
 
int main(void)
{
 int fd;
 int wd;
 char buffer[1024];
 char * offset = NULL;
 struct inotify_event * event;
 int len, tmp_len;
 char strbuf[16];
 int i = 0;
 
 fd = inotify_init();
 if (fd < 0) {
  printf("Fail to initialize inotify./n");
  exit(-1);
 }

 for (i=0; imask & IN_ISDIR) {
    memcpy(strbuf, "Direcotory", 11);
   }
   else {
    memcpy(strbuf, "File", 5);
   }
   printf("Object type: %s/n", strbuf);
   for (i=0; iwd != wd_array[i].wd) continue;
    printf("Object name: %s/n", wd_array[i].name);
    break;
   }
   printf("Event mask: %08X/n", event->mask);
   for (i=0; imask & (1<len;
   event = (struct inotify_event *)(offset + tmp_len);
   offset += tmp_len;
  }
 }
}

 


    该程序将监视发生在当前目录下的文件 tmp_file 与当前目录下的目录 tmp_dir 上的所有文件系统事件, 同时它也将监视发生在文件 /mnt/sda3/windows_file 上的文件系统事件,注意,/mnt/sda3 是 SATA 硬盘分区 3 的挂接点。

    细心的读者可能注意到,该程序首部使用 _syscallN 来声明 inotify 系统调用,原因是这些系统调用是在最新的稳定内核 2.6.13 中引入的,glibc 并没有实现这些系统调用的库函数版本,因此,为了能在程序中使用这些系统调用,必须通过 _syscallN 来声明这些新的系统,其中的 N 为要声明的系统调用实际的参数数。还有需要注意的地方是系统的头文件必须与被启动的内核匹配,为了让上面的程序能够成功编译,必须让 2.6.13 的内核头文件(包括 include/linux/*, include/asm/* 和 include/asm-generic/*)在头文件搜索路径内,并且是第一优先搜索的头文件路径,因为 _syscallN 需要用到这些头文件中的 linux/unistd.h 和 asm/unistd.h,它们包含了 inotify 的三个系统调用的系统调用号 __NR_inotify_init、__NR_inotify_add_watch 和 __NR_inotify_rm_watch.

    因此,要想成功编译此程序,只要把用户编译好的内核的头文件拷贝到该程序所在的路径,并使用如下命令编译即可:

 

$gcc -o inotify_example  -I. inotify_example.c

 


    注意:当前目录下应当包含 linux、asm 和 asm-generic 三个已编译好的 2.6.13 内核的有文件目录,asm 是一个链接,因此拷贝 asm 头文件的时候需要拷贝 asm 与 asm-ARCH(对于 x86 平台应当是 asm-i386)。然后,为了运行该程序,需要在当前目录下创建文件 tmp_file 和目录 tmp_dir,对于/mnt/sda3/windows_file 文件,用户需要依自己的实际情况而定,可能是/mnt/dosc/windows_file,即 /mnt/dosc 是一个 FAT32 的 windows 硬盘,因此用户在编译该程序时需要根据自己的实际情况来修改 /mnt/sda3.Windows_file 是在被 mount 硬盘上创建的一个文件,为了运行该程序,它必须被创建。

    以下是作者在 redhat 9.0 上运行此程序得到的一些结果:

    当运行此程序的时候在另一个虚拟终端执行 cat ./tmp_file,此程序的输出为:


Some event happens, len = 48.
Object type: File
Object name: ./tmp_file
Event mask: 00000020
Event: File was opened
Object type: File
Object name: ./tmp_file
Event mask: 00000001
Event: File was accessed
Object type: File
Object name: ./tmp_file
Event mask: 00000010
Event: Unwrittable file closed

 


    以上事件清楚地说明了 cat 指令执行了文件 open 和 close 操作,当然 open 和 close操作都属于 access 操作,任何对文件的操作都是 access 操作。

    此外,运行 vi ./tmp_file,发现 vi实际在编辑文件时复制了一个副本,在未保存之前是对副本进行操作。运行 vi ./tmp_file, 修改并保存退出时,发现 vi 实际在保存修改时删除了最初的文件并把那个副本文件名更改为最初的文件的名称。注意,事件"File was ignored"表示系统把该文件对应的 watch 从 inotify 实例的 watch 列表中删除,因为文件已经被删除。读者可以自己分别执行命令:echo "abc" > ./tmp_file 、rm -f tmp_file、 ls tmp_dir、 cd tmp_dir;touch c.txt、 rm c.txt 、 umount /mnt/sda3(实际用户需要使用自己当时的 mount 点路径名),然后分析一下结果。Umount 触发两个事件,一个表示文件已经被删除或不在存在,另一个表示该文件的 watch被从 watch 列表中删除。

    五、典型应用

    beagle 是 GNOME 的桌面搜索引擎项目,inotify 的引入就是完全受它的驱动而做的。对于桌面搜索引擎,它一般作为一个优先级很低的后台进程运行, 只有在系统没有其他任务可运行时才被调度执行,桌面搜索引擎的主要用途就是为系统的文件系统的文件建立索引数据库,以便用户在需要某文件但又想不起存放在哪里时能够根据某些关键字或特征快速地搜索到需要的文件,就象使用网络搜索引擎 google 一样便捷。文件系统有个特点就是只有某些文件会变化,因此桌面搜索引擎在第一次建立完索引数据库后,没必要重复遍历所有的文件建立新的索引,它只需要更新修改了的文件的索引,建立新增加的文件的索引,删除已经删除的文件的索引就足够了,这样桌面搜索引擎需要做的工作就大大地减少。Inotify 就是为这一意图专门设计的,beagle 为需要监视的目录或文件创建了inotify 实例,然后它就等待该 inotify 上发生文件系统事件,如果没有任何文件变化,beagle 将不需要任何开销,只有在有被监视的事件发生时,beagle 才被唤醒并根据实际事件来更新对应的文件的索引,然后继续睡眠等待下一个文件系统事件发生。在 SuSe 9.3 和即将发布的 10.0 中就包含了该桌面搜索引擎,它能够为文档、email、音乐、图象和应用等建立索引。使用过 windows 下的桌面搜索引擎的读者对 google 和 yahoo 以及 Microsoft 的桌面搜索引擎有深刻的体会,感兴趣读者可以安装 SuSe 使用一下。

    六、小结

    inotify 是在 2.6.13 中引入的新功能,它为用户态监视文件系统的变化提供了强大的支持,本文详尽地介绍了其起源、内核实现、用户接口以及使用,有兴趣的读者可以读 2.6.13的相关源码来进一步了解其实现细节。


/////////////////////////////////////////////////////////////////////////////////////////////////////////////

 
文章发表于: 2006年 10月02日 11:35    发表主题: 转贴 :关于Linux 文件系统的异步 I/O 扩展  引用并回复
本文中要介绍一个所谓的"Linux 文件系统的守护神",这是指一个能实时地观察 Linux 文件系统的变化情况的程序模块。能够实时的观察文件系统的变化情况,并做出及时的适当的反应,这对于应用 Linux 做桌面计算机系统来说,是十分的有趣,也是十分的重要的。本文还要介绍 Linux 文件系统的异步 I/O 的扩展。同样,这对于 Linux 系统的桌面应用也是关键的。

1 Linux 文件系统的守护神
传统的 Linux 文件系统呈现给用户程序的界面,确实是十分的干净利落。用户程序可以打开一个文件,向文件中线性的写入数据,从文件的某一位置开始,线性的读出数据,关闭一个文件,删除一个文件,创建一个文件,等等。请看,只有这么若干个简洁的操作原语,可是却能提供这么多丰富的应用。但是,我们注意到,用于访问 Linux 的文件系统的这些操作原语,并没有提供非常复杂的加锁解锁的功能。这是一件很奇妙的事情,如果来自不同的用户程序的请求发生了冲突怎么办呢?
我们不妨走的再靠近一点,仔细的看看删除一个文件是怎样进行的。如果已经有一个用户程序在访问一个文件,而另外一个用户程序正好要删除这一个文件,这时会发生些什么呢?我们知道,Linux 的文件系统是基于所谓的 inode 的,每个文件都相伴有一个 inode。在 inode 中记录了关于这个文件的一些系统信息,比如文件的所有者,文件相关的一些权限记录,关于文件的若干个时间戳,等等。在内存中的 inode 还维持着一个关于自己的使用计数。每当一个 inode 所代表的文件被打开一次,这个 inode 就把关于自己的使用计数加一。每当这个 inode 所代表的文件一被关闭,这个 inode 就把关于自己的使用计数减一。当用户程序删除一个文件的时候,相关的系统调用很快就返回到这个用户程序,告诉它,相应的文件已经被删除了。但是相应的 inode 还是保留在系统中,inode 首先要检查自己的使用计数,如果使用计数为零,那么 Linux Kernel 才可以真正的去删除这个文件。如果使用计数大于零,也就是说,还有其它的用户程序在访问这一个文件,那么 Linux Kernel 需要等待这些其他的用户程序一个个都完成对这一个文件的访问才行。也就是说,要等到这个 inode 的使用计数掉到零,才能真正的去删除这一个文件。
我们可以设想一下,如果有一个 MP3 播放程序在播放一首 MP3 音乐,我们觉得它不好听,就到硬盘上找到这个文件,把它 rm 掉了。这时候,MP3 播放程序并不受到影响,还是可以继续播放这首 MP3 音乐,虽然这时候在文件系统上用 ls 已经找不到这个 MP3 音乐文件了。实际上,一直要到 MP3 播放程序停止播放这首 MP3 音乐,然后 Linux 文件系统才真正的从硬盘上删除这个 MP3 文件。这个经验和我们在 Windows 平台上遇到的截然不同。
在 Windows 平台上,当我们试图在文件夹窗口中用鼠标点击右键菜单删除 Winamp 正在播放的一首 MP3 音乐的时候,Windows 系统会用一个弹出对话框告诉我们,这个文件正在被使用,没办法删除。Windows 系统的关于删除文件的这样一个解释,如果使用不当的话,会带来一个滑稽可笑的问题。我们可以设想一下,用户的一个 P2P 的文件共享程序提供了一个 MP3 文件以供别人下载,恰巧这个 MP3 音乐文件十分的热门,不断的有人来下载,这个用户最终决定要节省一下带宽,想要把这个 MP3 音乐文件删除掉,但是 Windows 系统却不允许用户这样做,因为这个 P2P 的文件共享程序总是在使用这个 MP3 文件。用户要想删除这个文件,不得不先把 P2P 的文件共享程序给停下来!呵呵。
但是 Linux 的文件系统的操作原语也有它自己的问题。我们知道,在一个 Linux Shell 的命令行上,先 rm,然后再 ls,非常的干净,被 rm 的文件没有了,被删除了。但是我们可以设想有一个图形界面的文件管理程序,当用户从 Shell 的命令行上 rm 掉一个文件的时候,这个图形界面的文件管理程序并没有收到任何人发给它的任何消息,它还以为什么都没有发生,被删除掉的文件还在那儿。这实在是很 U.G.L.Y. 啊。
那么要想解决这个问题,一个明显的但是非常不好的办法,就是让一个后台进程 Daemon 每隔一个很短的时间间隔,就检查一下文件系统上这个目录的情况,看看有没有发生什么变化。这个办法的缺点真的是显而易见的,不但系统的性能受到影响,而且它的反应也还不是实时的。
如果我们需要用户程序能够实时地了解文件系统上某一个目录的变化情况,从实时这个角度出发,显然,我们需要有一个中断机制。我们都知道,硬件中断能够实时地把系统某一个部件的情况反映给中央处理器,同样的,要想把位于系统内核中的文件系统的情况实时地反映给用户程序,我们也需要一个由操作系统内核到达用户进程的软件中断机制。熟悉 Linux 系统编程的读者朋友们立即就会想到,这个中断机制在 Linux 系统中早已就有了,这就是信号传递 signal。
找到了信号传递这样一个中断用户进程的机制,一切似乎都已齐备,看来可以动手实现这样一个 Linux 文件系统的守护神,来实时地监视文件系统的变化情况,并且及时地把消息通知给用户程序了。不过且慢,让我们搜索一下 Linux Kernel,看看是否有别人也在做同样的工作。哈哈,果不其然,原来这样一个实时地监视文件系统情况的机制早已在 Linux 内核中实现了。下面一段就是取自 Linux Kernel 文档的一段小小例程,说明了 Linux Kernel 中的 dnotify 功能的用法。dnotify 就是指 directory notification,监视文件系统上一个目录中的情况。
代码:
 
#define _GNU_SOURCE /* needed to get the defines */
#include /* in glibc 2.2 this has the needed
values defined */
#include
#include
#include

static volatile int event_fd;

// 信号处理例程
static void handler(int sig, siginfo_t *si, void *data)
{
event_fd = si->si_fd;
}

int main(void)
{
struct sigaction act;
int fd;

// 登记信号处理例程
act.sa_sigaction = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = SA_SIGINFO;
sigaction(SIGRTMIN, &act, NULL);

// 需要了解当前目录"."的情况
fd = open(".", O_RDONLY);
fcntl(fd, F_SETSIG, SIGRTMIN);
fcntl(fd, F_NOTIFY, DN_MODIFY|DN_CREATE|DN_MULTISHOT);
/* we will now be notified if any of the files
in "." is modified or new files are created */
while (1) {
// 收到信号后,就会执行信号处理例程。
// 而 pause() 也就结束了。
pause();
printf("Got event on fd=%d/n", event_fd);
}
}


上面这一小段例程,对于熟悉 Linux 系统编程的读者朋友们来说,是很容易理解的。程序首先注册一个信号处理例程,然后通知 Kernel,我要观察 fd 上的 DN_MODIFY 和 DN_CREATE 和 DN_MULTISHOT 事件。(关于这些事件的详细定义,请读者朋友们参阅文后所列的参考资料。) Linux Kernel 收到这个请求后,把相应的 fd 的 inode 给做上记号,然后 Linux Kernel 和用户应用程序就自顾自去处理各自的别的事情去了。等到 inode 上发生了相应的事件,Linux Kernel 就把信号发给用户进程,于是开始执行信号处理例程,用户程序对文件系统上的变化也就可以及时的做出反应了。而在这整个过程中,系统以及用户程序的正常运行基本上未受到性能上的影响。这里还需要说明的是,dnotify 并没有通过增加新的系统调用来完成它的功能,而是通过 fcntl 来完成任务的。增加一个系统调用,相对来说是一个很大的手术,而且如果设计不当,处理得不好的话,伤疤会一直留在那里,这是 Linux Kernel 的开发者们所非常不愿意见到的事情。

2 Linux 文件系统的异步 I/O 扩展
对于桌面计算机系统来说,能够快速的响应用户的请求,这也是十分关键的。换句话说,当用户移动鼠标的时候,不管系统正在进行什么天大的、重要的、神圣的、不可打断的工作,它都得立即停下,并且要让鼠标立即流畅的在计算机屏幕上完美地运动起来。对于习惯在传统的 Linux 命令行上工作的读者朋友们来说,让鼠标能够在任何时间都可以在计算机屏幕上向无头苍蝇一样地乱窜,竟然被当成是最重要的系统任务,这实在有一点让人难以接受。不过,当你从 Linux 命令行上转移到 GNOME 或者 KDE 这样的图形界面的用户环境的时候,鼠标被锁死,百分之百的也是会让你失去理智的。所以,还是让我们接受这一个现实,看一看如何才能增加系统的响应速度吧。
从文件系统的角度讲,特别是考虑到网络文件系统,它的响应速度有可能会相当的慢。当用户在文件管理程序中,选择了对文件进行某一个操作以后,文件系统可能会需要相当长的时间,才能完成这一操作。如果文件管理程序必须要等待文件系统完成这一操作,然后才能继续的话,这显然会给文件管理程序的用户带来非常不愉快的经历。解决这一个问题的办法,就是要实现异步的文件系统 I/O。
在 Linux 的 Gnome 桌面环境中,由 GnomeVFS 包裹了真正的 Linux 文件系统 I/O,实现了一个异步的文件系统 I/O 接口 API。我们可以看到下面这个用 GnomeVFS 打开文件的例子。
代码:

enum _GnomeVFSOpenMode {
GNOME_VFS_OPEN_NONE = 0,
GNOME_VFS_OPEN_READ = 1 << 0,
GNOME_VFS_OPEN_WRITE = 1 << 1,
GNOME_VFS_OPEN_RANDOM = 1 << 2
};

typedef enum _GnomeVFSOpenMode GnomeVFSOpenMode;

typedef void (* GnomeVFSAsyncOpenCallback)
(GnomeVFSAsyncHandle *handle,
GnomeVFSResult result,
gpointer callback_data);

GnomeVFSResult gnome_vfs_async_open
(GnomeVFSAsyncHandle **handle_return,
const gchar *text_uri,
GnomeVFSOpenMode open_mode,
GnomeVFSAsyncOpenCallback callback,
gpointer callback_data);

我们注意到,上面的代码段中,用户程序为了打开一个文件,向 GnomeVFS 注册了一个 call back 例程。在注册了这一个 call back 例程之后,函数调用就立即返回给用户程序,用户程序就可以处理自己的别的事情去了,比如进一步响应来自用户的其??肭螅?鹊取6?蔽募?低惩瓿啥晕募?拇蚩?僮饕院螅珿nomeVFS 就会调用刚刚注册的 call back 例程,通知用户程序,文件已经打开。
3 小结
我们在本文中了解了 Linux Kernel 中的 dnotify,可以帮助我们实时地监视文件系统目录树中的变化情况;也了解了 Gnome 桌面环境的 GnomeVFS 异步文件系统 I/O 扩展;可以帮助用户程序不至于被文件系统的请求所 Block。这两个功能对于 Linux 系统在桌面上的应用都是很重要的。


 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章