使用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这种文件(可以理解为消息管道,并不存储信息),当有事件到达这两种文件,这两种文件被写入了信息,并且变得可读,本地文件写入了新东西并没有变得可读。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容