Chinaunix首页 | 论坛 | 博客
  • 博客访问: 257161
  • 博文数量: 65
  • 博客积分: 2599
  • 博客等级: 少校
  • 技术积分: 710
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-04 10:49
文章分类

全部博文(65)

文章存档

2015年(4)

2013年(2)

2012年(4)

2011年(51)

2010年(4)

分类: LINUX

2011-04-29 13:55:22

                       内核同步原语

一. 情景
首先,初始化一个信号量,调用 sema_init(sem, 1)。 这相当于初始化一个
互斥量。之后有三个进程申请这个信号量保护的互斥资源,p[0..2]。而且,p0
首先申请到,所以p1和p2会休眠,之后,p0释放这个信号量。

p[0..2]的申请过程,使用a[0..2]来分别代表。

/**
 * 内核版本 2.6.10
 * 用到的函数和结构体的定义都是在
 * include/asm-i386/semaphore.h
 **/

/**
 * 定义
 **/
struct semaphore {
    atomic_t count;
    int sleepers;
    wait_queue_head_t wait;
};

static inline void sema_init (struct semaphore *sem, int val)
{
/*
 *    *sem = (struct semaphore)__SEMAPHORE_INITIALIZER((*sem),val);
 *
 * i'd rather use the more flexible initialization above, but sadly
 * GCC 2.7.2.3 emits a bogus warning. EGCS doesn't. Oh well.
 */
    atomic_set(&sem->count, val);
    sem->sleepers = 0;
    init_waitqueue_head(&sem->wait);
}
这里,我们可以看出,初始化过程中,是将 count 设置成了1,sleepers设置成0
并且初始化了等待队列的头wait。

现在,p0申请信号量sem,调用down()函数。

static inline void down(struct semaphore * sem)
{
    might_sleep();
    __asm__ __volatile__(
        "# atomic down operation\n\t"
        LOCK "decl %0\n\t"     /* --sem->count */        /*@a1: 锁住总线,原子的将count减 1,这时候,count是0!*/
        "js 2f\n"                                        /*@a1: 只有count减为负数的时候,才会跳转。所以直接执行LOCK_SECTION_START("")
                                                                ,申请成功。
                                                          @a2: 由于count在为0的时候减1变成了-1,出现了负数,执行跳转!
                                                          @a3: 在p3到达这里的时候,sem->count的值变成-2,调用__down_failed
                                                        */
        "1:\n"
        LOCK_SECTION_START("")
        "2:\tlea %0,%%eax\n\t"                            /*@a2: 将count的值,存入eax,调用__down_failed,p2需要休眠*/    
        "call __down_failed\n\t"
        "jmp 1b\n"
        LOCK_SECTION_END
        :"=m" (sem->count)
        :
        :"memory","ax");
}

由于以下代码 arch/i386/kernel/semaphore.c
asm(
".section .sched.text\n"
".align 4\n"
".globl __down_failed\n"
"__down_failed:\n\t"
#if defined(CONFIG_FRAME_POINTER)
    "pushl %ebp\n\t"
    "movl  %esp,%ebp\n\t"
#endif
    "pushl %edx\n\t"
    "pushl %ecx\n\t"
    "call __down\n\t"
    "popl %ecx\n\t"
    "popl %edx\n\t"
#if defined(CONFIG_FRAME_POINTER)
    "movl %ebp,%esp\n\t"
    "popl %ebp\n\t"
#endif
    "ret"
);
这就将down_failed函数,在i386环境下,定义为了以上的这段代码,其实调用的是__down函数。


fastcall void __sched __down(struct semaphore * sem)
{
    struct task_struct *tsk = current;                                /*创建task struct,创建和p2相关的wait_queue*/
    DECLARE_WAITQUEUE(wait, tsk);
    unsigned long flags;

    tsk->state = TASK_UNINTERRUPTIBLE;                                /*将当前进程的状态改为TASK_UNINTERRUPTIBLE*/
    spin_lock_irqsave(&sem->wait.lock, flags);                        /*使用信号量等待队列的自旋锁构建临界区*/
    add_wait_queue_exclusive_locked(&sem->wait, &wait);                /*将当前进程加入到信号量的等待队列*/

    sem->sleepers++;                                                /*a2:这时候,将sem的sleeper加一,变成1*/
    for (;;) {
        int sleepers = sem->sleepers;                                /*a3:当p3到达这里的时候,sleepers临时变成2,count变成-2*/

        /*
         * Add "everybody else" into it. They aren't
         * playing, because we own the spinlock in
         * the wait_queue_head.
         */
        if (!atomic_add_negative(sleepers - 1, &sem->count)) {        /*a2:原子的将sleeper-1加到count上,这时候,count是-1,sleeper-1是0,
                                                                         所以,结果是-1,返回true,整个表达式返回假
                                                                      a3:由于count是-2,sleeper减一是1,所以结果还是负数,整个表达式
                                                                         返回假。

                                                                      在这里,我们可以总结出,这个算法的核心思想:
                                                                      开始:
                                                                            count是-1,sleepers是1,这样结果是-1,
                                                                      迭代:
                                                                            count是-2,这时候sleepers是2,这样结果还是-1,
                                                                            count变成-1,sleepers变成1。
                                                                            所以,无论执行多少次,count都是临时会变成-2,然后
                                                                            sleepers也是临时变成2,之后原子的相加,结果是count
                                                                            变回-1,sleepers变回1.
                                                                    */
            sem->sleepers = 0;
            break;
        }
        sem->sleepers = 1;    /* us - see -1 above */                    /*当count为-1的时候,就会执行到这里*/
        spin_unlock_irqrestore(&sem->wait.lock, flags);                /*结束临界区*/

        schedule();                                                    /*开始调度*/            

        spin_lock_irqsave(&sem->wait.lock, flags);
        tsk->state = TASK_UNINTERRUPTIBLE;
    }
    remove_wait_queue_locked(&sem->wait, &wait);
    wake_up_locked(&sem->wait);
    spin_unlock_irqrestore(&sem->wait.lock, flags);
    tsk->state = TASK_RUNNING;
}

当,p1释放信号量的时候,会唤醒sem->wait中的进程。这时候,count的值会变成0,然后,例如p2被唤醒,
这时候,sleepers是1,所以 atomic_add_negative(sleepers - 1, &sem->count) 之后,count仍然是0,sleepers是0!

下一次,p3被唤醒的时候,count是1, atomic_add_negative(sleepers - 1, &sem->count) 之后,count变成了0,
sleepers变成了0!

所以这里,我们可以看出,只要是有进程等待在wait队列上,有进程释放信号量的时候,count就会在第一次
的时候从-1变成0,sleepers有1变成0,之后一直保持!

如果在这期间,还有进程申请信号量,那么count就会再次变成-1,而sleepers也会再次变成1。重复上述过程。


直到最后一个进程释放信号量的时候,count变成了1,sleepers变成了0!回到了最初状态!

阅读(754) | 评论(0) | 转发(0) |
0

上一篇:vim 多行注释

下一篇:参考资料网址glibc等

给主人留下些什么吧!~~