根据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:


[cpp]  view plain copy
  1. /* 
  2.  *  Receive a datagram from a socket.  
  3.  */  
  4.   
  5. asmlinkage long sys_recv(int fd, void __user * ubuf, size_t size, unsigned flags)  
  6. {  
  7.     return sys_recvfrom(fd, ubuf, size, flags, NULL, NULL);  
  8. }  

然后看 sys_recvfrom 的实现:

[cpp]  view plain copy
  1. asmlinkage long sys_recvfrom(int fd, void __user * ubuf, size_t size, unsigned flags,  
  2.                  struct sockaddr __user *addr, int __user *addr_len)  
  3. {  
  4.     struct socket *sock;  
  5.     struct iovec iov;  
  6.     struct msghdr msg;  
  7.     char address[MAX_SOCK_ADDR];  
  8.     int err,err2;  
  9.     struct file *sock_file;  
  10.     int fput_needed;  
  11.   
  12.     sock_file = fget_light(fd, &fput_needed);  
  13.     if (!sock_file)  
  14.         return -EBADF;  
  15.   
  16.     sock = sock_from_file(sock_file, &err);  
  17.     if (!sock)  
  18.         goto out;  
  19.   
  20.     msg.msg_control=NULL;  
  21.     msg.msg_controllen=0;  
  22.     msg.msg_iovlen=1;  
  23.     msg.msg_iov=&iov;  
  24.     iov.iov_len=size;  
  25.     iov.iov_base=ubuf;  
  26.     msg.msg_name=address;  
  27.     msg.msg_namelen=MAX_SOCK_ADDR;  
  28.     if (sock->file->f_flags & O_NONBLOCK)  
  29.         flags |= MSG_DONTWAIT;  
  30.     err=sock_recvmsg(sock, &msg, size, flags);  
  31.   
  32.     if(err >= 0 && addr != NULL)  
  33.     {  
  34.         err2=move_addr_to_user(address, msg.msg_namelen, addr, addr_len);  
  35.         if(err2<0)  
  36.             err=err2;  
  37.     }  
  38. out:  
  39.     fput_light(sock_file, fput_needed);  
  40.     return err;  
  41. }  

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

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

[cpp]  view plain copy
  1. /* 
  2.  * Lightweight file lookup - no refcnt increment if fd table isn't shared.  
  3.  * You can use this only if it is guranteed that the current task already  
  4.  * holds a refcnt to that file. That check has to be done at fget() only 
  5.  * and a flag is returned to be passed to the corresponding fput_light(). 
  6.  * There must not be a cloning between an fget_light/fput_light pair. 
  7.  */  
  8. struct file fastcall *fget_light(unsigned int fd, int *fput_needed)  
  9. {  
  10.     struct file *file;  
  11.     struct files_struct *files = current->files;  
  12.   
  13.     *fput_needed = 0;  
  14.         /* 如果你的程序是多线程的,或者多进程并且进程间是通过clone产生并且带着CLONE_FILES标识, 
  15.         那么此处&files->count值不为1 */  
  16.         if (likely((atomic_read(&files->count) == 1))) {  
  17.         file = fcheck_files(files, fd);  
  18.     } else {  
  19.         rcu_read_lock();  
  20.         file = fcheck_files(files, fd);  
  21.         if (file) {  
  22.                         /* 因为要引用和此fd相关的struct file结构,所以需要将引用计数器加1, 
  23.                           但是只有在f_count不为0的时候才去加1,如果f_count为0,说明此fd已经 
  24.                           被close掉,并且释放掉资源了。*/  
  25.                          if (atomic_inc_not_zero(&file->f_count))  
  26.                 *fput_needed = 1;  
  27.              else  
  28.                  /* Didn't get the reference, someone's freed */  
  29.                 file = NULL;  
  30.         }  
  31.         rcu_read_unlock();  
  32.     }  
  33.   
  34.     return file;  
  35. }  

再看函数fcheck_files

[cpp]  view plain copy
  1. static inline struct file * fcheck_files(struct files_struct *files, unsigned int fd)  
  2. {  
  3.     struct file * file = NULL;  
  4.     struct fdtable *fdt = files_fdtable(files);  
  5.   
  6.     if (fd < fdt->max_fds)  
  7.         file = rcu_dereference(fdt->fd[fd]);  
  8.     return file;  
  9. }  

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

1、fcheck_files内发现fd > fdt->max_fds 会返回NULL,这种情况一般不会发生,除非你调用recv的时候传入的fd值不是真实使用的fd,而是一个很大的值。还有就是如果进行fcheck_files的时候,此fd已经被close了,则会返回NULL,也会报EBADF.

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


所以EBADF就是此fd被别人给close了,或者本身就不是个有效的socketfd


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

[cpp]  view plain copy
  1. /"font-family:SimSun;">* NR_OPEN_DEFAULT就是long的bit数,x86是32,x64是64 */  
  2. /span>"font-family:SimSun;">         nfds = NR_OPEN_DEFAULT;  
  3. /* 
  4.  * Expand to the max in easy steps, and keep expanding it until 
  5.  * we have enough for the requested fd array size. 
  6.  */  
  7. do {  
  8. if NR_OPEN_DEFAULT < 256  
  9.     if (nfds < 256)  
  10.         nfds = 256;  
  11.     else  
  12. endif  
  13.     if (nfds < (PAGE_SIZE / sizeof(struct file *)))  
  14.         nfds = PAGE_SIZE / sizeof(struct file *);  
  15.     else {  
  16.         nfds = nfds * 2;  
  17.         if (nfds > NR_OPEN)  
  18.             nfds = NR_OPEN;  
  19.         }  
  20. 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这个错误的。

你可能感兴趣的:(Linux)