精华内容
下载资源
问答
  • 2016-09-02 21:49:20

    有关linux内核同步的参考:

    memory barrier: http://www.wowotech.net/kernel_synchronization/memory-barrier.html


    阅读《深入理解linux内核》笔记

    内核抢占:如果进程正在执行内核函数时(即它在内核态运行时),允许发生内核切换(被替换的进程是正在执行内核函数的进程),这个内核就是抢占的。

    运行在内核态的进程可以自动放弃cpu,称为计划性进程切换,抢占式内核中,进程被迫放弃CPU,称为强制性进程切换。抢占内核的主要特点是:一个内核态运行的进程,可能在执行内核函数期间被另一个进程取代。

    内核抢占就会发生一种情况:两个或两个以上的交叉内核路径嵌套时,就可能出现竞争条件。还有一种情况,多核环境下,多个core上的进程同时进入内核,就会出现访问的竞争。因此内核需要同步。内核同步技术主要有以下方式:


    1.per-cpu 变量:将内核变量声明为per-cpu变量,它的数据结构是数组,系统的每个CPU对应数组中的一个元素,一个CPU只能修改自己对应的元素,不用担心竞争条件。per-cpu只能对来自不同CPU的并发访问提供保护,但对自身的异步函数(中断处理函数和可延迟函数)的访问不提供保护,需要同步原语

    2.原子操作(linux中有一个专门的atomic_t类型):操作在芯片级是原子的,原子操作必须以单个指令执行,中间不能中断,且避免其他的CPU访问同一存储器单元。

    3.优化屏障(optimization barrier)和内存屏障(memory barrier):现代编译器可能重新安排汇编语言指令的吮吸,CPU通常并行的执行多条指令,且可能重新安排内存访问,但是,当处理同步时,必须避免指令重新排序,如果放在同步原语之后的一条指令在同步原语之前被执行,就很糟糕!内存屏障:确保在原语之后的操作开始执行之前,原语之前的操作已经完成。mb(), smp_mb();

    简单的说,由于内存乱序以允许更好的性能,在某些情况下,需要内存屏障以强制保证内存顺序,否则将出现很糟糕的问题。

    程序在运行时内存实际的访问顺序和程序代码编写的访问顺序不一定一致,这就是内存乱序访问。内存乱序访问行为出现的理由是为了提升程序运行时的性能。内存乱序访问主要发生在两个阶段:

    1. 编译时,编译器优化导致内存乱序访问(指令重排)
    2. 运行时,多 CPU 间交互引起内存乱序访问

    Memory barrier 能够让 CPU 或编译器在内存访问上有序。一个 Memory barrier 之前的内存访问操作必定先于其之后的完成。Memory barrier 包括两类:

    1. 编译器 barrier
    2. CPU Memory barrier

    4.spin lock:当内核控制路径必须访问共享数据结构或进入临界区时,需要锁,spin lock的循环指令表示“忙等”,即使等待的内核控制路径无事可做,它也在CPU上保持运行

    5.读写spin lock:它的引入主要是为了增加内核的并发能力,只要没有内核路径对数据结构进行修改,读写spin lock允许多个内核控制路径同时读同一个数据结构。但是写者需要独占。

    6.顺序锁:读写spin lock中读者和写着有相同的优先级,当读者多的时候,可能出现一种情况,写着一直得不到服务,可能饥饿。引入顺序锁(seqlock),它与读写spin lock非常相似,但是它赋予写着较高的优先级。

    7.RCU(read-copy update):是为了保护在多数情况下被多个CPU读的数据结构而设计的另一种同步方式。RCU允许多个读者和写者并发执行,而且RCU不使用锁。但RCU的使用有限制:

    • RCU只能保护动态分配并通过指针引用的数据结构
    • 在被RCU保护的临界区中,任何内核控制路径都不能睡眠
    内核控制路径要读取被RCU保护的数据结构时,执行宏rcu_read_lock(),但是读者在完成对数据结构的读操作之前,是不能睡眠的,rcu读结束之后,调用宏rcu_read_unlock()标记临界区的结束。
    由于读者几乎不做任何事情来防止竞争条件的出现,所以写着必须多做一些。

    8.信号量

    更多相关内容
  • Linux内核同步机制

    2021-01-20 14:49:52
    Linux内核同步机制,挺复杂的一个东西,常用的有自旋锁,信号量,互斥体,原子操作,顺序锁,RCU,内存屏障等。这里说说它们的特点和基本用法。  自旋锁 :通用的 和读写的  特点:  1. 处理的时间很短。  2...
  • Linux系统进程通信中信号概念及信号处理,进程间的管道通信编程,进程间的内存共享编程
  • Linux内核同步机制

    2020-11-10 07:29:16
     在主流的Linux内核中包含了几乎所有现代的操作系统具有的同步机制,这些同步机制包括:原子操作、信号量(semaphore)、读写信号量(rw_semaphore)、spinlock、BKL(Big Kernel Lock)、rwlock、brlock(只包含在...
  • Linux内核同步编程技术.doc
  • 自旋就是自己连续的循环等待。如果你有抱着你的爱人旋转的经历,那么你应该知道一件事情,为了安全,你不能旋转太久,你的爱人如果头昏,也想你早日释放。是的,自旋的缺点,就是它频繁的循环直到等待锁的释放,将它...
  • linux内核同步机制.pdf

    2020-07-24 17:05:33
    并发(Concurrency)是Linux内核中经常会发生的事情. 并发会引起竞态(Race)和阻塞(Block)的问题. 首先, 我们看一个并发引起竞态示例, 对并发有个大致印象, 看看竞态会引起那些问题. 然后, 我们会详解介绍并发, 竞态, ...
  • 操作系统同步研究——Linux内核同步机制.pdf
  • 自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要自旋锁)。
  • 本文主要介绍了linux内核自旋锁是如何工作的。
  • Linux内核同步操作详解,英文版,开发驱动必备。
  • 浅析Linux内核同步机制(转)

    千次阅读 2018-07-30 23:50:19
     很早之前就接触过同步这个概念了,但是一直都很模糊,没有深入地学习了解过,近期有时间了,就花时间研习了一下《linux内核标准教程》和《深入linux设备驱动程序内核机制》这两本书的相关章节。...

    原文地址:https://blog.csdn.net/fzubbsc/article/details/37736683?utm_source=tuicool&utm_medium=referral

     很早之前就接触过同步这个概念了,但是一直都很模糊,没有深入地学习了解过,近期有时间了,就花时间研习了一下《linux内核标准教程》和《深入linux设备驱动程序内核机制》这两本书的相关章节。趁刚看完,就把相关的内容总结一下。为了弄清楚什么事同步机制,必须要弄明白以下三个问题:

     

    • 什么是互斥与同步?
    • 为什么需要同步机制?
    •  Linux内核提供哪些方法用于实现互斥与同步的机制?

     

    1、什么是互斥与同步?(通俗理解)

     

    • 互斥与同步机制是计算机系统中,用于控制进程对某些特定资源的访问的机制。
    • 同步是指用于实现控制多个进程按照一定的规则或顺序访问某些系统资源的机制。
    • 互斥是指用于实现控制某些系统资源在任意时刻只能允许一个进程访问的机制。互斥是同步机制中的一种特殊情况。
    • 同步机制是linux操作系统可以高效稳定运行的重要机制。

     

    2、Linux为什么需要同步机制?

            在操作系统引入了进程概念,进程成为调度实体后,系统就具备了并发执行多个进程的能力,但也导致了系统中各个进程之间的资源竞争和共享。另外,由于中断、异常机制的引入,以及内核态抢占都导致了这些内核执行路径(进程)以交错的方式运行。对于这些交错路径执行的内核路径,如不采取必要的同步措施,将会对一些关键数据结构进行交错访问和修改,从而导致这些数据结构状态的不一致,进而导致系统崩溃。因此,为了确保系统高效稳定有序地运行,linux必须要采用同步机制。

    3、Linux内核提供了哪些同步机制?

            在学习linux内核同步机制之前,先要了解以下预备知识:(临界资源与并发源)
            在linux系统中,我们把对共享的资源进行访问的代码片段称为临界区。把导致出现多个进程对同一共享资源进行访问的原因称为并发源。

            Linux系统下并发的主要来源有:

     

    • 中断处理:例如,当进程在访问某个临界资源的时候发生了中断,随后进入中断处理程序,如果在中断处理程序中,也访问了该临界资源。虽然不是严格意义上的并发,但是也会造成了对该资源的竞态。
    • 内核态抢占:例如,当进程在访问某个临界资源的时候发生内核态抢占,随后进入了高优先级的进程,如果该进程也访问了同一临界资源,那么就会造成进程与进程之间的并发。
    • 多处理器的并发:多处理器系统上的进程与进程之间是严格意义上的并发,每个处理器都可以独自调度运行一个进程,在同一时刻有多个进程在同时运行 。

     

    如前所述可知:采用同步机制的目的就是避免多个进程并发并发访问同一临界资源。 

    Linux内核同步机制:

    (1)禁用中断 (单处理器不可抢占系统)

            由前面可以知道,对于单处理器不可抢占系统来说,系统并发源主要是中断处理。因此在进行临界资源访问时,进行禁用/使能中断即可以达到消除异步并发源的目的。Linux系统中提供了两个宏local_irq_enable与 local_irq_disable来使能和禁用中断。在linux系统中,使用这两个宏来开关中断的方式进行保护时,要确保处于两者之间的代码执行时间不能太长,否则将影响到系统的性能。(不能及时响应外部中断)

    (2)自旋锁

    应用背景:自旋锁的最初设计目的是在多处理器系统中提供对共享数据的保护。

    自旋锁的设计思想:在多处理器之间设置一个全局变量V,表示锁。并定义当V=1时为锁定状态,V=0时为解锁状态。自旋锁同步机制是针对多处理器设计的,属于忙等机制。自旋锁机制只允许唯一的一个执行路径持有自旋锁。如果处理器A上的代码要进入临界区,就先读取V的值。如果V!=0说明是锁定状态,表明有其他处理器的代码正在对共享数据进行访问,那么此时处理器A进入忙等状态(自旋);如果V=0,表明当前没有其他处理器上的代码进入临界区,此时处理器A可以访问该临界资源。然后把V设置为1,再进入临界区,访问完毕后离开临界区时将V设置为0。

    注意:必须要确保处理器A“读取V,半段V的值与更新V”这一操作是一个原子操作。所谓的原子操作是指,一旦开始执行,就不可中断直至执行结束。

    自旋锁的分类:

    2.1、普通自旋锁

    普通自旋锁由数据结构spinlock_t来表示,该数据结构在文件src/include/linux/spinlock_types.h中定义。定义如下:

    typedef struct { raw_spinklock_t   raw_lock;
    
           #ifdefined(CONFIG_PREEMPT)  &&  defined(CONFIG_SMP)
    
                   unsigned int break_lock;
    
           #endif
    
    } spinlock_t;

    成员raw_lock:该成员变量是自旋锁数据类型的核心,它展开后实质上是一个Volatileunsigned类型的变量。具体的锁定过程与它密切相关,该变量依赖于内核选项CONFIG_SMP。(是否支持多对称处理器)

    成员break_lock:同时依赖于内核选项CONFIG_SMP和CONFIG_PREEMPT(是否支持内核态抢占),该成员变量用于指示当前自旋锁是否被多个内核执行路径同时竞争、访问。

    在单处理器系统下:CONFIG_SMP没有选中时,变量类型raw_spinlock_t退化为一个空结构体。相应的接口函数也发生了退化。相应的加锁函数spin_lock()和解锁函数spin_unlock()退化为只完成禁止内核态抢占、使能内核态抢占。

    在多处理器系统下:选中CONFIG_SMP时,核心变量raw_lock的数据类型raw_lock_t在文件中src/include/asm-i386/spinlock_types.h中定义如下:

    typedef struct {  volatileunsigned int slock;} raw_spinklock_t;

           从定义中可以看出该数据结构定义了一个内核变量,用于计数工作。当结构中成员变量slock的数值为1时,表示自旋锁处于非锁定状态,可以使用。否则,表示处于锁定状态,不可以使用。

    普通自旋锁的接口函数:

    spin_lock_init(lock)        //声明自旋锁是,初始化为锁定状态
    
    spin_lock(lock)             //锁定自旋锁,成功则返回,否则循环等待自旋锁变为空闲
    
    spin_unlock(lock)           //释放自旋锁,重新设置为未锁定状态
    
    spin_is_locked(lock)        //判断当前锁是否处于锁定状态。若是,返回1.
    
    spin_trylock(lock)          //尝试锁定自旋锁lock,不成功则返回0,否则返回1
    
    spin_unlock_wait(lock)      //循环等待,直到自旋锁lock变为可用状态。
    
    spin_can_lock(lock)         //判断该自旋锁是否处于空闲状态。 

    普通自旋锁总结:自旋锁设计用于多处理器系统。当系统是单处理器系统时,自旋锁的加锁、解锁过程分为别退化为禁止内核态抢占、使能内核态抢占。在多处理器系统中,当锁定一个自旋锁时,需要首先禁止内核态抢占,然后尝试锁定自旋锁,在锁定失败时执行一个死循环等待自旋锁被释放;当解锁一个自旋锁时,首先释放当前自旋锁,然后使能内核态抢占。

     

    2.2、自旋锁的变种

            在前面讨论spin_lock很好的解决了多处理器之间的并发问题。但是如果考虑如下一个应用场景:处理器上的当前进程A要对某一全局性链表g_list进行操作,所以在操作前调用了spin_lock获取锁,然后再进入临界区。如果在临界区代码当中,进程A所在的处理器上发生了一个外部硬件中断,那么这个时候系统必须暂停当前进程A的执行转入到中断处理程序当中。假如中断处理程序当中也要操作g_list,由于它是共享资源,在操作前必须要获取到锁才能进行访问。因此当中断处理程序试图调用spin_lock获取锁时,由于该锁已经被进程A持有,中断处理程序将会进入忙等状态(自旋)。从而就会出现大问题了:中断程序由于无法获得锁,处于忙等(自旋)状态无法返回;由于中断处理程序无法返回,进程A也处于没有执行完的状态,不会释放锁。因此这样导致了系统的死锁。即spin_lock对存在中断源的情况是存在缺陷的,因此引入了它的变种。

    spin_lock_irq(lock) 
    
    spin_unlock_irq(lock)

    相比于前面的普通自旋锁,它在上锁前增加了禁用中断的功能,在解锁后,使能了中断。

    2.3、读写自旋锁rwlock

    应用背景:前面说的普通自旋锁spin_lock类的函数在进入临界区时,对临界区中的操作行为不细分。只要是访问共享资源,就执行加锁操作。但是有时候,比如某些临界区的代码只是去读这些共享的数据,并不会改写,如果采用spin_lock()函数,就意味着,任意时刻只能有一个进程可以读取这些共享数据。如果系统中有大量对这些共享资源的读操作,很明显spin_lock将会降低系统的性能。因此提出了读写自旋锁rwlock的概念。对照普通自旋锁,读写自旋锁允许多个读者进程同时进入临界区,交错访问同一个临界资源,提高了系统的并发能力,提升了系统的吞吐量。

    读写自旋锁有数据结构rwlock_t来表示。定义在…/spinlock_types.h中

    读写自旋锁的接口函数:

    DEFINE_RWLOCK(lock)     //声明读写自旋锁lock,并初始化为未锁定状态
    
    write_lock(lock)        //以写方式锁定,若成功则返回,否则循环等待
    
    write_unlock(lock)      //解除写方式的锁定,重设为未锁定状态
    
    read_lock(lock)         //以读方式锁定,若成功则返回,否则循环等待
    
    read_unlock(lock)       //解除读方式的锁定,重设为未锁定状态

    读写自旋锁的工作原理:

             对于读写自旋锁rwlock,它允许任意数量的读取者同时进入临界区,但写入者必须进行互斥访问。一个进程要进行读,必须要先检查是否有进程正在写入,如果有,则自旋(忙等),否则获得锁。一个进程要进程写,必须要先检查是否有进程正在读取或者写入,如果有,则自旋(忙等)否则获得锁。即读写自旋锁的应用规则如下:

    (1)如果当前有进程正在写,那么其他进程就不能读也不能写。

    (2)如果当前有进程正在读,那么其他程序可以读,但是不能写。

    2.4、顺序自旋锁seqlock

    应用背景:顺序自旋锁主要用于解决自旋锁同步机制中,在拥有大量读者进程时,写进程由于长时间无法持有锁而被饿死的情况,其主要思想是:为写进程提高更高的优先级,在写锁定请求出现时,立即满足写锁定的请求,无论此时是否有读进程正在访问临界资源。但是新的写锁定请求不会,也不能抢占已有写进程的写锁定。

    顺序锁的设计思想:对某一共享数据读取时不加锁,写的时候加锁。为了保证读取的过程中不会因为写入者的出现导致该共享数据的更新,需要在读取者和写入者之间引入一个整形变量,称为顺序值sequence。读取者在开始读取前读取该sequence,在读取后再重新读取该值,如果与之前读取到的值不一致,则说明本次读取操作过程中发生了数据更新,读取操作无效。因此要求写入者在开始写入的时候更新。

    顺序自旋锁由数据结构seqlock_t表示,定义在src/include/linux/seqlcok.h

    顺序自旋锁访问接口函数:

    seqlock_init(seqlock)               //初始化为未锁定状态
    
    read_seqbgin()、read_seqretry()     //保证数据的一致性
    
    write_seqlock(lock)                 //尝试以写锁定方式锁定顺序锁
    
    write_sequnlock(lock)               //解除对顺序锁的写方式锁定,重设为未锁定状态。

    顺序自旋锁的工作原理:写进程不会被读进程阻塞,也就是,写进程对被顺序自旋锁保护的临界资源进行访问时,立即锁定并完成更新工作,而不必等待读进程完成读访问。但是写进程与写进程之间仍是互斥的,如果有写进程在进行写操作,其他写进程必须循环等待,直到前一个写进程释放了自旋锁。顺序自旋锁要求被保护的共享资源不包含有指针,因为写进程可能使得指针失效,如果读进程正要访问该指针,将会出错。同时,如果读者在读操作期间,写进程已经发生了写操作,那么读者必须重新读取数据,以便确保得到的数据是完整的。

     

    (3)信号量机制(semaphore)

    应用背景:前面介绍的自旋锁同步机制是一种“忙等”机制,在临界资源被锁定的时间很短的情况下很有效。但是在临界资源被持有时间很长或者不确定的情况下,忙等机制则会浪费很多宝贵的处理器时间。针对这种情况,linux内核中提供了信号量机制,此类型的同步机制在进程无法获取到临界资源的情况下,立即释放处理器的使用权,并睡眠在所访问的临界资源上对应的等待队列上;在临界资源被释放时,再唤醒阻塞在该临界资源上的进程。另外,信号量机制不会禁用内核态抢占,所以持有信号量的进程一样可以被抢占,这意味着信号量机制不会给系统的响应能力,实时能力带来负面的影响。

    信号量设计思想:除了初始化之外,信号量只能通过两个原子操作P()和V()访问,也称为down()和up()。down()原子操作通过对信号量的计数器减1,来请求获得一个信号量。如果操作后结果是0或者大于0,获得信号量锁,任务就可以进入临界区。如果操作后结果是负数,任务会放入等待队列,处理器执行其他任务;对临界资源访问完毕后,可以调用原子操作up()来释放信号量,该操作会增加信号量的计数器。如果该信号量上的等待队列不为空,则唤醒阻塞在该信号量上的进程。

    信号量的分类:

    3.1、普通信号量

    普通信号量由数据结构struct semaphore来表示,定义在src/inlcude/ asm-i386/semaphore.h中.

    信号量(semaphore)定义如下:

    <include/linux/semaphore.h>
    
    struct semaphore{
    
           spinlock_t       lock; //自旋锁,用于实现对count的原子操作
    
           unsigned int    count; //表示通过该信号量允许进入临界区的执行路径的个数
    
           struct list_head      wait_list; //用于管理睡眠在该信号量上的进程
    
    };

    普通信号量的接口函数:

    sema_init(sem,val)      //初始化信号量计数器的值为val
    
    int_MUTEX(sem)          //初始化信号量为一个互斥信号量
    
    down(sem)               //锁定信号量,若不成功,则睡眠在等待队列上
    
    up(sem)                 //释放信号量,并唤醒等待队列上的进程

    DOWN操作:linux内核中,对信号量的DOWN操作有如下几种:

    void down(struct semaphore *sem); //不可中断
    
    int down_interruptible(struct semaphore *sem);//可中断
    
    int down_killable(struct semaphore *sem);//睡眠的进程可以因为受到致命信号而被唤醒,中断获取信号量的操作。
    
    int down_trylock(struct semaphore *sem);//试图获取信号量,若无法获得则直接返回1而不睡眠。返回0则 表示获取到了信号量
    
    int down_timeout(struct semaphore *sem,long jiffies);//表示睡眠时间是有限制的,如果在jiffies指明的时间到期时仍然无法获得信号量,则将返回错误码。

    在以上四种函数中,驱动程序使用的最频繁的就是down_interruptible函数

    UP操作:LINUX内核只提供了一个up函数

    void up(struct semaphore *sem)

    加锁处理过程:加锁过程由函数down()完成,该函数负责测试信号量的状态,在信号量可用的情况下,获取该信号量的使用权,否则将当前进程插入到当前信号量对应的等待队列中。函数调用关系如下:down()->__down_failed()->__down.函数说明如下:

    down()功能介绍:该函数用于对信号量sem进行加锁,在加锁成功即获得信号的使用权是,直接退出,否则,调用函数__down_failed()睡眠到信号量sem的等待队列上。__down()功能介绍:该函数在加锁失败时被调用,负责将进程插入到信号量 sem的等待队列中,然后调用调度器,释放处理器的使用权。

    解锁处理过程:普通信号量的解锁过程由函数up()完成,该函数负责将信号计数器count的值增加1,表示信号量被释放,在有进程阻塞在该信号量的情况下,唤醒等待队列中的睡眠进程。 

    3.2读写信号量(rwsem)

    应用背景:为了提高内核并发执行能力,内核提供了读入者信号量和写入者信号量。它们的概念和实现机制类似于读写自旋锁。

    工作原理:该信号量机制使得所有的读进程可以同时访问信号量保护的临界资源。当进程尝试锁定读写信号量不成功时,则这些进程被插入到一个先进先出的队列中;当一个进程访问完临界资源,释放对应的读写信号量是,该进程负责将该队列中的进程按一定的规则唤醒。

    唤醒规则:唤醒排在该先进先出队列中队首的进程,在被唤醒进程为写进程的情况下,不再唤醒其他进程;在唤醒进程为读进程的情况下,唤醒其他的读进程,直到遇到一个写进程(该写进程不被唤醒)

    读写信号量的定义如下:

    <include/linux/rwsem-spinlock.h>
    
    sturct rw_semaphore{
    
           __s32      activity; //用于表示读者或写者的数量
    
           spinlock_t      wait_lock;
    
           struct list_head      wait_list;
    
    };

    读写信号量相应的接口函数

    读者up、down操作函数:

    void up_read(Sturct rw_semaphore *sem);
    
    void __sched down_read(Sturct rw_semaphore *sem);
    
    Int down_read_trylock(Sturct rw_semaphore *sem);

    写入者up、down操作函数:

    void up_write(Sturct rw_semaphore *sem);
    
    void __sched down_write(Sturct rw_semaphore *sem);
    
    int down_write_trylock(Sturct rw_semaphore *sem);

    3.3、互斥信号量

    在linux系统中,信号量的一个常见的用途是实现互斥机制,这种情况下,信号量的count值为1,也就是任意时刻只允许一个进程进入临界区。为此,linux内核源码提供了一个宏DECLARE_MUTEX,专门用于这种用途的信号量定义和初始化

    <include/linux/semaphore.h>
    
    #define DECLARE_MUTEX(name)  \
    
                  structsemaphore name=__SEMAPHORE_INITIALIZER(name,1)

    (4)互斥锁mutex

    Linux内核针对count=1的信号量重新定义了一个新的数据结构struct mutex,一般都称为互斥锁。内核根据使用场景的不同,把用于信号量的down和up操作在struct mutex上做了优化与扩展,专门用于这种新的数据类型。

    (5)RCU

    RCU概念:RCU全称是Read-Copy-Update(读/写-复制-更新),是linux内核中提供的一种免锁的同步机制。RCU与前面讨论过的读写自旋锁rwlock,读写信号量rwsem,顺序锁一样,它也适用于读取者、写入者共存的系统。但是不同的是,RCU中的读取和写入操作无须考虑两者之间的互斥问题。但是写入者之间的互斥还是要考虑的。

    RCU原理:简单地说,是将读取者和写入者要访问的共享数据放在一个指针p中,读取者通过p来访问其中的数据,而读取者则通过修改p来更新数据。要实现免锁,读写双方必须要遵守一定的规则。

    读取者的操作(RCU临界区)

    对于读取者来说,如果要访问共享数据。首先要调用rcu_read_lock和rcu_read_unlock函数构建读者侧的临界区(read-side critical section),然后再临界区中获得指向共享数据区的指针,实际的读取操作就是对该指针的引用。

    读取者要遵守的规则是:(1)对指针的引用必须要在临界区中完成,离开临界区之后不应该出现任何形式的对该指针的引用。(2)在临界区内的代码不应该导致任何形式的进程切换(一般要关掉内核抢占,中断可以不关)。

    写入者的操作

    对于写入者来说,要写入数据,首先要重新分配一个新的内存空间做作为共享数据区。然后将老数据区内的数据复制到新数据区,并根据需要修改新数据区,最后用新数据区指针替换掉老数据区的指针。写入者在替换掉共享区的指针后,老指针指向的共享数据区所在的空间还不能马上释放(原因后面再说明)。写入者需要和内核共同协作,在确定所有对老指针的引用都结束后才可以释放老指针指向的内存空间。为此,写入者要做的操作是调用call_rcu函数向内核注册一个回调函数,内核在确定所有对老指针的引用都结束时会调用该回调函数,回调函数的功能主要是释放老指针指向的内存空间。Call_rcu函数的原型如下:

    Void call_rcu(struct rcu_head *head,void (*func)(struct rcu_head *rcu));

    内核确定没有读取者对老指针的引用是基于以下条件的:系统中所有处理器上都至少发生了一次进程切换。因为所有可能对共享数据区指针的不一致引用一定是发生在读取者的RCU临界区,而且临界区一定不能发生进程切换。所以如果在CPU上发生了一次进程切换切换,那么所有对老指针的引用都会结束,之后读取者再进入RCU临界区看到的都将是新指针。

    老指针不能马上释放的原因:这是因为系统中爱可能存在对老指针的引用,者主要发生在以下两种情况:(1)一是在单处理器范围看,假设读取者在进入RCU临界区后,刚获得共享区的指针之后发生了一个中断,如果写入者恰好是中断处理函数中的行为,那么当中断返回后,被中断进程RCU临界区中继续执行时,将会继续引用老指针。(2)另一个可能是在多处理器系统,当处理器A上的一个读取者进入RCU临界区并获得共享数据区中的指针后,在其还没来得及引用该指针时,处理器B上的一个写入者更新了指向共享数据区的指针,这样处理器A上的读取者也饿将引用到老指针。

    RCU特点:由前面的讨论可以知道,RCU实质上是对读取者与写入者自旋锁rwlock的一种优化。RCU的可以让多个读取者和写入者同时工作。但是RCU的写入者操作开销就比较大。在驱动程序中一般比较少用。

    为了在代码中使用RCU,所有RCU相关的操作都应该使用内核提供的RCU API函数,以确保RCU机制的正确使用,这些API主要集中在指针和链表的操作。

    下面是一个RCU的典型用法范例:

    假设struct shared_data是一个在读取者和写入者之间共享的受保护数据
     
    Struct shared_data{
     
    Int a;
     
    Int b;
     
    Struct rcu_head rcu;
     
    };
     
     
     
    //读取者侧的代码
     
    Static void demo_reader(struct shared_data *ptr)
     
    {
     
           Struct shared_data *p=NULL;
     
           Rcu_read_lock();
     
           P=rcu_dereference(ptr);
     
           If(p)
     
                  Do_something_withp(p);
     
           Rcu_read_unlock();
     
    }
     
     
     
    //写入者侧的代码
     
     
     
    Static void demo_del_oldptr(struct rcu_head *rh) //回调函数
     
    {
     
           Struct shared_data *p=container_of(rh,struct shared_data,rcu);
     
           Kfree(p);
     
    }
     
    Static void demo_writer(struct shared_data *ptr)
     
    {
     
           Struct shared_data *new_ptr=kmalloc(…);
     
           …
     
           New_ptr->a=10;
     
           New_ptr->b=20;
     
           Rcu_assign_pointer(ptr,new_ptr);//用新指针更新老指针
     
           Call_rcu(ptr->rcu,demo_del_oldptr); 向内核注册回调函数,用于删除老指针指向的内存空间
     
    }
    
    
    (6)完成接口completion
    

    Linux内核还提供了一个被称为“完成接口completion”的同步机制,该机制被用来在多个执行路径间作同步使用,也即协调多个执行路径的执行顺序。在此就不展开了。

     

    展开全文
  • linux 内核同步机制

    2011-11-29 16:22:06
    本文档介绍在linux内核中两个不同进程或者过程访问和使用同一共享资源时,处理顺序的随机性,可能出现访问错误。结果依赖于多个任务的相对执行顺序(竟态条件)。其中处理竟态条件的同步机制在不同内核过程中处理的...
  • 本文为大家介绍了Linux内核中的同步和互斥分析报告。
  • Linux内核代码中,信号量被定义成semaphore结构体(代码位于include/linux/semaphore.h中): struct semaphore { raw_spinlock_t lock; unsigned int count; struct list_head wait_list; }; 这个结构体由三...

    在Linux内核代码中,信号量被定义成semaphore结构体(代码位于include/linux/semaphore.h中):

    struct semaphore {
    	raw_spinlock_t		lock;
    	unsigned int		count;
    	struct list_head	wait_list;
    };

    这个结构体由三部分组成:

    • lock:用来保护这个信号量结构体的自旋锁。
    • count:信号量用来保护的共享资源的数量。如果该值大于0,表示资源是空闲的,调用者可以立即获得这个信号量,进而访问被保护的共享资源;如果该值等于0,表示资源是忙的,但是没有别的进程在等待这个信号量被释放;如果这个值小于0,表示资源是忙的,且有至少一个别的进程正在等待该信号量被释放。
    • wait_list:在这个信号量上等待的所有进程链表。

    所有插入wait_list等待链表的进程节点由semaphore_waiter结构体表示:

    struct semaphore_waiter {
    	struct list_head list;
    	struct task_struct *task;
    	bool up;
    };

    这个结构体也由三部分组成:

    • list:插入链表的节点。
    • task:指向等待进程的task_struct结构体。
    • up:真表示该等待进程是因为信号量释放被唤醒的,否则都是假。

    初始化信号量

    信号量在使用之前要对其进行初始化,一般有两种方法:

    DEFINE_SEMAPHORE(sem1);
     
    struct semaphore sem2;
    sema_init(&lock2);

    第一种方法是用宏直接定义并且初始化一个顺序锁变量:

    #define __SEMAPHORE_INITIALIZER(name, n)				\
    {									\
    	.lock		= __RAW_SPIN_LOCK_UNLOCKED((name).lock),	\
    	.count		= n,						\
    	.wait_list	= LIST_HEAD_INIT((name).wait_list),		\
    }
    
    #define DEFINE_SEMAPHORE(name)	\
    	struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

    所以,直接通过宏定义初始化就是定义了一个semaphore结构体变量,将其内部的自旋锁lock初始化为未加锁,将表示共享资源数量的count变量初始化为1,将等待进程链表初始化为空链表。count的值为1,表示这个信号量只能同时由一个进程持有,也就是说被保护的共享资源只能被互斥的访问,这种特殊的信号量也称作二值(Binary)信号量。但是,通常大家用的都是这种二值信号量,用法如下:

    down(&sem);
    /* 临界区 */
    up(&sem);

    第二种方法是自己定义一个semaphore结构体变量,然后调用sema_init函数将其初始化:

    static inline void sema_init(struct semaphore *sem, int val)
    {
    	......
    	*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
    	......
    }

    最终也是通过__SEMAPHORE_INITIALIZER对信号量进行的初始化。但是,通过这种方式初始化可以指定count的值,而不像前者默认设置成1。

    获取信号量

    要想获取一个信号量,通常是通过调用down函数:

    void down(struct semaphore *sem)
    {
    	unsigned long flags;
    
            /* 获得自旋锁并关中断 */
    	raw_spin_lock_irqsave(&sem->lock, flags);
            /* 如果信号量的count大于0 */
    	if (likely(sem->count > 0))
                    /* 直接获得该信号量并将count值递减 */
    		sem->count--;
    	else
    		__down(sem);
            /* 释放自旋锁并开中断 */
    	raw_spin_unlock_irqrestore(&sem->lock, flags);
    }
    EXPORT_SYMBOL(down);

    如果信号量的count值大于0,则直接将其递减然后返回,调用的进程直接获得该信号量。如果小于或等于0,则接着调用__down函数:

    static noinline void __sched __down(struct semaphore *sem)
    {
    	__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
    }

    __down函数直接调用了__down_common函数。传入的第二个参数是TASK_UNINTERRUPTIBLE,表示;传入的第三个参数是MAX_SCHEDULE_TIMEOUT,表示会一直等待该信号量,直到获得为止,没有到期时间。

    除了最基本的down函数外,还可以通过下面几个函数来获得信号量:

    • down_interruptible:基本功能和down相同,区别是如果无法获得信号量,会将该进程置于TASK_INTERRUPTIBLE状态。因此,在进程睡眠时可以通过信号(Signal)将其唤醒。
    • down_killable:如果无法获得信号量,会将该进程置于TASK_KILLABLE状态。
    • down_timeout:不会一直等待该信号量,而是有一个到期时间,时间到了后即使没有获得信号量也会返回。等待的时候也和down函数一样,会将进程置于TASK_UNINTERRUPTIBLE状态。

    所有获得信号量的方法最终都是调用了__down_common函数,只不过传入的第二个和第三个参数不一样。

    static noinline int __sched __down_interruptible(struct semaphore *sem)
    {
    	return __down_common(sem, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
    }
    
    static noinline int __sched __down_killable(struct semaphore *sem)
    {
    	return __down_common(sem, TASK_KILLABLE, MAX_SCHEDULE_TIMEOUT);
    }
    
    static noinline int __sched __down_timeout(struct semaphore *sem, long timeout)
    {
    	return __down_common(sem, TASK_UNINTERRUPTIBLE, timeout);
    }

    我们接着回来看__down_common函数的实现:

    static inline int __sched __down_common(struct semaphore *sem, long state,
    								long timeout)
    {
    	struct semaphore_waiter waiter;
    
            /* 将表示等待进程的节点插入信号量的等待列表中 */
    	list_add_tail(&waiter.list, &sem->wait_list);
    	waiter.task = current;
    	waiter.up = false;
    
    	for (;;) {
                    /* 如果有待处理的信号则跳到interrupted标签处 */
    		if (signal_pending_state(state, current))
    			goto interrupted;
                    /* 如果超时了则跳到timed_out标签处 */
    		if (unlikely(timeout <= 0))
    			goto timed_out;
                    /* 设置当前进程的状态 */
    		__set_current_state(state);
                    /* 释放自旋锁 */
    		raw_spin_unlock_irq(&sem->lock);
                    /* 当前进程睡眠 */
    		timeout = schedule_timeout(timeout);
                    /* 再次获得自旋锁 */
    		raw_spin_lock_irq(&sem->lock);
                    /* 如果是因为自旋锁释放而被唤醒的则返回0 */
    		if (waiter.up)
    			return 0;
    	}
    
     timed_out:
    	list_del(&waiter.list);
    	return -ETIME;
    
     interrupted:
    	list_del(&waiter.list);
    	return -EINTR;
    }

    该函数先将表示等待进程的节点插入信号量的等待列表中,该节点的task变量指向表示当前进程的task_struct结构体,up变量被初始化为否。然后会进入一个大的循环中,循环的退出条件有三个:

    1. 当前进程有待处理的信号。
    2. 当前进程等待信号量超时了。
    3. 当前进程被另一个释放信号量的进程唤醒。

    signal_pending_state函数用来判断当前进程是否有待处理的信号(代码位于include/linux/sched/signal.h中):

    static inline int signal_pending_state(long state, struct task_struct *p)
    {
    	if (!(state & (TASK_INTERRUPTIBLE | TASK_WAKEKILL)))
    		return 0;
    	if (!signal_pending(p))
    		return 0;
    
    	return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p);
    }

    所以,当前进程的状态必须包含TASK_INTERRUPTIBLE或TASK_WAKEKILL才行。也就是说,只有通过调用down_interruptible或down_killable函数获得信号量时,signal_pending_state函数才有可能返回真。

    schedule_timeout用来将当前进程休眠,同时设置到期时间(代码位于kernel/time/timer.c中):

    signed long __sched schedule_timeout(signed long timeout)
    {
    	struct process_timer timer;
    	unsigned long expire;
    
    	switch (timeout)
    	{
    	case MAX_SCHEDULE_TIMEOUT:
    		/* 没有到期时间间隔直接睡眠 */
    		schedule();
    		goto out;
    	default:
    		if (timeout < 0) {
    			printk(KERN_ERR "schedule_timeout: wrong timeout "
    				"value %lx\n", timeout);
    			dump_stack();
    			current->state = TASK_RUNNING;
    			goto out;
    		}
    	}
    
            /* 计算到期时间 */
    	expire = timeout + jiffies;
    
    	timer.task = current;
            /* 设置定时器到期处理函数为process_timeout */
    	timer_setup_on_stack(&timer.timer, process_timeout, 0);
            /* 设置定时器 */
    	__mod_timer(&timer.timer, expire, 0);
            /* 休眠 */
    	schedule();
            /* 删除定时器 */
    	del_singleshot_timer_sync(&timer.timer);
    
    	......
    
            /* 计算距离到期时间还剩多少时间 */
    	timeout = expire - jiffies;
    
     out:
    	return timeout < 0 ? 0 : timeout;
    }
    EXPORT_SYMBOL(schedule_timeout);

    如果传入的到期时间间隔被设置成了MAX_SCHEDULE_TIMEOUT,表示当前进程在获得信号量时没有设置超时,因此直接调用schedule调度另一个进程执行,本进程进入休眠。如果设置了一个有效的到期时间间隔,那么在调用schedule之前必须要先添加一个定时器,让其在到期时间点被触发。但是,表示定时器本身的结构并不包含由哪个进程设置它的信息,所以还需要定义一个新的结构体process_timer:

    struct process_timer {
    	struct timer_list timer;
    	struct task_struct *task;
    };

    注意,表示定时器的结构体timer_list是在process_timer结构体中的第一个变量。这样,如果我们有了一个指向表示定时器的timer_list结构体的指针,那么它其实也是指向process_timer结构体的。通过前面的分析可以看到,定时器的到期函数被设置成了process_timeout,它的参数就是一个指向timer_list结构体的指针:

    static void process_timeout(struct timer_list *t)
    {
            /* 从timer_list指针获得包含它的process_timer */
    	struct process_timer *timeout = from_timer(timeout, t, timer);
    
            /* 唤醒到期进程 */
    	wake_up_process(timeout->task);
    }

    就是通过指向定时器结构体timer_list的指针获得包含它的process_timer结构体指针,从而获得设置该定时器的进程,然后唤醒它。

    释放信号量

    要想释放一个信号量,只能通过调用up函数:

    void up(struct semaphore *sem)
    {
    	unsigned long flags;
    
            /* 获得自旋锁并关中断 */
    	raw_spin_lock_irqsave(&sem->lock, flags);
            /* 如果等待进程链表为空 */
    	if (likely(list_empty(&sem->wait_list)))
                    /* 直接将count递增 */
    		sem->count++;
    	else
    		__up(sem);
            /* 释放自旋锁并开中断 */
    	raw_spin_unlock_irqrestore(&sem->lock, flags);
    }
    EXPORT_SYMBOL(up);

    如果释放的时候发现等待进程链表是空的,表示当前没有被的进程在等这个信号量,直接递增count值就行了;如果不为空,表示有别的进程在等这个信号量被释放,那么当前进程需要接着调用__up函数试着将某个等待进程唤醒。

    static noinline void __sched __up(struct semaphore *sem)
    {
            /* 获得等待链表中的第一个节点 */
    	struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
    						struct semaphore_waiter, list);
            /* 将该节点从等待链表中删除 */
    	list_del(&waiter->list);
    	waiter->up = true;
            /* 将该等待进程唤醒 */
    	wake_up_process(waiter->task);
    }

    功能很简单,获得等待链表中的第一个节点,然后唤醒该节点表示的进程。需要把该节点的up字段置为真,让被唤醒的进程知道其是因为信号量被释放才被唤醒的。

    使用场景

    要使用信号量,一般要满足以下的一些使用场景或条件:

    • 信号量适合于保护较长的临界区。它不应该用来保护较短的临界区,因为竞争信号量时有可能使进程睡眠和切换,然后被再次唤醒,代价很高,这种场景下应该使用自旋锁。
    • 如果被保护的共享资源有多份,并不只是互斥访问的,那非常适合使用信号量。
    • 只有允许睡眠的场景下才能使用内核信号量,也就是说在中断处理程序和可延迟函数中都不能使用信号量。
    展开全文
  • Linux内核同步机制之基本概念

    千次阅读 2020-11-17 12:57:47
    一、概述 近期深入的学习了 Linux 内核同步机制,将相关内容整理于此,既是一次梳理,也是一个分享,希望能帮助到读者一二。当然,所谓的深入也只是笔者现有的技术能力所能达到的程度而已。由于能力有限,有错误之处...

    一、概述

    近期深入的学习了 Linux 内核同步机制,将相关内容整理于此,既是一次梳理,也是一个分享,希望能帮助到读者一二。当然,所谓的深入也只是笔者现有的技术能力所能达到的程度而已。由于能力有限,有错误之处还请各位读者不吝指教,一起学习一起进步。

    常用的 Linux 内核同步机制有原子操作、Per-CPU 变量、内存屏障、自旋锁、Mutex 锁、信号量和 RCU 等,后面几种锁实现会依赖于前三种基础同步机制。在正式开始分析具体的内核同步机制实现之前,需要先澄清一些基本概念。

    二、基本概念

    2.1 同步

    既然是同步机制,那就首先要搞明白什么是同步。同步是指用于实现控制多个执行路径按照一定的规则或顺序访问某些系统资源的机制。所谓执行路径,就是在 CPU 上运行的代码流。我们知道,CPU 调度的最小单位是线程,可以是用户态线程,也可以是内核线程,甚至是中断服务程序。所以,执行路径在这里就包括用户态线程、内核线程和中断服务程序。执行路径、执行单元、控制路径等等,叫法不同,但本质都一样。那为什么需要同步机制呢?请继续往下看。

    2.2 并发与竞态

    并发是指两个以上的执行路径同时被执行,而并发的执行路径对共享资源(硬件资源和软件上的全局变量等)的访问则很容易导致竞态。例如,现在系统有一个 LED 灯可以由 APP 控制,APP1 控制灯亮一秒灭一秒,APP2 控制灯亮 500ms 灭 1500ms。如果 APP1 和 APP2 分别在 CPU1 和 CPU2 上并发运行,LED 灯的行为会是什么样的呢?很有可能 LED 灯的亮灭节奏都不会如这两个 APP 所愿,APP1 在关掉 LED 灯时,很有可能恰逢 APP2 正要打开 LED 灯。很明显,APP1 和 APP2 对 LED 灯这个资源产生了竞争关系。竞态是危险的,如果不加以约束,轻则只是程序运行结果不符合预期,重则系统崩溃。在操作系统中,更复杂、更混乱的并发大量存在,而同步机制正是为了解决并发和竞态问题。同步机制通过保护临界区(访问共享资源的代码区域)达到对共享资源互斥访问的目的,所谓互斥访问,是指一个执行路径在访问共享资源时,另一个执行路径被禁止去访问。关于并发与竞态,有个生活例子很贴切。假如你和你的同事张小三都要上厕所,但是公司只有一个洗手间而且也只有一个坑。当张小三进入厕所关起门的那一刻起,你就无法进去了,只能在门外侯着。当小三哥出来后你才能进去解决你的问题。这里,公司厕所就是共享资源,你和张小三同时需要这个共享资源就是并发,你们对厕所的使用需求就构成了竞态,而厕所的门就是一种同步机制,他在用你就不能用了。更多内容请查阅宋宝华老师的《Linux 设备驱动开发详解》一书中第七章第一节,书中详细列举了竞态发生的场景,总结如下图。

    2.3 中断与抢占

            中断本身的概念很简单,本文不予解释。当然,这并不是说 Linux 内核的中断部分也很简单。事实上,Linux 内核的中断子系统也相当复杂,因为中断对于操作系统来说实在是太重要了。以后有机会,笔者计划开专题再来介绍。对于同步机制的代码分析来说,了解中断的概念即可,不需要深入分析内核的具体代码实现。抢占属于进程调度的概念,Linux 内核从 2.6 版本开始支持抢占调度。进程调度(管理)是 Linux 内核最核心的子系统之一,异常庞大,本文只简单介绍基本概念,对于同步机制的代码分析已然足够。通俗地说,抢占是指一个正愉快地运行在 CPU 上的 task(可以是用户态进程,也可以是内核线程) 被另一个 task(通常是更高优先级)夺去 CPU 执行权的故事。中断和抢占之间有着比较暧昧的关系,简单来说,抢占依赖中断。如果当前 CPU 禁止了本地中断,那么也意味着禁止了本 CPU 上的抢占。但反过来,禁掉抢占并不影响中断。Linux 内核中用 preempt_enable() 宏函数来开启本 CPU 的抢占,用 preempt_disable() 来禁掉本 CPU 的抢占。这里,“本 CPU” 这个描述其实不太准确,更严谨的说法是运行在当前 CPU 上的 task。preempt_enable() 和 preempt_disable() 的具体实现展开来介绍的话也可以单独成文了,笔者没有深究过,就不班门弄斧了,感兴趣的读者可以去 RTFSC。不管是用户态抢占还是内核态抢占,并不是什么代码位置都能发生,而是有抢占时机的,也就是所谓的抢占点。抢占时机如下:
           用户态抢占:1、从系统调用返回用户空间时;2、从中断(异常)处理程序返回用户空间时。
           内核态抢占:1、当一个中断处理程序退出,返回到内核态时;2、task 显式调用 schedule();3、task 发生阻塞(此时由调度器完成调度)。
           以上情况是个人理解,欢迎补充其它 case。

    2.4 编译乱序与编译屏障

    参阅笔者转载的这篇文章 https://blog.csdn.net/weixin_43555423/article/details/113481578

    2.5 执行乱序与内存屏障

    《Linux 设备驱动开发详解》一书中第七章第二节对编译乱序和执行乱序都有简略的介绍,通俗易懂,不可不读。不管是编译乱序还是执行乱序,都是为了提升 CPU 的性能。执行乱序是处理器运行时的行为,和 CPU 内部设计架构有关。而对于从事在 Linux 内核的程序员来说,要真正的理解透执行乱序所带来的软件方面的影响,首先需要搞清楚 cache 的概念。强烈建议关注奔跑 Linux 社区这个公众号,里面有三篇关于 cache 的文章,分为上中下,作者笨叔讲解的非常透彻。内存屏障是为了解决执行乱序引入的问题,个人认为是同步机制里最难理解的一块。关于内存屏障,可以参阅 wowo 翻译的这几篇文章,链接如下:

    http://www.wowotech.net/kernel_synchronization/Why-Memory-Barriers.html  Why Memory Barriers?中文翻译(上)
    http://www.wowotech.net/kernel_synchronization/why-memory-barrier-2.html  Why Memory Barriers?中文翻译(下)
    http://www.wowotech.net/kernel_synchronization/memory-barrier-1.html  perfbook memory barrier(14.2章节)中文翻译(上)
    http://www.wowotech.net/kernel_synchronization/perfbook-memory-barrier-2.html  perfbook memory barrier(14.2章节)中文翻译(下)

    perbook 一书的全称是《Is Parallel Programming Hard And If So What Can You Do About It?》,是并行编程领域的权威著作,作者是大名鼎鼎的 Paul E. Mckenney。Paul 目前供职于 IBM Linux 技术中心,也是 Linux 社区 RCU 模块的领导者和维护者。perbook 的中文版本名称是 《深入理解并行编程》,由国产内核大神谢宝友倾情翻译。谢大神目前就职于阿里,他和 Linux 之间也有一段传奇经历。关于两位大神更多的趣闻轶事,感兴趣的读者可以上网搜寻。谢大神的翻译非常贴近原文,但读起来终觉比较晦涩,可能是笔者功力不够。wowo 的翻译则优先考虑的是汉语表述的流畅性,所以读起来会很顺畅,唯一美中不足的就是排版[笑哭]。笔者在学习过程中重新整理了排版,并删掉了英文原文,然后用红色字体加入了自己的一些读书笔记,有兴趣的读者朋友可以留言索取,欢迎一起交流。如果有的读者朋友英语很好,强烈建议你们去读原文。

    展开全文
  • Linux内核同步介绍及方法

    千次阅读 2016-11-06 18:50:56
    Linux内核设计与实现 第9章内核同步介绍 (1)多个执行线程同时访问和操作数据,就有可能发生各线程之间相互覆盖共享数据的情况,造成共享数据处于不一致状态。并发访问共享数据是造成系统不稳定的一类隐患...
  • Linux内核同步机制之信号量与锁

    千次阅读 2012-03-21 09:10:39
     Linux内核同步控制方法有很多,信号量、锁、原子量、RCU等等,不同的实现方法应用于不同的环境来提高操作系统效率。首先,看看我们最熟悉的两种机制——信号量、锁。   一、信号量    首先还是看看内核中是...
  • 深入理解linux内核,中文第三版,可随便复制粘贴,包含所有20章的内容
  • Linux 内核经典面试题

    千次阅读 2020-12-22 09:12:59
    点击上方“服务端思维”,选择“设为星标”回复”669“获取独家整理的精选资料集回复”加群“加入全国服务端高端社群「后端圈」内核经典面试题总览主要有哪几种内核锁?Linux 内核同步机制...
  • Linux内核同步方法——读写锁

    千次阅读 2018-06-08 13:49:15
    读 - 写自旋锁 一个或多个任务可以并发地持有读者锁;相反,用于写的锁最多只能被...基本数据结构 在内核代码中,读-写自旋锁用rwlock_t类型表示,typedef struct { /** * 这个锁标志与自旋锁不一样,自旋锁的lo...
  • Linux内核同步

    千次阅读 2013-03-04 14:41:35
    Linux内核学习从零单排(四) 1.所谓临界区(临界段)就是访问和操作共享数据的代码段。避免并发和防止竞争条件成为同步。 2.死锁产生条件:要有一个或多个执行线程和一个或多个资源,每个线程都在等待其中的一个...
  • linux内核同步机制

    2008-11-12 08:07:52
    详细描述了linux内核同步的方法,spinlock在不同情况下的详细用法
  • Linux操作系统内核同步机制分析.PDF
  • 边干边学:LINUX内核指导 李善平, PDF文档,据作者在浙江大学计算机学院多年的教学实践,教材内容选定了Linux环境,shell编程,时钟与定时器、系统调用,进程调度,进程同步,虚拟存储,共享内存,设备驱动,文件...
  • Linux Kernel 除了提供了自旋锁,还提供了睡眠锁,信号量就是一种睡眠锁。信号量的特点是,如果一个任务试图获取一个已经被占用的信号量,他会被推入等待队列,让其进入睡眠。此刻处理器重获自由,去执行其他的代码...
  • linux内核知识系列:同步与互斥 华嵌智能提供 www.embedded-cn.com http://embedded-cn.taobao.com
  • 自旋锁 Linux的的内核最常见的锁是自旋锁。自旋锁最多只能被一个可执行线程持有。如果一个执行线程试图获得一个被已经持有(争用)的自旋锁,那么该线程就会一直进行忙循环-旋转-等待锁重新可用要是锁未被争用,...
  • Linux内核数据同步经典方法介绍 来自于英本教育内部资料 内带详细讲解和代码 希望对大家有所帮助

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 162,276
精华内容 64,910
关键字:

linux内核同步