文件描述符是?
Last updated on 8 months ago
fd
是什么?
文件描述符是一个与输入/输出资源相关的整数,也可以被称为文件句柄(file handle)、文件指针(file pointer)或文件引用(file reference)。简单来说,它是操作系统为了管理 I/O 操作而维护的一个表中的索引,代表着系统中打开的文件的一个“门牌号”,在linux 世界中一切皆文件,文件描述符占比很关键
文件描述符的应用
文件操作:
open() 函数打开一个文件并获取文件描述符。通过 read() 和 write() 函数可以读写文件,使用 lseek() 函数可以移动文件读写指针,fcntl() 函数用于控制文件的属性等。进程控制:
进程之间的通信需要使用进程间通信机制(IPC),管道(pipe)可以用于进程间的无名管道通信,socketpair() 可以创建一对已连接的 socket,以便进程间可以进行通信等。网络编程:
每个套接字也是由一个fd管理
文件描述符就只是单纯的数字吗?
写一个简单的demo,打开一个文件,返回一个fd,并打印fd值
1 |
|
每个进程都有个 pid, 在 /proc 目录中可以找到对应的pid目录,该目录包含了进程本身相关信息的文件,其中就有 fd信息
1 |
|
可以看到 fd 里面有0、1、2、3;3 是刚打开一个 文件返回的,damo 里面我们是写入了 dd 字符到 abc 文件里面,那我们是不是可以 用echo 命令重定向 一些内容到 文件描述符 3里面?
1 |
|
是可以的!我们在改下demo,加入一个文件被打开多次? fd 还会是同一个吗?
1 |
|
fd 只能指向一个文件
一个多文件可以被多个fd 指向,
每个进程的fd 是隔离的,fd 只是个数字,对于不同进程指向的内容是不同的
我们也可以用 losf
命令看 一个文件别多少个fd 占用
1 |
|
从用户的使用来看, fd 像是一个连接用户与文件桥梁,不只是文件,还有很多,我更觉得像 handle
文件描述符 0,1,2 什么 ?
发现每个进程的文件描述符都是 从3开始的,因为0,1,2被占用了,那这三个fd 的作用?
在Linux和unix系统中,文件描述符0,1,2是系统预留的,每个程序在运行后,都会至少打开三个文件描述符,分别是0、1、2,它们的意义分别有如下对应关系:
0 stdin (标准输入)
1 stdout (标准输出)
2 stderr (标准错误)
比如我们经常 把一些结果过滤后重定向到一个文件
1 |
|
当然我们也可以直接输出到终端,这个终端输出的就是 通过 标准输出fd 1 输出的,我们我们把 文件描述符1 重定向到文件,效果也一样
1 |
|
描述文件符2,是这个终端标准错误,比如ls 不存在的文件,重定向文件描述符 2 到 /tmp/log
1 |
|
文件描述符0 ,则是 输入,会读取键盘的输入,文件的输入等等…
fd在内核中是怎么构造的?
就已 open 函数来分析,在系统调用章节,我们可以推出 open系统调用在内核代码中对应的函数
1 |
|
主要分析 get_unused_fd_flags,看看 fd 是如何分配的
1 |
|
current->files 是什么?
Linux内核通过一个被称为进程描述符的**task_struct**
结构体来管理进程,这个结构体包含了一个进程所需的所有信息,#define current get_current()
宏就是获取当前的进程 的task_struct,current->files 则是获取当前进程的file table structure files_struct
1 |
|
files_struct 是什么?
1 |
|
回到 __alloc_fd
1 |
|
有几个问题:
fdtable 是什么?
file 文件描述符表,用了位图方式记录 已经打开的fd,可用的fd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17struct fdtable {
//记录当前最大的max_fds
unsigned int max_fds;
struct file __rcu **fd; /* current fd array */
// unsigned long 以下三个用作位图, 64bit,0 代表fd 为使用,1 为使用
unsigned long *close_on_exec;
//每个bit 代表一个文件描述符
// 第35 bit为1,则表示文件描述符35已经被使用
unsigned long *open_fds;
// 每个bit代表64位数组,这个数组代表 0-63的文件描述符
// bit0 为1 则表明0~63都使用了,为0 0~63还没被使用
unsigned long *full_fds_bits;
struct rcu_head rcu;
};find_next_fd 函数是怎么找的呢?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20static unsigned int find_next_fd(struct fdtable *fdt, unsigned int start)
{
//当前最大的fd上限
unsigned int maxfd = fdt->max_fds;
//除以 64先找到 ,第几组文件描述符,比如现在 max_fds 为67, 得到 maxbit 为1,目前第一组还有空的,这里 maxfd / BITS_PER_LONG 说明想找到最后 一组
unsigned int maxbit = maxfd / BITS_PER_LONG;
//找到最开始的一组,start 即时 最大nextfd
unsigned int bitbit = start / BITS_PER_LONG;
//现在 才真正的开始找,最start 和 end 都有了,找到还有空闲的那一组(其实就是找首个非1的bit) 乘以BITS_PER_LONG得到真正的fd
bitbit = find_next_zero_bit(fdt->full_fds_bits, maxbit, bitbit) * BITS_PER_LONG;
//bitbit 超过了 maxfd,直接返回
if (bitbit > maxfd)
return maxfd;
//超过satrt 才是正常的
if (bitbit > start)
start = bitbit;
// 然后从 open_fds 找到 空闲fd
return find_next_zero_bit(fdt->open_fds, maxfd, start);
}从 find_next_fd看到, bit 是可能超过 maxfd的,那怎么处理呢?
从函数如果超过了,就直接返回,其实 后面还是有处理的 在 expand_files里面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50/*
* Expand files.
* This function will expand the file structures, if the requested size exceeds
* the current capacity and there is room for expansion.
* Return <0 error code on error; 0 when nothing done; 1 when files were
* expanded and execution may have blocked.
* The files->file_lock should be held on entry, and will be held on exit.
*/
static int expand_files(struct files_struct *files, unsigned int nr)
__releases(files->file_lock)
__acquires(files->file_lock)
{
struct fdtable *fdt;
int expanded = 0;
repeat:
//和上面一样,先后去 fd table
fdt = files_fdtable(files);
/* Do we need to expand? */
//比maxfd小直接返回
if (nr < fdt->max_fds)
return expanded;
/* Can we expand? */
//大于文件描述符限制 , ulimit -a 可以看 最大值
if (nr >= sysctl_nr_open)
return -EMFILE;
//这种情况是 在另外一处也在扩容fd,这里上锁等待 TODO: wait_event??
if (unlikely(files->resize_in_progress)) {
spin_unlock(&files->file_lock);
expanded = 1;
wait_event(files->resize_wait, !files->resize_in_progress);
spin_lock(&files->file_lock);
goto repeat;
}
/* All good, so we try */
files->resize_in_progress = true;
// 扩容 fd
//里面大致实现是 根据当前的nr 值,重新allocte 一个新的maxfd ,然后将原来的拷贝到新的 fdtalbe
expanded = expand_fdtable(files, nr);
files->resize_in_progress = false;
wake_up_all(&files->resize_wait);
return expanded;
}
next_fd 作用是?
每次获取到新的fd, 基于这个fd+1得到 next_fd,好像是为了准备一下个fd;可用的fd 最终是在**__set_open_fd** 函数是有更新到 open_fd 位图中的,但是我们发现 full_fds_bits_init 位图是没有实质上的更新的,只是单凭借起始位置 strart 和 maxfd 来判断,而 start 正是 next_fd, 由此发现 next_fd 作用是为了定位 full_fds_bits_init 当前位置
结语
到现为止我们知道 fd 是怎么生成,以及哪里有记录,都介绍了; open的实现到此为止,本来也是讲open,继续挖下去,感觉应先把 inode 这些原理讲清楚,这样才可以方便展开讲