File Descriptors

本文介绍UNIX系统中文件描述符的概念及其作用,详细解释了标准输入、输出和错误输出,并探讨了如何通过管道实现进程间的通信。

One of the first things a UNIX programmer learns is that every running program starts with three files already opened:

Table 1.1. Standard Files Provided by Unix
Descriptive Name Short Name File NumberDescription
Standard In stdin 0 Input from the keyboard
Standard Out stdout 1 Output to the console
Standard Error stderr 2Error output to the console

Figure 1.2. Default Unix Files
The standard files opened with any UNIX program.

This raises the question of what an open file represents. The value returned by an open call is termed a file descriptor and is essentially an index into an array of open files kept by the kernel.

Figure 1.3. Abstraction
File descriptors associate the abstraction provided by device drivers with a file interface provided to a user.

File descriptors are an index into a file descriptor table stored by the kernel. The kernel creates a file descriptor in response to an open call and associates the file descriptor with some abstraction of an underlying file-like object, be that an actual hardware device, or a file system or something else entirely. Consequently a process's read or write calls that reference that file descriptor are routed to the correct place by the kernel to ultimately do something useful.


In short, the file descriptor is the gateway into the kernel's abstractions of underlying hardware. An overall view of the abstraction for physical-devices is shown in Figure 1.3, “Abstraction”.

Starting at the lowest level, the operating system requires a programmer to create a device driver to be able to communicate with a hardware device. This device driver is written to an API provided by the kernel just like in Example 1.2, “Abstraction in include/linux/virtio.h; the device driver will provide a range of functions which are called by the kernel in response to various requirements. In the simplified example above, we can see the drivers provide a read and write function that will be called in response to the analogous operations on the file descriptor. The device driver knows how to convert these generic requests into specific requests or commands for a particular device.

To provide the abstraction to user-space, the kernel provides a file-interface via what is generically termed a device layer. Physical devices on the host are represented by a file in a special file system such as /dev. In UNIX-like systems, so-called device-nodes have what are termed a major and a minor number, which allow the kernel to associate particular nodes with their underlying driver. These can be identified via ls as illustrated in Example 1.3, “Example of major and minor numbers”.

Example 1.3. Example of major and minor numbers
        $ ls -l /dev/null /dev/zero /dev/tty
crw-rw-rw- 1 root root 1, 3 Aug 26 13:12 /dev/null
crw-rw-rw- 1 root root 5, 0 Sep  2 15:06 /dev/tty
crw-rw-rw- 1 root root 1, 5 Aug 26 13:12 /dev/zero

      

This brings us to the file descriptor, which is the handle user-space uses to talk to the underlying device. In a broad sense, what happens when a file isopened is that the kernel is using the path information to map the file descriptor with something that provides an appropriate read and write, etc., API. When this open is for a device (/dev/sr0 above), the major and minor number of the opened device node provides the information the kernel needs to find the correct device driver and complete the mapping. The kernel will then know how to route further calls such as read to the underlying functions provided by the device driver.

A non-device file operates similarly, although there are more layers in between. The abstraction here is the mount point; mounting a file system has the dual purpose of setting up a mapping so the file system knows the underlying device that provides the storage and the kernel knows that files opened under that mount-point should be directed to the file system driver. Like device drivers, file systems are written to a particular generic file system API provided by the kernel.

There are indeed many other layers that complicate the picture in real-life. For example, the kernel will go to great efforts to cache as much data from disks as possible in otherwise-free memory; this provides many speed advantages. It will also try to organise device access in the most efficient ways possible; for example trying to order disk-access to ensure data stored physically close together is retrieved together, even if the requests did not arrive in sequential order. Further, many devices are of a more generic class such as USB or SCSI devices which provide their own abstraction layers to write to. Thus, rather than writing directly to devices, file systems will go through these many layers. Understanding the kernel is to understand how these many APIs interrelate and coexist.

The Shell

The shell is the gateway to interacting with the operating system. Be it bashzshcsh or any of the many other shells, they all fundamentally have only one major task — to allow you to execute programs (you will begin to understand how the shell actually does this when we talk about some of the internals of the operating system later).

But shells do much more than allow you to simply execute a program. They have powerful abilities to redirect files, allow you to execute multiple programs simultaneously and script complete programs. These all come back to the everything is a file idiom.

Redirection

Often we do not want the standard file descriptors mentioned in the section called “File Descriptors” to point to their default places. For example, you may wish to capture all the output of a program into a file on disk or, alternatively, have it read its commands from a file you prepared earlier. Another useful task might like to pass the output of one program to the input of another. With the operating system, the shell facilitates all this and more.

Table 1.2. Standard Shell Redirection Facilities
Name Command DescriptionExample
Redirect to a file > filename Take all output from standard out and place it into filename. Note using>> will append to the file, rather than overwrite it. ls > filename
Read from a filefilename Copy all data from the file to the standard input of the program echo < filename
Pipe program1 | program2 Take everything from standard out of program1 and pass it to standard input of program2ls | more

Implementing pipe

The implementation of ls | more is just another example of the power of abstraction. What fundamentally happens here is that instead of associating the file descriptor for the standard-output with some sort of underlying device (such as the console, for output to the terminal), the descriptor is pointed to an in-memory buffer provided by the kernel commonly termed a pipe. The trick here is that another process can associate its standard input with the other side of this same buffer and effectively consume the output of the other process. This is illustrated in Figure 1.4, “A pipe in action”.

Figure 1.4. A pipe in action
The pipe is an in-memory buffer provided by the kernel which allows the output of one process to be consumed as the input to another.

The pipe is an in-memory buffer that connects two processes together. file descriptors point to the pipe object, which buffers data sent to it (via a write) to be drained (via a read).


Writes to the pipe are stored by the kernel until a corresponding read from the other side drains the buffer. This is a very powerful concept and is one of the fundamental forms of inter-process communication or IPC in UNIX-like operating systems. The pipe allows more than just a data transfer; it can act as a signaling channel. If a process reads an empty pipe, it will by default block or be put into hibernation until there is some data available (this is discussed in much greater depth in Chapter 5, The Process). Thus two processes may use a pipe to communicate that some action has been taken just by writing a byte of data; rather than the actual data being important, the mere presence of any data in the pipe can signal a message. Say for example one process requests that another print a file — something that will take some time. The two processes may set up a pipe between themselves where the requesting process does a read on the empty pipe; being empty, that call blocks and the process does not continue. Once the print is done, the other process can write a message into the pipe, which effectively wakes up the requesting process and signals the work is done.

Allowing processes to pass data between each other like this springs another common UNIX idiom of small tools doing one particular thing. Chaining these small tools gives flexibility that a single monolithic tool often can not.

https://www.bottomupcs.com/file_descriptors.xhtml

### 概念解析 File descriptors(文件描述符)是操作系统内核用来访问文件或输入/输出资源的一种抽象机制。在Unix和类Unix系统中,每个打开的文件、套接字、管道或设备都对应一个唯一的整数标识符,称为文件描述符。 文件描述符的值通常从0开始,0、1和2分别默认对应标准输入(stdin)、标准输出(stdout)和标准错误(stderr)。当程序打开其他文件或网络连接时,系统会分配更高的整数作为描述符。 在系统运行中,文件描述符是有限的资源,每个进程能打开的文件描述符数量受到限制。这种限制分为两种类型: - **Soft limit**:当前生效的限制值,进程可以自行调整,但不能超过hard limit。 - **Hard limit**:最大允许设置的soft limit值,只有root用户或特定权限的用户才能调整[^1]。 ### 系统限制的影响 在某些应用(如Elasticsearch、Nexus Repository Manager和RabbitMQ)中,如果文件描述符的限制过低,可能导致性能瓶颈或服务异常。例如: - **Elasticsearch**:需要处理大量索引和并发请求,因此要求更高的文件描述符限制。官方推荐至少65536[^3]。 - **Nexus Repository Manager**:在版本3.8.0-02中,系统检测到文件描述符限制为4096时会提示用户增加到65536以确保正常运行[^2]。 - **RabbitMQ**:默认安装的文件描述符限制较低(如924),容易导致Socket连接占满,影响客户端长连接的稳定性。建议提高到更高的值(如65536)[^4]。 ### 设置文件描述符限制 要调整文件描述符限制,通常需要修改系统配置文件 `/etc/security/limits.conf`,例如: ```conf elasticsearch soft nofile 65536 elasticsearch hard nofile 65536 ``` 此外,可以通过命令 `ulimit -Hn` 检查当前的hard限制值。对于通过SSH登录的普通用户,系统会读取该配置文件并应用相应的限制[^1]。 ### 示例代码 以下是一个简单的Python代码片段,用于查看当前进程的文件描述符使用情况: ```python import resource # 获取当前进程的文件描述符软限制和硬限制 soft_limit, hard_limit = resource.getrlimit(resource.RLIMIT_NOFILE) print(f"Current soft limit: {soft_limit}") print(f"Current hard limit: {hard_limit}") ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值