面试题答案
一键面试1. I/O事件处理原理
在Linux C语言异步I/O实现中,常见的事件处理机制有select、poll和epoll等。以epoll为例,其原理如下:
- 内核态与用户态交互优化:epoll采用了内核事件表,通过epoll_create创建一个epoll实例,在内核空间中开辟了一块区域用于存放监控的文件描述符。这样就避免了每次调用像select那样将所有文件描述符从用户态拷贝到内核态的开销。
- 事件通知方式:epoll有两种事件通知模式,水平触发(LT, Level Triggered)和边缘触发(ET, Edge Triggered)。水平触发模式下,只要文件描述符对应的缓冲区还有未处理的数据,就会一直触发事件;边缘触发模式下,只有当文件描述符对应的缓冲区状态发生变化(比如有新数据可读)时才会触发事件,这种模式要求应用程序在接收到事件后尽可能多地处理数据,通常性能更高,但编程难度也稍大。
2. 代码示例
以下是一个使用epoll处理异步I/O事件的简单示例,以监听标准输入(键盘输入)为例:
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <unistd.h>
#define MAX_EVENTS 10
int main() {
int epollFd, nfds;
struct epoll_event ev, events[MAX_EVENTS];
char buffer[1024];
// 创建epoll实例
epollFd = epoll_create1(0);
if (epollFd == -1) {
perror("epoll_create1");
exit(EXIT_FAILURE);
}
// 添加标准输入(文件描述符0)到epoll实例中,监听读事件
ev.events = EPOLLIN;
ev.data.fd = STDIN_FILENO;
if (epoll_ctl(epollFd, EPOLL_CTL_ADD, STDIN_FILENO, &ev) == -1) {
perror("epoll_ctl: STDIN_FILENO");
close(epollFd);
exit(EXIT_FAILURE);
}
// 等待事件发生
for (;;) {
nfds = epoll_wait(epollFd, events, MAX_EVENTS, -1);
if (nfds == -1) {
perror("epoll_wait");
break;
}
for (int n = 0; n < nfds; ++n) {
if (events[n].data.fd == STDIN_FILENO) {
ssize_t count = read(STDIN_FILENO, buffer, sizeof(buffer));
if (count == -1) {
perror("read");
break;
} else if (count == 0) {
printf("End of input\n");
close(epollFd);
exit(EXIT_SUCCESS);
} else {
buffer[count] = '\0';
printf("Read: %s", buffer);
}
}
}
}
close(epollFd);
return 0;
}
在这个示例中:
- 首先使用
epoll_create1
创建一个epoll实例。 - 然后使用
epoll_ctl
将标准输入(文件描述符为STDIN_FILENO
即0)添加到epoll实例中,监听读事件(EPOLLIN
)。 - 接着在一个无限循环中调用
epoll_wait
等待事件发生。当有事件发生时,检查事件对应的文件描述符是否为标准输入,如果是则读取输入数据并打印。