http://www.embexperts.com/viewthread.php?tid=31

两者最大区别:信号量可以允许多个线程进入临界区,而互斥体只允许一个线程进入临界区。本贴将描述信号量与互斥体之间的细微区别以及在实际的代码设计中如何使用它们。

信号量在2.6.26中的定义如下:
struct semaphore {
        spinlock_t                lock;
        unsigned int             count;
        struct list_head        wait_list;
};

从以上信号量的定义中,可以看到信号量底层使用到了spin lock的锁定机制,这个spin lock主要用来确保对count成员的原子性的操作(count--)和测试(count > 0)。

信号量的P操作:
(1).void down(struct semaphore *sem);
(2).int down_interruptible(struct semaphore *sem);
(3).int down_trylock(struct semaphore *sem);

其中(1)中的函数根据2.6.26中的代码注释,这个函数已经out了(Use of this function is deprecated),所以从实用角度,彻底忘了它吧。其中(3)试图去获得一个信号量,如果没有获得,函数立刻返回1而不会让当前进程进入睡眠状态。
(2)最常用,所以是我们的重点关注:

/**
* down_interruptible - acquire the semaphore unless interrupted
* @sem: the semaphore to be acquired
*
* Attempts to acquire the semaphore.  If no more tasks are allowed to
* acquire the semaphore, calling this function will put the task to sleep.
* If the sleep is interrupted by a signal, this function will return -EINTR.
* If the semaphore is successfully acquired, this function returns 0.
*/
int down_interruptible(struct semaphore *sem)
{
        unsigned long flags;
        int result = 0;

spin_lock_irqsave(&sem->lock, flags);
        if (likely(sem->count > 0))
                sem->count--;
        else
                result = __down_interruptible(sem);
        spin_unlock_irqrestore(&sem->lock, flags);

return result;
}

这段函数很好理解:在保证原子操作的前提下,先测试count是否大于0,如果是说明可以获得信号量,这种情况下需要先将count--,以确保别的进程能否获得该信号量,然后函数返回,其调用者开始进入临界区。如果没有获得信号量,当前进程利用struct semaphore 中wait_list加入等待队列,开始睡眠。
对于需要休眠的情况,在__down_interruptible()函数中,会构造一个struct semaphore_waiter类型的变量(struct semaphore_waiter定义如下:
struct semaphore_waiter {
        struct list_head list;
        struct task_struct *task;
        int up;
}
;),将当前进程赋给task,并利用其list成员将该变量的节点加入到以sem中的wait_list为头部的一个列表中,假设有多个进程在sem上调用down_interruptible,则sem的wait_list上形成的队列如下图:

(注:将一个进程阻塞,一般的经过是先把进程放到等待队列中,接着改变进程的状态,比如设为TASK_INTERRUPTIBLE,然后调用调度函数schedule(),后者将会把当前进程从cpu的运行队列中摘下)

信号量的V操作:
void up(struct semaphore *sem);

/**
* up - release the semaphore
* @sem: the semaphore to release
*
* Release the semaphore.  Unlike mutexes, up() may be called from any
* context and even by tasks which have never called down().
*/
void up(struct semaphore *sem)
{
        unsigned long flags;

spin_lock_irqsave(&sem->lock, flags);
        if (likely(list_empty(&sem->wait_list)))
                sem->count++;
        else
                __up(sem);
        spin_unlock_irqrestore(&sem->lock, flags);
}

如果没有其他线程等待在目前即将释放的信号量上,那么只需将count++即可。如果有其他线程正因为等待该信号量而睡眠,那么调用__up.

__up的定义:
static noinline void __sched __up(struct semaphore *sem)
{
        struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
                                                struct semaphore_waiter, list);
        list_del(&waiter->list);
        waiter->up = 1;
        wake_up_process(waiter->task);
}

这个函数首先获得sem所在的wait_list为头部的链表的第一个有效节点,然后从链表中将其删除,然后唤醒该节点上睡眠的进程。
由此可见,对于sem上的每次down_interruptible调用,都会在sem的wait_list链表尾部加入一新的节点。对于sem上的每次up调用,都会删除掉wait_list链表中的第一个有效节点,并唤醒睡眠在该节点上的进程。

互斥体mutex
Linux 2.6.26中mutex的定义:
struct mutex {
        /* 1: unlocked, 0: locked, negative: locked, possible waiters */
        atomic_t                count;
        spinlock_t                wait_lock;
        struct list_head        wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
        struct thread_info        *owner;
        const char                 *name;
        void                        *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
        struct lockdep_map        dep_map;
#endif
};

对比1楼中的struct semaphore,struct mutex除了增加了几个作为debug用途的成员变量外,和semaphore几乎长得一样。但是mutex的引入主要是为了提供互斥机制,以避免多个进程同时在一个临界区中运行。

如果静态声明一个count=1的semaphore变量,可以使用DECLARE_MUTEX(name), 宏DECLARE_MUTEX的相关定义如下:
#define __SEMAPHORE_INITIALIZER(name, n)                                \
{                                                                        \
        .lock                = __SPIN_LOCK_UNLOCKED((name).lock),                \
        .count                = n,                                                \
        .wait_list        = LIST_HEAD_INIT((name).wait_list),                \
}

#define __DECLARE_SEMAPHORE_GENERIC(name, count) \
        struct semaphore name = __SEMAPHORE_INITIALIZER(name, count)

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

所以DECLARE_MUTEX(name)实际上是定义一个semaphore,所以它的使用应该对应信号量的P,V函数.

如果要定义一个静态mutex型变量,应该使用DEFINE_MUTEX:
#define __MUTEX_INITIALIZER(lockname) \
                { .count = ATOMIC_INIT(1) \
                , .wait_lock = __SPIN_LOCK_UNLOCKED(lockname.wait_lock) \
                , .wait_list = LIST_HEAD_INIT(lockname.wait_list) \
                __DEBUG_MUTEX_INITIALIZER(lockname) \
                __DEP_MAP_MUTEX_INITIALIZER(lockname) }#define DEFINE_MUTEX(mutexname) \
        struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)

如果在程序运行期要初始化一个mutex变量,可以使用mutex_init(mutex),mutex_init是个宏,在该宏定义的内部,会调用__mutex_init函数。
#define mutex_init(mutex)                                                \
do {                                                                        \
        static struct lock_class_key __key;                                \
                                                                        \
        __mutex_init((mutex), #mutex, &__key);                                \
} while (0)

__mutex_init定义如下:
/***
* mutex_init - initialize the mutex
* @lock: the mutex to be initialized
*
* Initialize the mutex to unlocked state.
*
* It is not allowed to initialize an already locked mutex.
*/
void
__mutex_init(struct mutex *lock, const char *name, struct lock_class_key *key)
{
        atomic_set(&lock->count, 1);
        spin_lock_init(&lock->wait_lock);
        INIT_LIST_HEAD(&lock->wait_list);

debug_mutex_init(lock, name, key);
}

从__mutex_init的定义可以看出,在使用mutex_init宏来初始化一个mutex变量时,应该使用mutex的指针型。

mutex上的P,V操作:void mutex_lock(struct mutex *lock)void __sched mutex_unlock(struct mutex *lock)

从原理上讲,mutex实际上是count=1情况下的semaphore,所以其PV操作应该和semaphore是一样的。但是在实际的Linux代码上,出于性能优化的角度,并非只是单纯的重用down_interruptible和up的代码。以ARM平台的mutex_lock为例,实际上是将mutex_lock分成两部分实现:fast path和slow path,主要是基于这样一个事实:在绝大多数情况下,试图获得互斥体的代码总是可以成功获得。所以Linux的代码针对这一事实用ARM V6上的LDREX和STREX指令来实现fast path以期获得最佳的执行性能。metex_lock在ARM平台的代码定义如下:
void inline __sched mutex_lock(struct mutex *lock)
{
        might_sleep();
        /*
         * The locking fastpath is the 1->0 transition from
         * 'unlocked' into 'locked' state.
         */
        __mutex_fastpath_lock(&lock->count, __mutex_lock_slowpath);
}

其中的might_sleep主要用于调试目的,这里不考虑。函数的核心是__mutex_fastpath_lock(&lock->count, __mutex_lock_slowpath),也就是上面提到的fast path和slow path。如果在__mutex_fastpath_lock函数中可以成功获得互斥体,那么__mutex_lock_slowpath将不会调用,否则调用之(使试图获得该互斥体而不可得的进程进入休眠状态)。在绝大多数情况下,__mutex_lock_slowpath都没机会被调用。__mutex_fastpath_lock主要实现原子级的dec_test这样的操作序列,可以想见应该是处理器相关的汇编代码,在ARM V6+,其实现如下:
static inline void
__mutex_fastpath_lock(atomic_t *count, void (*fail_fn)(atomic_t *))
{
        int __ex_flag, __res;

__asm__ (

"ldrex        %0, [%2]        \n\t"
                "sub        %0, %0, #1        \n\t"
                "strex        %1, %0, [%2]        "

: "=&r" (__res), "=&r" (__ex_flag)
                : "r" (&(count)->counter)
                : "cc","memory" );

__res |= __ex_flag;
        if (unlikely(__res != 0))
                fail_fn(count);
}

代码看起来很简单,如果count减1之后不为0(说明这之前该互斥体已被别的进程获得)或者是最后的strex导致代码中的"%1",也就是__ex_flag不等于0(说明此时有别的进程正在和当前进程竞争该互斥体,而且别的进程较当前进程更早发起对该互斥体的锁定请求,这种情况下当前进程将不会在本次的竞争中获胜),这两种情况都将导致__mutex_lock_slowpath被调用。但是绝大多数情况下,当前进程都会成功获得互斥体,换句话说,绝大多数情况下,__res都是等于0的(这也是为什么在if语句中使用了unlikely的原因)。

__mutex_lock_slowpath的代码实现因为加入了debug支持的缘故显得有点晦涩,本贴不再仔细剖析。如果当前进程在竞争一个互斥体的过程中fast path lock部分失败,那么在进入low path lock部分,依然有机会重新获得该互斥体(比如之前获得该互斥体的进程已经释放了它),如果依然没法获得,那么将当前进程的状态设置为TASK_INTERRUPTIBLE,调用schedule()使当前进程进入休眠。

从代码阅读的角度,似乎一切看起来都很正常。但是有一个问题,在Linux开源社区,经常会听到有人说:读懂Linux的代码并不难,难得是明白代码背后隐藏的哲学。如果经常去琢磨Linux为什么要象现在看起来的那样去设计代码,对提升自己的设计能力无疑是有帮助的。所以,从mutex_lock的设计思想出发,想想为什么semaphore上的down_interruptible是另一幅样子呢?能否象mutex那样去实现down_interruptible函数呢?

Linux系统中的信号量(semphore)与互斥体(mutex)的更多相关文章

  1. linux系统中的进程状态分析

    转载地址:https://blog.csdn.net/shenwansangz/article/details/51981459 linux是一个多用户,多任务的系统,可以同时运行多个用户的多个程序, ...

  2. linux系统中的一些典型问题汇总

    一.文件系统破坏导致系统无法启动:Checking root filesystem/dev/sda6 contains a file system with errors,check forcedAn ...

  3. Linux系统中的Device Mapper学习

    在linux系统中你使用一些命令时(例如nmon.iostat 如下截图所示),有可能会看到一些名字为dm-xx的设备,那么这些设备到底是什么设备呢,跟磁盘有什么关系呢?以前不了解的时候,我也很纳闷. ...

  4. 在linux系统中安装VSCode(Visual Studio Code)

    在linux系统中安装VSCode(Visual Studio Code) 1.从官网下载压缩包(话说下载下来解压就直接可以运行了咧,都不需要make) 访问Visual Studio Code官网  ...

  5. Linux 系统中的MySQL数据库默认区分大小写

    今天在开发中遇到这么个问题,将连接的数据库改为服务器上的时候(服务器是Linux系统的),程序跑起来后一直出错,总提示数据库的表找不到, 而打开数据库看该表明明是存在的,在我的印象中MySQL数据是不 ...

  6. Linux 系统中僵尸进程

    Linux 系统中僵尸进程和现实中僵尸(虽然我也没见过)类似,虽然已经死了,但是由于没人给它们收尸,还能四处走动.僵尸进程指的是那些虽然已经终止的进程,但仍然保留一些信息,等待其父进程为其收尸.配图源 ...

  7. Linux系统中配置jdk

    在Linux系统下安装jdk 1.到Oracle公司的官网里下载好jdk,网址 http://www.oracle.com/technetwork/java/javase/downloads/jdk8 ...

  8. Linux 系统中用户切换

    1. Linux系统中用户切换的命令为su,语法为: su [-fmp] [-c command] [-s shell] [--help] [--version] [-] [USER [ARG]] 参 ...

  9. 77 swapon-激活Linux系统中交换空间

    Linux swapon命令用于激活Linux系统中交换空间,Linux系统的内存管理必须使用交换区来建立虚拟内存. 语法 /sbin/swapon -a [-v] /sbin/swapon [-v] ...

随机推荐

  1. 安卓中AsyncTask的基本使用

    安卓中AsyncTask的基本使用 使用场景介绍 在安卓开发中,我们经常需要访问互联网资源,这些访问是都需要在后台线程中去完成的,因为安卓的UI线程不允许执行耗时任务.然而,后台线程是不可以修改安卓的 ...

  2. MySQL之分页问题解决

    最近遇到很多大分页的问题,服务器负载很高,SQL的执行时间也较长,非常的头痛.下面我们就说说分页问题的解决. 大家都知道对于mysql来说,select * from table where x=‘? ...

  3. word2007-2010排版中解决段后插入分页符 新页首行空行问题

    word2007-2010排版中,很多人都会遇到 这个问题.当你在 段后插入分页符 想开启新的一页的时候,新页首行有个空行.如果删除,会连同分页符一起删除.不删除有影响排版美观.那怎么解决呢: 解决办 ...

  4. MC34063组成DC-DC电路

    +VO的输出电压峰值可达2倍V_IN,-VO的输出电压可达-V_IN. 需要注意的是,3路的峰值电路不能超过1.5A,同时两路附加电源的输出功率和必须小于V_IN·I·(1-D), 其中I为主输出的电 ...

  5. 关于DSP的GPIO的输入输出设置

    DSP 28335 的 GPIO的输入设置: GPIO的输入设置时,除了将此GPIO设置为输入之外,还需要将此GPIO口的电平拉高 //RXD3 GpioCtrlRegs.GPBMUX2.bit.GP ...

  6. Caffe简单入门 AI

    https://yq.aliyun.com/articles/112207?spm=5176.100239.bloglist.58.wN003U

  7. 探究rh6上mysql5.6的主从、半同步、GTID多线程、SSL认证主从复制

    http://407711169.blog.51cto.com/6616996/1203973/

  8. UVa 637 - Booklet Printing

    题目:模拟输出n页书的装订打印状态. 分析:模拟.页数为(n+3)/ 4,仅仅有n不超过半篇时会输出半篇. 说明:好多曾经做过的题目(⊙_⊙). #include <cstdlib> #i ...

  9. BusyBox 简化嵌入式 Linux 系统

    BusyBox 是很多标准 Linux® 工具的一个单个可执行实现.BusyBox 包含了一些简单的工具,例如 cat 和 echo,还包含了一些更大.更复杂的工具,例如 grep.find.moun ...

  10. ORACLE 中如何截取到时间的年月日中的年

    在Oracle中,要获得日期中的年份,例如把sysdate中的年份取出来,并不是一件难事.常用的方法是:Select to_number(to_char(sysdate,'yyyy')) from d ...