上一篇文章讲了利用原子变量防止设备竞态的发生,这一节将如何利用信号量达到同样的效果。
学过ucosIII操作系统的朋友会对信号量这个概念比较熟悉。程序中请求信号量,如果得到信号量则信号量的值-1,并且程序继续运行。若请求不到信号量则会阻塞或者进行其他操作。
当程序结束前要释放信号量,则信号量的值+1,这样别的程序才能得到信号量。
信号量的相关内核代码在<asm/semaphore.h>中。
(1)初始化信号量
信号量的相关结构体为struct semaphore。为了初始化信号量有不同的办法,其中之一就是直接创建:
int sema_init(struct semaphore *sem, int val);
其中val是信号量的初值。
但是因为信号量经常被用于互斥模式,即它的值通常是取1的,所以内核提供了一种更加方便的定义方式
DECLARE_MUTEX(name); //定义信号量name,初始值为1
DECLARE_MUTEX_LOCKED(name); //定义信号量name,初始值为0
其中DECLARE_MUTEX的定义为
#define DECLARE_MUTEX(name) \
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
#define __SEMAPHORE_INITIALIZER(name, n) \
{ \
.lock = __SPIN_LOCK_UNLOCKED((name).lock), \
.count = n, \
.wait_list = LIST_HEAD_INIT((name).wait_list), \
}
(2)请求信号量
请求信号量的相关函数为
void down(struct semaphore * sem); // 获取不到就进入不被中断的休眠状态(down函数中睡眠)
int down_interruptible(struct semaphore * sem); //获取不到就进入可被中断的休眠状态(down函数中睡眠)
int down_trylock(struct semaphore * sem); //试图获取信号量,获取不到则立刻返回正数
其中前两个函数如果请求不到信号量会阻塞,因此考虑是否使用阻塞机制中分情况调用不同的函数。
一般来说应该在平时编程时应该使用down_interruptible,但是使用时要格外小心,如果操作被中断,则会返回非零值,且调用者不会获得信号量。因此对down_interruptible的正确使用需要始终检查返回值,从而做出不同的响应。
一旦进程成功down了信号量之后就受到了信号量赋予的临界段的保护。
(3)释放信号量
void up(struct semaphore *sem);
在进程结束前要释放信号量。
阻塞机制
当进程无法得到调用的信号量的时候,分为阻塞和非阻塞两种情况。如果阻塞,则进程一直阻塞,此时调用的是down()函数;如果非阻塞,则此时进程结束,调用的是down_trylock()函数。
至于是否采用阻塞,是在open打开文件时通过标志量O_NOBLOCK设定的
fd = open("/dev/buttons ", O_RDWR | O_NONBLOCK);
综上所诉,开始编写程序
首先在开头定义信号量
DECLARE_MUTEX(sig);
之后在驱动的open()函数内,通过flags判断是否阻塞,然后调用不同的信号函数
if(file -> f_flags & O_NONBLOCK)) // 非阻塞
{
if(down_trylock(&sig))
return -1;
}
else
down(); //阻塞
同样在read函数内也需要进行判断
if(file -> f_flags & O_NOBLOCK) //非阻塞
{
if(!ev_press)
return -1;
}
else //阻塞
{
wait_event_interruptible(button_waitq, ev_press);
}
完整的驱动程序:
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <mach/gpio.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/semaphore.h>
//#include <mach/regs-gpio.h>
//#include <mach/gpio-bank-n.h>
#include <plat/gpio-cfg.h>
DECLARE_MUTEX(sig); //初始化信号量
struct class *key_class;
struct class_device *key_class_device;
volatile unsigned long *gpmcon = NULL;
volatile unsigned long *gpmdat = NULL;
struct cdev cdev;
dev_t dev; //设备号
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
static volatile int ev_press = 0;
unsigned int key_val;
struct pin_desc
{
unsigned pin;
unsigned key_val;
};
struct pin_desc pins_desc[4] = {
{S3C64XX_GPN(0), 0x01},
{S3C64XX_GPN(1), 0x02},
{S3C64XX_GPN(2), 0x03},
{S3C64XX_GPN(3), 0x04},
};
static irqreturn_t button_irq(int irq, void *dev)
{
unsigned int pval;
struct pin_desc *pdesc = (struct pin_desc*)dev;
pval = gpio_get_value(pdesc -> pin);
if(pval)
{
key_val = 0x80 | pdesc -> key_val;
}
else
key_val = pdesc -> key_val;
ev_press = 1;
wake_up_interruptible(&button_waitq);
printk("press check\n");
return IRQ_RETVAL(IRQ_HANDLED);
}
unsigned int key_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait);//将button_waitq放入队列中。到这里还没有进入休眠
if (ev_press)
mask |= POLLIN | POLLWRNORM;//mask是返回应用程序poll的返回值,mask一定要和应用程序中的events相对应,不然还会进行超时
return mask;
}
int key_open(struct inode *inode,struct file *file)
{
if(file->f_flags & O_NONBLOCK)
{
if(down_trylock(&sig)) //如果获取不到信号量则返回正值
return -EBUSY;
}
else
down(&sig);
request_irq(IRQ_EINT(0), button_irq, IRQF_SAMPLE_RANDOM | IRQ_TYPE_EDGE_BOTH |IRQF_SHARED, "s0",&pins_desc[0]); /* 这里的irq指的不是中断源 */
request_irq(IRQ_EINT(1), button_irq, IRQF_SAMPLE_RANDOM | IRQ_TYPE_EDGE_BOTH |IRQF_SHARED, "s1",&pins_desc[1]);
request_irq(IRQ_EINT(2), button_irq, IRQF_SAMPLE_RANDOM | IRQ_TYPE_EDGE_BOTH |IRQF_SHARED, "s2",&pins_desc[2]);
request_irq(IRQ_EINT(3), button_irq, IRQF_SAMPLE_RANDOM | IRQ_TYPE_EDGE_BOTH |IRQF_SHARED, "s3",&pins_desc[3]);
/*gpmcon = (volatile unsigned long *)ioremap(0x7f008820,4);
writel(0x1111,gpmcon);
gpmdat = (volatile unsigned long *)ioremap(0x7f008824,4); /* 指针加1是以数据类型相加的*/
printk("open!\n");
return 0;
}
ssize_t key_release(struct inode *inode,struct file *file)
{
up(&sig);
free_irq(IRQ_EINT(0),&pins_desc[0]);
free_irq(IRQ_EINT(1),&pins_desc[1]);
free_irq(IRQ_EINT(2),&pins_desc[2]);
free_irq(IRQ_EINT(3),&pins_desc[3]);
return 0;
}
ssize_t key_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
{
if(count != 1)
return -EINVAL;
printk("$$$$$$$$$$key_read$$$$$$$$$\n");
if(file->f_flags & O_NONBLOCK)
{
if(ev_press != 1)
{
return -1;
}
}
else
{
wait_event_interruptible(button_waitq, ev_press);
}
copy_to_user(buf,&key_val,1);
ev_press = 0;
return count;
}
ssize_t key_write(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
char mbuf[10];
printk("write!\n");
copy_from_user(mbuf,buf,count);
switch(mbuf[0])
{
case 1:
writel(0x01, gpmdat);
break;
case 0:
writel(0x0F,gpmdat);
break;
default:
break;
}
return count;
}
struct file_operations key = {
.owner = THIS_MODULE,
.open = key_open,
.read = key_read,
.write = key_write,
.release = key_release,
.poll = key_poll,
};
int mykey_init(void)
{
cdev_init(&cdev,&key);
alloc_chrdev_region(&dev,0,1,"int_key_chrdev");
cdev_add(&cdev,dev,1);
key_class = class_create(THIS_MODULE,"int_key");
/*if(IS_ERR(led_class))
return PTR_ERR(led_class);*/
key_class_device = device_create(key_class,NULL,dev,NULL,"int_key1");
/* if(unlikely(IS_ERR(led_class)))
return PTR_ERR(led_class); */
return 0;
}
void key_exit(void)
{
cdev_del(&cdev);
unregister_chrdev_region(dev,1);
device_unregister(key_class_device);
class_destroy(key_class);
}
MODULE_LICENSE("GPL");
module_init(mykey_init);
module_exit(key_exit);
测试程序
int main(int argc,char **argv)
{
int oflag;
unsigned int val=0;
fd=open("/dev/int_key1",O_RDWR ); //阻塞操作,若阻塞则加上O_NOBLOCK
if(fd<0)
{printf("can't open, fd=%d\n",fd);
return -1;}
else
{
printf("can open,PID=%d\n",getpid()); //打开成功,打印pid进程号
}
while(1)
{
val=read( fd, &ret, 1); //读取驱动层数据
printf("key_vale=0X%x,retrun=%d\r\n",ret,val);
}
return 0;
}