学会使用设备树后,要学习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. 注意事项
中断中只能使用自旋锁。




















