第23章 信号量实验(iTOP-RK3568开发板驱动开发指南 )

在上面两个章节对自旋锁和自旋锁死锁进行了学习,自旋锁会让请求的任务原地“自旋”,在等待的过程中会循环检测自旋锁的状态,进而占用系统资源,而本章节要讲解的信号量也是解决竞争的一种常用方法,与自旋锁不同的是,信号量会使等待的线程进入休眠状态,适用于那些占用资源比较久的场合。下面对信号量相关知识的进行讲解。

23.1 信号量

信号量是操作系统中最典型的用于同步和互斥的手段,本质上是一个全局变量,信号量的值表示控制访问资源的线程数,可以根据实际情况来自行设置,如果在初始化的时候将信号量量值设置为大于1,那么这个信号量就是计数型信号量,允许多个线程同时访问共享资源。如果将信号量量值设置为1,那么这个信号量就是二值信号量,同一时间内只允许一个线程访问共享资源,注意!信号量的值不能小于0。当信号量的值为0时,想访问共享资源的线程必须等待,直到信号量大于0时,等待的线程才可以访问。当访问共享资源时,信号量执行“减一”操作,访问完成后再执行“加一”操作。

相比于自旋锁,信号量具有休眠特性,因此适用长时间占用资源的场合,但由于信号量会引起休眠,所以不能用在中断函数中,最后如果共享资源的持有时间比较短,使用信号量的话会造成频繁的休眠,反而带来更多资源的消耗,使用自旋锁反而效果更好。再同时使用信号量和自旋锁的时候,要先获取信号量,再使用自旋锁,因为信号量会导致睡眠。

以现实生活中的银行办理业务为例,银行的业务办理窗口就是共享资源,业务办理窗口的数量就是信号量量值,进入银行之后,客户需要领取相应的排序码,然后在休息区进行等待,可以看作线程的睡眠阶段,当前面的客户办理完业务之后,相应的窗口会空闲出来,可以看作信号量的释放,之后银行会通过广播,提醒下一位客户到指定的窗口进行业务的办理,可以看作线程的唤醒并获取到信号量,访问共享资源的过程。

Linux 内核使用semaphore结构体来表示信号量,该结构体定义在“内核源码/include/linux/semaphore.h”文件内(所以在下一章节的信号量实验中需要加入该头文件),结构体内容如下所示:

 struct semaphore {raw_spinlock_t      lock;unsigned int        count;struct list_head    wait_list;};

与信号量相关的 API 函数同样定义在semaphore.h文件内,部分常用API函数如下(表23-1)所示:

函数描述
DEFINE_SEAMPHORE(name)定义信号量,并且设置信号量的值为 1。
void sema_init(struct semaphore *sem, int val)初始化信号量 sem,设置信号量值为 val。
void down(struct semaphore *sem)获取信号量,不能被中断打断,如ctrl+c
int down_interruptible(struct semaphore *sem)获取信号量,可以被中断打断,如ctrl+c
void up(struct semaphore *sem)释放信号量
int down_trylock(struct semaphore *sem);尝试获取信号量,如果能获取到信号量就获取,并且返回 0。如果不能就返回非 0

表 23-1

至此,关于信号量相关的知识就讲解完成了,上述API函数会在下一小节的实验中用到。

23.2 实验程序的编写

23.2.1 驱动程序编写

本实验对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\18\module。

与之前章节设置标志位,在同一时间内只允许一个任务对共享资源进行访问的方式所不同,本小节将采用信号量的方式避免竞争的产生。本实验设置的信号量量值为1,所以需要在open()函数中加入信号量获取函数,在release()函数中加入信号量释放函数即可。

编写完成的semaphore.c代码如下所示

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/semaphore.h>struct semaphore semaphore_test;//定义一个semaphore类型的结构体变量semaphore_test
static int open_test(struct inode *inode,struct file *file)
{printk("\nthis is open_test \n");
down(&semaphore_test);//信号量数量减1return 0;
}static ssize_t read_test(struct file *file,char __user *ubuf,size_t len,loff_t *off)
{int ret;char kbuf[10] = "topeet";//定义char类型字符串变量kbufprintk("\nthis is read_test \n");ret = copy_to_user(ubuf,kbuf,strlen(kbuf));//使用copy_to_user接收用户空间传递的数据if (ret != 0){printk("copy_to_user is error \n");}printk("copy_to_user is ok \n");return 0;
}
static char kbuf[10] = {0};//定义char类型字符串全局变量kbuf
static ssize_t write_test(struct file *file,const char __user *ubuf,size_t len,loff_t *off)
{int ret;ret = copy_from_user(kbuf,ubuf,len);//使用copy_from_user接收用户空间传递的数据if (ret != 0){printk("copy_from_user is error\n");
}if(strcmp(kbuf,"topeet") == 0 ){//如果传递的kbuf是topeet就睡眠四秒钟ssleep(4);}else if(strcmp(kbuf,"itop") == 0){//如果传递的kbuf是itop就睡眠两秒钟ssleep(2);}printk("copy_from_user buf is %s \n",kbuf);return 0;
}
static int release_test(struct inode *inode,struct file *file)
{
up(&semaphore_test);//信号量数量加1printk("\nthis is release_test \n");return 0;
}struct chrdev_test {dev_t dev_num;//定义dev_t类型变量dev_num来表示设备号int major,minor;//定义int类型的主设备号major和次设备号minorstruct cdev cdev_test;//定义struct cdev 类型结构体变量cdev_test,表示要注册的字符设备struct class *class_test;//定于struct class *类型结构体变量class_test,表示要创建的类
};
struct chrdev_test dev1;//创建chrdev_test类型的
struct file_operations fops_test = {.owner = THIS_MODULE,//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块.open = open_test,//将open字段指向open_test(...)函数.read = read_test,//将read字段指向read_test(...)函数.write = write_test,//将write字段指向write_test(...)函数.release = release_test,//将release字段指向release_test(...)函数
};static int __init atomic_init(void)
{sema_init(&semaphore_test,1);//初始化信号量结构体semaphore_test,并设置信号量的数量为1if(alloc_chrdev_region(&dev1.dev_num,0,1,"chrdev_name") < 0 ){//自动获取设备号,设备名chrdev_nameprintk("alloc_chrdev_region is error \n");}printk("alloc_chrdev_region is ok \n");dev1.major = MAJOR(dev1.dev_num);//使用MAJOR()函数获取主设备号dev1.minor = MINOR(dev1.dev_num);//使用MINOR()函数获取次设备号printk("major is %d,minor is %d\n",dev1.major,dev1.minor);cdev_init(&dev1.cdev_test,&fops_test);//使用cdev_init()函数初始化cdev_test结构体,并链接到fops_test结构体dev1.cdev_test.owner = THIS_MODULE;//将owner字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用cdev_add()函数进行字符设备的添加dev1.class_test = class_create(THIS_MODULE,"class_test");//使用class_create进行类的创建,类名称为class_testdevice_create(dev1.class_test,0,dev1.dev_num,0,"device_test");//使用device_create进行设备的创建,设备名称为device_testreturn 0;
}static void __exit atomic_exit(void)
{device_destroy(dev1.class_test,dev1.dev_num);//删除创建的设备class_destroy(dev1.class_test);//删除创建的类cdev_del(&dev1.cdev_test);//删除添加的字符设备cdev_testunregister_chrdev_region(dev1.dev_num,1);//释放字符设备所申请的设备号printk("module exit \n");
}
module_init(atomic_init);
module_exit(atomic_exit)
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("topeet");

23.2.2 编写测试 APP

本实验应用程序对应的网盘路径为:iTOP-RK3568开发板【底板V1.7版本】\03_【iTOP-RK3568开发板】指南教程\02_Linux驱动配套资料\04_Linux驱动例程\18\app。

本测试app代码和上一章节相同,需要输入两个参数,第一个参数为对应的设备节点,第二个参数为“topeet”或者“itop”,分别代表向设备写入的数据,编写完成的应用程序app.c内容如下所示:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>#include <unistd.h>
int main(int argc, char *argv[])
{int fd;//定义int类型的文件描述符char str1[10] = {0};//定义读取缓冲区str1fd = open(argv[1],O_RDWR);//调用open函数,打开输入的第一个参数文件,权限为可读可写if(fd < 0 ){printf("file open failed \n");return -1;}/*如果第二个参数为topeet,条件成立,调用write函数,写入topeet*/    if (strcmp(argv[2],"topeet") == 0 ){write(fd,"topeet",10);}/*如果第二个参数为itop,条件成立,调用write函数,写入itop*/  else if (strcmp(argv[2],"itop") == 0 ){write(fd,"itop",10);}close(fd); return 0;
}

23.3 运行测试

23.3.1 编译驱动程序

在上一小节中的semaphore.c代码同一目录下创建 Makefile 文件,Makefile 文件内容如下所示:

export ARCH=arm64#设置平台架构
export CROSS_COMPILE=aarch64-linux-gnu-#交叉编译器前缀
obj-m += semaphore.o    #此处要和你的驱动源文件同名
KDIR :=/home/topeet/Linux/linux_sdk/kernel    #这里是你的内核目录                                                                                                                            
PWD ?= $(shell pwd)
all:make -C $(KDIR) M=$(PWD) modules    #make操作
clean:make -C $(KDIR) M=$(PWD) clean    #make clean操作

对于Makefile的内容注释已在上图添加,保存退出之后,来到存放semaphore.c和Makefile文件目录下,如下图(图23-2)所示:

img

图 23-2

然后使用命令“make”进行驱动的编译,编译完成如下图(图23-3)所示:

img

图 23-3

编译完生成semaphore.ko目标文件,如下图(图23-4)所示:

img

图 23-4

至此驱动模块就编译成功了,下面进行应用程序的安装。

23.3.2 编译应用程序

来到应用程序app.c文件的存放路径如下图(图 23-5)所示:

img

图 23-5

然后使用以下命令对app.c进行交叉编译,编译完成如下图(图23-6)所示:

aarch64-linux-gnu-gcc -o app app.c -static

img

图23-6

生成的app文件就是之后放在开发板上运行的可执行文件,至此应用程序的编译就完成了。

23.3.3 运行测试

开发板启动之后,使用以下命令进行驱动模块的加载,如下图(图 23-7)所示:

insmod semaphore.ko

img

图 23-7

可以看到申请的主设备号和次设备号就被打印了出来,然后使用以下代码对自动生成的设备节点device_test进行查看,如下图(图23-8)所示:

 ls /dev/device_test

img

图 23-8

可以看到device_test节点已经被自动创建了,然后使用以下命令运行测试app,运行结果如下图(图23-9)所示:

./app /dev/device_test topeet

img

图 23-9

可以看到传递的buf值为topeet,然后输入以下命令在后台运行两个app,来进行竞争测试,运行结果如下图(图23-10)所示:

./app /dev/device_test topeet &./app /dev/device_test itop 

img

图 23-10

​ 上述打印信息正常,证明数据被正确传递了,没有发生共享资源的竞争,第一个任务运行之后,由于设置的信号量量值为1,所以第二个任务会进入休眠状态,第一个任务执行完毕之后,会唤醒第二个任务去执行,所以避免了并发与竞争。

最后可以使用以下命令进行驱动的卸载,如下图(图 23-11)所示:

rmmod semaphore.ko

img

图 23-11

至此,信号量实验就完成了。

【最新驱动资料(文档+例程)】

链接 https://pan.baidu.com/s/1M4smUG2vw_hnn0Hye-tkog

提取码:hbh6

【B 站配套视频】

https://b23.tv/XqYa6Hm

【RK3568 购买链接】

https://item.taobao.com/item.htm?spm=a1z10.5-c-s.w4002-2245

rmmod semaphore.ko

[外链图片转存中…(img-nql99Xd2-1694222692051)]

图 23-11

至此,信号量实验就完成了。

【最新驱动资料(文档+例程)】

链接 https://pan.baidu.com/s/1M4smUG2vw_hnn0Hye-tkog

提取码:hbh6

【B 站配套视频】

https://b23.tv/XqYa6Hm

【RK3568 购买链接】

https://item.taobao.com/item.htm?spm=a1z10.5-c-s.w4002-2245

2452613.11.2fec74a6elWNeA&id=669939423234

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/73564.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无需麻烦验证,文字验证码一键通过

前言 文字验证码&#xff0c;简单易用&#xff0c;安全可靠&#xff01;不需要麻烦的图形识别。这种验证方式不仅方便快捷&#xff0c;而且能有效防止恶意攻击和机器人访问。无需担心复杂操作&#xff0c;只需几秒钟就能完成验证过程。保护您的个人信息和数据安全&#xff0c;…

【HTML专栏4】常用标签(标题、段落、换行、文本格式化、注释及特殊字符)

本文属于HTML/CSS专栏文章&#xff0c;适合WEB前端开发入门学习&#xff0c;详细介绍HTML/CSS如果使用&#xff0c;如果对你有所帮助请一键三连支持&#xff0c;对博主系列文章感兴趣点击下方专栏了解详细。 博客主页&#xff1a;Duck Bro 博客主页系列专栏&#xff1a;HTML/CS…

在VScode中如何将界面语言设置为中文

VSCode安装后的默认界面是只有英文的&#xff0c;如果想用中文界面&#xff0c;那么就需要安装对应的插件&#xff0c;vscode插件可以从扩展中心去搜索并安装。 安装vscode后打开vscode&#xff0c;点击左侧的扩展按钮。 在搜索框中输入chinese&#xff0c;弹出chinese&#x…

2023高教社杯数学建模国赛C题思路解析+代码+论文

如下为C君的2023高教社杯全国大学生数学建模竞赛C题思路分析代码论文 C题蔬菜类商品的自动定价与补货决策 在生鲜商超中&#xff0c;一般蔬菜类商品的保鲜期都比较短&#xff0c;且品相随销售时间的增加而变差, 大部分品种如当日未售出&#xff0c;隔日就无法再售。因此&…

nmp ERR! code ERR SOCKET TIMEOUT nmp ERR!network npmSocket timeout(已解决)

当安装vue-cli时&#xff0c;出现超时错误 npm ERR! code ECONNRESET npm ERR! network This is a problem related to network connectivity npm ERR! code ECONNRESET npm ERR! network aborted npm ERR! network This is a problem related to network connectivity. npm E…

用go实现一个循环队列

目录 队列数组队列的“假溢出”现象循环队列三种判断队列空和满的方法无下标&#xff08;链式&#xff09;有下标&#xff08;顺序&#xff09;长度标记 go用顺序表实现一个循环队列队列的链式存储结构 队列 队列&#xff08;queue&#xff09;是只允许在一端进行插入操作&…

Python—下载清华大学鹏城实验室遥感数据

当想下载清华大学鹏城实验室10m土地利用数据的时候&#xff0c;发现他们的下载方式很奇怪&#xff0c;只能一页页的点名称全选 &#xff0c;然后批量下载&#xff0c;再一个个的加入浏览器下载&#xff0c;当一次下载过多就回卡顿和下载失败&#xff0c;所以就有了想用python进…

C++项目实战——基于多设计模式下的同步异步日志系统-①-项目介绍

文章目录 专栏导读项目介绍开发环境核心技术环境搭建日志系统介绍1.为什么需要日志系统2.日志系统技术实现2.1同步写日志2.2异步写日志 专栏导读 &#x1f338;作者简介&#xff1a;花想云 &#xff0c;在读本科生一枚&#xff0c;C/C领域新星创作者&#xff0c;新星计划导师&a…

Java23种设计模式之【单例模式】

目录 一.单例模式的起源&#xff0c;和应用场景 1.单例模式的前世今生&#xff01; 2.什么是单例模式&#xff1f; 2.1使用单例模式的注意事项 2.2如何理解单例模式&#xff1f; 2.3单例模式的优势以及不足&#xff01; 2.4使用场景 二.实现 1.实现思路 1.1创建一个 S…

基于SSM的网络游戏公司官方平台

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

python实现zscore归一化和minmax标准化

zscore归一化&#xff1a; minmax from sklearn import preprocessing from sklearn.preprocessing import StandardScaler import numpy as np# 数据 x np.array([[1.,-1.,2.],[2.,0.,0.],[0.,1.,-1.]]) print(----------------minmaxscaler标准化-------------) # 调用minma…

初始化一个 vite + vue 项目

创建项目 首先使用以下命令创建一个vite项目 npm create vite然后根据提示命令 cd 到刚创建的项目目录下&#xff0c;使用npm install安装所需要的依赖包&#xff0c;再使用npm run dev即可启动项目 配置 vite.config.js 添加process.env配置&#xff0c;如果下面 vue-route…

mysql课堂笔记 mac

目录 启动mac上的mysql 进入mysql mac windows 创建数据库 创建表 修改字段数据类型 修改字段名 增加字段 删除字段 启动mac上的mysql sudo /usr/local/mysql/support-files/mysql.server start 直接输入你的开机密码即可。 编辑 进入mysql mac sudo /usr/local…

Java认识异常(超级详细)

目录 异常的概念和体系结构 异常的概念 异常的体系结构 异常的分类 1.编译时异常 2.运行时异常 异常的处理 防御式编程 LBYL EAFP 异常的抛出 异常的捕获 异常声明throws try-catch捕获并处理 finally 异常的处理流程 异常的概念和体系结构 异常的概念 在Java中…

一篇文章教会你如何降低代码的冗余度——探索指针数组,数组指针,函数指针,函数指针数组,回调函数的奥妙

前言&#xff1a;人们总说指针是c语言的灵魂&#xff0c;是因为指针的使用技巧是“千姿百态”的&#xff0c;程序员可以通过指针来直接访问内存&#xff0c;这就赋予了它功能的多样性以及更多意想不到的编程技巧与方式&#xff0c;在本篇文章中&#xff0c;笔者就给大家带来指针…

嵌入式Linux驱动开发(LCD屏幕专题)(二)

一、结合APP分析LCD驱动程序 1、open app: open("/dev/fb0", ...) 主设备号: 29, 次设备号: 0 -------------------------------------------------------------- kernel:fb_open // fbmem.cstruct fb_info *info;info get_fb_info(fbidx);if (info->fbop…

VB:顺序查找

VB&#xff1a;顺序查找 Private Sub Command1_Click()Dim i%, m%Dim x(1 To 10) As SingleFor i 1 To 10x(i) Val(InputBox("请输入"))Next im seqSearch(x, 10)If (m 1) ThenPrint "已找到"ElsePrint "未找到"End If End Sub Function se…

Altium 高级技巧 在扁平原理图中创建多个ROOM

Altium 的 ROOM 这个特性非常有用&#xff0c; 使用ROOM在编辑PCB时&#xff0c;可以很方便的整体拖动局部器件和电路图&#xff0c;为模块化电路设计提供便利 常规的设计模式应该采用垂直模式&#xff0c; 由顶层-中间层-底层&#xff0c; 顶层设计整体的框图连接&#xff…

JavaSE【 String 类】(2)(

一、字符串的不可变性 1.String本身不可变 字符串修改 注意&#xff1a;尽量避免直接对 String 类型对象进行修改&#xff0c;因为 String 类是不能修改的&#xff0c;所有的修改都会创建新对象&#xff0c;效率 非常低下。 public static void main(String[] args) {/*** S…

sublime编辑latex 出现参考文献无法编译报错:citation “...” undefined

问题描述 使用sublime编译latex文件时&#xff0c;参考文献按照常规的方式放好&#xff0c;ctrl B 编译的时候&#xff0c;显示找不到参考文献&#xff0c;编译出的pdf文件也没有references&#xff1a; 但是把文件放到overleaf上就可以直接编译出来&#xff0c;说明是本地编…