并发与竞争(四)信号量

信号量概念

概念

两个特点:

  • 信号量会引起调用者的休眠
  • 在持有锁的时间比较长的情况下,使用自旋锁是不合适的;得用信号量。

 自旋锁是通过“原地等待”的方式来处理并发与竞争的,所以被保护的临界区不能太长,以免造成对CPU资源的浪费。但是有些情况我们必不可免的要长时间对一些资源进行保护。这时候就可以使用信号量了。

什么是信号量呢?
 举个例子,现在有一个电话亭,里面只有一个公共电话。某天A去打电话,恰好过了一会B也来了要打电话。但是此时A在打电话,所以B就只能等待A打完电话才可以打。如果是自旋锁的,B就要一直等待着A打完。但是A的事情很重要,需要打很长时间电话。这时候自旋锁就不合适了。那A是不是就可以告诉B,你先去休息一会儿,等我打完了告诉你,你再来打电话。这个就是信号量。信号量会引起调用者睡眠,所以信号量也叫睡眠锁。

信号的工作方式

 信号量的本质是一个全局变量。信号量的值可以根据实际情况来自行设置(取值范围大于等于0),当有线程来访问资源时,信号量执行“减一”操作,访问完以后,再执行“加一”操作。

 比如一个屋子有5把钥匙,这5把钥匙就是信号量的值,也就是说5个人可以进到这个屋子(允许多个线程同时访问共享资源)。当某个人想进屋子的时候,就要先拿一把钥匙,此时信号量的值“减一”。直到这5把钥匙全部拿走以后,这个屋子别人就进不去了。如果有人从屋子里面出来,还回去一把钥匙,此时信号量的值“加一”那就又可以进去一个人了。

信号量的描述

Linux内核使用结构体semaphore来表示信号量,定义在semaphore.h文件中,如下所示:

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

信号量的API函数

函数 描述
DEFINE_SEMAPHORE(name) 定义信号量,并设置信号量的值为1
void sema_init(struct semaphore *sem, int val) 初始化信号量sem并设置信号的值为val
void down(struct semaphore *sem) 获取信号量,不能被信号打断,如Ctrl+C
int down_interruptible(struct semaphore *sem) 获取信号量,能被信号打断,如Ctrl+C
void up(struct semaphore *sem) 释放信号量
int down_trylock(struct semaphore *sem) 尝试获取信号量,如果获取到信号量就返回0,获取不到就返回非0

信号量的注意事项

  1. 信号量的值不能小于0
  2. 访问共享资源时,信号量执行“减一”操作,访问完毕后再执行“加一”操作
  3. 当信号量的值为0时,想访问共享资源的线程必须等待,知道信号量大于0时,等待的线程才可以访问
  4. 因为信号量会引起休眠,所以中断里面不能用信号量
  5. 共享资源持有时间比较长,一般用信号量而不用自旋锁
  6. 在同时使用信号量和自旋锁的时候,要先获取信号量,再使用自旋锁。因为信号量会导致睡眠。

写代码

简单实现:信号量实现驱动只能被一个程序调用

核心代码

static struct semaphore semlock;

static void module_init(void)
{
    
    
	...
	sema_init(&semlock, 1);//设置值为1,就是说只能允许一个程序调用:一把钥匙
	...
}

int misc_open(struct inode *inode,struct file *file)
{
    
    
#if 0
	down(&semlock);
#endif

	if(down_interruptible(&semlock)) // 可以被中断打断的信号量,减一
	{
    
    
		return -EINTR;
	}

	printk("hello misc_open\n ");
	return 0;
}

int misc_release(struct inode *inode,struct file *file)
{
    
    
	up(&semlock); // 信号量加一,释放掉后就可以被其他程序打开了
	
	printk("hello misc_relaease bye bye \n ");
	return 0;
}

完成代码

模块代码:

#include <linux/init.h>         //初始化头文件
#include <linux/module.h>       //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h>   //包含了miscdevice结构的定义及相关的操作函数。
#include <linux/fs.h>           //文件系统头文件,定义文件表结构(file,buffer_head,m_inode等)
#include <linux/uaccess.h>      //包含了copy_to_user、copy_from_user等内核访问用户进程内存地址的函数定义。
#include <linux/io.h>           //包含了ioremap、iowrite等内核访问IO内存等函数的定义。
#include <linux/kernel.h>		//驱动要写入内核,与内核相关的头文件

#include <linux/atomic.h>
#include <asm/atomic.h> 

#define GPIO_DR 0xfdd60000     //LED物理地址,通过查看原理图得知
unsigned int *vir_gpio_dr;     //存放映射完的虚拟地址的首地址

static struct semaphore semlock;

int misc_open(struct inode *inode,struct file *file)
{
    
    
#if 0
	down(&semlock);
#endif

	if(down_interruptible(&semlock)) // 可以被中断打断的信号量,减一
	{
    
    
		return -EINTR;
	}

	printk("hello misc_open\n ");
	return 0;
}

int misc_release(struct inode *inode,struct file *file)
{
    
    
	up(&semlock); // 信号量加一,释放掉后就可以被其他程序打开了
	
	printk("hello misc_relaease bye bye \n ");
	return 0;
}

ssize_t misc_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{
    
    
	printk("misc_read\n ");
	return 0;
}

ssize_t misc_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{
    
    	
    /*应用程序传入数据到内核空间,然后控制蜂鸣器的逻辑,在此添加*/
	// kbuf保存的是从应用层读取到的数据
    char kbuf[64] = {
    
    0};
    // copy_from_user 从应用层传递数据给内核层
	if(copy_from_user(kbuf,ubuf,size)!= 0) 
	{
    
    
        // copy_from_user 传递失败打印
		printk("copy_from_user error \n ");
		return -1;
	}
    //打印传递进内核的数据
    //printk("kbuf is %d\n ",kbuf[0]); 
	if(kbuf[0]==1) //传入数据为1 ,LED亮
	{
    
    
		*vir_gpio_dr = 0x80008000; 
	}
	else if(kbuf[0]==0) //传入数据为0,LED灭
		*vir_gpio_dr = 0x80000000;
	return 0;
}

//文件操作集
struct file_operations misc_fops={
    
    
	.owner = THIS_MODULE,
	.open = misc_open,
	.release = misc_release,
	.read = misc_read,
	.write = misc_write,
};
//miscdevice结构体
struct miscdevice  misc_dev = {
    
    
	.minor = MISC_DYNAMIC_MINOR,
	.name = "hello_misc",
	.fops = &misc_fops,
};
static int misc_init(void)
{
    
    
	int ret;

	sema_init(&semlock, 1);//设置值为1,就是说只能允许一个程序调用:一把钥匙

    //注册杂项设备
	ret = misc_register(&misc_dev);
	if(ret<0)
	{
    
    
		printk("misc registe is error \n");
	}
	printk("misc registe is succeed \n");
    //将物理地址转化为虚拟地址
	vir_gpio_dr = ioremap(GPIO_DR,4);
	if(vir_gpio_dr == NULL)
	{
    
    
	printk("GPIO_DR ioremap is error \n");
	return EBUSY;
	}
	printk("GPIO_DR ioremap is ok \n");	
	return 0;
}
static void misc_exit(void){
    
    
    //卸载杂项设备
	misc_deregister(&misc_dev);
	iounmap(vir_gpio_dr);
	printk(" misc gooodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");


测试APP

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc,char *argv[])
{
    
    
	int fd;
	char buf[64] = {
    
    0};//定义buf缓存
	char val[1];
	char cnt = 0;
	//打开设备节点

	fd = open("/dev/hello_misc",O_RDWR);
	if(fd < 0)
	{
    
    
		//打开设备节点失败
		perror("open error \n"); 
		return fd;
	}
	printf("open ok!\n");

	sleep(10);

	close(fd);
	printf("close ok!\n");
	
	return 0;
}

测试:

[root@RK356X:/opt]# insmod led.ko
[24800.610524] misc registe is succeed
[root@RK356X:/opt]# [24800.611512] GPIO_DR ioremap is ok

[root@RK356X:/opt]#
[root@RK356X:/opt]# cp app.armelf app2.armelf
[root@RK356X:/opt]# ./app.armelf &
[root@RK356X:/opt]# open ok!

[root@RK356X:/opt]# ./app2.armelf
close ok!
[24834.366689] hello misc_open
[24834.366689]
[24844.open ok!
367459] hello misc_relaease bye bye
[24844.367459]
l248c4l4o.s3e6 7o5k7!2] he
 lo misc_open
[24844.367572]
[1]+  Done                       ./app.armelf

效果是没有问题:

  • app后台运行,打开设备节点成功
  • app2前台运行,无法打开设备节点,然后就进入睡眠,直到app释放,app2才打开成功。

下面就是多复制了一个,然后app,app2,app3就排着队打开设备节点了。

[root@RK356X:/opt]# cp app.armelf app3.armelf
[root@RK356X:/opt]# ./app.armelf &
[root@RK356X:/opt]# [24854.369391] hello misc_relaease bye bye
[24854.369391]
open ok!
[root@RK356X:/opt]# ./app2.armelf &
[root@RK356X:/opt]# ./app.aclose ok!
[25146.299718] hello misc_open
[25146.299718]
[25156.3011open ok!
86] hello misc_relaease bye bye
[root@RK356X:/opt]# ./app3.armelf

6lose ok![2515
 .301322] hello misc_open
[25156.301322]
[25166.303open ok!
492] hello misc_relaease bye bye
[25166.303492]
5lose ok![2
 166.303620] hello misc_open
[25166.303620]
[2]+  Done                       ./app2.armelf
[1]+  Done                       ./app.armelf

猜你喜欢

转载自blog.csdn.net/qq_28877125/article/details/128223063