内核并发来源:

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不保证原子性

  1. 禁止该指令与之前和之后的读和写指令重排序。
  2. 把写缓冲区中的所有数据刷新到内存中。
  3. 使其他处理器里的缓存行无效

  进行写入操作时,会在写后面加上一条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的更多相关文章

  1. Linux并发与同步专题 (4) Mutex互斥量

    关键词:mutex.MCS.OSQ. <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Linux并发与同步 ...

  2. Linux并发与同步专题 (3) 信号量

    关键词:Semaphore.down()/up(). <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Li ...

  3. Linux并发与同步专题 (2)spinlock

    关键词:wfe.FIFO ticket-based.spin_lock/spin_trylock/spin_unlock.spin_lock_irq/spin_lock_bh/spin_lock_ir ...

  4. Linux并发与同步专题

    并发访问:多个内核路径同时访问和操作数据,就有可能发生相互覆盖共享数据的情况,造成被访问数据的不一致. 临界区:访问和操作共享数据的代码段. 并发源:访问临界区的执行线程或代码路径. 在内核中产生并发 ...

  5. C#异步编程(三)内核模式线程同步

    其实,在开发过程中,无论是用户模式的同步构造还是内核模式,都应该尽量避免.因为线程同步都会造成阻塞,这就影响了我们的并发量,也影响整个应用的效率.不过有些情况,我们不得不进行线程同步. 内核模式 wi ...

  6. 关于Web开发里并发、同步、异步以及事件驱动编程的相关技术

    一.开篇语 我的上篇文章<关于如何提供Web服务端并发效率的异步编程技术>又成为了博客园里“编辑推荐”的文章,这是对我写博客很大的鼓励,也许是被推荐的原因很多童鞋在这篇文章里发表了评论,有 ...

  7. Windows API学习---线程与内核对象的同步

    前言 若干种内核对象,包括进程,线程和作业.可以将所有这些内核对象用于同步目的.对于线程同步来说,这些内核对象中的每种对象都可以说是处于已通知或未通知的状态之中.这种状态的切换是由Microsoft为 ...

  8. Java 并发 线程同步

    Java 并发 线程同步 @author ixenos 同步 1.异步线程本身包含了执行时需要的数据和方法,不需要外部提供的资源和方法,在执行时也不关心与其并发执行的其他线程的状态和行为 2.然而,大 ...

  9. Linux并发与同步专题 (1)原子操作和内存屏障

    关键词:. <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Linux并发与同步专题 (3) 信号量> ...

随机推荐

  1. html学习(3)

    为你的网页中添加一些空格 语法:   1 body> 2 <h1>感悟梦想</h1> 3 来源:作文网  作者:为梦想而飞 4 </body> 认识<h ...

  2. Selenium之自动化常遇问题

    1.等待方式的选择 大家都知道Selenium中等待方式有三种,当在页面没有找到定位的元素抛出异常,那么加个等待,还有问题就换个等待方式 强制等待 time.sleep(10) 显式等待 driver ...

  3. 【UR #13】Yist

    UOJ小清新题表 题目摘要 UOJ链接 给出一个排列 \(A\) 以及它的一个非空子序列 \(B\),给出一个 \(x\) 并进行若干次操作,每一次操作需要在 \(A\) 中选择一个长度恰好为 \(x ...

  4. utf-8和utf-8-sig的区别

    前言:在写入csv文件中,出现了乱码的问题. 解决:utf-8 改为utf-8-sig 区别如下: 1."utf-8" 是以字节为编码单元,它的字节顺序在所有系统中都是一样的,没有 ...

  5. python爬取知乎评论

    点击评论,出现异步加载的请求 import json import requests from lxml import etree from time import sleep url = " ...

  6. 算法初步(julyedu网课整理)

    date: 2018-11-19 13:41:29 updated: 2018-11-19 14:31:04 算法初步(julyedu网课整理) 1 O(1) 基本运算 O(logn) 二分查找 分治 ...

  7. 【应用服务 App Service】App Service中上传文件/图片(> 2M)后就出现500错误(Maximum request length exceeded).

    问题描述 在使用App Service (Windows)做文件/图片上传时候,时常遇见上传大文件时候出现错误,这是因为IIS对文件的大小由默认限制.当遇见(Maximum request lengt ...

  8. Linux系统搭建Hadoop集群

    一.环境说明 IP地址 主机名 备注 操作系统 192.168.92.11 hserver1 namenode Ubuntu 16.04 192.168.92.12 hserver2 datanode ...

  9. 【总结】springmvc

    一.springmvc 1.基本概念 springmvc属于三层架构(表现层,业务层,持久层)的表现层.mvc指model,view,controller.Model(模型) : 通常指的是数据模型 ...

  10. 自己动手实现一个简单的 IOC容器

    控制反转,即Inversion of Control(IoC),是面向对象中的一种设计原则,可以用有效降低架构代码的耦合度,从对象调用者角度又叫做依赖注入,即Dependency Injection( ...