Linux内核的同步机制(2)信号量(semaphore)

http://www.cppblog.com/aaxron/archive/2013/04/12/199387.html

  信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。

  一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。

  当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。

信号量的API有:
DECLARE_MUTEX(name)
该宏声明一个信号量name并初始化它的值为1,即声明一个互斥锁

//在Linux2.6.26中没找到该宏
DECLARE_MUTEX_LOCKED(name)
该宏声明一个互斥锁name,但把它的初始值设置为0,即锁在创建时就处在已锁状态。因此对于这种锁,一般是先释放后获得。

void sema_init (struct semaphore *sem, int val);
该函用于数初始化设置信号量的初值,它设置信号量sem的值为val。

void init_MUTEX (struct semaphore *sem);
该函数用于初始化一个互斥锁,即它把信号量sem的值设置为1

void init_MUTEX_LOCKED (struct semaphore *sem);
该函数也用于初始化一个互斥锁,但它把信号量sem的值设置为0,即一开始就处在已锁状态。

void down(struct semaphore * sem);
该函数用于获得信号量sem,它会导致睡眠,因此不能在中断上下文(包括IRQ上下文和softirq上下文)使用该函数。该函数将把sem的值减1,如果信号量sem的值非负,就直接返回,否则调用者将被挂起,直到别的任务释放该信号量才能继续运行。

int down_interruptible(struct semaphore * sem);
该函数功能与down类似,不同之处为,down不会被信号(signal)打断,但down_interruptible能被信号打断,因此该函数有返回值来区分是正常返回还是被信号中断,如果返回0,表示获得信号量正常返回,如果被信号打断,返回-EINTR。

int down_trylock(struct semaphore * sem);
该函数试着获得信号量sem,如果能够立刻获得,它就获得该信号量并返回0,否则,表示不能获得信号量sem,返回值为非0值。因此,它不会导致调用者睡眠,可以在中断上下文使用。

void up(struct semaphore * sem);
该函数释放信号量sem,即把sem的值加1,如果sem的值为非正数,表明有任务等待该信号量,因此唤醒这些等待者。 

{

补充:此{}里的内容原文中不存在,现在自己添加的也是别人的,有助于理解记忆

http://blog.csdn.net/ce123/article/details/7547973 

 

一个进程在调用down_interruptible()之后,如果sem<0,那么就进入到可中断的睡眠状态并调度其它进程运行, 但是一旦该进程收到信号,那么就会从down_interruptible函数中返回。并标记错误号为:-EINTR。一个形象的比喻:传入的信号量为1好比天亮,如果当前信号量为0,进程睡眠,直到(信号量为1)天亮才醒,但是可能中途有个闹铃(信号)把你闹醒。又如:小强下午放学回家,回家了就要开始吃饭嘛,这时就会有两种情况:情况一:饭做好了,可以开始吃;情况二:当他到厨房去的时候发现妈妈还在做,妈妈就对他说:“你先去睡会,待会做好了叫你。”小强就答应去睡会,不过又说了一句:“睡的这段时间要是小红来找我玩,你可以叫醒我。”小强就是down_interruptible,想吃饭就是获取信号量,睡觉对应这里的休眠,而小红来找我玩就是中断休眠。

   使用可被中断的信号量版本的意思是,万一出现了semaphore的死锁,还有机会用ctrl+c发出软中断,让等待这个内核驱动返回的用户态进程退出。而不是把整个系统都锁住了。在休眠时,能被中断信号终止,这个进程是可以接受中断信号的!比如你在命令行中输入# sleep 10000,按下ctrl + c,就给上面的进程发送了进程终止信号。信号发送给用户空间,然后通过系统调用,会把这个信号传给递给驱动。信号只能发送给用户空间,无权直接发送给内核的,那1G的内核空间,我们是无法直接去操作的。

}

#include <linux/semaphore.h>
/*
 * Copyright (c) 2008 Intel Corporation
 * Author: Matthew Wilcox <willy@linux.intel.com>
 *
 * Distributed under the terms of the GNU GPL, version 2
 *
 * Please see kernel/semaphore.c for documentation of these functions
 */
#ifndef __LINUX_SEMAPHORE_H
#define __LINUX_SEMAPHORE_H

#include <linux/list.h>
#include <linux/spinlock.h>

/* Please don't access any members of this structure directly */
struct semaphore {
    spinlock_t        lock;
    unsigned int        count;
    struct list_head    wait_list;
};

#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)

static inline void sema_init(struct semaphore *sem, int val)
{
    static struct lock_class_key __key;
    *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
    lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}

#define init_MUTEX(sem)        sema_init(sem, 1)
#define init_MUTEX_LOCKED(sem)    sema_init(sem, 0)

extern void down(struct semaphore *sem);
extern int __must_check down_interruptible(struct semaphore *sem);
extern int __must_check down_killable(struct semaphore *sem);
extern int __must_check down_trylock(struct semaphore *sem);
extern int __must_check down_timeout(struct semaphore *sem, long jiffies);
extern void up(struct semaphore *sem);

#endif /* __LINUX_SEMAPHORE_H */


{{{{{{{{{{{{{{{{{{{{{{
此部分也是原文没有的,自己添加的
http://www.cnblogs.com/biyeymyhjob/archive/2012/07/21/2602015.html

信号量

      信号量又称为信号灯,它是用来协调不同进程间的数据对象的,而最主要的应用是共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作: 
   (1) 测试控制该资源的信号量。 
   (2) 若此信号量的值为正,则允许进行使用该资源。进程将信号量减1。 
   (3) 若此信号量为0,则该资源目前不可用,进程进入睡眠状态,直至信号量值大于0,进程被唤醒,转入步骤(1)。 
   (4) 当进程不再使用一个信号量控制的资源时,信号量值加1。如果此时有进程正在睡眠等待此信号量,则唤醒此进程。 
    维护信号量状态的是Linux内核操作系统而不是用户进程。我们可以从头文件/usr/src/linux/include/linux/sem.h 中看到内核用来维护信号量状态的各个结构的定义。信号量是一个数据集合,用户可以单独使用这一集合的每个元素。要调用的第一个函数是semget,用以获得一个信号量ID。Linux2.6.26下定义的信号量结构体:

struct semaphore {
        spinlock_t                lock;
        unsigned int             count;
        struct list_head        wait_list;
};

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

1.信号量的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),所以从实用角度,彻底忘了它吧。

(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的运行队列中摘下)

(3)试图去获得一个信号量,如果没有获得,函数立刻返回1而不会让当前进程进入睡眠状态。

 

2.信号量的V操作 (众多的down函数,但是仅需要一个up函数)

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链表中的第一个有效节点,并唤醒睡眠在该节点上的进程。

 

关于Linux环境下信号量其他API 详见LKD和ULD



}


linux使用信号量的具体的一个例子:

http://blog.csdn.net/wangchaoxjtuse/article/details/6025385

信号量是一种睡眠锁。它是实现同步操作,防止竟态的方式之一。任何进程在对共享数据进行读写操作之前必须获得用来保护共享数据的信号量,否则不能供访问权限,信号量会把这个访问进程放进一个等待队列中(这个等待队列是在信号量初始化过程中被初始化的),然后让其进入睡眠状态。这是处理器重新调度,去执行其他进程的操作。保护共享数据的信号量被释放,被这个信号量放进等待队列的进程会被激活,获得该信号量,然后对共享数据进行访问。
下面的代码演示了在设备驱动程序的读写操作中,信号量的使用.

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

#include <linux/wait.h>
#include <asm/semaphore.h>   
MODULE_LICENSE("GPL");  
  
#define MAJOR_NUM 254  
  
static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);  
static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);  
  
struct file_operations globalvar_fops =  
{  
  read: globalvar_read,
  write: globalvar_write, };
static int global_var = 0; static struct semaphore sem; static wait_queue_head_t outq; static int flag = 0;
static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)  
{  
   //等待数据可获得  
  if (wait_event_interruptible(outq, flag != 0)) //wait_event_interruptible把进程状态设为TASK_INTERRUPTIBLE,nonexclusive
{
    return - ERESTARTSYS;   
   }
  if (down_interruptible(&sem)) //获得信号量,相当于获得锁
   {
    return - ERESTARTSYS;
   }
   flag = 0;
 if (copy_to_user(buf, &global_var, sizeof(int)))
  {
    up(&sem); //如果读取失败,则释放信号量,相当于释放锁
    return - EFAULT;
  }
  up(&sem); //若读取成功也释放信号量 return sizeof(int);
}

//写设备
static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len,loff_t *off)
{
  
  if (down_interruptible(&sem)) //获得信号量
  {
     return - ERESTARTSYS;
  }
  if (copy_from_user(&global_var, buf, sizeof(int)))
  {
    up(&sem); //写失败后释放锁 
    return - EFAULT;
  }
  up(&sem); //写成功后,也释放信号量
  flag = 1;
  wake_up_interruptible(&outq); //唤醒等待进程,通知已经有数据可以读取  
  return sizeof(int);
}

static int __init globalvar_init(void) {   int ret;   ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);   if (ret)   {     printk("globalvar register failure");   }   else   {     printk("globalvar register success");     init_MUTEX(&sem); //初始化一个互斥锁,即它把信号量sem的值设置为1     init_waitqueue_head(&outq); //初始化等待队列outq   }   return ret;
}
static void __exit globalvar_exit(void) {   int ret;   ret = unregister_chrdev(MAJOR_NUM, "globalvar");   if (ret)   {     printk("globalvar unregister failure");   }   else   {     printk("globalvar unregister success");   } }

module_init(globalvar_init);
module_exit(globalvar_exit);

 




 

}}}}}}}}}}}}}}}}}}}}}}





 

posted on 2016-11-15 21:20  Red_Point  阅读(1438)  评论(0)    收藏  举报

导航