zoukankan      html  css  js  c++  java
  • 根据linux内核源码查找recv返回EBADF(errno 9)的原因

    linux的内核版本是2.6.18,x86_64.

    man里的解释是:

    EBADF

    The argument s is an invalid descriptor

    我的模拟测试环境是:

    前端loadrunner模拟web点击,通过后端的weblogic压自己的服务的时候发现,有时候recv会收到这个错误,意思就是这个fd已经失效了,但是有点不是很明白,所以查询下内核实现,验证下。

    首先recv的实现就是调用的recvfrom:


    /*
     *	Receive a datagram from a socket. 
     */
    
    asmlinkage long sys_recv(int fd, void __user * ubuf, size_t size, unsigned flags)
    {
    	return sys_recvfrom(fd, ubuf, size, flags, NULL, NULL);
    }


    然后看 sys_recvfrom 的实现:

     

    asmlinkage long sys_recvfrom(int fd, void __user * ubuf, size_t size, unsigned flags,
    			     struct sockaddr __user *addr, int __user *addr_len)
    {
    	struct socket *sock;
    	struct iovec iov;
    	struct msghdr msg;
    	char address[MAX_SOCK_ADDR];
    	int err,err2;
    	struct file *sock_file;
    	int fput_needed;
    
    	sock_file = fget_light(fd, &fput_needed);
    	if (!sock_file)
    		return -EBADF;
    
    	sock = sock_from_file(sock_file, &err);
    	if (!sock)
    		goto out;
    
    	msg.msg_control=NULL;
    	msg.msg_controllen=0;
    	msg.msg_iovlen=1;
    	msg.msg_iov=&iov;
    	iov.iov_len=size;
    	iov.iov_base=ubuf;
    	msg.msg_name=address;
    	msg.msg_namelen=MAX_SOCK_ADDR;
    	if (sock->file->f_flags & O_NONBLOCK)
    		flags |= MSG_DONTWAIT;
    	err=sock_recvmsg(sock, &msg, size, flags);
    
    	if(err >= 0 && addr != NULL)
    	{
    		err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len);
    		if(err2<0)
    			err=err2;
    	}
    out:
    	fput_light(sock_file, fput_needed);
    	return err;
    }
    

    从代码内可以看到是fget_light这个函数如果返回NULL,则对外报EBADF错误。

    那么fget_light这个函数做了什么呢,继续看代码:

    /*
     * Lightweight file lookup - no refcnt increment if fd table isn't shared. 
     * You can use this only if it is guranteed that the current task already 
     * holds a refcnt to that file. That check has to be done at fget() only
     * and a flag is returned to be passed to the corresponding fput_light().
     * There must not be a cloning between an fget_light/fput_light pair.
     */
    struct file fastcall *fget_light(unsigned int fd, int *fput_needed)
    {
    	struct file *file;
    	struct files_struct *files = current->files;
    
    	*fput_needed = 0;
            /* 如果你的程序是多线程的,或者多进程并且进程间是通过clone产生并且带着CLONE_FILES标识,
            那么此处&files->count值不为1 */
            if (likely((atomic_read(&files->count) == 1))) {
    		file = fcheck_files(files, fd);
    	} else {
    		rcu_read_lock();
    		file = fcheck_files(files, fd);
    		if (file) {
                            /* 因为要引用和此fd相关的struct file结构,所以需要将引用计数器加1,
                              但是只有在f_count不为0的时候才去加1,如果f_count为0,说明此fd已经
                              被close掉,并且释放掉资源了。*/
                             if (atomic_inc_not_zero(&file->f_count))
    				*fput_needed = 1;
    			 else
    				 /* Didn't get the reference, someone's freed */
     				file = NULL;
    		}
    		rcu_read_unlock();
    	}
    
    	return file;
    }

    再看函数fcheck_files

     

    static inline struct file * fcheck_files(struct files_struct *files, unsigned int fd)
    {
    	struct file * file = NULL;
    	struct fdtable *fdt = files_fdtable(files);
    
    	if (fd < fdt->max_fds)
    		file = rcu_dereference(fdt->fd[fd]);
    	return file;
    }

    其实到这里就可以看出来,fget_light总共有两个地方返回NULL

    1、fcheck_files内发现fd > fdt->max_fds 会返回NULL,这种情况一般不会发生,除非你调用recv的时候传入的fd值不是真实使用的fd,而是一个很大的值。

    2、当我们要增加引用计数的时候,发现这个引用计数是0,也就是说被你自己进程的其他线程close了。

    max_fds可以简单通过/proc/pid/statusFDSize查看值,这个max_fds其实是计算出来的,下面给出算法

     

    	/* NR_OPEN_DEFAULT就是long的bit数,x86是32,x64是64 */
    nfds = NR_OPEN_DEFAULT;
    	/*
    	 * Expand to the max in easy steps, and keep expanding it until
    	 * we have enough for the requested fd array size.
    	 */
    	do {
    #if NR_OPEN_DEFAULT < 256
    		if (nfds < 256)
    			nfds = 256;
    		else
    #endif
    		if (nfds < (PAGE_SIZE / sizeof(struct file *)))
    			nfds = PAGE_SIZE / sizeof(struct file *);
    		else {
    			nfds = nfds * 2;
    			if (nfds > NR_OPEN)
    				nfds = NR_OPEN;
      		}
    	} while (nfds <= nr);

    初始值是NR_OPEN_DEFAULT就是longbit数,x6464x8632

    如果超过这个就变成256,如果超过2564096/8就是512,超过512就是

    512*2=1024,再超过就是1024*2=2048,一直这样成倍增加,直到最大值NR_OPEN,是1024*1024=1048576

    可能会有人问,不是还有ulimit设置的open files的限制的吗?

    恩,是有,这个叫做进程的资源限制,不过这个检查是在分配新的fd,比如accept这时候去检查的,如果新的fd大于那个限制是会直接报错EMFILE这个错误的。




  • 相关阅读:
    wifi应用领域
    wifi主要功能
    Wi-Fi技术原理
    自适应通信类型与基本原理
    自适应通信发展背景
    自适应通信
    无线通信技术
    无线通信的一些专业术语
    无线通信
    Bluetooth vs. Wi-Fi(IEEE 802.11)
  • 原文地址:https://www.cnblogs.com/james1207/p/3400192.html
Copyright © 2011-2022 走看看