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

ARM Linux 驱动开发篇--- Linux 并发与竞争实验(信号量实现 LED 设备互斥访问)--- Ubuntu20.04信号量实验
🎬 渡水无言个人主页渡水无言

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

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

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

| 省级优秀毕业生获得者 | ZEEKLOG新星杯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个核心特点(记牢!):

  1. 支持线程休眠,适合临界区较长、占用资源较久的场景(比如拷贝数据、I/O操作)。
  2. 不能用于中断上下文!因为中断不能休眠,而信号量会导致线程休眠,用在中断里直接报错。
  3. 不适合短临界区场景:频繁的线程休眠、切换,开销比自旋锁的“忙等”更大,反而降低系统性能。
再补充一个关键知识点:信号量分两种,按需选择即可:计数型信号量:初始化时信号量值>1,允许多个线程同时访问共享资源(比如停车场100个车位,允许100辆车同时停)。二值信号量:初始化时信号量值=1,只允许一个线程访问共享资源,本质就是简易的互斥(比如厕所,一次只能一个人用)。

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

1.2 本次实验设计思路

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

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

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

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

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

 

从图中可以看出,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_CNT 1 /* 设备号个数 */ 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)的实战应用,敬请关注!

Read more

当人人都会用AI,你靠什么脱颖而出?

当人人都会用AI,你靠什么脱颖而出?

文章目录 * 一、引言:AI时代,你真的准备好了吗? * 二、脉向AI:连接AI与普通人的桥梁 * 2.1 什么是脉向AI? * 2.2 脉向AI的合作生态 * 2.3 为什么你需要关注脉向AI? * 三、本期重磅:《小Ni会客厅×AI熊厂长》深度对话 * 3.1 访谈背景 * 3.2 核心观点一:商业认知决定变现能力 * 3.3 核心观点二:个人标签决定商业价值 * 3.4 核心观点三:爆款策略决定起步速度 * 3.5 核心观点四:产品思维决定变现上限 * 四、从认知到行动:如何真正用AI赚到钱? * 4.1 建立正确的商业认知 * 4.2 找到你的70分领域

By Ne0inhk
【进程间通信】Unix/Linux 信号:原理、触发与响应机制实战

【进程间通信】Unix/Linux 信号:原理、触发与响应机制实战

半桔:个人主页  🔥 个人专栏: 《Linux手册》《进程间通信》《C++从入门到入土》 🔖世界正在我眼前自行展开,我觉得我能去任何地方,做任何事情,成为任何人。 -玛丽·劳森- 文章目录 * 前言 * 信号的基本概念 * 操作系统读取`ctrl + c`信号的流程 * 信号的产生 * 键盘组合键 * kill命令 * 系统接口/库函数 * 异常 * 软件条件 * 信号的默认行为种类 * 信号的发送 * 信号的保存 * 信号集操作函数 * 信号的捕捉 * 补充 * volatile关键字 * SIGCHLD信号 前言 在计算机程序的运行世界里,进程如同一个个独立运作的生命体,它们在操作系统的调度下各司其职。这些进程间相互是独立的,但是都需要接受操作系统的调度,操作系统需要一种高效的方法来先不同进程传递信息,进程也要能够响应外部事件。 信号是操作系统提供的通信机制,它能够直接控制进程。当用户按下快捷键ctrl + c终止一个程序,当系统资源耗尽需要通知进程,操作系统通过信号来通知相应进程,从而做出

By Ne0inhk
Flutter 三方库 remove_markdown 的鸿蒙化适配指南 - 打造纯净文本提取、内容预处理实战、鸿蒙级文本解析专家

Flutter 三方库 remove_markdown 的鸿蒙化适配指南 - 打造纯净文本提取、内容预处理实战、鸿蒙级文本解析专家

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 remove_markdown 的鸿蒙化适配指南 - 打造纯净文本提取、内容预处理实战、鸿蒙级文本解析专家 在鸿蒙跨平台应用处理海量的 Markdown 博文、技术文档或用户输入的富文本内容时,有时我们需要剥离所有的样式标记(如加粗、链接、列表),还原出最原始、最纯洁的文字内容。如果你需要为搜索索引构建、智能语音播报(TTS)或是内容摘要生成提供高质量的数据源。今天我们要深度解析的 remove_markdown——一个专注于高效、无损 Markdown 语法剥离的轻量级 Dart 库,正是帮你实现“内容减负”的核心引擎。 前言 remove_markdown 是一套基于正则表达式与高效字符扫描的转换工具。它的设计初衷极其明确:将复杂的 Markdown 源码瞬间坍缩为易于阅读和处理的纯文本。在鸿蒙端项目中,利用它你可以确保在展示搜索片段(

By Ne0inhk
Flutter 三方库 metalink_advanced_final 的鸿蒙化适配指南 - 在 OpenHarmony 打造极致、安全的 P2P 下载与资源分发底座

Flutter 三方库 metalink_advanced_final 的鸿蒙化适配指南 - 在 OpenHarmony 打造极致、安全的 P2P 下载与资源分发底座

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 metalink_advanced_final 的鸿蒙化适配指南 - 在 OpenHarmony 打造极致、安全的 P2P 下载与资源分发底座 在大数据传输、大型游戏资源更新以及分布式固件升级场景中,传统的单点 HTTP 下载往往面临带宽压力和校验失效的风险。metalink 协议(RFC 5854)通过整合多个源地址与强校验机制,提供了一套工业级的资源分发方案。metalink_advanced_final 库为 Flutter 开发者提供了该协议的终极解析与执行引擎。本文将深度解析如何在 OpenHarmony(鸿蒙)环境下,结合鸿蒙的并发架构,完美适配这一强大的下载工具。 前言 随着鸿蒙系统(HarmonyOS)跨终端特性的普及,应用在不同设备间流转时的资源同步速度成为了用户体验的“胜负手”。metalink_advanced_final

By Ne0inhk