《Linux Device Drivers》第五章 并发和竞态——note

  • 并发及其管理

    • 竞态通常作为对资源的共享访问结果而产生
    • 当两个执行线程需要访问相同的数据结构(或硬件资源)时,并发的可能性就永远存在
    • 只要可能就应该避免资源的共享,但共享通常是必须的,硬件本质上就是共享的
    • 访问管理的常见技术称为“锁定”或者“互斥”
  • 信号量和互斥体
    • 建立临界区:在任意给定的时刻,代码只能被一个线程执行
    • 可以使用一种锁定机制,当进程在等待对临界区的访问时,此机制可让进程进入休眠状态
    • 一个信号量本质上是一个整数值,它和一对函数联合使用,这一对函数通常称为P和V
    • 当信号量用于互斥时,信号量的值应初始化为1,这种信号量有时也称为“互斥体(mutex)”
    • Linux信号量的实现
      • <asm/semaphore.h>
      • struct semaphore;
      • void sema_init(struct semaphore *sem, int val);
      • DECLARE_MUTEX(name);
        • 称为name的信号量变量被初始化为1
      • DECLARE_MUTEX_LOCKED(name);
        • 称为name的信号量变量被初始化为0
      • void init_MUTEX(struct semaphore *sem);
      • void init_MUTEX_LOCKED(struct semaphore *sem);
      • void down(struct semaphore *sem);
        • 一直等待
      • int down_interruptible(struct semaphore *sem);
        • 操作是可中断的
        • 如果操作被中断,该函数会返回非零值
        • 通常使用的是可中断的down版本
      • int down_trylock(struct semaphore *sem);
        • 永远不会休眠
        • 如果信号量在调用时不可获得,会立即返回一个非零值
      • void up(struct semaphore *sem);
    • 读取者/写入者信号量
      • 一些任务只需要读取受保护的数据结构,而其他的则必须做出修改
      • <linux/rwsem.h>
      • struct rw_semaphore;
      • void init_rwsem(struct rw_semaphore *sem);
      • void down_read(struct rw_semaphore *sem);
        • 只读访问,可和其他读取者并发地访问
      • int down_read_trylock(struct rw_semaphore *sem);
        • 在授予访问时返回非零,其他情况下返回零
      • void up_read(struct rw_semaphore *sem);
      • void down_write(struct rw_semaphore *sem);
      • int down_write_trylock(struct rw_semaphore *sem);
      • void up_write(struct rw_semaphore *sem);
      • void downgrade_write(struct rw_semaphore *sem);
      • 最好在很少需要写访问且写入者只会短期拥有信号量的时候使用rwsem
  • completion

    • 内核编程中常见的一种模式是,在当前线程之外初始化某个活动,然后等待该活动的结束
    • <linux/completion.h>
    • DECLARE_COMPLETION(my_completion);
    • init_completion(struct completion *c);
    • void wait_for_completion(struct completion *c);
      • 非中断的等待
    • void complete(struct completion *c);
    • void complete_all(struct completion *c);
    • 一个completion通常是一个单次(one-shot)设备
    • 如果没有使用complete_all,可以重复使用一个complete结构
    • 如果使用了complete_all,则必须在重复使用该结构之前重新初始化它
    • INIT_COMPLETE(struct completion c);
    • void complete_and_exit(struct completion *c, long retval);
  • 自旋锁
    • 自旋锁可在不能休眠的代码中使用,比如中断处理例程
    • 可提供比信号量更高的性能
    • 一个自旋锁是一个互斥设备,它只能有两个值:锁定和解锁
    • 通常实现为某个整数值中的单个位
    • 如果锁可用,则“锁定”位被设置,而代码继续进入临界区
    • 如果锁被其他人获得,则代码进入忙循环并重复检查这个锁,直到该锁可用为止,这个循环就是自旋锁的“自旋”部分
    • “测试并设置”的操作必须以原子方式完成
    • 在超线程处理器上,还必须仔细处理以避免死锁,超线程处理器可实现多个虚拟的CPU,它们共享单个处理器核心及缓存
    • 自旋锁API介绍
      • <linux/spinlock.h>
      • spinlock_t
      • spinlock_t my_lock = SPIN_LOCK_UNLOCKED;
      • void spin_lock_init(spinlock_t *lock);
      • void spin_lock(spinlock_t *lock);
        • 不可中断
        • 在获得锁之前一直处于自旋状态
      • void spin_unlock(spinlock_t *lock);
    • 自旋锁和原子上下文
      • 任何拥有自旋锁的代码都必须是原子的,不能休眠,不能因为任何原因放弃处理器,除了服务中断之外
    • 自旋锁函数
      • void spin_lock(spinlock_t *lock);
      • void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
        • 在获得自旋锁之前禁止中断,而先前的中断状态保存在flags中
      • void spin_lock_irq(spinlock_t *lock);
      • void spin_lock_bh(spinlock_t *lock);
        • 在获得锁之前禁止软件中断
      • void spin_unlock(spinlock_t *lock);
      • void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
        • flags参数必须是传递给spin_lock_irqsave的同一个变量
      • void spin_unlock_irq(spinlock_t *lock);
      • void spin_unlock_bh(spinlock_t *lock);
      • int spin_trylock(spinlock_t *lock);
        • 成功时返回非零值,否则返回零
      • int spin_trylock_bh(spinlock_t *lock);
        • 成功时返回非零值,否则返回零
    • 读取值/写入者自旋锁
      • <linux/spinlock.h>
      • rwlock_t
      • rwlock_t my_rwlock = RW_LOCK_UNLOCKED;
      • void rwlock_init(rwlock_t * lock);
      • void read_lock(rwlock_t *lock);
      • void read_lock_irqsave(rwlock_t *lock, unsigned long flags);
      • void read_lock_irq(rwlock_t *lock);
      • void read_lock_bh(rwlock_t *lock);
      • void read_unlock(rwlock_t *lock);
      • void read_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
      • void read_unlock_irq(rwlock_t *lock);
      • void read_unlock_bh(rwlock_t *lock);
      • void write_lock(rwlock_t *lock);
      • void write_lock_irqsave(rwlock_t *lock, unsigned long flags);
      • void write_lock_irq(rwlock_t *lock);
      • void write_lock_bh(rwlock_t *lock);
      • int write_trylock(rwlock_t *lock);
      • void write_unlock(rwlock_t *lock);
      • void write_unlock_irqrestore(rwlock_t *lock, unsigned long flags);
      • void write_unlock_irq(rwlock_t *lock);
      • void write_unlock_bh(rwlock_t *lock);
      • 读取者/写入者锁可能造成读取者饥饿
  • 锁陷阱
    • 不明确的规则

      • 不论是信号量还是自旋锁,都不允许锁拥有者第二次获得这个相同的锁,如果试图这样做,系统将挂起
    • 锁的顺序规则
      • 在必须获取多个锁时,应该始终以相同的顺序获得
      • 如果必须获得一个局部锁以及一个属于内核更中心位置的锁,则应该首先获取自己的局部锁
      • 如果拥有信号量和自旋锁的组合,则必须首先获得信号量
    • 细粒度锁和粗粒度锁的对比
      • 细粒度锁具有良好的伸缩性
      • 细粒度锁将带来某种程序的复杂性
      • 应该在最初使用粗粒度的锁
      • 使用lockmeter工具可度量内核花费在锁上的时间
        • http://oss.sgi.com/projects/lockmeter/
  • 除了锁之外的方法
    • 免锁算法

      • 经常用于免锁的生产者/消费者任务的数据结构之一是循环缓冲区
    • 原子变量
      • <asm/atomic.h>
      • atomic_t
      • 一个atomic_t变量保存一个int值,但不能记录大于24位的整数
      • void atomic_set(atomic_t *v, int i);
      • atomic_t v = ATOMIC_INIT(0);
      • int atomic_read(atomic_t *v);
      • void atomic_add(int i, atomic_t *v);
      • void atomic_sub(int i, atomic_t *v);
      • void atomic_inc(atomic_t *v);
      • void atomic_dec(atomic_t *v);
      • int atomic_inc_and_test(atomic_t *v);
      • int atomic_dec_and_test(atomic_t *v);
      • int atomic_sub_and_test(int i, atomic_t *v);
      • int atomic_add_negative(int i, atomic_t *v);
      • int atomic_add_return(int i, atomic_t *v);
      • int atomic_sub_return(int i, atomic_t *v);
      • int atomic_inc_return(atomic_t *v);
      • int atomic_dec_return(atomic_t *v);
      • 需要多个atomic_t变量的操作,仍然需要某种类型的锁
    • 位操作
      • <asm/bitops.h>
      • nr参数通常被定义为int,但在少数架构上被定义为unsigned long
      • void set_bit(nr, void *addr);
      • void clear_bit(nr, void *addr);
      • void change_bit(nr, void *addr);
      • test_bit(nr, void *addr);
      • int test_and_set_bit(nr, void *addr);
      • int test_add_clear_bit(nr, void *addr);
      • int test_and_change_bit(nr, void *addr);
    • seqlock
      • 允许读取者对资源的自由访问,但需要读取者检查是否和写入者发生冲突
      • <linux/seqlock.h>
      • seqlock_t
      • seqlock_t lock1 = SEQLOCK_UNLOCKED;
      • void seqlock_init(seqlock_t *lock);
      • unsigned int read_seqbegin(seqlock_t *lock);
      • int read_seqretry(seqlock_t *lock, unsigned int seq);
      • unsigned int read_seqbegin_irqsave(seqlock_t *lock, unsigned long flags);
      • int read_seqretry_irqrestore(seqlock_t *lock, unsigned int seq, unsigned long flags);
      • void write_seqlock(seqlock_t *lock);
      • void write_sequnlock(seqlock_t *lock);
      • void write_seqlock_irqsave(seqlock_t *lock, unsigned long flags);
      • void write_seqlock_irq(seqlock_t *lock);
      • void write_seqlock_bh(seqlock_t *lock);
      • void write_sequnlock_irqrestore(seqlock_t *lock, unsigned long flags);
      • void write_sequnlock_irq(seqlock_t *lock);
      • void write_sequnlock_bh(seqlock_t *lock);
    • 读取-复制-更新
      • read-copy-update(RCU)也是一种高级的互斥机制
      • 很少在驱动程序中使用
      • http://www.rdrop.com/users/paulmck/rclock/intro/rclock_intro.html
      • 针对经常发生读取而很少写入的情形做了优化
      • 被保护的资源应该通过指针访问
      • 在需要修改该数据结构时,写入线程首先复制,然后修改副本,之后用新的版本替代相关指针。当确信老的版本没有其他引用时,就可释放老的版本
      • <linux/rcupdate.h>
      • rcu_read_lock
      • rcu_read_unlock
      • void call_rcu(struct rcu_head *head, void (*func)(void *arg), void *arg);
时间: 2024-12-11 08:38:21

《Linux Device Drivers》第五章 并发和竞态——note的相关文章

Linux内核分析(七)----并发与竞态

Linux内核分析(七) 这两天家里的事好多,我们今天继续接着上一次的内容学习,上次我们完善了字符设备控制方法,并深入分析了系统调用的实质,今天我们主要来了解一下并发和竞态. 今天我们会分析到以下内容: 1.      并发和竞态简介 2.      竞态解决办法 3.      为我们的虚拟设备增加并发控制 在前几次博文我们已经实现了简单的字符设备,看似完美但我们忽视了一个很严重的问题,即并发问题,那么什么是并发,又如何解决并发呢,我们下面进行分析. l  并发和竞态简介 1.       并

《Linux Device Drivers》 第九章 与硬件通信

概述,本章正式接触真正的硬件. 将介绍对设备提供I/O访问的方法和函数,用于从设备读取数据或将数据写入到设备 并详细说明I/O端口和I/O内存 I/O端口和I/O内存 每种外设都通过读写寄存器进行控制 在硬件层,内存区域和I/O区域没有概念上的区别:它们都通过向地址总线和控制总线发送电平信号进行访问,再通过数据总线读写数据 I/O寄存器和常规内存 I/O寄存器和RAM的最主要区别就是I/O操作具有边际效应,而内存操作则没有 内存写操作的唯一结果就是在指定位置存储一个数值 内存读操作则仅仅返回指定

linux device drivers - debugging之proc

在书籍"linux device drivers"的第四章,专门介绍驱动开发中的debugging技术. printk只是其中一种技术,这种技术要求printk打印消息,并且会写入到磁盘里的文件中,这会拖慢整个代码的执行速度. 还有其中的debugging技术,并且对代码执行速度的影响比prink小. 其中讲到了,我们可以让驱动程序在proc文件系统里面创建文件,并且将需要的debugging信息写入到这个文件里面. linux内核提供了相应的API,来和proc文件系统交互,例如创建

LINUX设备驱动程序笔记(四)并发和竞态

       <一>.并发及其管理 大部分竞态可通过使用内核的并发控制原语,并应用几个基本的原理来避免.第一个规则是,只要可能,就应该避免资源的共享,这种思想的明显应用就是避免使用全局变量.但硬件资源本质上就是共享的,软件资源经常需要对其他执行线程可用.全局变量并不是共享数据的唯一途径,只要我们的代码将一个指针传递给了内核的其他部分,一个新的共享就可能建立.在单个执行线程之外共享硬件或软件资源的任何时候,因为另外一个线程可能产生对该资源的不一致观察,因此必须显示地管理对该资源的访问.访问管理的

Hasen的linux设备驱动开发学习之旅--linux设备驱动中的并发与竞态

/** * Author:hasen * 参考 :<linux设备驱动开发详解> * 简介:android小菜鸟的linux * 设备驱动开发学习之旅 * 主题:linux设备驱动中的并发与竞态 * Date:2014-11-04 */ 1.并发与竞态 并发(concurrency)指的是多个执行单元同时.并行被执行,而并发的执行单元对共享资源(软件上的全 局变量,静态变量等)的访问则很容易导致竞态(race conditions). 主要的竞态发生在以下几种情况: (1)对称多处理(SMP)

LDD3之并发和竞态-completion(完成量)的学习和验证

LDD3之并发和竞态-completion(完成量)的学习和验证 首先说下测试环境: Linux2.6.32.2 Mini2440开发板 一开始难以理解书上的书面语言,这里<linux中同步例子(完成量completion)>举了一个公交车上司机和乘客的例子还不错,转过来: 这是一个公交司机和售票员之间的线程调度,用于理解完成量,完成量是对信号量的一种补充,主要用于多处理器系统上发生的一种微妙竞争.在这里两个线程间同步,只有当售票员把门关了后,司机才能开动车,只有当司机停车后,售票员才能开门.

LDD3阅读笔记之内核中的并发和竞态

内核中的并发和竞态 一般驱动程序运行在内核态中,只能调用内核空间中提供的函数,因此在处理并发和竞态时不能使用用户空间提供的库,如pthread库,内核有着自己的一套内部实现机制. 并发及其管理 现代Linux系统中存在大量的并发来源,因此会导致可能的竞态.SMP系统甚至可能在不同的处理器上同时执行我们的代码.内核代码是可抢占的,因此,我们的驱动程序代码可能在任何时候丢失对处理器的独占,而拥有处理器的进程可能正在调用我们的驱动程序代码.设备中断是异步事件,也会导致代码的并发执行.内核还提供了很多可

《Linux Device Drivers》第十四章 Linux 设备模型

简介 2.6内核的设备模型提供一个对系统结构的一般性抽象描述,用以支持多种不同的任务 电源管理和系统关机 与用户空间通信 热插拔设备 设备类型 对象生命周期 kobject.kset和子系统 kobject是组成设备模型的基本结构 对象的引用计数 sysfs表述 数据结构关联 热插拔事件处理 kobject基础知识 <linux/kobject.h> 嵌入的kobject 内核代码很少去创建一个单独的kobject对象,kobject用于控制对大型域相关对象的访问 kobject的初始化 首先

《Linux Device Drivers》 第七章 时间、延时及延缓操作——note

度量时间差 内核通过定时器中断来跟踪时间流 时钟中断由系统定时硬件以周期性的间隔产生,这个间隔由内核根据HZ的值设定,在常见的x86 PC平台上,默认定义为1000 <linux/param.h> <linux/timex.h> jiffies_64 unsigned long jiffies 使用jiffies计数器 <linux/jiffies.h> int time_after(unsigned long a, unsigned long b); int time