利用inotify和epoll 达成简易的tail命令

tail命令是最常用来看日志改变的工具,比如在进行某个职责时会往地面文件中打入日志,然后采纳类似

tail -f file_path

的授命来查阅最新的日记新闻。

要已毕那种功效,一般会想到轮询,也就是不停地去读取文件然后比较内容,输出最新的即可,网上搜一下也是有成百上千那种落成(比如那么些)。

妇孺皆知那不够优雅。

事先听河狸家的技术老董就说到了这一个的化解方案,查了眨眼之间间,发现使用inotify来监听文件变化并向程序发送事件,再用select,poll,epoll来监听inotify发生的风云可以完毕tail命令的基本效用。

为此,了解一下生死相依的调用。

inotify相关

头文件
#include<sys/inotify.h>
初始化
int fd = inotify_init();

那里的fd可以驾驭为inotify创制的一个通报事件的公文描述符,类似网络编程中的socket,当监听文件变化时,inotify会向fd中写入事件的连带新闻。

增加监听
int wd = inotify_add_watch(int fd, const char *path, unit32_t mask);

那边的fd就是开端化调用inotify_init再次来到的文件描述符,path就是您要监听的文书路径,mask就是您要监听的转变类型,可以有很多种重组,重返值wd在文档上就好像此一句话:

On success, inotify_add_watch() returns a nonnegative watch
descriptor. On error -1 is returned and errno is set appropriately.

令人摸不着头脑,不清楚再次来到的到底是哪个文件的叙述符(事实注明这一个wd也不是path文件的叙说符)。

mask 能够是以下值的结缘

  • IN_ACCESS,文件被访问
  • IN_ATTRIB,文件属性被涂改
  • IN_CLOSE_WRITE,可写文件被关闭
  • IN_CLOSE_NOWRITE,不可写文件被关门
  • IN_CREATE,文件/文件夹被创立
  • IN_DELETE,文件/文件夹被去除
  • IN_DELETE_SELF,被监督的靶子自我被剔除
  • IN_MODIFY,文件被改动
  • IN_MOVE_SELF,被监督的对象自我被活动
  • IN_MOVED_FROM,文件被移出被监控目录
  • IN_MOVED_TO,文件被移入被监控目录
  • IN_OPEN,文件被打开

实现tail是用的IN_MODIFY

当调用添加监视目的后就可以坐等事件时有爆发了,当path对应的公文被修改后,fd就变得可读,而且转移的音信也写入了fd,那里我们就可以用select,poll,epoll来监听fd以变相监听文件的浮动了。

epoll相关

创建
    int epfd = epoll_create(int size);

创造一个epoll,size是要监听的公文数量

注册
int epoll_ctl(int epfd, int op,int fd, struct epoll_event *event)

epfd就是创办epoll_create重返值,op表示动作,
可选的有

  • EPOLL_CTL_ADD:注册新的fd到epfd中;
  • EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
  • EPOLL_CTL_DEL:从epfd中除去一个fd;

fd是要监听的文件

event 是索要监听的情节,例如fd可读,可写等等。

等候事件暴发
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

前多少个参数都是地点的,第多个参数maxevents其实是告诉内核 events
有多大,不会当先成立时的size,第多个参数timeout是指等待的过期时间,比如设为500,表示500微秒不管有没有事件暴发都会回去,设为0则直接不通直到事件爆发。再次回到值是发出事变的个数。

介绍完多少个连串调用,就足以初始撸代码了。花了多少个时辰撸了一个不难版本,好在可以用,只限于append。

#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/inotify.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>

#define BUFSIZE 81

int main(int argc, char* argv[]) {
    printf("LOG:argc = %d\n", argc);
    printf("LOG:argc[1] = %s\n", argv[1]);
    int fd = inotify_init();
    printf("LOG:fd = %d\n", fd);
    if (fd < 0) {
        printf("LOG:inotify_init error\n");
        return -1;
    }
    int wd = inotify_add_watch(fd, argv[1], IN_MODIFY);
    printf("LOG:wd = %d\n", wd);
    if (wd < 0) {
        printf("LOG:inotify_add_watch error");
        return -1;
    }

    int epfd = epoll_create(1);
    printf("LOG:epfd = %d\n", epfd);
    if (epfd < 0) {
        printf("LOG:epoll_create error\n");
        return -1;
    }

    struct epoll_event event;
    event.events = EPOLLIN | EPOLLET;
    event.data.fd = fd;

    int re = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);     
    printf("LOG:re = %d\n", re);
    if (re != 0) {
        printf("LOG:epoll_ctl error:%s\n", strerror(errno));
        return -1;
    }

    ssize_t n;
    ssize_t tmp;
    int file_fd = open(argv[1], O_RDONLY);
    printf("LOG:file_fd = %d\n", file_fd);
    if (file_fd == -1) {
        printf("LOG:open error\n");
        return -1;
    }
    char buf[BUFSIZE];

    int seek_re;    

    while (1) {
        int num = epoll_wait(epfd, &event, 1, -1);
        printf("LOG:epoll_wait num = %d\n", num);
        if (num > 0) {
            // 这里是为了读掉消息而不重复通知
            tmp = read(fd, buf, BUFSIZE);
            seek_re = lseek(file_fd, (off_t) -1, SEEK_CUR);
            n = read(file_fd, buf, BUFSIZE);
            if (n == -1) {
                printf("break\n");
                break;
            }
            if (n > 0) {
                printf("%s", buf);
            }
        }
        printf("LOG:new loop\n");
    }

    return 0;
}

编译

gcc mytail.c -o mytail

执行

touch ./test.txt
./mytail ./test.txt

另开一个终端

echo aa > ./test.txt
echo aabb > ./test.txt
echo aabbcc > ./test.txt

可以看来类似tail的出口,但是多了部分log。

Q&A

  • Q:最后怎么我用vim编辑test.txt文件再保存却绝非功能啊?

  • A:其实是那样的,vim那类编辑器并从未改动文件,而是copy一份来编排,编辑完了交替掉原先的公文,借使要兑现这些,可以用notify监听文件的删除移动等。

  • Q:为啥是epoll而不是select,poll?

  • A:那些相对好玩,用select,poll也能达标相同的功能,在监听少数文书下是从未有过分其他,网上说的反差紧要仍然对准监听大量文件的事态下,常常也是网络请求高并发下,epoll会显示相对优势,那里epoll只监听inotify的fd文件,永远唯有一个,所以更未曾不一致了。

  • Q:既然epoll可以监听inotify的风波,为啥epoll不间接监听变化的公文而是要绕这么大一个弯?

  • A:linux中的epoll本身不协助监听本地文件,只可以监听类似inotify和socket那种文件(可以知晓为新闻管道,并不存储信息),当有事件到达那三种文件,那二种文件被写入了音讯,并且变得可读,本地文件写入了新东西并从未变得可读。

相关文章