一、Linux下文件处理
大家都知道,在Linux下,一切皆文件。这样,所有的I/O操作都可以抽象为文件的操作。这对于初学者可能感觉有点不好接受,特别是从Windows转到Linux上的开发人员更是如此。但这种抽象在某种程度上更容易让开发者统一编程的接口,简化对多种资源处理的操作方法,让开发者的设计和开发更具有灵活性。
二、eventFD
eventFD是Linux内核提供的一个轻量级别的进程间的通信机制,专门用来传递事件通知。其通过创建一个由内核管理的“事件计数器”,而这个计数器由文件描述符(fd)表现出来。从而体现上面刚刚说到的“一切皆文件”。通过对事件的读写操作,就可以实现事件机制。内核中对其的定义如下:
// 内核中的 eventfd
struct eventfd_ctx {
struct kref kref; // 引用计数
wait_queue_head_t wqh; // 等待队列头
__u64 count; // 事件计数器
unsigned int flags; // 标志位
int id; // 唯一标识id
};
// eventfd 的文件操作数据结构体定义
static const struct file_operations eventfd_fops = {
.release = eventfd_release,
.poll = eventfd_poll,
.read = eventfd_read,
.write = eventfd_write,
.llseek = noop_llseek,
};
eventFD的特点主要包括:
- 使用一个64位的计数器,且对其的操作仅用来处理计数并能保证原子操作(线程安全)
- 由于其仅操作计算器,故其性能极高 (无数据拷贝)
- 与epoll原生集成(IO复用)
三、工作原理
eventFD的主要工作原理如下:
- 创建并初始化
开发者可以通过eventfd()接口由内核创建eventfd_ctx对象实例并初始化相关的计数器以及此实例中的相关成员。同时,内核会创建一个文件(file)结构对象并返回给用户应用接口调用 - 读写操作
应用程序可以通过返回的文件描述符对文件句柄进行读和写操作。它些操作最终会调用eventfd_fops中定义的 eventfd_read和eventfd_write函数。在写操作时,通过原子操作保证计数器的安全操作。在写入操作后,根据结果设置相关的标志位并返回执行的结果。而在读操作时,通过相关标志位读取和处理计数器,如果计数器为0,在阻塞的状态下则会阻塞操作 - 文件操作接口
Linux提供了不少的事件操作接口,如select,epoll等,它们都可以利用多路复用机制来处理事件通知
四、应用场景
eventFD的应用场景在Linux下应用非常广泛,常见的主要有:
- 异步任务
其实在Linux下说是异步任务有些勉强,不过,eventFD的事件机制确实可以实现仿异步任务的功能 - 与IO框架集成
这个就比较多了,比如select,poll,epoll,io_uring等。特别是io_uring,是最新一代的异步框架,这个在前面分析过,有兴趣可以看看 - 跨任务的数据交换
可以利用事件通知机制来保证数据在不同的线(进)程的交换的安全性
五、例程
在前面的网络编程和io_uring的分析中都给出了相关的例程,这里只给出一个普通的应用例程:
#include <sys/eventfd.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <pthread.h>
int fd;
void* workThread(void* arg) {
uint64_t u;
ssize_t s;
s = read(fd, &u, sizeof(uint64_t));
if (s != sizeof(uint64_t)) {
return NULL;
}
printf("worker thread received event! Counter value = %lu\n", u);
sleep(2);
printf("Worker thread end.\n");
return NULL;
}
int main() {
pthread_t thread;
// 创建eventfd
fd = eventfd(0, 0);
if (fd == -1) {
return -1;
}
printf("Main thread created eventfd, starting worker thread...\n");
if (pthread_create(&thread, NULL, workThread, NULL) != 0) {
return -1;
}
sleep(2);
printf("send event worker thread...\n");
uint64_t u = 1;
ssize_t s = write(fd, &u, sizeof(uint64_t));
if (s != sizeof(uint64_t)) {
return -1;
}
pthread_join(thread, NULL);
close(fd);
return 0;
}
代码不复杂,大家直接运行或调试一下即可明白。
六、总结
事件在Linux中是一个非常重要的机制,常见的IO多路复用以及IPC通信等,都离不开事件。特别是与网络开发的相关开发者,更是会与事件有更多的接触。掌握事件开发的重要性,不言而喻。有机会还是应该把事件从内核到应用的整体流程都掌握的明白才是根本。

732





