《Linux内核设计与实现》——内核同步方法

本文介绍了Linux内核中的多种同步机制,包括原子操作、自旋锁、读-写锁、信号量、互斥体等,详细解释了每种机制的特点及使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、原子操作

  一)、相关介绍

  1、原子操作可以保证指令以原子的方式执行——执行过程中不被打断。


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



 二)、原子整数操作

  1、针对整数的原子操作只能对atomic_t类型的数据进行处理。atomix_t类型定义在文件<linux/types.h>中:

         typedef  struct {

                     volatile  int  counter;

         }atomic_t;

    1)、尽管Linux支持的所有机器上的整数数据都是32位的,但是使用atomic_t的代码只能将该类型的数据当作24位来用。


  2、使用原子整数操作需要的声明都在<asm/atomic.h>文件中。


  3、原子整数操作列表(P143  表10.1)

    1)、原子操作通常是内联函数,往往是通过内嵌汇编指令来实现的。如果某个函数本来就是原子操作,那么它往往会被定义成一个宏。



 三)、63位原子操作

  1、对64位原子操作只能通过atomic64.t类型操作。

         typedef  struct  {

                     volatile  long  counter;

         }atomic64_t;


  2、64位原子整型操作(P145  表10.2)



 四)、原子位操作

  1、位操作汗是是对普通的内存地址进行操作,它的参数是一个指针和一个位号,第0位是给定地址的最低有效位。


  2、由于原子位操作是对普通的指针进行的操作,所以不像原子整数操作有对应atomic_t,其没有特殊的类型。相反,只要指针指向了任何你希望的数据,就可以对它进行操

        作。


  3、原子位操作(P146  表10-3)


  4、内核提供了两个例程用来从指定的地址开始搜索第一个被设置的位:

        int  find_first_bit(unsigned  *addr,  unsigned  int  size);

        int  find_fiest_zero_bit(unsigned  long  *addr,  unsigned  int  size);




二、自旋锁

 一)、相关介绍

  1、Linux内核中最常见的锁是自旋锁。自旋锁,最多只能被一个可执行的线程持有。在任意期间,自旋锁都可以防止多于一个的执行线程同时进入临界区。同一个锁可以用在

        多个位置。


  2、一个被争用的自旋锁使得请求它的线程在等待锁重新可用时自旋(特别浪费处理器时间),这种行为是自旋锁的要点。所以自旋锁不应被长时间持有。



  二)、自旋锁方法

  1、自旋锁的实现和体系结构有关,代码往往通过汇编实现。这些与体系结构相关的代码定义在文件<asm/spinlock.h>中,实际需要用到的接口定义在文件<linux/spinlock.h>

        中。自旋锁基本使用形式如下:

        DEFONE_SPINLOCK(mr_lock);

        spin_lock(&mr_lock);

        /*临界区.  .  .  */

        spin_unlock(&mr_lock);


  2、内核提供的禁止中断同时请求锁的接口:

        DEFONE_SPINLOCK(mr_lock);

        unsigned  long  flags;

        spin_lock_irqsave(&mr_lock);

        /*临界区.  .  .  */

        spin_unlock_irqrestore(&mr_lock);

    1)、函数spin_lock_irqsave()保存中断的当前状态,并禁止本地中断,然后再去获取指定的锁。

    2)、反过来spin_unlock_irqrestore();对指定的锁解锁,然后让中断恢复到加锁前的状态。


  3、如果能确定中断在加锁前是激活的,就不需要解锁后恢复中断以前的状态。可以无条件地在解锁时激活中断。这时,使用spin_lock_irq()和spin_unlock_irq更好一些:

        DEFONE_SPINLOCK(mr_lock);

        spin_lock_irq(&mr_lock);

        /*临界区.  .  .  */

        spin_unlock_irq(&mr_lock);



 三)、其他针对自旋锁的操作

  1、可以使用spin_lock_init()方法来初始化动态创建的自旋锁。


  2、spin_try_lock()试图获得某个特定的自旋锁,如果该锁已经被争用,那么该方法立即会返回一个非0值,而不会自旋锁等待被释放;如果成功获得了这个锁,该函数返回0。


  3、spin_is_locked()方法用于检查特定的锁当前是否已经被占用,如果已被占用,返回非0值,否则返回0.该方法制作判断,并不实际占用。


  4、自旋锁方法列表(P150  表10-4)



 四)、自旋锁和下半部




三、读-写锁

  1、Linux内核提供了专门的读-写自旋锁,这种自旋锁为读和写分别提供了不同的锁。一个或多个读任务可以并发地持有读者锁;相反,用于写的锁最多只能被一个写任务持

        有,而且此时不能有并发的读操作。


  2、有时把读/写锁叫做共享/排斥锁,或者并发/排斥锁,因为这种锁以共享和排斥的形式获得使用。


  3、读/写锁操作

    1)、读/写自旋锁的初始化:

          DEFINE_RWLOCK(mr_lock);

    2)、然后,在读者的代码分支中使用如下函数:

          read_lock(&mr_lock);

          /*临界区(只读)...*/

          read_unlock(&mr_lock);

    3)、最后,在写者的代码分支中使用如下函数:

          write_lock(&mr_lock);

          /*临界区(读写)...*/

          write_unlock(&mr_lock);


  4、读-写自旋锁方法列表(P152  表10-5)




五、信号量

 一)、相关介绍

  1、信号量和自旋锁的差异。


  2、如果需要在自旋锁和信号量中做出选择,应该根据锁被持有的时间长短作判断。另外,信号量不同于自旋锁,它不会禁止内核抢占,所以持有信号量的代码可以被抢占。



 二)、计数信号量和二值信号量

  1、信号量同时允许的持有者数量可以在声明信号量时指定。这个值称为使用者数量或简单地叫数量

    1)、通常情况下,信号量和自旋锁一样,在一个时刻仅允许有一个锁持有者。这时技术等于1,这样的信号量叫作二值信号量。或者称为互斥信号量

    2)另一方面ian,初始化时也可以把数量设置为大于1的非0值。这种请情况,信号量被称为计数信号量,它允许在一个时刻至多有count个锁持有者。


  2、信号量支持两个原子操作P()和V()。前者叫做测试操作,后者叫做增加操作。后来的系统把两种操作分别叫做down()和up(),Linux也遵从这种叫法。



 三)、创建和初始化信号量

  1、信号量的实现与体系结构有关,具体实现方式在文件<asm/semaphore.h>中。struct  semaphore类型用来表示信号量。


  2、静态声明信号量

     I、struct  semaphore  name;

          sema_init(&name,  count);

     II、创建更普通的互斥信号量可以使用以下快捷方式:

          static  DECLARE_MUTEX(name);


  3、动态声明信号量

      I、sema_init(sem,  count);

      II、初始化一个东动态创建的互斥量:

           init_MUTEX(sem);



 四)、使用信号量

  1、获取信号量

    1)、函数down_interruptible()试图获取指定的信号量,如果信号量不可用,它将把调度进程置成TASK_INTERRUPTIBLE——进入睡眠。

    2)、另一个函数down()会让进程在TASK_UNINTERRUPTIBLE状态下睡眠。

    3)、使用down_trylock()函数,可以尝试以堵塞方式来获取指定的信号量。在信号量已被占据时,它立即返回非0值;否则。它返回0,而且让你成功持有信号量锁。


  2、要释放指定的信号量,需要调用up()函数。


  3、信号量方法列表(P155  表10-6)




五、读-写信号量

  1、读-写信号量在内核中是由rw_semaphore结构表示,定义在文件<linux/rwsem.h>中。


  2、通过以下语句可以创建静态声明的读-写信号量:

        static  DECLARE_RWSEM(name);


  3、动态创建的读-写信号量可以通过以上函数初始化:

        init_rwsem(struct  rw_semaphore  *sem);


  4、读-写信号量也提供了down_read_trylock()和down_write_trylock()方法。


  5、读-写信号量相比读-写自旋锁多一种特有的操作:downgrade_write()。这个函数可以动态地将获取的写锁转换为读锁。




六、互斥体

 一)、相关简介

  1、mutex在内核中对应数据结构mutex,其行为和使用计数为1的信号量类似,但操作接口更简单,实现也更高效,而且使用限制更强。


  2、静态地定义mutex:

       DEFINE_MUTEX(name);


  3、动态初始化mutex:

       mutex_init(&mutex);


  4、对互斥体锁定和解锁:

       mutex_lock(&mutex);

       /*  临界区  */

       mutex_unlock(&mutex);


  5、Mutex方法(P157  表10-7)


  6、mutex使用场景



 二)、信号量和互斥体


 三)、自旋锁和互斥体

  1、自旋锁和互斥体的比较。




七、完成变量

  1、如果在内核中一个任务需要发出通知另一个任务发生了某件事情,利用完成变量是使两个任务得以同步的简单方法。


  2、完成变量由结构completion表示,定义在<linux/completion.h>中。


  3、通过以下宏创建完成变量:

    1)、静态:DECLARE_COMPLETION(mr_comp);

    2)、动态:init_completion()


  4、在一个特定的完成变量上,需要等待的任务调用wait_for_completion()来等待特定事件。当特定事件发生后,产生事件的任务调用completion()来发送信号唤醒正在等待的

        任务。


  5、完成变量方法(P159  表10-9)



八、BLK:大内核锁

  1、BKL函数列表(P160  表10-10)



九、顺序锁

  1、顺序锁,简称seq锁。这种锁提供了一种很简单的机制,用于读写共享数据。


  2、顺序锁操作

    1)、定义一个seq锁

       seqlock_t  mr_seq_lock = DEFINE_SEQLOCK(mr_seq_lock);

    2)、写锁

       write_seqlock(&mr_seq_lock);

       /*写锁被获取  .  .  .   */

       write_sequnlock(&mr_seq_lock);


  3、使用seq锁的选择。



十、禁止抢占

  1、内核抢占的相关函数(P162  表10-11)



十一、顺序和屏障

  1、内核和编译器屏障方法(P165  表10-12)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值