Linux系统下fd分配的方法
最近几天在公司里写网络通讯的代码比较多,自然就会涉及到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中
# 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[];
int error; error = do_pipe_flags(fd, flags);
if (!error) {
if (copy_to_user(fildes, fd, sizeof(fd))) {
sys_close(fd[]);
sys_close(fd[]);
error = -EFAULT;
}
}
return error;
} SYSCALL_DEFINE1(pipe, int __user *, fildes)
{
return sys_pipe2(fildes, );
}
进一步分析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 < )
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 + ; 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个数为1024,所以没有将其改为大于1024的情况下使用select来检测IO事件是不会因fdset读写而导致的内存越界的问题;
但网络服务场合,若有高并发的需求,多会对这项系统配置改为更大的值,此时使用select就有问题。并且现在的Linux系统都有poll()这个调用,所以完全没有使用select()的必要,完全可以用poll()代替select()
Linux系统下fd分配的方法的更多相关文章
- Linux系统下查找文件的方法
Linux系统下查找文件的方法 作者:Vashon 时间:20150419 方法一.在当前目录里查找所有名为以 java 开头的文件: find ./ -name "java*" ...
- linux系统下使用apt-get install 方法安装lamp环境
1.更新源,获得最近的软件包的列表,列表中包含一些包的信息,比如这个包是否更新过. sudo apt-get update 2.更新系统中已安装的软件包 sudo apt-get upgrade 3. ...
- linux系统下mySQL数据库 备份方法和脚本
数据库备份1.创建个备份存储目录mkdir /root/backup/2.以下内容写到dbbackup.sh #!/bin/bash cd /data/db_backup/mysqldump -uad ...
- Linux系统下修改环境变量PATH路径的三种方法
这里介绍Linux的知识,比如把/etc/apache/bin目录添加到PATH中有三种方法,看完之后你将学会Linux系统下如何修改环境变量PATH路径,需要的朋友可以参考下 电脑中必不可少的就是操 ...
- 查找linux系统下的端口被占用进程的两种方法 【转】
在linux下开发时,你的软件可能要使用某一个端口,或者想查找某一个端口是否被占用.需要怎么做呢??这的确是一个比较烦恼的问题,我也此为这个苦恼过.但是通过查找man手册,还是同事的交流.总结出来两种 ...
- Linux系统下不同机器之间拷贝文件的方法
在Linux系统下,不同机器上实现文件拷贝 一.将本地文件拷贝到远程机器: scp /home/administrator/news.txt root@192.168.6.129:/etc/squid ...
- Linux系统下常见的数据盘分区丢失的问题以及对应的处理方法
在修复数据前,您必须先对分区丢失的数据盘创建快照,在快照创建完成后再尝试修复.如果在修复过程中出现问题,您可以通过快照回滚将数据盘还原到修复之前的状态. 前提条件 在修复数据前,您必须先对分区丢失的数 ...
- Linux系统下Java 转换Word到PDF时,结果文档内容乱码的解决方法
本文分享在Linux系统下,通过Java 程序代码将Word转为PDF文档时,结果文档内容出现乱码该如何解决.具体可参考如下内容: 1.问题出现的背景 在Windows系统中,使用Spire.Doc ...
- Linux系统下DHCP服务安装部署和使用详解
一.概述 DHCP :动态主机设置协议(英语:Dynamic Host Configuration Protocol,DHCP)是一个局域网的网络协议,使用UDP协议工作,主要有两个用途:用于内部网或 ...
随机推荐
- 让下拉框中同时显示Key与Value
声明:原创作品,转载时请注明文章来自SAP师太技术博客( 博/客/园www.cnblogs.com):www.cnblogs.com/jiangzhengjun,并以超链接形式标明文章原始出处,否则将 ...
- xshell 终端窗口目录显示为深蓝色的不易分辨问题
xshell更改终端窗口目录展示深蓝色的不易分辨 经常使用xshell远程连接服务器,使用ls命令,目录的颜色都是深蓝色, 如果终端窗口背景颜色是黑色的(对眼睛较好的黑色的背景色,大家一般都选择黑色背 ...
- python pymongo-ensure_index
create_index 与 ensure_index 一.概述 ensure_index() 在mongoDB中创建索引 看了[1]感觉自己有些断章取义 ----------41316-- 参考链接 ...
- ssh框架介绍
SSH 为 struts+spring+hibernate 的一个集成框架,是目前较流行的一种JAVA Web应用程序开源框架. Struts Struts是一个基于Sun J2EE平台的MVC框架, ...
- RMAN的实战篇--备份脚本
案列一. 目标: 1.每天夜间1 点执行:2.数据库全备,同时备份控制文件及归档日志文件,备份文件保存至: /backup\目录下,并在完成归档日志文件备份后,自动删除已备份的归档日志:3.备份保留7 ...
- Krajee 文件上传
http://plugins.krajee.com/file-input/demo#ajax-uploads 插件官网 项目要个好看点的上传控件,于是搜到了这个. git的地址是 https://gi ...
- kafka原理存储
http://www.open-open.com/lib/view/open1421150566328.html
- linux驱动初探之字符驱动
关键字:字符驱动.动态生成设备节点.helloworld linux驱动编程,个人觉得第一件事就是配置好平台文件,这里以字符设备,也就是传说中的helloworld为例~ 此驱动程序基于linux3. ...
- springmvc使用spring自带日期类型验证
控制器 @Controller public class MyController { // 处理器方法 @RequestMapping(value = "/first.do") ...
- 读<jquery 权威指南>[7]-性能优化与最佳实践
一.优化选择器执行速度 1. 优先使用ID选择器和标记选择器 使用选择器时应该首选ID选择器($("#id")),其次是标记选择器($("div")),最后再选 ...