在前面的博客《用可变参数扩展printf》中讨论到如何在应用中控制log的输出。
我们假设的情景是,一个长期运行的Linux程序,想在不退出运行的情况下,通过某种机制,可以让程序知道要不要打印出log 。
我们当时的实现是:
这样子我们在linux系统上,如果不想打印出这些log,可以向这个文件写其他标志位。
但是,这种做法效率太低了。那如何解决上面的问题呢?
有两种做法:
在本节中,讲的是linux inotify机制。
参考资料http://www.man7.org/linux/man-pages/man7/inotify.7.html
Inotify机制作用:
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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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] 22392
ubuntu:~/test/69test$ Watching printlog using wd 1
修改文件printlog
ubuntu:~/test/69test$ echo 1 > printlog
实验结果
Read 32bytes from inotify fd
IN_MODIFY
logFlag = 1
IN_MODIFY
logFlag = 1
从上面的程序和结果中可以看到,程序定义了一个logFlag全局变量,然后调用inotify_init函数创建inotify实例,接着调用inotify_add_watch函数监听文件printlog是否被修改。当监听到文件printlog被修改时,打开文件printlog,把文件的内容转化为整数给logFlag全局变量。这是简单的使用inotify机制的一个例子。
回到最初的那个问题:“一个长期运行的Linux程序,想在不退出运行的情况下,通过某种机制,可以让程序知道要不要打印出log”。
这里我们可以创建一个线程,然后让这个线程监视printlog这个文件,像上面的那个例子一样,写文件内容到全局变量logFlag,然后我们每次打印log之前判断一下这个全局变量logFlag是否等于我们约定的那个值,如果是就打印log,如果不是就return。多线程读写logFlag全局变量要用linux锁机制。这样基本能实现我们打印log的需求。