libuv之inotify源码分析

103 篇文章 9 订阅
39 篇文章 4 订阅

inotify是linux系统提供用于监听文件系统的机制。inotify机制的逻辑大致是
1 init_inotify创建一个inotify机制的实例,返回一个文件描述符。类似epoll。
2 inotify_add_watch往inotify实例注册一个需监听的文件(inotify_rm_watch是移除)。
3 read((inotify实例对应的文件描述符, &buf, sizeof(buf))),如果没有事件触发,则阻塞(除非设置了非阻塞)。否则返回待读取的数据长度。buf就是保存了触发事件的信息。
libuv在inotify机制的基础上做了一层封装。
今天分析一下libuv中的实现。我们从一个使用例子开始。

int main(int argc, char **argv) {
    // 实现循环核心结构体loop
    loop = uv_default_loop();
    fprintf(stderr, "Adding watch on %s\n", argv[0]);
    uv_fs_event_t *fs_event_req = malloc(sizeof(uv_fs_event_t));
    // 初始化fs_event_req结构体的类型为UV_FS_EVENT
    uv_fs_event_init(loop, fs_event_req);
    // argv[argc]是文件路径,uv_fs_event_start向底层注册监听文件argv[argc],cb是事件触发时的回调
    uv_fs_event_start(fs_event_req, cb, argv[argc], UV_FS_EVENT_RECURSIVE);
	// 开启事件循环
    return uv_run(loop, UV_RUN_DEFAULT);
}

libuv在第一次监听文件的时候,会创建一个inotify实例。

static int init_inotify(uv_loop_t* loop) {
  int err;
  // 初始化过了则直接返回 	
  if (loop->inotify_fd != -1)
    return 0;
  // 调用操作系统的inotify_init函数申请一个inotify实例,并设置UV__IN_NONBLOCK,UV__IN_CLOEXEC标记
  err = new_inotify_fd();
  if (err < 0)
    return err;
  // 记录inotify实例对应的文件描述符
  loop->inotify_fd = err;
  // inotify_read_watcher是一个io观察者,uv__io_init设置io观察者的文件描述符(待观察的文件)和回调
  uv__io_init(&loop->inotify_read_watcher, uv__inotify_read, loop->inotify_fd);
  // 往libuv中注册该io观察者,感兴趣的事件为可读
  uv__io_start(loop, &loop->inotify_read_watcher, POLLIN);

  return 0;
}

分析完libuv申请inotify实例的逻辑,我们回到main函数看看uv_fs_event_start函数。

uv_fs_event_start(fs_event_req, cb, argv[argc], UV_FS_EVENT_RECURSIVE);

用户使用uv_fs_event_start函数来往libuv注册一个待监听的文件。我们看看实现。

int uv_fs_event_start(uv_fs_event_t* handle,
                      uv_fs_event_cb cb,
                      const char* path,
                      unsigned int flags) {
  struct watcher_list* w;
  int events;
  int err;
  int wd;

  if (uv__is_active(handle))
    return UV_EINVAL;
  // 申请一个inotify实例
  err = init_inotify(handle->loop);
  if (err)
    return err;
  // 监听的事件
  events = UV__IN_ATTRIB
         | UV__IN_CREATE
         | UV__IN_MODIFY
         | UV__IN_DELETE
         | UV__IN_DELETE_SELF
         | UV__IN_MOVE_SELF
         | UV__IN_MOVED_FROM
         | UV__IN_MOVED_TO;
  // 调用操作系统的函数注册一个待监听的文件,返回一个对应于该文件的id
  wd = uv__inotify_add_watch(handle->loop->inotify_fd, path, events);
  if (wd == -1)
    return UV__ERR(errno);
  // 判断该文件是不是已经注册过了
  w = find_watcher(handle->loop, wd);
  // 已经注册过则跳过插入的逻辑
  if (w)
    goto no_insert;
  // 还没有注册过则插入libuv维护的红黑树
  w = uv__malloc(sizeof(*w) + strlen(path) + 1);
  if (w == NULL)
    return UV_ENOMEM;

  w->wd = wd;
  w->path = strcpy((char*)(w + 1), path);
  QUEUE_INIT(&w->watchers);
  w->iterating = 0;
  // 插入libuv维护的红黑树,inotify_watchers是根节点
  RB_INSERT(watcher_root, CAST(&handle->loop->inotify_watchers), w);

no_insert:
  // 激活该handle
  uv__handle_start(handle);
  // 同一个文件可能注册了很多个回调,w对应一个文件,注册在用一个文件的回调排成队
  QUEUE_INSERT_TAIL(&w->watchers, &handle->watchers);
  // 保存信息和回调
  handle->path = w->path;
  handle->cb = cb;
  handle->wd = wd;

  return 0;
}

我们先看一下架构图,然后再来具体分析。

下面我们逐步分析上面的函数逻辑。
1 如果是首次调用该函数则新建一个inotify实例。并且往libuv插入一个观察者io,libuv会在poll io阶段注册到epoll中。
2 往操作系统注册一个待监听的文件。返回一个id。
3 libuv判断该id是不是在自己维护的红黑树中。不在红黑树中,则插入红黑树。返回一个红黑树中对应的节点。把本次请求的信息封装到handle中(回调时需要)。然后把handle插入刚才返回的节点的队列中。见上图。
这时候注册过程就完成了。libuv在poll io阶段如果检测到有文件发生变化,则会执行回调uv__inotify_read。

static void uv__inotify_read(uv_loop_t* loop,
                             uv__io_t* dummy,
                             unsigned int events) {
  const struct uv__inotify_event* e;
  struct watcher_list* w;
  uv_fs_event_t* h;
  QUEUE queue;
  QUEUE* q;
  const char* path;
  ssize_t size;
  const char *p;
  /* needs to be large enough for sizeof(inotify_event) + strlen(path) */
  char buf[4096];
  // 一次可能没有读完
  while (1) {
    do
      // 读取触发的事件信息,size是数据大小,buffer保存数据
      size = read(loop->inotify_fd, buf, sizeof(buf));
    while (size == -1 && errno == EINTR);
    // 没有数据可取了
	if (size == -1) {
      assert(errno == EAGAIN || errno == EWOULDBLOCK);
      break;
    }
	// 处理buffer的信息
    for (p = buf; p < buf + size; p += sizeof(*e) + e->len) {
      // buffer里是多个uv__inotify_event结构体,里面保存了事件信息和文件对应的id(wd字段)
      e = (const struct uv__inotify_event*)p;

      events = 0;
      if (e->mask & (UV__IN_ATTRIB|UV__IN_MODIFY))
        events |= UV_CHANGE;
      if (e->mask & ~(UV__IN_ATTRIB|UV__IN_MODIFY))
        events |= UV_RENAME;
	  // 通过文件对应的id(wd字段)从红黑树中找到对应的节点
      w = find_watcher(loop, e->wd);
      
      path = e->len ? (const char*) (e + 1) : uv__basename_r(w->path);
      w->iterating = 1;
      // 把红黑树中,wd对应节点的handle队列移到queue变量,准备处理
      QUEUE_MOVE(&w->watchers, &queue);
      while (!QUEUE_EMPTY(&queue)) {
      	// 头结点
        q = QUEUE_HEAD(&queue);
        // 通过结构体偏移拿到首地址
        h = QUEUE_DATA(q, uv_fs_event_t, watchers);
		// 从处理队列中移除
        QUEUE_REMOVE(q);
        // 放回原队列
        QUEUE_INSERT_TAIL(&w->watchers, q);
		// 执行回调
        h->cb(h, path, events, 0);
      }
    }
  }
}

uv__inotify_read函数的逻辑就是从操作系统中把数据读取出来,这些数据中保存了哪些文件触发了用户感兴趣的事件。然后遍历每个触发了事件的文件。从红黑树中找到该文件对应的红黑树节点。再取出红黑树节点中维护的一个handle队列,最后执行handle队列中每个节点的回调。
    总结:本文介绍了libuv中的inotify机制。他是对操作系统的封装,但是也加入了自己的一些逻辑。文中有很多地方没有展开分析,是因为在之前的文章中已经分析过了很多次。如果有疑问可以留言。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值