本文共 4007 字,大约阅读时间需要 13 分钟。
a 初始化 为 0A 内核进程 对 变量 a 加1B 内核进程 对 变量 a 加1a 现在 为 2下面的顺序不会有问题A load 内存中的a 进 寄存器A 寄存器 自加 1A store 寄存器中的值 进 内存B load 内存中的a 进 寄存器B 寄存器 自加 1B store 寄存器中的值 进 内存此时a的值为2下面的顺序会有问题A load 内存中的a 进 寄存器1A 寄存器1 自加 1B load 内存中的a 进 寄存器2B 寄存器2 自加 1A store 寄存器1中的值 进 内存B store 寄存器2中的值 进 内存此时a的值为1 // 此时发生了竞态 : 意想不到的事件// 此时 a 被 两个内核线程访问,a叫做共享资源// 此时 "A 内核进程 对 变量 a 加1" 这个动作相关的代码叫做临界区
-----------------------------------------竞态由于 中断 调度 的存在, 会存在 伪并发由于 SMP 的存在, 会存在 真并发解决 中断 矛盾问题引入了软中断,也就引入了 软中断 产生的竞态在 __irq_svc 返回时调度, 也就引入了内核抢占的 竞态总之, 竞态原因有以下五种 中断 调度 SMP 软中断 内核抢占对于内核代码(驱动,内核线程,中断,其他内核模块)来说(因为地址空间相同),存在的并发原因有五种 中断 调度 SMP 软中断 内核抢占
中断处理程序可以打断软中断,tasklet和进程上下文的执行软中断和tasklet之间不会并发,但可以打断进程上下文的执行在支持抢占的内核中,进程上下文之间会产生并发在不支持抢占的内核中,进程上下文之间不会产生并发
同一类型的中断处理程序不会并发,但是不同类型的中断有可能被送到不同的cpu上,因此不同类型的中断处理程序可能存在并发执行同一类型的软中断会在不同的cpu上并发执行同一类型的tasklet是串行执行的,不会再多个cpu上并发不同cpu上的进程上下文会并发
静态局部变量全局变量共享的数据结构缓存链表红黑树
对于应用代码来说,存在的并发原因有一种(对 进程地址空间中的内存相同部分 进行并发访问),这时候我们不对 并发原因(并发原因只有调度)分类,而是对(共享资源)分类,共享资源分为多种 多线程代码中的所有变量 多进程代码中的共享内存中的变量
解决竞态的方案 叫做 同步所有的 同步 都是基于原子操作,是芯片提供的指令,不是软件做的.软件做的是在原子操作的封装同步根据 竞态原因 的不同, 而 分为多种
// 涉及同步时,指令重排可能会带来问题,如果放在同步原语之后的指令在同步原语之前被执行了,就可能会出问题我们可以用不同的 同步 方法 围住 共享资源 保证 在 某个情景下 不会产生竞态虽然在代码上,我们的同步方法围住了共享资源, 但是 却 不能保证 共享资源 一定会被 同步 方法 围住.因为存在着cpu重排序指令这个问题这个问题主要分为以下几种: // https://blog.csdn.net/Roland_Sun/article/details/106895899 1.编译器编译时的优化 2.处理器执行时的多发射和乱序优化 3.读取和存储指令的优化 4.缓存同步顺序(导致可见性问题)而cpu 重排序指令的 解决方案 为 内存屏障原语,在外表现为多种形式 1. volatile 关键字 // 被volatile修饰的变量在编译成字节码文件时会多个lock指令,该指令在执行过程中会生成相应的内存屏障,以此来解决可见性跟重排序的问题 2. barrier() // 解决编译器乱序 3. mb/rmb/wmb/smp_mb/smp_rmb/smp_wmb // 解决运行时乱序volatile 关键字使用的是Lock指令,volatile的作用取决于Lock指令。 // ??? TODO volatile 的实现 // volatile 与 屏障 的区别 Lock是软件指令 Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线和高速缓存加锁,可以理解为CPU指令级的一种锁。类似于Lock指令。 volatile的变量在进行写操作时,会在前面加上lock质量前缀。 Lock前缀,Lock不是一种内存屏障,但是它能完成类似内存屏障的功能。Lock会对CPU总线和高速缓存加锁,可以理解为CPU指令级的一种锁。 而Lock前缀是这样实现的 它先对总线/缓存加锁,然后执行后面的指令,最后释放锁后会把高速缓存中的脏数据全部刷新回主内存。 在Lock锁住总线的时候,其他CPU的读写请求都会被阻塞,直到锁释放。Lock后的写操作会让其他CPU相关的cache失效,从而从新从内存加载最新的数据,这个是通过缓存一致性协议做的。 lock前缀指令相当于一个内存屏障(也称内存栅栏)(既不是Lock中使用了内存屏障,也不是内存屏障使用了Lock指令),内存屏障主要提供3个功能: 确保指令重排序时不会把其后面的指令排到内存屏障之前的位置,也不会把前面的指令排到内存屏障的后面;即在执行到内存屏障这句指令时,在它前面的操作已经全部完成; 强制将对缓存的修改操作立即写入主存,利用缓存一致性机制,并且缓存一致性机制会阻止同时修改由两个以上CPU缓存的内存区域数据; 如果是写操作,它会导致其他CPU中对应的缓存行无效。内存屏障是CPU指令
swap and swap byte instructions 在ARMv6中不推荐使用,建议所有软件都迁移到使用新的同步原语ARMv6提供了一种新的机制来支持更全面的非阻塞共享内存同步原语,这种原语可以扩展到多处理器系统设计中 Load-Exclusive : LDREX Store-Exclusive : STREX
不同的同步机制 都是 以 同步原语(Synchronization primitives) 和 屏障 为基础的针对不同的 竞态原因,有不同的同步方法 // 也就是说,如果你要怕因为中断产生竞态,那么就关中断 // 但是该技术(关中断)不会解决 其他竞态原因 产生的竞态 per-cpu 变量 atomic bit 64位 变量 禁中断/中断屏蔽 禁抢占 禁软中断 自旋锁 读写锁 顺序锁 信号量(count初始化为1)/读写信号量 互斥锁 RCU 大内核锁BLK
解决竞态的方案(同步) 是 为了 不让 并发(真并发和伪并发),保证数据的一致性而 事件同步 是为了 两个事件必须有先后顺序(叫做两个同步事件)不相干的事件 不需要 有先后顺序而 不管 两个事件(A B)要不要先后顺序,都需要 做 解决竞态的方案(同步) // 并发可能是 A与C B与C 的并发设计 让 两个事件同步 的 机制 有 1. 等待一段时间 // sleep 2. 等待事件完成或条件满足 // 信号量(count初始化为0) 和 等待队列 和 completion
// 其实 异步事件 的使用场景 和 同步事件 的 使用场景 没有任何关系 ,没有可比性// 这里 只是 其分类为 同步和异步事件同步,某事件等待另一件事件结束事件异步,某事件不等待另一事件结束A 等待 B 结束 1. A 等待一段时间(sleep并调出) 等到 硬件复位(B) // sleep 2. A 调用 poll(wait并调出) 等待事件完成或条件满足(数据到来)(B) // 等待队列A 不等待 B 结束 1. A 调用 提交任务给 tasklet/工作队列 ,A 继续执行 . B在一个时机处理 任务 // tasklet/软中断/工作队列 2. A 调用 设置回调 给 timer/hrtimer ,A 继续执行 . 定时器到了,B调用回调 // 定时器timer/定时器hrtimer
同步 : 确定会会断在哪条指令上异步 : 不确定会会断在哪条指令上
进程A的一个IO请求分为两个过程 1.等待数据准备就绪 2.将数据从内核态拷贝到用户空间阻塞IO与非阻塞IO的定义 第一个过程阻塞/非阻塞(A)对应 阻塞IO/非阻塞IO(B)同步IO与异步IO的定义 第二个过程阻塞/非阻塞(A)对应 同步IO/异步IO(非同步)read 非阻塞 第一个过程 阻塞 第二个过程 阻塞 第一个过程使用的 api read 不阻塞(此时说的是read的等待数据准备好) 第二个过程使用的 api read 阻塞(此时说的是read的拷贝数据)read 阻塞 第一个过程 阻塞 第二个过程 阻塞 第一个过程使用的 api read 阻塞(此时说的是read的等待数据准备好) 第二个过程使用的 api read 阻塞(此时说的是read的拷贝数据)select 第一个过程 阻塞 第二个过程 阻塞 第一个过程使用的 api select 阻塞(此时说的是select的等待数据准备好) 第二个过程使用的 api read 阻塞(此时说的是read的拷贝数据,不是read的等待数据准备好)