(笔记)Linux内核学习(七)之内核同步机制和实现方式

一 原子操作

       指令以原子的方式执行——执行过程不被打断。

1 原子整数操作

       原子操作函数接收的操作数类型——atomic_t

复制代码
复制代码
//定义
atomic_t v;
//初始化 atomic_t u = ATOMIC_INIT(0); //操作 atomic_set(&v,4); // v = 4 atomic_add(2,&v); // v = v + 2 = 6 atomic_inc(&v); // v = v + 1 = 7
//实现原子操作函数实现 static inline void atomic_add(int i, atomic_t *v) { unsigned long tmp; int result; __asm__ __volatile__("@ atomic_add "     "1: ldrex %0, [%3] "     " add %0, %0, %4 "     " strex %1, %0, [%3] "     " teq %1, #0 "     " bne 1b"   : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)   : "r" (&v->counter), "Ir" (i)   : "cc"); }
复制代码
复制代码

 

2 原子位操作

复制代码
复制代码
//定义
unsigned long word = 0;

//操作
set_bit(0,&word);       //第0位被设置1
set_bit(0,&word);       //第1位被设置1
clear_bit(1,&word);   //第1位被清空0

//原子位操作函数实现
static inline void ____atomic_set_bit(unsigned int bit, volatile unsigned long *p)
{
       unsigned long flags;
       unsigned long mask = 1UL << (bit & 31);

       p += bit >> 5;
       raw_local_irq_save(flags);
       *p |= mask;
       raw_local_irq_restore(flags);
}
复制代码
复制代码

二 自旋锁

  原子位和原子整数仅能对简单的整形变量进行原子操作,对于复杂的数据复杂的操作并不适用。

需要更复杂的同步方法实现保护机制——锁。

  自旋锁:同一时刻只能被一个可执行线程持有,获得自旋锁时,如果已被别的线程持有则该线程进行循环等待锁重新可用,

然后继续向下执行。

过程如下:

      

    

 

使用锁得基本形式如下:

复制代码
复制代码
spinlock_t lock;
//获取锁
spin_lock(&lock);
//临界区
……
 
//释放锁
spin_unlock(&lock);
复制代码
复制代码

使用自旋锁防止死锁:

       自旋锁不可递归,自旋处于等待中造成死锁;

       中断处理程序中,获取自旋锁前要先禁止本地中断,中断会打断正持有自旋锁的任务,中断处理程序有可能争用已经被持有的自旋锁,造成死锁。

       读写自旋锁:将锁的用途直接分为读取和写入。

三 信号量

  信号量:睡眠锁。如果有一个任务试图获取信号量时,

              信号量未被占用:该任务获得成功信号量;

              信号量已被占用:信号量将任任务推进等待队列,让其睡眠,处理器继续工作;当信号量被释放后,

        唤醒信号量队列中的任务,并获取该信号量。

              可有读写信号量。

      

      

声明信号量:

复制代码
复制代码
信号量数据结构:
/* Please don't access any members of this structure directly */
struct semaphore {

       raw_spinlock_t           lock;

       unsigned int        count;

       struct list_head   wait_list;

};
复制代码
复制代码

静态声明信号量:

复制代码
复制代码
//声明可用数量为1信号量
#define DEFINE_SEMAPHORE(name)  
      struct semaphore name = __SEMAPHORE_INITIALIZER(name, 1)

//声明可用数量为n的信号量
#define __SEMAPHORE_INITIALIZER(name, n)                      
{                                                              
       .lock              = __RAW_SPIN_LOCK_UNLOCKED((name).lock),      

       .count           = n,                                    

       .wait_list       = LIST_HEAD_INIT((name).wait_list),           
}
复制代码
复制代码

动态声明信号量:

复制代码
复制代码
static inline void sema_init(struct semaphore *sem, int val)
{
    static struct lock_class_key __key;
    *sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
    lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);

}
复制代码
复制代码

使用信号量: 

复制代码
复制代码
  //初始化定义信号量
    struct semaphore driver_lock;
    sema_init(&driver_lock, 1);

    //获取信号量
    if (down_interruptible(&driver_lock))
           return -EINTR;
    //执行临界区
    ……

    //释放信号量
    up(&driver_lock);
复制代码
复制代码

自旋锁与信号量对比:

       需求                             使用锁

    低开销加锁         :               优先使用自旋锁

    短期锁定            :               优先使用自旋锁

    长期锁定            :               优先使用信号量

    中断上下文加锁   :               使用自旋锁

    持有锁需要睡眠   :               使用信号量

四 完成变量

  完成变量:如果在内核中一个任务需要发出信号通知另一个任务发生了某个特定事件,

       使用完成变量去唤醒在等待的任务,使两个任务得以同步。信号量的简易方法。

数据结构:

struct completion {
       unsigned int done;
       wait_queue_head_t wait;
};

完成变量声明:

复制代码
复制代码
动态:#define COMPLETION_INITIALIZER(work) 
                            { 0, __WAIT_QUEUE_HEAD_INITIALIZER((work).wait) }

静态: static inline void init_completion(struct completion *x)
{
       x->done = 0;
       init_waitqueue_head(&x->wait);
}
复制代码
复制代码

完成变量使用:

//等待制定的完成变量
void __sched wait_for_completion(struct completion *x)


//发信号唤醒等待的任务
void complete(struct completion *x)   

还有实现同步机制诸如:禁止抢占,Seq锁(读写共享数据),顺序和屏障……

原文地址:https://www.cnblogs.com/cyyljw/p/7880448.html