我要等你多久---Linux Select

一. Select是Linux下提供的一种监控文件状态的机制。Seletc对多个描述符的状态进行监控,一旦所监控的描述符符合可读、可写、异常中的一种,线程结束阻塞,否则阻塞到描述符集发生变化,或者超出等待时间

原型为:

sys_select(int n, fd_set *inp, fd_set *outp, fd_set *exp, struct timeval *tvp)
参数1:表示监控的描述符的个数,为描述符的个数加1.

参数2:表示监控状态为可读取的描述符的集合

参数3:表示监控状态为可写的描述符的集合

参数4:表示监控状态为异常(exception)的描述符的集合

参数5:时间段,超时时间的长短,超过这个时间以后,即使文件不可读、不可写、异常,sys_select也会退出,不在阻塞。

 

二.Linux的特点:一切皆文件。通常情况下可多种以将任意对象转化为描述符的显示,如socket、消息队列,可以转化描述的资源都可以使用select等待。

 

三.select的优点

显示中,许多任务是由消息驱动的,有任务来临的时候线程执行,如dhcp server、snmp server,当有报文过得时候,开始处理报文,如果报文处理完以后,任务结束,等待新的消息。

1.1 最容易象想到的线程等待的方法是sleep,伪代码如:

      while(1)
   {

           接收消息;

           处理消息;

           sleep(1s);

  },

select的伪代码:

   while(1)

 {

    ret =select();

    接收消息;

    处理消息

}

  这种使用sleep的方法可以避免线程循环允许空任务,造成cpu的浪费。但是如果长时间没有消息到来,执行接收消息也是要占用cpu的,即使读出的为空消息。而select很好的解决了这个问题:如果我的消息队列是不可读,也就是没有消息过来,用户空间线程这个时候休眠,不占用任何资源,一旦有消息到来,内核唤醒该线程,select不再阻塞,线程被内核唤醒。

   在内核中执行do_select函数也是使用的定期轮询的方式监控描述符。

 

四、select的机制

  select调用图

    select通过调用内核的_sys_select,在内核中循环监控描述符的poll状态,一般驱动中,设备都有poll函数的实现,该函数在内核执行,一个unix socket和tcp socket的描述符结构如:

 

static struct proto_ops unix_stream_ops = {
    .family =   PF_UNIX,
    .owner =    THIS_MODULE,
    .release =  unix_release,
    .bind =     unix_bind,
    .connect =  unix_stream_connect,
    .socketpair =   unix_socketpair,
    .accept =   unix_accept,
    .getname =  unix_getname,
    .poll =     unix_poll,
    .ioctl =    unix_ioctl,
    .listen =   unix_listen,
    .shutdown = unix_shutdown,
    .setsockopt =   sock_no_setsockopt,
    .getsockopt =   sock_no_getsockopt,
    .sendmsg =  unix_stream_sendmsg,
    .recvmsg =  unix_stream_recvmsg,
    .mmap =     sock_no_mmap,
    .sendpage = sock_no_sendpage,
};
const struct proto_ops inet_stream_ops = {
	.family		   = PF_INET,
	.owner		   = THIS_MODULE,
	.release	   = inet_release,
	.bind		   = inet_bind,
	.connect	   = inet_stream_connect,
	.socketpair	   = sock_no_socketpair,
	.accept		   = inet_accept,
	.getname	   = inet_getname,
	.poll		   = tcp_poll,
	.ioctl		   = inet_ioctl,
	.listen		   = inet_listen,
	.shutdown	   = inet_shutdown,
	.setsockopt	   = sock_common_setsockopt,
	.getsockopt	   = sock_common_getsockopt,
	.sendmsg	   = inet_sendmsg,
	.recvmsg	   = inet_recvmsg,
	.mmap		   = sock_no_mmap,
	.sendpage	   = inet_sendpage,
	.splice_read	   = tcp_splice_read,
#ifdef CONFIG_COMPAT
	.compat_setsockopt = compat_sock_common_setsockopt,
	.compat_getsockopt = compat_sock_common_getsockopt,
	.compat_ioctl	   = inet_compat_ioctl,
#endif
};

 

内核的设备的Poll函数在状态变为可读是,会主动唤醒被阻塞的线程。

 

五、关键函数

 

/*源码来源于Linux-3.4.69*/

/*
end_time是一个时间点,而不是时间段,由系统调用sys_select入口,通过poll_select_set_timeout计算
*/

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;

	rcu_read_lock();
	retval = max_select_fd(n, fds);
	rcu_read_unlock();

	if (retval < 0)
		return retval;
	n = retval;

	poll_initwait(&table);
	wait = &table.pt;
	if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
		wait->_qproc = NULL;
		timed_out = 1;
	}

    /*设置轮询的周期:select_estimate_accuracy函数根据当前时间和截止时间,选择一个合适的周期,去执行外层循环,执行完以后会休眠slack时间,slack以纳秒为单位,这种方式极大地提高了CPU的使用率,避免使用死循环造成的资源浪费*/
	if (end_time && !timed_out)
		slack = select_estimate_accuracy(end_time);

	retval = 0;
    /*外层循环为任务死循环,用于连续执行循环内部代码,满足条件以后通过break跳出。
     中层循环表示轮询每个描述符集。
     内层循环表示轮询每个描述符 */
	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;

		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;
			const struct file_operations *f_op = NULL;
			struct file *file = NULL;

			in = *inp++; out = *outp++; ex = *exp++;
			all_bits = in | out | ex;
			if (all_bits == 0) {
				i += BITS_PER_LONG;
				continue;
			}

			for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1) {
				int fput_needed;
				if (i >= n)
					break;
				if (!(bit & all_bits))
					continue;
				file = fget_light(i, &fput_needed);
				if (file) {
					f_op = file->f_op;
					mask = DEFAULT_POLLMASK;
        /*调用描述符的poll函数,poll函数会在文件实现的时候添加函数,该函数的本质是查看文件的状态*/
					if (f_op && f_op->poll) {
						wait_key_set(wait, in, out, bit);
						mask = (*f_op->poll)(file, wait);
					}
					fput_light(file, fput_needed);
					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;
					}
				}
			}
			if (res_in)
				*rinp = res_in;
			if (res_out)
				*routp = res_out;
			if (res_ex)
				*rexp = res_ex;
			cond_resched();
		}
		wait->_qproc = NULL;
        /*
          条件1:retval不为0时,表示检测的描述符的状态发生了变化时,跳出死循环。
          条件2:time_out为0,表示超时等待的时间结束,此时跳出循环。
          条件3:有信号唤醒当前进程
        */
		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;
		}

        /*置当前进程为可中断等待状态,等待被唤醒,执行一次poll_schedule_timeout的时间为slack,当到达超时时间to以后,poll_schedule_timeout返回0,跳出循环。
        哪个位置会唤醒进程呢?*/
		if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
					   to, slack))
			timed_out = 1;
	}

	poll_freewait(&table);

	return retval;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值