Chinaunix首页 | 论坛 | 博客
  • 博客访问: 307323
  • 博文数量: 56
  • 博客积分: 2058
  • 博客等级: 中尉
  • 技术积分: 688
  • 用 户 组: 普通用户
  • 注册时间: 2011-04-11 09:19
个人简介

code rush

文章分类

全部博文(56)

文章存档

2012年(2)

2011年(54)

分类: LINUX

2011-06-26 01:16:19

对于内核的互斥, semephone 和自旋锁是一个有用的工具, 不象semephone, 自旋锁可用在
不能睡眠的代码中。

自旋锁只能有 2 个值:"上锁"和"解锁". 它常常实现为一个整数值中的一个单个位. 想获取
一个锁的就测试相关的位. 如果锁是可用的, 这个"上锁"位被置位并且代码继续进入临界区.
如果这个锁已经被别人获得, 代码进入一个紧凑的循环中反复检查这个锁, 直到它变为可用.
这个循环就是自旋锁的"自旋"部分.

这个"测试并置位"操作必须以原子方式进行, 以便只有一个线程能够获得锁, 如果有多个进程
在任何给定时间自旋. 必须小心以避免在超线程处理器上死锁 。

如果一个非抢占的单处理器系统进入一个锁上的自旋, 它将永远自旋; 没有其他的线程再能够
获得 CPU 来释放这个锁. 因此, 自旋锁在没有打开抢占的单处理器系统上的操作被优化为
什么不作。


自旋锁原语的包含文件是 . 一个实际的锁有类型 spinlock_t. 
 一个 自旋锁必须初始化. 这个初始化可以在编译时完成, 如下:
spinlock_t my_lock = SPIN_LOCK_UNLOCKED; 

或者在运行时使用:
void spin_lock_init(spinlock_t *lock); 

在进入一个临界区前, 你的代码必须获得需要的lock , 用:
void spin_lock(spinlock_t *lock); 
注意:所有的自旋锁不可中断的. 一旦你调用 spin_lock, 你将自旋直到锁变为可用.

为释放一个你已获得的锁, 传递它给:
void spin_unlock(spinlock_t *lock);

应用到自旋锁的核心规则是任何代码必须, 在持有自旋锁时, 是原子性的. 它不能睡眠;
事实上, 它不能因为任何原因放弃处理器, 除了服务中断(并且有时即便此时也不行)

内核抢占的情况由自旋锁代码自己处理. 内核代码持有一个自旋锁的任何时间, 抢占
在相关处理器上被禁止. 即便单处理器系统必须以这种方式禁止抢占以避免竞争情况.
因此需要正确的加锁, 即便你从不期望你的代码在多处理器机器上运行.

在持有一个锁时避免睡眠,很多内核函数可能睡眠, 拷贝数据到或从用户空间是一个
明显的例子: 请求的用户空间页可能需要在拷贝进行前从磁盘上换入, 这个操作显然
需要一个睡眠. 必须分配内存的任何操作都可能睡眠. kmalloc 能够决定放弃处理器, 
睡眠可能发生在令人惊讶的地方; 编写会在自旋锁下执行的代码需要注意你调用的每个函数.

如果驱动在执行并且已经获取了一个锁来控制对它的设备的存取. 当持有这个锁时, 
设备发出一个中断, 使得你的中断处理运行. 中断处理, 在存取设备之前, 必须获得锁.
在一个中断处理中获取一个自旋锁是一个要做的合法的事情; 这是自旋锁操作不能睡眠
的其中一个理由. 但是如果中断处理和起初获得锁的代码在同一个处理器上会发生什么? 
当中断处理在自旋, 非中断代码不能运行来释放锁. 这个处理器将永远自旋.

避免这个陷阱需要在持有自旋锁时禁止中断( 只在本地 CPU ). 有各种自旋锁函数会
为你禁止中断。

关于自旋锁使用的最后一个重要规则是自旋锁必须一直是尽可能短时间的持有.
 你持有一个锁越长, 另一个进程可能不得不自旋等待你释放它的时间越长, 它不得不
 完全自旋的机会越大. 长时间持有锁也阻止了当前处理器调度, 意味着高优先级进程
--真正应当能获得 CPU 的进程-- 可能不得不等待. 
 

自旋锁函数

void spin_lock(spinlock_t *lock);
void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);
void spin_lock_irq(spinlock_t *lock);
void spin_lock_bh(spinlock_t *lock)

spin_loc_irqsave 禁止中断(只在本地处理器)在获得自旋锁之前; 之前的中断状态保存
在 flags 里. 如果你绝对确定在你的处理器上没有禁止中断的(或者, 换句话说, 你确信
在释放自旋锁时打开中断), 你可以使用 spin_lock_irq 代替, 并且不必保持跟踪 flags.
spin_lock_bh 在获取锁之前禁止软件中断, 但是硬件中断留作打开的.

如果有一个可能被在(硬件或软件)中断上下文运行的代码获得的自旋锁, 你必须使用一种 
spin_lock 形式来禁止中断. 其他做法可能死锁系统, 可以使用 spin_lock_bh 来安全地
避免死锁, 而仍然允许硬件中断被服务.

void spin_unlock(spinlock_t *lock);
void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);
void spin_unlock_irq(spinlock_t *lock);
void spin_unlock_bh(spinlock_t *lock);
每个 spin_unlock 变体恢复由对应的 spin_lock 函数锁做的工作. 传递给 
spin_unlock_irqrestore 的 flags 参数必须是传递给 spin_lock_irqsave 的同一个变量.
 你必须也调用 spin_lock_irqsave 和 spin_unlock_irqrestore 在同一个函数里. 

还有一套非阻塞的自旋锁操作:

int spin_trylock(spinlock_t *lock);
int spin_trylock_bh(spinlock_t *lock);
这些函数成功时返回非零( 获得了锁 ), 否则 0. 没有"try"版本来禁止中断.

读者/写者自旋锁

内核提供了一个自旋锁的读者/写者形式, 这些锁允许任何数目的读者同时进入临界区, 
但是写者必须是排他的存取. 读者写者锁有一个类型 rwlock_t, 在
中定义. 它们可以以 2 种方式被声明和被初始化:

rwlock_t my_rwlock = RW_LOCK_UNLOCKED; /* Static way */ 
rwlock_t my_rwlock;
rwlock_init(&my_rwlock); /* Dynamic way */

对于读者, 下列函数是可用的:

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);
读者/写者锁能够饿坏读者
阅读(2169) | 评论(2) | 转发(0) |
给主人留下些什么吧!~~

crazyhadoop2011-06-27 22:28:13

timhuang_2008: 楼主,可以写点贴近硬件的东西吗,就是函数里面的东西。 UP的时候是如何,SMP的时候是如何, 你上面的东东都是书上讲的呀.....
慢慢也许会写到,都是边学边写~~

timhuang_20082011-06-27 22:03:41

楼主,可以写点贴近硬件的东西吗,就是函数里面的东西。 UP的时候是如何,SMP的时候是如何, 你上面的东东都是书上讲的呀