根據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,而是一個很大的值。還有就是如果進行fcheck_files的時候,此fd已經被close了,則會返回NULL,也會報EBADF.

2、當我們要增加引用計數的時候,發現這個引用計數是0,也就是說被你自己進程的其他線程close了。


所以EBADF就是此fd被別人給close了,或者本身就不是個有效的socketfd


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這個錯誤的。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章