服务器编程中的文件描述符

  linux系统下一切皆文件,通过虚拟文件系统(VFS)的机制将所有底层屏蔽掉,用户可以通过统一的接口来实现对不同驱动的操作,对于每一个文件需要一个引用来指示,此时文件描述符应用而生,文件描述符类似于widows下的handle,对于文件的大部分操作都是通过这个描述符来操作的,例如read,write。对于每一个文件描述符,内核使用三种数据结构来管理。

(1)  每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:

  (a)  文件描述符标志。 (当前只定义了一个文件描述符标志FD_CLOEXEC)

  (b)  指向一个文件表项的指针。

(2)  内核为所有打开文件维持一张文件表。每个文件表项包含:

  (a)  文件状态标志(读、写、增写、同步、非阻塞等 )。

  (b)  当前文件位移量。(即为lseek函数所操作的值)

  (c)  指向该文件v节点表项的指针。

(3)  每个打开文件(或设备)都有一个 v 节点结构。 v节点包含了文件类型和对此文件进行各种操作的函数的指针信息。对于大多数文件, v 节点还包含了该文件的 i 节点(索引节点)。这些信息是在打开文件时从盘上读入内存的,所以所有关于文件的信息都是快速可供使用的。例如, i 节点包含了文件的所有者、文件长度、文件所在的设备、指向文件在盘上所使用的实际数据块的指针等等点。

  经过上述文件系统的三层封装,每层负责不同的职责,从上到下第一层用于标识文件,第二层用于管理进程独立数据,第三层管理文件系统元数据,直接关联一个文件。这种分层思想的一个优点就是上层可以复用下层的结构。可能有多个文件描述符项指向同一个文件表项,也可以有多个文件表项指向同一个V节点。

  如果两个独立的进程打开了同一个文件,打开此文件的每个进程都得到一个文件表项,但是两个文件表项的V节点指针指向相同的V节点,这样的安排使得每个进程都有他自己的对该文件的当前位移量,且支持不同的打开方式(O_RDONLY, O_WRONLY, ORDWR)。

  当一个进程通过fork创建出子进程后,此时父,子进程内的文件描述符共享同一个文件表项,也就是说父子进程的文件描述符的指向相同。一般我们会在fork后关闭掉各自不需要的fd,例如父子进程通过pipe或socketpair进行通信,往往会close掉自己不需要读(或写)的一端。只有在没有文件描述符引用当前文件表项的时候,close操作才真正销毁当前文件表项数据结构,有点类似于引用计数的思想。这也是网络编程中close和shutdown函数的区别,前者只有在最后一个使用该socket的句柄的进程关闭的时候才真正断开连接,而后者毫不商量直接断开一侧连接。但是在多线程的环境中,由于父子线程共享地址空间,此时文件描述符共同拥有,只有一份,所以也就不能在线程内close掉自己不需要的fd,否则会导致其它需要该fd的线程也受影响。因为父,子进程内打开的文件描述符共享同一个文件表项,所以在某些系统的服务器编程中,如果采用preforking模型(服务器预先派生多个子进程,在每个子进程监听listenfd来accept连接)就会导致惊群现象的发生,服务器派生的多个子进程各自调用accept并因而均被投入睡眠,当第一个客户连接到达时,尽管只有一个进程获得连接,但是所有进程都被唤醒,这样导致性能受损。参见UNP P657。

  同时如果fork之后调用exec,所有的文件描述符继续保持打开状态。这可以用来给exec后的程序传递某些文件描述符。同时文件描述符标志FD_CLOEXEC 就是用来关闭exec时继续保持开放的文件描述符的选项。

  也可以通过dup或fcntl显式复制一个文件描述符,他们指向相同的文件表项。通过dup2将文件描述符复制到制定数值。

  每个进程都有一个文件描述符表,进程间独立,两个进程之间的文件描述符并无直接关系,所以在进程内可以直接传递文件描述符,但是如果跨越进程传递就失去了意义,unix可以通过sendmsg/recvmsg进行专门的文件描述符的传递(参见书UNP 15.7节)。每个进程的前三个文件描述符分别对应标准输入,标准输出,标准错误。但是一个进程可打开的文件描述符数量是有限制的,如果打开的文件描述符太多会出现”Too many open files”的问题。在网络服务器中,通过listenfd调用调用accept时,体现为产生EMFILE错误,这主要是因为文件描述符是系统的一个重要资源,系统资源是有尽的,系统对单一进程文件描述符限制默认值一般是1024,使用ulimit -n命令可以查看。当然也可以调高进程文件描述符数目,但这是治标不治本的方法,因为处理高并发服务时,服务器资源有限,难免资源枯竭。

  当结合epoll的水平触发方式来监听lisenfd的连接时,大量socket连接涌来如果不处理会塞满TCP的连接队列,listenfd会一直产生可读事件,将服务器陷入忙等待,用C++开源网络库muduo作者陈硕的做法是事先准备一个空闲的文件描述符,当产生EMFILE错误时就先关闭这个空闲文件,获得一个文件描述符名额,再accept拿到一个socket连接的文件描述符,随后立刻close,这样就优雅的断开了与客户端的连接,最后重新打开空闲文件,把”坑”填上,以备再次出现这种情况时使用。

 1 //在程序开头先”占用”一个文件描述符
 2
 3 int idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC);
 4 …………
 5
 6 //然后当出现EMFILE错误的时候处理这个错误
 7
 8 peerlen = sizeof(peeraddr);
 9 connfd = accept4(listenfd,  (struct sockaddr*)&peeraddr, &peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
10
11 if (connfd == -1)
12 {
13     if (errno == EMFILE)
14     {
15         close(idlefd);
16         idlefd = accept(listenfd, NULL, NULL);
17         close(idlefd);
18         idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC);
19         continue;
20     }
21     else
22         ERR_EXIT("accept4");
23 }

  由于文件描述符涉及内容庞杂,此时仅作抛砖引玉,后期会持续更新....

时间: 2024-10-24 16:23:37

服务器编程中的文件描述符的相关文章

每天进步一点点——Linux中的文件描述符与打开文件之间的关系

转载请说明出处:http://blog.csdn.net/cywosp/article/details/38965239 1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文件.链接文件和设备文件.文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符.程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误.如果此时去打开一个

(转)Linux中的文件描述符

本文转自:http://blog.csdn.net/cywosp/article/details/38965239 作者:cywosp 1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文件.链接文件和设备文件.文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符.程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误.如果

Linux中的文件描述符与打开文件之间的关系

1. 概述 在Linux系统中一切皆可以看成是文件,文件又可分为:普通文件.目录文件.链接文件和设备文件.文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,其是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符.程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误.如果此时去打开一个新的文件,它的文件描述符会是3.POSIX标准要求每次打开文件时(含socket)必须使用当前进程中最小可用的文件描述符号

【翻译自mos文章】在unix/linux中使用文件描述符(File Descriptors)来找回被删掉的文件(数据文件or redo log)

在unix/linux中使用文件描述符(File Descriptors)来找回被删掉的文件(数据文件or redo log) 参考原文: Retrieve deleted files on Unix / Linux using File Descriptors (Doc ID 444749.1) 适用于: Oracle Database - Enterprise Edition - Version 8.1.7.0 to 11.2.0.3 [Release 8.1.7 to 11.2] Linu

Linux中对文件描述符的操作(FD_ZERO、FD_SET、FD_CLR、FD_ISSET

在Linux中,内核利用文件描述符(File Descriptor)即文件句柄,来访问文件.文件描述符是非负整数.打开现存文件或新建文件时,内核会返回一个文件描述符.读写文件也需要使用文件描述符来指定待读写的文件.宏FD_ZERO.FD_SET.FD_CLR.FD_ISSET中"FD"即为file descriptor的缩写,下面来一一进行介绍. 首先介绍一个重要的结构体:fd_set,它会作为下面某些函数的参数而多次用到,fd_set可以理解为一个集合,这个集合中存放的是文件描述符(

Unix系统编程()文件描述符和打开文件之间的关系

目前学习到的是一个文件描述符对应着一个打开的文件,似乎是一一对应的关系.但是实际上并不是这样的.多个文件描述符指向同一个打开的文件,是可能的也是必要的.这些文件描述符可以在相同或者不同的进程中打开. 要理解具体情况,需要查看内核维护的3个数据结构. 进程级的文件描述符表 系统级的打开文件表 文件系统的i-node表 针对每个进程,内核为其维护打开文件的描述符(open file descriptor)表.该表的每一条目都记录了单个文件描述符的相关信息.包括有一下信息: 控制文件描述符操作的一组标

Linux中文件描述符fd和文件指针flip的理解

转自:http://www.cnblogs.com/Jezze/archive/2011/12/23/2299861.html 简单归纳:fd只是一个整数,在open时产生.起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针filp. open:文件描述符的操作(如: open)返回的是一个文件描述符(int fd),内核会在每个进程空间中维护一个文件描述符表, 所有打开的文件都将通过此表中的文件描述符来引用(fd1,fd2,fd3...); fopen:而流(如: f

通过UNIX域套接字传递文件描述符

传送文件描述符是高并发网络服务编程的一种常见实现方式.Nebula 高性能通用网络框架即采用了UNIX域套接字传递文件描述符设计和实现.本文详细说明一下传送文件描述符的应用. 1. TCP服务器程序设计范式 ??开发一个服务器程序,有较多的的程序设计范式可供选择,不同范式有其自身的特点和实用范围,明了不同范式的特性有助于我们服务器程序的开发.常见的TCP服务器程序设计范式有以下几种: 迭代服务器 并发服务器,每个客户请求fork一个子进程 预先派生子进程,每个子进程无保护地调用accept 预先

关于Linux文件描述符的笔记

当某个程序打开文件时,操作系统返回相应的文件描述符,程序为了处理该文件必须引用此描述符.所谓的文件描述符是一个低级的正整数.最前面的三个文件描述符(0,1,2)分别与标准输入(stdin),标准输出(stdout)和标准错误(stderr)对应.因此,函数 scanf() 使用 stdin,而函数 printf() 使用 stdout.你可以用不同的文件描述符改写默认的设置并重定向进程的 I/O 到不同的文件. 首先说什么是文件描述符,它有什么作用? 文件描述符是一个简单的整数,用以标明每一个被