您的位置:首页 > 其它

Select函数实现

2016-08-10 00:52 23 查看
int select(int nfds,

fd_set *restrict readfds,

fd_set *restrict writefds,

fd_set *restrict errorfds,

  struct timeval *restrict timeout);
[/code]
SYSCALL_DEFINE5(select, int, n,

  fd_set __user *, inp, 

  fd_set __user *, outp, 

  fd_set __user *, exp, 

  struct timeval __user *, tvp)

{

 ret = core_sys_select(n, inp, outp, exp, to);
 ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);

 return ret;

}

[/code]
core_sys_select
主要工作:

初始化读写还有异常的bitmap
调用
do_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;

 // …

 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)))//
*get_fd_set仅仅调用copy_from_user从用户空间拷贝了fd_set*/
  goto out;

 zero_fd_set(n, fds.res_in);

 zero_fd_set(n, fds.res_out);

 zero_fd_set(n, fds.res_ex);


 //发现do_select函数

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

 /*把结果集,拷贝回用户空间*/
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;  

}

[/code]
int do_select(int n, fd_set_bits *fds, struct timespec *end_time)

{

  

struct poll_wqueues table;

 poll_table *wait;

 poll_initwait(&table);//
这个函数实现很关键,其内部的
init_poll_funcptr
初始化回调函数为
__pollwait
, 后面轮询会回调这个函数,然后通过这个函数把进程添加到对应的监听文件等待队列,当有事件到来时,就会唤醒这个进程。
 for (;;) {

  //一次大循环

  for (i = 0; i < n; ++rinp, ++routp, ++rexp) {

// …

struct fd f;

f = fdget(i);

if (f.file) {

 const struct file_operations *f_op;  //每个设备拥有一个struct file_operations结构体

 f_op = f.file->f_op; 

 mask = DEFAULT_POLLMASK;

 if (f_op->poll) { //轮询函数不为空,每当设备模块加载就自动会加载设备轮询函数,等于将轮回函数统一付给poll这个指针,以便调用

  wait_key_set(wait, in, out,
bit, busy_flag);//检查集合
  // 对每个fd进行I/O事件检测 (*f_op->poll)返回当前设备fd的状态(可读可写)

  mask = (*f_op->poll)(f.file, wait);//将会调用poll_wait函数,检测文件设备的状态,并且将当前进程加入到设备等待队列中。并且返回掩码

 }

 fdput(f);

}

  }

  // 退出循环体 

  if (retval || timed_out || signal_pending(current))

break;

  // 轮询一遍没有发现就绪。那就休眠

  if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,

 to, slack))

timed_out = 1;

 }

}

[/code]

这个函数实现很关键,这里 init_poll_funcptr
初始化回调函数为
__pollwait
, 后面轮询会回调这个函数,然后通过这个函数把进程添加到对应的监听文件等待队列,当有事件到来时,就会唤醒这个进程。

poll_initwait(&table);

void poll_initwait(struct poll_wqueues *pwq){
//这里p->_qproc实际就是__pollwait函数,因为p->qproc在init_poll_funcptr中被赋值为__pollwait函数指针
init_poll_funcptr(&pwq->pt, __pollwait); //初始化函数指针,设置为__pollwait

 pwq->error = 0;

 pwq->table = NULL;

 pwq->inline_index = 0;}

static inline void
init_poll_funcptr(poll_table *pt, poll_queue_proc qproc){
 pt->qproc = qproc; 

}

[/code]
以下根据scull设备分析轮询函数每个驱动设备对应一个fd每个fd包含struct file_operationsstruct file_operations 每个设备都对应一个这样的结构体
struct file {

 struct path  f_path;//路径

 struct inode  *f_inode;//inode

 const struct file_operations *f_op; //包含各种用于操作设备的函数指针

} __attribute__((aligned(4))); /* lest something weird decides that 2
[/code]
struct file_operations {

 struct module *owner;

 loff_t (*llseek) (struct file *, loff_t, int);

 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

 // select()轮询设备fd的操作函数,对应一个file 跟poll_table_struct *

 unsigned int (*poll) (struct file *, struct poll_table_struct *); //驱动加载。一般就挂到这个地方轮询函数

};

[/code]具体分析scull设备
每个设备都有一个这样的结构体。而这样的结构体基本都有一个等待队列
struct scull_pipe {

  wait_queue_head_t inq, outq; //可读可写队列

};

[/code]这个设备的轮询操作函数是scull_p_poll.驱动模块加载,这个函数就被挂到(*poll)函数指针sk;
返回当前设备的I/O状态,并且调用了poll_wait函数,将当前进程加入到等待队列,把wait_queue_head_t队列当做参数传入
static unsigned int scull_p_poll(struct file *filp, poll_table *wait)

{

 struct scull_pipe *dev = filp->private_data;

 unsigned int mask = 0;

 mutex_lock(&dev->mutex);

 poll_wait(filp, &dev->inq,  wait);//pollwait函数包含了__pollwait.这函数就是把当前进程添加到设备队列中

 poll_wait(filp, &dev->outq, wait);//等待

 if (dev->rp != dev->wp)

  mask |= POLLIN | POLLRDNORM; //可读

 if (spacefree(dev))

  mask |= POLLOUT | POLLWRNORM;//可写

 mutex_unlock(&dev->mutex);

 return mask;//返回该设备的掩码,是否就绪可读可写

}

[/code]注意poll_wait函数,把设备自己的等待队列给传进去了,还传了一个poll_table看看poll_wait函数的最主要功能就是调用__pollwait将当前进程添加到设备等待队列
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)

{

 if (p && p->_qproc && wait_address)

  p->_qproc(filp, wait_address, p);//这里p->_qproc实际就是__pollwait函数,因为p->qproc在do_select中被赋值为__pollwait函数指针

}

[/code]poll_table结构体包含 poll_queue_proc _qproc,unsigned long _key; 2个变量,其中第一变量是一个函数指针
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

我们找下poll_table的初始化在哪
poll_table里的函数指针,是在
do_select()
初始化的。
int do_select(int n, fd_set_bits *fds, struct timespec *end_time)

{

struct poll_wqueues table;

 poll_table *wait;

 poll_initwait(&table);//初始化

}


void poll_initwait(struct poll_wqueues *pwq)

{

 // 初始化poll_table里的函数指针

 init_poll_funcptr(&pwq->pt, __pollwait);

}

EXPORT_SYMBOL(poll_initwait);


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

{

 pt->_qproc = qproc;//将poll_table的函数指针设置为__pollwait完成初始化工作

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

}

[/code]
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,

 poll_table *p)

{

 // 把当前进程装到设备的等待队列

 add_wait_queue(wait_address, &entry->wait);

}

[/code]如果当设备有数据可写的时候。将调用此函数那将此等待可写的队列中的进程唤醒
static ssize_t scull_p_write(struct file *filp, const char __user *buf, size_t count,

 loff_t *f_pos)

{

 wake_up_interruptible(&dev->inq);  //唤醒当前进程

}

[/code]

select慢的原因
从上面看,在第一次所有监听都没有事件时,调用 select 都需要把进程挂到所有监听的文件描述符一次。
有事件到来时,不知道是哪些文件描述符有数据可以读写,需要把所有的文件描述符都轮询一遍才能知道。
通知事件到来给用户进程,需要把整个 bitmap 拷到用户空间,让用户空间去查询。
select返回时,会将该进程从全部监听的fd的等待队列里移除掉,这样就需要select每次都要重新传入全部监听的fd,然后重现将本进程挂载到全部的监测fd的等待队列

来自为知笔记(Wiz)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: