linx 内核 并发与同步 1
内核并发来源:
1、硬件中断和异常:中断服务程序和被中断的进程可能发生并发访问资源
2、软中断和tasklet,软中断和taklet随时都可能倍调度执行,从而打断当前正在执行
进程的上下文。
3、内核抢占:调度器支持可抢占性,会导致进程和进程之间的并发访问。
4、多处理器并发执行,多处理器上可以同时运行多个进程
单处理器:
1、硬中断服务程序可以打断软中断、taklet以及进程上下文
2、软中断和tasklet之间不会并发,但可以打断进程上下文
3、支持抢占式的内核中,进程上下文会并发
4、不支持抢占的内核中,进程上下文不会发生并发。
注意:tasklet 也是一种软中断
多处理器
1、同一类型的中断服务程序不会并发,但是不同类型的中断可能到达不同的cpu上,
所以不同类型的中断服务程序之间可能存在并发执行
2、同一类型的软中断会在不同的cpu上并发执行
3、同一类型的tasklet是串行执行的,不会在多个cpu上并发
4、不同cpu 上的进程上下文会并发
临界区:访问和操作共享数据的代码段
编写代码的要点是:保护资源或者数据,而不是保护代码
比如:静态局部变量、全局变量、共享的内存、数据结构、buffer、链表等
写内核代码时需要作出一些思考:
1、除了当前内核代码路径外是否还有其他内核路径代码会访问它?比如中断服务程序、
工作者worker处理程序、tasklet 处理程序、软中断
2、当前内核代码访问该资源时发生被抢占,被调度执行的进程会不会访问该数据
3、进程会不会睡眠阻塞等待该资源。
linux 内核提供了多种并发访问的保护机制。
1、原子操作和内存屏障
//x86 平台
static inline void atomic_add(int i, atomic_t *v)
{
asm volatile(LOCK_PREFIX "addl %1,%0"
: "+m" (v->counter)
: "ir" (i));
} static inline void atomic_sub(int i, atomic_t *v)
{
asm volatile(LOCK_PREFIX "subl %1,%0"
: "+m" (v->counter)
: "ir" (i));
}
原子操作的实现:使用了volatile 关键字 还有LOCK_PREFIX (不知道啥:貌似是 lock 锁住总线防止smp 存在访问??)
内存屏障:
解决啥问题?
是cpu指令,
- 保证指令执行的顺序,内存屏障前的指令一定先于内存屏障后的指令,因为cpu和编译器会进行优化而导致指令重排列,单线程情况下,没什么影响,而多线程时,会发生与我们代码执行顺序不一样的结果
- 将write buffer的缓存行,立即刷新到内存中
volatile作用:(#lock前缀)volatile不保证原子性
- 禁止该指令与之前和之后的读和写指令重排序。
- 把写缓冲区中的所有数据刷新到内存中。
- 使其他处理器里的缓存行无效
进行写入操作时,会在写后面加上一条store指令,将本地内存中的共享变量值立即刷新到主存。
在进行读操作时,会在读前面加上一条load指令,从主存中读取变量。
spinlock:
自旋锁是一种非阻塞锁,如果某线程需要获取自旋锁,但该锁已经被其他线程占用时,该线程不会被挂起,而是在不断的消耗CPU的时间,不停的试图获取自旋锁。
typedef struct spinlock {
union {
struct raw_spinlock rlock; #ifdef CONFIG_DEBUG_LOCK_ALLOC
# define LOCK_PADSIZE (offsetof(struct raw_spinlock, dep_map))
struct {
u8 __padding[LOCK_PADSIZE];
struct lockdep_map dep_map;
};
#endif
};
} spinlock_t;
typedef struct raw_spinlock {
arch_spinlock_t raw_lock;
#ifdef CONFIG_GENERIC_LOCKBREAK
unsigned int break_lock;
#endif
#ifdef CONFIG_DEBUG_SPINLOCK
unsigned int magic, owner_cpu;
void *owner;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
struct lockdep_map dep_map;
#endif
} raw_spinlock_t;
//arm 架构下
typedef struct {
union {
u32 slock;
struct __raw_tickets {
#ifdef __ARMEB__
u16 next;
u16 owner;
#else
u16 owner;
u16 next;
#endif
} tickets;
};
} arch_spinlock_t;
static __always_inline void spin_lock(spinlock_t *lock)
{
raw_spin_lock(&lock->rlock);
}
#define raw_spin_lock(lock) _raw_spin_lock(lock) static inline void __raw_spin_lock(raw_spinlock_t *lock)
{
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
spink_lock 首先做的事是:关闭内核抢占,为什么?--->1、如果sinlock临界区允许抢占,临界区发生中断,中断返回时回去检查抢占调度,那么 会导致抢占调度相当于持有锁的进程睡眠,违背了spinlock锁不能休眠和快速完成的设计语义。2、抢占调度进程也可能回去申请spinlock,这样也会导致死锁。
如果没有打开lockdep 和lock_stat 那么spin_acpuire就是空函数;LOCK_CONTENDED()
只是调用do_raw_spin_lock函数,
https://blog.csdn.net/chenpuo/article/details/78297902
static inline void do_raw_spin_lock(raw_spinlock_t *lock) __acquires(lock)
{
__acquire(lock);
arch_spin_lock(&lock->raw_lock);
}
static inline void arch_spin_lock(arch_spinlock_t *lock)
{
unsigned long tmp;
u32 newval;
arch_spinlock_t lockval; prefetchw(&lock->slock);
__asm__ __volatile__(
"1: ldrex %0, [%3]\n"
" add %1, %0, %4\n"
" strex %2, %1, [%3]\n"
" teq %2, #0\n"
" bne 1b"
: "=&r" (lockval), "=&r" (newval), "=&r" (tmp)
: "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
: "cc"); while (lockval.tickets.next != lockval.tickets.owner) {------ 初始化时lock->tickets.owner、lock->tickets.next
都为0,假设第一次执行arch_spin_lock,lockval = *lock,lock->tickets.next++,lockval.tickets.next
等于lockval.tickets.owner,获取到自旋锁;自旋锁未释放,第二次执行的时候,
lock->tickets.owner = 0, lock->tickets.next = 1,拷贝到lockval后,
lockval.tickets.next != lockval.tickets.owner,会执行wfe等待被自旋锁释放被唤醒,
自旋锁释放时会执行lock->tickets.owner++,lockval.tickets.owner重新赋值
wfe();------ 暂时中断挂起执行,使处理器进入a low-power state等待状态
lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);------ 重新读取lock->tickets.owner
}
smp_mb();
}
嵌入了asm 汇编, __volatile__关键字确保下面代码不被优化改变。
根据嵌入汇编的规则,%0 是lockval ,类型为arch_spinlock_t类型,%1 %2 依次是newval 和tmp,依次类推。
把lock->slock值保存到lock_val
newval=lockval+1<<TICKET_SHIFT TICKET_SHIFT 是 用来表明arch_spinlock_t中owner 和next各自所占多少bit,定义为16则,owner和next各占16bit 加1<<TICKET_SHIFT 就是next++
第3,4行:lock->slock = newval ,判断strex返回值,确认是否成功更新锁,如果更新失败,说明有其他内核路径插入。
ldrex 和strex 用这种方法保证原子性,所以同一时间只会有一个线程能成功更新lock->slock的值。
同样内核中原子操作也是用类似的方法实现。
第5行:如果更新失败回到1行。如果更新成功,到下面c代码
对于单核处理器:
#define _raw_spin_lock(lock) __LOCK(lock) #define __LOCK(lock) \
do { preempt_disable(); ___LOCK(lock); } while (0)
#define ___LOCK(lock) \
do { __acquire(lock); (void)(lock); } while (0) #ifdef __CHECKER__
……
# define __acquire(x) __context__(x,1)
# define __release(x) __context__(x,-1)
#else
……
# define __acquires(x)
# define __releases(x)
对于单核处理器紧紧只是禁止抢占而已
问题:当驱动程序调用spinklock去访问某临界资源以及数据时,在处于临界区时发生了外部硬件中断,此时本地cpu 暂停当前进程去执行中断服务程序, 中断服务程序中spinlock也要访问此临界区域,此时就会造成死锁,所以此时不能使用spinklock
为了解决此场景:需要使用带有关闭本地cpu 中断的spin_lock,也就是spin_lock_irq;
static __always_inline void spin_lock_irq(spinlock_t *lock)
{
raw_spin_lock_irq(&lock->rlock);
}
#define raw_spin_lock_irq(lock) _raw_spin_lock_irq(lock)
void __lockfunc _raw_spin_lock_irq(raw_spinlock_t *lock)
{
__raw_spin_lock_irq(lock);
}
static inline void __raw_spin_lock_irq(raw_spinlock_t *lock)
{
local_irq_disable();//关闭本地处理器中断
preempt_disable();
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
spin_lock_irqsave:保存本地cpu当前irq状态并且关闭本地cpu 中断,然后获取锁,
local_irq_save:保存本地cpu当前irq状态并且关闭本地中断,----local_irq_restore()--->
这样是为了防止破坏中断响应的状态
spin_lock_bh:用于处理进程和延时处理机制(中断下半部)并发访问互斥的问题。
spin_lock的缺点:获取锁不公平 性能下降。
只要和其他CPU 互斥,就要用spin_lock/spin_unlock,如果要和irq及其他CPU互斥,就要用 spin_lock_irq/spin_unlock_irq,
如果既要和irq及其他CPU互斥,又要保存 EFLAG的状态,就要用spin_lock_irqsave/spin_unlock_irqrestore,
如果 要和bh及其他CPU互斥,就要用spin_lock_bh/spin_unlock_bh,
static __always_inline void spin_lock_bh(spinlock_t *lock)
{
raw_spin_lock_bh(&lock->rlock);
}
#define raw_spin_lock_bh(lock) _raw_spin_lock_bh(lock)
static inline void __raw_spin_lock_bh(raw_spinlock_t *lock)
{
__local_bh_disable_ip(_RET_IP_, SOFTIRQ_LOCK_OFFSET);//关闭软中断
spin_acquire(&lock->dep_map, 0, 0, _RET_IP_);
LOCK_CONTENDED(lock, do_raw_spin_trylock, do_raw_spin_lock);
}
如果不需要和 其他CPU互斥,只要和irq互斥,则用local_irq_disable/local_irq_enable,
#define local_irq_disable() do { raw_local_irq_disable(); } while (0)
#define raw_local_irq_disable() arch_local_irq_disable()
/*
* Disable IRQs
*/
#define arch_local_irq_disable arch_local_irq_disable
static inline void arch_local_irq_disable(void)
{
unsigned long temp;
asm volatile(
" mrs %0, cpsr @ arch_local_irq_disable\n"
" orr %0, %0, #128\n"
" msr cpsr_c, %0"
: "=r" (temp)
:
: "memory", "cc");
}
如果不需要和其他CPU互斥,只要和bh互斥,则用local_bh_disable/local_bh_enable,
static inline void local_bh_disable(void)
{
__local_bh_disable_ip(_THIS_IP_, SOFTIRQ_DISABLE_OFFSET);
}
1.有些情况下需要在访问共享资源时必须中断失效,而访问完后必须中断使能,这样的情形使用spin_lock_irq和spin_unlock_irq最好;
2.spin_lock_irqsave保存访问共享资源前的中断标志,然后失效中断;spin_unlock_irqrestore将恢复访问共享资源前的中断标志而不是直接使能中断;
3.如果被保护的共享资源只在进程上下文访问和软中断上下文访问,那么当在进程上下文访问共享资源时,可能被软中断打断,从而可能进入软中断上下文来对被保护的共享资源访问,因此对于这种情况,对共享资源的访问必须使用spin_lock_bh和 spin_unlock_bh来保护。当然使用spin_lock_irq和spin_unlock_irq以及spin_lock_irqsave和 spin_unlock_irqrestore也可以,它们失效了本地硬中断,失效硬中断隐式地也失效了软中断。但是使用spin_lock_bh和 spin_unlock_bh是最恰当的,它比其他两个快。如果被保护的共享资源只在进程上下文和tasklet或timer上下文访问,那么应该使用与上面情况相同的获得和释放锁的宏,因为tasklet和timer是用软中断实现的。
4.对tasklet和timer和互斥操作
如果被保护的共享资源只在一个tasklet或timer上下文访问,那么不需要任何自旋锁保护,因为同一个tasklet或timer只能在一个CPU上运行,即使是在SMP环境下也是如此;如果被保护的共享资源只在两个或多个tasklet或timer上下文访问,那么对共享资源的访问仅需要用spin_lock和spin_unlock来保护,不必使用_bh版本,因为当tasklet或timer运行时,不可能有其他tasklet或timer在当前CPU上运行。
5.spin_lock用于阻止在不同CPU上的执行单元对共享资源的同时访问以及不同进程上下文互相抢占导致的对共享资源的非同步访问,而中断失效和软中断失效却是为了阻止在同一CPU上软中断或中断对共享资源的非同步访问
接口API的类型 | spinlock中的定义 | raw_spinlock的定义 |
定义spin lock并初始化 | DEFINE_SPINLOCK | DEFINE_RAW_SPINLOCK |
动态初始化spin lock | spin_lock_init | raw_spin_lock_init |
获取指定的spin lock | spin_lock | raw_spin_lock |
获取指定的spin lock同时disable本CPU中断 | spin_lock_irq | raw_spin_lock_irq |
保存本CPU当前的irq状态,disable本CPU中断并获取指定的spin lock | spin_lock_irqsave | raw_spin_lock_irqsave |
获取指定的spin lock同时disable本CPU的bottom half | spin_lock_bh | raw_spin_lock_bh |
释放指定的spin lock | spin_unlock | raw_spin_unlock |
释放指定的spin lock同时enable本CPU中断 | spin_unlock_irq | raw_spin_unock_irq |
释放指定的spin lock同时恢复本CPU的中断状态 | spin_unlock_irqstore | raw_spin_unlock_irqstore |
获取指定的spin lock同时enable本CPU的bottom half | spin_unlock_bh | raw_spin_unlock_bh |
尝试去获取spin lock,如果失败,不会spin,而是返回非零值 | spin_trylock | raw_spin_trylock |
判断spin lock是否是locked,如果其他的thread已经获取了该lock,那么返回非零值,否则返回0 | spin_is_locked | raw_spin_is_locked |
linx 内核 并发与同步 1的更多相关文章
- Linux并发与同步专题 (4) Mutex互斥量
关键词:mutex.MCS.OSQ. <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Linux并发与同步 ...
- Linux并发与同步专题 (3) 信号量
关键词:Semaphore.down()/up(). <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Li ...
- Linux并发与同步专题 (2)spinlock
关键词:wfe.FIFO ticket-based.spin_lock/spin_trylock/spin_unlock.spin_lock_irq/spin_lock_bh/spin_lock_ir ...
- Linux并发与同步专题
并发访问:多个内核路径同时访问和操作数据,就有可能发生相互覆盖共享数据的情况,造成被访问数据的不一致. 临界区:访问和操作共享数据的代码段. 并发源:访问临界区的执行线程或代码路径. 在内核中产生并发 ...
- C#异步编程(三)内核模式线程同步
其实,在开发过程中,无论是用户模式的同步构造还是内核模式,都应该尽量避免.因为线程同步都会造成阻塞,这就影响了我们的并发量,也影响整个应用的效率.不过有些情况,我们不得不进行线程同步. 内核模式 wi ...
- 关于Web开发里并发、同步、异步以及事件驱动编程的相关技术
一.开篇语 我的上篇文章<关于如何提供Web服务端并发效率的异步编程技术>又成为了博客园里“编辑推荐”的文章,这是对我写博客很大的鼓励,也许是被推荐的原因很多童鞋在这篇文章里发表了评论,有 ...
- Windows API学习---线程与内核对象的同步
前言 若干种内核对象,包括进程,线程和作业.可以将所有这些内核对象用于同步目的.对于线程同步来说,这些内核对象中的每种对象都可以说是处于已通知或未通知的状态之中.这种状态的切换是由Microsoft为 ...
- Java 并发 线程同步
Java 并发 线程同步 @author ixenos 同步 1.异步线程本身包含了执行时需要的数据和方法,不需要外部提供的资源和方法,在执行时也不关心与其并发执行的其他线程的状态和行为 2.然而,大 ...
- Linux并发与同步专题 (1)原子操作和内存屏障
关键词:. <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Linux并发与同步专题 (3) 信号量> ...
随机推荐
- 3.字符设备led驱动
1.硬件原理图 由图可知,led1,led2,led3,led4,分别对应GPB5,GPB6,GPB7,GPB8,由s3c2440芯片手册可得到如下图所示,分别配置GPBCON和GPBDAT即可 2. ...
- C 和 C++ 打起来了!曾今最亲密的伙伴到现今的不爽?
70年代初,贝尔实验室创建了C语言,它是开发UNIX的副产品.很快C就成为了最受欢迎的编程语言之一.但是对于Bjarne Stroustrup来说,C的表达能力还不够.于是,他在1983年的博士论文中 ...
- 【Luogu】P4381 [IOI2008]Island
一.题目 Description 你将要游览一个有N个岛屿的公园.从每一个岛i出发,只建造一座桥.桥的长度以Li表示.公园内总共有N座桥.尽管每座桥由一个岛连到另一个岛,但每座桥均可以双向行走.同时, ...
- VitualBox CentOS增强功能的安装使用 - Linux操作系统
本人因为电脑配置原因,安装的是CentOS 6.6 minimal版本,虚拟环境为VirtualBox 4.3.18. 当我使用的时候,想从本机(WindowXP)电脑将文件共享到虚拟(Cen ...
- spring boot: 设计接口站api的版本号,支持次版本号(spring boot 2.3.2)
一,为什么接口站的api要使用版本号? 1,当服务端接口的功能发生改进后, 客户端如果不更新版本, 则服务端返回的功能可能不能使用, 所以在服务端功能升级后, 客户端也要相应的使用 ...
- jquery1.9+,jquery1.10+ 为什么不支持live方法了?
live() 替换成 on() die() 替换成off() 根据jQuery的官方描述,live方法在1.7中已经不建议使用,在1.9中删除了这个方法.并建议在以后的代码中使用on方法来替代. o ...
- CPU 底层运算之乘法运算
CPU 运算加减法运算 假设计算 3+3 原码是0011 * 0011(以4位存贮单元,因为是原码,最高位不代表符号位) 1. 首先 判断 两个加数是否有 负数(减法) 如果有 负数 先将负数转 ...
- Codeforces Round #678 (Div. 2)
Codeforces Round #678 (Div. 2) A. Reorder 题意:有一个有 n 个数的序列 a ,以及一个数 m ,问能否给序列a重新排序,能够满足式子 $\sum_{i=1} ...
- 拿了十几个offer,怎样做选择?
本文已经收录至我的GitHub,欢迎大家踊跃star 和 issues. https://github.com/midou-tech/articles 最近收到好几个读者的咨询,关于如何选offer的 ...
- Hadoop 指令
date: 2018-04-30 09:07:56 updated: 2018-04-30 09:07:56 1.ls hadoop fs -ls / 列出hdfs文件系统根目录下的目录和文件 had ...