欢迎光临
我们一直在努力

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(信号量实现 LED 设备互斥访问)--- Ubuntu20.04信号量实验

🎬 渡水无言:个人主页渡水无言

❄专栏传送门: linux专栏》《嵌入式linux驱动开发》《linux系统移植专栏》

❄专栏传送门: freertos专栏》 《STM32 HAL库专栏 ⭐️流水不争先,争的是滔滔不绝

 📚博主简介:第二十届中国研究生电子设计竞赛全国二等奖 |国家奖学金 | 省级三好学生

| 省级优秀毕业生获得者 | csdn新星杯TOP18 | 半导纵横专栏博主 | 211在读研究生

在这里主要分享自己学习的linux嵌入式领域知识;有分享错误或者不足的地方欢迎大佬指导,也欢迎各位大佬互相三连

目录

前言

一、实验基础说明

1.1、信号量简介

1.2 本次实验设计思路

二、硬件原理分析(看过之前博客的可以忽略)

三、实验程序编写

3.1 信号量 LED 驱动代码(spinlock.c)

3.2、驱动代码分段解析

3.2.1、设备结构体定义(30-41 行)

3.2.2、设备打开函数(52-65 行)

3.2.3、设备写操作函数(88-109 行)

3.2.4、设备释放函数(116-123 行)

3.2.5、设备操作函数集(126-132 行)

3.2.6驱动入口函数(139-202 行)

3.2.7、驱动出口函数(209-217 行)

3.3、测试APP编写

  四、运行测试

4.1、编译驱动程序和测试 APP

4.2、运行测试

总结


前言

前两期博客我们分别用原子操作(轻量变量保护)、自旋锁(短临界区保护)实现了 LED 设备的互斥访问,但二者都有明显限制:原子操作仅能保护单个整形变量,自旋锁临界区不能包含休眠操作。本次我们引入信号量—— 支持休眠的并发保护机制,可保护任意时长的临界区,是 Linux 驱动中处理 “慢临界区” 的核心方案。


一、实验基础说明

1.1、信号量简介

学过FreeRTOS的小伙伴,对信号量肯定不陌生吧!它本质是一种同步机制,Linux内核也提供了信号量功能,核心作用就是控制共享资源的访问,尤其适合临界区较长的场景。

给大家举个最直观的生活例子,一看就懂:

某个停车场有100个停车位(这就是共享资源),大家都可以来停车。你开车到停车场,肯定要先看一下当前停了多少车、还有没有空位——这个“当前停车数量”,就相当于信号量;具体的停车数量,就是信号量的值。

当信号量值达到100时,说明停车场满了,你只能等着;等有车开出去,停车数量减1(信号量减1),就空出一个车位,你就能开进去,停车数量加1(信号量加1)。这个场景用的,就是计数型信号量。

如下图所示:

再对比一下前面讲的自旋锁,大家就能快速分清两者的区别:

假设A、B、C合租一套房,只有一个厕所(共享资源),一次只能一个人用。某天早上A先去上厕所了,过一会儿B也想用:

  • 如果B一直站在厕所门口等,不做其他事,直到A出来——这就是自旋锁(忙等)。

  • 如果B告诉A“你出来叫我一声”,然后自己回房间睡觉,等A出来再通知他——这就是信号量(休眠等待)。

从这个例子就能看出信号量的核心优势:不会浪费CPU资源。因为等待的线程会进入休眠,CPU可以去处理其他任务,等资源释放了再唤醒它。

但凡事有得有失,信号量的开销比自旋锁大——因为线程休眠、唤醒会涉及线程切换,这是有性能损耗的。

👉 总结信号量的3个核心特点(记牢!):

  • 支持线程休眠,适合临界区较长、占用资源较久的场景(比如拷贝数据、I/O操作)。

  • 不能用于中断上下文!因为中断不能休眠,而信号量会导致线程休眠,用在中断里直接报错。

  • 不适合短临界区场景:频繁的线程休眠、切换,开销比自旋锁的“忙等”更大,反而降低系统性能。

  • 再补充一个关键知识点:信号量分两种,按需选择即可:

    • 计数型信号量:初始化时信号量值>1,允许多个线程同时访问共享资源(比如停车场100个车位,允许100辆车同时停)。

    • 二值信号量:初始化时信号量值=1,只允许一个线程访问共享资源,本质就是简易的互斥(比如厕所,一次只能一个人用)。

    简单理解:信号量就像一把把钥匙,信号量值就是钥匙的数量,要访问资源必须先拿一把钥匙(获取信号量),用完再还回去(释放信号量)。钥匙被拿完了,其他人就只能等着。

    1.2 本次实验设计思路

    信号量支持休眠,因此无需像自旋锁那样 “短临界区保护”,可直接在open函数申请信号量、release函数释放信号量:

    初始化信号量计数值为 1(二值信号量),代表 LED 设备空闲; open函数调用down_interruptible申请信号量: 成功:计数值减为 0,独占 LED 设备; 失败:进程休眠,等待其他进程释放信号量; release函数调用up释放信号量:计数值恢复为 1,唤醒休眠的进程; 硬件、设备树无需修改,仅替换同步机制为信号量。

     实验在此次博客基础上实现:

    ARM Linux 驱动开发篇— Linux 并发与竞争实验(自旋锁实现 LED 设备互斥访问)— Ubuntu20.04自旋锁实验-CSDN博客

    二、硬件原理分析(看过之前博客的可以忽略)

     

    从图中可以看出,LED0 接到了 GPIO_3 上,GPIO_3 就是 GPIO1_IO03,当 GPIO1_IO03 输出低电平 (0) 的时候发光二极管 LED0 就会导通点亮,当 GPIO1_IO03 输出高电平 (1) 的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 GPIO1_IO03 的输出电平,输出 0 就亮,输出 1 就灭。

    三、实验程序编写

    本次实验基于上一节自旋锁的 LED 驱动修改,仅替换同步机制为信号量,硬件和设备树无需修改。把spinlock.c改成 semaphore.c文件。

    3.1 信号量 LED 驱动代码(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>
    6 #include <linux/module.h>
    7 #include <linux/errno.h>
    8 #include <linux/gpio.h>
    9 #include <linux/cdev.h>
    10 #include <linux/device.h>
    11 #include <linux/of.h>
    12 #include <linux/of_address.h>
    13 #include <linux/of_gpio.h>
    14 #include <linux/semaphore.h>
    15 #include <asm/mach/map.h>
    16 #include <asm/uaccess.h>
    17 #include <asm/io.h>
    18 /***************************************************************
    19 文件名: semaphore.c
    20 版本 : V1.0
    21 描述 : 信号量实验,使用信号量来实现对实现设备的互斥访问
    22 其他 : 无
    23 ***************************************************************/
    24 #define GPIOLED_CNT1 /* 设备号个数 */
    25 #define GPIOLED_NAME"gpioled"/* 名字 */
    26 #define LEDOFF 0/* 关灯 */
    27 #define LEDON 1/* 开灯 */
    28
    29
    30 /* gpioled设备结构体 */
    31 struct gpioled_dev{
    32 dev_t devid;/* 设备号 */
    33 struct cdev cdev;/* cdev */
    34 struct class *class;/* 类 */
    35 struct device *device;/* 设备 */
    36 int major;/* 主设备号 */
    37 int minor;/* 次设备号 */
    38 struct device_node*nd; /* 设备节点 */
    39 int led_gpio;/* led所使用的GPIO编号*/
    40 struct semaphore sem;/* 信号量 */
    41 };
    42
    43 struct gpioled_dev gpioled;/* led设备 */
    44
    45 /*
    46 * @description: 打开设备
    47 * @param – inode : 传递给驱动的inode
    48 * @param – filp : 设备文件,file结构体有个叫做private_data的成员变量
    49 * 一般在open的时候将private_data指向设备结构体。
    50 * @return : 0 成功;其他 失败
    51 */
    52 static int led_open(struct inode *inode, struct file *filp)
    53 {
    54 filp->private_data = &gpioled; /* 设置私有数据 */
    55
    56 /* 获取信号量 */
    57 if (down_interruptible(&gpioled.sem)) { /* 获取信号量,进入休眠状态的进程可以被信号打断 */
    58 return -ERESTARTSYS;
    59 }
    60 #if 0
    61 down(&gpioled.sem);/* 不能被信号打断 */
    62 #endif
    63
    64 return 0;
    65 }
    66
    67 /*
    68 * @description: 从设备读取数据
    69 * @param – filp : 要打开的设备文件(文件描述符)
    70 * @param – buf : 返回给用户空间的数据缓冲区
    71 * @param – cnt : 要读取的数据长度
    72 * @param – offt : 相对于文件首地址的偏移
    73 * @return : 读取的字节数,如果为负值,表示读取失败
    74 */
    75 static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
    76 {
    77 return 0;
    78 }
    79
    80 /*
    81 * @description: 向设备写数据
    82 * @param – filp : 设备文件,表示打开的文件描述符
    83 * @param – buf : 要写给设备写入的数据
    84 * @param – cnt : 要写入的数据长度
    85 * @param – offt : 相对于文件首地址的偏移
    86 * @return : 写入的字节数,如果为负值,表示写入失败
    87 */
    88 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
    89 {
    90 int retvalue;
    91 unsigned char databuf[1];
    92 unsigned char ledstat;
    93 struct gpioled_dev *dev = filp->private_data;
    94
    95 retvalue = copy_from_user(databuf, buf, cnt);
    96 if(retvalue < 0) {
    97 printk("kernel write failed!\\r\\n");
    98 return -EFAULT;
    99 }
    100
    101 ledstat = databuf[0];/* 获取状态值 */
    102
    103 if(ledstat == LEDON) {
    104 gpio_set_value(dev->led_gpio, 0);/* 打开LED灯 */
    105 } else if(ledstat == LEDOFF) {
    106 gpio_set_value(dev->led_gpio, 1);/* 关闭LED灯 */
    107 }
    108 return 0;
    109 }
    110
    111 /*
    112 * @description: 关闭/释放设备
    113 * @param – filp : 要关闭的设备文件(文件描述符)
    114 * @return : 0 成功;其他 失败
    115 */
    116 static int led_release(struct inode *inode, struct file *filp)
    117 {
    118 struct gpioled_dev *dev = filp->private_data;
    119
    120 up(&dev->sem);/* 释放信号量,信号量值加1 */
    121
    122 return 0;
    123 }
    124
    125 /* 设备操作函数 */
    126 static struct file_operations gpioled_fops = {
    127 .owner = THIS_MODULE,
    128 .open = led_open,
    129 .read = led_read,
    130 .write = led_write,
    131 .release = led_release,
    132 };
    133
    134 /*
    135 * @description: 驱动入口函数
    136 * @param : 无
    137 * @return : 无
    138 */
    139 static int __init led_init(void)
    140 {
    141 int ret = 0;
    142
    143 /* 初始化信号量 */
    144 sema_init(&gpioled.sem, 1);
    145
    146 /* 设置LED所使用的GPIO */
    147 /* 1、获取设备节点:gpioled */
    148 gpioled.nd = of_find_node_by_path("/gpioled");
    149 if(gpioled.nd == NULL) {
    150 printk("gpioled node not find!\\r\\n");
    151 return -EINVAL;
    152 } else {
    153 printk("gpioled node find!\\r\\n");
    154 }
    155
    156 /* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
    157 gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    158 if(gpioled.led_gpio < 0) {
    159 printk("can't get led-gpio");
    160 return -EINVAL;
    161 }
    162 printk("led-gpio num = %d\\r\\n", gpioled.led_gpio);
    163
    164 /* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
    165 ret = gpio_direction_output(gpioled.led_gpio, 1);
    166 if(ret < 0) {
    167 printk("can't set gpio!\\r\\n");
    168 }
    169
    170 /* 注册字符设备驱动 */
    171 /* 1、创建设备号 */
    172 if (gpioled.major) {/* 定义了设备号 */
    173 gpioled.devid = MKDEV(gpioled.major, 0);
    174 register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    175 } else {/* 没有定义设备号 */
    176 alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);/* 申请设备号 */
    177 gpioled.major = MAJOR(gpioled.devid);/* 获取分配号的主设备号 */
    178 gpioled.minor = MINOR(gpioled.devid);/* 获取分配号的次设备号 */
    179 }
    180 printk("gpioled major=%d,minor=%d\\r\\n",gpioled.major, gpioled.minor);
    181
    182 /* 2、初始化cdev */
    183 gpioled.cdev.owner = THIS_MODULE;
    184 cdev_init(&gpioled.cdev, &gpioled_fops);
    185
    186 /* 3、添加一个cdev */
    187 cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    188
    189 /* 4、创建类 */
    190 gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    191 if (IS_ERR(gpioled.class)) {
    192 return PTR_ERR(gpioled.class);
    193 }
    194
    195 /* 5、创建设备 */
    196 gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    197 if (IS_ERR(gpioled.device)) {
    198 return PTR_ERR(gpioled.device);
    199 }
    200
    201 return 0;
    202 }
    203
    204 /*
    205 * @description: 驱动出口函数
    206 * @param : 无
    207 * @return : 无
    208 */
    209 static void __exit led_exit(void)
    210 {
    211 /* 注销字符设备驱动 */
    212 cdev_del(&gpioled.cdev);/* 删除cdev */
    213 unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */
    214
    215 device_destroy(gpioled.class, gpioled.devid);
    216 class_destroy(gpioled.class);
    217 }
    218
    219 module_init(led_init);
    220 module_exit(led_exit);
    221 MODULE_LICENSE("GPL");
    222 MODULE_AUTHOR("duan");

    3.2、驱动代码分段解析

    3.2.1、设备结构体定义(30-41 行)

    31 struct gpioled_dev{
    32 dev_t devid;/* 设备号 */
    33 struct cdev cdev;/* cdev */
    34 struct class *class;/* 类 */
    35 struct device *device;/* 设备 */
    36 int major;/* 主设备号 */
    37 int minor;/* 次设备号 */
    38 struct device_node*nd; /* 设备节点 */
    39 int led_gpio;/* led所使用的GPIO编号*/
    40 struct semaphore sem;/* 信号量 */
    41 };

    第 40 行是核心修改点:

    在设备结构体中新增struct semaphore sem成员,用于存储信号量实例,作为 LED 设备互斥访问的核心同步对象; 其余成员为字符设备驱动标准成员,负责设备号管理、GPIO 解析、设备节点创建等基础功能。

    3.2.2、设备打开函数(52-65 行)

    52 static int led_open(struct inode *inode, struct file *filp)
    53 {
    54 filp->private_data = &gpioled; /* 设置私有数据 */
    55
    56 /* 获取信号量 */
    57 if (down_interruptible(&gpioled.sem)) { /* 获取信号量,进入休眠状态的进程可以被信号打断 */
    58 return -ERESTARTSYS;
    59 }
    60 #if 0
    61 down(&gpioled.sem);/* 不能被信号打断 */
    62 #endif
    63
    64 return 0;
    65 }

    第 54 行:将设备结构体指针赋值给filp->private_data,后续write/release函数可通过该指针访问设备结构体; 第 57-59 行(核心):

    调用down_interruptible申请信号量: 若信号量计数值 > 0(设备空闲):计数值减 1,函数返回 0,进程继续执行; 若信号量计数值 = 0(设备被占用):进程进入可中断休眠状态,等待其他进程释放信号量; 若休眠过程中收到信号(如kill指令):函数返回非 0 值,驱动返回-ERESTARTSYS,避免进程僵死; 第 61 行(注释):down函数为 “不可中断申请”,进程休眠时无法被信号打断,易导致进程僵死,实际开发中慎用。

    3.2.3、设备写操作函数(88-109 行)

    88 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
    89 {
    90 int retvalue;
    91 unsigned char databuf[1];
    92 unsigned char ledstat;
    93 struct gpioled_dev *dev = filp->private_data;
    94
    95 retvalue = copy_from_user(databuf, buf, cnt);
    96 if(retvalue < 0) {
    97 printk("kernel write failed!\\r\\n");
    98 return -EFAULT;
    99 }
    100
    101 ledstat = databuf[0];/* 获取状态值 */
    102
    103 if(ledstat == LEDON) {
    104 gpio_set_value(dev->led_gpio, 0);/* 打开LED灯 */
    105 } else if(ledstat == LEDOFF) {
    106 gpio_set_value(dev->led_gpio, 1);/* 关闭LED灯 */
    107 }
    108 return 0;
    109 }

    第 93 行:从filp->private_data取出设备结构体指针; 第 95 行:copy_from_user将用户空间的控制指令(LED 亮 / 灭)拷贝到内核空间,是内核与用户空间数据交互的标准函数; 第 103-107 行:根据用户指令控制 GPIO 电平,实现 LED 亮灭; 该函数无需额外同步:因open函数已通过信号量保证只有一个进程能进入,天然实现互斥。

    3.2.4、设备释放函数(116-123 行)

    116 static int led_release(struct inode *inode, struct file *filp)
    117 {
    118 struct gpioled_dev *dev = filp->private_data;
    119
    120 up(&dev->sem);/* 释放信号量,信号量值加1 */
    121
    122 return 0;
    123 }

    第 120 行(核心):调用up函数释放信号量,信号量计数值加 1(恢复为 1); 若有其他进程因申请该信号量而休眠,内核会自动唤醒其中一个进程,使其获取信号量并继续执行; 这是 “申请 – 释放” 闭环的关键,保证设备使用完成后能被其他进程抢占。

    3.2.5、设备操作函数集(126-132 行)

    126 static struct file_operations gpioled_fops = {
    127 .owner = THIS_MODULE,
    128 .open = led_open,
    129 .read = led_read,
    130 .write = led_write,
    131 .release = led_release,
    132 };

    字符设备驱动的核心函数集,将open/write/release等函数与内核关联,用户空间调用open/write/close时,内核会回调对应函数; .owner = THIS_MODULE:标记驱动所属模块,避免模块卸载时函数被非法调用。

    3.2.6驱动入口函数(139-202 行)

    139 static int __init led_init(void)
    140 {
    141 int ret = 0;
    142
    143 /* 初始化信号量 */
    144 sema_init(&gpioled.sem, 1);
    145
    146 /* 设置LED所使用的GPIO */
    147 /* 1、获取设备节点:gpioled */
    148 gpioled.nd = of_find_node_by_path("/gpioled");
    149 if(gpioled.nd == NULL) {
    150 printk("gpioled node not find!\\r\\n");
    151 return -EINVAL;
    152 } else {
    153 printk("gpioled node find!\\r\\n");
    154 }
    155
    156 /* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
    157 gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
    158 if(gpioled.led_gpio < 0) {
    159 printk("can't get led-gpio");
    160 return -EINVAL;
    161 }
    162 printk("led-gpio num = %d\\r\\n", gpioled.led_gpio);
    163
    164 /* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
    165 ret = gpio_direction_output(gpioled.led_gpio, 1);
    166 if(ret < 0) {
    167 printk("can't set gpio!\\r\\n");
    168 }
    169
    170 /* 注册字符设备驱动 */
    171 /* 1、创建设备号 */
    172 if (gpioled.major) {/* 定义了设备号 */
    173 gpioled.devid = MKDEV(gpioled.major, 0);
    174 register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
    175 } else {/* 没有定义设备号 */
    176 alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT, GPIOLED_NAME);/* 申请设备号 */
    177 gpioled.major = MAJOR(gpioled.devid);/* 获取分配号的主设备号 */
    178 gpioled.minor = MINOR(gpioled.devid);/* 获取分配号的次设备号 */
    179 }
    180 printk("gpioled major=%d,minor=%d\\r\\n",gpioled.major, gpioled.minor);
    181
    182 /* 2、初始化cdev */
    183 gpioled.cdev.owner = THIS_MODULE;
    184 cdev_init(&gpioled.cdev, &gpioled_fops);
    185
    186 /* 3、添加一个cdev */
    187 cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
    188
    189 /* 4、创建类 */
    190 gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
    191 if (IS_ERR(gpioled.class)) {
    192 return PTR_ERR(gpioled.class);
    193 }
    194
    195 /* 5、创建设备 */
    196 gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
    197 if (IS_ERR(gpioled.device)) {
    198 return PTR_ERR(gpioled.device);
    199 }
    200
    201 return 0;
    202 }

    第 144 行(核心):sema_init(&gpioled.sem, 1)初始化信号量,第二个参数为 1,表示信号量初始计数值为 1(二值信号量),代表 LED 设备初始为空闲状态; 第 148-168 行:设备树解析流程,获取 LED 对应的 GPIO 节点和编号,并配置为输出模式,默认高电平(LED 熄灭); 第 170-199 行:字符设备驱动标准注册流程: 创建设备号(静态注册 / 动态申请); 初始化cdev并添加到内核; 创建设备类和设备节点,最终在/dev目录下生成gpioled设备文件。

    3.2.7、驱动出口函数(209-217 行)

    209 static void __exit led_exit(void)
    210 {
    211 /* 注销字符设备驱动 */
    212 cdev_del(&gpioled.cdev);/* 删除cdev */
    213 unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */
    214
    215 device_destroy(gpioled.class, gpioled.devid);
    216 class_destroy(gpioled.class);
    217 }

    驱动卸载时的资源释放流程,与入口函数的注册流程反向:删除cdev→注销设备号→销毁设备节点→销毁设备类; 信号量无需手动释放:模块卸载时内核会自动清理信号量资源。

    3.3、测试APP编写

    直接复用原子操作实验的atomicApp.c,仅重命名为semaApp.c,核心逻辑不变:

  • 向 LED 驱动发送亮 / 灭控制指令;
  • 模拟对 LED 设备的长时间占用(25 秒),验证在占用期间其他应用无法打开设备;
  • 占用结束后释放设备,验证其他应用可正常访问。
  •   四、运行测试

    4.1、编译驱动程序和测试 APP

    编写 Makefile 文件,本次实验的 Makefile 文件和之前的led实验基本一样,只是将 obj-m 变量的值改为semaphore.o,Makefile 内容如下所示:

    KERNELDIR := /home/duan/linux/linux-imx-rel_imx_4.1.15_2.1.1_ga_alientek_v2.2
    CURRENT_PATH := $(shell pwd)

    obj-m := semaphore.o

    build: kernel_modules

    kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
    clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

    第 4 行,设置 obj-m 变量的值为semaphore.o。

    输入如下命令编译出驱动模块文件:

    make -j32

    编译成功以后就会生成一个名为“semaApp.c”的驱动模块文件。

    编译测试 APP

    输入如下命令编译测试试 spinlockApp.c 这个测试程序:

    arm-linux-gnueabihf-gcc semaApp.c -o semaApp

    4.2、运行测试

    将上一小节编译出来的semaphore.ko semaApp这两个文件拷贝到 rootfs/lib/modules/4.1.15 目录中。

    sudo cp semaphore.ko /home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -f
    sudo cp semaApp /home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -f

    进入到目录 lib/modules/4.1.15 中,输入如下命令加载semaphore.ko 驱动模块:

    depmod //第一次加载驱动的时候需要运行此命令
    modprobe semaphore.ko//加载驱动

    驱动加载成功以后会在终端中输出一些信息,如下图所示:

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

    ./semaApp /dev/gpioled 1& //打开led

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

    running times ”,如下图 所示:

    从上图可以看出,semaApp 运行正常,输出了“App running times:1”和“App running

    times:2”,这就是模拟 25S 占用,说明semaApp 这个软件正在使用 LED 灯。

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

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

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

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

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

    rmmod semaphore.ko


    总结

    本次实验通过信号量实现了 LED 设备的互斥访问,本文如有错误或不足,欢迎评论区指正,也欢迎大家三连交流~

    下一期博客将讲解互斥体(mutex)的实战应用,敬请关注!

    赞(0)
    未经允许不得转载:171主机测评 » ARM Linux 驱动开发篇--- Linux 并发与竞争实验(信号量实现 LED 设备互斥访问)--- Ubuntu20.04信号量实验
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址