面试必问的epoll技术,从内核源码出发彻底搞懂epoll

面试必问的epoll技术,从内核源码出发彻底搞懂epoll,第1张

epoll是linux中IO多路复用的一种机制,I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写 *** 作。当然linux中IO多路复用不仅仅是epoll,其他多路复用机制还有select、poll,但是接下来介绍epoll的内核实现。

events可以是以下几个宏的集合:

epoll相比select/poll的优势

epoll相关的内核代码在fs/eventpollc文件中,下面分别分析epoll_create、epoll_ctl和epoll_wait三个函数在内核中的实现,分析所用linux内核源码为412版本。

epoll_create用于创建一个epoll的句柄,其在内核的系统实现如下:

sys_epoll_create:

可见,我们在调用epoll_create时,传入的size参数,仅仅是用来判断是否小于等于0,之后再也没有其他用处。

整个函数就3行代码,真正的工作还是放在sys_epoll_create1函数中。

sys_epoll_create -> sys_epoll_create1:

sys_epoll_create1 函数流程如下:

sys_epoll_create -> sys_epoll_create1 -> ep_alloc:

sys_epoll_create -> sys_epoll_create1 -> ep_alloc -> get_unused_fd_flags:

linux内核中,current是个宏,返回的是一个task_struct结构(我们称之为进程描述符)的变量,表示的是当前进程,进程打开的文件资源保存在进程描述符的files成员里面,所以current->files返回的当前进程打开的文件资源。rlimit(RLIMIT_NOFILE) 函数获取的是当前进程可以打开的最大文件描述符数,这个值可以设置,默认是1024。

相关视频推荐:

支撑亿级io的底层基石 epoll实战揭秘

网络原理tcp/udp,网络编程epoll/reactor,面试中正经“八股文”

学习地址:C/C++Linux服务器开发/后台架构师零声教育-学习视频教程-腾讯课堂

需要更多C/C++ Linux服务器架构师学习资料加群 812855908 获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

__alloc_fd的工作是为进程在[start,end)之间(备注:这里start为0, end为进程可以打开的最大文件描述符数)分配一个可用的文件描述符,这里就不继续深入下去了,代码如下:

sys_epoll_create -> sys_epoll_create1 -> ep_alloc -> get_unused_fd_flags -> __alloc_fd:

然后,epoll_create1会调用anon_inode_getfile,创建一个file结构,如下:

sys_epoll_create -> sys_epoll_create1 -> anon_inode_getfile:

anon_inode_getfile函数中首先会alloc一个file结构和一个dentry结构,然后将该file结构与一个匿名inode节点anon_inode_inode挂钩在一起,这里要注意的是,在调用anon_inode_getfile函数申请file结构时,传入了前面申请的eventpoll结构的ep变量,申请的file->private_data会指向这个ep变量,同时,在anon_inode_getfile函数返回来后,ep->file会指向该函数申请的file结构变量。

简要说一下file/dentry/inode,当进程打开一个文件时,内核就会为该进程分配一个file结构,表示打开的文件在进程的上下文,然后应用程序会通过一个int类型的文件描述符来访问这个结构,实际上内核的进程里面维护一个file结构的数组,而文件描述符就是相应的file结构在数组中的下标。

dentry结构(称之为“目录项”)记录着文件的各种属性,比如文件名、访问权限等,每个文件都只有一个dentry结构,然后一个进程可以多次打开一个文件,多个进程也可以打开同一个文件,这些情况,内核都会申请多个file结构,建立多个文件上下文。但是,对同一个文件来说,无论打开多少次,内核只会为该文件分配一个dentry。所以,file结构与dentry结构的关系是多对一的。

同时,每个文件除了有一个dentry目录项结构外,还有一个索引节点inode结构,里面记录文件在存储介质上的位置和分布等信息,每个文件在内核中只分配一个inode。 dentry与inode描述的目标是不同的,一个文件可能会有好几个文件名(比如链接文件),通过不同文件名访问同一个文件的权限也可能不同。dentry文件所代表的是逻辑意义上的文件,记录的是其逻辑上的属性,而inode结构所代表的是其物理意义上的文件,记录的是其物理上的属性。dentry与inode结构的关系是多对一的关系。

sys_epoll_create -> sys_epoll_create1 -> fd_install:

总结epoll_create函数所做的事:调用epoll_create后,在内核中分配一个eventpoll结构和代表epoll文件的file结构,并且将这两个结构关联在一块,同时,返回一个也与file结构相关联的epoll文件描述符fd。当应用程序 *** 作epoll时,需要传入一个epoll文件描述符fd,内核根据这个fd,找到epoll的file结构,然后通过file,获取之前epoll_create申请eventpoll结构变量,epoll相关的重要信息都存储在这个结构里面。接下来,所有epoll接口函数的 *** 作,都是在eventpoll结构变量上进行的。

所以,epoll_create的作用就是为进程在内核中建立一个从epoll文件描述符到eventpoll结构变量的通道。

epoll_ctl接口的作用是添加/修改/删除文件的监听事件,内核代码如下:

sys_epoll_ctl:

根据前面对epoll_ctl接口的介绍,op是对epoll *** 作的动作(添加/修改/删除事件),ep_op_has_event(op)判断是否不是删除 *** 作,如果op != EPOLL_CTL_DEL为true,则需要调用copy_from_user函数将用户空间传过来的event事件拷贝到内核的epds变量中。因为,只有删除 *** 作,内核不需要使用进程传入的event事件。

接着连续调用两次fdget分别获取epoll文件和被监听文件(以下称为目标文件)的file结构变量(备注:该函数返回fd结构变量,fd结构包含file结构)。

接下来就是对参数的一些检查,出现如下情况,就可以认为传入的参数有问题,直接返回出错:

当然下面还有一些关于 *** 作动作如果是添加 *** 作的判断,这里不做解释,比较简单,自行阅读。

在ep里面,维护着一个红黑树,每次添加注册事件时,都会申请一个epitem结构的变量表示事件的监听项,然后插入ep的红黑树里面。在epoll_ctl里面,会调用ep_find函数从ep的红黑树里面查找目标文件表示的监听项,返回的监听项可能为空。

接下来switch这块区域的代码就是整个epoll_ctl函数的核心,对op进行switch出来的有添加(EPOLL_CTL_ADD)、删除(EPOLL_CTL_DEL)和修改(EPOLL_CTL_MOD)三种情况,这里我以添加为例讲解,其他两种情况类似,知道了如何添加监听事件,其他删除和修改监听事件都可以举一反三。

为目标文件添加监控事件时,首先要保证当前ep里面还没有对该目标文件进行监听,如果存在(epi不为空),就返回-EEXIST错误。否则说明参数正常,然后先默认设置对目标文件的POLLERR和POLLHUP监听事件,然后调用ep_insert函数,将对目标文件的监听事件插入到ep维护的红黑树里面:

sys_epoll_ctl -> ep_insert:

前面说过,对目标文件的监听是由一个epitem结构的监听项变量维护的,所以在ep_insert函数里面,首先调用kmem_cache_alloc函数,从slab分配器里面分配一个epitem结构监听项,然后对该结构进行初始化,这里也没有什么好说的。我们接下来看ep_item_poll这个函数调用:

sys_epoll_ctl -> ep_insert -> ep_item_poll:

ep_item_poll函数里面,调用目标文件的poll函数,这个函数针对不同的目标文件而指向不同的函数,如果目标文件为套接字的话,这个poll就指向sock_poll,而如果目标文件为tcp套接字来说,这个poll就是tcp_poll函数。虽然poll指向的函数可能会不同,但是其作用都是一样的,就是获取目标文件当前产生的事件位,并且将监听项绑定到目标文件的poll钩子里面(最重要的是注册ep_ptable_queue_proc这个poll callback回调函数),这步 *** 作完成后,以后目标文件产生事件就会调用ep_ptable_queue_proc回调函数。

接下来,调用list_add_tail_rcu将当前监听项添加到目标文件的f_ep_links链表里面,该链表是目标文件的epoll钩子链表,所有对该目标文件进行监听的监听项都会加入到该链表里面。

然后就是调用ep_rbtree_insert,将epi监听项添加到ep维护的红黑树里面,这里不做解释,代码如下:

sys_epoll_ctl -> ep_insert -> ep_rbtree_insert:

前面提到,ep_insert有调用ep_item_poll去获取目标文件产生的事件位,在调用epoll_ctl前这段时间,可能会产生相关进程需要监听的事件,如果有监听的事件产生,(revents & event->events 为 true),并且目标文件相关的监听项没有链接到ep的准备链表rdlist里面的话,就将该监听项添加到ep的rdlist准备链表里面,rdlist链接的是该epoll描述符监听的所有已经就绪的目标文件的监听项。并且,如果有任务在等待产生事件时,就调用wake_up_locked函数唤醒所有正在等待的任务,处理相应的事件。当进程调用epoll_wait时,该进程就出现在ep的wq等待队列里面。接下来讲解epoll_wait函数。

总结epoll_ctl函数:该函数根据监听的事件,为目标文件申请一个监听项,并将该监听项挂人到eventpoll结构的红黑树里面。

epoll_wait等待事件的产生,内核代码如下:

sys_epoll_wait:

首先是对进程传进来的一些参数的检查:

参数全部检查合格后,接下来就调用ep_poll函数进行真正的处理:

sys_epoll_wait -> ep_poll:

ep_poll中首先是对等待时间的处理,timeout超时时间以ms为单位,timeout大于0,说明等待timeout时间后超时,如果timeout等于0,函数不阻塞,直接返回,小于0的情况,是永久阻塞,直到有事件产生才返回。

当没有事件产生时((!ep_events_available(ep))为true),调用__add_wait_queue_exclusive函数将当前进程加入到ep->wq等待队列里面,然后在一个无限for循环里面,首先调用set_current_state(TASK_INTERRUPTIBLE),将当前进程设置为可中断的睡眠状态,然后当前进程就让出cpu,进入睡眠,直到有其他进程调用wake_up或者有中断信号进来唤醒本进程,它才会去执行接下来的代码。

如果进程被唤醒后,首先检查是否有事件产生,或者是否出现超时还是被其他信号唤醒的。如果出现这些情况,就跳出循环,将当前进程从ep->wp的等待队列里面移除,并且将当前进程设置为TASK_RUNNING就绪状态。

如果真的有事件产生,就调用ep_send_events函数,将events事件转移到用户空间里面。

sys_epoll_wait -> ep_poll -> ep_send_events:

ep_send_events没有什么工作,真正的工作是在ep_scan_ready_list函数里面:

sys_epoll_wait -> ep_poll -> ep_send_events -> ep_scan_ready_list:

ep_scan_ready_list首先将ep就绪链表里面的数据链接到一个全局的txlist里面,然后清空ep的就绪链表,同时还将ep的ovflist链表设置为NULL,ovflist是用单链表,是一个接受就绪事件的备份链表,当内核进程将事件从内核拷贝到用户空间时,这段时间目标文件可能会产生新的事件,这个时候,就需要将新的时间链入到ovlist里面。

仅接着,调用sproc回调函数(这里将调用ep_send_events_proc函数)将事件数据从内核拷贝到用户空间。

sys_epoll_wait -> ep_poll -> ep_send_events -> ep_scan_ready_list -> ep_send_events_proc:

ep_send_events_proc回调函数循环获取监听项的事件数据,对每个监听项,调用ep_item_poll获取监听到的目标文件的事件,如果获取到事件,就调用__put_user函数将数据拷贝到用户空间。

回到ep_scan_ready_list函数,上面说到,在sproc回调函数执行期间,目标文件可能会产生新的事件链入ovlist链表里面,所以,在回调结束后,需要重新将ovlist链表里面的事件添加到rdllist就绪事件链表里面。

同时在最后,如果rdlist不为空(表示是否有就绪事件),并且由进程等待该事件,就调用wake_up_locked再一次唤醒内核进程处理事件的到达(流程跟前面一样,也就是将事件拷贝到用户空间)。

到这,epoll_wait的流程是结束了,但是有一个问题,就是前面提到的进程调用epoll_wait后会睡眠,但是这个进程什么时候被唤醒呢?在调用epoll_ctl为目标文件注册监听项时,对目标文件的监听项注册一个ep_ptable_queue_proc回调函数,ep_ptable_queue_proc回调函数将进程添加到目标文件的wakeup链表里面,并且注册ep_poll_callbak回调,当目标文件产生事件时,ep_poll_callbak回调就去唤醒等待队列里面的进程。

总结一下epoll该函数: epoll_wait函数会使调用它的进程进入睡眠(timeout为0时除外),如果有监听的事件产生,该进程就被唤醒,同时将事件从内核里面拷贝到用户空间返回给该进程。

lsof(list open files)是一个列出当前系统打开文件的工具。在Linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访问网络连接 和硬件。所以如传输控制协议 (tcp) 和用户数据报协议 (udp) 套接字等,系统在后台都为该应用程序分配了一个文件描述符,无论这个文件的本质如何,该文件描述符为应用程序与基础 *** 作系统之间的交互提供了通用接口。因 为应用程序打开文件的描述符列表提供了大量关于这个应用程序本身的信息,因此通过lsof工具能够查看这个列表对系统监测以及排错将是很有帮助的。

lsof使用

lsof输出信息含义:

在终端下输入lsof即可显示系统打开的文件,因为 lsof 需要访问核心内存和各种文件,所以必须以 root 用户的身份运行它才能够充分地发挥其功能。

command pid user fd type device size node name

init 1 root cwd dir 3,3 1024 2 /

init 1 root rtd dir 3,3 1024 2 /

init 1 root txt reg 3,3 38432 1763452 /sbin/init

init 1 root mem reg 3,3 106114 1091620 /lib/libdl-26so

init 1 root mem reg 3,3 7560696 1091614 /lib/libc-26so

init 1 root mem reg 3,3 79460 1091669 /lib/libselinuxso1

init 1 root mem reg 3,3 223280 1091668 /lib/libsepolso1

init 1 root mem reg 3,3 564136 1091607 /lib/ld-26so

init 1 root 10u fifo 0,15 1309 /dev/initctl

每行显示一个打开的文件,若不指定条件默认将显示所有进程打开的所有文件。lsof输出各列信息的意义如下:

command:进程的名称

pid:进程标识符

user:进程所有者

fd:文件描述符,应用程序通过文件描述符识别该文件。如cwd、txt等

type:文件类型,如dir、reg等

device:指定磁盘的名称

size:文件的大小

node:索引节点(文件在磁盘上的标识)

name:打开文件的确切名称

其中fd 列中的文件描述符cwd 值表示应用程序的当前工作目录,这是该应用程序启动的目录,除非它本身对这个目录进行更改。txt 类型的文件是程序代码,如应用程序二进制文件本身或共享库,如上列表中显示的 /sbin/init 程序。其次数值表示应用程序的文件描述符,这是打开该文件时返回的一个整数。如上的最后一行文件/dev/initctl,其文件描述符为 10。u 表示该文件被打开并处于读取/写入模式,而不是只读 R 或只写 w 模式。同时还有大写 的w 表示该应用程序具有对整个文件的写锁。该文件描述符用于确保每次只能打开一个应用程序实例。初始打开每个应用程序时,都具有三个文件描述符,从 0 到 2,分别表示标准输入、输出和错误流。所以大多数应用程序所打开的文件的 fd 都是从 3 开始。

与 fd 列相比,type 列则比较直观。文件和目录分别称为 reg 和 dir。而chr 和 blk,分别表示字符和块设备;或者 unix、fifo 和 ipv4,分别表示 unix 域套接字、先进先出 (fifo) 队列和网际协议 (ip) 套接字。

lsof常用参数

lsof 常见的用法是查找应用程序打开的文件的名称和数目。可用于查找出某个特定应用程序将日志数据记录到何处,或者正在跟踪某个问题。例如,linux限制了进程能够打开文件的数目。通常这个数值很大,所以不会产生问题,并且在需要时,应用程序可以请求更大的值(直到某个上限)。如果你怀疑应用程序耗尽了文件描述符,那么可以使用 lsof 统计打开的文件数目,以进行验证。lsof语法格式是:

# lsof [options] filename

常用的参数列表:

lsof filename 显示打开指定文件的所有进程

lsof -a 表示两个参数都必须满足时才显示结果

lsof -c string 显示command列中包含指定字符的进程所有打开的文件

lsof -u username 显示所属user进程打开的文件

lsof -g gid 显示归属gid的进程情况

lsof +d /dir/ 显示目录下被进程打开的文件

lsof +d /dir/ 同上,但是会搜索目录下的所有目录,时间相对较长

lsof -d fd 显示指定文件描述符的进程

lsof -n 不将ip转换为hostname,缺省是不加上-n参数

lsof -i 用以显示符合条件的进程情况

lsof -i[46] [protocol][@hostname|hostaddr][:service|port]

46 --> ipv4 or ipv6

protocol --> tcp or udp

hostname --> internet host name

hostaddr --> ipv4地址

service --> /etc/service中的 service name (可以不只一个)

port --> 端口号 (可以不只一个)

例如: 查看22端口现在运行的情况

# lsof -i :22

command pid user fd type device size node name

sshd 1409 root 3u ipv6 5678 tcp :ssh (listen)

查看所属root用户进程所打开的文件类型为txt的文件:

# lsof -a -u root -d txt

command pid user fd type device size node name

init 1 root txt reg 3,3 38432 1763452 /sbin/init

mingetty 1632 root txt reg 3,3 14366 1763337 /sbin/mingetty

mingetty 1633 root txt reg 3,3 14366 1763337 /sbin/mingetty

mingetty 1634 root txt reg 3,3 14366 1763337 /sbin/mingetty

mingetty 1635 root txt reg 3,3 14366 1763337 /sbin/mingetty

mingetty 1636 root txt reg 3,3 14366 1763337 /sbin/mingetty

mingetty 1637 root txt reg 3,3 14366 1763337 /sbin/mingetty

kdm 1638 root txt reg 3,3 132548 1428194 /usr/bin/kdm

x 1670 root txt reg 3,3 1716396 1428336 /usr/bin/xorg

kdm 1671 root txt reg 3,3 132548 1428194 /usr/bin/kdm

startkde 2427 root txt reg 3,3 645408 1544195 /bin/bash

lsof使用实例

一、查找谁在使用文件系统

在卸载文件系统时,如果该文件系统中有任何打开的文件, *** 作通常将会失败。那么通过lsof可以找出那些进程在使用当前要卸载的文件系统,如下:

# lsof /gtes11/

command pid user fd type device size node name

bash 4208 root cwd dir 3,1 4096 2 /gtes11/

vim 4230 root cwd dir 3,1 4096 2 /gtes11/

在 这个示例中,用户root正在其/gtes11目录中进行一些 *** 作。一个 bash是实例正在运行,并且它当前的目录为/gtes11,另一个则显示的是vim正在编辑/gtes11下的文件。要成功地卸载/gtes11,应该 在通知用户以确保情况正常之后,中止这些进程。 这个示例说明了应用程序的当前工作目录非常重要,因为它仍保持着文件资源,并且可以防止文件系统被卸载。这就是为什么大部分守护进程(后台进程)将它们的 目录更改为根目录、或服务特定的目录(如 sendmail 示例中的 /var/spool/mqueue)的原因,以避免该守护进程阻止卸载不相关的文件系统。

二、恢复删除的文件

当linux计算机受到入侵时,常见的情况是日志文件被删除,以掩盖攻击者的踪迹。管理错误也可能导致意外删除重要的文件,比如在清理旧日志时,意外地删除了数据库的活动事务日志。有时可以通过lsof来恢复这些文件。

当进程打开了某个文件时,只要该进程保持打开该文件,即使将其删除,它依然存在于磁盘中。这意味着,进程并不知道文件已经被删除,它仍然可以向打开该文件时提供给它的文件描述符进行读取和写入。除了该进程之外,这个文件是不可见的,因为已经删除了其相应的目录索引节点。

在/proc 目录下,其中包含了反映内核和进程树的各种文件。/proc目录挂载的是在内存中所映射的一块区域,所以这些文件和目录并不存在于磁盘中,因此当我们对这 些文件进行读取和写入时,实际上是在从内存中获取相关信息。大多数与 lsof 相关的信息都存储于以进程的 pid 命名的目录中,即 /proc/1234 中包含的是 pid 为 1234 的进程的信息。每个进程目录中存在着各种文件,它们可以使得应用程序简单地了解进程的内存空间、文件描述符列表、指向磁盘上的文件的符号链接和其他系统信 息。lsof 程序使用该信息和其他关于内核内部状态的信息来产生其输出。所以lsof 可以显示进程的文件描述符和相关的文件名等信息。也就是我们通过访问进程的文件描述符可以找到该文件的相关信息。

当 系统中的某个文件被意外地删除了,只要这个时候系统中还有进程正在访问该文件,那么我们就可以通过lsof从/proc目录下恢复该文件的内容。 假如由于误 *** 作将/var/log/messages文件删除掉了,那么这时要将/var/log/messages文件恢复的方法如下:

首先使用lsof来查看当前是否有进程打开/var/logmessages文件,如下:

# lsof |grep /var/log/messages

syslogd 1283 root 2w reg 3,3 5381017 1773647 /var/log/messages (deleted)

从 上面的信息可以看到 pid 1283(syslogd)打开文件的文件描述符为 2。同时还可以看到/var/log/messages已经标记被删除了。因此我们可以在 /proc/1283/fd/2 (fd下的每个以数字命名的文件表示进程对应的文件描述符)中查看相应的信息,如下:

# head -n 10 /proc/1283/fd/2

aug 4 13:50:15 holmes86 syslogd 141: restart

aug 4 13:50:15 holmes86 kernel: klogd 141, log source = /proc/kmsg started

aug 4 13:50:15 holmes86 kernel: linux version 26221-8 (root@everestbuilderlinux-renorg) (gcc version 420) #1 smp wed jul 18 11:18:32 edt 2007

aug 4 13:50:15 holmes86 kernel: bios-provided physical ram map:

aug 4 13:50:15 holmes86 kernel: bios-e820: 0000000000000000 - 000000000009f000 (usable)

aug 4 13:50:15 holmes86 kernel: bios-e820: 000000000009f000 - 00000000000a0000 (reserved)

aug 4 13:50:15 holmes86 kernel: bios-e820: 0000000000100000 - 000000001f7d3800 (usable)

aug 4 13:50:15 holmes86 kernel: bios-e820: 000000001f7d3800 - 0000000020000000 (reserved)

aug 4 13:50:15 holmes86 kernel: bios-e820: 00000000e0000000 - 00000000f0007000 (reserved)

aug 4 13:50:15 holmes86 kernel: bios-e820: 00000000f0008000 - 00000000f000c000 (reserved)

从上面的信息可以看出,查看 /proc/8663/fd/15 就可以得到所要恢复的数据。如果可以通过文件描述符查看相应的数据,那么就可以使用 i/o 重定向将其复制到文件中,如:

# cat /proc/1283/fd/2 > /var/log/messages

对于许多应用程序,尤其是日志文件和数据库,这种恢复删除文件的方法非常有用。

# lsof -i:3306

查看3306端口被谁占用

Ubuntu Linux 命令一句话技巧集合Linux Web服务器网站故障分析常用的命令

延伸阅读

抱歉,暂无相关内容!

10 个你需要了解的 Linux 网络和监控命令

30个实用的Linux find命令示例

Git命令10个很有用的使用技巧

可能由病毒引起。

在文件描述符关掉以后,继续使用这个文件描述符访问

打开文件,获取文件描述符fd(其实是一个整形)

关闭文件

打开sqlite文件,获取文件描述符(碰巧也是)fd

另一个线程继续使用fd,写文件

sqlite文件被损坏

在事务进行过程中,进行数据库备份或恢复

在数据库事务过程中,数据库文件既包括老的内容,也包括新的内容。如果此时拷贝这个文件,数据库可能会被损坏。 备份数据库最好使用sqlite的api。

删除日志文件

日志文件中包括rollback需要的信息。删除以后,无法正确回滚,有可能会导致数据库损坏。

有时候我们需要检测某个目录下文件或者子目录的改动状况,如添加、删除、以及更新等,Linux系统上提供了inotify来完成这个功能。inotify是在版本2613的内核中首次出现,现在的发行本应该都包含这个系统调用了。

下面的描述中的文件如无特别说明包括文件以及目录

使用inotify的第一步就是调用inotify_init()创建一个inotify实例,该函数返回一个文件描述符。这个文件描述符关联了一个inotify事件队列,通过read读取该文件描述符,就能获取底层的inotify事件。

1

int inotify_fd = inotify_init();

还有另外一个系统调用inotify_init1(int flag),该函数提供了一个参数可用于设置文件描述符属性

1

int inotify_fd = inotify_init1(flag);

其效果与如下代码相同

1

2

int inotify_fd = inotify_init();

fcntl(inotify_fd, F_SETFL, flags)

一旦成功创建了inotify实例,获得了相应的文件描述符,下一步就是告诉内核需要关注的文件以及关注的事件类型。这一步是通过函数inotify_add_watch()实现的。

1

int wd = inotify_add_watch(instance_fd, file_name, event_mask)

上面的调用中,file_name就是需要关注的文件,而event_mask是关注的事件类型掩码。目前inotify支持的事件类型包括如下几种

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

这里面值得注意的是IN_DELETE、IN_MOVE_TO和IN_DELETE_SELF、IN_MOVE_SELF,简单来说带有SELF结尾的事件,发生在被关注目录自身,而不带SELF的发生在关注对象的子目录或者子文件之上。例如对于目录A调用inotify_add_watch,如果目录A中的文件B被删除,内核会发出IN_DELETE事件,而目录A被删除,内核发出IN_DELETE_SELF事件。

如果决定不再关注某个文件,只需调用inotify_rm_watch(instance_fd, wd)即可,其中的wd为inotify_add_watch的返回值。

设置好了关注文件以及事件类型,剩下的就是inotify事件的处理了。

首先第一步就是要获取inotify事件,这一步非常简单,只需要对于instance_fd调用read进行读取即可。注意,read读出的数据只是一些字符序列,你要通过强制转换才能获得inotify_event

1

2

3

4

5

6

7

struct inotify_event {

int wd;

uint32_t mask;

uint32_t cookie;

uint32_t len;

char name[]

};

具体的含义可以使用man命令去看,值得一体的是mask字段和cookie字段。这里的mask字段除了包含事件类型之外,还可能包含其他信息,诸如IN_ISDIR标示事件是否是发生在目录之上,IN_UMOUNT标示关注对象所在文件系统是否被卸载等。

Windows下也有类似的系统调用ReadDirectoryChanges,不过我在FreeBSD以及AIX下都未找到相应的系统调用

以上就是关于面试必问的epoll技术,从内核源码出发彻底搞懂epoll全部的内容,包括:面试必问的epoll技术,从内核源码出发彻底搞懂epoll、关于lsof命令查看某个文件被哪个进程打开、导致数据库损坏的原因有哪些,病毒感染会不会等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/9805297.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2023-05-02
下一篇2023-05-02

发表评论

登录后才能评论

评论列表(0条)

    保存