Linux/Unix 文件描述符(File Describer)的本质
Linux/Unix(以下简称Linux)系统中,每个进程都有一个专用的数组,数组的元素是一个结构体,称为文件描述符File Descriptor(以下简称fd),但是至少包含一个文件指针,指向Linux内核的Open File Table(以下简称Open表),Open表也可以理解一个数组,使用偏移量来指示每个元素的位置,上述fd的文件指针指向的就是这里说的偏移位置。Open表的元素称为File Description(以下简称FD,注意描述的差异),每个FD都有一个INODE指针,指向文件系统的INODE Table。文件系统的INODE Table(以下简称INODE表),每个元素也是个结构类型,包括了文件在磁盘中的具体位置、所有者、写入时间等的信息,文件驱动器通过INODE表来实际操作文件。具体如下图:
创建fd的方式:
- 系统调用,比如使用
socket()
的函数进行操作 - 从父进程中继承,线程A使用
fork()
函数生成线程B,那么B就有了自己的fd,不过指向的是相同的FD。
注意:如果在复制的时候,对某些fd使用了CLOSE_ONEXEC
标记,那么子进程的这些fd就失效了,但是不影响父进程的fd使用
销毁fd的方式:
close()
系统调用- 进程结束
关于INODE,前面提到INODE也是一个结构类型,但是它仍然不会存储数据的磁盘数据,它存储的是文件的信息。文件系统是软硬件的结合处,该系统通过INODE的信息查找文件。Linux中的每一个文件(Linux一切皆文件)都对应一个INODE实体,每个系统有一个INODE上限。
理解epoll底层原理(非具体实现)
创建epoll()
:
#include <sys/epoll.h>
int epoll_create(int size);
size
指定大小epoll
将要创建事件队列的容量,不过该参数在内核2.6.8之后就废弃了,由系统自动化分配。
函数返回epoll在进程中的fd。
#include <sys/epoll.h>
int epoll_create1(int flags);
flags=0功能同上,另一个选项是EPOLL_CLOEXEC
。这个选项的作用是当父进程fork
出一个子进程的时候,子进程不会包含epoll
的fd
在epoll
上注册事件
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
epfd
是创建的epoll
的fdop
表示操作的类型EPOLL_CTL_ADD
:注册事件EPOLL_CTL_MOD
:更改事件EPOLL_CTL_DEL
:删除事件
fd
是相应的文件描述符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_data_t data;
};
事件是一些宏定义,可以查表,data是共用体,ptr
表示内核中OPEN表的指针,fd
表示
epoll_wait
等待事件发生
int epoll_wait(int epfd, struct epoll_event* evlist, int maxevents, int timeout);
epfd
是epoll
的文件描述符evlist
是发生的事件队列maxevents
是队列最长的长度timeout
是事件限制
错误返回-1,超时返回0,成功返回事件的个数。
基本流程
以下是基本的流程,但不是真正的内存模型。一个epoll
有一个注册事件的fd
的列表,列表中发生事件的fd会被存储在epoll_wait
函数的队列中。
epoll
的陷阱与内部的原理
给出一个典型的陷阱,借用之前的图片:
A线程fd0指向一个系统资源,A线程的fd3是复制的fd0的。A线程fork出B线程,但是fd3复制的时候标记为close-on-exec
,那么复制后的fd3就不能再表示之前的资源了。同时还可以看出,FD是进程间共享的,如果任意一个进程更改了FD,那么其它进程的fd对应的FD也会发生更改,这在多进程模型中是需要注意的。
epoll
的内部基本机制(不含实现)
fd0和fd1是两个已经开启的文件描述符,而且指向不同的INODE。之后系统调用epoll_create
创建新的INODE实体(等效在内核中创建一个FD实体),之后调用该函数的线程会获取一个fd,假设是fd9,那么此时fd9和进程A任然共享同一个Interest List,此时A也会响应fd9的事件。假设B进程又添加了fd8,那么A也会响应fd8.
一个epoll程序实例
#include <stdlib.h>
#include <iostream>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <strings.h>
const int MAX_EVENTS = 200;
int setnoonblocking(int fd) {
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
return old_option;
}
int main(int argc, char* argv[]) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <port of server>\n";
exit(1);
}
int port = atoi(argv[1]);
if (port < 0) {
std::cerr << "port error\n";
exit(1);
}
struct sockaddr_in serv_addr;
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
int socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd < 0) {
std::cerr << "socker() error\n";
exit(1);
}
if (bind(socketfd, (const sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
std::cerr << "bind() error\n";
exit(1);
}
if (listen(socketfd, 10) < 0) {
std::cerr << "listen() error\n";
exit(1);
}
int epollfd = epoll_create1(0);
if (epollfd < 0) {
std::cerr << "epoll_create1() error\n";
exit(1);
}
epoll_event ev, events[MAX_EVENTS];
ev.events = EPOLLIN;
ev.data.fd = socketfd;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, socketfd, (epoll_event*)&ev) < 0) {
std::cerr << "epoll_ctl() error\n";
exit(1);
}
for (;;) {
int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (nfds == -1) {
std::cerr << "epoll_wait\n";
exit(1);
}
for (int i = 0; i < nfds; ++i) {
if (events[i].data.fd == socketfd) {
int conn_sock = accept(socketfd, (sockaddr*)NULL, NULL);
if (conn_sock < 0) {
std::cerr << "accept() error\n";
exit(1);
}
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, conn_sock, &ev) < 0) {
std::cerr << "epoll_ctl() error\n";
exit(1);
} else {
std::cout << "get a new connection\n";
}
} else if (events[i].events & EPOLLIN) {
int fd = events[i].data.fd;
char buffer[1024];
bzero(buffer, sizeof(buffer));
int ret = recv(fd, buffer, sizeof(buffer), MSG_DONTWAIT);
if (ret <= 0) {
std::cout << "recv() error or user leave\n";
close(fd);
} else {
std::cout << "Get user datas: " << buffer << std::endl;
snprintf(buffer, sizeof(buffer) - 1, "Your fd is %d", fd);
send(fd, buffer, sizeof(buffer), MSG_DONTWAIT);
}
} else {
std::cerr << "Unknown error\n";
exit(1);
}
}
}
exit(0);
}