Linux并发与竞争实验

发布于:2025-09-02 ⋅ 阅读:(13) ⋅ 点赞:(0)

        在上一章中我们学习了Linux下的并发与竞争,并且学习了四种常用的处理并发和竞争的

机制:原子操作、自旋锁、信号量和互斥体。本章我们就通过四个实验来学习如何在驱动中使

用这四种机制。

1 原子操作实验

        本例程我们在第四十五章的gpioled.c文件基础上完成。在本节使用中我们使用原子操作来

实现对LED这个设备的互斥访问,也就是一次只允许一个应用程序可以使用LED灯。

1.1 实验程序编写

        本节实验在实验驱动文件gpioled.c的基础上修改而来。新建名为“7_atomic”的文件夹,然后在7_atomic文件夹里面创建vscode工程,工作区命名为“atomic”。将5_gpioled实验中的gpioled.c复制到7_atomic文件夹中,并且重命名为atomic.c。本节实验重点就是使用atomic来实现一次只能允许一个应用访问LED,所以我们只需要在atomic.c文件源码的基础上加上添加atomic相关代码即可,完成以后的atomic.c文件内容如下所示:

#include <linux/types.h> 
#include <linux/kernel.h> 
#include <linux/delay.h> 
#include <linux/ide.h> 
#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/errno.h> 
#include <linux/gpio.h> 
#include <linux/cdev.h> 
#include <linux/device.h> 
#include <linux/of.h> 
#include <linux/of_address.h> 
#include <linux/of_gpio.h> 
#include <asm/mach/map.h> 
#include <asm/uaccess.h> 
#include <asm/io.h> 

#define GPIOLED_CNT      1            /* 设备号个数  */ 
#define GPIOLED_NAME     "gpioled"    /* 名字  */ 
#define LEDOFF          0            /* 关灯  */ 
#define LEDON           1            /* 开灯  */ 

/* 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 lock;            /* 原子变量  */ 
}; 

struct gpioled_dev gpioled; /* led设备  */ 

static int led_open(struct inode *inode, struct file *filp) 
{ 
    if (!atomic_dec_and_test(&gpioled.lock)) { 
        atomic_inc(&gpioled.lock); 
        return -EBUSY; 
    } 
    filp->private_data = &gpioled; 
    return 0; 
} 

static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) 
{ 
    return 0; 
} 

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) 
{ 
    int retvalue; 
    unsigned char databuf[1]; 
    unsigned char ledstat; 
    struct gpioled_dev *dev = filp->private_data; 

    retvalue = copy_from_user(databuf, buf, cnt); 
    if(retvalue < 0) { 
        printk("kernel write failed!\r\n"); 
        return -EFAULT; 
    } 

    ledstat = databuf[0];          
    if(ledstat == LEDON) {   
        gpio_set_value(dev->led_gpio, 0); 
    } else if(ledstat == LEDOFF) { 
        gpio_set_value(dev->led_gpio, 1); 
    } 
    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; 
} 

static struct file_operations gpioled_fops = { 
    .owner = THIS_MODULE, 
    .open = led_open, 
    .read = led_read, 
    .write = led_write, 
    .release =  led_release, 
};

/*
 * @description  : 驱动入口函数 
 * @param         : 无 
 * @return        : 无 
 */ 
static int __init led_init(void) 
{ 
    int ret = 0; 
 
    /* 初始化原子变量 */ 
    atomic_set(&gpioled.lock, 1);   /* 原子变量初始值为1 */ 
 
    /* 设置LED所使用的GPIO */ 
    /* 1、获取设备节点:gpioled */ 
    gpioled.nd = of_find_node_by_path("/gpioled"); 
    if(gpioled.nd == NULL) { 
        printk("gpioled node not find!\r\n"); 
        return -EINVAL; 
    } else { 
        printk("gpioled node find!\r\n"); 
    } 
 
    /* 2、获取设备树中的gpio属性,得到LED所使用的LED编号 */ 
    gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0); 
    if(gpioled.led_gpio < 0) { 
        printk("can't get led-gpio"); 
        return -EINVAL; 
    } 
    printk("led-gpio num = %d\r\n", gpioled.led_gpio); 
 
    /* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */ 
    ret = gpio_direction_output(gpioled.led_gpio, 1); 
    if(ret < 0) { 
        printk("can't set gpio!\r\n"); 
    } 
 
    /* 注册字符设备驱动 */ 
    /* 1、创建设备号 */ 
    if (gpioled.major) {           /* 定义了设备号 */ 
        gpioled.devid = MKDEV(gpioled.major, 0); 
        register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME); 
    } else {                       /* 没有定义设备号 */ 
        alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);  /* 申请设备号 */ 
        gpioled.major = MAJOR(gpioled.devid);/* 获取分配号的主设备号 */ 
        gpioled.minor = MINOR(gpioled.devid);/* 获取分配号的次设备号 */ 
    } 
    printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);    
     
    /* 2、初始化cdev */ 
    gpioled.cdev.owner = THIS_MODULE; 
    cdev_init(&gpioled.cdev, &gpioled_fops); 
     
    /* 3、添加一个cdev */ 
    cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT); 
 
    /* 4、创建类 */ 
    gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME); 
    if (IS_ERR(gpioled.class)) { 
        return PTR_ERR(gpioled.class); 
    } 
 
    /* 5、创建设备 */ 
    gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME); 
    if (IS_ERR(gpioled.device)) { 
        return PTR_ERR(gpioled.device); 
    } 
     
    return 0; 
} 
 
/* 
 * @description  : 驱动出口函数 
 * @param         : 无 
 * @return        : 无 
 */ 
static void __exit led_exit(void) 
{ 
    /* 注销字符设备驱动 */ 
    cdev_del(&gpioled.cdev);      /* 删除cdev */ 
    unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); 
 
    device_destroy(gpioled.class, gpioled.devid); 
    class_destroy(gpioled.class); 
} 
 
module_init(led_init); 
module_exit(led_exit); 
MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("zuozhongkai");

        第42行,原子变量lock,用来实现一次只能允许一个应用访问LED灯,led_init驱动入口

函数会将lock的值设置为1。

        第57~60行,每次调用open函数打开驱动设备的时候先申请lock,如果申请成功的话就表

示LED灯还没有被其他的应用使用,如果申请失败就表示LED灯正在被其他的应用程序使用。

每次打开驱动设备的时候先使用atomic_dec_and_test函数将lock减1,如果atomic_dec_and_test

函数返回值为真就表示lock当前值为0,说明设备可以使用。如果atomic_dec_and_test函数返

回值为假,就表示lock当前值为负数(lock值默认是1),lock值为负数的可能性只有一个,那就

是其他设备正在使用LED。其他设备正在使用LED灯,那么就只能退出了,在退出之前调用

函数atomic_inc将lock加1,因为此时lock的值被减成了负数,必须要对其加1,将lock的值

变为0。

        第120行,LED灯使用完毕,应用程序调用close函数关闭的驱动文件,led_release函数执

行,调用atomic_inc释放lcok,也就是将lock加1。

第143行,初始化原子变量lock,初始值设置为1,这样每次就只允许一个应用使用LED

灯。

1.2 测试APP编写

#include "stdio.h" 
#include "unistd.h" 
#include "sys/types.h" 
#include "sys/stat.h" 
#include "fcntl.h" 
#include "stdlib.h" 
#include "string.h" 

#define LEDOFF     0 
#define LEDON      1 

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

    if(argc != 3){
        printf("Error Usage!\r\n"); 
        return -1; 
    } 

    filename = argv[1]; 

    fd = open(filename, O_RDWR); 
    if(fd < 0){
        printf("file %s open failed!\r\n", argv[1]); 
        return -1; 
    } 

    databuf[0] = atoi(argv[2]); 

    retvalue = write(fd, databuf, sizeof(databuf)); 
    if(retvalue < 0){
        printf("LED Control Failed!\r\n"); 
        close(fd); 
        return -1; 
    } 

    while(1) {
        sleep(5); 
        cnt++; 
        printf("App running times:%d\r\n", cnt); 
        if(cnt >= 5) break; 
    } 

    printf("App running finished!"); 
    retvalue = close(fd); 
    if(retvalue < 0){
        printf("file %s close failed!\r\n", argv[1]); 
        return -1; 
    } 
    return 0; 
}

        重点是加入了第63~68行的模拟占用25秒LED的代码。测试APP在获取到LED灯驱动的使用权以后会使用25S,在使用的这段时间如果有其他的应用也去获取LED灯使用权的话肯定会失败!

1.3 运行测试

        编译文件的方法不再赘述。

        驱动加载成功以后就可以使用atomicApp软件来测试驱动是否工作正常,输入如下命令以

后台运行模式打开LED灯,“&”表示在后台运行atomicApp这个软件:

./atomicApp /dev/gpioled 1&   //打开LED灯

        输入上述命令以后观察开发板上的红色LED灯是否点亮,然后每隔5秒都会输出一行“App

running times ”,如图所示:

        可以看出,atomicApp运行正常,输出了“App running times:1”和“App running

times:2”,这就是模拟25S占用,说明atomicApp这个软件正在使用LED灯。此时再输入如下

命令关闭LED灯:

./atomicApp    /dev/gpioled 0 //关闭LED灯 

        从图可以看出,打开/dev/gpioled失败!原因是在图中运行的atomicAPP软件正在占用/dev/gpioled,如果再次运行atomicApp软件去操作/dev/gpioled肯定会失败。必须等待图中的atomicApp运行结束,也就是25S结束以后其他软件才能去操作/dev/gpioled。这个就是采用原子变量实现一次只能有一个应用程序访问LED灯。

2 自旋锁实验

        上一节我们使用原子变量实现了一次只能有一个应用程序访问LED灯,本节我们使用自旋

锁来实现此功能。在使用自旋锁之前,先回顾一下自旋锁的使用注意事项:

①、自旋锁保护的临界区要尽可能的短,因此在open函数中申请自旋锁,然后在release函

数中释放自旋锁的方法就不可取。我们可以使用一个变量来表示设备的使用情况,如果设备被

使用了那么变量就加一,设备被释放以后变量就减1,我们只需要使用自旋锁保护这个变量即

可。

②、考虑驱动的兼容性,合理的选择API函数。

        综上所述,在本节例程中,我们通过定义一个变量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 实验程序编写

        本节实验在第上一节实验驱动文件atomic.c的基础上修改而来。新建名为“8_spinlock”的

文件夹,然后在8_spinlock文件夹里面创建vscode工程,工作区命名为“spinlock”。将7_atomic

实验中的atomic.c复制到8_spinlock文件夹中,并且重命名为spinlock.c。将原来使用atomic的

地方换为spinlock即可,其他代码不需要修改,完成以后的spinlock.c文件内容如下所示(有省

略):

1   #include <linux/types.h> 
2   #include <linux/kernel.h> 
3   #include <linux/delay.h> 
4   #include <linux/ide.h> 
5   #include <linux/init.h> 
...... 
17  /*************************************************************** 
18  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 
19  文件名    : spinlock.c 
20  作者     : 左忠凯 
21  版本     : V1.0 
22  描述     : 自旋锁实验,使用自旋锁来实现对实现设备的互斥访问 
23  其他     : 无 
24  论坛     : www.openedv.com 
25  日志     : 初版V1.0 2019/7/18 左忠凯创建 
26  ***************************************************************/ 
27  #define GPIOLED_CNT      1           /* 设备号个数   */ 
28  #define GPIOLED_NAME     "gpioled"   /* 名字     */ 
29  #define LEDOFF           0           /* 关灯     */ 
30  #define LEDON            1           /* 开灯     */ 
31   
32  /* gpioled设备结构体 */ 
33  struct gpioled_dev{ 
34      dev_t devid;            /* 设备号     */ 
35      struct cdev cdev;       /* cdev     */ 
36      struct class *class;    /* 类     */ 
37      struct device *device;  /* 设备     */ 
38      int major;              /* 主设备号     */ 
39      int minor;              /* 次设备号     */ 
40      struct device_node *nd; /* 设备节点     */ 
41      int led_gpio;           /* led所使用的GPIO编号 */ 
42      int dev_stats;          /* 设备状态,0,设备未使用;>0,设备已经被使用 */ 
43      spinlock_t lock;        /* 自旋锁     */ 
44  }; 
45   
46  struct gpioled_dev gpioled; /* led设备     */ 
47   
48  /* 
49   * @description   : 打开设备 
50   * @param – inode : 传递给驱动的inode 
51   * @param - filp  : 设备文件,file结构体有个叫做private_data的成员变量 
52   *                 一般在open的时候将private_data指向设备结构体。 
53   * @return        : 0 成功;其他 失败 
54   */ 
55  static int led_open(struct inode *inode, struct file *filp) 
56  { 
57      unsigned long flags; 
58      filp->private_data = &gpioled;  /* 设置私有数据 */ 
59   
60      spin_lock_irqsave(&gpioled.lock, flags);  /* 上锁     */ 
61      if (gpioled.dev_stats) {                  /* 如果设备被使用了  */
62          spin_unlock_irqrestore(&gpioled.lock, flags); /* 解锁 */ 
63          return -EBUSY; 
64      } 
65      gpioled.dev_stats++;    /* 如果设备没有打开,那么就标记已经打开了 */ 
66      spin_unlock_irqrestore(&gpioled.lock, flags);/* 解锁 */ 
67   
68      return 0; 
69  } 
...... 
116 /* 
117  * @description   : 关闭/释放设备 
118  * @param – filp  : 要关闭的设备文件(文件描述符) 
119  * @return        : 0 成功;其他 失败 
120  */ 
121 static int led_release(struct inode *inode, struct file *filp) 
122 { 
123     unsigned long flags; 
124     struct gpioled_dev *dev = filp->private_data; 
125  
126     /* 关闭驱动文件的时候将dev_stats减1 */ 
127     spin_lock_irqsave(&dev->lock, flags);   /* 上锁 */ 
128     if (dev->dev_stats) { 
129         dev->dev_stats--; 
130     } 
131     spin_unlock_irqrestore(&dev->lock, flags);/* 解锁 */ 
132  
133     return 0; 
134 } 
135  
136 /* 设备操作函数 */ 
137 static struct file_operations gpioled_fops = { 
138     .owner = THIS_MODULE, 
139     .open = led_open, 
140     .read = led_read, 
141     .write = led_write, 
142     .release = led_release, 
143 }; 
144  
145 /* 
146  * @description  : 驱动入口函数 
147  * @param        : 无 
148  * @return       : 无 
149  */ 
150 static int __init led_init(void) 
151 { 
152     int ret = 0; 
153 
154     /* 初始化自旋锁 */ 
155     spin_lock_init(&gpioled.lock); 
...... 
212     return 0; 
213 } 
214  
215 /* 
216  * @description  : 驱动出口函数 
217  * @param        : 无 
218  * @return       : 无 
219  */ 
220 static void __exit led_exit(void) 
221 { 
222     /* 注销字符设备驱动 */ 
223     cdev_del(&gpioled.cdev);/* 删除cdev */ 
224     unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); 
225  
226     device_destroy(gpioled.class, gpioled.devid); 
227     class_destroy(gpioled.class); 
228 } 
229  
230 module_init(led_init); 
231 module_exit(led_exit); 
232 MODULE_LICENSE("GPL"); 
233 MODULE_AUTHOR("zuozhongkai"); 

        第43行,dev_stats表示设备状态,如果为0的话表示设备还没有被使用,如果大于0的

话就表示设备已经被使用了。

        第44行,定义自旋锁变量lock。

        第61~67行,使用自旋锁实现对设备的互斥访问,第61行调用spin_lock_irqsave函数获

取锁,为了考虑到驱动兼容性,这里并没有使用spin_lock函数来获取锁。第62行判断

dev_stats是否大于0,如果是的话表示设备已经被使用了,那么就调用spin_unlock_irqrestore

函数释放锁,并且返回-EBUSY。如果设备没有被使用的话就在第66行将dev_stats加1,表

示设备要被使用了,然后调用spin_unlock_irqrestore函数释放锁。自旋锁的工作就是保护

dev_stats变量,真正实现对设备互斥访问的是dev_stats。

        第126~131行,在release函数中将dev_stats减1,表示设备被释放了,可以被其他的应用

程序使用。将dev_stats减1的时候需要自旋锁对其进行保护。

第155行,在驱动入口函数led_init中调用spin_lock_init函数初始化自旋锁。

1.2 测试APP

        与上面一致。

3 信号量实验

        本节我们来使用信号量实现了一次只能有一个应用程序访问 LED 灯,信号量可以导致休

眠,因此信号量保护的临界区没有运行时间限制,可以在驱动的open函数申请信号量,然后在

release函数中释放信号量。但是信号量不能用在中断中,本节实验我们不会在中断中使用信号

量。

1、LED驱动修改

        本节实验在第上一节实验驱动文件spinlock.c的基础上修改而来。新建名为“9_semaphore”

的文件夹,然后在9_semaphore文件夹里面创建vscode工程,工作区命名为“semaphore”。将

8_spinlock实验中的spinlock.c复制到9_semaphore文件夹中,并且重命名为semaphore.c。将原

来使用到自旋锁的地方换为信号量即可,其他的内容基本不变,完成以后的semaphore.c文件内

容如下所示(有省略):

  1 #include <linux/types.h>
  ...
 14 #include <linux/semaphore.h>
 15 #include <asm/mach/map.h>
 16 #include <asm/uaccess.h>
 17 #include <asm/io.h>
 18 /***************************************************************
 19 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
 20 文件名     : semaphore.c
 21 作者       : 左忠凯
 22 版本       : V1.0
 23 描述       : 信号量实验,使用信号量来实现对实现设备的互斥访问
 24 其他       : 无
 25 论坛       : www.openedv.com
 26 日志       : 初版V1.0 2019/7/18 左忠凯创建
 27 ***************************************************************/
 28 #define GPIOLED_CNT      1            /* 设备号个数   */
 29 #define GPIOLED_NAME     "gpioled"    /* 名字         */
 30 #define LEDOFF          0             /* 关灯         */
 31 #define LEDON           1             /* 开灯         */
 32
 33 /* gpioled设备结构体 */
 34 struct gpioled_dev{
 35     dev_t devid;                      /* 设备号        */
 36     struct cdev cdev;                 /* cdev          */
 37     struct class *class;              /* 类            */
 38     struct device *device;            /* 设备          */
 39     int major;                        /* 主设备号      */
 40     int minor;                        /* 次设备号      */
 41     struct device_node *nd;           /* 设备节点      */
 42     int led_gpio;                     /* led所使用的GPIO编号 */
 43     struct semaphore sem;             /* 信号量        */
 44 };
 45
 46 struct gpioled_dev gpioled;           /* led设备 */
 47
 48 /*
 49  * @description  : 打开设备
 50  * @param – inode : 传递给驱动的inode
 51  * @param - filp  : 设备文件,file结构体有个叫做private_data的成员变量
 52  *                    一般在open的时候将private_data指向设备结构体。
 53  * @return        : 0 成功;其他 失败
 54  */
 55 static int led_open(struct inode *inode, struct file *filp)
 56 {
 57     filp->private_data = &gpioled;    /* 设置私有数据 */
 58
 59     /* 获取信号量,进入休眠状态的进程可以被信号打断 */
 60     if (down_interruptible(&gpioled.sem)) {
 61         return -ERESTARTSYS;
 62     }
 63 #if 0
 64     down(&gpioled.sem);               /* 不能被信号打断 */
 65 #endif
 66
 67     return 0;
 68 }
 ...
114 /*
115  * @description   : 关闭/释放设备
116  * @param – filp  : 要关闭的设备文件(文件描述符)
117  * @return        : 0 成功;其他 失败
118  */
119 static int led_release(struct inode *inode, struct file *filp)
120 {
121     struct gpioled_dev *dev = filp->private_data;
122
123     up(&dev->sem);                    /* 释放信号量,信号量值加1 */
124
125     return 0;
126 }
127
128 /* 设备操作函数 */
129 static struct file_operations gpioled_fops = {
130     .owner = THIS_MODULE,
131     .open = led_open,
132     .read = led_read,
133     .write = led_write,
134     .release = led_release,
135 };
136
137 /*
138  * @description  : 驱动入口函数
139  * @param        : 无
140  * @return       : 无
141  */
142 static int __init led_init(void)
143 {
144     int ret = 0;
145
146     /* 初始化信号量 */
147     sema_init(&gpioled.sem, 1);
...
204     return 0;
205 }
206
207 /*
208  * @description  : 驱动出口函数
209  * @param        : 无
210  * @return       : 无
211  */
212 static void __exit led_exit(void)
213 {
214     /* 注销字符设备驱动 */
215     cdev_del(&gpioled.cdev);          /* 删除cdev */
216     unregister_chrdev_region(gpioled.devid, GPIOLED_CNT);
217
218     device_destroy(gpioled.class, gpioled.devid);
219     class_destroy(gpioled.class);
220 }
221
222 module_init(led_init);
223 module_exit(led_exit);
224 MODULE_LICENSE("GPL");
225 MODULE_AUTHOR("zuozhongkai");

        第14行,要使用信号量必须添加<linux/semaphore.h>头文件。

        第43行,在设备结构体中添加一个信号量成员变量sem。

        第60~65行,在open函数中申请信号量,可以使用down函数,也可以使用down_interruptible

函数。如果信号量值大于等于1就表示可用,那么应用程序就会开始使用LED灯。如果信号量

值为0就表示应用程序不能使用LED灯,此时应用程序就会进入到休眠状态。等到信号量值大

于1的时候应用程序就会唤醒,申请信号量,获取LED灯使用权。

        第123行,在release函数中调用up函数释放信号量,这样其他因为没有得到信号量而进

入休眠状态的应用程序就会唤醒,获取信号量。

        第147行,在驱动入口函数中调用sema_init函数初始化信号量sem的值为1,相当于sem

是个二值信号量。

        总结一下,当信号量sem为1的时候表示LED灯还没有被使用,如果应用程序A要使用

LED灯,先调用open函数打开/dev/gpioled,这个时候会获取信号量sem,获取成功以后sem的

值减1变为0。如果此时应用程序B也要使用LED灯,调用open函数打开/dev/gpioled就会因

为信号量无效(值为0)而进入休眠状态。当应用程序A运行完毕,调用close函数关闭/dev/gpioled

的时候就会释放信号量sem,此时信号量sem的值就会加1,变为1。信号量sem再次有效,表

示其他应用程序可以使用LED灯了,此时在休眠状态的应用程序A就会获取到信号量sem,

获取成功以后就开始使用LED灯。

2 测试APP

        与上面一致。

3 运行测试

        驱动加载成功以后就可以使用semaApp软件测试驱动是否工作正常,测试方法一样,先输入如下命令让semaApp软件模拟占用25S的LED灯:

./ semaApp /dev/gpioled 1&    //打开LED灯

        紧接着再输入如下命令关闭LED灯:

./ semaApp /dev/gpioled 0&    //关闭LED灯 

        注意两个命令都是运行在后台,第一条命令先获取到信号量,因此可以操作LED 灯,将

LED灯打开,并且占有25S。第二条命令因为获取信号量失败而进入休眠状态,等待第一条命

令运行完毕并释放信号量以后才拥有LED灯使用权,将LED灯关闭,运行结果如图所示:

4 互斥体实验

        前面我们使用原子操作、自旋锁和信号量实现了对LED灯的互斥访问,但是最适合互斥的

就是互斥体mutex了。本节我们来学习一下如何使用mutex实现对LED灯的互斥访问。

1.LED驱动修改

        本节实验在第上一节实验驱动文件semaphore.c的基础上修改而来。新建名为“10_mutex”

的文件夹,然后在10_mutex文件夹里面创建vscode工程,工作区命名为“mutex”。将9_semaphore

实验中的semaphore.c复制到10_mutex文件夹中,并且重命名为mutex.c。将原来使用到信号量

的地方换为mutex即可,其他的内容基本不变,完成以后的mutex.c文件内容如下所示(有省略):

  1 #include <linux/types.h> 
......
 17 #include <asm/io.h> 
 18 /*************************************************************** 
 19 Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 
 20 文件名     : mutex.c 
 21 作者        : 左忠凯 
 22 版本        : V1.0 
 23 描述        : 互斥体实验,使用互斥体来实现对实现设备的互斥访问 
 24 其他        : 无 
 25 论坛        : www.openedv.com 
 26 日志        : 初版V1.0 2019/7/18 左忠凯创建 
 27 ***************************************************************/ 
 28 #define GPIOLED_CNT         1            /* 设备号个数   */ 
 29 #define GPIOLED_NAME         "gpioled"  /* 名字   */ 
 30 #define LEDOFF                0            /* 关灯   */ 
 31 #define LEDON                 1           /* 开灯   */ 
 32 
 33 /* gpioled设备结构体 */ 
 34 struct gpioled_dev{ 
 35     dev_t devid;              /* 设备号   */ 
 36     struct cdev cdev;         /* cdev   */ 
 37     struct class *class;     /* 类   */ 
 38     struct device *device;   /* 设备   */ 
 39     int major;                /* 主设备号   */ 
 40     int minor;                /* 次设备号   */ 
 41     struct device_node  *nd; /* 设备节点   */ 
 42     int led_gpio;             /* led所使用的GPIO编号*/ 
 43     struct mutex lock;       /* 互斥体   */ 
 44 }; 
 45 
 46 struct gpioled_dev gpioled; /* led设备 */ 
 47 
 48 /* 
 49  * @description   : 打开设备 
 50  * @param – inode : 传递给驱动的inode 
 51  * @param - filp  : 设备文件,file结构体有个叫做private_data的成员变量 
 52  *                    一般在open的时候将private_data指向设备结构体。
 53  * @return        : 0 成功;其他 失败 
 54  */ 
 55 static int led_open(struct inode *inode, struct file *filp) 
 56 { 
 57     filp->private_data = &gpioled; /* 设置私有数据 */ 
 58 
 59     /* 获取互斥体,可以被信号打断 */ 
 60     if (mutex_lock_interruptible(&gpioled.lock)) { 
 61         return -ERESTARTSYS; 
 62     } 
 63 #if 0 
 64     mutex_lock(&gpioled.lock);  /* 不能被信号打断 */ 
 65 #endif 
 66 
 67     return 0; 
 68 } 
......
114 /* 
115  * @description   : 关闭/释放设备 
116  * @param – filp : 要关闭的设备文件(文件描述符) 
117  * @return         : 0 成功;其他 失败 
118  */ 
119 static int led_release(struct inode *inode, struct file *filp) 
120 { 
121     struct gpioled_dev *dev = filp->private_data; 
122 
123     /* 释放互斥锁 */ 
124     mutex_unlock(&dev->lock); 
125 
126     return 0; 
127 } 
128 
129 /* 设备操作函数 */ 
130 static struct file_operations gpioled_fops = { 
131     .owner = THIS_MODULE, 
132     .open = led_open, 
133     .read = led_read, 
134     .write = led_write, 
135     .release =  led_release, 
136 }; 
137 
138 /* 
139  * @description  : 驱动入口函数
140  * @param        : 无 
141  * @return       : 无 
142  */ 
143 static int __init led_init(void) 
144 { 
145     int ret = 0; 
146 
147     /* 初始化互斥体 */ 
148     mutex_init(&gpioled.lock); 
......
205     return 0; 
206 } 
......
223 module_init(led_init); 
224 module_exit(led_exit); 
225 MODULE_LICENSE("GPL"); 
226 MODULE_AUTHOR("zuozhongkai");

        第43行,定义互斥体lock。

        第60~65行,在open函数中调用mutex_lock_interruptible或者mutex_lock获取mutex,成

功的话就表示可以使用LED灯,失败的话就会进入休眠状态,和信号量一样。

        第124行,在release函数中调用mutex_unlock函数释放mutex,这样其他应用程序就可以

获取mutex了。

        第148行,在驱动入口函数中调用mutex_init初始化mutex。

        互斥体和二值信号量类似,只不过互斥体是专门用于互斥访问的。

3.运行测试

        与信号量一样。


网站公告

今日签到

点亮在社区的每一天
去签到