基本概念
有时共享资源可能恰好是一个简单的整数值,我们也需要进行互斥访问,那么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.