日期:2014-05-16  浏览次数:20769 次

《Linux内核设计与实现》ch09

《Linux内核设计与实现》ch09
2010年11月01日
  内核同步方法
  Linux提供了一组相当完备的同步方法。
  9.1原子操作
  保证指令以原子方式执行,即执行过程不被打断。
  内核提供了两组原子操作接口,一组针对整数进行操作,另一组针对单独的位进行操作。在Linux支持的所有体系结构上都实现了这两组接口。大多数体系结构要么本来就支持简单的原子操作,要么就为单步执行提供了锁内存总线的指令。
  9.1.1 原子整数操作
  针对整数的原子操作只能对atomic_t类型的数据进行处理。
  在这里之所以引入了一个特殊数据类型,而没有直接使用C语言的int类型,主要出于以下原因:
  1.让原子函数只接受atomic_t类型的操作数,可以确保原子操作数只与这种特殊类型数据一起使用。同时也保证了该类型数据不会被传递给其它任何非原子函数;
  2.使用atomic_t类型确保编译器不对相应的值访问优化--这点使得原子操作最终接收到正确的内存地址,而不只是一个别名;
  3.在不同结构体上实现原子操作的时候,使用atomic_t可以屏蔽其间的差异。
  使用原子整型操作需要的声明都在文件中。有些体系结构会提供一些只能在该体系结构上使用的额外原子操作方法,但所有的体系结构都能保证内核使用到的所有操作的最小集。
  定义一个atomic_t类型的数据:
  atomic_t   v;         /* 定义 v */
  atomic_t u = ATOMIC_INIT(0);   /*定义u并初始化为0 */
  操作也很简单:
  atomic_set(&v,4);      /* v=4(原子的) */
  atomic_add(2, &v)    /* v = v+2 = 6 (原子的) */
  atomic_inc(&v)        /* v=v+1=7(原子的)   */
  当需要把atomic_t转换成int型时,可以使用atomic_read()来完成:
  printk("%d\n", atomic_read(&v));              /* 将打印 "7" */
  原子整数操作最常见的用途就是实现计数器,atomic_inc()和atomic_dec()。
  原子操作通常都是内联函数(http://blog.csdn.net/northplayboy/archive/2005/12/ 12/550413.aspx),往往是通过内嵌汇编指令来实现的。
  原子性确保指令执行期间不被打断,要么全部执行,要么根本不执行。
  顺序性确保即使两条或多条指令出现在独立的执行线程甚至是独立的处理器上,它们仍然要保持特定的执行顺序。
  9.1.2原子位操作
  除了原子整数操作外,内核还提供了一组针对位这一级数据进行操作的函数。它们是与体系结构相关的操作,定义在中。
  位操作函数是对普通的内存地址进行操作的。它的参数是一个指针和一个位号。
  unsigned long word = 0;
  set_bit(0, &word);          /* 第0位被设置 */
  set_bit(1, &word);
  printk("%ul\n" , word); /* 打印3 */
  非原子位操作的意义?
  例如:假定给出2个原子位操作:先对某位置1,然后清0。如果没有原子操作,那么这一位可能的确清0了,但可能根本没有置位。置位操作可能与清除操作同时发生,但没成功。原子操作能保证置位真正发生。
  9.2自旋锁
  Linux中最常见的锁是自旋锁(spin lock)。自旋锁最多只能被一个可执行线程持有。如果一个线程试图获得一个被争用的自旋锁,那么该线程会一直忙循环(特别浪费处理器时间),等待锁重新可用,所以自旋锁不应该被长时间持有。这也是自旋锁的初衷:在短期内进行轻量级加锁。
  另外,还可以采用别的方式处理对锁的争用:让请求线程睡眠,直到重新可用时再唤醒它。这样处理器就不必等待,可以去执行其他代码。但这会带来一定的开销(2次线程切换)。
  自旋锁的实现和体系结构密切相关,代码往往通过汇编实现。这些与体系结构相关的代码定义在文件中,实际上需要用到的接口定义在文件中。基本使用如下:
  spinlock_t   mr_lock = SPIN_LOCK_UNLOCKED;
  spin_lock(&mr_lock);
  /* 临界区 */
  spin_unlock(&mr_lock);
  警告:Linux内核实现的自旋锁是不可递归的,这点不同于自旋锁在其他操作系统上的实现。 
  自旋锁可以用在中断处理程序中。此时,一定要先禁止本地(当前处理器)中断。否则可能出现:中断处理程序打断正持有锁的内核代码,并争用锁,而持有锁者在等待中断程序结束,发生死锁。
  内核为我们提供了禁止中断同时申请锁的接口,使用起来很方便:
  spinlock_t   mr_lock = SPIN_LOCK_UNLOCKED;
  unsigned   long   flags;
  spin_lock_irqsave(&mr_lock, flags);
  /* 临界区   */
  spin_unlock_irqrestore(&mr_lock, flags);
  9.2.1其他对自旋锁的操作
  spin_lock()                     获取指定的自旋锁
  spin_lock_irq()                禁止本地中断并获取指定的锁(不提倡使用)
  spin_lock_irqsave()          保存本地中断的当前状态,禁止本地中断,并获取指定的锁
  spin_unlock()                  释放指定的锁
  spin_unlock_irq()             释放指定的锁,并激活本地中断
  spin_unlock_irqrestore      释放指定的锁,并让本地中断恢复到以前的状态
  spin_lock_init()                 动态初始化指定的spinlock_t
  spin_try_lock()                 试图获得锁,未获得返回非0
  spin_is_locked()