学会使用设备树后,要学习linux驱动编写中容易出现的”并发与竞争“。
代码是在之前代码的基础上进行修改。
并发与竞争
(本部分来自于正点原子pdf)
什么是并发与竞争,为什么会出现并发与竞争:
要保护的内容是:
什么是原子操作:
Linux提供的API函数
atomic
整形操作
位操作
自旋锁
信号量 互斥体
代码修改(atomic)
设备结构体
多了atomic_t lock;
设备操作函数
模块入口/出口函数
__init :
APP程序
在之前的APP.c中修改,添加了部分内容。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
/*
* @description : main主程序
* @param - argc : argv数组元素个数
* @param - argv : 具体参数
* @return : 0 成功;其他 失败
*/
int main(int argc, char *argv[])
{
int fd, ret;
int cnt = 0;
unsigned char buf[1];
if(3 != argc) {
printf("Usage:\n"
"\t./atomicAPP /dev/gpio-led 1 @ close LED\n"
"\t./atomicAPP /dev/gpio-led 0 @ open LED\n"
);
return -1;
}
/* 打开设备 */
fd = open(argv[1], O_RDWR);
if(0 > fd) {
printf("file %s open failed!\r\n", argv[1]);
return -1;
}
/* 将字符串转换为int型数据 */
buf[0] = atoi(argv[2]);
/* 向驱动写入数据 */
ret = write(fd, buf, sizeof(buf));
if(0 > ret){
printf("LED Control Failed!\r\n");
close(fd);
return -1;
}
/* 模拟占用25s LED设备 */
for(;;) {
sleep(5);
cnt++;
printf("APP running times: %d\r\n", cnt);
if(cnt >= 5) break;
}
printf("APP running finished\r\n");
/* 关闭设备 */
close(fd);
return 0;
}
代码修改(自旋锁)
代码在atomic.c的基础上修改
自旋锁(上锁、解锁) ,控制的是dev_stats变量。也就是在改变dev_stats变量的值前,要上锁(其他设备不能修改dev_stats值),修改值后,要进行解锁。
代码修改(信号量)
在spinlock.c的基础上进行修改。
运行
atomic
正点原子pdf:
实际运行: failed
后来查看代码,发现led_open操作函数的if条件写错了。
因为atomic_dec_and_test函数,是用来上锁的:将lock值减1,然后判断是否为0,如果是,则返回1(上锁成功,设备占用成功),否则返回1。
所以原来的代码是:
if(atomic_dec_and_test(&gpioled.lock))
{
// error
return -EBUSY;
}
即,设备占用成功,atomic_dec_and_test返回值为1,满足if条件,进入if条件下的语句(但是是error的语句) ,认为设备busy,占用失败。【相互矛盾了不是】
修改代码:
修改代码后: succeed
自旋锁
信号量
总结
1. Linux是一个多任务系统。当多个任务共同操作同一段内存或同一个设备(共享资源)时,容易出现并发与竞争的情况。所以要处理对共享资源的并发访问。
2. 并发与竞争出现的几个主要原因:多线程并发访问;抢占式并发访问;中断程序并发访问;SMP(多核)核间并发访问,etc。
3. 在编写驱动的时候,就要注意避免并发核防止竞争访问。不然,会给后期埋下隐患。
4. 什么是共享资源,哪些内容需要保护。如,全局变量,设备结构体,etc。要弄清楚需要保护的内容或数据。
5. 几种处理并发和竞争的方法:原子操作;自旋锁;信号量;互斥体,etc。
- 1. 原子操作,是指不能进一步分割的操作。一般用于变量或者位操作。e.g 对某个变量进行赋值,将这个赋值的过程作为一个整体进行运行,即一个原子。
(整形操作)需要用到:atomic_t结构体。初始化、自增、自减函数。
(位操作)对内存直接操作。
- 2. 自旋锁,保护结构体变量。 当一个线程访问某共享资源前,要获取相应的锁,此线程不释放锁,其他线程就不能获取。 锁只能被一个线程持有。 如果另一个线程想要获取锁,就处于循环-等待的状态,直至锁可用。
缺点:等待自旋锁的线程会一直等待,会浪费处理器时间,降低系统性能。
适用于:短时期的轻量级加锁。
注意事项:被自旋锁保护的临界区一定不能调用任何能够引起睡眠和阻塞的API函数。 不能递归申请自旋锁。 取锁之前一定要禁止本地中断,否则可能导致死锁。
需要:spinlock_t结构体。初始化、加锁、解锁函数。
- 3. 信号量,用于控制对共享资源的访问。
特点:可以使线程(等待的)进入休眠状态。 使用信号量会提高处理器的使用效率(不用像自旋锁那样一直在等待)。
适用于:占用资源比较久的场合。
缺点:信号量的开销比自旋锁大,因为信号量使线程进入休眠状态后会切换线程。
注意事项:不能用于中断中。因为中断不能休眠。
需要:struct semaphore。初始化、获取信号量、释放信号量的函数。
- 4. 互斥体,即,一次只有一个线程可以访问共享资源,不能递归申请互斥体。
适用于:建议需要互斥访问的地方用互斥体。
特点:互斥体可以导致休眠。因此中断中不能使用。 和信号量一样,互斥体保护的临界区可以调用引起阻塞的API函数。 不能递归上锁和解锁。
6. 注意事项
中断中只能使用自旋锁。