Linux内核同步的方法 总结

来源:互联网 发布:知乎 金钱 编辑:程序博客网 时间:2024/06/10 19:26


1 禁用中断。对于单处理器不可抢占系统来说,系统并发源主要是中断处理,在进行临界资源访问时,通过local_irq_enablelocal_irq_disable使能和禁用中断。


2 自旋锁。在多处理器之间设置一个全局变量V,表示锁。定义V=1时为锁定状态,V=0时为解锁状态。如果cpu0运行的代码进入临界区,就先读取V的值,如果V=0为锁定状态,表明其它cpu对临界区的资源进行访问,cpu0就要进入忙等状态(自旋);如果V=0,表明当前没有其它cpu对临界区访问,cpu0进入临界区时,把V设置为1,访问完成离开临界区时将V设置为0

2.1普通自旋锁,由数据结构spinlock_t表示,

typedef struct {

    raw_spinklock_t  raw_lock;

 #ifdefined(CONFIG_PREEMPT) && defined(CONFIG_SMP)

    unsigned  int break_lock;

 #endif

} spinlock_t;

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

接口函数:

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_irq(lock)spin_unlock_irq(lock)

应用场景cpu0当前进程A要对某一全局链表g_list进行操作,操作前调用spin_lock获取锁,然后进入临界区。在执行临界区代码时,cpu0发生一个外部中断,cpu0就会暂停进程A,执行中断服务程序。假如中断服务程序也要操作g_list,中断服务程序试图调用spin_lock获取锁,由于该锁被进程A持有,中断处理程序就会进入忙等待状态。导致中断程序无法返回,进程A无法执行,系统死锁。

2.3读写自旋锁rwlock允许多个读进程同时进入临界区,交错访问临界资源,提高系统并发能力。写入进程必须互斥访问。如果当前进程在写,其它进程不能读也不能写;如果当前进程在读,其它进程可以读但不能写。

接口函数为

DEFINE_RWLOCK(lock)//声明读写自旋锁lock,并初始化为未锁定状态

write_lock(lock)//以写方式锁定,若成功则返回,否则循环等待

  write_unlock(lock)//解除写方式的锁定,重设为未锁定状态

read_lock(lock)//以读方式锁定,若成功则返回,否则循环等待

read_unlock(lock)//解除读方式的锁定,重设为未锁定状态 

2.4顺序自旋锁seqlock,主要解决在拥有大量读进程时,写进程由于长时间无法持有锁而死的情况。对某一共享数据读取时不加锁,写的时候加锁

接口函数:

seqlock_init(seqlock)//初始化为未锁定状态

read_seqbgin()read_seqretry() //保证数据的一致性

write_seqlock(lock)//尝试以写锁定方式锁定顺序锁

write_sequnlock(lock)//解除对顺序锁的写方式锁定,重设为未锁定状态。


    3信号量,信号量只能通过原子操作P()V()访问,也称为down()up()down()对信号量的计数器减1,获取一个信号量。如果操作后的结果是大于等于0,获得信号量锁,进程就可以进入临界区;如果操作后是负数,进程就会进入等待队列。离开临界区后,调用up()释放信号量,计数器加1

3.1普通信号量

 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) //释放信号量,并唤醒等待队列上的进程

3.2读写信号量(rwsem

该信号量机制使得所有的读进程可以同时访问信号量保护的临界资源。当进程尝试锁定读写信号量不成功时,则这些进程被插入到一个先进先出的队列中;当一个进程访问完临界资源,释放对应的读写信号量是,该进程负责将该队列中的进程按一定的规则唤醒。唤醒排在该先进先出队列中队首的进程,在被唤醒进程为写进程的情况下,不再唤醒其他进程;在唤醒进程为读进程的情况下,唤醒其他的读进程,直到遇到一个写进程。

 sturct rw_semaphore{

       __s32     activity; //用于表示读者或写者的数量

       spinlock_t     wait_lock;

       struct list_head     wait_list;

 };

接口函数:

 void up_read(Sturct rw_semaphore *sem);

 void __sched down_read(Sturct rw_semaphore*sem);

 int down_read_trylock(Sturct rw_semaphore*sem);

 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互斥信号量count值为1,只有一个进程进入临界区


4 互斥锁(mutex)

Linux可以使用互斥信号量(count值为1)来表示互斥锁,那就是通过宏DECLARE_MUTEX来定义一个互斥信号量。

接口函数:

voidmutex_lock(struct mutex *lock);

 intmutex_lock_interruptible(struct mutex *lock);

 intmutex_trylock(struct mutex *lock);

 voidmutex_unlock(struct mutex *lock);


5RCU

RCURead-Copy-Update(/-复制-更新),是linux提供的一种免锁的同步机制。将读进程和写入进程访问的共享数据放在一个指针p中,读进程通过p读数据,写进程通过p写数据

读进程的操作:首先调用rcu_read_lock()rcu_read_unlock()构建读进程临界区(read-sidecritical section),然后在临界区获得共享数据区的指针。其中,对指针的引用必须要在临界区中完成,离开临界区后不再引用指针,在临界区内的代码不应该导致进程切换。

写进程操作:首先分配一个新的内存空间作为共享数据区,然后将老数据区的数据复制到新数据区,修改新数据区,最后用新数据区的指针替换老数据区的指针。写进程需要内核共同协作,在确定所有对老指针的引用都结束后才可以释放老指针指向的内存空间,因此写入进程需要调用call_rcu()向内核注册一个回调函数,内核在确定对老指针的引用结束后就会调用该回调函数,在回调函数里释放老指针指向的内存空间

Voidcall_rcu(struct rcu_head *head,void (*func)(struct rcu_head *rcu));

内核确定不再引用老指针的条件是:系统中所有cpu至少发生一次进程切换,因为rcu临界区不允许进程切换,进程切换之后在进入临界区将引用新指针。

老指针不能马上释放的原因:当cpu0的一个读进程进入rcu临界区并获得共享数据区的指针后,在其还没来得及引用该指针时,cpu1的一个写进程更新指向共享数据区的指针,cpu0上的杜金城将引用老指针。

原创粉丝点击