竞态与同步(1)

内核里处理的竞态主要通过以下方法处理: 信号量(互斥量)、自旋锁、读写信号量、读写自旋锁、等待队列、完成量。

  • 信号量(互斥量)
//初始化信号量,val表示可并发使用的资源数量
void sema_init(struct semaphore *sem, int val);
//获取一个资源,资源数量减1,获取不到则线程休眠
void down (&sem);
//可中断的down操作,休眠中可被中断唤醒
int down_interruptible(&sem);
//尝试获取,不成功不会阻塞,直接返回非0
int down_trylock(&sem);
//释放信号量
void up(&sem);

上述获取函数中,down_interruptible和down_trylock都有返回值,返回为0表示获取信号量成功,非0则说明未获取到。对 down_interruptible 来说是在等待休眠中被打断了,对down_trylock则是说明信号量已全被占用。
使用这两种down肯定要始终检查返回值。
互斥量就是信号量初始化为1的情况,也是最常用的情况:

DECLARE_MUTEX(name);
DECLARE_MUTEX_LOCKED(name)
//或者
void init_MUTEX(struct semaphore *sem);
void init_MUTEX_LOCKED(struct semaphore *sem);

example:

//用信号量保证设备同时只被一个线程打开
static DECLARE_MUTEX(mutex);
static xxx_open(struct inode *inode, struct file *filp)
{
    ......
    if(0 != down_trylock(&mutex)) {
        //返回设备忙
        return -EBUSY;
    }
    ......
    //正常返回,设备文件打开
    return 0;
}
static xxx_release(struct inode *inode, struct file *filp)
{
    //close时释放互斥量
    up(&mutex);
    return 0;
}

***关闭设备时一定要 up !! ***

  • 自旋锁
    自旋锁也是一个互斥设备,线程获取到锁后可使用某个资源,使用完后释放该锁。在未释放时,另一线程申请该锁,则陷入自旋,即忙等待,期间一直对锁进行测试,一旦可用就锁定。对SMP及preemptive的单处理器就是如此,对于non-preemptive的单处理器自旋锁什么也不需要做,因为线程执行中并不会被抢占,一旦某个线程陷入自旋,就不会有其他线程来释放它等待的锁。

自旋锁的实现描述:

do {
    preempt_disable(); __acquire(lock); (void)(lock);
}while(0)

即关闭抢占 -> 请求锁 -> 锁定(置位)

//初始化
spinlock_t  lock = SPIN_LOCK_UNLOCKED;
//或者
void spinlock_init(spinlock_t *lock);
//请求锁
void spin_lock(spinlock_t *lock);
//请求锁、保存中断状态、禁止中断
void spin_lock_irqsave(&lock, unsigned long flag);
//请求锁、禁止中断
void spin_lock_irq(&lock);
//请求锁、禁止软中断、允许硬件中断
void spin_lock_bh(&lock)
//---------------------------------------------------
//释放
void spin_unlock(spinlock_t *lock);
//其他lock都有对应的unlock
void spin_unlock_irqrestore(&lock, unsigned long falg);
void spin_unlock_irq(&lock);
void spin_unlock_bh(&iock);
//-------------------------------------------------
//try 版本,锁被占用立即返回非0
int spin_trylock(&lock);
int spin_trylock_bh(&lock);

获取自旋锁期间的代码段是禁止休眠的,
这里要注意禁止使用一些可能引起休眠的函数,包括copy_to_user, copy_from_user, kmalloc等

  • 读写信号量
    基本机制就是: 允许多个读取者获得信号量,但只允许一个写入者,且一旦一个写入者获取到锁,就会禁止其他读取/写入者获取信号量,即完全锁定了。
void init_rwsem(struct rw_semaphore *sem);
//读
void down_read(&sem);
int dow_read_trylock(&sem);
void up_read(&sem);
//写
void down_write(&sem);
int down_write_trylock(&sem);
void up_write(&sem);
//一段时间内禁止获取写入者信号量
void downgrade_write(&sem);

downgrade_write的意义:
读写信号量中写入者优先级比读取者高,且排斥读取者,这就可能会出现在写入频繁时,读取者长时间获取不到锁。因此可以在某次写入者释放锁后,调用downgrade_write可在一段时间内禁止写入,即将保护资源变成了只读。

  • 读写自旋锁
    使用和自旋锁API相似,如下:
void rwlock_init(rwlock_t *lock);
void read_lock(&lock);
void read_lock_irqsave(&lock);
void read_lock_irq(&lock);
void read_lock_bh(&lock);
//释放锁参考自旋锁部分
//写入锁多一个try版本,读取者并没有try版本
int lock_write_rrylock(&lock);

对自旋锁似乎并不是很在意读取者饥饿的情况。

  • 完成量 & 等待队列
    信号量和自旋锁都用于处理并发时的资源保护,等待队列和完成量则是用于内核线程间的同步。
    完成量可以很好的实现同步,接口也很简洁:
//初始化
DECLARE_COMPLETION(cmp);
//或者,动态的创建
void init_completion(struct completion *cmp);
//等待完成量,非中断等待,不可杀
void wait_for_completion(&cmp);
//通知完成量
//唤醒一个等待线程
void complete(&cmp);
//唤醒所有等待线程
void complete_all(&cmp);

等待队列更加古老,完成量是在其基础上封装的,
唤醒在等待队列上休眠的线程需要两个条件,一个是condition为真,另一个是对应的wake_up被调用。
基本原理是线程调用wait_even_xxx后,线程被置为TASK_INTERRUPTIBLE(或UNINTERRUPTIBLE),并被调度出去。调用wake_up就是将这些线程重新放回就绪队列。线程被再次调度后,首先判断condition是否为真,如果不是,重复进入等待。

#define wait_event(wq, condition)                   \  
do {                                    \  
   if (condition)                          \  
       break;                          \  
   __wait_event(wq, condition);                    \  
} while (0) 
//-------------------------------------------------------------------------
#define __wait_event(wq, condition)                     \  
do {                                    \  
   DEFINE_WAIT(__wait);                        \  
                                   \  
   for (;;) {                          \  
       prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE);    \  
       if (condition)                      \  
           break;                      \  
       schedule();                     \  
   }                               \  
   finish_wait(&wq, &__wait);                  \  
} while (0) 

使用步骤如下:

//初始化等待队列头
 DECLARE_WAIT_QUEUE_HEAD(queue_head);  
init_waitqueue_head(wait_queue_head_t *queue_head);
//等待事件
wait_event(queue_head, condition)
wait_event_interruptible(queue_head, condition);
wait_event_timeout(queue_head, condition, timeout);
wait_event_interruptible_timeout(queue_head, condition, timeout);
//唤醒
void wake_up(&queue_head);
void wake_up_interruptible(&queue_head);

另一种方法,直接向等待队列中增加一个任务,控制程序调度,实际上就是手动控制wait_event的过程,显然麻烦了。

/*接口*/
//定义一个等待队列节点
DECLARE_WAITQUEUE(queue, tsk);
// 添加/移除等待队列
 void fastcall add_wait_queue(&queue_head, &queue);
 void fastcall remove_wait_queue(&queue_head, &queue);
/*例子*/
DECLEARE_WAITQUEUE(queue,current); //保存current指针
add_wait_queue(&queue_head, &queue);
while(!conditon)
{
set_current_state(TASK_INTERRUPTIBLE);
if(signal_pending(currrent))
/*处理信号*/
schedule();
}
set_current_state(TASK_RUNNING);
remove_wait_queue(q,&wait);

参考 wait_event_interruptible 使用方法

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容