I.MX6U嵌入式Linux驱动开发(8)互斥

通过原子操作、自旋锁、信号量和互斥体这四个实验来学习如何在驱动中使用这四种机制。

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表示设备不可使用
}

参看文档。
信号量调用完之后,会进入一个休眠,然后再唤醒。

  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值