Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1029661
  • 博文数量: 61
  • 博客积分: 958
  • 博客等级: 准尉
  • 技术积分: 2486
  • 用 户 组: 普通用户
  • 注册时间: 2011-05-21 13:36
文章分类
文章存档

2020年(2)

2019年(1)

2018年(5)

2017年(7)

2015年(2)

2014年(4)

2012年(10)

2011年(30)

分类: LINUX

2011-11-14 22:20:52

摘要:本文主要讲述linux如何处理ARM cortex A9多核处理器的内核同步部分。主要包括读写信号量介绍。

法律声明LINUX3.0内核源代码分析》系列文章由谢宝友()发表于http://xiebaoyou.blog.chinaunix.net,文章中的LINUX3.0源代码遵循GPL协议。除此以外,文档中的其他内容由作者保留所有版权。谢绝转载。

 

1.1 读写信号量

读写信号量类似于信号量,但是它允许同时存在多个读者。在不能获得信号量时,当前进程会调度出去。

读写自旋锁用于不允许调度的上下文(如中断上下文),而读写信号量用于允许调度的上下文。

通常情况下(配置了CONFIG_RWSEM_GENERIC_SPINLOCK时),读写信号量的数据结构是rw_semaphore

/**

 * 读写信号量数据结构

 */

struct rw_semaphore {

         /**

          * 如果为0表示没有读者,也没有写者

          * 如果大于0表示当前的并发读者数。

          * 如果为-1,表示有一个写者。

          */

         __s32                          activity;

         /**

          * 保护本数据结构的自旋锁。

          */

         spinlock_t                  wait_lock;

         /**

          * 等待信号量的等待队列。

          */

         struct list_head        wait_list;

#ifdef CONFIG_DEBUG_LOCK_ALLOC

         /**

          * 用于调试。

          */

         struct lockdep_map dep_map;

#endif

};

1.1.1      获取和释放读信号量

获取读信号量的函数是down_read

/**

 * 获取读信号量

 */

void __sched __down_read(struct rw_semaphore *sem)

{

         struct rwsem_waiter waiter;

         struct task_struct *tsk;

         unsigned long flags;

 

         /**

          * 获得信号量的自旋锁。并关中断。关中断的原因是系统提供了可用于中断中的原语__down_read_trylock

          */

         spin_lock_irqsave(&sem->wait_lock, flags);

 

         /**

          * 如果以下两个条件满足,则可以顺利申请到读锁:

          *              当前没有写者获取到写锁。

          *              也没有进程在申请写锁

          */

         if (sem->activity >= 0 && list_empty(&sem->wait_list)) {

                   /* granted */

                   sem->activity++;/* 递增读者计数。 */

                   spin_unlock_irqrestore(&sem->wait_lock, flags);/* 释放自旋锁并开中断 */

                   goto out;/* ??有必要?? */

         }

 

         /**

          * 下面将进程挂到等待队列中

          */

         tsk = current;

         /**

          * 设置进程状态为TASK_UNINTERRUPTIBLE,请注意这里有一个内存屏障和编译屏障

          */

         set_task_state(tsk, TASK_UNINTERRUPTIBLE);

 

         /* set up my own style of waitqueue */

         waiter.task = tsk;

         /**

          * 此标志表示当前任务正在等待读锁。唤醒时需要判断此标志。

          */

         waiter.flags = RWSEM_WAITING_FOR_READ;

         get_task_struct(tsk);/* ??暂时不清楚为什么要增加进程的引用计数?? */

 

         list_add_tail(&waiter.list, &sem->wait_list);/* 将进程加入信号量的等待队列 */

 

         /* we don't need to touch the semaphore struct anymore */

         spin_unlock_irqrestore(&sem->wait_lock, flags);/* 释放自旋锁并开中断 */

 

         /* wait to be given the lock */

         for (;;) {/* 这里的死循环最多执行两次 */

                   /**

                    * 如果进程还没有调用schedule切换出去,就被信号量唤醒,那么就退出循环

                    * 其实不判断似乎也没有问题,也许是出于效率的原因。

                    */

                   if (!waiter.task)

                            break;

                   schedule();/* 这里调度出去,并等待被唤醒 */

                   set_task_state(tsk, TASK_UNINTERRUPTIBLE);

         }

 

         tsk->state = TASK_RUNNING;/* 恢复任务状态 */

 out:

         ;

}

释放读锁的函数是up_read

/**

 * 释放读信号量。

 */

void __up_read(struct rw_semaphore *sem)

{

         unsigned long flags;

 

         /**

          * 获得自旋锁并关中断。

          */

         spin_lock_irqsave(&sem->wait_lock, flags);

 

         /**

          * 当满足以下两个条件时,就唤醒等待的写者

          *              已经没有读者持有信号量。

          *              等待队列上有写者

          */

         if (--sem->activity == 0 && !list_empty(&sem->wait_list))

                   sem = __rwsem_wake_one_writer(sem);/* 唤醒等待队列中的写者(只唤醒第一个写者) */

 

         spin_unlock_irqrestore(&sem->wait_lock, flags);/* 释放自旋锁并开中断 */

}

 

static inline struct rw_semaphore *

__rwsem_wake_one_writer(struct rw_semaphore *sem)

{

         struct rwsem_waiter *waiter;

         struct task_struct *tsk;

 

         /**

          * activity置为-1表示当前信号量被写者持有。

          */

         sem->activity = -1;

 

         /**

          * 取出等待队列中的第一个对象,表示第一个等待信号量的写者

          */

         waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list);

         list_del(&waiter->list);/* 将等待的写者从队列中摘除。 */

 

         tsk = waiter->task;

         /**

          * 这里的屏障应该是为了确保被唤醒的写者在看到waiter->task = NULL前,看到sem->activity = -1;

          * 虽然wake_up_process将进程唤醒后,进程会看到wake_up_process之前的所有修改,但是等待任务在调度前就在判断waiter->task

          * 这时wake_up_process函数的屏障还没有起作用,因此需要在这里加一个屏障

          */

         smp_mb();

         waiter->task = NULL;

         wake_up_process(tsk);/* 唤醒等待的任务并递减任务引用计数 */

         put_task_struct(tsk);

         return sem;

}

1.1.2      获取和释放写信号量

获取写锁的函数是down_write

/**

 * 获取写锁

 */

void __sched __down_write_nested(struct rw_semaphore *sem, int subclass)

{

         struct rwsem_waiter waiter;

         struct task_struct *tsk;

         unsigned long flags;

 

         spin_lock_irqsave(&sem->wait_lock, flags);/* 获取自旋锁并关中断 */

 

         /**

          * 没有读者和写者,并且没有其他处于等待状态的读者和写者

          */

         if (sem->activity == 0 && list_empty(&sem->wait_list)) {

                   /* granted */

                   sem->activity = -1;/* activity置为-1表示被写者拥有 */

                   spin_unlock_irqrestore(&sem->wait_lock, flags);

                   goto out;

         }

 

         /**

          * 运行到这里,说明有读者或者写者,将当前任务挂起

          */

         tsk = current;

         set_task_state(tsk, TASK_UNINTERRUPTIBLE);/* 设置线程状态 */

 

         /* set up my own style of waitqueue */

         waiter.task = tsk;

         /* 这个标志表示等待者是一个写者 */

         waiter.flags = RWSEM_WAITING_FOR_WRITE;

         get_task_struct(tsk);/* 递增进程的引用计数。在被唤醒时,由释放信号量的进程递减其引用计数 */

 

         /**

          * 将进程添加到等待队列中。

          */

         list_add_tail(&waiter.list, &sem->wait_list);

 

         /* we don't need to touch the semaphore struct anymore */

         spin_unlock_irqrestore(&sem->wait_lock, flags);/* 释放自旋锁并开中断 */

 

         /* wait to be given the lock */

         for (;;) {

                   if (!waiter.task)/* 如果信号量被释放并且唤醒了本线程,则退出 */

                            break;

                   schedule();/* 没有释放信号量,必须调度出去 */

                   set_task_state(tsk, TASK_UNINTERRUPTIBLE);

         }

 

         /* 成功获取了信号量,退出 */

         tsk->state = TASK_RUNNING;

 out:

         ;

}

 

写者释放读写信号量的函数是up_write,实质上是调用__up_write

/**

 *

写者释放读写信号量

 */

void __up_write(struct rw_semaphore *sem)

{

         unsigned long flags;

 

         spin_lock_irqsave(&sem->wait_lock, flags);/* 获取自旋锁并关中断 */

 

         sem->activity = 0;/* 因为仅仅只能有一个写者,也不能有读者能够与写者同在,因此写者释放信号量时,可以将activity设置为0,表示没有写者和读者了 */

         if (!list_empty(&sem->wait_list))/* 有读者或者写者在等待获得信号量,需要唤醒一个或者多个读者,或者唤醒一个写者 */

                   sem = __rwsem_do_wake(sem, 1);

 

         spin_unlock_irqrestore(&sem->wait_lock, flags);/* 释放自旋锁并开中断 */

}

 

与读者释放读锁不同,读者仅仅会唤醒队列中第一个等待者,这个等待者必然是一个写者。写者释放写锁时,可能唤醒一个或者多个等待的进程:

 

/**

 * 写者释放信号量时,唤醒等待的读者和写者

 */

static inline struct rw_semaphore *

__rwsem_do_wake(struct rw_semaphore *sem, int wakewrite)

{

         struct rwsem_waiter *waiter;

         struct task_struct *tsk;

         int woken;

 

         /**

          * 取得等待队列上的第一个等待进程。

          */

         waiter = list_entry(sem->wait_list.next, struct rwsem_waiter, list);

 

         if (!wakewrite) {/* 调用者不希望唤醒写者 */

                   if (waiter->flags & RWSEM_WAITING_FOR_WRITE)/* 第一个等待进程是等待写锁 */

                            goto out;/* 此时不能成功唤醒写者,退出 */

                   goto dont_wake_writers;/* 跳转到dont_wake_writers,开始唤醒等待的读者 */

         }

 

         /* if we are allowed to wake writers try to grant a single write lock

          * if there's a writer at the front of the queue

          * - we leave the 'waiting count' incremented to signify potential

          *   contention

          */

         if (waiter->flags & RWSEM_WAITING_FOR_WRITE) {/* 第一个等待者是在等待写锁,只唤醒这个进程就行了。 */

                   sem->activity = -1;/* 将标志设置为-1,表示锁被写者持有了 */

                   list_del(&waiter->list);/* 将等待者从队列中删除 */

                   tsk = waiter->task;

                   /* Don't touch waiter after ->task has been NULLed */

                   smp_mb();

                   waiter->task = NULL;/* task字段设置为空,这样等待者被唤醒后,就知道是被信号量唤醒的 */

                   wake_up_process(tsk);/* 唤醒等待者 */

                   put_task_struct(tsk);/* 释放等待者的进程结构的引用计数。这个计数在等待者将自己添加到等待队列时增加。 */

                   goto out;

         }

 

         /* grant an infinite number of read locks to the front of the queue */

         /**

          * 运行到这里,说明第一个等待者是在等待读锁,需要唤醒所有读者,直到遇到一个写者。

          */

 dont_wake_writers:

         woken = 0;/* 被唤醒的读者计数 */

         while (waiter->flags & RWSEM_WAITING_FOR_READ) {/* 只要等待队列中的等待者是读者,就继续处理,将所有读者全部唤醒。 */

                   struct list_head *next = waiter->list.next;/* waiter是当前待唤醒任务,这里取得下一个任务 */

 

                   /**

                    * 将等待者从队列中摘除。

                    */

                   list_del(&waiter->list);

                   tsk = waiter->task;

                   smp_mb();

                   /**

                    * 将等待者唤醒。被唤醒任务通过waiter->task字段即能够判断出自己是被正常唤醒的。

                    */

                   waiter->task = NULL;

                   wake_up_process(tsk);

                   /**

                    * 释放等待进程的任务结构的引用计数。

                    */

                   put_task_struct(tsk);

                   woken++;/* 将唤醒的读者数量加1 */

                   if (list_empty(&sem->wait_list))/* 没有更多的等待者了,退出 */

                            break;

                   /**

                    * 处理下一个等待者,如果它不是写者,则继续将它唤醒。

                    */

                   waiter = list_entry(next, struct rwsem_waiter, list);

         }

 

         /**

          * activity设置为当前活动的读者数量。

          */

         sem->activity += woken;

 

 out:

         return sem;

}

 

1.1.3      关于读写信号量的疑问
经过反复思考,我觉得读写信号量的实现确实有点问题。已经向作者David Howells ()发送了邮件:
 
 
阅读(4184) | 评论(3) | 转发(5) |
给主人留下些什么吧!~~

xiebaoyou2012-09-25 20:59:21

diytvgy: 楼主,有一疑问
我对你smp_mb函数的作用不太明白,能不能详细解释一下。
我只能理解到这是为了确保tsk的赋值在waiter->task的赋值之前完成.....
要详细解释smp_mb的作用,需要一本书。
建议到http://xiebaoyou.download.csdn.net上下载我翻译的《深入理解并行编程》,原作者是IBM linux中心主任。

diytvgy2012-09-24 14:29:12

楼主,有一疑问
我对你smp_mb函数的作用不太明白,能不能详细解释一下。
我只能理解到这是为了确保tsk的赋值在waiter->task的赋值之前完成

walleeee2011-11-15 18:56:00

希望能吧解释放在每个函数的开头,代码中不要放注释,影响视觉