linux内核分析笔记----内核同步

2023-11-12

       内核同步讲的比较多了,我也就不太啰嗦了,先说一些概念,然后就是方法。

       同步就是避免并发和防止竞争条件。有关临界区的例子我就不举了,随便一本操作系统的书上都有。锁机制的提出也算解决了一些问题,我们待会再说,现在只要知道锁的使用是自愿的,非强制的。linux自身也提供了几种不同的锁机制,区别主要在于当锁被争用时,有些会简单地执行等待,而有些锁会使当前任务睡眠直到锁可用为止,这个后面细说。真正的困难在于发现并辨认出真正需要共享的数据和相应的共享区。先来说一些感性的话:大多数内核数据结构都需要加锁,如果有其他执行线程可以访问这些数据,那么就给这些数据加上某种形式的锁。如果任何其他什么东西能看到它,那么就要锁住它。简而言之,几乎访问所有的内核全局变量和共享数据都需要某种形式的同步方法。有关加锁的细节,在嵌套的锁时,要保证以相同的顺序获取锁,不要重复请求同一个锁,释放时,最好还是以获得锁的相反顺序来释放锁。加锁的粒度用来描述加锁保护的数据规模。接下来就开始讨论真正的同步方法:

       1.原子操作。就是指执行过程不被打断的操作,是 不能够被分割的指令。关于这个linux内核提供了两组原子操作接口:原子整数操作和原子位操作。好,先来说说这个原子整数操作。针对整数的原子操作只能对atomic_t类型的数据进行处理。需要说明的是,尽管linux支持的所有机器上的整形数据都是32位的,但是使用atomic_t的代码只能将该类型的数据当作24位来用,原因就不说了。使用原子操作需要的声明在asm/atomic.h中。原子整数操作列表如下;

      1

       在编写代码的时候,能使用原子操作的时候,就尽量不要使用复杂的加锁机制,因为大多数或者100%情况下,原子操作比更复杂的同步方法相比较而言,给系统带来的开销小,对高速缓存行(cache-line)的影响也很小。

       对应于原子整数操作,还有一种原子操作就是原子位操作,它们是与体系结构相关的操作,定义在文件<asm/bitops.h>,它是对普通的内存地址进行操作的。它的参数是一个指针和一个位号,第0位是给定地址的最低有效位。这里没有想atomic_t一样的数据结构,只要指针指向任何希望的数据,就可以进行操作。原子位操作函数列表如下:

      2

       同时,内核还提供了一组与上述操作对应的非原子位函数,操作完全相同,不同在于不保证原子性且名字前缀多了两个下划线,例如与test_bit()对应的非原子形式是__test_bit().如果不需要原子操作,这时这些函数的执行效率可能更高。内核还提供了两个函数用来从指定的地址开始搜索第一个被设置(或未被设置)的位:

int find_first_bit(unsigned long *addr,unsigned int size);
int find_first_zero_bit(unsigned long *addr,unsigned int size);

       其中,第一个参数是一个指针,第二个参数是要搜索的总位数。返回值分别是第一个被设置的(或没被设置的)位的位号。如果要搜素的范围仅限于一个字,使用_ffs()和__ffz()这两个函数更好,它们只需要给定一个要搜索的地址做参数。

       2.自旋锁。临界区远没有我们想象那样的那样简单,有时可能跨越多个函数。自旋锁是linux内核中最常见的锁。如果一个执行线程试图获得一个被争用的(已经被持有)的自旋锁,那么这个线程就会一直进行忙循环----旋转----等待锁重新可用。同一个锁可以用在多个位置,例如,对于给定数据的访问都可以得到保护和同步。上述的自旋过程是很费时间的,所以自旋锁不应该被长时间持有。我们前边所过也可以让请求线程休眠,CPU可以执行其他代码,直到锁可用时在唤醒它,但自旋锁由于忙等待,它是占用CPU的。上面的休眠过程也会带来上下文切换带来的开销,所以持有自旋锁的时间最好小于完成两次上下文切换的时间。自旋锁的实现和体系结构密切相关,代码往往用汇编实现,这些与体系结构相关的部分定义在<asm/spinlock.h>,实际需要用到的接口定义在文件<linux/spinlock.h>中。自旋锁操作列表如下:

      3

       自旋锁仅仅被当作一个设置内核抢占机制时候被启用的开关,如果禁止内核抢占,那么在编译时自旋锁会被完全剔除出内核。另外就是linux内核实现的自旋锁是不可递归的。自旋锁可是用在中断处理程序中(使用信号量,会导致睡眠)。在中断处理程序中使用自旋锁,一定要在获取锁之前禁止本地中断(当前处理器上的中断请求)。否则,中断处理程序会打断正持有锁的内核代码,有可能会试图去争用这个已经被持有的自旋锁。这样一来,中断处理程序就会自旋。但锁的持有者在这个中断处理程序执行完毕前不可能运行,这就是双重请求死锁。注意,需要关闭的只是当前处理器上中断,如果中断发生在不同的处理器上,即使中断处理程序在同一个锁上自旋,也不会妨碍锁的持有者(在不同处理器上)最终释放锁。最后两个函数spin_lock_bh()用于获取指定锁,同时它会禁止所有下半部的执行。相应的spin_unlock_bh()函数执行相反的操作。最后,提醒要注意自旋锁和下半部的关系。

       3.读--写自旋锁。有时,锁的用途是可以明确分为读取和写入的。当对某个数据结构的操作可以被划分为读/写两种类别时,就可以使用这里说的读---写自旋锁。这种机制为读和写分别提供了不同的锁。一个或多个读任务可以并发的持有读者锁,而写锁只能被一个写任务持有,而且此时不能有并发的读。操作函数列表如下:

      5  

       事实上,即使一个线程递归地获得同一读锁也是安全的。还是那句话,使用之前要能明确的分清读和写。如果在中断处理程序中只有读操作而没有写操作,那么,就可以混合使用“中断禁止”锁,使用read_lock()而不是read_lock_irqsave()对读进行保护。不过,你还是需要用write_lock_irqsave()禁止有写操作的中断,否则,中断里读操作就有可能锁死在写锁上(假如读者正在进行操作,包含写操作的中断发生了,由于读锁还没有全部被释放,所以写操作会自旋,而读操作只能在包含写操作的中断返回后才能继续,释放读锁,这时死锁就发生了)。最后需要说明的是,这种机制偏向与读锁:当读锁被持有时,写操作为了互斥访问只能等待,但是,读者却可以继续成功地占用锁。而自旋等待的写者在所有读锁释放锁之前是无法获得锁的。所以大量的读者会使挂起的写者处于饥饿状态。

       4.信号量。它是一种睡眠锁,实现和体系结构相关的,定义在<asm/semaphore.h>。如果有一个任务试图获得一个已经被占用的信号量时,信号量会将其推进一个等待队列,然后让其睡眠。这时的处理器会重获自由,从而去执行其他代码。当持有信号量的进程将信号量释放后,处于等待队列中的那个任务将被唤醒,并获得该信号量。如果需要在自旋锁和信号量中做出选择,应该根据锁被持有的时间长短做判断,如果加锁时间不长并且代码不会休眠,利用自旋锁是最佳选择。相反,如果加锁时间可能很长或者代码在持有锁有可能睡眠,那么最好使用信号量来完成加锁功能。信号量一个有用特性就是它可以同时允许任意数量的锁持有者,而自旋锁在一个时刻最多允许一个任务持有它。信号量同时允许的持有者数量可以在声明信号量时指定,当为1时,成为互斥信号量,否则成为计数信号量。操作函数列表如下:

      4

       信号量支持pv操作,我就不说了。

       5.读-写信号量。这个和信号量的关系是和自旋锁与读写自旋锁是一样的关系,由rw_semaphore结构表示的。定义在linux/rwsem.h中。所有的读写信号量都是互斥信号量。操作函数有:

DECLARE_RWSEM(name)				//声明名为name的读写信号量,并初始化它。
void init_rwsem(struct rw_semaphore *sem);	//对读写信号量sem进行初始化。
void down_read(struct rw_semaphore *sem);	//读者用来获取sem,若没获得时,则调用者睡眠等待。
void up_read(struct rw_semaphore *sem);		//读者释放sem。
int down_read_trylock(struct rw_semaphore *sem); //读者尝试获取sem,如果获得返回1,如果没有获得返回0。可在中断上下文使用。
void down_write(struct rw_semaphore *sem);	//写者用来获取sem,若没获得时,则调用者睡眠等待。
int down_write_trylock(struct rw_semaphore *sem);	//写者尝试获取sem,如果获得返回1,如果没有获得返回0。可在中断上下文使用
void up_write(struct rw_semaphore *sem);	//写者释放sem。
void downgrade_write(struct rw_semaphore *sem);	//把写者降级为读者。

       其实上面的就是分了两类:信号量和自旋锁,使用时选择的比较如下:

      6

       6.完成变量。如果内核中一个任务需要发出信号通知另外一个任务发生了某个特定事件,利用完成变量(complete variables)是使两个任务得以同步的简单方法。如果任务要执行一些工作时,另一个任务就会在完成量上等待,当这个任务完成工作后,会使用完成变量去唤醒在等待的任务,就像信号量一样,它们两者思想是一样的, 它仅仅提供了代替信号量的一个简单地解决方法。它有结构completion表示,定义在linux/completion.h中。操作接口列表如下:

      7

       完成变量的通常用法是将完成变量作为数据结构中的一项动态创建,而完成变量的初始化工作的内核代码将调用wait_for_completion()进行等待。初始化完成后,初始化函数调用completion()唤醒在等待的内核任务。

       7.BKL(大内核锁)。是一个全局自旋锁,年代有些久远了,使用它主要是为了方便实现从linux最初的SMP过度到细粒度加锁机制,有趣的特性如下:

1.持有BKL的任务可以睡眠。因为当任务无法调度时,所加锁会自动被丢弃,所加锁会自动被丢弃;当任务被调度时,锁又会被重新获得。当然,这不是
  说,当任务持有BKL时,睡眠是安全的,仅仅是这样做,因为睡眠不会造成任务死锁。
2.BKL是一种递归锁,一个进程可以多次请求一个锁,并不会像自旋锁那样产生死锁现象。
3.BKL可以在进程上下文中。
4.BKL是有害的。

       不要奇怪,这种机制在内核中已经几乎不存在了,也不鼓励使用。这里提到这样的思想和接口,是为了万一遇到呢?操作接口(linux/smp_lock.h)列表如下:

      8

       BKL在被持有的时候同样会禁止内核抢占。多数情况下,BKL更像是保护代码而不是保护数据。

       8.Seq锁。这种锁提供了一种简单机制,用于读写共享数据。实现这种锁主要依靠一个序列计数器。当数据被进行写入操作时,会得到一个锁,并且序列值会增加。在读取数据之前和之后,序列号都被读取。如果读取的序列号值相同,说明在读操作进行的过程中没有被写操作打断过。此外,如果读取的值是偶数,那么就说明写操作没有发生(要明白因为锁的初始值是0,所以写锁会使值为奇数,释放的时候变成偶数)。操作接口如下:

seqlock_t mr_seq_lock = SEQLOCK_UNLOCKED;    //定义顺序锁
write_seqlock(&mr_seq_lock);		     //写操作代码块…
write_sequnlock(&mr_seq_lock);

       这和普通自旋锁类似,不同的情况发生在读时,写自旋锁有很大不同:

unsigned long seq;
do{
	seq = read_seqbegin(&my_seq_lock);
}while(read_seqretry(&my_seq_lock,seq));

       在多个读者和少数读者共享一把锁的时候,seq锁有助于提供一种非常轻量级和具有可扩展性的外观。但是seq锁对写者更有利。只有没有其他写者,写锁总是能够被成功获得。读者不会影响写锁,这点和读写自旋锁及信号量是一样的。另外,挂起的写者会不断地使得读操作循环,知道不再有任何写者锁持有锁为止。

       9.禁止抢占:实际情况是这样的,有时我们不需要锁,但同时又不希望进程抢占(内核抢占)的修改某些数据,这时就要关闭内核抢占。可以通过preempt_disable()禁止内核抢占。这时一个可以嵌套调用的函数,可以使用任意次。每次调用都必须有一个相应的preempt_enable()调用,当最后一次preempt_enable()被调用时,内核抢占才重新启用。内核抢占相关操作如下:

      9

       抢占技术存放着被持有锁的数量和preempt_disable()的调用次数,当计数为0时,那么内核可以进行抢占。否则不能。为了用更简洁的方法解决每个处理器上的数据访问问题,你可以通过get_cpu()获得处理器编号(假定用这种编号来对每个处理器的数据进行索引的)。这个函数在返回当前处理器号前首先会关闭内核抢占:

int cpu = get_cpu();
...对每个处理器的数据进行操作
put_cpu();

       10.屏障。当处理多处理器之间或硬件设备之间的同步问题时,有时需要在程序代码中以指定的顺序发出读内存(写入)和写内存(存储)指令。在和硬件交互时,时常需要确保一个给定的读操作发生在其他读或写操作之前。另外,在多处理器上,可能需要按写数据的顺序读数据(通常确保后以同样的顺序进行读取)。但是编译器和处理器为了提高效率,可能对读和写重新排序,这样无疑是问题复杂化了。幸好,所有可能重新排序和写的处理器提供了机器指令来确保顺序要求。同样也可以指示编译器不要对给定点周围的指令序列进行重新排序。这些确保顺序的指令叫做屏障(barriers);这样的内存和编译屏障方法列表如下:

      10

       最后,说明的是,对于不同的体系结构,屏障的实际效果差别很大。但应该在最坏的情况下使用恰当的内存屏障,这样代码才能在编译时执行针对体系结构的优化。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux内核分析笔记----内核同步 的相关文章

  • Sqli-labs之Less-20和Less-21和Less-22

    Less 20 基于错误的cookie头部POST注入 首先从已知的条件中我们知道这又是一道 头部注入 那么我们先输入正确的用户名和密码看一下登录成功是什么样子的 回显有User Agent IP这样从当次Request直接获取的 也有Co
  • 兴业银行利用以太坊区块链发行债券,金融科技冲击下的银行业未来(上篇)

    点击上方 蓝色字 可关注我们 暴走时评 日前 兴业银行通过以太坊区块链发行了类证券代币的债券 兴业银行的举动可能意味着银行承认 即使比特币或以太等无许可协议可能会带来颠覆性的威胁 他们依旧无法放弃其中潜在的巨大机遇 作者 Michael J
  • PHP+Laravel框架RabbitMQ简单使用

    RabbitMQ安装教程请转到 RabbitMQ安装教程 超详细 1 创建生产者 在app Http Controllers里创建一个php控制器文件 namespace App Http Controllers use App Http
  • docker中安装jupyter,并远程打开jupyter

    一 拉取镜像 拉取一个自带miniconda的镜像源 docker pull continuumio miniconda3 二 启动容器 docker run id p 宿主机端口 容器端口 name 自己取的容器名 v 宿主机目录 容器目
  • Python 综合应用小项目一

    数据库报错重连机制 利用异常捕获来获取mysql断开的报错 然后再重连 1 import MySQLdb as mysql 2 3 class DB 4 def init self host user passwd db name 5 se
  • 基于spring validation多层对象校验

    1 第一层对象定义 package com ybw validation demo vo import lombok AllArgsConstructor import lombok Data import lombok NoArgsCon
  • idea workspace.xml 报错

    1 找到 workspace xml 位置并删除 2 重新install
  • LeGO-LOAM 系列(1): LeGO-LOAM 安装以及概述

    一 github GitHub RobustFieldAutonomyLab LeGO LOAM 二 安装依赖 1 ROS Ubuntu 64 bit 16 04 ROS Kinetic 比较常规 就不赘述了 2 gtsam Georgia
  • 集合引用类型 上

    目录 Object Array 创建数组 数组的静态方法 数组空位 数组索引 检测数组 迭代器方法 复制和填充方法 转换方法 栈方法 队列方法 排序方法 操作方法 搜索和位置方法 迭代方法 归并方法 Object 显式地创建Object 的
  • Windows7 Python3 搭建Scrapy 爬虫框架

    Windows7 64位 Python3 7 安装Scrapy 提示如下错误信息 解决办法 1 在python库中下载twisted相应的包 whl文件 官网地址 https www lfd uci edu gohlke pythonlib
  • Android底部导航栏的三种风格实现

    一 效果图展示 如果动图没有动的话 也可以看下面这个静态图 以下挨个分析每个的实现 这里只做简单的效果展示 大家可以基于目前代码做二次开发 二 BottomNavigationView 这是 Google 给我们提供的一个专门用于底部导航的
  • JWT 登录认证及 token 自动续期方案解读

    欢迎关注方志朋的博客 回复 666 获面试宝典 方志朋 号主为CSDN博客之星 博客访问量突破一千万 著有畅销书 深入理解SpringCloud与微服务构建 主要分享Java 后端架构等技术 用大厂程序员的视角来探讨技术进阶 面试指南 职业
  • 昨天看了一本c#的教程

    昨天看了一本c 的教程 昨天看了一本c 的教程 那是本很早前就买了的书 虽然也不是没看过 但是昨天重新看了下 感觉收获还是不小的 从c 的类型 到它的方法 还有就是面向对象的一些概念 覆盖 继承 我不敢说我学到了多少 但是我很喜欢 post
  • 2024年计算机专业毕业设计题目大全-吊炸天的2024届计算机毕业设计选题推荐参考

    作者 计算机源码社 个人简介 本人七年开发经验 擅长Java Python PHP NET 微信小程序 爬虫 大数据等 大家有这一块的问题可以一起交流 学习资料 程序开发 技术解答 文档报告 JavaWeb项目 微信小程序项目 Python
  • openwrt编译ipk包提示缺少feeds.mk文件

    问题具体表现如下 这个问题困扰了我两个多星期 总算解决了 解决方案如下 首先 先应该把配置菜单调好 我的硬件是7620a 要编译的ipk包为helloworld 所以应该使用 make menuconfig命令进入配置菜单 进入后 将1号框
  • TCP/IP基础&pysocket

    TCP IP基础 pysocket 1 网络简述 网络 计算机网络功能主要包括实现资源共享 实现数据信息的快速传递 网络协议 在网络数据传输中 都遵循的执行规则 规范 C S 服务器 Server 向客户端提供资源 保存客户端数据 处理客户
  • 【华为OD机试真题 python】支持优先级的队列【2023 Q2

    题目描述 支持优先级的队列 实现一个支持优先级的队列 高优先级先出队列 同优先级时先进先出 如果两个输入数据和优先级都相同 则后一个数据不入队列被丢弃 队列存储的数据内容是一个整数 输入描述 一组待存入队列的数据 包含内容和优先级 输出描述
  • PHP SQL实现公司数据库的增删改查

    文末附文件 题目要求 Use the following SQL DDL statements to create the six tables required for this project Note that you need to
  • python之celery

    Celery是由Python开发的一个简单 灵活 可靠的处理大量任务的分发系统 可以实时处理任务 也可以定时异步处理任务 每次分发任务后得到一个ID 然后根据这个ID查询任务执行情况 安装 pip install celery eventl

随机推荐

  • sqllabs详解与知识点汇总(内含代码审计)

    sqllabs 1 65 详解 关于注释符的详解 SQL注入注释符 使用条件及其他注释方式的探索 impulse 博客园 cnblogs com HTTP请求方法 GET 对比 POST HTTP 方法 GET 对比 POST 菜鸟教程 r
  • docker基本操作

    Docker官方建议在Ubuntu中安装 建议安装在CentOS7 X以上版本 1 安装Docker 1 yum包更新到最新 sudo yum update 2 安装需要的软件包 yum util提供yum config manager功能
  • java.math.BigDecimal用法

    Java在java math包中提供的API类BigDecimal 用来对超过16位有效位的数进行精确的运算 双精度浮点型变量double可以处理16位有效数 在实际应用中 需要对更大或者更小的数进行运算和处理 float和double只能
  • 继承和多态的内存图解

    今天被继承和多态困扰 在CSDN上找了好几个内存分配讲解 个人感觉不全吧 就把他们做了个整合 讲解的是多态的方法和成员调用和继承中的方法和变量的调用 什么是多态 同一个对象 在不同时刻表现出来的不同形态 多态的前提 要有继承或实现关系要有方
  • web robotframework xpath元素定位

    1 定位购买按钮 在这里 我写的是 td class text center button class ng isolate scope span text 购买 提示找不到元素 原因是button的class值 我把他改成class bt
  • 调试osgEarth(七)地图map图层的构建过程-添加layer(4)--打开ImageLayer

    继续调试 创建空影像 建了个1x1x1的空图片 这个也比较简单 ImageLayer建立了一个1x1x1的空图片
  • spring boot 2.x 应用可视化监控

    来源 简书 内容 应用可视化监控 prometheus grafana https www jianshu com p 7ecb57a3f326 修改为spring boot 2 0时 1 首先 添加依赖如下依赖
  • E: Unable to locate package kubelet 解决

    昨天搭建k8s集群环境时 安装报错 显示无法找到 1 打开vim etc apt sources list 写入阿里云的源 deb https mirrors aliyun com kubernetes apt kubernetes xen
  • aiVMS----CentOS7.6安装RabbitMQ安装

    entOS7 6安装RabbitMQ安装 安装一 快速的安装方法是使用Package Cloud提供的脚本 Package Cloud也可以用于通过yum安装最新的Erlang版本 使用PackageCloud安装RabbitMQ 官网参考
  • table问题总结

    前景 最近开发需要原生table 之前使用很少用 了解比较少 这次对于样式和功能要求也比较高 对与遇到的问题做下总结和分享 问题与解决方案 行高不定问题 描述 表格每一行的高度不确定 会自动适配 设置行高和高度均无效 产生原因 表格设置了固
  • R语言用ROCR包出现载入程辑包:‘gplots’ The following object is masked from ‘package:stats’错误

    谢谢点进来 如果你觉得有帮助 麻烦点个赞 假如在R studio运行的代码是这样的 library ROCR 首先看到这个问题的时候 我认为没有安装gplots包 可以按下图所示看是否有该包 如果没有则点击install输入包名安装 奇怪的
  • Ledger of Harms

    Under immense pressure to prioritize engagement and growth technology platforms have created a race for human attention
  • JavaScript快速排序算法

  • C#单线程和多线程端口扫描器

    C 单线程和多线程端口扫描器 一 项目创建以及页面设计 一 项目新建 二 页面设计 二 单线程实现端口扫描 一 代码实现 二 运行结果 三 多线程实现端口扫描 一 程序实现 二 运行结果 四 总结 五 参考资料 一 项目创建以及页面设计 一
  • JCenter下载太慢?教你修改Maven仓库地址为国内镜像

    转载自 http www yrom net blog 2015 02 07 change gradle maven repo url 近来迁移了一些项目到Android Studio 采用Gradle构建确实比原来的Ant方便许多 但是编译
  • StyleCLIP学习笔记

    https github com orpatashnik StyleCLIP The main inferece script is placed in mapper scripts inference py Inference argum
  • 安装librocksdb.so.4.1的共享库

    安装librocksdb so 4 1的共享库 注 以下命令需在root模式下进行 1 clone rocksDB 命令行运行git clone https github com facebook rocksdb git 2 切换到4 1
  • Java调试原理初探

    对于所有程序员 程序调试是一项必备的技能 在java程序中 最简单的就是通过 System out println 来打印输出各种变量来发现问题 而用的最多的莫过于通过各种调试器来进行调试 如图一所示的eclipse调试器 甚至还可以进行远
  • 微信号正则校验

    由于最近有朋友做微信开发 让我帮其找一个微信号正则校验 代码 本来以为网上会有很多 但一搜才发现 没有一个可用的校验微信号的正则 所以只好自己写一个了 废话不多说 直接贴结果 首先我们要明确微信号规则 微信账号仅支持6 20个字母 数字 下
  • linux内核分析笔记----内核同步

    内核同步讲的比较多了 我也就不太啰嗦了 先说一些概念 然后就是方法 同步就是避免并发和防止竞争条件 有关临界区的例子我就不举了 随便一本操作系统的书上都有 锁机制的提出也算解决了一些问题 我们待会再说 现在只要知道锁的使用是自愿的 非强制的