Linux 设备驱动 ====> 并发控制 --- 信号量与互斥体

原创 2012年03月28日 21:37:25

信号量

信号量的使用

信号量(semaphore)是用于保护临界区的一种常用方法,他的用法和自旋锁类似,但是,与自旋锁不同的是,当获取不到信号量时,进程不会原地打转,而是进入休眠等状态。

Linux中信号量的操作主要有

1.定义信号量

struct semaphore sem;

2.初始化信号量

void sema_init(struct semaphore *sem, int val);

该函数初始化信号量,并设置信号量sem的值为val,尽管信号量可以被初始化为大于1的值而成为一个计数信号量,但是通常不建议这么去做。

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

这个宏用于初始化一个互斥的信号量,把sem的值设置为1.

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

初始化一个信号量,值设置为0;

也可以使用如下快捷方式初始化

DELCEAR_MUTEX(name)

DELCEAR_MUTEX_LOCKED(name)

3.获得信号量

void down(struct semaphore *sem);

该函数用于获得信号量sem,它会导致睡眠,因此不能再中断上下文中使用;

int down_interruptible(struct semaphore *sem);

该函数与down类似,因为down进入睡眠状态的进程不能被信号打断,但因为down_interruptible而进入睡眠状态的进程能被信号打断,信号也会导致该函数返回,返回非0;

int down_trylock(struct semaphone *sem);

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

在使用down_interruptible()获得信号量时,对返回值一般会进行检查,如果非0,通常立即返回 -ERESTARTSYS

if(down_interruptible(&sem))
	return -ERESTARTSYS;

4.释放信号

void up(struct semaphore *sem);

该函数释放信号量sem,唤醒等待者。

用法

DECLARE_MUTEX(mount_sem);
down(&mount_sem);	//get semaphore
...
critical section
...
up(&mount_sem);		//release semaphore

举例使用信号量实现设备只能被一个进程打开

static DECLARE_MUTEX(xxx_lock);	//declare mutex lock

static int xxx_open(struct ...)
{
	...
	if(down_trylock(&xxx_lock))
		return -EBUSY;
	...
	return 0;
}

static int xxx_release(struct ...)
{
	up(&xxx_lock);
	return 0;
}

信号量用于同步

如果信号量初始化为0, 则它可以用于同步,同步意味着一个执行单元的继续执行需要另外一个执行单元完成某事,保证执行的先后顺序。

下面一图说明此事

                                    执行单元A                                                                     执行单元B

                                    struct semphore sem;                                               

                                    init_MUTEX_LOCKED(&sem);                                 代码区域c

                                     代码区域a                         

                                     down(&sem);              <---------激活------                   up(&sem)

                                     代码区域b


在执行单元A中,因为互斥量被设置为0,所以在down的时候试图获得信号量,因为得不到而进入休眠,所以代码区域b一直无法执行到,当执行单元B 发生,执行up来释放互斥量,使得执行单元A被唤醒,从而继续往下执行。


完成量用于同步

完成量的用法

1. 定义完成量

struct completion my_compeltion;

2. 初始化completion

init_completion(&my_completion);

or

DECLARE_COMPLETION(my_completion);

3. 等待完成量

下面函数用于等待一个完成量被唤醒

void wait_for_completion(struct completion *c);

4. 唤醒完成量

void completion(struct completion *c);

void completion_all(struct completion *c);

前者只唤醒一个等待的执行单元,后者释放所有等待同意完成量的执行单元。

完成量的同步功能

                                    执行单元A                                                                                           执行单元B

                                    struct completion com;                                               

                                    init_completion(&com);                                                                    代码区域c

                                     代码区域a                         

                                     wait_for_completion(&com);           <---------激活------                 completion(&com);

                                     代码区域b


下面举例说明完成量用于同步。

我们还是沿用我们之前的globalmem字符驱动,我们这么来设计我们的代码,在read回调函数最开始的时候,我们等待完成量被激活,在write回调函数的最后我们释放完成量,这样的话,我们先cat globalmem,会等待完成量,只有当我们write之后,完成量才会被释放,这样才会执行读的动作。

struct completion com;

static ssize_t globalmem_read(struct file *filp, char __user *buf,size_t size,
		loff_t *ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct globalmem_dev *dev = filp->private_data;	//get global data pointer
	
	//wait for write then read go...
	wait_for_completion(&com);
	
	if(p>=GLOBALMEM_SIZE)
		return 0;
	if(count > GLOBALMEM_SIZE-p)
		count = GLOBALMEM_SIZE-p;

	if(copy_to_user(buf, (void *)(dev->mem + p), count)) 
		ret = -EFAULT;
	else {
		*ppos += count;
		ret = count;
		
		printk(KERN_INFO "read %u bytes(s) from %lu\n",count,p);
	}

	return ret;
}

static ssize_t globalmem_write(struct file *filp, const char __user *buf,
		size_t size, loff_t *ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct globalmem_dev *dev = filp->private_data;	//get global data pointer

	if(p >= GLOBALMEM_SIZE)
		return 0;
	if(count > GLOBALMEM_SIZE - p)
		count = GLOBALMEM_SIZE - p;

	if(copy_from_user(dev->mem+p, buf, count)) {
		printk(KERN_INFO "copy from user error!!\n");
		ret = -EFAULT;
	}
	else {
		*ppos += count;
		ret = count;
		printk(KERN_INFO "written %u bytes(s) from %lu\n",count,p);
	}
	//after write then active completion begin to read ...
	complete(&com);

	return ret;
}

int globalmem_init(void)
{
	int result;
//	spin_lock_init(&lock);
	init_completion(&com);

重新编译模块,在加载我们的globalmem模块,然后进行测试

我们先cat

jay@jay:/dev$ 
jay@jay:/dev$ cat globalmem 

发现光标停在那,不动了,因为在等待完成量,然后我们另开一个终端,去echo

jay@jay:/dev$ echo "456" > globalmem

当我们往里面写数据之后发现之前的终端有反应了

jay@jay:/dev$ cat globalmem 
456

这样就达到了我们的目的,这里我只是举了一个很简单的例子,个人感觉这个方法设计代码有时候蛮有用处的,特别是当2个驱动之间有一些数据的同步,或者一些设置的同步时,可以利用完成量来设计代码达到同步的效果。


自旋锁   VS   信号量

自旋锁和信号量都是解决互斥问题的基本手段,面对特定的情况,我们要加以选择。

信号量时进程级的,用于多个进程之间对资源的互斥。如果竞争失败,会发生进程上下文切换,因为进程上下文切换的开销比较大,因此,只有当进程占用资源时间较长时,选用信号量才是较好的选择。

所要保护的临界资源访问时间比较短时,用自旋锁是非常方便的,它不会引起进程睡眠而导致上下文切换。

总结:

1. 如果访问临界资源的时间较长,则选用信号量,否则选用自旋锁。

2. 信号量所保护的临界资源区可包含可能引起阻塞的代码,而自旋锁则绝对要避免这样的代码,阻塞意味着需要进程上下文切换,如果进程被切换出去,这个时候如果另外一个进程想获得自旋锁的话,会引起死锁。

3. 信号量存在于进程上下文,因此,如果被保护的资源需要在中断或者软终端情况下使用,则只能选择自旋锁。


互斥体

虽然信号量已经可以实现互斥的功能,而且有一些宏定义可以使用,但在Linux 中还是有一套标准的mutex机制。

定义互斥体并初始化

struct mutex my_mutex;

mutex_init(&my_mutex);

获取互斥体

void inline __sched mutex_lock(struct mutex *lock);

int __sched mutex_lock_interruptible(struct mutex *lock);

int __sched mutex_trylock(struct mutex *lock);

这三个函数与spinlock的几个类似的函数用法相同。

释放互斥体

void __sched mutex_unlock(struct mutex *lock);

mutex的使用方法和信号量用于互斥的场合完全一样

struct mutex my_mutex;      //declare mutex
mutex_init(&mu_mutex);      //init mutex

mutex_lock(&my_mutex);
......           //临界资源
mutex_unlock(&my_mutex);

修改我们的globalmem字符驱动增加并发控制

定义初始化信号量

struct globalmem_dev {
	struct miscdevice mdev;
	unsigned char mem[GLOBALMEM_SIZE];
	struct semaphore sem;
};

int globalmem_init(void)
{
	int result;
	globalmem_devp = kmalloc(sizeof(struct globalmem_dev) ,GFP_KERNEL);
	if(!globalmem_devp) {
		result = -ENOMEM;
		goto fail_malloc;
	}
	memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
	
	globalmem_devp->mdev = mdev_struct;

	result = misc_register(&(globalmem_devp->mdev));
	if(result<0)
		return result;
	else {
		init_MUTEX(&globalmem_devp->sem);
		return 0;
	}

fail_malloc:
	return result;
}

在read/write/ioctl 函数中添加信号量

static int globalmem_ioctl(struct inode *inode, struct file *filp, 
		unsigned int cmd, unsigned long arg)
{
	struct globalmem_dev *dev = filp->private_data;	//get global data pointer

	switch(cmd) {
		case	MEM_CLEAR:
			if(down_interruptible(&dev->sem))
				return -ERESTARTSYS;
			memset(dev->mem, 0, GLOBALMEM_SIZE);
			up(&dev->sem);
			printk(KERN_INFO "clear globalmem!\n");
			break;

		default:
			return -EINVAL;
	}

	return 0;
}

static ssize_t globalmem_read(struct file *filp, char __user *buf,size_t size,
		loff_t *ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct globalmem_dev *dev = filp->private_data;	//get global data pointer
	
	if(p>=GLOBALMEM_SIZE)
		return 0;
	if(count > GLOBALMEM_SIZE-p)
		count = GLOBALMEM_SIZE-p;

	if(down_interruptible(&dev->sem))
		return -ERESTARTSYS;

	if(copy_to_user(buf, (void *)(dev->mem + p), count)) 
		ret = -EFAULT;
	else {
		*ppos += count;
		ret = count;
		
		printk(KERN_INFO "read %u bytes(s) from %lu\n",count,p);
	}

	up(&dev->sem);

	return ret;
}

static ssize_t globalmem_write(struct file *filp, const char __user *buf,
		size_t size, loff_t *ppos)
{
	unsigned long p = *ppos;
	unsigned int count = size;
	int ret = 0;
	struct globalmem_dev *dev = filp->private_data;	//get global data pointer

	if(p >= GLOBALMEM_SIZE)
		return 0;
	if(count > GLOBALMEM_SIZE - p)
		count = GLOBALMEM_SIZE - p;

	if(down_interruptible(&dev->sem))
		return -ERESTARTSYS;

	if(copy_from_user(dev->mem+p, buf, count)) {
		printk(KERN_INFO "copy from user error!!\n");
		ret = -EFAULT;
	}
	else {
		*ppos += count;
		ret = count;
		printk(KERN_INFO "written %u bytes(s) from %lu\n",count,p);
	}

	up(&dev->sem);

	return ret;
}

在读写之前使用down_interruptible检查是否可以获得信号量,若不能直接返回,在读写完成后使用up来释放信号量。

应用程序的测试与之前一样,自行测试。

信号量与互斥体就介绍到这,结束。

=========================================================

mail & MSN :zhangjie201412@live.com

=========================================================


linux驱动之 信号量 自旋锁 互斥体

原子操作 原子操作就是单位操作,也就是说操作过程不能被中断 下面代码中每条语句看起来是原子操作,其实不是原子操作; int main(0 { int i=2;//两天汇编语句组成 i=i+3;//三条...
  • u012590688
  • u012590688
  • 2015年05月28日 10:40
  • 631

信号量、互斥体和自旋锁的区别

信号量/互斥体允许进程睡眠属于睡眠锁,自旋锁则不允许调用者睡眠,而是让其循环等待,所以有以下区别应用 1)、信号量和读写信号量适合于保持时间较长的情况,它们会导致调用者睡眠,因而自旋锁适合...
  • zzhere2007
  • zzhere2007
  • 2013年06月17日 09:04
  • 4946

Linux系统编程——进程同步与互斥:System V 信号量

信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。 编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则...
  • tennysonsky
  • tennysonsky
  • 2015年08月20日 19:22
  • 1655

Linux 内核中的并发--信号量与互斥体

信号量(down操作->临界区->up操作) 信号量的使用方式和自旋锁类似,进程只有得到信号量才能执行临界区代码 但与自旋锁不同的是,当进程获取不到信号量时并不是原地打转而是睡眠等待 中断服...
  • xyfabcde
  • xyfabcde
  • 2017年06月01日 18:16
  • 438

Linux下信号量实现进程同步、互斥(生产者消费者问题)

linux的进程同步互斥实现生产者和消费者
  • Vista_feat
  • Vista_feat
  • 2016年10月27日 15:59
  • 1472

linux kernel 信号量、互斥锁、自旋锁

1.信号量1.1 概念信号量又称为信号灯(semaphore),本质上信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。一般说来,为了获得共享资源,进程需要执行下列操作:    (...
  • u012719256
  • u012719256
  • 2016年09月26日 14:42
  • 1970

FreeRTOS学习笔记——互斥型信号量

来自:http://blog.csdn.net/xukai871105/article/details/43456985 0.前言     在嵌入式操作系统中互斥型信号量是任务间资源保...
  • aidem_brown
  • aidem_brown
  • 2017年03月30日 19:54
  • 392

临界区,互斥量,信号量,事件的区别(线程同步)

临界区,互斥量,信号量,事件的区别(线程同步) 四种进程或线程同步互斥的控制方法 1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。  2、互斥量:为协调共...
  • xringm
  • xringm
  • 2016年03月24日 10:37
  • 465

linux多线程下互斥量与信号量的区别

互斥量(Mutex)   互斥量表现互斥现象的数据结构,也被当作二元信号灯。一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同...
  • ycc541
  • ycc541
  • 2015年05月08日 16:23
  • 1844

Linux基础:信号量和互斥锁区别与联系

信号量与互斥锁 信号量与普通整型变量的区别:①信号量(semaphore)是非负整型变量,除了初始化之外,它只能通过两个标准原子操作:wait(semap) , signal(semap) ; 来进...
  • shaohua_lv
  • shaohua_lv
  • 2017年04月20日 12:32
  • 316
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux 设备驱动 ====> 并发控制 --- 信号量与互斥体
举报原因:
原因补充:

(最多只允许输入30个字)