个人笔记
并发是指在同一时间段内,多个任务或操作在同一个系统中同时进行。这并不意味着这些任务同时发生在物理上,而是在逻辑上同时发生。在计算机系统中,通常有多个任务或操作同时执行,这样可以提高系统的吞吐量和效率。
Linux 系统是个多任务操作系统,会存在多个任务同时访问同一片内存区域,这些任务可
能会相互覆盖这段内存中的数据,造成内存数据混乱。针对这个问题必须要做处理,严重的话
可能会导致系统崩溃。现在的 Linux 系统并发产生的原因很复杂,总结一下有下面几个主要原
因:
①、多线程并发访问,Linux 是多任务(线程)的系统,所以多线程访问是最基本的原因。
②、抢占式并发访问,从 2.6 版本内核开始,Linux 内核支持抢占,也就是说调度程序可以
在任意时刻抢占正在运行的线程,从而运行其他的线程。
③、中断程序并发访问,这个无需多说,学过 STM32 的同学应该知道,硬件中断的权利可
是很大的。
④、SMP(多核)核间并发访问,现在 ARM 架构的多核 SOC 很常见,多核 CPU 存在核间并
发访问。
为了避免这一情况发生,我们就必须在编写驱动前考虑这一情况并且把数据保护起来。就有了原子操作、互斥锁、自旋锁和信号量等操作。
1.原子操作
原子操作是指在执行过程中不会被中断的操作,要么完全执行,要么完全不执行,不存在执行过程中被中断的情况。在多线程或并发编程中,原子操作是指不可分割的操作,它们能够保证在多线程环境下对共享数据的访问操作是线程安全的,不会发生竞态条件。
ATOMIC_INIT(int i) 定义原子变量的时候对其初始化。
int atomic_read(atomic_t *v) 读取 v 的值,并且返回。
void atomic_set(atomic_t *v, int i) 向 v 写入 i 值。
void atomic_add(int i, atomic_t *v) 给 v 加上 i 值。
void atomic_sub(int i, atomic_t *v) 从 v 减去 i 值。
void atomic_inc(atomic_t *v) 给 v 加 1,也就是自增。
void atomic_dec(atomic_t *v) 从 v 减 1,也就是自减
int atomic_dec_return(atomic_t *v) 从 v 减 1,并且返回 v 的值。
int atomic_inc_return(atomic_t *v) 给 v 加 1,并且返回 v 的值。
int atomic_sub_and_test(int i, atomic_t *v) 从 v 减 i,如果结果为 0 就返回真,否则返回假
int atomic_dec_and_test(atomic_t *v) 从 v 减 1,如果结果为 0 就返回真,否则返回假
int atomic_inc_and_test(atomic_t *v) 给 v 加 1,如果结果为 0 就返回真,否则返回假
int atomic_add_negative(int i, atomic_t *v) 给 v 加 i,如果结果为负就返回真,否则返回假
void set_bit(int nr, void *p) 将 p 地址的第 nr 位置 1。
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 位原来的
2.自旋锁
自旋锁是一种线程同步的机制,用于在多线程环境中保护共享资源,确保同一时间只有一个线程能够访问共享资源。与传统的互斥锁不同,自旋锁不会使线程在无法获取锁时进入睡眠或阻塞状态,而是通过不断地循环检测锁的状态,直到获取到锁为止,因此称为“自旋”
自旋锁会一直等待进程结束会消耗大量的资源。所以只适合短时间的进程,时间长的不适合用进程锁。
在自旋锁锁上了之后,中断是可以打断的,比如某一进程正在运行,自旋锁已经锁上了,然后中断又来了,中断又开了一个锁。他们两个都锁上了。而中断会抢夺cpu的使用权,但是他又没有锁的使用权。他就会原地自旋等待某进程结束,然而进程有没有cpu的使用权,他们两个就这样一直等着,卡死在这里了。
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。
最好的解决办法就是禁止中断。
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)将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁。
DEFINE_SPINLOCK(lock) /* 定义并初始化一个锁 */
/* 线程 A */
void functionA (){
unsigned long flags; /* 中断状态 */
spin_lock_irqsave(&lock, flags) /* 获取锁 */
/* 临界区 */
spin_unlock_irqrestore(&lock, flags) /* 释放锁 */
}
/* 中断服务函数 */
void irq() {
spin_lock(&lock) /* 获取锁 */
/* 临界区 */
spin_unlock(&lock) /* 释放锁 */
}
3.信号量
信号量是一种用于线程同步与互斥的机制,用于控制对共享资源的访问。它是由一个整型变量和两个原子操作组成的。
相比于自旋锁,信号量可以使线程进入休眠状态,比如 A 与 B、C 合租了一套房子,这个
房子只有一个厕所,一次只能一个人使用。某一天早上 A 去上厕所了,过了一会 B 也想用厕
所,因为 A 在厕所里面,所以 B 只能等到 A 用来了才能进去。B 要么就一直在厕所门口等着,
等 A 出来,这个时候就相当于自旋锁。B 也可以告诉 A,让 A 出来以后通知他一下,然后 B 继
续回房间睡觉,这个时候相当于信号量。可以看出,使用信号量会提高处理器的使用效率,毕
竟不用一直傻乎乎的在那里“自旋”等待。但是,信号量的开销要比自旋锁大,因为信号量使
线程进入休眠状态以后会切换线程,切换线程就会有开销。总结一下信号量的特点:
①、因为信号量可以使等待资源线程进入休眠状态,因此适用于那些占用资源比较久的场
合。
②、因此信号量不能用于中断中,因为信号量会引起休眠,中断不能休眠。
③、如果共享资源的持有时间比较短,那就不适合使用信号量了,因为频繁的休眠、切换
线程引起的开销要远大于信号量带来的那点优势
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) 释放信号量
实验:
还是用点灯。
取用之前写过的 gpioled 驱动,在设备结构体中添加一个原子变量
struct gpioled_dev{
dev_t devid;
struct cdev cdev;
int major;
int minor;
struct class *class;
struct device *device;
struct device_node *nd;
int led_gpio;
atomic_t lock; //原子操作
};
要使用这个的先在函数入口处初始化:
static int __init gpioled_init(void)
{
int ret = 0;
//初始化原子量
atomic_set(&gpioled.lock, 1);
//注册字符设备
gpioled.major = 0;
if(gpioled.major){
gpioled.devid = MKDEV(gpioled.major, 0);
register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
}else{
ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
printk("maj:%d min:%d\r\n", gpioled.major, gpioled.minor);
if(ret < 0){
goto fail_devid;
}
//初始化 dev
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &filp);
//cdev 添加到设备
ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
if(ret < 0){
goto fail_cdev;
}
//创建设备节点
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if(IS_ERR(gpioled.class)){
ret = PTR_ERR(gpioled.class);
goto fail_class;
}
//创建设备
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)){
ret = PTR_ERR(gpioled.device);
goto fail_device;
}
//获取设备节点
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL){
goto fail_node;
}
//获取led的对应的gpio
gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"led-gpios",0);
if(gpioled.led_gpio < 0){
printk("find errir\r\n");
ret = -EINVAL;
goto fail_find_node;
}
printk("led gpio num = %d\r\n",gpioled.led_gpio);
//申请io 申请了才能使用
ret = gpio_request(gpioled.led_gpio, "led-gpio");
if(ret){
printk("gpio_request error\r\n");
ret = -EINVAL;
goto fail_quest;
}
//使用io
ret = gpio_direction_output(gpioled.led_gpio, 1);//设置为灭 0 亮
if(ret){
goto fail_io;
}
gpio_direction_output(gpioled.led_gpio, 0);
return ret;
fail_io:
gpio_free(gpioled.led_gpio);
fail_quest:
fail_find_node:
fail_node:
device_destroy(gpioled.class, gpioled.devid);
fail_device:
class_destroy(gpioled.class);
fail_class:
cdev_del(&gpioled.cdev);
fail_cdev:
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
fail_devid:
return ret;
}
在函数被调用的时候,我们就得把这个锁给打开。
static int gpioled_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled;
//判断lock
if(!atomic_dec_and_test(&gpioled.lock)){
atomic_inc(&gpioled.lock);
return -EBUSY;
}
#if 0
if(atomic_read(&gpioled.lock) <= 0){
return -EBUSY;
}else{
atomic_dec(&gpioled.lock);
}
#endif
return 0;
}
在函数结束时,我们就要释放锁。
static int gpioled_release(struct inode *inode, struct file *filp)
{
struct gpioled_dev *dev = filp->private_data;
atomic_inc(&dev->lock); //加一释放驱动
return 0;
}
为了明显观测到这一现象,我们需要将程序空转个几秒,不然程序一下就执行完了看不到现象。
atomicApp.c 使用 arm-linux-guneabihf-gcc atomicApp.c -o atomicApp编译
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define LEDOFF 0
#define LEDON 1
int main(int argc,char *argv[])
{
int cnt;
int fd;
int ret;
char *filename;
unsigned char databuff[1];
if(argc != 3){
printf("data deficiency\r\n");
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file open errer\r\n");
return -1;
}
databuff[0] = atoi(argv[2]);
ret = write(fd,databuff,sizeof(databuff));
if(ret < 0){
printf("control failed\r\n");
close(fd);
return -1;
}
while(1){
sleep(5);
cnt++;
printf("APP RUNING TIME:%d\r\n",cnt);
if(cnt >= 5){
break;
}
}
printf("APP OVER\r\n");
close(fd);
return 0;
}
效果:
先 depmod 刷新,然后modprobe atomic.ko 注册驱动
执行atomicApp文件 1 表示关灯 & 表示在后台运行。
这里调用 函数一次后 在25秒的运行时间内不可以再次调用,文件打开失败。
接着是自旋锁的:(互斥体,信号量都在里面)
驱动文件
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/atomic.h>
#define GPIOLED_CNT 1
#define GPIOLED_NAME "gpioled"
#define LEDOFF 1
#define LEDON 0
//gpio 设备结构体
struct gpioled_dev{
dev_t devid;
struct cdev cdev;
int major;
int minor;
struct class *class;
struct device *device;
struct device_node *nd;
int led_gpio;
/*
int dev_status; //0 表示设备可以使用 大于 1 表示不可以使用 自旋锁
spinlock_t lock;
*/
/* //信号量
struct semaphore sem;
*/
//互斥体
struct mutex lock;
};
struct gpioled_dev gpioled;
static int gpioled_open(struct inode *inode, struct file *filp)
{
// unsigned long irqflag;
filp->private_data = &gpioled;
/* //加锁保护变量
//spin_lock(&gpioled.lock);
spin_lock_irqsave(&gpioled.lock, irqflag);
if(gpioled.dev_status){//驱动不能使用
spin_unlock(&gpioled.lock);
return -EBUSY;
}
gpioled.dev_status++;//表示不能使用
// spin_unlock(&gpioled.lock);
spin_unlock_irqrestore(&gpioled.lock, irqflag);
*/
/*
down(&gpioled.sem);//获取信号量
*/
mutex_lock(&gpioled.lock);
return 0;
}
static int gpioled_release(struct inode *inode, struct file *filp)
{
// unsigned long irqflag;
struct gpioled_dev *dev = filp->private_data;
/*
//spin_lock(&dev->lock);
spin_lock_irqsave(&dev->lock, irqflag);
if(dev->dev_status){
dev->dev_status--;//标记驱动可以使用
}
//spin_unlock(&dev->lock);
spin_unlock_irqrestore(&dev->lock, irqflag);
*/
/*
up(&dev->sem);
*/
mutex_unlock(&dev->lock);
return 0;
}
static ssize_t gpioled_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
int ret;
unsigned char data[1];
struct gpioled_dev *dev = filp->private_data;
ret = copy_from_user(data, buf, count);
if(ret < 0){
printk("write error\r\n");
return ret;
}
if(data[0] == LEDON){
gpio_set_value(dev->led_gpio, LEDON);
}else if(data[0] == LEDOFF){
gpio_set_value(dev->led_gpio, LEDOFF);
}
return 0;
}
static const struct file_operations filp = {
.owner = THIS_MODULE,
.open = gpioled_open,
.release = gpioled_release,
.write = gpioled_write,
};
//入口出口
static int __init gpioled_init(void)
{
int ret = 0;
// 初始化互斥体
mutex_init(&gpioled.lock);
/* //初始化信号量
sema_init(&gpioled.sem, 1);
*/
//初始化自旋锁
// spin_lock_init(&gpioled.lock);
// gpioled.dev_status = 0;
//注册字符设备
gpioled.major = 0;
if(gpioled.major){
gpioled.devid = MKDEV(gpioled.major, 0);
register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
}else{
ret = alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);
gpioled.major = MAJOR(gpioled.devid);
gpioled.minor = MINOR(gpioled.devid);
}
printk("maj:%d min:%d\r\n", gpioled.major, gpioled.minor);
if(ret < 0){
goto fail_devid;
}
//初始化 dev
gpioled.cdev.owner = THIS_MODULE;
cdev_init(&gpioled.cdev, &filp);
//cdev 添加到设备
ret = cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
if(ret < 0){
goto fail_cdev;
}
//创建设备节点
gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
if(IS_ERR(gpioled.class)){
ret = PTR_ERR(gpioled.class);
goto fail_class;
}
//创建设备
gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
if(IS_ERR(gpioled.device)){
ret = PTR_ERR(gpioled.device);
goto fail_device;
}
//获取设备节点
gpioled.nd = of_find_node_by_path("/gpioled");
if(gpioled.nd == NULL){
goto fail_node;
}
//获取led的对应的gpio
gpioled.led_gpio = of_get_named_gpio(gpioled.nd,"led-gpios",0);
if(gpioled.led_gpio < 0){
printk("find errir\r\n");
ret = -EINVAL;
goto fail_find_node;
}
printk("led gpio num = %d\r\n",gpioled.led_gpio);
//申请io 申请了才能使用
ret = gpio_request(gpioled.led_gpio, "led-gpio");
if(ret){
printk("gpio_request error\r\n");
ret = -EINVAL;
goto fail_quest;
}
//使用io
ret = gpio_direction_output(gpioled.led_gpio, 1);//设置为灭 0 亮
if(ret){
goto fail_io;
}
gpio_direction_output(gpioled.led_gpio, 0);
return ret;
fail_io:
gpio_free(gpioled.led_gpio);
fail_quest:
fail_find_node:
fail_node:
device_destroy(gpioled.class, gpioled.devid);
fail_device:
class_destroy(gpioled.class);
fail_class:
cdev_del(&gpioled.cdev);
fail_cdev:
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
fail_devid:
return ret;
}
static void __exit gpioled_exit(void)
{
gpio_direction_output(gpioled.led_gpio, 1);
//注销字符驱动
cdev_del(&gpioled.cdev);
//删除设备号
unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
//摧毁驱动
device_destroy(gpioled.class, gpioled.devid);
//摧毁类
class_destroy(gpioled.class);
//销毁io
gpio_free(gpioled.led_gpio);
}
//设备注册与卸载
module_init(gpioled_init);
module_exit(gpioled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("lakzhu");
软件:spinlockApp.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#define LEDOFF 0
#define LEDON 1
int main(int argc,char *argv[])
{
int cnt;
int fd;
int ret;
char *filename;
unsigned char databuff[1];
if(argc != 3){
printf("data deficiency\r\n");
}
filename = argv[1];
fd = open(filename, O_RDWR);
if(fd < 0){
printf("file open errer\r\n");
return -1;
}
databuff[0] = atoi(argv[2]);
ret = write(fd,databuff,sizeof(databuff));
if(ret < 0){
printf("control failed\r\n");
close(fd);
return -1;
}
while(1){
sleep(5);
cnt++;
printf("APP RUNING TIME:%d\r\n",cnt);
if(cnt >= 5){
break;
}
}
printf("APP OVER\r\n");
close(fd);
return 0;
}
他们都能够让程序自动排队,一个执行完另外一个就可以接着运行。