【Linux内核解析-linux-5.14.10-内核源码注释】信号量semaphore机制

news2024/11/28 14:38:00

信号量实现方法

信号量机制是一种用于控制并发访问的同步机制,常用于多进程或多线程之间的协调。在Linux内核中,信号量机制是通过struct semaphore结构体来实现的。

每个semaphore结构体包含一个计数器和一个等待队列,它们用于跟踪当前可用的资源数量以及正在等待这些资源的进程或线程。当某个进程或线程需要访问共享资源时,它会先尝试获取信号量。如果信号量的计数器大于0,表示有可用的资源,该进程或线程就可以继续执行,并将信号量计数器减1;否则,它会被阻塞并加入等待队列,直到有其他进程或线程释放了资源并增加了信号量计数器。

Linux内核提供了若干函数用于操作信号量,例如:

  • sema_init(): 初始化一个新的信号量,并设置初始计数器值。
  • down(): 尝试获取信号量。如果信号量计数器大于0,则将其减1并返回0;否则,当前进程或线程将被阻塞,直到有其他进程或线程释放了资源。
  • up(): 释放信号量,并将其计数器加1。如果有任何进程或线程在等待信号量,则唤醒其中一个进程或线程。

在Linux内核中,信号量机制是用于实现各种同步原语的基础。例如,读写锁、自旋锁等都是基于信号量机制实现的。另外,Linux内核还提供了一些高级的同步原语,例如spinlock和mutex等,它们通常也使用了信号量机制作为底层实现。

为什么说读写锁、自旋锁等都是基于信号量机制实现的

读写锁、自旋锁等都是用于控制并发访问的同步机制,在Linux内核中常被用于保护共享资源的访问。这些同步机制之所以被认为是基于信号量机制实现的,是因为它们在底层使用了信号量来进行实现。

具体来说,读写锁是一种允许多个线程同时读取共享资源,但只允许一个线程写入共享资源的同步机制。在Linux内核中,读写锁是通过rw_semaphore结构体来实现的。rw_semaphore结构体包含两个信号量:一个用于保护共享资源的读取,另一个用于保护共享资源的写入。当有多个线程尝试读取共享资源时,它们会获取读取信号量,并将读取计数器加1。如果有一个线程尝试写入共享资源,则它必须先获取写入信号量,将读取计数器置为0,然后执行写入操作。当写入操作完成后,该线程会释放写入信号量,并唤醒等待读取信号量的线程。

自旋锁是一种忙等待的同步机制,它允许多个线程竞争访问共享资源,但只允许一个线程成功获取锁。在Linux内核中,自旋锁是通过spinlock_t结构体来实现的。该结构体包含一个原子变量和一个等待队列,用于记录当前锁的状态以及等待获取锁的线程。当一个线程尝试获取自旋锁时,它会先忙等待一段时间,如果在这段时间内没有其他线程持有锁,则该线程将成功获取锁并执行临界区代码。如果在这段时间内发现有其他线程持有锁,则当前线程会被放入等待队列中,并进入休眠状态,直到有其他线程释放了锁并唤醒了等待队列中的线程。

无论是读写锁还是自旋锁,它们都使用了信号量机制作为底层实现。例如,rw_semaphore结构体中的读取信号量和写入信号量都是基于struct semaphore来实现的;spinlock_t结构体中的等待队列也是基于struct semaphore实现的。因此,这些同步机制之所以被认为是基于信号量机制实现的,是因为它们在底层使用了信号量来进行实现。
在这里插入图片描述

内核同步机制信号量和其他方式的区别

在Linux内核中,同步机制主要包括信号量、自旋锁、读写锁、互斥锁等。这些同步机制的实现方式不同,各有优缺点,可以根据具体情况选择使用。

  1. 信号量

信号量是一种经典的同步机制,用于控制并发访问的同步机制。在Linux内核中,信号量通过struct semaphore结构体来实现,它的主要特点是:

  • 支持多进程或多线程之间的同步。
  • 可以用于保护共享资源的访问。
  • 当某个进程或线程需要访问共享资源时,它会先尝试获取信号量。如果信号量的计数器大于0,表示有可用的资源,该进程或线程就可以继续执行,并将信号量计数器减1;否则,它会被阻塞并加入等待队列,直到有其他进程或线程释放了资源并增加了信号量计数器。
  1. 自旋锁

自旋锁是一种忙等待的同步机制,它允许多个线程竞争访问共享资源,但只允许一个线程成功获取锁。在Linux内核中,自旋锁通过spinlock_t结构体来实现,它的主要特点是:

  • 支持多CPU的并发访问。
  • 可以用于保护共享资源的访问。
  • 当一个线程尝试获取自旋锁时,它会先忙等待一段时间,如果在这段时间内没有其他线程持有锁,则该线程将成功获取锁并执行临界区代码。如果在这段时间内发现有其他线程持有锁,则当前线程会被放入等待队列中,并进入休眠状态,直到有其他线程释放了锁并唤醒了等待队列中的线程。
  1. 读写锁

读写锁是一种允许多个线程同时读取共享资源,但只允许一个线程写入共享资源的同步机制。在Linux内核中,读写锁通过rw_semaphore结构体来实现,它的主要特点是:

  • 支持多进程或多线程之间的同步。
  • 可以允许多个线程同时读取共享资源,从而提高并发性能。
  • 当一个线程需要写入共享资源时,它必须先获取写锁,将读取计数器置为0,然后执行写入操作。当写入操作完成后,该线程会释放写锁,并唤醒等待读取信号量的线程。而读取操作则可以通过获取读锁来进行,如果有其他线程持有写锁,则读取操作将被阻塞,直到写锁被释放为止。
  1. 互斥锁

互斥锁是一种最简单、最常用的同步机制,它允许只有一个线程访问共享资源。在Linux内核中,互斥锁通过mutex结构体来实现,它的主要特点是:

  • 支持多进程或多线程之间的同步。
  • 只允许一个线程同时访问共享资源,从而保证了数据的一致性和完整性。
  • 当一个线程需要访问共享资源时,它必须先获取互斥锁,执行完临界区代码后再释放锁。如果其他线程也需要访问该共享资源,则它们必须等待当前线程释放锁后才能继续执行。

总体来说,这些同步机制都可以用于保护共享资源的访问,但在具体应用场景中,需要根据不同的需求选择合适的同步机制。例如,在需要高并发读取的情况下,可以使用读写锁;在需要支持多CPU并发访问的情况下,可以使用自旋锁;而在需要简单易用、适用于多种场景的情况下,可以使用互斥锁。

信号量代码内核中位置在Linux内核中,信号量的实现代码位于kernel/lock/semaphore.c文件中。

该文件包含了用于管理信号量的函数,例如:

  1. sys_semget():用于创建或获取一个信号量集。
  2. sys_semop():用于对一个信号量集中的一个或多个信号量执行操作。
  3. sys_semctl():用于控制一个信号量集,例如删除或获取信号量集信息。
  4. down_interruptible()up():用于等待和释放一个二进制信号量。

此外,include/linux/semaphore.h头文件定义了Linux内核中使用的信号量结构体和函数原型。

信号量大VP操作解释

V操作和P操作是信号量的两种基本操作,它们的名称来自于荷兰语单词:

- V操作:表示“verhogen”,意为“增加”或“提高”。
- P操作:表示“proberen”,意为“尝试”或“试图”。

这些术语最初由计算机科学家Edsger W. Dijkstra在1965年引入,用于描述一种同步机制。后来,它们成为了信号量的标准操作名称,在计算机科学中广泛使用。

V操作和P操作是信号量的两种基本操作,也称为释放操作和获取操作。在Linux内核中,它们通常被分别实现为up()down_interruptible()函数。

具体来说:

  • V操作(释放操作):使用up()函数实现。它将一个二进制信号量的值加1,或者将一个计数信号量的值加n(n为参数),并唤醒任何等待该信号量的进程。

  • P操作(获取操作):使用down_interruptible()函数实现。它将一个二进制信号量的值减1,或者将一个计数信号量的值减n(n为参数)。如果信号量的值为0,则当前进程会被阻塞,并加入到该信号量的等待队列中。

需要注意的是,Linux内核中的信号量有多种类型,包括二进制信号量和计数信号量等。对于不同类型的信号量,V操作和P操作可能会略有不同的实现方式。但是,它们的基本概念和功能都是相似的。
在这里插入图片描述

down 获取操作实现

在这里插入图片描述

static noinline void __sched __down(struct semaphore *sem)
{
	__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}

> 1、 这里noinline含义

:
#define noinline __attribute__((noinline))
是一个宏定义,定义了一个函数属性__attribute__((noinline)),用于告诉编译器不要对带有该属性的函数进行内联优化。内联优化是一种编译器优化技术,它将函数的代码插入到调用函数的代码中,以减少函数调用的开销。但是,内联优化可能会导致代码体积增大,缓存命中率降低,从而降低程序的性能。因此,在某些情况下,需要禁止内联优化,使用该属性可以达到这个目的。在这里,noinline属性用于告诉编译器不要对带有该属性的函数进行内联优化,即使在编译器开启了内联优化的选项也不会进行内联优化。

> 2、解释__down函数
这段代码定义了一个静态、不进行内联优化的函数__down(),用于对信号量进行下降操作。该函数调用了__down_common()函数,传递了三个参数:指向信号量的指针、睡眠状态和最大超时时间。其中,睡眠状态为TASK_UNINTERRUPTIBLE,表示当前进程在等待信号量时不可被中断;最大超时时间为MAX_SCHEDULE_TIMEOUT,表示当前进程等待信号量的最长时间为一个调度周期。这个函数是一个阻塞函数,只有当获取到信号量时才会返回。由于该函数使用了__attribute__((noinline))属性,因此不会被内联优化,从而避免了代码体积增大和缓存命中率降低的问题。

信号量的实现通常需要使用自旋锁来保证信号量的原子性和线程安全性。自旋锁是一种轻量级的锁机制,它不会引起进程的上下文切换,因此在短时间内可以快速地获得锁。在信号量的实现中,自旋锁通常用于保护信号量的计数值和等待队列,以防止并发访问和修改。当信号量的计数值为0时,需要将当前进程挂起并加入到等待队列中,等待信号量的计数值被其他进程增加后被唤醒。此时,需要使用自旋锁来保护等待队列,以防止其他进程在访问等待队列时发生竞争和冲突。因此,信号量中会使用自旋锁来保证并发访问的正确性和线程安全性。

UP 释放操作

内核源码
内核源码
解释:
这段代码实现了一个计数信号量的上升操作,用于释放一个计数信号量。与mutex不同的是,up()函数可以被任何上下文调用,包括从未调用down()函数的任务。当信号量的计数值被释放时,将会唤醒等待队列中的一个进程,使其继续执行。计数信号量的上升操作不会引起进程的睡眠,因此可以在任何上下文中调用。这个函数的参数是一个指向信号量的指针。在函数内部,通过原子操作保证了信号量的原子性和线程安全性。当信号量的计数值被释放时,如果等待队列中有等待的进程,则会唤醒其中的一个进程;否则,计数值将会增加1。这个函数是一个非阻塞函数,不会引起进程的睡眠。

这个函数内部,通过原子操作raw_spin_lock_irqsave()和raw_spin_unlock_irqrestore()保证了信号量的原子性和线程安全性。首先,通过list_empty()函数判断等待队列sem->wait_list是否为空。如果等待队列为空,则将信号量的计数值加1;否则,调用__up()函数唤醒等待队列中的一个进程,并将信号量的计数值保持不变。这个函数是一个非阻塞函数,不会引起进程的睡眠。如果等待队列中有等待的进程,则会唤醒其中的一个进程,使其继续执行;否则,计数值将会增加1。

关于信号量相关内容重点解释,面试有用!

  1. 什么是信号量?信号量是一种同步机制,用于在多个进程或线程之间协调访问共享资源。

  2. 信号量的实现原理是什么?信号量的实现原理是通过一个计数器和一个等待队列来控制对共享资源的访问。当计数器的值大于0时,表示共享资源可用,进程或线程可以直接访问;当计数器的值等于0时,表示共享资源不可用,进程或线程需要等待。等待的进程或线程会被加入到等待队列中,等待资源可用时被唤醒。

  3. 信号量的类型有哪些?信号量的类型包括二元信号量和计数信号量。二元信号量的计数器只有两个值,通常用于互斥访问共享资源;计数信号量的计数器可以有多个值,通常用于限制共享资源的访问数量。

  4. 信号量的操作有哪些?信号量的操作包括初始化、上升操作(up)、下降操作(down)等。初始化操作用于初始化信号量的计数器和等待队列;上升操作用于释放信号量,增加计数器的值并唤醒等待队列中的一个进程;下降操作用于获取信号量,减少计数器的值并将当前进程加入到等待队列中等待。

  5. 信号量和互斥锁的区别是什么?信号量和互斥锁都是用于同步访问共享资源的机制,但是它们的实现方式和使用场景不同。互斥锁通常用于保护临界区,只有一个进程或线程可以进入临界区,其他进程或线程需要等待;信号量通常用于限制资源的数量,多个进程或线程可以同时访问共享资源,但是访问数量受到信号量的限制。

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

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

相关文章

【Promptulate】一个强大的LLM Prompt Layer框架,构建更强悍的GPT应用

本文节选自笔者博客: https://www.blog.zeeland.cn/archives/promptulate666 项目地址:https://github.com/Undertone0809/promptulate 💖 作者简介:大家好,我是Zeeland,全栈领域优质创作者。📝…

2023.5.14 第五十三次周报

目录 前言 文献阅读:基于BO-EMD-LSTM模型预测教室长期二氧化碳浓度 背景 思路 BO-EMD-LSTM 混合模型 EMD 算法 与其他模型的比较 结论 论文代码 总结 前言 This week, I studied an article that uses LSTM to predict gas concentration.This study wa…

Netty中NioEventLoopGroup介绍

一、Netty基本介绍 Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。Netty 在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。 Netty 是一…

cpuinfo库: 使用Python生成C++接口

文章目录 1. 目的2. 设计3. 生成器: Python 代码4. 结果: C++ 代码1. 目的 背景: cpuinfo 库提供了 CPU 信息的查询, 涵盖了 x86 和 arm 等常见的指令集架构, 可以提供自行实现的 CPU 基础功能函数的正确性验证。自行实现 CPU 基础功能则是为了简化功能、同时提供原汁原味…

详细介绍如何将博客图片从github/gitee批量转移到阿里云图床

背景介绍 我为什么想把博客文章中存储在github种的图片转移到阿里云呢? 存储在github中的图片有时会不稳定,破坏写文章过程中的思路和心流体验。写着写着就发现图片上传又出现问题了,很影响心情,阿里云是付费图床,在国…

【更新中】苹果自家的as汇编器的特色风格(与wasm的不同)

as汇编器是苹果的汇编器,Xcode 生成的代码也是as风格的,而as风格与大部分人在大学里所学的微软的masm风格大相径庭,所以本文会列出二者的不同。不过由于细节太多,没法一次性写出所有的不同,所以会持续更新。 你可能会…

linux常用系统工作命令

前言: 最近又重新抓起linux开始学习起来了,以前干过一段时间的菜鸟运维,对linux操作系统有点浅显的认识,中间又因为工作关系渐渐的又接触不到了,最近的博文算是回忆也当作笔记用,重新开始学习linux系统。 …

MYSQL主从复制和读写分离.1

1、什么是读写分离? 读写分离,基本的原理是让主数据库处理事务性增、删、改操作(insert、update、delete),而从数据库处理select查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。 2、为什…

Mysql日志管理与备份恢复

目录 一、Mysql日志管理1、日志的分类1.1 错误日志1.2 通用查询日志1.3 二进制日志1.4 慢查询日志1.5 配置日志文件 2、日志的查询 二、Mysql备份与分类1、数据备份的重要性2、造成数据丢失的原因3、数据库备份的分类3.1 从物理与逻辑的角度划分3.2 从数据库的备份策略角度划分…

【配电网重构】基于SOE算法的多时段随机配电网重构方法【IEEE33节点、IEEE84节点】(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

go语言channel(管道)和 select的结合使用

给个小建议:如果是初学者,建议把基础知识朗读一遍,有个大概印象,后面思考多了,就会“由量变达到质变”,从而有所顿悟。 目录 一、基础知识二、例子11、管道ch的缓冲区为10,select中有case读取管…

基于stm32物联网开发板(2)--LCD屏幕

基于stm32物联网开发板(2)–LCD屏幕 LCD应用展示: LCD屏幕应用 1.概述 屏幕尺寸为1.3寸,分辨率240*240,颜色格式RGB565,驱动IC:ST7789VW;超大可视角度:大于160(显示屏中可视角度最大的一种屏幕)&#xff1…

《深入理解Java虚拟机》JVM是怎么实现方法的动态调用的?方法句柄

《深入理解Java虚拟机》JVM是怎么实现方法的动态调用的?方法句柄 1.方法句柄出现的原因 某个国家举办了跑步比赛,有亚洲,欧洲还是非洲人参赛,但是有机器人也参赛了。机器人不属于人类阵营,怎么能让机器人也参加进来呢&#xff1…

Java递归生成树

1.建菜单表 CREATE TABLE t_menu ( id int(11) NOT NULL AUTO_INCREMENT, pid int(11) NOT NULL, name varchar(255) DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB AUTO_INCREMENT11 DEFAULT CHARSETutf8mb4; 2.造一些数据 注意:根节点的pid0&#xff0c…

利用Rsoft开展弯曲光纤仿真分析

Rsoft是一款优秀的光学仿真软件,里面集成了多个模块,其中BPM模块利用光束传播法(Beam Propagation Method),能够进行多种类型光器件的仿真,比如分束器、光纤等。这次,利用该模块展示如何开展光纤…

SpringSecurity简单的练手项目(SpringBoot+SpringSecurity+JWT)

文章目录 一、项目介绍二、SpringSecurity简介SpringSecurity中的几个重要组件:1.SecurityContextHolder(class)2.SecurityContext(Interface)3.Authentication(Interface)4.AuthenticationMana…

Eclipse的介绍与安装

Eclipse简介 Eclipse 是一个开放源代码的,基于 Java 的可扩展开发平台。Eclipse官方版是一个集成开发环境(IDE),可以通过安装不同的插件实现对其它计算机语言编辑开发,如C、Php、Python等等。 Eclipse的下载 下载时需要访问网址 http://…

Android系统原理性问题分析 - RefBase、sp、wp 分析

声明 在Android系统中经常会遇到一些系统原理性的问题,在此专栏中集中来讨论下。接触Android系统,遇到很多sp、wp相关问题,此篇分析Android系统内的智能指针问题。此篇参考一些博客和书籍,代码基于Android 9.0.0,不方…

3D点云的基本操作(基于PCL编程)

知识储备 右手系 右手,拇指,食指,中指,分别是x,y,z的正方向。左手系则同理。 旋转矩阵 本质:两个坐标系之间的旋转关系。 用途:旋转点云。 原理:设传感器的坐标系为O1X1Y1Z1,设…

mysql 分组语句测试

建表 建表语句: CREATE TABLE student( id int not null, name char(12), sex char(1) ); 预置数据 insert into student values(1, wh, 1); insert into student values(2, wh1, 0); insert into student values(3, zyx, 0); commit; 增加字段 alt…