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 驱动代码(mutex.c)

3.2.1、设备结构体定义(28-39 行)

3.2.2、设备打开函数(50-63 行)

3.2.3、设备写操作函数(86-107 行)

3.2.4、设备释放函数(114-122 行)

3.2.5驱动入口函数(138-201 行)

3.2.6、驱动出口函数(208-216 行)

3.3、测试APP编写

  四、运行测试

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

4.2、运行测试

总结


前言

前几期博客我们分别用原子操作、自旋锁、信号量实现了 LED 设备的互斥访问,但信号量本质是 “同步原语”,仅靠二值信号量实现互斥略显 “大材小用”。Linux 内核专门提供了互斥体为 “互斥访问” 量身定制的同步机制,语法更简洁、语义更明确,是进程上下文互斥的首选方案。


 一、实验基础说明

1.1、互斥体简介

 前面讲信号量的时候提到,把信号量值设为1,就能实现互斥访问(一次只能一个线程访问资源)。但Linux内核专门提供了一个更专业的互斥机制——互斥体

简单说:互斥体就是“专门用来做互斥”的工具,比二值信号量更轻量、更规范,是驱动开发中实现互斥访问的首选

学过FreeRTOS的小伙伴肯定清楚,互斥体的核心就是“一次只能一个持有者”,和二值信号量类似,但有几个关键区别:

互斥体不能递归上锁/解锁:线程已经持有互斥体,再去申请同一个,直接死锁(自己等自己释放)。互斥体的持有者必须释放:谁上锁,谁解锁,不能由其他线程释放(信号量可以)。互斥体只能用于线程上下文:和信号量一样,会导致线程休眠,不能用于中断
💡 生活类比:卫生间的门锁(互斥体),一次只能一个人用,必须自己开门、自己关门,不能替别人关门,也不能自己锁门后再锁一次。

Linux内核用 struct mutex 结构体表示互斥体(定义在 <include/linux/mutex.h>),省略条件编译后结构如下:

struct mutex { /* 1: 未上锁, 0: 已上锁, 负数: 已上锁且有等待线程 */ atomic_t count; spinlock_t wait_lock; // 自旋锁,保护等待队列 };
👉 互斥体使用注意事项(必看!避免踩坑):不能用于中断上下文:会导致休眠,中断无法休眠,只能用自旋锁。临界区可以调用休眠函数:和信号量一样,因为线程可以休眠,所以允许调用copy_from_user、msleep等函数。不能递归操作:同一线程不能多次上锁,否则死锁。严格互斥:一次只能一个线程持有,适合需要“独占资源”的场景(比如设备的打开/关闭操作)。

1.2 本次实验设计思路

互斥体的使用逻辑与二值信号量高度相似,但 API 更简洁:

初始化互斥体,代表 LED 设备初始为空闲状态;
open函数调用mutex_lock_interruptible获取互斥体:
成功:独占 LED 设备;
失败:进程休眠,等待其他进程释放;
release函数调用mutex_unlock释放互斥体,唤醒休眠进程;
设备树、硬件、测试 APP 完全复用信号量实验代码,仅替换同步机制为互斥体。

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

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 驱动修改,仅替换同步机制为信号量,硬件和设备树无需修改。把 semaphore.c改成mutex.c文件。

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

3.2、驱动代码分段解析

3.2.1、设备结构体定义(28-39 行)

28 /* gpioled设备结构体 */ 29 struct gpioled_dev{ 30 dev_t devid; /* 设备号 */ 31 struct cdev cdev; /* cdev */ 32 struct class *class; /* 类 */ 33 struct device *device; /* 设备 */ 34 int major; /* 主设备号 */ 35 int minor; /* 次设备号 */ 36 struct device_node *nd; /* 设备节点 */ 37 int led_gpio; /* led所使用的GPIO编号 */ 38 struct mutex lock; /* 互斥体:核心同步成员 */ 39 };
核心修改点(38 行):

用struct mutex lock替代原信号量成员,是实现互斥访问的核心载体;
其余成员为字符设备驱动标准成员:负责设备号管理、GPIO 解析、设备节点创建等基础功能;
互斥体无需计数值:天然为 “二值” 设计,初始状态为 “可用”,无需像信号量那样设置sema_init(&sem, 1)

3.2.2、设备打开函数(50-63 行)

50 static int led_open(struct inode *inode, struct file *filp) 51 { 52 filp->private_data = &gpioled; /* 设置私有数据 */ 53 54 /* 获取互斥体,可以被信号打断 */ 55 if (mutex_lock_interruptible(&gpioled.lock)) { 56 return -ERESTARTSYS; 57 } 58 #if 0 59 mutex_lock(&gpioled.lock); /* 不能被信号打断 */ 60 #endif 61 62 return 0; 63 }
52 行:将设备结构体指针赋值给filp->private_data,后续write/release函数可通过该指针访问设备结构体;
55-57 行(核心):调用mutex_lock_interruptible获取互斥体:
成功:互斥体被当前进程持有,其他进程无法获取,实现 LED 独占访问;
失败:进程进入可中断休眠状态(可被kill、Ctrl+C等信号打断),返回-ERESTARTSYS,避免进程僵死;
59 行(注释):mutex_lock为 “不可中断获取”,进程休眠时无法被信号唤醒,易导致死锁,实际开发中严禁使用;

3.2.3、设备写操作函数(86-107 行)

86 static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) 87 { 88 int retvalue; 89 unsigned char databuf[1]; 90 unsigned char ledstat; 91 struct gpioled_dev *dev = filp->private_data; 92 93 retvalue = copy_from_user(databuf, buf, cnt); 94 if(retvalue < 0) { 95 printk("kernel write failed!\r\n"); 96 return -EFAULT; 97 } 98 99 ledstat = databuf[0]; /* 获取状态值 */ 100 101 if(ledstat == LEDON) { 102 gpio_set_value(dev->led_gpio, 0); /* 打开LED灯 */ 103 } else if(ledstat == LEDOFF) { 104 gpio_set_value(dev->led_gpio, 1); /* 关闭LED灯 */ 105 } 106 return 0; 107 }
91 行:从filp->private_data取出设备结构体指针;
93 行:copy_from_user将用户空间的控制指令(LED 亮 / 灭)拷贝到内核空间,是内核与用户空间数据交互的标准函数;
101-105 行:根据用户指令控制 GPIO 电平,实现 LED 亮灭;
无需额外同步:因open函数已通过互斥体保证只有一个进程能进入,天然实现互斥访问。

3.2.4、设备释放函数(114-122 行)

114 static int led_release(struct inode *inode, struct file *filp) 115 { 116 struct gpioled_dev *dev = filp->private_data; 117 118 /* 释放互斥锁 */ 119 mutex_unlock(&dev->lock); 120 121 return 0; 122 }
119 行(核心):调用mutex_unlock释放互斥体:
互斥体计数值恢复为 “可用”;
内核自动唤醒等待该互斥体的休眠进程,使其获取互斥体并继续执行;
对比信号量:mutex_unlock ≈ up,但互斥体禁止 “跨进程释放”(必须由持有者释放),降低死锁风险。

3.2.5驱动入口函数(138-201 行)

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

mutex_init(&gpioled.lock)初始化互斥体,无参数(互斥体天然为互斥设计,无需设置计数值);
147-167 行:设备树解析流程:
of_find_node_by_path获取 LED 设备节点;
of_get_named_gpio解析设备树中led-gpio属性,得到 GPIO 编号;
gpio_direction_output配置 GPIO 为输出模式,默认高电平(LED 熄灭);
169-198 行:字符设备驱动标准注册流程:
创建设备号(静态注册 / 动态申请);
初始化cdev并添加到内核;
创建类和设备节点,最终在/dev目录生成gpioled设备文件。

3.2.6、驱动出口函数(208-216 行)

208 static void __exit led_exit(void) 209 { 210 /* 注销字符设备驱动 */ 211 cdev_del(&gpioled.cdev);/* 删除cdev */ 212 unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销设备号 */ 213 214 device_destroy(gpioled.class, gpioled.devid); 215 class_destroy(gpioled.class); 216 }
驱动卸载时的资源释放流程,与入口函数注册流程反向;
互斥体无需手动释放:模块卸载时内核会自动清理互斥体资源。

3.3、测试APP编写

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

#include "stdio.h" #include "unistd.h" #include "sys/types.h" #include "sys/stat.h" #include "fcntl.h" #include "stdlib.h" #include "string.h" /*************************************************************** Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved. 文件名 : mutexApp.c 作者 : 左忠凯 版本 : V1.0 描述 : 互斥体测试APP,测试信号量能不能实现一次 只允许一个应用程序使用LED。 其他 : 无 使用方法 :./mutexApp /dev/gpioled 0 关闭LED灯 ./mutexApp /dev/gpioled 1 打开LED灯 论坛 : www.openedv.com 日志 : 初版V1.0 2019/1/30 左忠凯创建 ***************************************************************/ #define LEDOFF 0 #define LEDON 1 /* * @description : main主程序 * @param - argc : argv数组元素个数 * @param - argv : 具体参数 * @return : 0 成功;其他 失败 */ 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]; /* 打开beep驱动 */ fd = open(filename, O_RDWR); if(fd < 0){ printf("file %s open failed!\r\n", argv[1]); return -1; } databuf[0] = atoi(argv[2]); /* 要执行的操作:打开或关闭 */ /* 向/dev/gpioled文件写入数据 */ retvalue = write(fd, databuf, sizeof(databuf)); if(retvalue < 0){ printf("LED Control Failed!\r\n"); close(fd); return -1; } /* 模拟占用25S LED */ while(1) { sleep(5); cnt++; printf("App running times:%d\r\n", cnt); if(cnt >= 5) break; } printf("App running finished!\r\n"); retvalue = close(fd); /* 关闭文件 */ if(retvalue < 0){ printf("file %s close failed!\r\n", argv[1]); return -1; } return 0; }
向 LED 驱动发送亮 / 灭控制指令;模拟对 LED 设备的长时间占用(25 秒),验证在占用期间其他应用无法打开设备;占用结束后释放设备,验证其他应用可正常访问。
代码段功能说明
if(argc != 3)参数校验:必须传入设备路径+控制指令(如./mutexApp /dev/gpioled 1),否则提示用法错误
fd = open(filename, O_RDWR)打开/dev/gpioled设备文件:- 若当前无其他进程占用,驱动open函数获取互斥体成功,返回文件描述符;- 若已有进程占用,当前进程会休眠,直到前一个进程释放互斥体
databuf[0] = atoi(argv[2])将用户传入的字符串指令("0"/"1")转为数字,作为控制 LED 的参数
write(fd, databuf, sizeof(databuf))向驱动写入控制指令,驱动write函数接收后控制 GPIO 电平
while(1) { sleep(5); cnt++; ... }核心测试逻辑:- 每 5 秒打印一次运行次数,总共运行 25 秒;- 这段时间内,当前进程一直持有互斥体,其他进程无法打开 LED 设备;- 模拟 “长时间占用硬件资源” 的真实场景
close(fd)关闭设备文件:触发驱动release函数,释放互斥体,其他进程可获取并访问 LED

  四、运行测试

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

编写 Makefile 文件,本次实验的 Makefile 文件和之前的led实验基本一样,只是将 obj-m 变量的值改为mutex.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 := mutex.o build: kernel_modules kernel_modules: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean 

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

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

make -j32

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

编译测试 APP

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

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

4.2、运行测试

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

sudo cp mutex.ko/home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -f
sudo cp mutexApp /home/duan/linux/nfs/rootfs/lib/modules/4.1.15/ -f

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

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

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

驱动加载成功以后就可以使用mutexApp 软件测试驱动是否工作正常。方便和上一期博客信号量实验是一样的。

如果要卸载驱动的话输入如下命令即可:

rmmod mutex.ko


总结

互斥体是 Linux 驱动中处理进程间互斥的 “最优解”,本期博客完成了驱动 + APP 实战。

Read more

华为OD机试双机位C卷 - 快递投放问题 (JAVA & Python & C++ & JS & GO)

华为OD机试双机位C卷 - 快递投放问题 (JAVA & Python & C++ & JS & GO)

快递投放问题 华为OD机试双机位C卷 - 华为OD上机考试双机位C卷 200分题型 华为OD机试双机位C卷真题目录点击查看: 华为OD机试双机位C卷真题题库目录|机考题库 + 算法考点详解 题目描述 有N个快递站点用字符串标识,某些站点之间有道路连接。 每个站点有一些包裹要运输,每个站点间的包裹不重复,路上有检查站会导致部分货物无法通行,计算哪些货物无法正常投递? 输入描述 * 第一行输入M N,M个包裹N个道路信息. * 0<=M,N<=100, * 检查站禁止通行的包裹如果有多个以空格分开 输出描述 * 输出不能送达的包裹,如:package2 package4, * 如果所有包裹都可以送达则输出:none, * 输出结果按照升序排列。 用例1 输入 4 2 package1 A C package2 A C package3 B C package

By Ne0inhk
Java Web开发基础与Servlet核心技术

Java Web开发基础与Servlet核心技术

Java Web开发基础与Servlet核心技术 15.1 学习目标与重点提示 学习目标:掌握Java Web开发的核心概念与Servlet技术的使用方法,包括Web应用的结构、Servlet的定义与使用、HTTP请求与响应的处理、会话管理、过滤器与监听器的使用,学会在实际开发中处理Web应用问题。 重点:Web应用的结构(目录结构、配置文件)、Servlet的定义与使用(Servlet接口、HttpServlet类、注解配置)、HTTP请求与响应的处理(Request、Response对象)、会话管理(Session、Cookie)、过滤器与监听器的使用、Web开发的实际应用场景。 15.2 Web开发概述 Java Web开发是用于处理Web应用的机制。 15.2.1 Web开发的定义 定义:Web开发是用于处理Web应用的机制。 作用: * 实现Web应用的开发。 * 实现客户端与服务器之间的通信。 * 实现动态网页的生成。 * 实现Web应用的部署与维护。 ✅ 结论:Web开发是用于处理Web应用的机制,作用是实现Web应用的开发、客户端与服务器之间的通

By Ne0inhk
基于Java+SpringBoot+SSM篮球管理系统(源码+LW+调试文档+讲解等)/篮球管理软件/篮球管理平台/篮球赛事管理系统/篮球俱乐部管理系统/篮球场馆管理系统

基于Java+SpringBoot+SSM篮球管理系统(源码+LW+调试文档+讲解等)/篮球管理软件/篮球管理平台/篮球赛事管理系统/篮球俱乐部管理系统/篮球场馆管理系统

博主介绍 💗博主介绍:✌全栈领域优质创作者,专注于Java、小程序、Python技术领域和计算机毕业项目实战✌💗 👇🏻 精彩专栏 推荐订阅👇🏻 2025-2026年最新1000个热门Java毕业设计选题大全✅ 2025-2026年最新500个热门微信小程序毕业设计选题大全✅ Java毕业设计最新1000套项目精品实战案例 微信小程序毕业设计最新500套项目精品案例 🌟文末获取源码+数据库🌟 感兴趣的可以先收藏起来,还有大家在毕设选题,项目以及论文编写等相关问题都可以给我留言咨询,希望帮助更多的人 本文项目技术选型介绍 前端:Spring+SpringMVC+Mybatis 后端:SpringBoot+Mybatis 数据库:MySQL、SQLServer 开发工具:IDEA、Eclipse、Navicat等 ✌关于毕设项目技术实现问题讲解也可以给我留言咨询!!! 详细视频演示 请联系博主获取更详细的演示视频-源码编号4560 具体实现截图 框架介绍 前端技术介绍 SSM 框架在程序设计中具有不可替代的地位。它不仅提供了丰富的功能和强大的性能

By Ne0inhk

Java最新面试题库——精选100道(含精简答案),收藏这篇就够了

JavaEE面试题整理 * 一、Java基础篇 * 二、JVM篇 * 三、Tomcat篇 * 四、MyBatis篇 * 五、Spring篇 * 六、SpringMVC面试题整理 * 七、Redis篇 * 八、Mongodb篇 * 九、MQ篇 * 十、Shiro篇 * 十一、搜索引擎篇 * 十二、Nginx篇 * 十三、SpringBoot篇 * 十四、Dubbo篇 一、Java基础篇 1、JAVA中的几种基本数据类型是什么,各自占用多少字节? 浮点类型:float(4字节)、double(8个字) 整数类型:byte(1字节)、short(2字节)、int(4字节)、long(8字节) 字符类型:char(

By Ne0inhk