【Linux】【INotify与Epoll机制】

 系列文章

可跳转到下面链接查看下表所有内容https://blog.csdn.net/handsomethefirst/article/details/138226266?spm=1001.2014.3001.5501文章浏览阅读2次。系列文章大全https://blog.csdn.net/handsomethefirst/article/details/138226266?spm=1001.2014.3001.5501


1.INotify

1.1 简介

INotify是一个Linux内核所提供的一种文件系统变化通知机制。它可以为应用程序监控文件系统的变化,如文件的创建、删除、读写等。当监听的文件发生相应的操作时,可以通过read函数从INotify中读取事件。

1.2 如何使用?

第一步:通过inotify_init()创建一个inotify对象。
第二步:通过inotify_add_watch将一个或多个监听添加到inotify对象中。
第三步:当事件发生时,通过read()函数从inotify对象中读取监听事件。
第四步:消费事件。

//第一步:通过inotify_init()创建一个inotify对象。
int notifyfd = inotify_init();

//第二步:监听此notify fd
int watchnotifyfd = inotify_add_watch(notifyfd,"/data", IN_CREATE | IN_DELETE);
    //监听data目录下是否有设备的添加和删除事件
    if (watchnotifyfd < 0) {
        
        return 0;
    }

//第三步:当事件发生时,通过read()函数从inotify对象中读取监听事件。
struct inotify_event *event;
char *p;
char buf[256] ={0};
ssize_t recvLen = read(notify_fd, buf, DEFAULT_BUF_LEN);//从notify的fd中读取消息等到buf中
//返回的是读取的字节数
         
for(p = buf;p<buf +recvLen;)
{
      event = (struct inotify_event*) p;
      uint32_t len = event->len;//len是event中name的长度
      event->name[len] = '\0';//给字符数组最后一位添加\0
             
      if(event ->mask & IN_DELETE)
      {//此时是删除设置事件                           
      }
      else if(event ->mask & IN_CREATE)//此时是增加设备事件
       {
       //第四步:消费事件
       }
             
       p += sizeof(struct inotify_event) + event->len;//指针向后移动,处理下一个事件                      
}               

1.3 原理介绍

INotify主要有两个基本对象。分别是inotify对象和watch对象。

1.3.1 inotify对象

Inotify对象对应了一个队列,应用程序可以向inotify对象添加多个监听。当监听的文件发生相应的操作时,可以通过read函数从INotify中读取事件。

inotify_init 是一个 Linux 系统调用,用于初始化 inotify实例并返回一个文件描述符,该文件描述符可以用于后续的inotify事件监听。inotify是Linux内核提供的一种文件系统事件监控机制,它允许应用程序监控文件或目录的更改,包括文件的打开、关闭、修改、移动或删除等操作。

创建方法如下:

int inotifyFd = inotify_init();
//inotify_init(void),该函数没有参数,并返回一个非负整数作为inotify实例的文件描述符。
//如果初始化失败,它会返回-1,并设置全局变量errno来指示错误原因。
//一旦你获得了inotify实例的文件描述符,你可以使用inotify_add_watch函数来指定要监控的文件或目录,
//以及你感兴趣的事件类型(如IN_MODIFY、IN_DELETE、IN_CREATE等)。
//然后,你可以使用read系统调用来从inotify实例的文件描述符中读取事件。

1.3.2 Watch对象

watch对象则用来描述文件系统的变化事件的监听。其包含监听目标和事件掩码。监听目标可以文件也可以是文件夹。事件掩码则是监听的事件的类型。文件的创建是IN_CREATE,文件的删除是IN_DELETE等。

其创建方法和inotify对象关联如下:

int watchid = inotify_add_watch (inotifyFd, “/dev/input”,IN_CREATE);
//原型:int inotify_add_watch(int fd, const char *pathname, uint32_t mask);
//参数说明:
//fd:由 inotify_init 返回的 inotify 实例的文件描述符。
//pathname:指向要监视的文件或目录路径的字符串。
//mask:一个位掩码,表示你感兴趣的inotify事件类型。可以使用预定义的宏(如 IN_CREATE、IN_DELETE、IN_MODIFY 等)来指定多个事件类型。

//inotify_add_watch 返回一个非负整数,称为监视描述符(watch descriptor),它唯一标识了新添加的监视对象。
//如果添加监视失败,它将返回 -1,并设置全局变量 errno 来指示错误原因。

1.3.3 read 函数

当监听的事件发生时(即/dev/input下有新的设备创建),会将相应的事件写入inotifyfd所描述的对象中,此时可以通过read函数从此fd中读取事件信息。

事件信息存在固定的格式,格式如下:

struct inotify_event
{
    int wd;          /* 事件对应的Watch对象的描述符.  */
    uint32_t mask;   /* 事件类型,例如文件被删除,此处值为IN_DELETE.  */
    uint32_t cookie; /* 同步两个事件的缓存  */
    uint32_t len;    /* name字段的长度  */
    char name __flexarr;
    // 可变长的字段,用于存储产生此事件的文件路径 * /
};

read() 函数用于从 inotify 文件描述符中读取事件。read函数如下:

char buf[256] ={0};
size_t recvLen = read (inotifyFd, buf, 256);
//原型:ssize_t read(int fd, void *buf, size_t count);
//fd: 这是通过 inotify_init() 初始化的 inotify 文件描述符。
//buf: 这是一个指向缓冲区的指针,该缓冲区用于存储读取到的事件。
//count: 这是你希望从 fd 中读取的最大字节数。
//返回值是实际读取的字节数,或者在出错时返回 -1。

然后可以将读取的buf,转化为inotify_event的数据格式,然后读取其信息。
转化如下:

ssize_t recvLen = read(notify_fd, buf, DEFAULT_BUF_LEN);//从notify的fd中读取消息等到buf中
         //返回的是读取的字节数
         
         for(p = buf;p<buf +recvLen;)
         {
             event = (struct inotify_event*) p;
             uint32_t len = event->len;//len是event中name的长度
             event->name[len] = '\0';//给字符数组最后一位添加\0
             
             if(event ->mask & IN_DELETE){//此时是删除设置事件                           
             }
             else if(event ->mask & IN_CREATE)//此时是增加设备事件
             {
                    //此处执行相关逻辑          
             }
             
             p += sizeof(struct inotify_event) + event->len;//指针向后移动,处理下一个事件                      
         }      

1.4 总结

第一步:通过inotify_init()创建一个inotify对象。
第二步:通过inotify_add_watch将一个或多个监听添加到inotify对象中。
第三步:当事件发生时,通过read()函数从inotify对象中读取监听事件。
第四步:消费事件。

但此时存在一个问题,INotify机制当事件发生时,并不是通过回调函数来通知的,而是调用者主动调用read方法读取事件,那么我们如何及时的判断事件的发生呢?避免过早读取,事件未发生,而过晚读取,效率低下,那么此时就需要用到Epoll机制了。


2.Epoll

2.1 简介

epoll是Linux内核为处理大批量文件描述符而作的改进的poll,是Linux下多路复用IO接口select/poll的增强版本。它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。Epoll可以使用一次等待监听多个描述符的可读/可写状态。当文件描述符的状态发生变化时,内核会将这个事件通知给用户空间,用户空间再根据事件类型进行相应的处理。

2.2 如何使用?

此处结合上文的notify一起使用。
第一步:创建epoll
第二步:创建notify对象,主要用于监听设备节点的增加和删除
第三步:监听此notify fd
第四步:创建此notify对应的epoll_event对象,并设置相应的值
第五步:将此notify的epoll_event添加到epoll中
第六步:阻塞,等待事件发生
第七步:处理消息notify中的消息

#define EPOLL_EVENT_MAX = 14  //代表epoll最大监听的事件数量
int main(){
    
    //第一步:创建epoll
    int Epollfd = epoll_create(EPOLL_EVENT_MAX); 
    if (-1 == Epollfd) {//创建epoll失败
        return 0;
    }
    
    
    //第二步:创建notify对象,主要用于监听设备节点的增加和删除
    int notifyfd = inotify_init();
    if (notifyfd == -1) 
    {
        return 0;
    }
    
    //第三步:监听此notify fd
    int watchnotifyfd = inotify_add_watch(notifyfd,"/data", IN_CREATE | IN_DELETE);
    //监听data目录下是否有设备的添加和删除事件
    if (watchnotifyfd < 0) {
        
        return 0;
    }
    
    //第四步:创建此notify对应的epoll_event对象,并设置相应的值
    epoll_event m_ev;
    m_ev.data.fd = notifyfd;//此处是notify的fd对象
    m_ev.events =EPOLLIN;//epoll监听固定是此notify的fd下有事件可读,如果有,就代表发生了设备的增加和删除
    
    
    //第五步:将此notify的epoll_event添加到epoll中
    if (-1 == epoll_ctl(Epollfd, EPOLL_CTL_ADD, m_ev.data.fd, &m_ev)) {
           
            return false;
     }
     
    //第六步:阻塞,等待事件发生
    epoll_event allevents[EPOLL_EVENT_MAX];//创建用于接收fd事件的epoll_event数组
    memset(allevents, 0, sizeof(allevents));
    
     int nfds = epoll_wait(Epollfd, allevents, EPOLL_EVENT_MAX, 300);//300毫秒
     
     
     
     //第七步:处理返回的消息
     if (nfds < 0) {
//代表epoll出错       
           close(Epollfd);
           return -1;
     }
       
     if (nfds == 0) {
        //printf("[%d] %s:epoll_wait timeout.", __LINE__, __func__);
        
      }
      
      for(int i = 0; i<nfds; i++)
      {
         int notify_fd = allevents[i].data.id;
         struct inotify_event *event;
         char *p;
         char buf[256] ={0};
         ssize_t recvLen = read(notify_fd, buf, DEFAULT_BUF_LEN);//从notify的fd中读取消息等到buf中
         //返回的是读取的字节数
         
         for(p = buf;p<buf +recvLen;)
         {
             event = (struct inotify_event*) p;
             uint32_t len = event->len;//len是event中name的长度
             event->name[len] = '\0';//给字符数组最后一位添加\0
             
             if(event ->mask & IN_DELETE){//此时是删除设置事件                           
             }
             else if(event ->mask & IN_CREATE)//此时是增加设备事件
             {
                              
             }
             
             p += sizeof(struct inotify_event) + event->len;//指针向后移动,处理下一个事件                      
         }                                  
      }

    return 0;
}

2.3 原理介绍

2.3.1 创建epoll对象

epoll_create(int max_fds):创建一个epoll对象的描述符,之后对epoll的操作均使用这个描述符完成。

int epoll_create(int size);
//size 参数是一个提示值,用来告诉内核这个 epoll 实例期望同时监视的文件描述符的大致数量
//epoll_create 的返回值是一个非负整数,表示新创建的 epoll 实例的文件描述符(epfd)。
//如果函数调用成功,你可以使用这个文件描述符来引用和操作 epoll 实例。
//如果函数调用失败,它会返回 -1,

2.3.2 填写epoll_event结构体

epoll_event结构体用于描述监控的事件,并通过epoll_ctl将监听的inotify的fd对象和此结构体关联起来,当监听的inotify的fd对象有数据可读时,会返回epoll_event结构体数组。

epoll_event结构体定义在<linux/eventpoll.h>头文件中,用于描述一个文件描述符上的事件。它的定义如下:

struct epoll_event {  
    uint32_t events;     /* 事件类型 */  
    epoll_data_t data;   /* 附加的数据 */  
};

其中,events字段表示感兴趣的事件类型,它是一个位字段组合,可以使用位或操作符(|)来设置多个事件。事件类型包括:
EPOLLIN:表示可读事件(即该文件描述符中有数据可读)。
EPOLLOUT:表示可写事件(即该文件描述符可以进行写操作)。
EPOLLRDHUP:表示对端关闭连接或者半关闭连接。
EPOLLERR:表示发生错误。
EPOLLHUP:表示发生挂起(例如对方套接字已经关闭,或者发生了某种异常情况)。

epoll_data_t是一个联合体(union),用于存储与当前被监听文件描述符相关的用户数据信息。它的定义如下:

typedef union epoll_data {  
    void *ptr;  //ptr可以指向任何类型的用户数据。
    int fd;  //fd表示文件描述符。一般会写Inotify的fd,描述当Inotify监听的事件发生时,要从Inotify的fd对象中读取消息
    uint32_t u32;  //u32和u64分别表示一个32位和64位的无符号整数。
    uint64_t u64;  
} epoll_data_t;

用户可以将自己需要的数据存放到这个字段中,当事件触发时,epoll系统调用会返回这个数据,以便用户处理事件。

2.3.3 epoll_ctl()函数

将事件注册进epoll对象。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//其中:
//epfd是由epoll_create或epoll_create1生成的epoll文件描述符。
//op是要执行的操作,有三个可能的值:EPOLL_CTL_ADD(向epoll实例注册新的文件描述符)、EPOLL_CTL_MOD(修改已注册的文件描述符的事件)和EPOLL_CTL_DEL(从epoll实例中删除文件描述符)。
//fd是需要添加、修改或删除的文件描述符。
//event是一个指向epoll_event结构体的指针,用于指定与文件描述符关联的事件。
//epoll_ctl函数在成功时返回0,失败时返回-1并设置errno以指示错误。

2.3.4 epoll_ctl()函数

epoll_wait()函数等待就监听事件的发生.
此处如何理解监听事件:epoll监听的是Inotify对应的fd对象下是否有数据可读。而何时Inotify对应的fd对象下有数据?
答:当Inotify 对象监听的/dev/input下有设备的创建和删除时。

函数原型如下:

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
//其中各参数的含义如下:
//- epfd:由epoll_create或epoll_create1创建并返回的epoll句柄,即epoll实例的文件描述符。
//- events:指向一个epoll_event结构体的数组的指针,用于存储触发事件的数据。当函数返回时,这个数组将被填充上实际触发事件的信息。
//- maxevents:events数组的大小,即最多可以存放多少个事件。这个值必须大于零。
//- timeout:等待超时时间,单位为毫秒。传递-1表示无限等待,直到有事件发生;传递0表示立即返回结果,不阻塞。
//返回值有以下几种情况:
//1.正数:表示返回的事件数量。这些事件被存储在events数组中,你可以遍历这个数组来处理每个事件。
//2.0:表示在指定的timeout时间内没有事件发生。
//3.-1:表示发生错误。你可以使用errno来获取具体的错误原因。常见的错误包括EBADF(epfd不是一个有效的文件描述符)、EFAULT(events指针指向的地址不在进程的地址空间中)等。
//因此,当你调用epoll_wait时,你应该检查其返回值以确定是否有事件需要处理,或者是否发生了错误。

2.4 总结

1.通过epoll_create()创建一个epoll对象。
2.为需要监听的描述符填充epoll_events结构体,并使用epoll_ctl()注册到epoll对象中。
3.使用epoll_wait()等待事件的发生。
4.当描述符对应的事件发生时,根据返回的epoll_events结构体数组进行处理。

  • 33
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值