本章实验由第六章“设备树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的特殊情况。
读写自旋锁和读写信号量是条件放宽的自旋锁和信号量,它们允许多个执行单元对共享资源的并发读,但不能并发写。