内核同步

内核同步解决并发带来的问题,多个线程对同一数据进行修改,数据会出现不一致的情况,同步用于保护共享数据等资源。

有两种形式的并发:

  1. 同时进行式并发,在不同cpu上执行的进程同时访问共享数据
  2. 二次进入式并发,某进程读写一段数据时,中断触发,在中断处理函数中再次修改之前进程读写的内容

访问共享数据的那部分代码被称为临界区。

原子操作

不可打断的操作为原子操作,一条汇编指令不可被中断,其为原子操作。在内核代码中,我们可以看到类似atomic64_add这样的函数,使用它们完成加减运算,而不是简单地使用”+”、”-“运算符。

x86_64架构下,访问一个对齐的long型是原子操作:

volatile unsigned long value_;
value_=;

以上赋值语句是原子的,即使多线程同时访问以上value_亦不需要加锁,所有线程要么看到旧值,要么看到新值。

但value_++;这条自增语句不是原子的,它需要读内存、改值、写内存三条指令:

4004ca:    8b  f8    mov -0x8(%rbp),%rax
4004ce: c0 add $0x1,%rax
4004d2: f8 mov %rax,-0x8(%rbp)

gcc等编译器,会针对这种操作,提供内建的原子方法,如上面的value_++可以修改为:

__sync_fetch_and_add(&value_, );

对应__sync_fetch_and_add的汇编如下:

4004bc:     8d  f8        lea -0x8(%rbp),%rax
4004c0: f0 lock addq $0x1,(%rax)

以上lock前缀用于锁定总线,保证后面一条指令对内存的独占访问。gcc提供了一组原子方法,更多可以参看gcc手册。

根据需要保护的数据的粒度、等待锁时进程是否可休眠等不同应用场景,锁有很多种类,下面我们来看内核代码中几种常用的锁。

原子锁

像以上介绍的atomic64_add就是一个原子锁,其用于保护一个整型值,在内核代码中由一条汇编语句实现:

static __inline__ void atomic64_add(long i, atomic64_t *v)
{
__asm__ __volatile__(
LOCK "addq %1,%0"
:"=m" (v->counter)
:"ir" (i), "m" (v->counter));
}

以上代码中,同样用到lock进行内存保护。

自旋锁

在我们编写应用程序的时候,常使用c库中的pthread_mutex_lock对临界区进行加锁,pthread_mutex_lock底层使用futex系统调用实现。若锁变量mutex已被其他线程占用,则后续申请锁的进程将进入休眠,当mutex被释放时,后续的进程被唤醒。

不同于pthread_mutex_lock获取不到锁的进程将进入休眠,使用自旋锁(spin lock)的进程,若锁已被其他进程占用,则一直占用cpu,重复检查锁的状态,直到该锁可用为止。

自旋锁是为多处理器的使用而设计的,对于运行可抢占内核的单处理器,其行为类似于多处理器。因而,自旋锁对多处理器和使用可抢占内核的单处理器都适用,均可用于临界区保护。

但自旋锁对使用不可抢占内核的单处理器没有意义,因为当cpu处于自旋状态时,它做不了任何有用的工作,非抢占式单处理器系统上通过禁止中断实现临界区的保护,自旋锁被实现为空操作。

在同一个cpu上,自旋锁不可递归获取。

下面是自旋锁的一个具体使用例子:

SYSCALL_DEFINE1(close, unsigned int, fd)
{
struct file * filp;
struct files_struct *files = current->files;
struct fdtable *fdt;
int retval;
spin_lock(&files->file_lock);
fdt = files_fdtable(files);
filp = fdt->fd[fd];
rcu_assign_pointer(fdt->fd[fd], NULL);
FD_CLR(fd, fdt->close_on_exec);
__put_unused_fd(files, fd);
spin_unlock(&files->file_lock);
retval = filp_close(filp, files);
return retval;
}

以上是close系统调用的实现代码(截取了自旋锁相关的部分)。可以看到操作文件结构、文件描述符前,先调用spin_lock获取当前文件对应的files_struct结构中的file_lock,之后修改临界区,完成清除标志位、把文件描述符fd放入未使用列表等工作,最后调用spin_unlock释放file_lock自旋锁。

读写自旋锁

对于读操作而言,其实并不需要加锁,因而我们可以对读和写区别对待:

  • 没有写操作时,可以进行多个读操作
  • 多个读操作进行时,写操作需要等待

使用读写自旋锁,在读得多,写得少的场景下,有很大的效率提升。

内核中读写自旋锁的类型为rwlock_t,相关的操作函数有read_lock、write_lock等。

信号量

信号量(semaphore),类似于c库中的pthread_mutex_lock。进程1申请的信号量若被进程2占用,则进程1进入休眠状态,这时允许进程调度,进程1被切换后,cpu可以进行其他工作。

内核中信号量用semaphore结构表示,获取信号量的函数为down(),释放信号量的函数为up()。

使用自旋锁时进程一直占用cpu,而使用信号量时进程可休眠,但进程休眠时发生切换将带来一定cpu开销。根据以上两种锁的特点,自旋锁与信号量适用于不同场景:

Requirement                               Recommended Lock
Low overhead locking Spin lock is preferred
Short lock hold time Spin lock is preferred
Long lock hold time Semaphore is preferred
Need to lock from interrupt contex Spin lock is required
Need to sleep while holding lock Semaphore is required

读写信号量

与自旋锁分读写自旋锁类似,信号量也分读写信号量。读写信号量由rw_semaphore表示,相关的操作函数有down_read/up_read、down_write/up_write。

下面来看进程获取信号量,进入休眠,唤醒并获取信号量的具体实现过程:

  • 进程调用down_read获取一个读信号量

down_read调用__down_read,在__down_read函数中,调用set_task_state设置进程状态,将获取读信号量的请求加入请求队列中,在获取不到锁的情况下,调用schedule进行进程切换

  • 进程调用up_read释放一个读信号量

up_read调用__up_read,__up_read函数调用rwsem_wake,该函数调用__rwsem_do_wake,__rwsem_do_wake函数中,获取请求队列中的下一个请求,调用wake_up_process函数唤醒发起下一个请求的进程,wake_up_process调用try_to_wake_up,try_to_wake_up调用activate_task,activate_task调用enqueue_task,将进程加入可运行队列

BKL

大内核锁(Big kernel lock, BKL),是一个全局可见的锁,它的出现是为了解决SMP出现后的并发问题。

获取BKL之后,内核态被上锁,同一时刻只能有一个cpu能运行内核代码,无法发挥多处理器的威力,BKL正逐渐地被其他更细粒度的锁替代。

Reference: Chapter 9 and chapter 10, Linux kernel development.3rd.Edition

kernel笔记——内核同步与锁的更多相关文章

  1. [内核同步]自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析

    转自:http://blog.csdn.net/wh_19910525/article/details/11536279 自旋锁的初衷:在短期间内进行轻量级的锁定.一个被争用的自旋锁使得请求它的线程在 ...

  2. linux内核笔记-内核同步

    linux内核就相当于不断对请求进行响应的服务器,这些请求可能来自CPU,可能来自发出中断的外部设备.我们将内核看作两种请求的侍者. (1)老板提出请求,侍者如果空闲,为老板服务.(系统调用或异常) ...

  3. [内核同步]自旋锁spin_lock、spin_lock_irq 和 spin_lock_irqsave 分析【转】

    转自:https://www.cnblogs.com/x_wukong/p/8573602.html 转自;https://www.cnblogs.com/aaronLinux/p/5890924.h ...

  4. kernel笔记——内核编译与进程管理

    内核与操作系统 由于一些商业操作系统设计上的缺陷以及日益庞杂,“操作系统”的概念对很多人而言变得含糊不清.在进一步讨论Linux内核的话题前,我们先区分“内核”与“操作系统”这两个概念. 操作系统:指 ...

  5. Linux内核同步:自旋锁

    linux内核--自旋锁的理解 自旋锁:如果内核配置为SMP系统,自旋锁就按SMP系统上的要求来实现真正的自旋等待,但是对于UP系统,自旋锁仅做抢占和中断操作,没有实现真正的“自旋”.如果配置了CON ...

  6. Linux内核设计笔记10——内核同步

    Linux内核同步笔记 几个基本概念 - 临界区(critical region):访问和操作共享数据的代码段: - 原子操作:操作在执行中不被打断,要么不执行,要么执行完: - 竞争条件: 两个线程 ...

  7. Linux 内核同步之自旋锁与信号量的异同【转】

    转自:http://blog.csdn.net/liuxd3000/article/details/8567070 Linux 设备驱动中必须解决的一个问题是多个进程对共享资源的并发访问,并发访问会导 ...

  8. python笔记10-多线程之线程同步(锁lock)

    前言 关于吃火锅的场景,小伙伴并不陌生,吃火锅的时候a同学往锅里下鱼丸,b同学同时去吃掉鱼丸,有可能会导致吃到生的鱼丸. 为了避免这种情况,在下鱼丸的过程中,先锁定操作,让吃火锅的小伙伴停一会,等鱼丸 ...

  9. 锁相关知识 & mutex怎么实现的 & spinlock怎么用的 & 怎样避免死锁 & 内核同步机制 & 读写锁

    spinlock在上一篇文章有提到:http://www.cnblogs.com/charlesblc/p/6254437.html  通过锁数据总线来实现. 而看了这篇文章说明:mutex内部也用到 ...

随机推荐

  1. dotnet core高吞吐Http api服务组件FastHttpApi

    简介 是dotNet core下基于Beetlex实现的一个高度精简化和高吞吐的HTTP API服务开源组件,它并没有完全实现HTTP SERVER的所有功能,而是只实现了在APP和WEB中提供数据服 ...

  2. 按行切割大文件(linux split 命令简版)

    按行切割大文件(linux split 命令简版) #-*- coding:utf-8 -*- __author__ = 'KnowLifeDeath' ''' Linux上Split命令可以方便对大 ...

  3. 【Java基础】【15Colletion集合】

    15.01_集合框架(对象数组的概述和使用) A:案例演示 需求:我有5个学生,请把这个5个学生的信息存储到数组中,并遍历数组,获取得到每一个学生信息. Student[] arr = new Stu ...

  4. Once More

    Topic Link http://ctf5.shiyanbar.com/web/more.php 1)源代码分析 发现 ereg()函数使得password必须是数字或字母同时长度必须是小于8val ...

  5. WebGL 纹理颜色原理

    本文由云+社区发表 作者:ivweb qcyhust 导语 WebGL绘制图像时,往着色器中传入颜色信息就可以给图形绘制出相应的颜色,现在已经知道顶点着色器和片段着色器一起决定着向颜色缓冲区写入颜色信 ...

  6. Spring拓展接口之FactoryBean,我们来看看其源码实现

    前言 开心一刻 那年去相亲,地点在饭店里,威特先上了两杯水,男方绅士的喝了一口,咧嘴咋舌轻放桌面,手抚额头闭眼一脸陶醉,白水硬是喝出了82年拉菲的感觉.如此有生活情调的幽默男人,果断拿下,相处后却发现 ...

  7. 痞子衡嵌入式:飞思卡尔i.MX RT系列MCU启动那些事(5)- 再聊eFUSE及其烧写方法

    大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RT系列MCU的eFUSE. 在i.MXRT启动系列第二篇文章 Boot配置(BOOT Pin, eFUSE) 里痞子 ...

  8. springboot(五)过滤器和拦截器

    前言 过滤器和拦截器二者都是AOP编程思想的提现,都能实现诸如权限检查.日志记录等.二者有一定的相似之处,不同的地方在于: Filter是servlet规范,只能用在Web程序中,而拦截器是Sprin ...

  9. __tostring()和__invoke()的用法

    PHP有很多内置的魔术方法,这里我们聊哈tostring和involk吧. __tostring()魔术方法 将一个对象当做一个字符串来使用时,会自动调用该方法,并且在该方法中,可以返回一定的字符串, ...

  10. PHP 中的Trait

    概述 在PHP中有一种代码复用的技术, 因为单继承的问题, 有些公共方法无法在父类中写出, 而 Trait可以应对这种情况, 它可以定义一些复用的方法, 然后在你需要使用的类中将其引入即可. 刚开始的 ...