首页 > 系统相关 >linux锁的介绍和使用 -04

linux锁的介绍和使用 -04

时间:2024-05-03 16:22:54浏览次数:15  
标签:Softirq 04 中断 lock 介绍 内核 linux spin CPU

本节参考:

https://www.kernel.org/doc/html/latest/locking/index.html

https://mirrors.edge.kernel.org/pub/linux/kernel/people/rusty/kernel-locking/

 

 

锁的类型

 

Linux内核提供了很多类型的锁,它们可以分为两类:

 

① 自旋锁(spinning lock);

 

② 睡眠锁(sleeping lock)。

自旋锁

简单地说就是无法获得锁时,不会休眠,会一直循环等待。有这些自旋锁:

自旋锁的加锁、解锁函数是:spin_lock、spin_unlock,还可以加上各种后缀,这表示在加锁或解锁的同时,还会做额外的事情:

 

睡眠锁

简单地说就是无法获得锁时,当前线程就会休眠。有这些休眠锁:

 

锁的内核函数

自旋锁

spinlock函数在内核文件include\linux\spinlock.h中声明,如下表:

 

自旋锁的加锁、解锁函数是:spin_lock、spin_unlock,还可以加上各种后缀,这表示在加锁或解锁的同时,还会做额外的事情:

 

信号量semaphore

semaphore函数在内核文件include\linux\semaphore.h中声明,如下表:

 

互斥量mutex

mutex函数在内核文件include\linux\mutex.h中声明,如下表:

 

semaphore和mutex的区别

semaphore中可以指定count为任意值,比如有10个厕所,所以10个人都可以使用厕所。

而mutex的值只能设置为1或0,只有一个厕所。

是不是把semaphore的值设置为1后,它就跟mutex一样了呢?不是的。

 

看一下mutex的结构体定义,如下:

 

它里面有一项成员“struct task_struct *owner”,指向某个进程。一个mutex只能在进程上下文中使用:谁给mutex加锁,就只能由谁来解锁。

而semaphore并没有这些限制,它可以用来解决“读者-写者”问题:程序A在等待数据──想获得锁,程序B产生数据后释放锁,这会唤醒A来读取数据。semaphore的锁定与释放,并不限定为同一个进程。

主要区别列表如下:

 

何时用何种锁

本节参考:https://wenku.baidu.com/view/26adb3f5f61fb7360b4c656e.html

英文原文:https://mirrors.edge.kernel.org/pub/linux/kernel/people/rusty/kernel-locking/

你可能看不懂下面这个表格,请学习完后面的章节再回过头来看这个表格。

 

举例简单介绍一下,上表中第一行“IRQ Handler A”和第一列“Softirq A”的交叉点是“spin_lock_irq()”,意思就是说如果“IRQ Handler A”和“Softirq A”要竞争临界资源,那么需要使用“spin_lock_irq()”函数。为什么不能用spin_lock而要用spin_lock_irq?也就是为什么要把中断给关掉?假设在Softirq A中获得了临界资源,这时发生了IRQ A中断,IRQ Handler A去尝试获得自旋锁,这就会导致死锁:所以需要关中断。

内核抢占(preempt)等额外的概念

早期的的Linux内核是“不可抢占”的,假设有A、B两个程序在运行,当前是程序A在运行,什么时候轮到程序B运行呢?

① 程序A主动放弃CPU:

比如它调用某个系统调用、调用某个驱动,进入内核态后执行了schedule()主动启动一次调度。

② 程序A调用系统函数进入内核态,从内核态返回用户态的前夕:

这时内核会判断是否应该切换程序。

③ 程序A正在用户态运行,发生了中断:

内核处理完中断,继续执行程序A的用户态指令的前夕,它会判断是否应该切换程序。

 

从这个过程可知,对于“不可抢占”的内核,当程序A运行内核态代码时进程是无法切换的(除非程序A主动放弃),比如执行某个系统调用、执行某个驱动时,进程无法切换。

这会导致2个问题:

① 优先级反转:

一个低优先级的程序,因为它正在内核态执行某些很耗时的操作,在这一段时间内更高优先级的程序也无法运行。

② 在内核态发生的中断不会导致进程切换

 

为了让系统的实时性更佳,Linux内核引入了“抢占”(preempt)的功能:进程运行于内核态时,进程调度也是可以发生的。

回到上面的例子,程序A调用某个驱动执行耗时的操作,在这一段时间内系统是可以切换去执行更高优先级的程序。

对于可抢占的内核,编写驱动程序时要时刻注意:你的驱动程序随时可能被打断、随时是可以被另一个进程来重新执行。对于可抢占的内核,在驱动程序中要考虑对临界资源加锁。

使用场景

本节参考:https://wenku.baidu.com/view/26adb3f5f61fb7360b4c656e.html

英文原文:https://mirrors.edge.kernel.org/pub/linux/kernel/people/rusty/kernel-locking/

1 只在用户上下文加锁

假设只有程序A、程序B会抢占资源,这2个程序都是可以休眠的,所以可以使用信号量,代码如下:

 

 

对于down_interruptible函数,如果信号量暂时无法获得,此函数会令程序进入休眠;别的程序调用up()函数释放信号量时会唤醒它。

在down_interruptible函数休眠过程中,如果进程收到了信号,则会从down_interruptible中返回;对应的有另一个函数down,在它休眠过程中会忽略任何信号。

注意:“信号量”(semaphore),不是“信号”(signal)。

 

也可以使用mutex,代码如下:

 

注意:一般来说在同一个函数里调用mutex_lock或mutex_unlock,不会长期持有它。这只是惯例,如果你使用mutex来实现驱动程序只能由一个进程打开,在drv_open中调用mutex_lock,在drv_close中调用mutex_unlock,这也完全没问题。

2 在用户上下文与Softirqs之间加锁

假设这么一种情况:程序A运行到内核态时,正在访问一个临界资源;这时发生了某个硬件中断,在硬件中断处理完后会处理Softirq,而某个Softirq也会访问这个临界资源。

怎么办?

在程序A访问临界资源之前,干脆禁止Softirq好了!

可以使用spin_lock_bh函数,它会先禁止本地CPU的中断下半部即Softirq,这样本地Softirq就不会跟它竞争了;假设别的CPU也想获得这个资源,它也会调用spin_lock_bh禁止它自己的Softirq。这2个CPU都禁止自己的Softirq,然后竞争spinlock,谁抢到谁就先执行。可见,在执行临界资源的过程中,本地CPU的Softirq、别的CPU的Softirq都无法来抢占当前程序的临界资源。

释放锁的函数是spin_unlock_bh。

spin_lock_bh/spin_unlock_bh的后缀是“_bh”,表示“Bottom Halves”,中断下半部,这是软件中断的老名字。这些函数改名为spin_lock_softirq也许更恰当,请记住:spin_lock_bh会禁止Softirq,而不仅仅是禁止“中断下半部”(timer、tasklet里等都是Softirq,中断下半部只是Softirq的一种)。

示例代码如下:

 

3 在用户上下文与Tasklet之间加锁

Tasklet也是Softirq的一种,所以跟前面是“在用户上下文与Softirqs之间加锁”完全一样。

 

4 在用户上下文与Timer之间加锁

Timer也是Softirq的一种,所以跟前面是“在用户上下文与Softirqs之间加锁”完全一样。

 

5 在TaskletTimer之间加锁

假设在Tasklet中访问临界资源,另一个CPU会不会同时运行这个Tasklet?不会的,所以如果只是在某个Tasklet中访问临界资源,无需上锁。

 

假设在Timer中访问临界资源,另一个CPU会不会同时运行这个timer?不会的,所以如果只是在某个Timer中访问临界资源,无需上锁。

 

如果在有2个不同的Tasklet或Timer都会用到一个临界资源,那么可以使用spin_lock()、spin_unlock()来保护临界资源。不需要用spin_lock_bh(),因为一旦当前CPU已经处于Tasklet或Timer中,同一个CPU不会同时再执行其他Tasklet或Timer。

 

6 在Softirq之间加锁

这里讲的softirq不含tasklet、timer。

同一个Softirq是有可能在不同CPU上同时运行的,所以可以使用spin_lock()、spin_unlock()来访问临界区。如果追求更高的性能,可以使用“per-CPU array”,本章不涉及。

 

不同的Softirq之间,可以使用spin_lock()、spin_unlock()来访问临界区。

 

总结起来,在Softirq之间(含timer、tasklet、相同的Softirq、不同的Softirq),都可以使用spin_lock()、spin_unlock()来访问临界区。

 

示例代码如下:

 

7 硬中断上下文

假设一个硬件中断服务例程与一个Softirq共享数据,需要考虑2点:

① Softirq执行的过程中,可能会被硬件中断打断;

② 临界区可能会被另一个CPU上的硬件中断进入。

怎么办?

在Softirq获得锁之前,禁止当前CPU的中断。

在硬件中断服务例程中不需要使用spin_lock_irq(),因为当它在执行的时间Softirq是不可能执行的;它可以使用spin_lock()用来防止别的CPU抢占。

 

如果硬件中断A、硬件中断B都要访问临界资源,怎么办?这篇文章里说要使用spin_lock_irq():

https://mirrors.edge.kernel.org/pub/linux/kernel/people/rusty/kernel-locking/

但是我认为使用spin_lock()就足够了。因为Linux不支持中断嵌套,即当前CPU正在处理中断A时,中断B不可能在当前CPU上被处理,不需要再次去禁止中断;当前CPU正在处理中断A时,假如有另一个CPU正在处理中断B,它们使用spin_lock()实现互斥访问临界资源就可以了。

spin_lock_irq()/spin_unlock_irq()会禁止/使能中断,另一套函数是spin_lock_irqsave()/spin_unlock_irqrestore(),spin_lock_irqsave()会先保存当前中断状态(使能还是禁止),再禁止中断;spin_unlock_irqrestore()会恢复之前的中断状态(不一定是使能中断,而是恢复成之前的状态)。

 

示例代码如下:

写在最后:这个链接是一篇很好的文档,以后我们会完全翻译出来,现在讲的知识暂时够用了。

https://mirrors.edge.kernel.org/pub/linux/kernel/people/rusty/kernel-locking/

 

标签:Softirq,04,中断,lock,介绍,内核,linux,spin,CPU
From: https://www.cnblogs.com/liusiluandzhangkun/p/18171307

相关文章

  • 在Linux中,什么是软件仓库,并且如何管理它?
    在Linux中,软件仓库是一个集中存放软件包的在线存储库,这些软件包经过预编译、测试,并按照特定的组织结构归类。每个软件包都包含了应用程序、库文件、配置文件以及其他必要的组件,并且附带有关于软件版本、依赖关系等元数据信息。软件仓库使得用户可以方便地搜索、安装、更新和卸载软......
  • 在Linux中,如何查看所有正在运行的进程?
    在Linux中,查看所有正在运行的进程可以使用多种命令,这些命令提供了不同的信息和视图。以下是一些常用的命令:1.ps命令ps(ProcessStatus)是一个基本的进程查看工具,它可以显示当前系统中活动进程的状态。查看所有进程:psauxaux选项组合表示查看所有用户的所有进程。查看特......
  • 在Linux中,如何启动、停止或重启服务?
    在Linux中,启动、停止或重启服务的方法取决于你使用的是Systemd还是SystemVinit系统,这两种系统在不同的Linux发行版中使用。以下是两种系统下如何管理服务的基本命令:1.对于使用Systemd的系统(较新版本的CentOS、Fedora、Debian、Ubuntu等)启动服务:sudosystemctlstart服务......
  • 在Linux中,如何杀死一个进程?
    在Linux中,你可以使用多种命令来杀死一个进程。下面是一些常用的方法:1.使用kill命令kill命令用于发送信号到进程。默认情况下,kill命令发送TERM信号(即终止信号),它允许进程优雅地关闭。如果进程没有响应TERM信号,你可以使用kill-9来发送KILL信号,这将强制终止进程。示......
  • GPU插件介绍
    对于NVIDIAGPU,存在3种设备插件的实现。官方NVIDIAGPU插件要求1.节点安装了NVIDIA驱动。2.节点安装nvidia-docker2.0。3.Docker的默认运行时必须设置为nvidia-container-runtime,而不是runc。4.NVIDIA驱动版本~=384.81。NVIDIAContainerRuntime把docker的默认运行时......
  • 系统管理之Linux启动流程
    1.系统初始化进程SysV风格:initcentos5配置文件:/etc/inittabUpstart风格:initcentos6配置文件:/etc/init.d/,/etc/inittabSystemd风格:systemdcentos7配置文件:/usr/lib/systemd/system/,/etc/systemd/system/systemctl是一个systemd管理工具2.Systemd服务的启动......
  • Windows使用WSL2及docker(Ubuntu22.04 LTS)
    WSL2初始化1.换源#1cp/etc/apt/sources.list/etc/apt/sources.list.bak#2vim/etc/apt/sources.list#清空原源并替换成以下源#deb-srchttps://mirrors.tuna.tsinghua.edu.cn/ubuntu/focalmainrestricteduniversemultiversedebhttps://mirrors.tuna.tsinghua.e......
  • 在Linux中,如何卸载软件?
    在Linux中卸载软件,方法取决于你所使用的包管理系统。以下是针对几种主要包管理系统的卸载指南:1.基于APT的系统(如Debian、Ubuntu及其衍生版)打开终端:首先,打开一个终端窗口。卸载软件:使用以下命令卸载软件包,同时清除配置文件:sudoapt-getautoremove--purge软件包名如果......
  • Linux下ffmpeg库的编译链接
    /usr/bin/ld:/usr/local/ffmpeg/lib/libavformat.a(aviobuf.o):infunction`ff_crc04C11DB7_update':/home/ann/FFmpeg/ffmpeg/libavformat/aviobuf.c:568:undefinedreferenceto`av_crc_get_table'/usr/bin/ld:/home/ann/FFmpeg/ffmpeg/libavformat/aviobuf......
  • linux18-软件安装
    linux18-软件安装yum需要root权限和网络连接RPM包软件管理器,用于自动化安装配置Linux软件,并可以自动解决依赖问题安装包为.rpm文件(适用于centOS,对应ubuntu中的apt)yum[-y][install|remove|search]软件名称选项:-y自动确认,无需手动确认安装和卸载的......