【Linux内核】信号量semaphore机制

news2024/11/27 17:55:47

信号量实现方法

信号量机制是一种用于控制并发访问的同步机制,常用于多进程或多线程之间的协调。在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/524741.html

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

相关文章

Linux 并发与竞争

一、并发与竞争 1、并发 Linux 系统是个多任务操作系统,会存在多个任务同时访问同一片内存区域,这些任务可 能会相互覆盖这段内存中的数据,造成内存数据混乱。 多线程并发访问, Linux 是多任务(线程)的系统,所以多线…

命令firewalld和firewall-cmd用法

firewalld命令跟firewall-cmd 1.启动firewalld服务 systemctl start firewalld.service2.关闭firewalld服务 systemctl stop firewalld.service3.重启firewalld服务 systemctl restart firewalld.service4.查看firewalld状态 systemctl status firewalld.service5.开机自启…

接口测试怎么做?全网最详细从接口测试到接口自动化详解,看这篇就够了...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 抛出一个问题&…

孙鑫VC++第三章 2.基于MFC的程序框架剖析

目录 1. MFC向导生成类 2. 框架流程 2.1 WinMain 2.2 全局对象:theApp 2.3 AfxWinMain函数 1.AfxWinMain: 2.AfxGetThread函数(thrdcore.cpp): 3.AfxGetApp是一个全局函数,定义于(afxwin1…

原型/原型链/构造函数/类

认识构造函数 为什么有构造函数 因为一般的创建对象的方式一次只能创建一个对象, 利用工厂模式创建的对象,对象的类型都是Object类型 什么是构造函数 构造函数也称之为构造器(constructor),通常是我们在创建对象时会调用的函数…

Uni-app项目应用总结(一)

目录 一.新建uniapp项目 第一步:下载HBuilder 第二步:创建uni-app项目 第三步:运行uni-app 二.uni-app组件使用 三.uni-app路由跳转  1.页面路由配置    (1)在pages.json中配置页面路由    (2)在pages.json中配置底部导航栏 2.路由跳转方法…

【输配电路 DZY-104端子排中间继电器 接通、信号转换 JOSEF约瑟】

DZY-104端子排中间继电器品牌:JOSEF约瑟型号:DZY-104名称:端子排式中间继电器触点容量:5A/250V功率消耗:≤1.5W/≤3W/≤7W/≤3VA/≤7VA/≥5W绝缘电阻:≥10MΩ 系列型号: DZY-101端子排中间继电器; DZY-104端子排中间继电器; DZY-105端子排…

华南农业大学|图像处理与分析技术综合测试|题目解答:求芒果单层坏损率

设计任务 对于一幅芒果果实内部的 CT 断层图像,试采用图像处理与分析技术,设计适当的算法和程序,首先分割出其中的坏损区域,然后计算其像素面积占整个果肉区域的百分比(单层坏损率)。请按统一要求写出算法…

nuc980 uboot 2017.11 移植:env 保存位置选择问题

开发环境 Win10 64位 ubuntu 20.04 虚拟机 VMware Workstation 16 Pro 开发板:NK-980IOT(NUC980DK61Y) gcc 交叉编译工具链: ARM 官方 gcc version 11.2.1 20220111 NUC980 uboot 版本 :尝试移植到 u-boot-2017.1…

科普 “平均工资又涨了”

周四晚上做了一个图,发了一则朋友圈,科普了一下为什么平均工资一直在涨: 曲线是 drawio 画的,不是类似 geogebra 画的精确数学函数,误差比较大,但大概就是这个意思。 收入应该是无标度分形的幂律分布&am…

孙鑫VC++第三章 4.窗口类、窗口类对象与窗口三者之间关系

目录 1. 创建CWnd 2. WinMain 3. 创建CButton 1. 创建CWnd 模拟CWnd类的封装过程。在解决方案ch04下添加一个新的空项目,项目名称为:WinMain,在项目创建完成后,将WinMain项目设为启动项目。 接下来在WinMain项目中添加一个名…

【C++ 学习 ④】- 类和对象(下)

目录 一、初始化列表 1.1 - 定义 1.2 - 使用初始化列表的原因 1.3 - 成员变量的初始化顺序 二、静态成员 2.1 - 静态成员变量 2.2 - 静态成员函数 三、友元 3.1 - 友元函数 3.2 - 友元类 四、内部类 五、匿名对象 5.1 - 匿名对象的特性 5.2 - 匿名对象的使用场景…

3.View的绘制流程

View是在什么时候显示在屏幕上面的?(如:MainActivity的布局文件activity_main.xml) setContentView最终的结果是将解析的xml文件中的View添加到DecorView中. 那么这个DecorView是什么时候添加到Window(PhoneWindow)的呢? DecorView是在ActivityThread.java的handleResumeA…

2-Zookeeper单机版安装

2-Zookeeper单机版安装 本文介绍的是 Linux 系统下 Zookeeper 安装方式 ① 下载 进入官网 https://zookeeper.apache.org/ 点击下载按钮 进入下载页 https://zookeeper.apache.org/releases.html 后选择 最新的稳定版本,如下: 3.7.1 为最新的稳定版本…

号称分割一切的图片分割模型开源了——Segment Anything Meta SAM

头条号:人工智能研究所 微信号:启示AI科技 微信小程序:AI人工智能工具 以前,要解决任何类型的分割问题,有两类方法。第一种是交互式分割,允许分割任何类别的对象,但需要人通过迭代细化掩码来指导。第二种,自动分割,允许分割提前定义的特定对象类别(例如,猫或椅子),…

【计算机系统】指令

leaq指令 一元指令 二元指令 例子 指令addq 指令subq 指令incq 指令subq 移位指令 移位指令用途 特殊运算指令

LitCTF2023 郑州轻工业大学首届网络安全赛 WP 部分

LitCTF2023 郑州轻工业大学首届网络安全赛 WP 部分 前言:Web:我Flag呢?导弹迷踪:Follow me and hack me:PHP是世界上最好的语言!!作业管理系统:Vim yyds:Ping&#xff1a…

Java基础-面向对象总结(2)

这篇文章主要讲解 Java中的 变量方法代码块访问修饰限定符Java 是值传递,还是引用传递?类和对象的生命周期..... 希望给您带来帮助 目录 变量 成员变量与局部变量的区别 静态变量和实例变量的区别?静态方法、实例方法呢? 可以…

数据分析06——Pandas中的数据抽取

1、前言: 在Pandas中进行数据抽取主要有两种方法,一种是loc方法,一种是iloc方法;在获取数据时可以获取的数据有三种形式,一种是Series类型,一种是DataFrame类型,还有一种是直接获取数据值&…