UNIX/LINUX,深入理解Linux/Unix文件描述符和epoll

 2023-10-21 阅读 32 评论 0

摘要:Linux/Unix 文件描述符(File Describer)的本質 Linux/Unix(以下簡稱Linux)系統中,每個進程都有一個專用的數組,數組的元素是一個結構體,稱為文件描述符File Descriptor(以下簡稱fd),但是至少包含一個文件指針,指向Linux內核的Open File Ta

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);

UNIX/LINUX、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,成功返回事件的個數。

基本流程

shell運行,以下是基本的流程,但不是真正的內存模型。一個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

版权声明:本站所有资料均为网友推荐收集整理而来,仅供学习和研究交流使用。

原文链接:https://hbdhgg.com/1/156981.html

发表评论:

本站为非赢利网站,部分文章来源或改编自互联网及其他公众平台,主要目的在于分享信息,版权归原作者所有,内容仅供读者参考,如有侵权请联系我们删除!

Copyright © 2022 匯編語言學習筆記 Inc. 保留所有权利。

底部版权信息