字符设备驱动(11)-互斥与同步:原子变量

基本概念

        有时共享资源可能恰好是一个简单的整数值,我们也需要进行互斥访问,那么Linux内核专门提供了一种数据类型atomic_t,用它来定义的变量称为原子变量。类型定义为:

typedef struct{
    int counter;
}atomic_t;

#ifdef CONFIG_64BIT
typedef struct {
	s64 counter;
} atomic64_t;
#endif

        下面这个是64位操作系统定义的原子变量数据类型atomic64_t。

        ARM专门提供了一些指令来实现原子操作,所谓原子操作就是对这个变量的操作是原子性的,类似于在汇编级代码上只要一条汇编指令就能完成,既然是这样进行操作,就不需要考虑并发带来的影响。

        这样的操作有:

//整型原子操作
//设置原子变量的值
atomic_t v = ATOMIC_INIT(0); /* 定义原子变量v并初始化为0 */
void atomic_set(atomic_t *v, int i); /* 设置原子变量的值为i */
//获取原子变量的值
atomic_read(atomic_t *v);  /* 返回原子变量的值*/
//原子变量加/减
void atomic_add(int i, atomic_t *v); /* 原子变量增加i */
void atomic_sub(int i, atomic_t *v); /* 原子变量减少i */
//原子变量自增/自减
void atomic_inc(atomic_t *v);    /* 原子变量增加1 */
void atomic_dec(atomic_t *v);    /* 原子变量减少1 */
//操作并测试
int atomic_inc_and_test(atomic_t *v);
int atomic_dec_and_test(atomic_t *v);
int atomic_sub_and_test(int i, atomic_t *v);
//上述操作对原子变量执行自增、自减和减操作后(注意没有加)测试其是否为0,为0返回true,否则返回false。
//操作并返回
int atomic_add_return(int i, atomic_t *v);
int atomic_sub_return(int i, atomic_t *v);
int atomic_inc_return(atomic_t *v);
int atomic_dec_return(atomic_t *v);
//上述操作对原子变量进行加/减和自增/自减操作,并返回新的值。

//按位进行操作
void atomic_and(int i, atomic_t *v)//v的值位与上i
void atomic_andnot(int i, atomic_t *v)//v的值位与上~i
void atomic_or(int i, atomic_t *v)//v的值位或上i
void atomic_xor(int i, atomic_t *v)//v的值位异或上i
//上述操作对原子变量进行位与、位与反、位或、位异或操作

//交换操作
static __always_inline int atomic_xchg(atomic_t *v, int new)//v的值更新为new,返回v的老值
static __always_inline int atomic_cmpxchg(atomic_t *v, int old, int new)//如果v的值为old,更新为new,返回v的老值

      当然还有对应的64位原子变量的操作。

      另外还有原子位操作,和原子变量不同,原子位操作没有atomic_t的数据结构,原子位操作直接对内存进行操作,操作速度非常快,但需要底层硬件支持。如果支持,就可以使用单条汇编指令来执行,并不需要禁止中断。

void set_bit(nr, void *addr)//addr的值第nr位置1
void clear_bit(nr, void *addr)//addr的值第nr位清0
void change_bit(nr, void *addr)//addr的值第nr位置当前值的反值
test_bit(nr, void *addr)//返回指定位的值
//操作并返回
int test_and_set_bit(nr, void *addr)
int test_and_clear_bit(nr, void *addr)
int test_and_change_bit(nr, void *addr)
//和前面函数一样的功能,返回这个位的老值

        总之,它的适用场景为只对整型有效,用于底层计数,对结构体不能使用,但在能使用原子变量时尽量使用,相比锁机制,它的开销小。

驱动程序关键点

        使用原子变量实现单个设备的互斥使用,即进程1使用了设备,进程2不能再使用设备。

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

atomic_t i = ATOMIC_INIT(1);

static int my_open(struct inode *pnode, struct file *pfile)
{
    if (atomic_dec_and_test(&i) == true)
    {
        printk("Open char dev.\n");
        return 0;
    }   
    else
    {   
        atomic_inc(&i);
        printk("Open char dev failed.\n");
        return -1; 
    }
}
 
static int my_close(struct inode *pnode, struct file *pfile)
{
    printk("Close cdev.\n");
    atomic_inc(&i);
    my_fasync(-1, pfile, 0);
    return 0;
}

完整驱动程序

//head
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/string.h>
#include <asm/atomic.h>

#define MAJOR_CHAR 100
#define MINOR_CHAR 0
#define MAX_LEN    64 

//异步结构定义
atomic_t i = ATOMIC_INIT(1);
 
static int my_open(struct inode *pnode, struct file *pfile)
{
    if (atomic_dec_and_test(&i) == true)
    {
        printk("Open char dev.\n");
        return 0;
    }   
    else
    {   
        atomic_inc(&i);
        printk("Open char dev failed.\n");
        return -1; 
    }
}
 
static int my_close(struct inode *pnode, struct file *pfile)
{
    printk("Close cdev.\n");
    atomic_inc(&i);
    return 0;
}

struct cdev cdevice;
 
struct file_operations cdev_ops = {
    .open    = my_open,
    .release = my_close,
};
 
//加载
static int hello_init(void)
{ 
    dev_t devno = MKDEV(MAJOR_CHAR,MINOR_CHAR);
    int ret = -1;
	printk(KERN_ALERT "Hello World.\n");
    //up kernel
        //1、注册设备号
        ret = register_chrdev_region(devno, 1, "hello");
        if (0 != ret)
        {
            printk("Register char device failed.\n");
            return ret;
        }
    
        //2、初始化字符设备结构体
        cdev_init(&cdevice, &cdev_ops);
    
        cdevice.owner = THIS_MODULE;
	
        //3、添加字符设备结构体给内核
        ret = cdev_add(&cdevice,devno , 1);
        if (0 != ret)
        {   
            //注意释放设备号
            unregister_chrdev_region(devno,1);
            printk("Unregister char device.\n");
            return ret;
        }
 
        printk("Register char device success.\n");
    //down hardware
 
    return 0;
} 
 
//卸载函数(必须)
static void hello_exit(void)//返回值是void类型,函数名自定义,参数是void
{
    dev_t devno = MKDEV(MAJOR_CHAR, MINOR_CHAR);
 
    printk(KERN_ALERT "Goodbye World.\n");
    // down hardware
 
    // up kernel
        //1、从内核中删除字符设备结构体
        cdev_del(&cdevice);
 
        //2、注销设备号
        unregister_chrdev_region(devno, 1);
}
 
//注册(必须)
module_init(hello_init);
module_exit(hello_exit);
 
//license(必须)
MODULE_LICENSE("GPL");
 
//作者与描述(可选)
MODULE_AUTHOR("Ono Zhang");
MODULE_DESCRIPTION("A simple Hello World Module");

用户态程序

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

#define FILE_NAME "/dev/mydev"

int main(int argc, char *argv[])
{
    int fd;
    
    fd = open(FILE_NAME, O_RDWR);
    if (0 > fd) 
    {   
        printf("Open failed.\n");
        return -1; 
    }   
    
    while(1)
    {
        printf("Wait for 1s\n");
        sleep(1);
    }
}

运行结果

$ sudo insmod cdev.ko
$ sudo mknod /dev/mydev c 100 0 
$ sudo ./a.out &
Wait for 1s
Wait for 1s
Wait for 1s
Wait for 1s

        在另一个终端窗口运行进程a.out

$sudo ./a.out
Open failed.

  • 11
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值