【系列文章】Linux中的并发与竞争[04]-信号量

【系列文章】Linux中的并发与竞争[04]-信号量

该文章为系列文章:Linux中的并发与竞争中的第4篇
该系列的导航页连接:
【系列文章】Linux中的并发与竞争-导航页


文章目录

  • 【系列文章】Linux中的并发与竞争[04]-信号量
    • 一、信号量
    • 二、实验程序的编写
      • 2.1驱动程序编写
      • 2.2编写测试 APP
      • 2.3运行测试


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

一、信号量

信号量是操作系统中最典型的用于同步和互斥的手段,本质上是一个全局变量,信号量的值表示控制访问资源的线程数,可以根据实际情况来自行设置,如果在初始化的时候将信号量量值设置为大于 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 函数如下:

函数描述
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

二、实验程序的编写

2.1驱动程序编写

与之前章节设置标志位,在同一时间内只允许一个任务对共享资源进行访问的方式所不同,本小节将采用信号量的方式避免竞争的产生。本实验设置的信号量量值为 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_teststatic 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 类型字符串全局变量 kbufstatic 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()函数初始化 cdev_test 结构体,并链接到fops_test 结构体cdev_init(&dev1.cdev_test,&fops_test);//将 owner 字段指向本模块,可以避免在模块的操作正在被使用时卸载该模块dev1.cdev_test.owner = THIS_MODULE;cdev_add(&dev1.cdev_test,dev1.dev_num,1);//使用 cdev_add()函数进行字符设备的添加//使用 class_create 进行类的创建,类名称为class_testdev1.class_test = class_create(THIS_MODULE,"class_test");//使用 device_create 进行设备的创建,设备名称为 device_testdevice_create(dev1.class_test,0,dev1.dev_num,0,"device_test");return 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");

2.2编写测试 APP

#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;
}

2.3运行测试

使用以下命令运行测试 app

./app /dev/device_test topeet

在这里插入图片描述
可以看到传递的 buf 值为 topeet,然后输入以下命令在后台运行两个 app,来进行竞争测试,运行结果如下图所示:

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

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

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

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

相关文章

Elasticsearch启动失败?5步修复权限问题

文章目录&#x1f6a8; 为什么会出现这个问题&#xff1f;✅ 解决方案&#xff1a;修复数据目录权限并确保配置生效步骤 1&#xff1a;确认数据目录存在且权限正确步骤 2&#xff1a;确认 elasticsearch.yml 中的配置步骤 3&#xff1a;**删除或清空 /usr/share/elasticsearch/…

Docker push 命令:镜像发布与管理的艺术

Docker push 命令&#xff1a;镜像发布与管理的艺术1. 命令概述2. 命令语法3. 核心参数解析4. 推送架构图解5. 完整工作流程6. 实战场景示例6.1 基础推送操作6.2 企业级推送流程6.3 多架构镜像推送7. 镜像命名规范详解8. 安全最佳实践8.1 内容信任机制8.2 最小权限原则9. 性能优…

智能合约测试框架全解析

概述 智能合约测试库是区块链开发中至关重要的工具&#xff0c;用于确保智能合约的安全性、正确性和可靠性。以下是主流的智能合约测试库及其详细解析。 一、主流测试框架对比 测试框架开发语言主要特点适用场景Hardhat WaffleJavaScript/TypeScript强大的调试功能&#xf…

【大模型算法工程师面试题】大模型领域新兴的主流库有哪些?

文章目录 大模型领域新兴主流库全解析:国产化适配+优劣对比+选型指南(附推荐指数) 引言 一、总览:大模型工具链选型框架(含推荐指数) 二、分模块详解:优劣对比+推荐指数+选型建议 2.1:训练框架(解决“千亿模型怎么训”) 2.2:推理优化(解决“模型跑起来慢”) 2.3:…

端口打开与服务可用

端口打开与服务可用“端口已打开但服务不可用” 并非矛盾&#xff0c;而是网络访问中常见的分层问题。要理解这一点&#xff0c;需要先明确 “端口打开” 和 “服务可用” 的本质区别&#xff1a;1. 什么是 “端口打开”&#xff1f;“端口打开” 通常指 操作系统的网络层监听该…

ByteDance_FrontEnd

约面了&#xff0c;放轻松&#xff0c;好好面 盲点 基础知识 Function 和 Object 都是函数&#xff0c;而函数也是对象。 Object.prototype 是几乎所有对象的原型链终点&#xff08;其 proto 是 null&#xff09;。 Function.prototype 是所有函数的原型&#xff08;包括 Obje…

go语言,彩色验证码生成,加减法验证,

代码结构相关代码 captcha/internal/captcha/generator.go package captchaimport (_ "embed" // &#x1f448; 启用 embed"image""image/color""image/draw""image/png""io""math/rand""golang.…

PuTTY软件访问ZYNQ板卡的Linux系统

PuTTY 是一款非常经典、轻量级、免费的 SSH、Telnet 和串行端口连接客户端&#xff0c;主要运行于 Windows 平台。它是在开源许可下开发的&#xff0c;因其小巧、简单、可靠而成为系统管理员、网络工程师和开发人员的必备工具。网上有非常多的下载资源。 我们使用PuTTY软件对ZY…

做一个RBAC权限

在分布式应用场景下&#xff0c;我们可以利用网关对请求进行集中处理&#xff0c;实现了低耦合&#xff0c;高内聚的特性。 登陆权限验证和鉴权的功能都可以在网关层面进行处理&#xff1a; 用户登录后签署的jwt保存在header中&#xff0c;用户信息则保存在redis中网关应该对不…

【算法】day1 双指针

1、移动零&#xff08;同向分3区域&#xff09; 283. 移动零 - 力扣&#xff08;LeetCode&#xff09; 题目&#xff1a; 思路&#xff1a;注意原地操作。快排也是这个方法&#xff1a;左边小于等于 tmp&#xff0c;右边大于 tmp&#xff0c;最后 tmp 放到 dest。 代码&#…

Linux 日志分析:用 ELK 搭建个人运维监控平台

Linux 日志分析&#xff1a;用 ELK 搭建个人运维监控平台 &#x1f31f; Hello&#xff0c;我是摘星&#xff01; &#x1f308; 在彩虹般绚烂的技术栈中&#xff0c;我是那个永不停歇的色彩收集者。 &#x1f98b; 每一个优化都是我培育的花朵&#xff0c;每一个特性都是我放飞…

Linux网络:socket编程UDP

文章目录前言一&#xff0c;socket二&#xff0c;服务端socket3-1 创建socket3-2 绑定地址和端口3-3 接收数据3-4 回复数据3-5关闭socket3-6 完整代码三&#xff0c;客户端socket3-1 为什么客户端通常不需要手动定义 IP 和端口前言 学习 socket 编程的意义在于&#xff1a;它让…

【从零到公网】本地电脑部署服务并实现公网访问(IPv4/IPv6/DDNS 全攻略)

从零到公网&#xff1a;本地电脑部署服务并实现公网访问&#xff08;IPv4/IPv6/DDNS 全攻略&#xff09; 适用场景&#xff1a;本地 API 服务、大模型推理服务、NAS、远程桌面等需要公网访问的场景 关键词&#xff1a;公网 IP、端口映射、内网穿透、IPv6、Cloudflare DDNS 一、…

模块二 落地微服务

11 | 服务发布和引用的实践 服务发布和引用常见的三种方式&#xff1a;Restful API、XML配置以及IDL文件。今天我将以XML配置方式为例&#xff0c;给你讲解服务发布和引用的具体实践以及可能会遇到的问题。 XML配置方式的服务发布和引用流程 1. 服务提供者定义接口 服务提供者发…

C++程序员速通C#:从Hello World到数据类型

C程序员光速入门C#&#xff08;一&#xff09;&#xff1a;总览、数据类型、运算符 一.Hello world&#xff01; 随着.NET的深入人心,作为一个程序员&#xff0c;当然不能在新技术面前停而止步&#xff0c;面对着c在.net中的失败,虽然有一丝遗憾&#xff0c;但是我们应该认识到…

Linux相关概念和易错知识点(44)(IP地址、子网和公网、NAPT、代理)

目录1.IP地址&#xff08;1&#xff09;局域网和公网①局域网a.网关地址b.局域网通信②运营商子网③公网&#xff08;2&#xff09;NAPT①NAPT过程②理解NAPT③理解源IP和目的IPa.目的IPb.源IP③最长前缀匹配④NAT技术缺陷2.代理服务&#xff08;1&#xff09;正向代理&#xf…

工业智能终端赋能自动化生产线建设数字化管理

在当今数字化浪潮的推动下&#xff0c;自动化生产线正逐渐成为各行各业提升效率和降低成本的重要选择。随着智能制造的深入发展&#xff0c;工业智能终端的引入不仅为生产线带来了技术革新&#xff0c;也赋予了数字化管理新的动力。一、工业智能终端&#xff1a;一体化设计&…

【Vue2手录06】计算属性Computed

一、表单元素的v-model绑定&#xff08;核心场景&#xff09; v-model 是Vue实现“表单元素与数据双向同步”的语法糖&#xff0c;不同表单元素的绑定规则存在差异&#xff0c;需根据元素类型选择正确的绑定方式。 1.1 四大表单元素的绑定规则对比表单元素类型绑定数据类型核心…

FPGA入门-数码管静态显示

19. 数码管的静态显示 在许多项目设计中&#xff0c;我们通常需要一些显示设备来显示我们需要的信息&#xff0c;可以选择的显示设备有很多&#xff0c;而数码管是使用最多&#xff0c;最简单的显示设备之一。数码管是一种半导体发光器件&#xff0c;具有响应时间短、体积小、…

深入理解大语言模型(5)-关于token

到目前为止对 LLM 的描述中&#xff0c;我们将其描述为一次预测一个单词&#xff0c;但实际上还有一个更重要的技术细 节。即 LLM 实际上并不是重复预测下一个单词&#xff0c;而是重复预测下一个 token 。对于一个句子&#xff0c;语言模型会 先使用分词器将其拆分为一个个 to…