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

自动高效的电脑自动化模型,Fara-7B:一种用于计算机的高效代理模型

Fara-7B 是微软的第一个 代理小语言模型 (SLM) 专门设计用于计算机使用。Fara-7B 仅具有70亿个参数,是一种超紧凑的计算机使用代理(CUA),在其尺寸类别内实现了最先进的性能,并可与更大、更资源密集型代理系统竞争。 尝试在本地使用 Fara-7B,如下所示(请参见 安装 有关 Windows 的详细指示)或通过 Magentic-UI: # 1. Clone repository git clone https://github.com/microsoft/fara.git cd fara # 2. Setup environment python3 -m venv .venv source .venv/bin/activate pip install -e . playwright

By Ne0inhk

远程连接卡在 设置 SSH 主机 正在初始化 VS Code 服务器

查看别人的文章,说是删除服务器上的vscode-server文件夹,再重启vscode远程连接就可以,但是试了还是不行。 改vscode的ssh连接模式为兼容模式就行了,在 VS Code 设置里搜索 remote.SSH.useExecServer,把这个选项关掉(勾选去掉),然后重新连接。有时候这个功能和某些服务器不兼容,关掉就好了。 齿轮所在的那个选项不要勾选 如果还是不行,就 在 VS Code 里按 Ctrl+, 打开设置,搜索 remote.SSH.showLoginTerminal,勾选这个选项。然后重新连接服务器,会弹出一个窗口显示登录过程,可能会看到具体的错误提示(比如 “找不到某个程序”)。然后一步步根据错误调试 第一个选项勾上

By Ne0inhk

把自己的网盘搬进服务器:OpenList 部署完整指南

前言 不知道你有没有这样的烦恼:手机里装着百度网盘、阿里云盘、夸克网盘好几个 App,想找个文件得挨个翻一遍;遇到喜欢的电影资源,还得先下载到本地才能看;想给朋友分享个文件,不是限速就是过期。 OpenList 这个工具能帮你解决这些问题。它是一个开源的目录列表程序,可以把各种网盘挂载到一起,统一管理和访问。你打开它的页面,就像打开了自己的文件管理器,里面是你所有网盘的内容,点一下视频就能直接播放,不用下载,也不用担心 bandwidth 不够——它会自动帮你找到最快的播放链接 。 这玩意儿最早是 AList 的一个分支。原来的 AList 被商业公司收购后,社区里有人担心代码不再透明、可能会有数据收集的问题,所以分出来做了 OpenList,继续走开源的路子 。现在它支持阿里云盘、百度网盘、OneDrive、夸克、WebDAV 等几十种存储服务,而且资源占用很低,一台普通的小主机就能跑起来 。 这篇教程我会一步步带你在一台 Linux 服务器上把 OpenList 搭起来。不管你是刚买了个云服务器想折腾点东西,

By Ne0inhk