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

【大模型实战篇】基于Claude MCP协议的智能体落地示例

【大模型实战篇】基于Claude MCP协议的智能体落地示例

1. 背景         之前我们在《MCP(Model Context Protocol) 大模型智能体第一个开源标准协议》一文中,介绍了MCP的概念,虽然了解了其概念、架构、解决的问题,但还缺少具体的示例,来帮助进一步理解整套MCP框架如何落地。         今天我们基于claude的官方例子--获取天气预报【1】,来理解MCP落地的整条链路。 2. MCP示例         该案例是构建一个简单的MCP天气预报服务器,并将其连接到主机,即Claude for Desktop。从基本设置开始,然后逐步发展到更复杂的使用场景。         大模型虽然能力非常强,但其弊端就是内容是过时的,这里的过时不是说内容很旧,只是表达内容具有非实时性。比如没有获取天气预报和严重天气警报的能力。因此我们将使用MCP来解决这一问题。         构建一个服务器,该服务器提供两个工具:获取警报(get-alerts)和获取预报(get-forecast)。然后,将该服务器连接到MCP主机(在本例中为Claude for Desktop)。         首先我们配置下环

By Ne0inhk
基于腾讯云HAI + DeepSeek快速设计自己的个人网页

基于腾讯云HAI + DeepSeek快速设计自己的个人网页

前言:通过结合腾讯云HAI 强大的云端运算能力与DeepSeek先进的 AI技术,本文介绍高效、便捷且低成本的设计一个自己的个人网页。你将了解到如何轻松绕过常见的技术阻碍,在腾讯云HAI平台上快速部署DeepSeek模型,仅需简单几步,就能获取一个包含个人简介、技能特长、项目经历及联系方式等核心板块的响应式网页。 目录 一、DeepSeek模型部署在腾讯云HAI 二、设计个人网页 一、DeepSeek模型部署在腾讯云HAI 把 DeepSeek 模型部署于腾讯云 HAI,用户便能避开官网访问限制,直接依托腾讯云 HAI 的超强算力运行 DeepSeek-R1 等模型。这一举措不仅降低了技术门槛,还缩短了部署时间,削减了成本。尤为关键的是,凭借 HAI 平台灵活且可扩展的特性,用户能够依据自身特定需求定制专属解决方案,进而更出色地适配特定业务场景,满足各类技术要求 。 点击访问腾讯云HAI控制台地址: 算力管理 - 高性能应用服务 - 控制台 腾讯云高性能应用服务HAI已支持DeepSeek-R1模型预装环境和CPU算力,只需简单的几步就能调用DeepSeek - R1

By Ne0inhk
AI革命先锋:DeepSeek与蓝耘通义万相2.1的无缝融合引领行业智能化变革

AI革命先锋:DeepSeek与蓝耘通义万相2.1的无缝融合引领行业智能化变革

云边有个稻草人-ZEEKLOG博客 目录 引言 一、什么是DeepSeek? 1.1 DeepSeek平台概述 1.2 DeepSeek的核心功能与技术 二、蓝耘通义万相2.1概述 2.1 蓝耘科技简介 2.2 蓝耘通义万相2.1的功能与优势 1. 全链条智能化解决方案 2. 强大的数据处理能力 3. 高效的模型训练与优化 4. 自动化推理与部署 5. 行业专用解决方案 三、蓝耘通义万相2.1与DeepSeek的对比分析 3.1 核心区别 3.2 结合使用的优势 四、蓝耘注册流程 五、DeepSeek与蓝耘通义万相2.1的集成应用 5.1 集成应用场景 1. 智能医疗诊断

By Ne0inhk
如何通过 3 个简单步骤在 Windows 上本地运行 DeepSeek

如何通过 3 个简单步骤在 Windows 上本地运行 DeepSeek

它是免费的——社区驱动的人工智能💪。         当 OpenAI 第一次推出定制 GPT 时,我就明白会有越来越多的人为人工智能做出贡献,并且迟早它会完全由社区驱动。         但从来没有想过它会如此接近😂让我们看看如何在 Windows 机器上完全免费使用第一个开源推理模型!  步骤 0:安装 Docker 桌面         我确信很多人已经安装了它,所以可以跳过,但如果没有 — — 这很简单,只需访问Docker 的官方网站,下载并运行安装 👍         如果您需要一些特定的设置,例如使用 WSL,那么有很多指导视频,请查看!我将继续下一步。 步骤 1:安装 CUDA 以获得 GPU 支持         如果您想使用 Nvidia 显卡运行 LLM,则必须安装 CUDA 驱动程序。(嗯……是的,它们需要大量的计算能力)         打开CUDA 下载页面,

By Ne0inhk