前文【linux驱动】讲解linux驱动开发中的并发与并行,并且给出解决驱动开发中资源竞争的解决方案(上)
自旋锁(spin lock)是为了保护共享资源提出的一种非阻塞锁机制,也就是说,如果某线程需要获取锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗 CPU 的时间,不停的试图获取锁。
举个形象生动的例子,以现实生活中银行 ATM 机办理业务为例,ATM 机防护舱在同一时间内只允许一个人进入,当有人进入 ATM 机防护舱之后,两秒钟之后自动上锁,其他也想要存取款的人员,只能在外部等待,办理完相应的存取款业务之后,舱内人员需要手动打开防护锁,其他人才能进入其中,办理业务。
自旋锁的特点:
自旋锁的死锁:
内核中以 spinlock_t
结构体来表示自旋锁,具体定义如下:
typedef struct spinlock {
union {
struct raw_spinlock rlock;
#ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
自旋锁的API如下:
/tr>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。 |
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) | 将中断状态恢复到以前的状态,并且激活本地中断,释放自旋锁 |
自旋锁的使用:使用自旋锁完成:同一个驱动仅允许一个应用程序打开。
首先,定义一个自旋锁spinlock_t mylock
。
其次,当驱动被打开时,获取自旋锁spin_lock(&mylock)
。
最后,当驱动关闭时,解除自旋锁spin_unlock(&mylock)
。
完成代码如下:
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h> //注册杂项设备头文件
#include <linux/fs.h> //注册设备节点的文件结构体
#include <linux/uaccess.h>
/* 含自旋锁操作驱动 */
// 驱动名
#define DEVICE_NAME "mytest"
// 数据buffer
static char kbuff[32] ;
// 定义一个自旋锁
spinlock_t mylock;
// 打开设备
int misc_open(struct inode *inode,struct file*file)
{
// 给自旋锁加锁
spin_lock(&mylock);
printk("misc_open is ok.\r\n");
return 0;
}
// 关闭设备
int misc_close(struct inode * inode, struct file *file)
{
// 给自旋锁解锁
spin_unlock(&mylock);
printk("misc_close\r\n");
return 0;
}
// 读取设备中信息
ssize_t misc_read (struct file *file, char __user *buff, size_t size, loff_t *loff)
{
int len = strlen(kbuff);
if(copy_to_user(buff,kbuff,len) != 0) // 将内核中的数据给应用
{
printk("copy_to_user error\r\n");
return -1;
}
printk("copy_to_user is successful\r\n");
return len;
}
// 写入设备信息
ssize_t misc_write (struct file *file, const char __user *buff, size_t size, loff_t *loff)
{
int ret = copy_from_user(kbuff,buff,size);
if( ret != 0) // 从应用那儿获取数据
{
printk("ret of error is %d.\r\n",ret);
return ret;
}
printk("copy_from_user data:%s.\r\n",kbuff);
return size;
}
// 设备文件操作集
struct file_operations misc_fops ={
.owner = THIS_MODULE,
.open = misc_open,
.release = misc_close,
.read = misc_read,
.write = misc_write
};
// 设备文件信息描述集
struct miscdevice misc_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &misc_fops
};
// 驱动的入口函数
static int __init misc_init(void)
{
// 申请杂项设备
int ret = 0;
ret = misc_register(&misc_dev);
printk("--------------------------------------------\r\n");
if(ret < 0)
{
printk("misc register is error.\r\n");
}
else
printk("misc register is seccussful.\r\n");
return ret;
}
//驱动的出口函数
static void __exit misc_exit(void)
{
// 注销杂项设备
misc_deregister(&misc_dev);
printk("misc_exit\r\n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zxj");
使用insmod
命令加载驱动到内核后,就可编写测试程序进行测试,由于此处的此时程序仅仅只需要打开关闭设备文件,因此此处就不过多赘述。测试结果如下:
通过测试结果可看出,在第一次打开设备文件后,如果在进行第二次打开就需要等待第一次完成关闭后才能再次打开设备文件。
信号量,本质上是一个全局变量,信号量的值表示控制访问资源的线程数,可以根据实际情况来自行设置:
注意!信号量的值不能小于 0 。
与自旋锁的比较:
信号量的结构体定义如下:
struct semaphore {
raw_spinlock_t lock;
unsigned int count;
struct list_head wait_list;
};
不过需要注意的是:不能直接使用结构体中的任何内容。
信号量的API如下:
函数名 | 作用 |
---|---|
DEFINE_SEMAPHORE(name) | 定义一个 struct semaphore name 结构体, count 值设置为 1 |
void sema_init(struct semaphore *sem, int val) | 初始化 semaphore |
void down(struct semaphore *sem) | 获得信号量,如果暂时无法获得就会休眠返回之后就表示肯定获得了信号量在休眠过程中无法被唤醒,即使有信号发给这个进程也不处理 |
int down_trylock(struct semaphore *sem) | 尝试获得信号量,不会休眠,返回值:0:获得了信号量 1:没能获得信号量 |
void up(struct semaphore *sem) | 释放信号量,唤醒其他等待信号量的进程 |
信号量的使用:使用信号量完成:同一个驱动仅允许一个应用程序打开。
DEFINE_SEMAPHORE(mysemaphore)
完成定义。或者使用先定义一个型号量变量,然后再使用函数void sema_init(struct semaphore *sem, int val)
完成初始化。可以看到宏定义的实质也是调用函数void sema_init(struct semaphore *sem, int val)
完成初始化操作。#define DEFINE_SEMAPHORE(name) \
struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)
down(&mysemaphore)
执行信号量减一操作。// 打开设备
int misc_open(struct inode *inode,struct file*file)
{
// 加锁
down(&mysemaphore);
printk("misc_open is ok.\r\n");
return 0;
}
up(&mysemaphore)
完成信号量加一操作。// 关闭设备
int misc_close(struct inode * inode, struct file *file)
{
// 解锁
up(&mysemaphore);
printk("misc_close\r\n");
return 0;
}
由于此处的实现驱动较为简答,因此,不再过多描述。大家直接将此处的misc_open
、misc_close
替换自旋锁的函数即可。并且两者的测试程序也是一样的。测试结果如下:
互斥锁,可以将之当成初值为1的信号量来理解。虽然两者功能相同但是具体实现方式不同,但是当需要完成互斥功能时,直接使用互斥锁效率更高、更简洁,所以如果使用到的信号量“量值”为 1,一般将其修改为使用互斥锁。
互斥锁会导致休眠,所以在中断里面不能用互斥锁。同一时刻只能有一个线程持有互斥锁,并且只有持有者才可以解锁,并且不允许递归上锁和解锁。
互斥锁需要注意:
一次只能有一个任务持有互斥对象
只有所有者才能解锁互斥对象
不允许多次解锁
不允许递归锁定
必须通过API初始化互斥对象
互斥对象不能通过memset或复制进行初始化
任务不能在持有互斥对象的情况下退出
不得释放持有锁所在的内存区域
持有的互斥对象不能重新初始化
互斥不能用于硬件或软件中断
互斥锁定义结构体如下:
struct mutex {
atomic_long_t owner;
spinlock_t wait_lock;
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
struct list_head wait_list;
#ifdef CONFIG_DEBUG_MUTEXES
void *magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
};
实现互斥锁相关的API有:
函数名 | 作用 |
---|---|
mutex_init(mutex) | 初始化一个 struct mutex 指针 |
DEFINE_MUTEX(mutexname) | 初始化 struct mutex mutexname |
int mutex_is_locked(struct mutex *lock) | 判断 mutex 的状态1:被锁了(locked)0:没有被锁 |
void mutex_lock(struct mutex *lock) | 获得 mutex,如果暂时无法获得,休眠返回之时必定是已经获得了 mutex |
int mutex_trylock(struct mutex *lock) | 尝试获取 mutex,如果无法获得,不会休眠,返回值:1:获得了 mutex,0:没有获得注意,这个返回值含义跟一般的 mutex 函数相反 |
void mutex_unlock(struct mutex *lock) | 释放 mutex,会唤醒其他等待同一个 mutex 的线程 |
互斥锁的使用:使用互斥锁完成:同一个驱动仅允许一个应用程序打开。
DEFINE_MUTEX(mymutex)
,参数即为互斥锁的名字。或者也可先定义一个互斥锁变量struct mutex
,然后再使用函数 mutex_init
初始化。宏定义的原型为:#define DEFINE_MUTEX(mutexname) \
struct mutex mutexname = __MUTEX_INITIALIZER(mutexname)
mutex_lock(&mylock)
执行加锁操作。// 打开设备
int misc_open(struct inode *inode,struct file*file)
{
// 加锁
mutex_lock(&mylock);
printk("misc_open is ok.\r\n");
return 0;
}
mutex_unlock(&mylock)
完成解锁操作。// 关闭设备
int misc_close(struct inode * inode, struct file *file)
{
// 解锁
mutex_unlock(&mylock);
printk("misc_close\r\n");
return 0;
}
由于此处的实现驱动较为简答,因此,不再过多描述。大家直接将此处的misc_open
、misc_close
替换自旋锁的函数即可。并且两者的测试程序也是一样的。测试结果如下:
(是不是感觉三次的测试结果差不多,哈哈哈!😂😂😂)
互斥锁与信号量的区别:
信号量一般以同步的方式对共享资源进行控制,而互斥锁通过互斥实现共享资源的控制;
信号量可以对进程的共享资源进行控制,而互斥锁不行;
信号量的值为非负整数,而互斥锁的值只能为0或1;
互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到;mutex和二值信号量的区别在于mutex必须是同一个进程来释放
自旋锁与互斥锁的区别:
因为自旋锁不会引起调用者睡眠,所以效率比较高
自旋锁比较适用于锁使用者保持锁时间比较短的情况。
自旋锁容易造成死锁,所以需要安全使用它;