Chinaunix首页 | 论坛 | 博客
  • 博客访问: 540477
  • 博文数量: 64
  • 博客积分: 1591
  • 博客等级: 上尉
  • 技术积分: 736
  • 用 户 组: 普通用户
  • 注册时间: 2010-12-08 14:54
文章分类

全部博文(64)

文章存档

2011年(42)

2010年(22)

分类: LINUX

2011-02-24 14:18:53

linux内核信号量学习  

  

一、定义:

/linux/include/asm-i386/semaphore.h

  44struct semaphore {

  45        atomic_t count;

  46        int sleepers;

  47        wait_queue_head_t wait;

  48};

 

二、作用:

Linux中的信号量是一种睡眠锁。如果有一个任务试图获得一个已被持有的信号量时,信号量会将其推入等待队列,然后让其睡眠。这时处理器获得自由去执行其它代码。当持有信号量的进程将信号量释放后,在等待队列中的一个任务将被唤醒,从而便可以获得这个信号量

。主要用在linux内核中的同步和互斥。

三、字段详解:

1atomic_t count;

typedef struct { int counter; } atomic_t;

在此根据count.counter的值不同该字段代表不同的意义:

(1)如果count.counter大于0,则资源是空闲的,该资源现在可以被使用。

(2)如果count.counter等于0,则信号量是忙的,但没有进程等待这个被保护的资源,当前只有该进程在访问被保护的资源。

(3)如果count.counter小于0,则该资源不可用,并且至少有一个进程在等待该资源。

2int sleepers;

存放一个标志,表示是否有一些进程在信号量上睡眠。在获取信号量操作的时候,使用该字段和count字段来判断信号量的状态和进行不同的操作。

3wait_queue_head_t;

  50struct __wait_queue_head {

  51        spinlock_t lock;

  52        struct list_head task_list;

  53};

  54typedef struct __wait_queue_head wait_queue_head_t;

 

task_list字段存放当前等待该信号量的所有进程的链表。如果count.counter大于或等于0,该链表就为空。

四、特点:

信号量的睡眠特性,使得信号量适用于锁会被长时间持有的情况;只能在进程上下文中使用,因为中断上下文中是不能被调度的;另外当代码持有信号量时,不可以再持有自旋锁。

一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。

五、操作:

1、定义及初始化:

(1)

struct semaphore sem; 

sema_init(&sem,1);

直接定义一个信号量sem,并调用sema_init()对其进行初始化:

  64static inline void sema_init (struct semaphore *sem, int val)

  65{

  66/*

  67 *      *sem = (struct semaphore)__SEMAPHORE_INITIALIZER((*sem),val);

  68 *

  69 * i'd rather use the more flexible initialization above, but sadly

  70 * GCC 2.7.2.3 emits a bogus warning. EGCS doesn't. Oh well.

  71 */

  72        atomic_set(&sem->count, val);

  73        sem->sleepers = 0;

  74        init_waitqueue_head(&sem->wait);

  75}

 

该函数会将sem->count.counter初始化为val。虽然val可以为任何整数,但通常会取10。并置sleepers0sem->wait.task_list为空链表。

(2)

srtuct semaphore sem;

init_MUTEX(&sem);

直接定义信号量sem并初始化为互斥信号量。

  77static inline void init_MUTEX (struct semaphore *sem)

  78{

  79        sema_init(sem, 1);

  80}

 

该函数直接将信号量的count.counter置为1,便是初始化一个用于互斥访问的信号量。也就是说被保护的资源不能同时被多个进程访问,此刻资源是空闲的,那么当有一个进程访问该资源时,也即获得了信号量,当再有进程到来且当前访问资源的进程没有释放信号量时,后来的进程是不能访问该资源的。此刻它被置入信号量的等待进程队列,并进入休眠状态。 

(3)

struct semaphore sem;

init_MUTEX_LOCKED(&sem);

直接定义信号量sem并初始化为资源忙状态,用于同步。

  82static inline void init_MUTEX_LOCKED (struct semaphore *sem)

  83{

  84        sema_init(sem, 0);

  85}

 

也就是说当前该信号量已经被锁定,一个执行单元的继续执行需等待另一执行单元完成,保证执行的先后顺序。例如: 进程A                                              进程B

struct semaphore sem;                 ...[CODE 2]...

init_MUTEX_LOCKED(&sem);        up(&sem);

...[CODE 1]...

down(&sem);

...[CODE 3]...

 

如上,进程A先运行,运行到down(&sem);的时候发现信号量为资源忙状态,不能获得,于是被置入信号量的等待队列中。当进程B执行完CODE 2代码段到up(&sem)时,会释放信号量,发现进程A在等待信号量,就将A从等待队列中删除,并唤醒A。这样就保证了代码的执行顺序是CODE 1 → CODE 2 → CODE 3。实现了同步。

(4)

DECLARE_MUTEX(sem);

DECLARE_MUTEX_LOCKED(sem);

  51#define __SEMAPHORE_INITIALIZER(name, n)                                \

  52{                                                                       \

  53        .count          = ATOMIC_INIT(n),                               \

  54        .sleepers       = 0,                                            \

  55        .wait           = __WAIT_QUEUE_HEAD_INITIALIZER((name).wait)    \

  56}

  57

  58#define __DECLARE_SEMAPHORE_GENERIC(name,count) \

  59        struct semaphore name = __SEMAPHORE_INITIALIZER(name,count)

  60

  61#define DECLARE_MUTEX(name) __DECLARE_SEMAPHORE_GENERIC(name,1)

  62#define DECLARE_MUTEX_LOCKED(name) __DECLARE_SEMAPHORE_GENERIC(name,0)

 

此两个宏都是定义初始化信号量。DECLARE_MUTEX()等同于以上所讲的第二个,DECLARE_MUTEX_LOCKED()等同于以上所讲的第三个。

2、获得信号量:

(1)

  97static inline void down(struct semaphore * sem)

  98{

  99        might_sleep();

 100        __asm__ __volatile__(

 101                "# atomic down operation\n\t"

 102                LOCK_PREFIX "decl %0\n\t"     /* --sem->count */

 103                "jns 2f\n"

 104                "\tlea %0,%%eax\n\t"

 105                "call __down_failed\n"

 106                "2:"

 107                :"+m" (sem->count)

 108                :

 109                :"memory","ax");

 110}

  64void __sched

  65__down_failed(struct semaphore *sem)

  66{

  67        struct task_struct *tsk = current;

  68        DECLARE_WAITQUEUE(wait, tsk);

  69

  70#ifdef CONFIG_DEBUG_SEMAPHORE

  71        printk("%s(%d): down failed(%p)\n",

  72               tsk->comm, tsk->pid, sem);

  73#endif

  74

  75        tsk->state = TASK_UNINTERRUPTIBLE;

  76        wmb();

  77        add_wait_queue_exclusive(&sem->wait, &wait);

  78

  79        /*

  80         * Try to get the semaphore.  If the count is > 0, then we've

  81         * got the semaphore; we decrement count and exit the loop.

  82         * If the count is 0 or negative, we set it to -1, indicating

  83         * that we are asleep, and then sleep.

  84         */

  85        while (__sem_update_count(sem, -1) <= 0) {

  86                schedule();

  87                set_task_state(tsk, TASK_UNINTERRUPTIBLE);

  88        }

  89        remove_wait_queue(&sem->wait, &wait);

  90        tsk->state = TASK_RUNNING;

  91

  92        /*

  93         * If there are any more sleepers, wake one of them up so

  94         * that it can either get the semaphore, or set count to -1

  95         * indicating that there are still processes sleeping.

  96         */

  97        wake_up(&sem->wait);

  98

  99#ifdef CONFIG_DEBUG_SEMAPHORE

 100        printk("%s(%d): down acquired(%p)\n",

 101               tsk->comm, tsk->pid, sem);

 102#endif

 103}

 

从代码中可知: 

如果count.counter1,则置count.counter0,直接跳出函数。

如果count.counter0count.counter被减为-1,之后执行__down_failed()函数。

__down_failed()函数首先将当前进程设置为不可中断状态(TASK_UNINTERRUPTIBLE

)然后将其添加进等待进程队列,接下来在whlie循环处试图获得信号量。如果count.counter大于0就获得了信号量,则不进入循环,将当前进程从等待队列中删除,并设置其状态为可运行状态(TASK_RUNNING),最后唤醒等待该信号量的进程(此处初始count.counter0,表示没有等待进程,所以此句相当于没有)。否则将count.counter设置为-1,并进入whlie循环,挂起当前进程,随后又恢复,继续测试count.counter字段直到其大于0(即获得信号量)

如果count.counter为-1,则其被置为-2,之后执行__down_failed()函数。与count.counter等于0不同的是其在获得了信号量之后,由于有等待进程(count.counter=-1),所以退出时会唤醒等待进程。

由于信号量会导致睡眠,所以不能用在中断上下文。再者使用down()而进入睡眠的进程不能被信号打断。

(2)

 112/*

 113 * Interruptible try to acquire a semaphore.  If we obtained

 114 * it, return zero.  If we were interrupted, returns -EINTR

 115 */

 116static inline int down_interruptible(struct semaphore * sem)

 117{

 118        int result;

 119

 120        might_sleep();

 121        __asm__ __volatile__(

 122                "# atomic interruptible down operation\n\t"

 123                "xorl %0,%0\n\t"

 124                LOCK_PREFIX "decl %1\n\t"     /* --sem->count */

 125                "jns 2f\n\t"

 126                "lea %1,%%eax\n\t"

 127                "call __down_failed_interruptible\n"

 128                "2:"

 129                :"=&a" (result), "+m" (sem->count)

 130                :

 131                :"memory");

 132        return result;

 133}

 105int __sched

 106__down_failed_interruptible(struct semaphore *sem)

 107{

 108        struct task_struct *tsk = current;

 109        DECLARE_WAITQUEUE(wait, tsk);

 110        long ret = 0;

 111

 112#ifdef CONFIG_DEBUG_SEMAPHORE

 113        printk("%s(%d): down failed(%p)\n",

 114               tsk->comm, tsk->pid, sem);

 115#endif

 116

 117        tsk->state = TASK_INTERRUPTIBLE;

 118        wmb();

 119        add_wait_queue_exclusive(&sem->wait, &wait);

 120

 121        while (__sem_update_count(sem, -1) <= 0) {

 122                if (signal_pending(current)) {

 123                        /*

 124                         * A signal is pending - give up trying.

 125                         * Set sem->count to 0 if it is negative,

 126                         * since we are no longer sleeping.

 127                         */

 128                        __sem_update_count(sem, 0);

 129                        ret = -EINTR;

 130                        break;

 131                }

 132                schedule();

 133                set_task_state(tsk, TASK_INTERRUPTIBLE);

 134        }

 135

 136        remove_wait_queue(&sem->wait, &wait);

 137        tsk->state = TASK_RUNNING;

 138        wake_up(&sem->wait);

 139

 140#ifdef CONFIG_DEBUG_SEMAPHORE

 141        printk("%s(%d): down %s(%p)\n",

 142               current->comm, current->pid,

 143               (ret < 0 ? "interrupted" : "acquired"), sem);

 144#endif

 145        return ret;

 146}

 

由代码可以看出down_interruptible()down()不同的是:down_interruptible()有返回值, 

在调用__down_failed_interruptible()函数时,while循环中稍有不同。__down_failed_interruptible()while中时,如果收到TIF_SIGPENDING信号时,会置count.counter0,跳出循环,可见down_interruptible()是可以被信号打断的,且返回非零(EINIR)

(3)

 139static inline int down_trylock(struct semaphore * sem)

 140{

 141        int result;

 142

 143        __asm__ __volatile__(

 144                "# atomic interruptible down operation\n\t"

 145                "xorl %0,%0\n\t"

 146                LOCK_PREFIX "decl %1\n\t"     /* --sem->count */

 147                "jns 2f\n\t"

 148                "lea %1,%%eax\n\t"

 149                "call __down_failed_trylock\n\t"

 150                "2:\n"

 151                :"=&a" (result), "+m" (sem->count)

 152                :

 153                :"memory");

 154        return result;

 155}

 

该函数尝试获得信号量sem,如果能够立即获得,则获得信号量sem并返回0,否则,返回非0。它不会导致调用者睡眠,可以在中断上下文使用。

3、释放信号量:

 168static inline void up(struct semaphore * sem)

 169{

 170        __asm__ __volatile__(

 171                "# atomic up operation\n\t"

 172                LOCK_PREFIX "incl %0\n\t"     /* ++sem->count */

 173                "jg 1f\n\t"

 174                "call __up_wakeup\n"

 175                "1:"

 176                :"=m" (sem->count)

 177                :"D" (sem)

 178                :"memory");

 179}

 148void

 149__up_wakeup(struct semaphore *sem)

 150{

 151        /*

 152         * Note that we incremented count in up() before we came here,

 153         * but that was ineffective since the result was <= 0, and

 154         * any negative value of count is equivalent to 0.

 155         * This ends up setting count to 1, unless count is now > 0

 156         * (i.e. because some other cpu has called up() in the meantime),

 157         * in which case we just increment count.

 158         */

 159        __sem_update_count(sem, 1);

 160        wake_up(&sem->wait);

 161}

 

up()函数首先使count.counter自增,如果其大于0,则,说明其初始值是0,没有等待进程,所以直接跳出。否则调用__up_wake_up()函数将count.counter置为1,再唤醒等待进程。

六、使用实例:

信号量的一般使用形式是:

DECLARE_MUTEX(sem);

down(&sem); //获得信号量

...[CODE]... //临界区(被保护的资源)

up(&sem); //释放信号量

七、信号量与自旋锁的比较:

信号量是进程级别的,用于多个进程之间对资源的互斥,虽然也是在内核中,但是该内核执行路径是以进程的身份,代表进程来竞争资源的。如果竞争失败,就会发生进程上下文切换,当前进程进入睡眠状态,CPU将运行其他进程。鉴于进程上下文切换的开销也很大,因此只有当进程占用资源时间较长时,用信号量才是较好的选择。当所要保护的临界区访问时间比较短时,用自旋锁是非常方便的,因为它节省上下文切换的时间。但是CPU得不到自旋锁会在那里空转知道其他执行单元解锁为止。所以要求锁不能在临界区里常时间停留,否则会降低系统的效率。

              自旋锁对信号量

------------------------------------------------------

       需求                                建议的加锁方法

低开销加锁                              优先使用自旋锁

短期锁定                                  优先使用自旋锁

长期加锁                                  优先使用信号量

中断上下文中加锁                     使用自旋锁

持有锁是需要睡眠、调度            使用信号量 

阅读(2224) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~