linux内核同步机制,Linux内核同步机制

本文详细介绍了Linux内核中的几种同步机制,包括自旋锁、信号量、读写锁、互斥体、原子操作、顺序锁、RCU、等待队列、完成接口和内存屏障。这些机制用于保证多线程和多处理器环境下的数据一致性,防止并发访问导致的问题。文章还提到了大内核锁(BKL)及其在现代内核中的逐渐淘汰。

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

而关于自旋锁的缺点?这里找到ibm一个文章

信号量(semaphore):通用的 和读写的

相对于自旋锁,它最大的特点就是允许调用它的线程进入睡眠

/* Please don't access any members of this structure directly */

struct semaphore {

raw_spinlock_t        lock;

unsigned int        count;

struct list_head    wait_list;

};

void sema_init(struct semaphore *sem, int val); // val值代表了同时多少个线程可以进入临界区,一般为1 即作为互斥体使用;当然>1 时,并发操作同一资源会引发什么呢?

down_interruptible(struct semaphore *sem); // 获取信号量 ,它是可以中断的。

up(struct semaphore *sem); // 释放信号量,一般配对使用,当然也可以在别的线程里释放它。

读写信号量:rwsem 它和读写自旋锁类似 除了线程可以睡眠

/* the rw-semaphore definition

* - if activity is 0 then there are no active readers or writers

* - if activity is +ve then that is the number of active readers

* - if activity is -1 then there is one active writer

* - if wait_list is not empty, then there are processes waiting for the semaphore

*/

struct rw_semaphore {

__s32            activity;

raw_spinlock_t        wait_lock;

struct list_head    wait_list;

#ifdef CONFIG_DEBUG_LOCK_ALLOC

struct lockdep_map dep_map;

#endif

};

init_rwsem(sem)    ;

// 初始化

down_read(struct rw_semaphore *sem);

// 获取读信号量

up_read(struct rw_semaphore *sem);

//释放读信号量

down_write(struct rw_semaphore *sem);

//获取写信号量

up_write(struct rw_semaphore *sem);

// 释放写信号量

互斥体(mutex):和count=1的信号量几乎没有区别,当然拥有互斥锁的进程总是尽可能的在短时间内释放

struct mutex {

/* 1: unlocked, 0: locked, negative: locked, possible waiters */

atomic_t        count;

spinlock_t        wait_lock;

struct list_head    wait_list;

#if defined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_SMP)

struct task_struct    *owner;

#endif

#ifdef CONFIG_DEBUG_MUTEXES

const char         *name;

void            *magic;

#endif

#ifdef CONFIG_DEBUG_LOCK_ALLOC

struct lockdep_map    dep_map;

#endif

};

mutex_init(mutex);

// init 互斥锁

mutex_lock();

//获取互斥锁,几乎都能获取

mutex_unlock();

//释放互斥锁

原子操作(atomic)(和架构相关,就是多条指令相当于一条指令执行,多用于计数)

组要是在smp上有意义,防止多条指令被多cpu执行。也是为了实现互斥。

顺序锁(sequence)

特点:

和读写自旋锁锁类似,但是它的写不会等待。写的时候持有自旋锁。首先读者的代码应该尽可能短且写者不能频繁获得锁,其次被保护的数据结构不包括被写修改的指针或被读间接引用的指针。当要保护的资源很小很简单,会很频繁被访问并且写入操作很少发生且必须快速时,就可以用seqlock。

Seqlock:

typedef struct {

unsigned sequence;

spinlock_t lock;

} seqlock_t;

seqlock_init(x) / DEFINE_SEQLOCK(x)

// init

write_seqlock(seqlock_t *sl) ;

// 获取写锁

write_sequnlock(seqlock_t *sl);

read_seqbegin 和 read_seqretry 结合使用

//读时如果有写锁,则循环等待直到锁释放.

应用实例drivers/md/md.c

retry:

seq = read_seqbegin(&bb->lock);

memset(bbp, 0xff, PAGE_SIZE);

for (i = 0 ; i < bb->count ; i++) {

u64 internal_bb = p[i];

u64 store_bb = ((BB_OFFSET(internal_bb) << 10)

| BB_LEN(internal_bb));

bbp[i] = cpu_to_le64(store_bb);

}

bb->changed = 0;

if (read_seqretry(&bb->lock, seq))

goto retry;

RCU:read-copy-update

在linux提供的所有内核互斥设施当中属于一种免锁机制。Rcu无需考虑读和写的互斥问题。

它实际上是rwlock的一种优化。读取者不必关心写入者。所以RCU可以让多个读取者与写入者同时工作。写入者的操作比例在10%以上,需要考虑其他互斥方法。并且必须要以指针的方式来访问被保护资源。

Rcu_read_lock //仅仅是关闭抢占

Rcu_read_unlock //打开抢占

Rcu_assign_pointer(ptr,new_ptr)

//等待队列:它并不是一种互斥机制。它辅助comletion。

//它主要用来实现进程的睡眠等待。

//操作接口:wait/ wake_up

struct __wait_queue_head {

spinlock_t lock;

struct list_head task_list;

};

typedef struct __wait_queue_head wait_queue_head_t;

完成接口(completion) :该机制被用来在多个执行路径间作同步使用,即协调多个执行路径的执行顺序。如果没有完成体,则睡眠在wait_list上。这里usb 在提交urb时会用到。

如果驱动程序要在执行后面操作之前等待某个过程的完成,它可以调用wait_for_completion,以要完成的事件为参数:

Completion机制是线程间通信的一种轻量级机制:允许一个线程告诉另一个线程工作已经完成

struct completion {

unsigned int done;

wait_queue_head_t wait;

};

接口:

DECLARE_COMPLETION(x)

// 静态定义completion

init_completion(struct completion *x);

// 动态init

INIT_COMPLETION(x);

// 初始化一个已经使用过的completion

Wait_for_completion(struct completion *x);

complete(struct completion *);

//done +1,唤醒等待的一个。

Complete_all

// 唤醒所有的,一般不会用。

内存屏障

内存屏障主要有:读屏障、写屏障、通用屏障、优化屏障

内存屏障主要解决了两个问题:单处理器下的乱序问题和多处理器下的内存同步问题

编译器优化以保证程序上下文因果关系为前提。

以 读屏障为例,它用于保证读操作有序。屏障之前的读操作一定会先于屏障之后的读操作完成,写操作不受影响,同属于屏障的某一侧的读操作也不受影响。类似的, 写屏障用于限制写操作。而通用屏障则对读写操作都有作用。而优化屏障则用于限制编译器的指令重排,不区分读写。前三种屏障都隐含了优化屏障的功能。比如:

tmp = ttt; *addr = 5; mb(); val = *data;

有了内存屏障就了确保先设置地址端口,再读数据端口。而至于设置地址端口与tmp的赋值孰先孰后,屏障则不做干预。有了内存屏障,就可以在隐式因果关系的场景中,保证因果关系逻辑正确。

在Linux中,优化屏障就是barrier()宏,它展开为asm volatile(“”:::”memory”)

smp_rmb(); // 读屏障

smp_wmb(); //写屏障

smp_mb(); // 通用屏障

Blk:大内核锁

BKL(大内核锁)是一个全局自旋锁,使用它主要是为了方便实现从Linux最初的SMP过度到细粒度加锁机制。它终将退出历史舞台。

BKL的特性:

持有BKL的任务仍然可以睡眠 。因为当任务无法调度时,所加的锁会自动被抛弃;当任务被调度时,锁又会被重新获得。当然,并不是说,当任务持有BKL时,睡眠是安全的,紧急是可以这样做,因为睡眠不会造成任务死锁。

BKL是一种递归锁。一个进程可以多次请求一个锁,并不会像自旋锁那么产生死锁。BKL可以在进程上下文中。

BKL是有害的:

在内核中不鼓励使用BKL。一个执行线程可以递归的请求锁lock_kernel(),但是释放锁时也必须调用同样次数的unlock_kernel()操作,在最后一个解锁操作完成之后,锁才会被释放。BKL在被持有时同样会禁止内核抢占。多数情况下,BKL更像是保护代码而不是保护数据.

备注:单核不可抢占内核 唯一的异步事件就是硬件中断 ,所以想要同步即关闭中断即可。对于单核可抢占和多核可抢占的 ,除了中断 还有进程调度(即优先级高的进程抢占cpu资源),而上述所有这些机制都是为了防止并发。

22/2<12

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值