Select函数实现原理分析

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

 

下面我们分两个过程来分析 select

 

1. select 的睡眠过程

 

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

 

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

 

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

 

下面分析一下代码是如何实现的。

select 的调用 path 如下: sys_select -> core_sys_select -> do_select

其中最重要的函数是 do_select, 最主要的工作是在这里 , 前面两个函数主要做一些准备工作。 do_select 定义如下:

 

 

2.  select 的唤醒过程

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

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

( 摘 自《 Linux Device Drivers – ThirdEdition Page 165)

将用户进程插入驱动的等待队列是通过poll_wait做的。Poll_wait定义如下:

这里的p->qproc在do_select内poll_initwait(&table)被初始化为__pollwait,如下:

__pollwait定义如下:

 

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

 

: 关于等待队列的工作原理可以参考下面这篇文档 :

http://blog.chinaunix.net/u2/60011/showart_1334657.html

 

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

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

 

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

举个例子,比如内核的 8250 uart driver:

Uart 是使用的 Tty 层维护的两个等待队列 , 分别对应于读和写 : (uart tty 设备的一种 )

struct tty_struct {

         ……

         wait_queue_head_t write_wait;

         wait_queue_head_t read_wait;

         ……

}

uart 设备接收到数据,会调用 tty_flip_buffer_push(tty); 将收到的数据 push tty 层的 buffer

然后查看是否有进程睡眠的读等待队列上,如果有则唤醒该等 待会列。

过程如下:

serial8250_interrupt -> serial8250_handle_port -> receive_chars -> tty_flip_buffer_push ->

flush_to_ldisc -> disc->receive_buf

disc->receive_buf 函数内:

if (waitqueue_active(&tty->read_wait)) // 若有进程阻塞在 read_wait 上则唤醒

wake_up_interruptible(&tty->read_wait);

 

到这里明白了 select 进程被唤醒的过程。由于该进程是 阻塞在所有监测的文件对应的设备等待队列上的,因此在 timeout 时间内,只要任意个设备变为可操作,都会立即唤醒该进程,从而继续往下执行。这就实现了 select 的当有一个文件描述符可操作时 就立即唤醒执行的基本原理。

 

Referece:

1.       Linux Device Drivers – ThirdEdition

2.       内核等待队列机制原理分析

http://blog.chinaunix.net/u2/60011/showart_1334657.html

3.       Kernel code : Linux 2.6.18_pro500 - Montavista



  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值