信号量概念
概念
两个特点:
- 信号量会引起调用者的休眠
- 在持有锁的时间比较长的情况下,使用自旋锁是不合适的;得用信号量。
自旋锁是通过“原地等待”的方式来处理并发与竞争的,所以被保护的临界区不能太长,以免造成对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 |
信号量的注意事项
- 信号量的值不能小于0
- 访问共享资源时,信号量执行“减一”操作,访问完毕后再执行“加一”操作
- 当信号量的值为0时,想访问共享资源的线程必须等待,知道信号量大于0时,等待的线程才可以访问
- 因为信号量会引起休眠,所以中断里面不能用信号量
- 共享资源持有时间比较长,一般用信号量而不用自旋锁
- 在同时使用信号量和自旋锁的时候,要先获取信号量,再使用自旋锁。因为信号量会导致睡眠。
写代码
简单实现:信号量实现驱动只能被一个程序调用
核心代码
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