linux中inotify机制如何应用

来源:互联网 发布:淘宝嘉年华妈妈装 编辑:程序博客网 时间:2024/06/14 02:26

在前面的博客《用可变参数扩展printf》中讨论到如何在应用中控制log的输出。
我们假设的情景是,一个长期运行的Linux程序,想在不退出运行的情况下,通过某种机制,可以让程序知道要不要打印出log 。
我们当时的实现是:

  1. 创建一个文件,写进标志位
  2. 然后每次要打印log之前先读取这个文件,按照标志位是什么来决定要不要打印log

这样子我们在linux系统上,如果不想打印出这些log,可以向这个文件写其他标志位。
但是,这种做法效率太低了。那如何解决上面的问题呢?
有两种做法:

  1. 一种是linux inotify机制
  2. 另外一种是linux 信号机制。注册一个信号,文件更改了,向这个程序发信号,printf_my_log再重新读取。

在本节中,讲的是linux inotify机制。

参考资料http://www.man7.org/linux/man-pages/man7/inotify.7.html
Inotify机制作用:

  1. Inotify 是一个 Linux特性,它监控文件系统操作,比如读取、写入和创建。
  2. Inotify 反应灵敏,用法非常简单,并且比 cron 任务的繁忙轮询高效得多。

inotify既可以监控文件,也可以监控目录。这里我们用来监视文件printlog。

下面讲一下如何在程序中使用Inotify机制。
第一步是创建 inotify 实例。
  int fd = inotify_init ();
  也可以用inotifyFd = inotify_init1(IN_NONBLOCK) 实现非阻塞
  每一个 inotify 实例对应一个独立的排序的队列。
第二步是添加需要被监视的文件。
  下面函数用于添加一个 watch:
  参数含义:
  fd : inotify_init() 返回的文件描述符
  path :被监视的目标的路径名(即文件名或目录名)
  mask: 是事件掩码
  int wd = inotify_add_watch (fd, path, mask);
第三步是读取inotify_event 数据到buf。
  numRead = read(inotifyFd,buf,BUF_LEN); //读取不到会阻塞
第四步是解析读取的数据,一般是根据inotify_event结构中的mask成员来判断的。

for(p=buf;p < buf+numRead;p+=sizeof(struct inotify_event) + event->len)  {      event = (struct inotify_event *)p;      if(event->mask & IN_ACCESS)   printf("IN_ACCESS\n");      if(event->mask & IN_DELETE_SELF)   printf("DELETE_SELF\n");      if(event->mask & IN_MODIFY)    printf("IN_MODIFY\n");    if(event->mask & IN_OPEN)   printf("IN_OPEN\n");}

经过上面四个步骤,就可以简单使用inotify机制了。
下面再看看Inotify一些重要知识点。
在头文件 linux/inotify.h 中定义了每一位代表的事件。
Inotify 可以监视的文件系统事件包括:

IN_ACCESS:文件被访问IN_MODIFY:文件被修改IN_ATTRIB:文件属性被修改IN_CLOSE_WRITE:可写文件被关闭IN_CLOSE_NOWRITE:不可写文件被关闭IN_OPEN:文件被打开IN_MOVED_FROM:文件被移走IN_MOVED_TO:文件被移来IN_CREATE:创建新文件IN_DELETE:文件被删除IN_DELETE_SELF:自删除,即一个可执行文件在执行时删除自己IN_MOVE_SELF:自移动,即一个可执行文件在执行时移动自己IN_UNMOUNT:宿主文件系统被卸载IN_CLOSE:文件被关闭IN_MOVE:文件被移动

文件事件用一个 inotify_event 结构表示,应用程序用read从一个inotify文件描述符来读取这个结构体

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 是事件掩码
cookie是一个独特的整数,连接相关的事件
len 是name字符串的长度,每次解析的数据长度为sizeof(struct inotify_event) + inotify_event->len
name 是被监视目标的路径名

好,到这里,inotify的基础知识已经介绍完了,现在可以用它来实现应用程序log的异步控制了。下面实现一个简单的应用程序来打印log。

#include<stdio.h>  #include<assert.h>  #include<unistd.h>  #include<stdlib.h>  #include<errno.h>  #include<string.h>  #include<sys/types.h>  #include<sys/inotify.h>  #include<limits.h>  #include<fcntl.h>  #define BUF_LEN 1024  void displayInotifyEvent(struct inotify_event *i)  {      printf("  wd = %2d; ",i->wd);      printf("mask = %d",i->mask);      if(i->mask & IN_ACCESS)   printf("IN_ACCESS\n");      if(i->mask & IN_DELETE_SELF)   printf("IN_DELETE_SELF\n");      if(i->mask & IN_MODIFY)  printf("IN_MODIFY\n");      if(i->mask & IN_OPEN)   printf("IN_OPEN\n");  }  int logFlag = 0;//全局变量,是否打印log的标志int main(int argc,char **argv)  {      int fd;//用于保存文件"printlog"的描述符    char filename[] = "printlog";    char readbuf[8] = {0};    int read_count = 0;    int inotifyFd,wd,j;      char buf[BUF_LEN];      ssize_t numRead;      char *p;      struct inotify_event *event;      int flags;      if(argc < 2 )      {          printf("error\n");      }      inotifyFd = inotify_init();      if(inotifyFd == -1)      {          printf("初始化失败");      }      /* //Create the file descriptor for accessing the inotify API     inotifyFd = inotify_init1(IN_NONBLOCK);//设置非阻塞    if (inotifyFd == -1) {       perror("inotify_init1");       //exit(EXIT_FAILURE);    }*/    //wd = inotify_add_watch(inotifyFd,argv[1],IN_ALL_EVENTS);      wd = inotify_add_watch(inotifyFd,argv[1],IN_MODIFY);    if(wd == -1)      {          printf("error\n");      }      printf("Watching %s using wd %d\n",argv[1],wd);      while(1)      {          //Read events.        numRead = read(inotifyFd,buf,BUF_LEN);  //读取不到会阻塞        if (numRead == -1 && errno != EAGAIN) {           perror("read");           //exit(EXIT_FAILURE);        }       /* If the nonblocking read() found no events to read, then          it returns -1 with errno set to EAGAIN. In that case,          we exit the loop. */       //if (numRead <= 0)        //   break;        printf("Read %ldbytes from inotify fd\n",(long)numRead);          for(p=buf;p < buf+numRead;)          {              event = (struct inotify_event *)p;              if(event->mask & IN_ACCESS)   printf("IN_ACCESS\n");              if(event->mask & IN_DELETE_SELF)   printf("IN_DELETE_SELF\n");              if(event->mask & IN_MODIFY)            {                printf("IN_MODIFY\n");                //打开文件,把监视的文件内容读到全局变量logFlag中                //logFlag正是程序是否要打印log的标志                if((fd = open(filename,O_RDWR))<0)                {                      perror("open");                }                 //读取文件一个字符到readbuf数组中                read_count = read(fd,readbuf,1);                //把字符串转化为数字                logFlag = atoi(readbuf);                printf("logFlag = %d\n",logFlag);                close(fd);            }            if(event->mask & IN_OPEN)   printf("IN_OPEN\n");              //displayInotifyEvent(event);              p+=sizeof(struct inotify_event) + event->len;  //一个事件的长度,p指向下一个事件        }      }      return 0;  }  

gcc编译程序

gcc creatfile.c -o creatfile

执行./creatfile printlog &,后台运行程序

ubuntu:~/test/69test$ ./creatfile printlog &[1] 22392ubuntu:~/test/69test$ Watching printlog using wd 1

修改文件printlog

ubuntu:~/test/69test$ echo 1 > printlog 

实验结果

Read 32bytes from inotify fdIN_MODIFYlogFlag = 1IN_MODIFYlogFlag = 1

从上面的程序和结果中可以看到,程序定义了一个logFlag全局变量,然后调用inotify_init函数创建inotify实例,接着调用inotify_add_watch函数监听文件printlog是否被修改。当监听到文件printlog被修改时,打开文件printlog,把文件的内容转化为整数给logFlag全局变量。这是简单的使用inotify机制的一个例子。
回到最初的那个问题:“一个长期运行的Linux程序,想在不退出运行的情况下,通过某种机制,可以让程序知道要不要打印出log”。
这里我们可以创建一个线程,然后让这个线程监视printlog这个文件,像上面的那个例子一样,写文件内容到全局变量logFlag,然后我们每次打印log之前判断一下这个全局变量logFlag是否等于我们约定的那个值,如果是就打印log,如果不是就return。多线程读写logFlag全局变量要用linux锁机制。这样基本能实现我们打印log的需求。

原创粉丝点击