Linux内核并发机制----自旋锁,读写锁,顺序锁

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_37806112/article/details/81484398

基本概念:

并发:多个执行单元同时发生
“执行单元”:硬件中断、软中断、进程
竞态:多个执行单元同时访问共享资源产生竞态
产生竞态的条件:
1,必须有多个执行单元
2,必须有共享资源
3,必须同时访问
共享资源:硬件资源(驱动程序中但凡设计的寄存器都是共享资源)和软件上的全局变量
互斥访问:当有多个执行单元要访问共享资源的时候,只允许一个执行单元访问共享资源,其他执行单元禁止访问!
临界区:访问共享资源的代码区域

互斥访问的本质目的:就是让临界区的代码执行路径具有原子性!

2,一定要明确在哪些场合会形成竞态?
多核:因为多核会共享内存,外存,IO,如果同时访问,势必形成竞态;
进程与进程的抢占;
中断和进程;
中断和中断;

3,linux内核解决竞态的方法
中断屏蔽:
能够解决哪些场合的竞态问题?
答:进程和进程的抢占(依赖中断来实现)
中断和进程
中断和中断
注意:中断屏蔽的是当前CPU的中断信号
中断屏蔽的相关使用方法:
1,在访问临界区之前屏蔽中断
unsigned long flags;
local_irq_disable();//仅仅屏蔽中断
或者
local_irq_save(flags);//屏蔽中断,保存中断状态到flags中断
2,执行临界区的代码
切记:临界区的代码要求执行速度要快,千万不能做休眠操作
3,在访问临界区之后恢复中断
local_irq_enable();//仅仅恢复中断
或者
local_irq_restore(flags);//恢复中断,从flags中恢复保存的中断状态

一. 自旋锁:

       概念:

          它的定义可表述如下:某个进程在试图加锁的时候,若当前锁已经处于“锁定”状态,试图加锁进程就进行不断的“旋转”,用一个死循环测试锁的状态,直到成功的获得锁。

       函数:

                 

         demo:

                  

          注意:

                      1.自旋锁需要消耗CPU的资源,所以临界区域必须要小,执行的要快
                      2.自旋锁的使用需要注意迭代现象
                      3.在自旋锁修饰的临界区域一定不能出现进程调度等会引发上下文切换的函数
                         kmalloc sleep copy_from_user  copy_to_user

二. 读写锁:

        锁的用途可以明确到分为读取和写入两个场景。比如,对于一个链表可能就要更新又要检索,当更新链表时,不能有其他代码并发的写链表或从链表中读取数据,写操作要求完全互斥。另一方面,当对其检索(读取)链表时,只要其他程序不对链表进行写操作就行了。只有没有写操作,多个并发的读操作都是安全的。可以通过读写锁搞定。     

函数: 

             //定义读写锁
             rwlock_t rwlock;
             //初始化读写锁
             rwlock_init(&rwlock);
             //读锁定
             read_lock(&rwlock);
             //读锁定并关闭中断
             read_lock_irq(&rwlock);
             //读解锁
             read_unlock(&rwlock);
             //读解锁并恢复中断
             read_unlock_irqrestore(&rwlock);
             //写锁定
             write_lock(&rwlock);
             //写锁定并关闭中断
             write_lock_irq(&rwlock);
             //写解锁
             write_unlock(&rwlock);
             //写解锁并恢复中断
             write_unlock_irqrestore(&rwlock);

   注意:读锁和写锁要位完全分隔开的代码分支中。

三 .顺序锁:       

        顺序锁的设计思想是:对某一个共享数据读取的时候不加锁,写的时候加锁。同时为了保证读取的过程中因为写进程修改了共享区的数据,导致读进程读取数据错误。在读取者和写入者之间引入了一个整形变量sequence,读取者在读取之前读取sequence, 读取之后再次读取此值,如果不相同,则说明本次读取操作过程中数据发生了更新,需要重新读取。而对于写进程在写入数据的时候就需要更新sequence的值。     

       也就是说临界区只允许一个write进程进入到临界区,在没有write进程的话,read进程来多少都可以进入到临界区。但是当临界区没有write进程的时候,write进程就可以立刻执行,不需要等待。

函数:           

        //定义顺序锁
       seqlock_t seq_lock;

       //初始化顺序锁
      seqlock_init(&seq_lock);

      //写锁定
      write_seqlock(seqlock_t * sl);
      write_seqlock_irq(seqlock_t * sl);

     //写解锁
     write_sequnlock(seqlock_t * sl);
     write_sequnlock_irqrestore(seqlock_t * sl);

     //读申请
     unsigned int read_seqbegin(const seqlock_t * sl);
     //读有效判定
     int read_seqretry(const seqlock_t * sl, unsigned start);

     //demo
     do
     {
         unsigned int seqnum=read_seqbegin(&seqlock);
           //读操作          .
       }while(read_seqretry(&seqlock,seqnum));

Seq锁的最佳选择:

      1.数据存在很多读者。

      2.数据写者很少。

      3.虽然写者少,但是你希望写优先于读,而且不允许读者让写者饥饿。
   

猜你喜欢

转载自blog.csdn.net/m0_37806112/article/details/81484398