百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

select每个程序员都应该懂得技术 select语句用来干什么

liebian365 2024-10-30 04:47 5 浏览 0 评论

来源本人公众号【技术原理君】作者:源理君

熟悉Linux环境编程的程序员们应该对select系统调用了如指掌吧,因为它的功能比较强大,可以用来定时,监听等等。可你们是否想过它的实现原理?这篇文章就来聊聊它的实现原理。

首先按照惯例,简单的讲述下select使用方法。

select是用来监听多个文件句柄的状态变化。程序会阻塞在select等待,直到监听的句柄有一个或者多个状态发生变化。函数的原型如下:


int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

函数声明中的参数就不一一解释了,默认你们都是烂熟于心了。典型的用法如下(网络接收数据,并写入文件):


main()
{
 int sock; int fd;
 fd_set fds;
 struct timeval timeout={0,3}; //select等待3微秒,3微秒轮询,要非阻塞就置0
 char buffer[256]={0}; //256字节的接收缓冲区
 /* 假定已经建立UDP连接,具体过程不写,简单,当然TCP也同理,主机ip和port都已经给定,要写的文件已经打开
 sock=socket(...);
 bind(...);
 fd=open(...); */
 while⑴
 {
 FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化
 FD_SET(sock,&fds); //添加描述符
 FD_SET(fd,&fds); //同上
 timeout.tv_sec=3;
 timeout.tv_usec=0;//select函数会不断修改timeout的值,所以每次循环都应该重新赋值[windows不受此影响]
 maxfdp=sock>fd?sock+1:fd+1; //描述符最大值加1
 switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用
 {
 case -1: exit(-1);break; //select错误,退出程序
 case 0:break; //再次轮询
 default:
 if(FD_ISSET(sock,&fds)) //测试sock是否可读,即是否网络上有数据
 {
 recvfrom(sock,buffer,256,.....);//接受网络数据
 if(FD_ISSET(fd,&fds)) //测试文件是否可写
 write(fd,buffer...);//写入文件
 buffer清空;
 }// end if break;
 }// end switch
 }//end while
}//end main

由于文章重点是在于原理,所以具体解释参考代码注释。下面进入原理正题。

原理

select在设备中需要驱动程序的支持,也就是说需要实现fops中的select()接口。select通过设备文件对应的poll()提供的信息判断当前是否可读可写,如果有的话返回文件描述符,否则睡眠。等待再次被唤醒。

下面就来分析下select的两个过程:

1.select的睡眠过程

我们都知道支持阻塞操作的设备驱动通常会实现一组自身的等待队列如读/写等待队列用于支持上层(用户层)所需的BLOCK或NONBLOCK操作。当应用程序通过设备驱动访问该设备时(默认为BLOCK操作),若该设备当前没有数据可读或写,则将该用户进程插入到该设备驱动对应的读/写等待队列让其睡眠一段时间,等到有数据可读/写时再将该进程唤醒。

select就是巧妙的利用等待队列机制让用户进程适当在没有资源可读/写时睡眠,有资源可读/写时唤醒。下面我们看看select睡眠的详细过程。

select会循环遍历它所监测的fd_set(一组文件描述符(fd)的集合)内的所有文件描述符对应的驱动程序的poll函数。驱动程序提供的poll函数首先会将调用select的用户进程插入到该设备驱动对应资源的等待队列(如读/写等待队列),然后返回一个bitmask告诉select当前资源哪些可用。当select循环遍历完所有fd_set内指定的文件描述符对应的poll函数后,如果没有一个资源可用(即没有一个文件可供操作),则select让该进程睡眠,一直等到有资源可用为止,进程被唤醒(或者timeout)继续往下执行。

select内部实现顺序:sys_select -> core_sys_select -> do_select,主要的工作由do_select完成,我们来分析下,下面代码:


int do_select(int n, fd_set_bits *fds, s64 *timeout)
{
 struct poll_wqueues table;
 poll_table *wait;
 int retval, i;
 rcu_read_lock();
 retval = max_select_fd(n, fds);
 rcu_read_unlock();
 if (retval 
 return retval;
 n = retval;
 poll_initwait(&table);
 wait = &table.pt;
 if (!*timeout)
 wait = NULL;
 retval = 0; //retval用于保存已经准备好的描述符数,初始为0
 for (;;) {
 unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
 long __timeout;
 set_current_state(TASK_INTERRUPTIBLE); //将当前进程状态改为TASK_INTERRUPTIBLE
 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 遍历每个描述符
 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 += __NFDBITS; // //如果这个字没有待查找的描述符, 跳过这个长字(32位)
 continue;
 }
 for (j = 0; j 遍历每个长字里的每个位
 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;
 MARK(fs_select, "%d %lld",
 i, (long long)*timeout);
 mask = DEFAULT_POLLMASK;
 if (f_op && f_op->poll)
/* 在这里循环调用所监测的fd_set内的所有文件描述符对应的驱动程序的poll函数 */
 mask = (*f_op->poll)(file, retval ? NULL : wait);
 fput_light(file, fput_needed);
 if ((mask & POLLIN_SET) && (in & bit)) {
 res_in |= bit; //如果是这个描述符可读, 将这个位置位
 retval++; //返回描述符个数加1
 }
 if ((mask & POLLOUT_SET) && (out & bit)) {
 res_out |= bit;
 retval++;
 }
 if ((mask & POLLEX_SET) && (ex & bit)) {
 res_ex |= bit;
 retval++;
 }
 }
 cond_resched();
 }
//返回结果
 if (res_in)
 *rinp = res_in;
 if (res_out)
 *routp = res_out;
 if (res_ex)
 *rexp = res_ex;
 }
 wait = NULL;
/* 到这里遍历结束。retval保存了检测到的可操作的文件描述符的个数。如果有文件可操作,则跳出for(;;)循环,直接返回。若没有文件可操作且timeout时间未到同时没有收到signal,则执行schedule_timeout睡眠。睡眠时间长短由__timeout决定,一直等到该进程被唤醒。 
那该进程是如何被唤醒的?被谁唤醒的呢?
我们看下面的select唤醒过程*/
 if (retval || !*timeout || signal_pending(current))
 break;
 if(table.error) {
 retval = table.error;
 break;
 }
 if (*timeout 
 /* Wait indefinitely */
 __timeout = MAX_SCHEDULE_TIMEOUT;
 } else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT - 1)) {
 /* Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in a loop */
 __timeout = MAX_SCHEDULE_TIMEOUT - 1;
 *timeout -= __timeout;
 } else {
 __timeout = *timeout;
 *timeout = 0;
 }
 __timeout = schedule_timeout(__timeout);
 if (*timeout >= 0)
 *timeout += __timeout;
 }
 __set_current_state(TASK_RUNNING);
 poll_freewait(&table);
 return retval;
}


2.select的唤醒过程

前面介绍了select会循环遍历它所监测的fd_set内的所有文件描述符对应的驱动程序的poll函数。驱动程序提供的poll函数首先会将调用select的用户进程插入到该设备驱动对应资源的等待队列(如读/写等待队列),然后返回一个bitmask告诉select当前资源哪些可用。

一个典型的驱动程序poll函数实现如下


//(摘自《Linux Device Drivers – ThirdEdition》Page 165)
static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
{
 struct scull_pipe *dev = filp->private_data;
 unsigned int mask = 0;
 /*
 * The buffer is circular; it is considered full
 * if "wp" is right behind "rp" and empty if the
 * two are equal.
 */
 down(&dev->sem);
 poll_wait(filp, &dev->inq, wait);
 poll_wait(filp, &dev->outq, wait);
 if (dev->rp != dev->wp)
 mask |= POLLIN | POLLRDNORM; /* readable */
 if (spacefree(dev))
 mask |= POLLOUT | POLLWRNORM; /* writable */
 up(&dev->sem);
 return mask;
}
//将用户进程插入驱动的等待队列是通过poll_wait做的。
//Poll_wait定义如下:
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
 if (p && wait_address)
 p->qproc(filp, wait_address, p);
}
//这里的p->qproc在do_select内poll_initwait(&table)被初始化为__pollwait,如下:
void poll_initwait(struct poll_wqueues *pwq)
{
 init_poll_funcptr(&pwq->pt, __pollwait);
 pwq->error = 0;
 pwq->table = NULL;
 pwq->inline_index = 0;
}
//__pollwait定义如下:
/* Add a new entry */
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
 poll_table *p)
{
 struct poll_table_entry *entry = poll_get_entry(p);
 if (!entry)
 return;
 get_file(filp);
 entry->filp = filp;
 entry->wait_address = wait_address;
 init_waitqueue_entry(&entry->wait, current);
 add_wait_queue(wait_address,&entry->wait);
}

通过init_waitqueue_entry初始化一个等待队列项,这个等待队列项关联的进程即当前调用select的进程。然后将这个等待队列项插入等待队列wait_address。Wait_address即在驱动poll函数内调用poll_wait(filp, &dev->inq, wait);时传入的该驱动的&dev->inq或者&dev->outq等待队列。

到这里我们明白了select如何将当前进程插入所有所监测的fd_set关联的驱动内的等待队列,那进程究竟是何时让出CPU进入睡眠状态的呢?

进入睡眠状态是在do_select内调用schedule_timeout(__timeout)实现的。当select遍历完fd_set内的所有设备文件,发现没有文件可操作时(即retval=0),则调用schedule_timeout(__timeout)进入睡眠状态。

唤醒该进程的过程通常是在所监测文件的设备驱动内实现的,驱动程序维护了针对自身资源读写的等待队列。当设备驱动发现自身资源变为可读写并且有进程睡眠在该资源的等待队列上时,就会唤醒这个资源等待队列上的进程。

觉得不错,记得转发点赞“在看”!

相关推荐

快递查询教程,批量查询物流,一键管理快递

作为商家,每天需要查询许许多多的快递单号,面对不同的快递公司,有没有简单一点的物流查询方法呢?小编的回答当然是有的,下面随小编一起来试试这个新技巧。需要哪些工具?安装一个快递批量查询高手快递单号怎么快...

一键自动查询所有快递的物流信息 支持圆通、韵达等多家快递

对于各位商家来说拥有一个好的快递软件,能够有效的提高自己的工作效率,在管理快递单号的时候都需要对单号进行表格整理,那怎么样能够快速的查询所有单号信息,并自动生成表格呢?1、其实方法很简单,我们不需要一...

快递查询单号查询,怎么查物流到哪了

输入单号怎么查快递到哪里去了呢?今天小编给大家分享一个新的技巧,它支持多家快递,一次能查询多个单号物流,还可对查询到的物流进行分析、筛选以及导出,下面一起来试试。需要哪些工具?安装一个快递批量查询高手...

3分钟查询物流,教你一键批量查询全部物流信息

很多朋友在问,如何在短时间内把单号的物流信息查询出来,查询完成后筛选已签收件、筛选未签收件,今天小编就分享一款物流查询神器,感兴趣的朋友接着往下看。第一步,运行【快递批量查询高手】在主界面中点击【添...

快递单号查询,一次性查询全部物流信息

现在各种快递的查询方式,各有各的好,各有各的劣,总的来说,还是有比较方便的。今天小编就给大家分享一个新的技巧,支持多家快递,一次能查询多个单号的物流,还能对查询到的物流进行分析、筛选以及导出,下面一起...

快递查询工具,批量查询多个快递快递单号的物流状态、签收时间

最近有朋友在问,怎么快速查询单号的物流信息呢?除了官网,还有没有更简单的方法呢?小编的回答当然是有的,下面一起来看看。需要哪些工具?安装一个快递批量查询高手多个京东的快递单号怎么快速查询?进入快递批量...

快递查询软件,自动识别查询快递单号查询方法

当你拥有多个快递单号的时候,该如何快速查询物流信息?比如单号没有快递公司时,又该如何自动识别再去查询呢?不知道如何操作的宝贝们,下面随小编一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号若干...

教你怎样查询快递查询单号并保存物流信息

商家发货,快递揽收后,一般会直接手动复制到官网上一个个查询物流,那么久而久之,就会觉得查询变得特别繁琐,今天小编给大家分享一个新的技巧,下面一起来试试。教程之前,我们来预览一下用快递批量查询高手...

简单几步骤查询所有快递物流信息

在高峰期订单量大的时候,可能需要一双手当十双手去查询快递物流,但是由于逐一去查询,效率极低,追踪困难。那么今天小编给大家分享一个新的技巧,一次能查询多个快递单号的物流,下面一起来学习一下,希望能给大家...

物流单号查询,如何查询快递信息,按最后更新时间搜索需要的单号

最近有很多朋友在问,如何通过快递单号查询物流信息,并按最后更新时间搜索出需要的单号呢?下面随小编一起来试试吧。需要哪些工具?安装一个快递批量查询高手快递单号若干怎么快速查询?运行【快递批量查询高手】...

连续保存新单号功能解析,导入单号查询并自动识别批量查快递信息

快递查询已经成为我们日常生活中不可或缺的一部分。然而,面对海量的快递单号,如何高效、准确地查询每一个快递的物流信息,成为了许多人头疼的问题。幸运的是,随着科技的进步,一款名为“快递批量查询高手”的软件...

快递查询教程,快递单号查询,筛选更新量为1的单号

最近有很多朋友在问,怎么快速查询快递单号的物流,并筛选出更新量为1的单号呢?今天小编给大家分享一个新方法,一起来试试吧。需要哪些工具?安装一个快递批量查询高手多个快递单号怎么快速查询?运行【快递批量查...

掌握批量查询快递动态的技巧,一键查找无信息记录的两种方法解析

在快节奏的商业环境中,高效的物流查询是确保业务顺畅运行的关键。作为快递查询达人,我深知时间的宝贵,因此,今天我将向大家介绍一款强大的工具——快递批量查询高手软件。这款软件能够帮助你批量查询快递动态,一...

从复杂到简单的单号查询,一键清除单号中的符号并批量查快递信息

在繁忙的商务与日常生活中,快递查询已成为不可或缺的一环。然而,面对海量的单号,逐一查询不仅耗时费力,还容易出错。现在,有了快递批量查询高手软件,一切变得简单明了。只需一键,即可搞定单号查询,一键处理单...

物流单号查询,在哪里查询快递

如果在快递单号多的情况,你还在一个个复制粘贴到官网上手动查询,是一件非常麻烦的事情。于是乎今天小编给大家分享一个新的技巧,下面一起来试试。需要哪些工具?安装一个快递批量查询高手快递单号怎么快速查询?...

取消回复欢迎 发表评论: