[置顶] linux select与poll实现机制与实例分析

    我们直到上层对文件操作结合select与poll可以实现阻塞操作,那么究竟是如何实现的呢?

select接口:
    int select(int nfds, fd_set *readset, fd_set *writeset,
               fd_set *exceptset, struct timeval *timeout);
      
其中:

nfds     
     需要检查的文件描述符个数,数值应该比是三组fd_set中最大数
     更大,而不是实际文件描述符的总数。
readset    
     用来检查可读性的一组文件描述符。
writeset
     用来检查可写性的一组文件描述符。
exceptset
     用来检查意外状态的文件描述符。(注:错误并不是意外状态)
timeout
     NULL指针代表无限等待,否则是指向timeval结构的指针,代表最
     长等待时间。(如果其中tv_sec和tv_usec都等于0, 则文件描述符
     的状态不被影响,但函数并不挂起)

实例:
JNIEXPORT jint JNICALL nativeTtySelect(JNIEnv* env, jclass jclazz, int fd) {

int select_ret = 0;

fd_set rfds;

struct timeval tv;

FD_ZERO(&rfds);

FD_SET(fd, &rfds);

tv.tv_sec = 1;

tv.tv_usec = 0;


select_ret = select(fd + 1, &rfds, NULL, NULL, &tv);

return select_ret;

}

上面调用select后,在内核中会调用到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;

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;
}

if (end_time && !timed_out)
slack = select_estimate_accuracy(end_time);

retval = 0;
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 += __NFDBITS;
continue;
}

for (j = 0; j < __NFDBITS; ++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;
if (f_op && f_op->poll) {
wait_key_set(wait, in, out, bit);
mask = (*f_op->poll)(file, wait);//调用驱动poll接口,里面会调用poll_waite 来add 等待队列到链表中
}
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();//sleep and wait for wake up,那么究竟在哪里被wakeup的呢?下面会有分析。
}
wait->_qproc = NULL;
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;
}

if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
to, slack))
timed_out = 1;
}

poll_freewait(&table);

return retval;
}


内核中fs poll:

struct sysfs_open_dirent {
atomic_t refcnt;
atomic_t event;
wait_queue_head_t poll;
struct list_head buffers; /* goes through sysfs_buffer.list */
};

static unsigned int sysfs_poll(struct file *filp, poll_table *wait)
{
struct sysfs_buffer * buffer = filp->private_data;
struct sysfs_dirent *attr_sd = filp->f_path.dentry->d_fsdata;
struct sysfs_open_dirent *od = attr_sd->s_attr.open;

/* need parent for the kobj, grab both */
if (!sysfs_get_active(attr_sd))
goto trigger;

poll_wait(filp, &od->poll, wait);//add poll wait queue

sysfs_put_active(attr_sd);

if (buffer->event != atomic_read(&od->event))
goto trigger;

return DEFAULT_POLLMASK;

trigger:
buffer->needs_read_fill = 1;
return DEFAULT_POLLMASK|POLLERR|POLLPRI;
}

wakeup:

void sysfs_notify_dirent(struct sysfs_dirent *sd)
{
struct sysfs_open_dirent *od;
unsigned long flags;

spin_lock_irqsave(&sysfs_open_dirent_lock, flags);

od = sd->s_attr.open;
if (od) {
atomic_inc(&od->event);
wake_up_interruptible(&od->poll);//在这里唤醒的
}

spin_unlock_irqrestore(&sysfs_open_dirent_lock, flags);
}
EXPORT_SYMBOL_GPL(sysfs_notify_dirent);

/* wakeup the userspace poll */

sysfs_notify(kobj, NULL, "xxxx");


那么上面方法能否实现当前进程阻塞呢?看看下面的常用的wait_event_interruptible,也是先将等待队列加入list之后,schedule()切换cpu执行其他进程,当前

进程休眠,唤醒是另外进程来wake up的,那么实现方法一致,所以select与poll结合完全可以实现进程的阻塞。

#define __wait_event_interruptible(wq, condition, ret)            \
do {                                    \
     DEFINE_WAIT(__wait);                        \
                                     \
     for (;;) {                            \
         prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);  \   //将wq
加入等待队列list
         if (condition)                        \
             break;                        \
         if (!signal_pending(current)) {                \
             schedule();                    \ //sleep and waite to wake up
             continue;                    \
         }                            \
         ret = -ERESTARTSYS;                    \
         break;                            \
     }                                \
     finish_wait(&wq, &__wait);                    \
} while (0)




实例:串口如何实现阻塞读取的?包含底层分析。

tty poll:
static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file,
poll_table *wait)
{
unsigned int mask = 0;

//add read and write wait queue
poll_wait(file, &tty->read_wait, wait);
poll_wait(file, &tty->write_wait, wait);
if (input_available_p(tty, TIME_CHAR(tty) ? 0 : MIN_CHAR(tty)))
mask |= POLLIN | POLLRDNORM;
if (tty->packet && tty->link->ctrl_status)
mask |= POLLPRI | POLLIN | POLLRDNORM;
if (test_bit(TTY_OTHER_CLOSED, &tty->flags))
mask |= POLLHUP;
if (tty_hung_up_p(file))
mask |= POLLHUP;
if (!(mask & (POLLHUP | POLLIN | POLLRDNORM))) {
if (MIN_CHAR(tty) && !TIME_CHAR(tty))
tty->minimum_to_wake = MIN_CHAR(tty);
else
tty->minimum_to_wake = 1;
}
if (tty->ops->write && !tty_is_writelocked(tty) &&
tty_chars_in_buffer(tty) < WAKEUP_CHARS &&
tty_write_room(tty) > 0)
mask |= POLLOUT | POLLWRNORM;
return mask;
}



wakeup:
通过wake_up(&tty->read_wait),来唤醒读取进程的。
下面的函数flush_to_ldisc,是在每次有数据过来中断接收读取完buferr后会调用tty_insert_flip_string(),
之后会调度flush_to_ldisc从内核缓冲区push bufer给上层。

/**
 *    flush_to_ldisc
 *    @work: tty structure passed from work queue.
 *
 *    This routine is called out of the software interrupt to flush data
 *    from the buffer chain to the line discipline.
 *
 *    Locking: holds tty->buf.lock to guard buffer list. Drops the lock
 *    while invoking the line discipline receive_buf method. The
 *    receive_buf method is single threaded for each tty instance.
 */

static void flush_to_ldisc(struct work_struct *work)
{
    struct tty_struct *tty =
        container_of(work, struct tty_struct, buf.work);
    unsigned long     flags;
    struct tty_ldisc *disc;

    disc = tty_ldisc_ref(tty);
    if (disc == NULL)    /*  !TTY_LDISC */
        return;

    spin_lock_irqsave(&tty->buf.lock, flags);

    if (!test_and_set_bit(TTY_FLUSHING, &tty->flags)) {
        struct tty_buffer *head;
        while ((head = tty->buf.head) != NULL) {
            int count;
            char *char_buf;
            unsigned char *flag_buf;

            count = head->commit - head->read;
            if (!count) {
                if (head->next == NULL)
                    break;
                tty->buf.head = head->next;
                tty_buffer_free(tty, head);
                continue;
            }
            /* Ldisc or user is trying to flush the buffers
               we are feeding to the ldisc, stop feeding the
               line discipline as we want to empty the queue */
            if (test_bit(TTY_FLUSHPENDING, &tty->flags))
                break;
            if (!tty->receive_room)
                break;
            if (count > tty->receive_room)
                count = tty->receive_room;
            char_buf = head->char_buf_ptr + head->read;
            flag_buf = head->flag_buf_ptr + head->read;
            head->read += count;
            spin_unlock_irqrestore(&tty->buf.lock, flags);
            disc->ops->receive_buf(tty, char_buf,
                            flag_buf, count);
            spin_lock_irqsave(&tty->buf.lock, flags);
        }
        clear_bit(TTY_FLUSHING, &tty->flags);
    }

    /* We may have a deferred request to flush the input buffer,
       if so pull the chain under the lock and empty the queue */
    if (test_bit(TTY_FLUSHPENDING, &tty->flags)) {
        __tty_buffer_flush(tty);
        clear_bit(TTY_FLUSHPENDING, &tty->flags);
        wake_up(&tty->read_wait);
    }
    spin_unlock_irqrestore(&tty->buf.lock, flags);

    tty_ldisc_deref(disc);
}



你可能感兴趣的:(select,poll)