linux select io处理

Posted osnet

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux select io处理相关的知识,希望对你有一定的参考价值。

文章目录


先看一下select机制
select需要驱动程序的支持,驱动程序实现fops内的poll函数。select通过每个设备文件对应的poll函数提供的信息判断当前是否有资源可用(如可读或写),如果有的话则返回可用资源的文件描述符个数,没有的话则睡眠,等待有资源变为可用时再被唤醒继续执行。

select系统调用

int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);

参数说明:

maxfdp:被监听的文件描述符的总数,它比所有文件描述符集合中的文件描述符的最大值大1,因为文件描述符是从0开始计数的;

readfds、writefds、exceptset:分别指向可读、可写和异常等事件对应的描述符集合。

timeout:用于设置select函数的超时时间,即告诉内核select等待多长时间之后就放弃等待。timeout == NULL 表示等待无限长的时间

SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,
		fd_set __user *, exp, struct timeval __user *, tvp)

	struct timespec end_time, *to = NULL;
	struct timeval tv;
	int ret;
   //保存超时等待时间,存储到end_time中
	if (tvp) 
		if (copy_from_user(&tv, tvp, sizeof(tv)))
			return -EFAULT;

		to = &end_time;
		if (poll_select_set_timeout(to,
				tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),
				(tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
			return -EINVAL;
	

	ret = core_sys_select(n, inp, outp, exp, to);
	//设置剩余超时时间,返回到用户空间
	ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);

	return ret;

core_sys_select

int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,
			   fd_set __user *exp, struct timespec *end_time)

	fd_set_bits fds;
	void *bits;
	int ret, max_fds;
	unsigned int size;
	struct fdtable *fdt;
	/* Allocate small arguments on the stack to save memory and be faster */
	long stack_fds[SELECT_STACK_ALLOC/sizeof(long)]; //分配栈空间,一个fd描述符用一个bit表示,最多支持1024个



	/* max_fds can increase, so grab it once to avoid race */
	rcu_read_lock();
	fdt = files_fdtable(current->files); //当前进程打开的文件描述符表
	max_fds = fdt->max_fds;
	rcu_read_unlock();
	if (n > max_fds)
		n = max_fds;

	/*
	 * We need 6 bitmaps (in/out/ex for both incoming and outgoing),
	 * since we used fdset we need to allocate memory in units of
	 * long-words. 
	 */
	size = FDS_BYTES(n); //用户空间调用设置的最大fd描述符
	bits = stack_fds;
	if (size > sizeof(stack_fds) / 6)  如果栈空间不够,需要申请空间;6个为一组,包括用户空间传进的in,out,exception,和
	内核保存用的res_in,res_out,res_exception,其中存储发生变化的fd描述符
		/* Not enough space in on-stack array; must use kmalloc */
	
		bits = kmalloc(6 * size, GFP_KERNEL);

	
	
	fds.in      = bits;
	fds.out     = bits +   size;
	fds.ex      = bits + 2*size;
	fds.res_in  = bits + 3*size;
	fds.res_out = bits + 4*size;
	fds.res_ex  = bits + 5*size;
    //把用户空间in,out,exception 存储到fds中
	if ((ret = get_fd_set(n, inp, fds.in)) ||
	    (ret = get_fd_set(n, outp, fds.out)) ||
	    (ret = get_fd_set(n, exp, fds.ex)))
		goto out;
	zero_fd_set(n, fds.res_in);
	zero_fd_set(n, fds.res_out);
	zero_fd_set(n, fds.res_ex);

	ret = do_select(n, &fds, end_time);


    //设置fdsres_in,res_out,res_exception到用户空间
	if (set_fd_set(n, inp, fds.res_in) ||
	    set_fd_set(n, outp, fds.res_out) ||
	    set_fd_set(n, exp, fds.res_ex))
		ret = -EFAULT;



do_select

int do_select(int n, fd_set_bits *fds, struct timespec *end_time)

	ktime_t expire, *to = NULL;
	struct poll_wqueues table;
	poll_table *wait;
	int retval, i, timed_out = 0;
	unsigned long slack = 0;


   //初始化poll_table 表,用于poll
	poll_initwait(&table);
	wait = &table.pt;
	//处理边界条件,超时时间为0
	if (end_time && !end_time->tv_sec && !end_time->tv_nsec) 
		wait->_qproc = NULL;
		timed_out = 1;
	

    //for 死循环
	for (;;) 
		unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
        
		inp = fds->in; outp = fds->out; exp = fds->ex;
		rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
        //遍历所有fd,一直到用户调用设置的最大n
		for (i = 0; i < n; ++rinp, ++routp, ++rexp) 
			unsigned long in, out, ex, all_bits, bit = 1, mask, j;
			unsigned long res_in = 0, res_out = 0, res_ex = 0;

			in = *inp++; out = *outp++; ex = *exp++;
			all_bits = in | out | ex;
			//如果in | out | ex 没有一设置,下一轮
			if (all_bits == 0) 
				i += BITS_PER_LONG; //每次遍历处理BITS_PER_LONG个
				continue;
			
             //每次遍历处理BITS_PER_LONG个
			for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1) 
				struct fd f;
				if (i >= n)
					break;
				
				//如果bit没有再all_bits = in | out | ex中,继续下一个
				if (!(bit & all_bits))
					continue;
				f = fdget(i); //获取i对应的描述符
				if (f.file) 
					const struct file_operations *f_op;
					f_op = f.file->f_op;
					mask = DEFAULT_POLLMASK;
					//调用对应文件的poll,把当前进程加入wait队列,等到驱动io处理时,唤醒当前进程
					if (f_op && f_op->poll) 
						wait_key_set(wait, in, out, bit);
						mask = (*f_op->poll)(f.file, wait);
					
					fdput(f);
					//如果poll返回值有,则函数返回
					if ((mask & POLLIN_SET) && (in & bit)) 
						res_in |= bit;
						retval++;
						wait->_qproc = NULL;
					
					if ((mask & POLLOUT_SET) && (out & bit)) 
						res_out |= bit;
						retval++;
						wait->_qproc = NULL;
					
					if ((mask & POLLEX_SET) && (ex & bit)) 
						res_ex |= bit;
						retval++;
						wait->_qproc = NULL;
					
				
			
			//设置的res_in,res_out,res_ex
			if (res_in)
				*rinp = res_in;
			if (res_out)
				*routp = res_out;
			if (res_ex)
				*rexp = res_ex;
			cond_resched();
		
		wait->_qproc = NULL;
		//如果设置了fd,或者超时,或者有信号pending,跳出死循环,函数返回
		if (retval || timed_out || signal_pending(current))
			break;
		if (table.error) 
			retval = table.error;
			break;
		

		/*
		 * If this is the first loop and we have a timeout
		 * given, then we convert to ktime_t and set the to
		 * pointer to the expiry value.
		 */
		 //计算超时时间
		if (end_time && !to) 
			expire = timespec_to_ktime(*end_time);
			to = &expire;
		
        //当前进程让出cpu,超时返回,或者被io唤醒;  被唤醒后继续循环,调用poll看是否有fd变化
		if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
					   to, slack))
			timed_out = 1;
	

	poll_freewait(&table);

	return retval;

static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)

	pt->_qproc = qproc;
	pt->_key   = ~0UL; /* all events enabled */

void poll_initwait(struct poll_wqueues *pwq)

	init_poll_funcptr(&pwq->pt, __pollwait);
	pwq->polling_task = current; //设置当前进程
	pwq->triggered = 0;
	pwq->error = 0;
	pwq->table = NULL;
	pwq->inline_index = 0;


/* Add a new entry */
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
				poll_table *p)

	struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
	struct poll_table_entry *entry = poll_get_entry(pwq); //构造一个poll_table_entry 
	if (!entry)
		return;
	entry->filp = get_file(filp);
	entry->wait_address = wait_address;
	entry->key = p->_key;
	init_waitqueue_func_entry(&entry->wait, pollwake);
	entry->wait.private = pwq; //把wait挂载都驱动的等待队列wait_address中,wait_address具体io驱动的等待队列地址
	add_wait_queue(wait_address, &entry->wait);

以上是关于linux select io处理的主要内容,如果未能解决你的问题,请参考以下文章

Linux select多路复用介绍(转)

Linux网络编程-IO复用技术

JqueryUI 数据表设置记录总数

IO复用之——select

Linux中的IO复用接口简介(文件监视?)

Linux select 机制深入分析