注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

Tsecer的回音岛

Tsecer的博客

 
 
 

日志

 
 

多进程/线程select同一文件问题  

2012-03-01 21:45:21|  分类: Linux内核 |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |
一、多进程select
这个是一个不太常见的场景,但是作为探讨性话题,大家可以在这里尽情YY一下,就像YY我们达到共产主义一样,想想会是什么情景,当然,还是这里讨论的问题更靠谱一些。
根据select的语义,就是进程来同时等待若干个文件可读/可写/错误状态,直到指定时间结束,这个我想大家都是明白的。现在的场景是对于一个文件,例如一个socket,控制台等设备的等待同时有多个,例如A进程,B进程两个都在select这个文件,那么当这个文件准备就绪的时候,两个线程是否都会被唤醒还是只有一个唤醒?唤醒之后它们谁会读到这个数据?
二、select等待
do_select--->>poll_initwait--->>>__pollwait--->>init_waitqueue_entry

static inline void init_waitqueue_entry(wait_queue_t *q, struct task_struct *p)
{
    q->flags = 0;
    q->private = p;
    q->func = default_wake_function;
}
这里是对线程的等待实体的初始化,之后唤醒的时候将会使用到里面的数据结构,暂且不表。
以大家熟悉的命名管道(也是比较容易测试的一种文件)为例,当它在线程在select可读的时候,如果有人对管道进行了写入操作,那么执行的代码为pipe_write--->>>wake_up_interruptible(&pipe->wait)---->>>__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)--->>>__wake_up_common

/*
 * The core wakeup function.  Non-exclusive wakeups (nr_exclusive == 0) just
 * wake everything up.  If it's an exclusive wakeup (nr_exclusive == small +ve
 * number) then we wake all the non-exclusive tasks and one exclusive task.
 *
 * There are circumstances in which we can try to wake a task which has already
 * started to run but is not in state TASK_RUNNING.  try_to_wake_up() returns
 * zero in this (rare) case, and we handle it by continuing to scan the queue.
 */
static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
                 int nr_exclusive, int sync, void *key)
{
    struct list_head *tmp, *next;

    list_for_each_safe(tmp, next, &q->task_list) {
        wait_queue_t *curr = list_entry(tmp, wait_queue_t, task_list);
        unsigned flags = curr->flags;

        if (curr->func(curr, mode, sync, key) &&
                (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
            break;
    }
}
其中对于等待队列的遍历是非常简明扼要的,如果说一个等待者比较喜欢吃独食,那么他在等待的标志位设置WQ_FLAG_EXCLUSIVE标志,表示只唤醒我一个,当然还要和传入的互斥唤醒个数也有关系,即使唤醒标志中设置了自己为互斥,如果参数中nc_exclusive要求多个,那么这个标志只能委屈一下了(即强*民意)。对于我们这里分析的场景,select的唤醒是非常容易相处的(easy-going),所以这个等待队列上所有的等待者都将会被唤醒,皆大欢喜。
三、所有select都会被唤醒返回吗
从上面看,是这样的,因为等待队列上所有的等待者都会被唤醒。本着蛋疼的精神,我试了一下,现象并非如此(准确的说,并不总是如此),也就是有时只有一个进程的select系统调用返回,另一个线程的select依然在阻塞,压根都没有返回到用户态,当然我的测试程序是在select返回之后从这个文件中读取数据。所以再审视一下do_select函数的实现
    for (;;) {
        unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
        long __timeout;

        set_current_state(TASK_INTERRUPTIBLE);
    for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
    for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
       if (file) {
                    f_op = file->f_op;
                    mask = DEFAULT_POLLMASK;
                    if (f_op && f_op->poll)
                        mask = (*f_op->poll)(file, retval ? NULL : wait);
……
        __timeout = schedule_timeout(__timeout);
        if (*timeout >= 0)
            *timeout += __timeout;
    }
这里要说的是,并不是说waiter被从schedule_timeout唤醒之后就功德圆满了,因为这里重重循环会迫使它再掉头去poll各个指定等待的文件里去咨询调查(poll)一下。现在精彩的部分来了:
唤醒操作一次会唤醒所有的等待者,它们将会同时到达可运行状态,但是对于单核系统来说,它们是顺序执行的。假设说A比较幸运,最早运行,它成功从select返回到用户态,然后A线程毫不客气,一下子读光了文件中的内容,然后满意而去,而B等了一段时间才会获得执行权,当它执行自己迟来的poll检测时,会发现那个文件并没有准备好,因为数据已经被A抢先消耗掉了,所以B空欢喜了一场,继续执行上面的schedule_time,继续等待,在用户态看来它没有从内核返回(有兴趣的同学可以看一下,它的调度次数应该会加一)。
四、再次等待时等待实体wait_queue_t何时回收
在进行唤醒的时候,这个实体并不会从从等待队列上删除,所以即使被唤醒了,它依然在目标文件的等待队列中,所以没有关系。在循环体中
mask = (*f_op->poll)(file, retval ? NULL : wait);
也可以看到,如果被唤醒,那么retval不等于零,所以之后poll的时候传入的实体为空,即复用上次对象,这里只是进行一次确认性检查。
五、推广
这里还可以推广到多个进程select,多个进程read同一个文件的情况。
六、todo
写个测试程序展示一下我想描述的内容。
  评论这张
 
阅读(1095)| 评论(0)
推荐 转载

历史上的今天

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017