文章目录
前言
Linux系统是个多任务操作系统,并发访问带来的问题就是竞争,所谓的临界区就是共享数据段,要保证临界区是原子访问的。主要方法有四种:原子操作,自旋锁,信号量,互斥体。本文主要介绍内核下各方式的常用API及基本用法,帮助大家快速上手。
一、原子操作
原子操作就是指不能在进一步分割的操作,原子操作只能对整形变量或者位进行保护。
1.原子整形操作:
typedef struct {
int counter;
} atomic_t;
操作API如下,对于32位的系统,把接口名的"atomic64_"换成"atomic_"即可。
ATOMIC64_INIT(int i) 定义原子变量的时候对其初始化。
int atomic64_read(atomic64_t *v) 读取 v 的值,并且返回。
void atomic64_set(atomic64_t *v, int i) 向 v 写入 i 值。
void atomic64_add(int i, atomic64_t *v) 给 v 加上 i 值。
void atomic64_sub(int i, atomic64_t *v) 从 v 减去 i 值。
void atomic64_inc(atomic64_t *v) 给 v 加 1,也就是自增。
void atomic64_dec(atomic64_t *v) 从 v 减 1,也就是自减
int atomic64_dec_return(atomic64_t*v) 从 v 减 1,并且返回 v 的值。
int atomic64_inc_return(atomic64_t *v) 给 v 加 1,并且返回 v 的值。
int atomic64_sub_and_test(int i,atomic64_t *v) 从 v 减 i,如果结果为 0 就返回真,否则返回假
int atomic64_dec_and_test(atomic64_t*v) 从 v 减 1,如果结果为 0 就返回真,否则返回假
int atomic64_inc_and_test(atomic64_t*v) 给 v 加 1,如果结果为 0 就返回真,否则返回假
int atomic64_add_negative(int i,atomic64_t *v) 给 v 加 i,如果结果为负就返回真,否则返回假
示例:
atomic_t a = ATOMIC_INT(3);
atomic64_set(&a,10); a赋值为10
atomic64_read(&a); 读出a的值
原子位操作API:
void set_bit(int nr, void *p) 将 p 地址的第 nr 位置 1。
void clear_bit(int nr,void *p) 将 p 地址的第 nr 位清零。
void change_bit(int nr, void*p) 将 p 地址的第 nr 位进行翻转。
int test_bit(int nr, void *p) 获取 p 地址的第 nr 位的值。
int test_and_set_bit(int nr, void*p) 将 p 地址的第 nr 位置 1,并且返回 nr 位原来的值。
int test_and_clear_bit(int nr,void *p) 将 p 地址的第 nr 位清零,并且返回 nr 位原来的值。
int test_and_change_bit(int nr,void *p) 将 p 地址的第 nr 位翻转,并且返回 nr 位原来的值。
驱动代码示例:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/of_address.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#define LED_NAME "gpio_init"
#define LED_MAJOR 202
#define LED_MINOR 0
struct led_dev {
struct class * fclass;
struct device * fdevice;
struct cdev * cdev;
dev_t dev_num;
struct device_node * nd;
int major;
int minor;
int gpio_led;
atomic64_t lock;
};
struct led_dev led = {0};
int led_open (struct inode *a, struct file *b)
{
int val = 0;
printk("lzw led open !!!\n");
if(atomic64_sub_and_test(1,&led.lock) == 0)
{
val = atomic64_inc_return(&led.lock); //减了1要加回来
printk("lzw led atomic has been open, now failed, val = %d !\n",val);
return -EBUSY;
}
else
printk("lzw led atomic open sueccess !\n");
b->private_data = &led;
return 0;
}
int led_close (struct inode *a, struct file *b)
{
atomic64_inc(&led.lock);
printk("lzw led atomic close, atomic lock +1 sueccess !\n");
return 0;
}
struct file_operations fop =
{
.owner = THIS_MODULE,
.open = led_open,
.release = led_close,
};
int __init led_init(void)
{
led.cdev = cdev_alloc();
led.dev_num = MKDEV(LED_MAJOR,LED_MINOR);
register_chrdev_region(led.dev_num, 1, LED_NAME);
cdev_init(led.cdev,&fop);
cdev_add(led.cdev,led.dev_num,1);
led.fclass = class_create(THIS_MODULE, "led_class");
led.fdevice = device_create(led.fclass, NULL, led.dev_num, NULL, "led_device");
led.major = LED_MAJOR;
led.minor = LED_MINOR;
atomic64_set(&led.lock,1);
printk("lzw led init success, atomic lock = %lld ~~~\n",atomic64_read(&led.lock));
return 0;
}
void __exit led_exit(void)
{
device_destroy(led.fclass,led.dev_num);
class_destroy(led.fclass);
cdev_del(led.cdev);
unregister_chrdev(LED_MAJOR, LED_NAME);
return;
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
应用程序代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#define LED_IOCTL _IO('T',0)
#define LED_UNDEFINE _IO('T',1)
struct led_dev {
struct class * fclass;
struct device * fdevice;
struct cdev * cdev;
dev_t dev_num;
struct device_node * nd;
int major;
int minor;
};
int main(void)
{
int fd = open("/dev/led_device",O_RDWR);
if(fd > 0)
printf("open 1 success \n");
else
printf("open 1 failed \n");
int fd2 = open("/dev/led_device",O_RDWR);
if(fd2 > 0)
printf("open 2 success \n");
else
printf("open 2 failed \n");
if(fd>0)
close(fd);
if(fd2>0)
close(fd2);
return 0;
}
加载驱动及运行结果如下:
这样就能保证该节点只被一个应用程序打开使用,后面的方法效果及原理也和此类似,就不讲得那么详细了。
二、自旋锁
自旋锁适用于短时期的轻量级加锁,等待锁的线程会一直处于自旋状态(会浪费处理器时间)。
用结构体spinlock_t表示自旋锁
spinlock_t lock;
基本操作API:
DEFINE_SPINLOCK(spinlock_t lock) 定义并初始化一个自选变量。
int spin_lock_init(spinlock_t*lock) 初始化自旋锁。
void spin_lock(spinlock_t*lock) 获取指定的自旋锁,也叫做加锁。
void spin_unlock(spinlock_t*lock) 释放指定的自旋锁。
int spin_trylock(spinlock_t*lock) 尝试获取指定的自旋锁,如果没有获取到就返回 0
int spin_is_locked(spinlock_t*lock) 检查指定的自旋锁是否被获取,如果没有被获取就返回非 0,否则返回 0。
注意,当线程获得锁,执行中被中断抢走cpu使用权,中断获取不到锁一直自旋,线程A执行不了,放不了锁,死锁发生
线程A 中断
spin_lock(&lock) |-->spin_lock(&lock)
spin_unlock(&lock)--| functionB
functionA() spin_unlock(&lock)
最好解决方法就是关闭本地中断:
void spin_lock_irq(spinlock_t*lock) 禁止本地中断,并获取自旋锁。
void spin_unlock_irq(spinlock_t *lock) 激活本地中断,并释放自旋锁。
void spin_lock_irqsave(spinlock_t *lock,unsigned long flags) 保存中断状态,禁止本地中断,并获取自旋锁。
void spin_unlock_irqrestore(spinlock_t*lock, unsigned long flags) 将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。
我们是很难确定某个时刻的中断状态,因此不推荐使用 spin_lock_irq/spin_unlock_irq
一般在线程中使用spin_lock_irqsave/spin_unlock_irqrestore,在中断中使用 spin_lock/spin_unlock。自旋锁使用注意事项:
1.锁的持有时间不能太长,否则会降低系统性能
2.不能调用任何可能导致线程休眠的API,否则可能死锁
3.不能递归申请自旋锁
4.必须考虑可移植性
驱动如下,真正实现设备互斥访问的是变量 dev_stats,但是要使用自旋锁对 dev_stats 来做保护:
struct led_dev {
...
int dev_stat; //以此作为计数值判断打开程序有几个
spinlock_t lock;
};
int led_open (struct inode *a, struct file *b)
{
unsigned long flags = 0;
int ret = 0;
printk("lzw led spinlock open !!!\n");
spin_lock_irqsave(&led.lock,flags);
if(led.dev_stat != 0)
{
printk("lzw led spin_lock has been open, now failed !\n");
ret = -1;
}
else
{
printk("lzw led spin_lock open sueccess !\n");
led.dev_stat++;
b->private_data = &led;
}
spin_unlock_irqrestore(&led.lock,flags);
return ret;
}
int led_close (struct inode *a, struct file *b)
{
unsigned long flags = 0;
spin_lock_irqsave(&led.lock,flags);
if(led.dev_stat)
led.dev_stat--;
spin_unlock_irqrestore(&led.lock,flags);
printk("lzw led spin_lock close +++ sueccess !\n");
return 0;
}
int __init led_init(void)
{
...
led.dev_stat = 0;
spin_lock_init(&led.lock);
printk("lzw led init spinlock success ~~~\n");
return 0;
}
测试用例:
int main(void)
{
int fd = open("/dev/led_device",O_RDWR);
if(fd > 0)
printf("open 1 success \n");
else
printf("open 1 failed \n");
int fd2 = open("/dev/led_device",O_RDWR);
if(fd2 > 0)
printf("open 2 success \n");
else
printf("open 2 failed \n");
if(fd>0)
close(fd);
if(fd2>0)
close(fd2);
return 0;
}
运行结果如下:
三、读写自旋锁
一次只允许一个写操作,不能有读;读的时候可以多人并发的读
typedef struct {
arch_rwlock_t raw_lock;
} rwlock_t;
API参考上面自旋锁:
void read_lock(rwlock_t *lock) 获取读锁
void write_lock(rwlock_t *lock) 获取写锁。
顺序锁:可以写操作时进行读操作,保护的资源不能是指针(同时读写可能崩溃)
typedef struct {
struct seqcount seqcount;
spinlock_t lock;
} seqlock_t;
void write_seqlock(seqlock_t *sl) 获取写顺序锁。
四、信号量
同步的一种方式
1.可以使等待线程进入休眠状态,适合占用资源久的场合
2.不能用于中断中,因为会引起休眠
3.共享资源持有时间短,不适合
结构体如下:
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
DEFINE_SEAMPHORE(name) 定义一个信号量,并且设置信号量的值为 1。
void sema_init(struct semaphore*sem, int val) 初始化信号量 sem,设置信号量值为 val。
void down(struct semaphore *sem) 获取信号量,因为会导致休眠,因此不能在中断中使用。
int down_trylock(struct semaphore*sem); 尝试获取信号量,如果能获取到信号量就获取,并且返回 0。如果不能就返回非 0,并且不会进入休眠。
int down_interruptible(struct semaphore *sem) 获取信号量,和 down 类似,只是使用 down 进入休眠状态的线程不能被信号打断。而使用此函数进入休眠以后是可以被信号打断的。
void up(struct semaphore *sem) 释放信号量
简单举例:
struct semaphore sem; /* 定义信号量 */
sema_init(&sem, 1); /* 初始化信号量 */
down(&sem); /* 申请信号量 */
/* 临界区 */
up(&sem); /* 释放信号量 */
驱动:
int led_open (struct inode *a, struct file *b)
{
printk("lzw led semphore open !!!\n");
down_interruptible(&led.sem); //如果获取不到进入休眠状态,sem大于等于1时会被唤醒
#if 0
down(&sem);
#endif
return 0;
}
int led_close (struct inode *a, struct file *b)
{
up(&led.sem);
printk("lzw led semphore close +++ sueccess !\n");
return 0;
}
int __init led_init(void)
{
...
sema_init(&led.sem,1);
}
测试用例test:
int fd = open("/dev/led_device",O_RDWR);
if(fd > 0)
printf("open 1 success \n");
else
printf("open 1 failed \n");
for(int i=0;i<10;i++)
{
sleep(1);
printf("led 1 on ~\n");
}
if(fd>0)
close(fd);
测试用例test2:
int fd2 = open("/dev/led_device",O_RDWR);
if(fd2 > 0)
printf("open 2 success \n");
else
printf("open 2 failed \n");
for(int i=0;i<5;i++)
{
sleep(1);
printf("led 2 on ~\n");
}
if(fd2>0)
close(fd2);
先运行test,再运行test2,可发现因为test已经占用打开了/dev/led_device,导致test2只能阻塞在open,等test关闭之后才能继续运行。
五、互斥体
互斥体,也叫互斥锁。虽然可以通过信号量为1实现互斥,但是 Linux 提供了一个比信号量更专业的机制来进行互斥。和二值信号量类似,只不过互斥体专门用于互斥访问。
注意:
1.mutex会导致休眠,所以不能在中断用,中断只能用自旋锁
2.和信号量一样,mutex保护的临界区可以调用引起阻塞的API
3.一次只有一个线程可以持有mutex,必须由mutex持有者释放mutex
struct mutex {
/* 1: unlocked, 0: locked, negative: locked, possible waiters */
atomic_t count;
spinlock_t wait_lock;
};
DEFINE_MUTEX(name) 定义并初始化一个 mutex 变量。
void mutex_init(struct mutex *lock) 初始化 mutex。
void mutex_lock(struct mutex *lock) 获取 mutex,也就是给 mutex 上锁。如果获取不到就进休眠。
void mutex_unlock(struct mutex*lock) 释放 mutex,也就给 mutex 解锁。
int mutex_trylock(struct mutex *lock) 尝试获取 mutex,如果成功就返回1,如果失败就返回 0。
int mutex_is_locked(struct mutex*lock) 判断 mutex 是否被获取,如果是的话就返回 1,否则返回 0。
int mutex_lock_interruptible(structmutex *lock)使用此函数获取信号量失败进入休眠以后可以被信号打断。
struct mutex lock;
mutex_init(&lock);
mutex_lock(&lock);
/* 临界区 */
mutex_unlock(&lock);
驱动:
struct led_dev {
...
struct mutex lock;
};
int led_open (struct inode *a, struct file *b)
{
printk("lzw led mutex open !!!\n");
if(mutex_lock_interruptible(&led.lock))//可被信号打断,失败进入休眠状态
return -1;
#if 0
mutex_lock(&led.lock);
#endif
return 0;
}
int led_close (struct inode *a, struct file *b)
{
mutex_unlock(&led.lock);
printk("lzw led mutex close +++ sueccess !\n");
return 0;
}
int __init led_init(void)
{
...
mutex_init(&led.lock);
}
测试用例及效果与第四节信号量的一致。
总结
以上就是今天要讲的内容,本文简单介绍了Linux内核下的并发与竞争,常用的API,用法以及测试案例。制作不易,多多包涵。
标签:led,struct,并发,int,lock,void,竞争,mutex,Linux From: https://blog.csdn.net/qq_40514606/article/details/139274822