2020年5月17日星期日

【原创】Linux信号量机制分析

【原创】Linux信号量机制分析


背景

  • Read the fucking source code! --By 鲁迅
  • A picture is worth a thousand words. --By 高尔基

说明:

  1. Kernel版本:4.14
  2. ARM64处理器,Contex-A53,双核
  3. 使用工具:Source Insight 3.5, Visio

1. 概述

  • 信号量semaphore,是操作系统中一种常用的同步与互斥的机制;
  • 信号量允许多个进程(计数值>1)同时进入临界区;
  • 如果信号量的计数值为1,一次只允许一个进程进入临界区,这种信号量叫二值信号量;
  • 信号量可能会引起进程睡眠,开销较大,适用于保护较长的临界区;
  • 与读写自旋锁类似,linux内核也提供了读写信号量的机制;

本文将分析信号量与读写信号量的机制,开始吧。

2. 信号量

2.1 流程分析

  • 可以将信号量比喻成一个盒子,初始化时在盒子里放入N把钥匙,钥匙先到先得,当N把钥匙都被拿走完后,再来拿钥匙的人就需要等待了,只有等到有人将钥匙归还了,等待的人才能拿到钥匙;

信号量的实现很简单,先看一下数据结构:

struct semaphore {	raw_spinlock_t		lock;  //自旋锁,用于count值的互斥访问	unsigned int		count;  //计数值,能同时允许访问的数量,也就是上文中的N把锁	struct list_head	wait_list;  //不能立即获取到信号量的访问者,都会加入到等待列表中};struct semaphore_waiter {	struct list_head list;  //用于添加到信号量的等待列表中	struct task_struct *task; //用于指向等待的进程,在实际实现中,指向current	bool up;     //用于标识是否已经释放};

流程如下:

  • down接口用于获取信号量,up用于释放信号量;
  • 调用down时,如果sem->count > 0时,也就是盒子里边还有多余的锁,直接自减并返回了,当sem->count == 0时,表明盒子里边的锁被用完了,当前任务会加入信号量的等待列表中,设置进程的状态,并调用schedule_timeout来睡眠指定时间,实际上这个时间设置的无限等待,也就是只能等着被唤醒,当前任务才能继续运行;
  • 调用up时,如果等待列表为空,表明没有多余的任务在等待信号量,直接将sem->count自加即可。如果等待列表非空,表明有任务正在等待信号量,那就需要对等待列表中的第一个任务(等待时间最长)进行唤醒操作,并从等待列表中将需要被唤醒的任务进行删除操作;

2.2 信号量缺点

  • 对比下《Linux Mutex机制分析》说过的MutexSemaphoreMutex在实现上有一个重大的区别:ownershipMutex被持有后有一个明确的owner,而Semaphore并没有owner,当一个进程阻塞在某个信号量上时,它没法知道自己阻塞在哪个进程(线程)之上;
  • 没有ownership会带来以下几个问题:
    1. 在保护临界区的时候,无法进行优先级反转的处理;
    2. 系统无法对其进行跟踪断言处理,比如死锁检测等;
    3. 信号量的调试变得更加麻烦;

因此,在Mutex能满足要求的情况下,优先使用Mutex

2.3 其他接口

信号量提供了多种不同的信号量获取的接口,介绍如下:

/* 未获取信号量时,进程轻度睡眠: TASK_INTERRUPTIBLE */int down_interruptible(struct semaphore *sem)/* 未获取到信号量时,进程中度睡眠: TASK_KILLABLE */int down_killable(struct semaphore *sem)/* 非等待的方式去获取信号量 */int down_trylock(struct semaphore *sem)/* 获取信号量,并指定等待时间 */int down_timeout(struct semaphore *sem, long timeout)

3. 读写信号量

【原创】linux spinlock/rwlock/seqlock原理剖析(基于ARM64)文章中,我们分析过读写自旋锁,读写信号量的功能类似,它能有效提高并发性,我们先明确下它的特点:

  • 允许多个读者同时进入临界区;
  • 读者与写者不能同时进入临界区(读者与写者互斥);
  • 写者与写者不能同时进入临界区(写者与写者互斥);

3.1 数据结构

读写信号量的数据结构与信号量的结构比较相似:

struct rw_semaphore {	atomic_long_t count;  //用于表示读写信号量的计数	struct list_head wait_list;  //等待列表,用于管理在该信号量上睡眠的任务	raw_spinlock_t wait_lock; //锁,用于保护count值的操作#ifdef CONFIG_RWSEM_SPIN_ON_OWNER	struct optimistic_spin_queue osq; /* spinner MCS lock */ //MCS锁,参考上一篇文章Mutex中的介绍	/*	 * Write owner. Used as a speculative check to see	 * if the owner is running on the cpu.	 */	struct task_struct *owner;  //当写者成功获取锁时,owner会指向锁的持有者

没有评论:

发表评论