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出一个子进程的时候,子进程不会包含epollfd

epoll上注册事件

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
  • epfd是创建的epoll的fd
  • op表示操作的类型
    • 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);
  • epfdepoll的文件描述符
  • 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);
}

参考资料

  • https://medium.com/@copyconstruct/the-method-to-epolls-madness-d9d2d6378642
  • http://man7.org/linux/man-pages/man7/epoll.7.html
  • https://blog.csdn.net/qq_35976351/article/details/85091193

深入理解Linux/Unix文件描述符和epoll相关推荐

  1. ftpclient读取服务器文件能获得文件名文件大小0_理解Linux的文件描述符FD与Inode

    FD 文件描述符 一.概念 Linux 系统中,把一切都看做是文件,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符,文件描述符就是内核为了高效管理已被打开的文件所创建的索引,用来指向被 ...

  2. Linux下文件描述符

    Linux下文件描述符 标签: linuxfilelinux内核apacheunixsocket 2012-08-17 15:45 5798人阅读 评论(0) 收藏 举报 分类: 调优和安全(5) 版 ...

  3. linux下文件描述符的介绍

    linux下文件描述符的介绍 (2012-10-02 16:01:56) 转载▼ 标签: 描述符 调用 返回 进程 限制 it 分类:linux 当某个程序打开文件时,操作系统返回相应的文件描述符,程 ...

  4. linux 文件指针,Linux中文件描述符fd与文件指针FILE*互相转换实例解析

    本文研究的主要是Linux中文件描述符fd与文件指针FILE*互相转换的相关内容,具体介绍如下. 1.文件描述符fd的定义:文件描述符在形式上是一个非负整数.实际上,它是一个索引值,指向内核为每一个进 ...

  5. linux用户文件描述符2表示,Linux下文件描述符

    Linux下文件描述符 文件描述符是一个简单的整数,用以标明每一个被进程所打开的文件和socket.第一个打开的文件是0,第二个是1,依此类推.Unix操作系 统通常给每个进程能打开的文件数量强加一个 ...

  6. linux中文件描述符fd和文件指针flip的理解

    整理自:http://www.cnblogs.com/Jezze/archive/2011/12/23/2299861.html 简单归纳:fd(file descriptor)只是一个整数,在ope ...

  7. <Linux基础--文件描述符fd、重定向、文件流指针FILE*概念理解>

    文章目录 1.文件描述符fd 2.重定向 3.文件流指针:FILE* 4.动态库和静态库 1.文件描述符fd 文件描述符:实际上就是内核中一个进程打开的文件描述信息数组(file* fd_array[ ...

  8. Linux:文件描述符

    1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文件.链接文件和设备文件.文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是 ...

  9. linux进程文件描述符 vnode,从flock引发的一个bug谈起(1) 进程的文件描述符

    引子 前两天我们QA发现了一个比较有意思的bug,我细细分析一下,发现多个进程卡死在一个·配置文件上.简单的说,我们为了防止多个进程同时写同一个配置文件,将文件格式破坏,我们用了flock,对于写打开 ...

最新文章

  1. matlab朴素贝叶斯手写数字识别_基于MNIST数据集实现手写数字识别
  2. STM32中I2C总线上数据的读、写。
  3. 汤家凤高等数学基础手写笔记-重积分
  4. matlab 与dsp联合仿真,matlab和DSP联合开发前景很大?
  5. python五子棋游戏15*15_在STM32上运行五子棋小游戏(15x15)
  6. feign请求的封装
  7. 一个基于 SpringBoot 开源的小说和漫画在线阅读网站,简洁大方、强烈推荐
  8. 成立十个月,融资五个亿,创新奇智完成超4亿人民币A轮和A+轮融资
  9. 课设(房屋出租系统)
  10. 如何正确在CSDN问答进行提问
  11. python unpack java,Java中的python struct.unpack - java
  12. 试着用markdown
  13. 人到中年怎样防止头发花白
  14. Python制作微信自动回复机器人,打游戏时自动回复女朋友消息
  15. CC2530基础实验四 串口通信
  16. 不用下载就能在线P图,这款工具分享给你
  17. 【商业源码】生日大放送-Newlife商业源码分享
  18. matlab python 股票,股票行情数据获取-Python获取股票数据?
  19. 微信小程序时间加法_微信小程序日期转换、比较、加减
  20. 数据分享|Spss Modeler关联规则Apriori模型、Carma算法分析超市顾客购买商品数据挖掘实例...

热门文章

  1. Lake Counting POJ2386 ( dfs )
  2. 最急救助(【CCF】NOI Online能力测试3 入门组)
  3. 洛谷 P1426 小鱼会有危险吗(C语言)
  4. 【已解决】能连接上无线,但打不开网页怎么办?
  5. Oracle11g x64使用Oracle SQL Developer报错:Unable to find a Java Virtual Machine
  6. 爬取京东评论、分词+词频统计、词云图展示
  7. DW06、DW07 锂电保护IC手册电路,锂电池过充过放过流短路保护芯片电路
  8. Python:for的多种写法
  9. 星尘小组第十一周翻译-设计和优化索引
  10. js 获取昨天,今天,本周,上周,季度等时间范围(封装的js)