按键驱动之同步互斥、阻塞机制

学习目的:

  • 了解同步互斥、阻塞机制
  • 在按键驱动程序中,使用原子操作、信号量实现互斥机制

1、同步互斥、阻塞机制引入原因

1.1 同步互斥机制

要理解同步互斥先要了解关于资源的概念,一般同步互斥机制的引入,大多为解决多个进程同时对同一资源进行访问而引发的读写异常问题。举例如下图所示:

按键驱动之同步互斥、阻塞机制

如上图情况,同一进程中有两个线程,每个线程执行一次时,均对y变量进行加一操作,用来统计两个线程总共运行的次数。很多情况下,这种方式可能很难达到我们预想的结果,一般计算的y的值总比两个线程实际运行次数要少。我们知道执行y++操作在cpu内部运算会分为三个步骤,首先从内存中读取y值到寄存器,然后执行加法操作,此时操作结果存放在寄存器中,最后将计算后的结果从寄存器写入到y变量内存。

假设y值为10时,线程1执行,读取y值到寄存器,恰巧此时线程1分配时间片用完,线程2就绪运行,线程2继续从内存中读取y,并执行自加操作,将写入结果内存,此时y值为11。线程1重新获得时间片开始运行,将寄存器中保存y值10自加后写入y变量内存。线程1线程2同时执行1次,y变量却是11,这个时候就造成了程序之间的运行错误,这种问题在共享资源中很容易发生。

同步互斥机制就是为解决上述问题,如果两个线程(进程)想同时访问同一块内存,竞争后只有一个线程能获取访问权限。如果A进程对共享资源有权限,B进程对共享资源就没有权限,这样便解决了上面出现问题。

1.2 阻塞机制

应用程序调用open打开I/O设备时,可以使用flags标志设置是否阻塞。阻塞操作是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。非阻塞操作进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。

2、原子操作、信号量使用方法

2.1 原子操作

原子操作即多步操作组成一个操作,如果该操作不能完整的执行,要么执行完所有步骤,要么一步也不执行,不同能只执行所有步骤中的一个子集。例如,上面举例子中y++语句的执行过程如果将y变量定义为原子变量,y++步骤换成原子操作的自加,就不会出现上面问题了。

常用原子操作函数举例:

atomic_t v = ATOMIC_INIT(0);------->①     
atomic_read(atomic_t *v); ------->②      
void atomic_inc(atomic_t *v);------->③    
void atomic_dec(atomic_t *v);------->④    
int atomic_dec_and_test(atomic_t *v);------->⑤ 

① 原子变量的定义,定义原子变量v并初始化为0

② 获取当前原子变量的值

③ 原子变量的值增加1

④ 原子变量的值减少1

⑤ 自减操作后测试其是否为0,为0则返回true,否则返回false

2.2 信号量

信号量(semaphore)是用于保护临界区的一种常用方法,只有得到信号量的进程才能执行临界区代码。当获取不到信号量时,进程进入休眠等待状态。

常用信号量的操作函数举例:

定义信号量
struct semaphore sem;
初始化信号量
void sema_init (struct semaphore *sem, int val);
void init_MUTEX(struct semaphore *sem);//初始化为0

获得信号量
void down(struct semaphore * sem);
int down_interruptible(struct semaphore * sem); 
int down_trylock(struct semaphore * sem);
释放信号量
void up(struct semaphore * sem);

down: 获取信号量,即把sem的值减1,如果信号量sem的值非负,就直接返回,否则调用者将被挂起,直到别的任务释放该信号量才能继续运行。

ip: 释放信号量,即把sem的值加1,如果sem的值为非正数,表明目前有任务等待该信号量,因此释放信号量后,等待信号量的任务将被唤醒。

down_trylock:该函数试着获得信号量sem,如果能够即时获得,他就获得该信号量并返回0,否则,表示不能获得信号量sem,返回值为非0值。因此,他不会导致调用者睡眠,能在中断上下文使用。

当信号量初始值被设置成1,可将它用于互斥操作。

3、按键驱动程序的改进

改进驱动程序,分别使用原子操作、信号量实现同步互斥,每次只能有一个应用程序能够打开驱动程序。

驱动程序实现阻塞非阻塞机制支持,应用程序open是可以通过flags标志设置

3.1 原子量实现同步互斥操作

原子量的使用和普遍变量一样,使用前需先定义,调用atomic_t no_opera = ATOMIC_INIT(1)创建并初始化原子量

static atomic no_opera = ATOMIC_INIT(1);

修改驱动程序中的button_drv_open函数。调用open函数打开设备时,先检测原子量的值,如果原子量值为1时,表示此时没有应用程序正在使用驱动程序,可以正常打开驱动,否则open失败。

static int button_drv_open(struct inode *inode, struct file *file)
{
    ...
    
    if(!atomic_dec_and_test(&no_opera))
    {
        atomic_inc(&no_opera);
        return -EBUSY;
    }
    
    ...
    
    return 0;
}

修改button_drv_close函数。调用close函数时关闭打开设备时,将原子量值加1,恢复到初始化值状态,保证下一次应用程序能正常的访问驱动。

static int button_drv_close(struct inode *inode, struct file *file)
{
    ...
    
    atomic_inc(&no_opera);
    
    ...
}

3.2 信号量实现同步互斥操作

先定义信号量,在button_drv_init函数中进行初始化,将信号量值设置成1,用于同步互斥。button_drv_init函数被module_init修饰,在insmod装载驱动程序时被自动调用。

struct semaphore sem;

static int button_drv_init(void)
{
        ...
    sema_init(&sem, 1);
    ...
}

button_drv_open函数,入口处先获取信号量,若此时有应用程序已经打开驱动时,信号量值非正值,当前应用程序被挂起休眠,等待信号量被释放。

static int button_drv_open(struct inode *inode, struct file *file)
{
        ...
    down(&sem);
        ...
}

button_drv_close函数,入口处释放信号量。若此时有应用程序关闭打开驱动时,应释放信号量,保证新运行的应用程序能正常获取信号量或正在阻塞等待信号量的应用程序能被唤醒执行。

static int button_drv_close(struct inode *inode, struct file *file)
{
        ...
    up(&sem);
        ...
}

3.3 阻塞、非阻塞操作实现

阻塞和非阻塞标识通过系统调用open函数的flags形参传入的,驱动程序在对应的open函数中需要对该参数进行判断处理,驱动程序的file->f_flags指针保存系统调用传入参数值

button_drv_open函数,对传入的参数值进行判断,如果是以非阻塞方式打开,先调用down_trylock函数尝试获取信号量值,不能获取信号量时open系统直接返回,不进行休眠等待。如果是以阻塞方式打开,直接调用down函数获取信号量,不能获取信号量是就进行休眠等待。

static int button_drv_open(struct inode *inode, struct file *file)
{
        ...
    if(file->f_flags & O_NONBLOCK)
    {
        if(down_trylock(&sem))
            return -EBUSY;
    }
    else
    {
        down(&sem);
    }
    ...
}

button_drv_read函数,同样对传入的参数值进行判断,如果是以非阻塞方式打开,先判断此时是否有效按键值可读取,如无按键值可读取就直接返回。如果是以非阻塞方式打开,如无按键值可读取就将应用程序休眠,等待有按键值读取时被唤醒。

static ssize_t button_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    ...
    if(file->f_flags & O_NONBLOCK)
    {
        if(event_trig != 1)
            return -EBUSY;
    }
    else
    {
        wait_event_interruptible(button_waitq, event_trig);
    }
    
    ...
}

完整驱动程序

按键驱动之同步互斥、阻塞机制
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <plat/gpio-fns.h>
#include <mach/gpio-nrs.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <linux/poll.h>

#define BUTTON_NUMS    4
#define IRQT_BOTHEDGE IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING

static int major;
static int event_trig = 0;

static unsigned char key_status;

static volatile unsigned long *gpfcon = NULL;
static volatile unsigned long *gpgcon = NULL;
static volatile unsigned long *gpfdat = NULL;
static volatile unsigned long *gpgdat = NULL;

static struct class *button_drv_class;
static struct class_device    *button_drv_class_dev;

static struct fasync_struct *button_async;

static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

#if 0
static atomic no_opera = ATOMIC_INIT(1);
#else
struct semaphore sem;
#endif


struct button_desc
{
    int pin;
    int irq_type;
    unsigned long flags;
    char *name;
    int key_val;
};

static struct button_desc btn_desc[BUTTON_NUMS] = {
    {S3C2410_GPF(0),  IRQ_EINT0,  IRQT_BOTHEDGE, "S2", 1},
    {S3C2410_GPF(2),  IRQ_EINT2,  IRQT_BOTHEDGE, "S3", 2},
    {S3C2410_GPG(3),  IRQ_EINT11, IRQT_BOTHEDGE, "S4", 3},
    {S3C2410_GPG(11), IRQ_EINT19, IRQT_BOTHEDGE, "S5", 4},
};

static int button_drv_open(struct inode *inode, struct file *file);
static ssize_t button_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos);
static ssize_t button_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos);
static int button_drv_close(struct inode *inode, struct file *file);
static unsigned button_drv_poll(struct file *file, poll_table *wait);
static int button_drv_fasync (int fd, struct file *filp, int on);


struct file_operations button_drv_fileop = {
    .owner  =   THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
    .open   =   button_drv_open,
    .read   =   button_drv_read,
    .write  =   button_drv_write,
    .release =  button_drv_close,
    .poll = button_drv_poll,
    .fasync = button_drv_fasync,
};

static irqreturn_t button_irq_handle(int irq, void *dev_id)
{    
    struct button_desc *pdesc = NULL;
    unsigned char pin_val;
    
    pdesc = (struct button_desc *)dev_id;
    
    pin_val = gpio_get_value(pdesc->pin);    

    if(pin_val == 1)
    {
        key_status = pdesc->key_val | 0x80;
    }
    else
    {
        key_status = pdesc->key_val;
    }
    
    event_trig = 1;
    wake_up_interruptible(&button_waitq); 
    
    kill_fasync (&button_async, SIGIO, POLL_IN);
    
    return IRQ_RETVAL(IRQ_HANDLED);
}

static int button_drv_open(struct inode *inode, struct file *file)
{
    int i;
#if 0
    if(!atomic_dec_and_test(&no_opera))
    {
        atomic_inc(&no_opera);
        return -EBUSY;
    }
#else 
    if(file->f_flags & O_NONBLOCK)
    {
        if(down_trylock(&sem))
            return -EBUSY;
    }
    else
    {
        down(&sem);
    }
    
#endif
    
    *gpfcon &= ~((0x3<<(0*2)) | (0x3<<(2*2)));
    *gpgcon &= ~((0x3<<(3*2)) | (0x3<<(11*2)));
    
    /* 注册中断处理函数 */
    for(i = 0; i < BUTTON_NUMS; i++)
        request_irq(btn_desc[i].irq_type, button_irq_handle, btn_desc[i].flags, btn_desc[i].name, &btn_desc[i]);
    
    return 0;
}

static ssize_t button_drv_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
    if(count != 1)
        return EINVAL;
    
    if(file->f_flags & O_NONBLOCK)
    {
        if(event_trig != 1)
            return -EBUSY;
    }
    else
    {
        wait_event_interruptible(button_waitq, event_trig);
    }
    
    if(copy_to_user(buf, &key_status, count))
        return EFAULT;
    
    event_trig = 0;
    return 1;
}

static ssize_t button_drv_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk("button_drv_write\n");
    
    return 0;
}

static int button_drv_close(struct inode *inode, struct file *file)
{
    int i;
#if 0
    atomic_inc(&no_opera);
#else 
    up(&sem);
#endif
    for(i = 0; i < BUTTON_NUMS; i++)
        free_irq(btn_desc[i].irq_type, &btn_desc[i]);
    
    return 0;
}

static unsigned button_drv_poll(struct file *file, poll_table *wait)
{
    unsigned int mask = 0;
    poll_wait(file, &button_waitq, wait); // 不会立即休眠

    if (event_trig)
        mask |= POLLIN | POLLRDNORM;

    return mask;
}

static int button_drv_fasync (int fd, struct file *filp, int on)
{
    printk("driver: sixth_drv_fasync\n");
    return fasync_helper (fd, filp, on, &button_async);
}
        
static int button_drv_init(void)
{
    sema_init(&sem, 1);
    
    major = register_chrdev(0, "button_light", &button_drv_fileop);
    
    button_drv_class = class_create(THIS_MODULE, "button_drv");
    //button_drv_class_dev = class_device_create(button_drv_class, NULL, MKDEV(major, 0), NULL, "button"); /* /dev/button */
    button_drv_class_dev = device_create(button_drv_class, NULL, MKDEV(major, 0), NULL, "button"); /* /dev/button */
    
    gpfcon = (volatile unsigned long *)ioremap(0x56000050, 16);
    gpgcon = (volatile unsigned long *)ioremap(0x56000060, 16);
    gpfdat = gpfcon + 1;
    gpgdat = gpgcon + 1;
    
    return 0;
}

static void button_drv_exit(void)
{
    unregister_chrdev(major, "button_drv");
    
    //class_device_unregister(button_drv_class_dev);
    device_unregister(button_drv_class_dev);
    class_destroy(button_drv_class);
    
    iounmap(gpfcon);
    iounmap(gpgcon);
}

module_init(button_drv_init);
module_exit(button_drv_exit);

MODULE_LICENSE("GPL");
irq_button_drv.c

完整驱动程序

按键驱动之同步互斥、阻塞机制
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>

int fd;

void handler_fcn(int signum)
{
    int ret;
    unsigned char key_buf;
    
    ret = read(fd, &key_buf, 1);
    if(ret < 0)
    {
        printf("read err...\n");
        exit(EXIT_FAILURE);
    }
    /* 判断有按键按下,打印按键信息 */
    printf("key_val=0x%x\n", key_buf);
}

int main(int argc, char **argv)
{
    int flags;
    
    fd = open("/dev/button", O_RDWR|O_NONBLOCK);
    if(fd < 0)
    {
        printf("can't open...\n");
        exit(EXIT_FAILURE);
    }
    
    signal(SIGIO, handler_fcn); //设置信号的处理函数
    
    fcntl(fd, F_SETOWN, getpid());
    flags = fcntl(fd, F_GETFL); 
    fcntl(fd, F_SETFL, flags | FASYNC);
    
    while(1)
    {
        sleep(100);
    }

    exit(EXIT_SUCCESS);
}
irq_button_test.c
上一篇:东南大学每日上报健康申报小助手


下一篇:u-boot kernel driver的理解