通过原子操作、自旋锁、信号量和互斥体这四个实验来学习如何在驱动中使用这四种机制。
1、原子操作
使用原子操作来实现对 LED 这个设备的互斥访问,也就是一次只允许一个应用程序可以使用 LED 灯。
Ubuntu下:
cd /limux/IMX6ULL/Linux_Drivers/
mkdir 8_atomic
cp 6_gpioled/ * 8_atomic/ -rf
cp 6_gpioled/.vscode 8_atomic/ -rf
cd 8_atomic/
make clean
rm gpioled.code-workspace
mv gpioled.c atomic.c
mv ledAPP.c atomicAPP.c
1.1、修改设备树
不需要对设备树做任何的修改。
1.2、修改LED驱动文件
只需要在 atomic.c 文件源码的基础上加上添加 atomic 相关代码即可。
添加头文件
#include <linux/atomic.h>
在gpioled设备结构体中添加原子变量:
struct gpioled_dev{
...
atomic_t lock;
}
还需要有一个原子初始化操作函数。为了表达这个驱动能不能被用,所以初始化的参数里面是1。
static int __init led_init(void)
{
/* 初始化原子变量 */
atomic_set(&gpioled.lock, 1);
}
现在默认的值是1,当第一次led_open时,先判断是否小于等于0,这就涉及到了原子变量的读,如果小于等于0,则说明被用过了。否则,减去1。
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled;
if(atomic_read(&gpioled.lock) <= 0){
return -EBUSY;
}else{
atomic_dec(&gpioled.lock);
}
return 0;
}
当释放掉的时候就必须加了,
static int led_release(struct inode *inode, struct file *filp)
{
struct gpioled_dev *dev = filp->private_data;
atomic_inc(&dev->lock);/* 释放驱动 */
return 0;
}
修改测试APP,
int cnt = 0;
/* 模拟应用占用驱动25s */
while(1){
sleep(5);
cnt++;
printf("App Runing times:%d\r\n", cnt);
if(cnt >= 5) break;
}
printf("App Runing finished!\r\n");
编译:
make
arm-linux-gnueabihf-gcc atomticAPP.c -o atomicAPP
sudo cp atomic.ko atomicAPP /home/yang/linux/nfs/rootfs/lib/modules/4.1.15 -f
开发板上电:
/lib/modules/4.1.15 # depmod
/lib/modules/4.1.15 # modpeobe atomic.ko
/lib/modules/4.1.15 # ./atomicAPP /dev/gpioled 1
运行过程中,无法输入
/lib/modules/4.1.15 # ps //查看当前运行进程
/lib/modules/4.1.15 # ./atomicAPP /dev/gpioled 1 & //后台运行
我们也可以替换为
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled;
//如果是1,则减1为0,如果为0,则减1为-1
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;
}
2、自旋锁操作
使用自旋锁操作来实现对 LED 这个设备的互斥访问,也就是一次只允许一个应用程序可以使用 LED 灯。
Ubuntu下:
cd /limux/IMX6ULL/Linux_Drivers/
mkdir 9_spinlock
cp 8_atomic/ * 9_spinlock/ -rf
cp 8_atomic/.vscode 9_spinlock/ -rf
cd 9_spinlock/
make clean
rm atomic.code-workspace
mv atomic.c spinlock.c
mv atomic.c spinlock.c
①、自旋锁保护的临界区要尽可能的短,因此在 open 函数中申请自旋锁,然后在 release 函数中释放自旋锁的方法就不可取。我们可以使用一个变量来表示设备的使用情况,如果设备被使用了那么变量就加一,设备被释放以后变量就减 1,我们只需要使用自旋锁保护这个变量即可。
②、通过定义一个变量 dev_stats 表示设备的使用情况,dev_stats为 0 的时候表示设备没有被使用,dev_stats 大于 0 的时候表示设备被使用。驱动 open 函数中先判断 dev_stats 是否为 0,也就是判断设备是否可用,如果为 0 的话就使用设备,并且将 dev_stats加 1,表示设备被使用了。使用完以后在 release 函数中将 dev_stats 减 1,表示设备没有被使用了。因此真正实现设备互斥访问的是变量 dev_stats,但是我们要使用自旋锁对 dev_stats 来做保护。
2.1、修改设备树
不需要对设备树做任何的修改。
2.2、修改LED驱动文件
只需要在 spinlock.c 文件源码的基础上加上添加 spinlock 相关代码即可。
在gpioled设备结构体中添加自旋锁变量:
struct gpioled_dev{
...
int dev_status; //0表示设备可以使用,1表示设备不可使用
spinlock_t lock;
}
还需要有一个自旋锁初始化操作函数。
static int __init led_init(void)
{
/* 初始化自旋锁 */
spin_lock_init(&gpioled.lock);
gpioled.dev_status = 0;
...
}
在open中使用dev_status来标记lock变量有没有使用。先加锁,再解锁,中间是代码临界区,对dev_status的保护。
static int led_open(struct inode *inode, struct file *filp)
{
filp->private_data = &gpioled;
spin_lock(&gpioled.lock);//加锁
if(gpioled.dev_staus){//驱动不能使用
spin_unlock(&gpioled.lock);
return -EBUSY;
}
gpioled.dev_staus++;//标记被使用
spin_unlock(&gpioled.lock);
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
struct gpioled_dev *dev = filp->private_data;
spin_lock(&dev->lock);//加锁
if(dev->dev_staus){//驱动不能使用
dev->dev_staus--;//标记驱动可以使用
}
gpioled.dev_staus++;//标记被使用
spin_unlock(&dev->lock);
return 0;
}
编译:
make
arm-linux-gnueabihf-gcc spinlockAPP.c -o spinlockAPP
sudo cp atomic.ko spinlockAPP /home/yang/linux/nfs/rootfs/lib/modules/4.1.15 -f
推荐上锁的时候使用:spin_lock_irqsave();
static int led_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_staus){//驱动不能使用
spin_unlock(&gpioled.lock);
return -EBUSY;
}
gpioled.dev_staus++;//标记被使用
//spin_unlock(&gpioled.lock);
spin_unlock_irqrestore(&gpioled.lock, irqflag);
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
unsigned long irqflag;
struct gpioled_dev *dev = filp->private_data;
//spin_lock(&dev->lock);//加锁
spin_lock_irqsave(&gpioled.lock, irqflag);
if(dev->dev_staus){//驱动不能使用
dev->dev_staus--;//标记驱动可以使用
}
gpioled.dev_staus++;//标记被使用
//spin_unlock(&dev->lock);
spin_unlock_irqrestore(&gpioled.lock, irqflag);
return 0;
}
3、信号量
使用信号量实现了一次只能有一个应用程序访问 LED 灯,信号量可以导致休眠,因此信号量保护的临界区没有运行时间限制,可以在驱动的 open 函数申请信号量,然后在release 函数中释放信号量。但是信号量不能用在中断中。
在gpioled设备结构体中添加信号量变量:
struct gpioled_dev{
...
struct semaphore sem; //0表示设备可以使用,1表示设备不可使用
}
参看文档。
信号量调用完之后,会进入一个休眠,然后再唤醒。