Linux驱动学习记录-9.并发与竞争实验

本章实验由第六章“设备树led驱动”代码修改而来,设备树不需要改动。


一、原子操作

1.驱动程序

实现一次只能操作一个led,当这个驱动正在操作这个led时,其他驱动不可访问这个led。
思路:在设备结构体定义一个原子整形变量,并在入口函数初始化为1,在使用open打开驱动的时候,先检查原子变量的值是否为1,是的话减一并设置私有数据,驱动正常运行。如果不是1,代表驱动在被占用,返回APP文件,打开驱动失败。在关闭设备函数release里要释放原子变量,加一。
以下程序修改自gpioled.c,撷取部分。

/* gpioled设备结构体 */
struct gpioled_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */
	int led_gpio;			/* led所使用的GPIO编号		*/
	atomic_t num;           /*原子整形变量*/
};
struct gpioled_dev gpioled;
/*这里对指针和结构体做下说明
open函数输入参数filp是struct file类型指针,即此file结构体的地址。
另外若a是一个结构体地址,b是整形结构体变量,则有:
a->b 等价于 (*a).b,且a->b是一个整形数,是实体而非指针;
‘->’左边必须是指针,而‘.’前面必须是实体。引出b后,整体就是b的类型。
回到open函数,atomic函数输入都是原子变量的地址(指针),gpioled是定义的结构体实体,此处用gpioled.num,得到原子变量,在前面加&获取原子变量的地址。
file结构体有一个变量private_data也是一个结构体指针,指针filp用‘->’获取private_data(结构体指针类型),使其等于gpoiled的地址。
*/
static int led_open(struct inode *inode, struct file *filp)
{
	if(atomic_dec_and_test(&gpioled.num)){ /*如果自减一是0,则原来是1,可操作*/
		filp->private_data = &gpioled; /* 设置私有数据 */
		return 0;
	}
	else{/*否则减一是0,需要加一返回负值,驱动没打开*/
		atomic_inc(&gpioled.num);
		return -1;
	}
}
/*
这里先定义gpioled_dev结构体指针变量dev,使其等于private_data(指针),atomic函数输入是地址,先用dev->获取原子变量,再加&得到地址。
*/
static int led_release(struct inode *inode, struct file *filp)
{
	struct gpioled_dev *dev = filp->private_data;
	atomic_inc(&dev->num);
	return 0;
}

static int __init led_init(void)
{
	atomic_set(&gpioled.num, 1);
	/* 设置LED所使用的GPIO */
	
	/* 注册字符设备驱动 */
	return 0;
}

2.测试

int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	unsigned char databuf[1];

	filename = argv[1];

	/* 打开led驱动 */
	fd = open(filename, O_RDWR);

	databuf[0] = atoi(argv[2]);	/* 要执行的操作:打开或关闭 */

	/* 向/dev/led文件写入数据 */
	retvalue = write(fd, databuf, sizeof(databuf));
	
	while(1){
		sleep(5);//延迟5s
		cnt++;
		printf("App running times:%d\r\n", cnt);
		if(cnt >= 5) break; //5次 25s
	}
	retvalue = close(fd); /* 关闭文件 */

	return 0;
}

在驱动程序卡在while时,还没close,原子变量还是0,再次执行APP,驱动无法打开。
执行ledApp时,输入&,可在后台运行程序,输入框仍可以用。

./ledApp /dev/led 1&

二、自旋锁实验

1.驱动程序

自旋锁保护的临界区尽可能短,因此在open函数加锁,release函数开锁不可取。思路:申请一个自旋锁标志位为1,在open函数判断flag是否为0,若是则返回负值,打开失败。若是1,则加锁,flag自减1,解锁。再设置私有数据。release函数加锁,flag自加1,解锁。

/* gpioled设备结构体 */
struct gpioled_dev{
	/**/
	spinlock_t spinlock;	/* spinlock*/
	int flag;               /*自旋锁状态*/
};
struct gpioled_dev gpioled;	/* led设备 */
static int led_open(struct inode *inode, struct file *filp)
{
	unsigned long flag;
	spin_lock_irqsave(&gpioled.spinlock, flag);
	if(gpioled.flag == 0) {
		spin_unlock_irqsave(&gpioled.spinlock, flag);
		return -1;
	}
	gpioled.flag --;
	spin_unlock_irqsave(&gpioled.spinlock, flag);
	filp->private_data = &gpioled;
	return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
	spin_lock_irqsave(&gpioled.lock, flag);
	gpioled.flag = 1;
	spin_unlock_irqsave(&gpioled.spinlock, flag);
	return 0;
}
static int __init led_init(void)
{
	int ret = 0;
	unsigned long flag;
	spin_lock_init(&gpioled.lock);
	spin_lock_irqsave(&gpioled.lock, flag);
	gpioled.flag = 1;
	spin_unlock_irqsave(&gpioled.spinlock, flag);
	/* 设置LED所使用的GPIO */

	/* 注册字符设备驱动 */

	return 0;
}

2.测试

和前一节一样。

三、信号量

1.驱动程序

信号量导致休眠,因此信号量保护的临界区没有时间限制,可以在open函数申请信号量,release函数释放信号量。

#include <linux/semaphore.h>
struct gpioled_dev{
	/**/
	struct semaphore sema;	/* semaphore*/
};

struct gpioled_dev gpioled;	/* led设备 */
static int led_open(struct inode *inode, struct file *filp)
{
	if(down_trylock(&gpioled.sema))
		return -1;
	filp->private_data = &gpioled;
	return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
	struct gpioled_dev *dev = filp->private_data
	up(&dev->sema);
	return 0;
}
static int __init led_init(void)
{
	int ret = 0;
	sema_init(&gpioled.sema, 1);
	/* 设置LED所使用的GPIO */

	/* 注册字符设备驱动 */
	return 0;
}

2.测试

和上节一样。

四、互斥体实验

1.驱动程序

上一节信号量实验,把信号量设置为1,相当于互斥体,对于互斥建议使用专门的互斥体。

/* gpioled设备结构体 */
struct gpioled_dev{
	/**/
	struct mutex mutex;	
};

struct gpioled_dev gpioled;	/* led设备 */
static int led_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &gpioled;
	/*获取互斥体,可以被信号打断*/
	if(mutex_lock_interruptible(&gpioled.mutex))
		return -1;
	return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
	struct gpioled_dev *dev = filp->private_data
	mutex_unlock(&dev.mutex);
	return 0;
}
static int __init led_init(void)
{
	int ret = 0;
	mutex_init(&gpioled.mutex);
	/* 设置LED所使用的GPIO */

	/* 注册字符设备驱动 */
	return 0;
}

2.测试

和前一节一样。

五、总结

原子操作只能对整形使用,因此自旋锁和信号量应用较广泛。
自旋锁会导致死循环,锁定期间不允许阻塞,因此要求临界区小。信号量允许临界期大。互斥体是信号量为1的特殊情况。
读写自旋锁和读写信号量是条件放宽的自旋锁和信号量,它们允许多个执行单元对共享资源的并发读,但不能并发写。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值