基本介绍

1. 文件属性刚刚改变了吗?

ev_stat会监视文件系统的相关属性变更. 准确来说, 它会定期调用stat(或者操作系统通知)查看对比与上次的变化. 只有在文件确实发生了状态变更后才会被报告.

注册ev_stat指定的路径可以是”不存在”的, 因为从”不存在”变为”存在”也是一种状态. 路径不得以斜杠结尾或包含特殊符号, 如: '.''..'. 设置的路径最好是绝对路径, 否则工作目录的变更可能导致受到影响.

没有通用的事件接口可以知道这些, 所以最好的办法就是在那些平台定期使用stat来查询变更. 唯一实现的特定于操作系统的接口是Linux inotify, kqueue虽然有一定支持但是无法实现stat的完整语义.

即使是操作系统支持也不适合启动大量的ev_stat, 因为这些资源(I/O、CPU)密集型对性能消耗极大.

2. ABI问题

Libev使用默认环境时选择的stat32位版本的, 当ABI改为64位的时候使用会失败. 这种情况下, 必须使用同样的ABI版本编译来保障二进制兼容性.

解决这个问题的办法是定制发行版的作者默认使用64位而并不仅是可选, Libev不会简单直接的支持, 因为这也需要与编译器、环境进行探测后统计知晓.

3. Inotify 与 KQUEUE

inotify支持已编译到Libev并在运行时出现时, 它将尽可能加快更改检测. inotify描述符将在第一个ev_stat启动时延迟创建.

inotify的存在不会改变ev_stat的语义, 只是能更早的检测到变更信息并且在某些情况下避免stat调用. 然而, 即使存在inotify的情况下, 有时候Libev也必须使用轮询进行统计. 但是只要Linux内核版本在2.6.25及以上, Libev对这些本地文件系统(ext2/3ifsreisefsxfs)上已存在的路径不需要使用轮询.

不支持kqueue是因为它显然不能用来实现这个功能, 因为需要在对象上一直打开一个文件描述符, 并且很难检测重命名、断开链接等.

4. stat() 是”同步”操作

Libev本身通常不执行任何类型的I/O, 所以一般不会阻塞住进程. ev_stat则会是一个例外, 因为它是一个同步操作.

对于本地路径这通常无关紧要: 除非系统非常繁忙或每次stat之间的间隔很大, 否则stat调用执行的很快. 因为这时候路劲数据都已经保存在内存中.

但对于远程文件系统, stat()可能会因为网络问题而长期阻塞. 即使再最好的情况下, 一次stat也需要毫秒级别的时间间隔.

因此最好避免在NFS等文件系统上使用ev_stat, 虽然ev_stat已经支持这么做.

5. 时间精度的特殊问题

stat()系统调用最多支持秒级精度, 即使是在更高精度的系统上大多数情况下也只支持秒级.

这意味着, 如果两次更新的周期很短则容易错过. 即: 同一秒内仅更新了时间, ev_stat则无法检测到(除非在其它数据层面也发生了变更). 解决问题的办法是将每次操作延迟1.02秒, 这个0.02的偏移值是为了解决时间精度不一致的问题.

相关函数

ev_stat_init (ev_stat , callback, const char path, ev_tstamp interval)

ev_stat_set (ev_stat , const char path, ev_tstamp interval)

配置ev_stat检查path的变更, interval则是每次检查的间隔(通常是0来让Libev选择一个合适的值).

callback收到EV_STAT事件的时候, 表示ev_stat相对于之前检测到了属性变更.

ev_stat_stat (loop, ev_stat *)

如果您在上述回调函数中更改了path值, 主动调用此函数会立刻更新stat的更改.

ev_tstamp interval [read-only]

间隔时间.

const char *path [read-only]

监视路径.

ev_statdata prev [read-only]

检测到变更事件之前的文件属性. 每当prev != attr的时候, 这些成员中会有一个或多个不同: st_dev, st_ino, st_mode, st_nlink, st_uid, st_gid, st_rdev, st_size, st_atime, st_mtime, st_ctime.

ev_statdata attr [read-only]

最新检测到的变更事件文件属性. 虽然类型是ev_statdata, 但是通常是是您系统中的struct stat类型. 如果st_nlink0, 则说明stat期间发生了一些错误.

使用示例

本示例演示了v.log的文件创建后退出事件循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 只需导入单个头文件
#include <ev.h>
#include <stdio.h>

// 当发现`v.log`文件被创建之后退出.
static void stat_cb (struct ev_loop *loop, ev_stat *w, int revents)
{
puts ("Bye.");
ev_break(loop, EVBREAK_ALL);
}


int main (void)
{
// 可以使用已定义的宏来获取默认的事件循环, 当然你也可以根据自己的需求创建指定的.
struct ev_loop *loop = EV_DEFAULT;

ev_stat estat;
ev_stat_init(&estat, stat_cb, "v.log", 0.);
ev_stat_start(loop, &estat);

// 开始运行事件循环
ev_run (loop, 0);

// 如果事件循环退出, 那将会执行到这里.
return 0;
}