Linux 内核同步

一、基本介绍

1、基本概念

        Linux 内核同步是指控制多个进程按照一定的规则或顺序访问某些系统资源的机制,下面是几个关键概念

1、临界区和竞争条件

        a.临界区:访问和操作共享数据的代码段

        b.竞争条件:多个执行线程在一个临界区同时执行

2、死锁:每个线程都在互相等待,但它们永远不会释放占用的资源

        a.自死锁:一个执行线程试图去获取一个自己已经持有的锁,它不得不等待锁释放,但因为它忙于等待这个锁,所以自己永远也不会有机会释放释放锁

        b.ABBA死锁:每个线程都持有一把其他进程需要得到的锁

2、避免死锁

使用以下设计规则避免死锁

a.按顺序加锁
b.防止发生饥饿
c.不要重复请求一个锁
4.设计力求简单,越复杂加锁机制,越可能造成死锁

二、内核同步方法

1、原子操作

1、两个原子操作绝对不会并发的访问一个变量

2、Linux提供了两组原子操作接口,一组针对整数进行操作,一组对单独的位进行操作

3、针对整数的原子操作只能对atomic_t类型的数据进行处理,便于移植和保证该类型数据不会传递给任何非原子函数

typedef struct {volatile int counter;
} atomic_ttypedef struct {volatile long counter;
} atomic64_t

        下面列举部分api, 全部api可搜索官方文档

(1)整数操作api(32位)
APIDesc
ATOMIC_INIT(int i)声明一个atomic_t变量,并初始化为i
int atomic_read(atomic_t *v)读取atomic_t变量,转化成int类型,原子操作
void atomic_set(atomic_t *v, int i)设置v值为i,原子操作
void atomic_add(int i, atomic_t *v)v=v+i,原子操作
(2)位操作api   
APIDesc
void set_bit(int nr, void *addr)设置addr所指对象的第nr位,原子操作
void clear_bit(int nr, void *addr)清空addr所指对象的第nr位,原子操作
void test_and_set_bit(int nr, void *addr)设置addr所指对象的第nr位,并返回原先的值,原子操作
void test_bit(int nr, void *addr)返回addr所指对象的第nr位,原子操作

2、自旋锁

        多个线程访问临界区,可以用自旋锁进行保护,自旋锁最多只能被一个可执行线程持有,其他线程需等待锁的释放。持有自旋锁的时间尽可能小于两次上下文切换耗时

自旋锁:其他线程自旋等待,消耗CPU时间
信号量:其他线程睡眠,锁释放后唤醒线程,两次上下文切换开销

(1)用法

        可以使用在中断处理程序中,但是在获取锁之前要禁用本地中断,否则可能导致死锁

 注:中断处理程序中不能使用信号量, 因为中断上下文是禁止睡眠的,中断通常发生频繁,考虑到系统性能不会给其分配task_struct,也就是说中断上下文不是一个进程,在睡眠后,无法调度唤醒

(2)api
APIDesc
spin_lock()获取指定自旋锁
spin_lock_irq()禁止本地中断,并获取指定的自旋锁
spin_lock_bh()禁用所有下半部的执行,并获取指定的自旋锁
spin_unlock()释放指定的自旋锁
spin_unlock_irq()释放指定的自旋锁,并激活本地中断
spin_unlock_bh()释放指定的自旋锁,并激活所有下半部的执行
  • 由于下半部可以抢占进程上下文的代码,故在下半部和进程上下文共享数据时,应该使用spin_lock_bh()禁止下半部,获取自旋锁对临界区的保护
  • 由于中断处理程序可以抢占下半部,故如果二者共享数据时,需要使用spin_lock_irqsave()或spin_lock_irq()禁止本地中断,获取自旋锁对临界区的保护
  • 由于同类tasklet不可能同时运行,故不需要加锁
  • 由于不同类tasklet可以在不同处理器上运行,如果共享数据,需要获取普通自旋锁对临界区进行保护
  • 由于同类型的软中断可以在不同处理器上运行,所以不同软中断共享数据时,需要获取锁的保护

3、读写自旋锁

读自旋锁:一个或多个任务并发持有读自旋锁,写锁等待所有读者释放锁

写自旋锁: 写自旋锁最多被一个任务持有,且此时不存在并发的读操作

APIDesc
rwlock_init()动态初始化指定的rwlock_t
read_lock()获取指定的读自旋锁
read_unlock()释放指定的读自旋锁
write_lock()获取指定的写自旋锁
write_unlock()释放指定的写自旋锁

4、信号量

        信号量是内核中允许睡眠的锁,如果一个任务试图获取已被占用的信号量,信号量会将其推入一个等待队列,让其睡眠,当信号量释放时,任务会被唤醒。相比于自旋锁,可以提高CPU的使用率,但是引入了两次上下文切换和维护等待队列的开销,适用于锁被长时间持有的情况

互斥信号量:临界区只允许一个任务持有,比较常用

计数信号量:临界区允许至多有count个任务持有,当count=1就是互斥信号量

(1)创建信号量 
// 静态声明信号量
// count:临界区允许访问的最多线程数量
struct semphore name;
sema_init(&name,count);// 互斥信号量的定义和初始化
static DECLARE_MUTEX(name);//初始化一个动态创建的计数信号量
//sem是指针
sema_init(sem,count);
//初始化一个动态创建的互斥信号量
init_MUTEX(sem);
(2)api
  • P()和down():通过信号量计数减1来请求获取一个信号量,如果结果大于等于0,则获取信号量锁,进入临界区。反之,线程进入等待队列,进入睡眠状态
  • V()和up():通过信号量计数加1来释放一个信号量,唤醒等待队列中的线程
apiDesc
sema_init(struct semaphore *, int)通过指定的信号量计数初始化信号量
init_MUTEX(struct semaphore *)通过信号量计数为1,初始化信号量
down_interruptible(struct semaphore *)尝试获取信号量,如果信号量被占用,则进入可中断睡眠状态
down(struct semaphore *)尝试获取信号量,如果信号量被占用,则进入不可中断睡眠状态
down_trylock(struct semaphore *)尝试获取信号量,如果信号量被占用,则返回非0值
up(struct semaphore *)释放信号量,如果等待队列不为空,则唤醒等待队列中的线程

5、互斥体

  • 任何时刻只有一个进程可以持有mutex
  • 需要在同一个上下文加解锁,不能递归加解锁
  • 不能在中断或者下半部使用mutex,可以睡眠
  • 相比信号量,优先使用mutex
apiDesc
DEFINE_MUTEX(struct mutex);静态地定义mutex
init_MUTEX(struct semaphore *)动态初始化mutex
mutex_lock(struct mutex *);为指定的mutex上锁,如果锁不可用则睡眠
mutex_unlock(struct mutex *);为指定的mutex解锁
mutex_trylock(struct mutex *);尝试获取指定的mutex,如果成功则返回1,反之返回0。
mutex_is_lock(struct mutex *);如果锁被占用,则返回1,反之返回0。

5、完成量

        内核中一个任务需要发出信号通知另一个任务发生了某个特定事件,利用完成变量可以做到同步,可以唤醒正在等待的任务

apiDesc
DECLARE_COMPLETION(mr_comp);静态地定义并初始化完成变量
init_completion(struct completion *);初始化指定的动态创建的完成变量
wait_for_completion(struct completion *);等待接收指定的完成变量信号
complete(struct completion *);发信号唤醒任何等待任务

6、顺序锁

        顺序锁对临界区进行读取数据时不加锁,只有写进程加锁。在写者少,读者多场景比较适用

        当有数据写入时,序列值会增加,读取前后会检查序列值的一致性,如不一致说明读时候有数据写入,需要重新读取。

apiDesc
DEFINE_SEQLOCK(const seqlock_t);静态地定义并初始化seq锁
write_seqlock(const seqlock_t *);获取写锁
write_sequnlock(const seqlock_t *);释放写锁
read_seqbegin(const seqlock_t *);读取获取读锁之前的值,返回sequence的值。
read_seqretry(const seqlock_t *,unsigned );将seq锁中sequence的前后值进行对比。如果前后值不相等,则返回值为1,读者需要重新进行读操作;反之,返回值为0,则读者成功完成了读操作。

7、屏障

        编译器为了提高效率,可能存在将读和写操作进行重新排序。屏障是一组确保代码顺序执行的指令,保证跨越屏障的读写操作不会发生重新排序

apiDesc
rmb()阻止跨越屏障的载入动作发生重排序
wmb()阻止跨越屏障的存储动作发生重排序
mb()阻止跨越屏障的载入和存储动作发生重排序
barrier()阻止编译器跨屏障对载入或存储操作进行优化

【参考博客】

[1] Linux 内核设计与实现

[2] Linux内核 | 内核同步 - 世至其美

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

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

相关文章

并发编程——8.阶段小结

并发编程到目前为止一共发了7篇文章了,下面就并发的内容简单的来做一个阶段性的小结。 在开始,我们通过一个很简单的例子(单线程的),讲了Java运行时内存,这部分内容很重要,对于以后理解并发有很…

Composer安装与配置:简化PHP依赖管理的利器(包括加速镜像设置)

在现代的PHP开发中,我们经常会使用许多第三方库和工具来构建强大的应用程序。然而,手动管理这些依赖项可能会变得复杂和耗时。为了解决这个问题,Composer应运而生。Composer是一个PHP的依赖管理工具,它可以帮助我们轻松地安装、更…

微服务和K8S

微服务和Kubernetes(通常简称为K8s)都是现代软件开发和部署中常用的概念和工具。它们有着各自独特的特点和作用: 1. **微服务**: - **定义**:微服务是一种架构设计风格,将应用程序拆分为一组小型、独立…

【JavaEE初阶系列】——网络编程 UDP客户端/服务器 程序实现

目录 🚩UDP和TCP之间的区别 🎈TCP是有连接的 UDP是无连接的 🎈TCP是可靠传输 UDP是不可靠传输 🎈TCP是面向字节流 UDP是面向数据报 🎈TCP和UDP是全双工 👩🏻‍💻UDP的socket ap…

共享IP和独享IP如何选择,两者有何区别?

有跨境用户在选择共享IP和独享IP时会有疑问,不知道该如何进行选择,共享IP和独享IP各有其特点和应用场景,选择哪种方式主要取决于具体需求和预算。以下是对两者的详细比较: 首先两者的主要区别在于使用方式和安全性:共…

使用c语言libexpat开源库解析XML数据

1 libexpat简介 Expat 是一个用 C 语言编写的开源 XML 解析库,以其高性能和小巧的体积著称。Expat 兼容多种操作系统平台,包括但不限于 Windows、Linux、macOS 等。由于其跨平台特性和简单易用的API,Expat 成为了许多C/C程序员解析XML文档的…

git安装配置教程(小白保姆教程2024最新版)

目录 一、Git是什么?二、安装Git1.下载git2.安装git3.检测git 三、配置Git1.配置本地信息2.配置SSH1)SSH与SSH Key是什么?2)生成SSH Key3)获取ssh key公钥内容(id_rsa.pub)4)Github账号上添加公…

【java数据结构-二叉树(上)】

java数据结构-二叉树(上) 二叉树的概念二叉树的节点介绍 二叉树构造如何使用兄弟表示法构造二叉树两种特别的二叉树二叉树的基本性质: 二叉树的存储二叉树的遍历:前序遍历:中序遍历:后序遍历:层…

Vue链接跳转地址 href 中有参数带有#

Vue链接跳转地址 href 中有参数带有# A跳转B 带参数backURL 转码一次会被浏览器解码 xxxx?backurlencodeURIComponent(url) 到B页面拿到的query 值取不到 需要对地址转码两次才能取值成功 xxxx?backurlencodeURIComponent(encodeURIComponent(url))

【随笔】Git 基础篇 -- 远程仓库 git clone(二十五)

💌 所属专栏:【Git】 😀 作  者:我是夜阑的狗🐶 🚀 个人简介:一个正在努力学技术的CV工程师,专注基础和实战分享 ,欢迎咨询! 💖 欢迎大…

如何本地搭建开源导航页配置服务Dashy并发布到公网分享好友使用

文章目录 简介1. 安装Dashy2. 安装cpolar3.配置公网访问地址4. 固定域名访问 正文开始前给大家推荐个网站,前些天发现了一个巨牛的 人工智能学习网站, 通俗易懂,风趣幽默,忍不住分享一下给大家。[点击跳转到网站] 简介 Dashy 是…

学习C++有没有必要学习boost库?

在深入学习C这一强大且灵活的编程语言的过程中,是否有必要学习Boost库是许多开发者会面临的一个重要问题。Boost库,被誉为C的“瑞士军刀”,以其丰富的工具集和强大的功能性深受广大C程序员的喜爱。本文将就此问题进行详细的探讨。 一、Boost…

前端开发基础(HTML5 + CSS3)【第一篇】:HTML标签之文字排版、图片、链接、音频、视频 涵盖了两个综合案例 做到了基础学得会,实战写的出

点击前往前端开发基础专栏: 文章目录 HTML5 CSS3 开发一、开发环境搭建下载 VS Code1. 2 插件的下载1.3 项目和文件的下载 二、 什么是 HTML2.1 标签的语法2.2 代码演示:2.3 小结 三 、HTML基本骨架3.1 快捷键生成HTML骨架3.2 代码展示3.3 小结 四、标…

阿里淘天一面凉经

电话面,秒挂。 由于答的依托。导致面试官一开始就准备要挂我了。后面问的参考性不大。 总结: 1.自我介绍 2.项目里自己体会比较多的,遇到困难比较大的技术实现。(没复习) 3.项目中什么场景下用到分布式锁&#xf…

【Vit】Vision Transformer 入门与理解

在学习VIT之前,建议先把 Transformer 搞明白了:【transformer】入门与理解 做了那些改进? 看图就比较明白了,VIT只用了Encoder的部分,把每一个图片裁剪成若干子图,然后把一个子图flatten一下,…

【MATLAB源码-第12期】基于matlab的4FSK(4CPFSK)的误码率BER理论值与实际值仿真。

1、算法描述 4FSK在频移键控(FSK)编码的基础上有所扩展。FSK是一种调制技术,它通过在不同频率上切换来表示不同的数字或符号。而4FSK则是FSK的一种变种,表示使用了4个不同的频率来传输信息。 在4FSK中,每个数字或符号…

真随机数和伪随机数

真随机数和伪随机数 我先是看的TI的DL_TRNG_sendCommand(TRNG, DL_TRNG_CMD_NORM_FUNC);函数,能生成真随机数。要在microchip的八位机上移植同样的功能,但是那个库函数是伪随机数,我就看了两者的区别。区别就是,真随机数会出现随机…

基于Java的图书借阅网站, java+springboot+vue开发的图书借阅管理系统 - 毕业设计 - 课程设计

基于Java的图书借阅网站, javaspringbootvue开发的图书借阅管理系统 - 毕业设计 - 课程设计 文章目录 基于Java的图书借阅网站, javaspringbootvue开发的图书借阅管理系统 - 毕业设计 - 课程设计一、功能介绍二、代码结构三、部署运行1、后端运行步骤2、…

PaddleDetection 项目使用说明

PaddleDetection 项目使用说明 PaddleDetection 项目使用说明数据集处理相关模块环境搭建 PaddleDetection 项目使用说明 https://github.com/PaddlePaddle/PaddleDetection/blob/release/2.7/configs/ppyoloe/README_cn.md 自己项目: https://download.csdn.net/d…

功能测试、自动化测试、性能测试的区别

🍅 视频学习:文末有免费的配套视频可观看 🍅 关注公众号:互联网杂货铺,回复1 ,免费获取软件测试全套资料,资料在手,薪资嘎嘎涨 按测试执行的类型来分:功能测试、自动化测…