Linux内核之inotify与epoll的具体例程实现

常见两种情况:
①:键盘即插即用:怎么检测键盘接入与拔出?
1.hotplug:内核发现键盘接入或者拔出—->启动hotplug进程—>将消息传送给输入系统
2.inotify:输入系统使用inotify检测目录: /dev/input
②:可使用多键盘:怎么知道哪个键盘被按下?采用epoll
意思就是可以检测多个文件:有无数据供读出,有无数据供写入

一、inotify的使用(监测目录/文件的变化)
①:fd = inotify_init()
②: inotify_add_watch( 目录/文件, 创建/删除)
③:read(fd )
返回结果:多个下列结构体,而且结构体长度可能不一样

struct inotify_event {
    __s32        wd;        /* watch descriptor */
    __u32        mask;        /* watch mask */ Garmen:检测发生了什么变化 __u32 cookie;        /* cookie to synchronize two events */
    __u32        len;        /* length (including nulls) of name */ Garmen:name的长度
    char        name[0];    /* stub for possible name */ Garmen:发生变化的文件 };

通过inotify监测系统目录下一个文件夹的文件的添加和删除

#include <unistd.h>
#include <stdio.h>
#include <sys/inotify.h>
#include <string.h>
#include <errno.h>


/* *参考: frameworks\native\services\inputflinger\EventHub.cpp */

/*Usage: inotify <dir> */

int read_process_inotify_fd(int fd)
{
    int res;
    char event_buf[512];
    int event_size;
    int event_pos = 0;
    struct inotify_event *event;

    /* read */   
    res = read(fd, event_buf, sizeof(event_buf));

    if(res < (int)sizeof(*event)) {
        if(errno == EINTR)
            return 0;
        printf("could not get event, %s\n", strerror(errno));
        return -1;
    }

    /* process * 读到的数据是1个或多个inotify_event * 它们的长度不一样 * 逐个处理 */

    while(res >= (int)sizeof(*event)) {
        event = (struct inotify_event *)(event_buf + event_pos);
        //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : "");
        if(event->len) {
            if(event->mask & IN_CREATE) {
                printf("create file: %s\n", event->name);
            } else {
                printf("delete file: %s\n", event->name);
            }
        }
        event_size = sizeof(*event) + event->len;
        res -= event_size;
        event_pos += event_size;
    }
    return 0;
}

int main(int argc, char **argv)
{
    int mINotifyFd;
    int result;

    if (argc != 2)
    {
        printf("Usage: %s <dir>\n", argv[0]);
        return -1;
    }

    /* inotify_init */

    mINotifyFd = inotify_init();

    /* add watch */
    result = inotify_add_watch(mINotifyFd, argv[1], IN_DELETE | IN_CREATE);

    /* read */
    while (1)
    {
        read_process_inotify_fd(mINotifyFd);
    }

    return 0;
}

使用方法:

inotify.c
gcc -o inotify inotify.c
mkdir tmp
./inotify tmp &

echo > tmp/1
echo > tmp/2
rm tmp/1 tmp/2

二、epoll
epoll的接口非常简单,一共就三个函数:

1、int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

struct epoll_event {
    __uint32_t events; /* Epoll events */
    epoll_data_t data; /* User data variable */
};

events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

3、int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

epoll的用法:有无数据供读出,有无数据供写入
①:epoll_create(创建fd)
②:对每个文件,执行epoll_ctl(…, EPOLL_CTL_ADD)
表示要检测它
③:epoll_wait(等待某个文件可用)
④:不想再检测文件:epoll_ctl(…, EPOLL_CTL_DEL, ….)

通过epoll监测某个文件的数据的写入:

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


#if 0
typedef union epoll_data {
   void        *ptr;
   int          fd;
   uint32_t     u32;
   uint64_t     u64;
} epoll_data_t;

#endif


#define DATA_MAX_LEN 500

/* usage: epoll <file1> [file2] [file3] ... */

int add_to_epoll(int fd, int epollFd)
{
    int result;
    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.fd = fd;
    result = epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &eventItem);
    return result;
}

void rm_from_epoll(int fd, int epollFd)
{
    epoll_ctl(epollFd, EPOLL_CTL_DEL, fd, NULL);
}


int main(int argc, char **argv)
{
    int mEpollFd;
    int i;
    char buf[DATA_MAX_LEN];

    // Maximum number of signalled FDs to handle at a time.
    static const int EPOLL_MAX_EVENTS = 16;

    // The array of pending epoll events and the index of the next event to be handled.
    struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS];


    if (argc < 2)
    {
        printf("Usage: %s <file1> [file2] [file3] ...\n", argv[0]);
        return -1;
    }

    /* epoll_create */
    mEpollFd = epoll_create(8);

    /* for each file: * open it * add it to epoll: epoll_ctl(...EPOLL_CTL_ADD...) */
    for (i = 1; i < argc; i++)     
    {
        //int tmpFd = open(argv[i], O_RDONLY|O_NONBLOCK);
        int tmpFd = open(argv[i], O_RDWR);
        add_to_epoll(tmpFd, mEpollFd);
    }

    /* epoll_wait */
    while (1)
    {

        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, -1);
        for (i = 0; i < pollResult; i++)
        {
            printf("Reason: 0x%x\n", mPendingEventItems[i].events);
            int len = read(mPendingEventItems[i].data.fd, buf, DATA_MAX_LEN);
            buf[len] = '\0';
            printf("get data: %s\n", buf);
            //sleep(3);
        }

    }

    return 0;
}

使用方法:

epoll.c
gcc -o epoll epoll.c
mkdir tmp
mkfifo  tmp/1 tmp/2 tmp/3
./epoll tmp/1 tmp/2 tmp/3 &
echo aaa > tmp/1
echo bbb > tmp/2

三:编写inotify_epoll.c, 用它来监测tmp/目录: 有文件被创建/删除, 有文件可读出数据
a. 当在tmp/下创建文件时, 会立刻监测到,并且使用epoll监测该文件
b. 当文件有数据时,读出数据
c. 当tmp/下文件被删除时,会立刻监测到,并且把它从epoll中移除不再监测

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


#define DATA_MAX_LEN 500
#define MAX_FILES 1000

static char *base_dir;
static char *epoll_files[MAX_FILES];

#if 0
typedef union epoll_data {
   void        *ptr;
   int          fd;
   uint32_t     u32;
   uint64_t     u64;
} epoll_data_t;

#endif



/* usage: epoll <file1> [file2] [file3] ... */

int add_to_epoll(int fd, int epollFd)
{
    int result;
    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.fd = fd;
    result = epoll_ctl(epollFd, EPOLL_CTL_ADD, fd, &eventItem);
    return result;
}

void rm_from_epoll(int fd, int epollFd)
{
    epoll_ctl(epollFd, EPOLL_CTL_DEL, fd, NULL);
}

int get_epoll_fd_for_name(char *name)
{
    int i;
    char name_to_find[500];
    sprintf(name_to_find, "%s/%s", base_dir, name);

    for (i = 0; i < MAX_FILES; i++)
    {
        if (!epoll_files[i])
            continue;

        if (!strcmp(epoll_files[i], name_to_find))
            return i;
    }
    return -1;
}


/* *参考: frameworks\native\services\inputflinger\EventHub.cpp */

/*Usage: inotify <dir> */

int read_process_inotify_fd(int mINotifyFd, int mEpollFd)
{
    int res;
    char event_buf[512];
    int event_size;
    int event_pos = 0;
    struct inotify_event *event;

    /* read */   
    res = read(mINotifyFd, event_buf, sizeof(event_buf));

    if(res < (int)sizeof(*event)) {
        if(errno == EINTR)
            return 0;
        printf("could not get event, %s\n", strerror(errno));
        return -1;
    }

    /* process * 读到的数据是1个或多个inotify_event * 它们的长度不一样 * 逐个处理 */

    while(res >= (int)sizeof(*event)) {
        event = (struct inotify_event *)(event_buf + event_pos);
        //printf("%d: %08x \"%s\"\n", event->wd, event->mask, event->len ? event->name : "");
        if(event->len) {
            if(event->mask & IN_CREATE) {     //Garmen:如果是创建文件的话,就把文件打开,然后添加到epool中去
                printf("create file: %s\n", event->name);
                char *name = malloc(512);
                sprintf(name, "%s/%s", base_dir, event->name);
                int tmpFd = open(name, O_RDWR);     //Garmen:fmpFd是文件句柄

                printf("add to epoll: %s\n", name);
                add_to_epoll(tmpFd, mEpollFd);

                epoll_files[tmpFd] = name;     //Garmen:使用数组建立文件名与文件句柄的联系,它以文件句柄为下标指向一个名字,当我们创建一个文件时候,通过mINotifyFd读出名字

            } else {
                printf("delete file: %s\n", event->name);
                int tmpFd = get_epoll_fd_for_name(event->name);
                if (tmpFd >= 0)
                {
                    printf("remove from epoll: %s/%s\n", base_dir, event->name);
                    rm_from_epoll(tmpFd, mEpollFd);
                    free(epoll_files[tmpFd]);
                }
            }
        }
        event_size = sizeof(*event) + event->len;
        res -= event_size;
        event_pos += event_size;
    }
    return 0;
}


int main(int argc, char **argv)
{
    int mEpollFd;
    int i;
    char buf[DATA_MAX_LEN];
    int mINotifyFd;
    int result;

    // Maximum number of signalled FDs to handle at a time.
    static const int EPOLL_MAX_EVENTS = 16;

    // The array of pending epoll events and the index of the next event to be handled.
    struct epoll_event mPendingEventItems[EPOLL_MAX_EVENTS];


    if (argc != 2)
    {
        printf("Usage: %s <tmp>\n", argv[0]);
        return -1;
    }

    base_dir = argv[1];

    /* epoll_create */
    mEpollFd = epoll_create(8);

    /* inotify_init */

    mINotifyFd = inotify_init();

    /* add watch */
    result = inotify_add_watch(mINotifyFd, base_dir, IN_DELETE | IN_CREATE);//Garmen:监视整个文件夹,当有文件添加或者删除,得到它的句柄mINotifyFd

    add_to_epoll(mINotifyFd, mEpollFd);//Garmen:然后从上面得到句柄mINotifyFd,再添加到epoll进行监视该文件夹


    /* epoll_wait */
    while (1)
    {
        /*以阻塞的方式监测,mEpollFd其实包含了文件夹的fd,还有将要创建的文件的fd,都在epoll的监测之中*/
        int pollResult = epoll_wait(mEpollFd, mPendingEventItems, EPOLL_MAX_EVENTS, -1);
        for (i = 0; i < pollResult; i++)
        {

            /*Garmen:通过打印fd明白这条分支是监测文件夹,有无文件的添加或者删除*/
            if (mPendingEventItems[i].data.fd == mINotifyFd)    
            {
                read_process_inotify_fd(mINotifyFd, mEpollFd);
            }
            /*Garmen:这条分支是监测文件的有无数据写入,有的话将数据读出*/
            else
            {
                printf("Reason: 0x%x\n", mPendingEventItems[i].events);
                int len = read(mPendingEventItems[i].data.fd, buf, DATA_MAX_LEN);
                buf[len] = '\0';
                printf("get data: %s\n", buf);
                //sleep(3);
            }
        }

    }

    return 0;
}

注意:
①:base_dir是文件夹名,例如tmp/
使用方法是./inotify_epoll tmp/ &
而base_dir = argv[1]; 故第2个参数是tmp/:即是文件夹的名字
②:inotify_add_watch() 监视了整个文件夹的添加或者删除

使用方法:

inotify_epoll.c
gcc -o inotify_epoll inotify_epoll.c
mkdir tmp
./inotify_epoll tmp/ &
mkfifo  tmp/1 tmp/2 tmp/3
echo aaa > tmp/1
echo bbb > tmp/2
rm tmp/3

你可能感兴趣的:(linux,kernel,epoll,内核)