蜗窝科技

2.4内核里semaphore源码的一个疑问

蜗窝讨论区存档 · Linux kernel技术问答 · 楼主 karma303 · 2016-10-26 · 4 帖

本文是原「蜗窝讨论区」的历史存档(2016-10-26),来自版块「Linux kernel技术问答」,共 4 帖。讨论区已停止服务,此处仅供查阅。

karma303 · 2016-10-26 18:32

博主你好, 请教一个问题. __down(里面有一段代码, 我觉得不那么保险.我先把__down的源码贴出来: ============================================= void __down(struct semaphore * sem) { struct task_struct *tsk = current; DECLARE_WAITQUEUE(wait, tsk); //定义一个"队列项", 等待者是当前进程 tsk->state = TASK_UNINTERRUPTIBLE; add_wait_queue_exclusive(&sem->wait, &wait); //把当前进程添加到该信号量的wait queue里.

spin_lock_irq(&semaphore_lock);                           //抓取"大锁"
sem->sleepers++;
for (;;) {
    int sleepers = sem->sleepers;
/*
 * Add "everybody else" into it. They aren't
 * playing, because we own the spinlock.
 */
if (!atomic_add_negative(sleepers - 1, &sem->count)) {    //临睡前最后一次尝试
    sem->sleepers = 0;
    break;
}
sem->sleepers = 1;    /* us - see -1 above */
spin_unlock_irq(&semaphore_lock);

schedule(;                             //睡眠
tsk->state = TASK_UNINTERRUPTIBLE;
spin_lock_irq(&semaphore_lock);

} spin_unlock_irq(&semaphore_lock); remove_wait_queue(&sem->wait, &wait); //取得信号量后, 退出该信号量的等待队列 tsk->state = TASK_RUNNING; wake_up(&sem->wait);

}

我也是这两天才开始读linux的源码. 我先说说我读到的一点经验. 2.4内核里的semaphore结构体里面没有lock字段, 整个semaphor.c里是共用一个文件域的大锁, 就是semaphore.c里定义的semaphore_lock. 每当要操作semaphore结构体之前, 就先抓取这个"全局锁". 但是up(操作的全程都没有理睬这把锁, 我很好奇, 会不会出现这样一种bug呢: 为了方便分析, 假设除了当前进程, 没有别的进程在竞争这个信号量. 刚才说到up(的全程都没理会"大锁", 所以在整个__down(的过程中, 别的cpu上, 随时可能会有一个up(平行的运行. up(最终调用的是wake_up_process(. ===================================== inline void wake_up_process(struct task_struct * p) { unsigned long flags;

/*
 * We want the common case fall through straight, thus the goto.
 */
spin_lock_irqsave(&runqueue_lock, flags);
p->state = TASK_RUNNING;    if (task_on_runqueue(p))        goto out;
add_to_runqueue(p);
reschedule_idle(p);

out: spin_unlock_irqrestore(&runqueue_lock, flags); } =============================================== 那么在__down(里面的这段区间, add_wait_queue_exclusive(&sem->wait, &wait); ... ... schedule(;

也就是, current进入信号量排队之后, 调用schedule(之前, 我们随时可能遭受"wake_up_process(". 如果在if (!atomic_add_negative(sleepers - 1, &sem->count)) 这句之前被wake_up_process(, 倒也无所谓, 因为反正我们能通过这个if拿到信号量, (既然有人up, 肯定就是有门票了). 但是如果这个if失败, 我们就要睡眠了. 在我们调用schdule入睡之前的这个空隙里, 即执行这几行代码的时候: sem->sleepers = 1; / us - see -1 above / spin_unlock_irq(&semaphore_lock); schedule(;
我们遭到了wake_up_process. 会发生什么呢? 其实看wake_up_process(的源码,它也做不了什么( 因为我们已经在运行队列里了 , 但它把我们的状态设置成TASK_RUNNING了. 就是说, 接下来, 我们是以"TASK_RUNNING"的身份调用schedule的. 更坏的是, 我们等于说是错过了这次up(, 再没有人来唤醒我们了.

我上面说的很麻烦, 简单的说, 就是, up(为什么不理睬semaphore_lock这个锁? 明明会出bug.

我想是我哪里错了, linux肯定不会有这种bug. 恳请指教.


* 为了方便一些, 我把相关的源码都贴上:


[== C++ ==]
== down(和up(的入口函数来自 include/asm-i386/semaphore.h
static inline void down(struct semaphore * sem)
{
#if WAITQUEUE_DEBUG
    CHECK_MAGIC(sem->__magic);
#endif
__asm__ __volatile__(
    "# atomic down operation\n\t"
    LOCK "decl %0\n\t"     /* --sem->count */
    "js 2f\n"
    "1:\n"
    ".section .text.lock,\"ax\"\n"
    "2:\tcall __down_failed\n\t"
    "jmp 1b\n"
    ".previous"
    :"=m" (sem->count)
    :"c" (sem)
    :"memory");

}

static inline void up(struct semaphore * sem) { #if WAITQUEUE_DEBUG CHECK_MAGIC(sem->__magic); #endif asm volatile( "# atomic up operation\n\t" LOCK "incl %0\n\t" /* ++sem->count */ "jle 2f\n" "1:\n" ".section .text.lock,"ax"\n" "2:\tcall __up_wakeup\n\t" "jmp 1b\n" ".previous" :"=m" (sem->count) :"c" (sem) :"memory"); }

===fall through失败后的操作, 都在arch/i386/kernel/semaphore.c asm( ".align 4\n" ".globl __down_failed\n" "__down_failed:\n\t" "pushl %eax\n\t" "pushl %edx\n\t" "pushl %ecx\n\t" "call __down\n\t" "popl %ecx\n\t" "popl %edx\n\t" "popl %eax\n\t" "ret" ;

void __down(struct semaphore * sem) { struct task_struct *tsk = current; DECLARE_WAITQUEUE(wait, tsk); tsk->state = TASK_UNINTERRUPTIBLE; add_wait_queue_exclusive(&sem->wait, &wait);

spin_lock_irq(&semaphore_lock);
sem->sleepers++;
for (;;) {
    int sleepers = sem->sleepers;

    /*
     * Add "everybody else" into it. They aren't
     * playing, because we own the spinlock.
     */
    if (!atomic_add_negative(sleepers - 1, &sem->count)) {
        sem->sleepers = 0;
        break;
    }
    sem->sleepers = 1;    /* us - see -1 above */
    spin_unlock_irq(&semaphore_lock);

    schedule(;
    tsk->state = TASK_UNINTERRUPTIBLE;
    spin_lock_irq(&semaphore_lock);
}
spin_unlock_irq(&semaphore_lock);
remove_wait_queue(&sem->wait, &wait);
tsk->state = TASK_RUNNING;
wake_up(&sem->wait);

}

void __up(struct semaphore *sem) { wake_up(&sem->wait); }

asm( ".align 4\n" ".globl __up_wakeup\n" "__up_wakeup:\n\t" "pushl %eax\n\t" "pushl %edx\n\t" "pushl %ecx\n\t" "call __up\n\t" "popl %ecx\n\t" "popl %edx\n\t" "popl %eax\n\t" "ret" ;

* <情景分析>这部分的内容在403页前后.

弯曲的旅行 · 2016-10-26 23:07

__up和__down确实不是原子操作,因此会出现同步问题。但是up和down(上面的汇编代码),却是原子操作,因此这种情况不会出现。

karma303 · 2016-10-27 13:51

弯曲的旅行 写道: __up和__down确实不是原子操作,因此会出现同步问题。但是up和down(上面的汇编代码),却是原子操作,因此这种情况不会出现。

"__up和__down确实不是原子操作,因此会出现同步问题。" 请问这句话是什么意思? ... 我说的情景, 就是"当前cpu在执行__down(, 同时, 另一个cpu进入__up(, 然后带来的race condition."

弯曲的旅行 · 2016-10-27 14:34

karma303 写道:

弯曲的旅行 写道: __up和__down确实不是原子操作,因此会出现同步问题。但是up和down(上面的汇编代码),却是原子操作,因此这种情况不会出现。

"__up和__down确实不是原子操作,因此会出现同步问题。" 请问这句话是什么意思? ... 我说的情景, 就是"当前cpu在执行__down(, 同时, 另一个cpu进入__up(, 然后带来的race condition."

我没有仔细去看你的代码,不过从kernel命名的常识来说,__开头的函数,都是指内部使用的、没有被保护的函数,只有在确保不会出现同步问题的时候才能调用。然后基于这些函数,封装出来正常的函数(up、down等),这些正常函数是被保护的,可以放心调用。 你检查一下你的代码,看看__up/__down都是谁调用的,调用的时候是否会出现同步问题。