共计 4581 个字符,预计需要花费 12 分钟才能阅读完成。
这篇文章将为大家详细讲解有关 Linux 系统下 fd 分配的方法是什么,文章内容质量较高,因此丸趣 TV 小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。
最近几天在公司里写网络通讯的代码比较多,自然就会涉及到 IO 事件监测方法的问题。我惊奇的发现 select 轮训的方法在那里居然还大行其道。我告诉他们现在无论在 Linux 系统下,还是 windows 系统下,select 都应该被废弃不用了,其原因是在两个平台上 select 的系统调用都有一个可以说是致命的坑。
在 windows 上面单个 fd_set 中容纳的 socket handle 个数不能超过 FD_SETSIZE(在 win32 winsock2.h 里其定义为 64,以 VS2010 版本为准),并且 fd_set 结构使用一个数组来容纳这些 socket handle 的,每次 FD_SET 宏都是向这个数组中放入一个 socket handle,并且此过程中是限定了不能超过 FD_SETSIZE,具体请自己查看 winsock2.h 中 FD_SET 宏的定义。
此处的问题是
若本身 fd_set 中的 socket handle 已经达到 FD_SETSIZE 个,那么后续的 FD_SET 操作实际上是没有效果的,对应 socket handle 的 IO 事件将被遗漏!!!
而在 Linux 系统下面,该问题其实也是处在 fd_set 的结构和 FD_SET 宏上。此时 fd_set 结构是使用 bit 位序列来记录每一个待检测 IO 事件的 fd。记录的方式稍微复杂,如下
/usr/include/sys/select.h 中
typedef long int __fd_mask; #define __NFDBITS (8 * sizeof (__fd_mask)) #define __FDELT(d) ((d) / __NFDBITS) #define __FDMASK(d) ((__fd_mask) 1 ((d) % __NFDBITS)) typedef struct { /* XPG4.2 requires this member name. Otherwise avoid the name from the global namespace. */ #ifdef __USE_XOPEN __fd_mask fds_bits[__FD_SETSIZE / __NFDBITS]; # define __FDS_BITS(set) ((set)- fds_bits) #else __fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS]; # define __FDS_BITS(set) ((set)- __fds_bits) #endif } fd_set; #define FD_SET(fd, fdsetp) __FD_SET (fd, fdsetp)
/usr/include/bits/select.h 中
1 # define __FD_SET(d, set) (__FDS_BITS (set)[__FDELT (d)] |= __FDMASK (d))
可以看出,在上面的过程,实际上每个 bit 在 fd_set 的 bit 序列中的位置对应于 fd 的值。而 fd_set 结构中 bit 位个数是__FD_SETSIZE 定义的,__FD_SETSIZE 在 /usr/include/bits/typesize.h(包含关系如下 sys/socket.h – bits/types.h – bits/typesizes.h)中被定义为 1024。
现在的问题是,当 fd =1024 时,FD_SET 宏实际上会引起内存写越界。而实际上在 man select 中对已也有明确的说明,如下
NOTES
An fd_set is a fixed size buffer. Executing FD_CLR() or FD_SET() with a value of fd that is negative or is equal to or
larger than FD_SETSIZE will result in undefined behavior. Moreover, POSIX requires fd to be a valid file descriptor.
这一点包括之前的我,是很多人没有注意到的,并且云风大神有篇博文《一起 select 引起的崩溃》也描述了这个问题。
可以看出在 Linux 系统 select 也是不安全的,若想使用,得小心翼翼的确认 fd 是否达到 1024,但这很难做到,不然还是老老实实的用 poll 或 epoll 吧。
扯得有点远了,但也引出了本片文章要叙述的主题,就是 Linux 系统下 fd 值是怎么分配确定,大家都知道 fd 是 int 类型,但其值是怎么增长的,在下面的内容中我对此进行了一点分析,以 2.6.30 版本的 kernel 为例,欢迎拍砖。
首先得知道是哪个函数进行 fd 分配,对此我以 pipe 为例,它是分配 fd 的一个典型的 syscall,在 fs/pipe.c 中定义了 pipe 和 pipe2 的 syscall 实现,如下
SYSCALL_DEFINE2(pipe2, int __user *, fildes, int, flags) { int fd[2]; int error; error = do_pipe_flags(fd, flags); if (!error) { if (copy_to_user(fildes, fd, sizeof(fd))) { sys_close(fd[0]); sys_close(fd[1]); error = -EFAULT; } } return error; } SYSCALL_DEFINE1(pipe, int __user *, fildes) { return sys_pipe2(fildes, 0); }
进一步分析 do_pipe_flags()实现,发现其使用 get_unused_fd_flags(flags)来分配 fd 的,它是一个宏
#define get_unused_fd_flags(flags) alloc_fd(0, (flags)),位于 include/linux/fs.h 中
好了咱们找到了主角了,就是 alloc_fd(),它就是内核章实际执行 fd 分配的函数。其位于 fs/file.c,实现也很简单,如下
int alloc_fd(unsigned start, unsigned flags) { struct files_struct *files = current- files; unsigned int fd; int error; struct fdtable *fdt; spin_lock( files- file_lock); repeat: fdt = files_fdtable(files); fd = start; if (fd files- next_fd) fd = files- next_fd; if (fd fdt- max_fds) fd = find_next_zero_bit(fdt- open_fds- fds_bits, fdt- max_fds, fd); error = expand_files(files, fd); if (error 0) goto out; /* * If we needed to expand the fs array we * might have blocked - try again. */ if (error) goto repeat; if (start = files- next_fd) files- next_fd = fd + 1; FD_SET(fd, fdt- open_fds); if (flags O_CLOEXEC) FD_SET(fd, fdt- close_on_exec); else FD_CLR(fd, fdt- close_on_exec); error = fd; #if 1 /* Sanity check */ if (rcu_dereference(fdt- fd[fd]) != NULL) { printk(KERN_WARNING alloc_fd: slot %d not NULL!\n , fd); rcu_assign_pointer(fdt- fd[fd], NULL); } #endif out: spin_unlock(files- file_lock); return error; }
在 pipe 的系统调用中 start 值始终为 0,而中间比较关键的 expand_files()函数是根据所给的 fd 值,判断是否需要对进程的打开文件表进行扩容,其函数头注释如下
/* * 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. */
此处对其实现就不做深究了,回到 alloc_fd(),现在可以看出,其分配 fd 的原则是
每次优先分配 fd 值最小的空闲 fd,当分配不成功,即返回 EMFILE 的错误码,这表示当前进程中 fd 太多。
到此也印证了在公司写的服务端程序 (kernel 是 2.6.18) 中,每次打印 client 链接对应的 fd 值得变化规律了,假如给一个新连接分配的 fd 值为 8,那么其关闭之后,紧接着的新的链接分配到的 fd 也是 8,再新的链接的 fd 值是逐渐加 1 的。
为此,我继续找了一下 socket 对应 fd 分配方法,发现最终也是 alloc_fd(0, (flags),调用序列如下
socket(sys_call) – sock_map_fd() – sock_alloc_fd() – get_unused_fd_flags()
open 系统调用也是用 get_unused_fd_flags(),这里就不列举了。
现在想回头说说开篇的 select 的问题。由于 Linux 系统 fd 的分配规则,实际上是已经保证每次的 fd 值尽量的小,一般非 IO 频繁的系统,的确一个进程中 fd 值达到 1024 的概率比较小。因而对此到底是否该弃用 select,还不能完全地做绝对的结论。如果设计的系统的确有其他措施保证 fd 值小于 1024,那么用 select 无可厚非。
但在网络通讯程序这种场合是绝不应该作此假设的,所以还是尽量的不用 select 吧!!
关于 Linux 系统下 fd 分配的方法是什么就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。