linux内核原理-原子变量,自旋锁,互斥锁

本文详细介绍了Linux内核中的原子变量、自旋锁(如自旋锁的获取与释放)以及互斥锁(如mutex的使用)的概念、原理和实现,展示了它们在保证并发程序正确性和性能优化中的作用。
摘要由CSDN通过智能技术生成

1.原子变量
针对数值类型基础的算术运算,提供的一种保证运算原子性机制.
(1). 要求
a. 必须针对atomic_t类型
b. 必须采用提供的原子方法实现基础算术运算原子性
(2). 原理

typedef struct { volatile int counter; } atomic_t;

通过volatile使得每次访问counter必须访问内存.

atomic_add为例说明linux内核如何保证原子操作原子性.
通过lock修饰汇编指令.lock使得其后的一条内存访问汇编指令在执行时以原子方式不可分割的执行完成.

#ifdef CONFIG_SMP
#define LOCK "lock ; "
#else
#define LOCK ""
#endif
static __inline__ void atomic_add(int i, atomic_t *v)
{
	__asm__ __volatile__(
		LOCK "addl %1,%0"
		:"=m" (v->counter)
		:"ir" (i), "m" (v->counter));
}

2.自旋锁
自旋锁用于保护临界区.即同时只应该被一个访问者访问的区域.
其特性是当某个访问者申请锁时,若申请不到,此访问者以自旋方式等待.访问者会100%占据cpu,持续检测.

typedef struct {
	volatile unsigned int lock;
#ifdef CONFIG_DEBUG_SPINLOCK
	unsigned magic;
#endif
#ifdef CONFIG_PREEMPT
	unsigned int break_lock;
#endif
} spinlock_t;

(1). 定义与初始化

#define SPINLOCK_MAGIC	0xdead4ead
#ifdef CONFIG_DEBUG_SPINLOCK
#define SPINLOCK_MAGIC_INIT	, SPINLOCK_MAGIC
#else
#define SPINLOCK_MAGIC_INIT	/* */
#endif

#define SPIN_LOCK_UNLOCKED (spinlock_t) { 1 SPINLOCK_MAGIC_INIT }

#define DEFINE_SPINLOCK(x) spinlock_t x = SPIN_LOCK_UNLOCKED

通过DEFINE_SPINLOCK(test);定义了一个名字为test的自旋锁,并对其执行了初始化.
(2). 加锁

#define _spin_lock(lock)	\
do { \
	preempt_disable(); \
	_raw_spin_lock(lock); \
	__acquire(lock); \
} while(0)
#define spin_lock(lock)		_spin_lock(lock)

通过spin_lock(&test);可以申请对test加锁.
preempt_disable();的作用是,在中断返回时,禁止执行重新调度,来避免当前进程被切换掉.
我们分析_raw_spin_lock(lock);

#define spin_lock_string \
	"\n1:\t" \
	"lock ; decb %0\n\t" \
	"jns 3f\n" \
	"2:\t" \
	"rep;nop\n\t" \
	"cmpb $0,%0\n\t" \
	"jle 2b\n\t" \
	"jmp 1b\n" \
	"3:\n\t"
	
static inline void _raw_spin_lock(spinlock_t *lock)
{
	__asm__ __volatile__(
		spin_lock_string
		:"=m" (lock->lock) : : "memory");
}

上述获取锁的逻辑是:
a. 原子的递减lock->lock.这使得内存中此变量减少1
b. 分析上一步结果.
若未产生负数.这意味着我们已经成功获取了锁.继续执行.
若产生负数.这意味着之前存在至少一个使用者已经获取了这个锁且未释放.进入c
c. 循环迭代:
空操作.
读取lock->lock实时值,和0比较.
若,小于等于0,继续循环迭代.
若,大于0,这对应锁的使用者释放了锁.跳到a.因为多核下,可能多个等待着此时同时感知此情况.因此需让它们接下来都执行a.首个执行成功者获得锁.后续执行者,将再次回到c中循环迭代的等待逻辑.

__acquire(lock);其实是一个空操作.
(3). 释放锁

#define spin_unlock(lock)	_spin_unlock(lock)

通过spin_unlock(&test);可以释放test

#define _spin_unlock(lock) \
do { \
	_raw_spin_unlock(lock); \
	preempt_enable(); \
	__release(lock); \
} while (0)

我们分析_raw_spin_unlock(lock);

#define spin_unlock_string \
	"movb $1,%0" \
		:"=m" (lock->lock) : : "memory"

static inline void _raw_spin_unlock(spinlock_t *lock)
{
	__asm__ __volatile__(
		spin_unlock_string
	);
}

释放锁的过程是将数值1直接设置到lock->lock的过程.在x86体系下,单独的读一次内存,写一次内存汇编操作无需lock修饰也是原子的.
(4).尝试加锁
通过一个原子交换汇编指令即可.若此前未被加锁,则获得锁,同时设置了lock->lock0.若此前已被加锁,再次为其设置为0,也无错.

3.互斥锁
互斥锁用于保护临界区.即同时只应该被一个访问者访问的区域.
其特性是当某个访问者申请锁时,若申请不到,此访问者以阻塞方式等待.主动放弃cpu,待条件满足时,由另一角色执行唤醒任务.
互斥锁在实现上可认为特殊的信号量.当某个信号量的初始资源数量为1时,此信号量就是互斥锁.
(1). 定义与初始化

#define MUTEX_DEFAULT		0x0
struct semaphore {
	raw_spinlock_t		lock;
	unsigned int		count;
	struct list_head	wait_list;
};
typedef struct semaphore	mutex_t;

mutex_t test;即可定义一个名为test的互斥锁.

#define __SEMAPHORE_INITIALIZER(name, n)				\
{									\
	.lock		= __RAW_SPIN_LOCK_UNLOCKED((name).lock),	\
	.count		= n,						\
	.wait_list	= LIST_HEAD_INIT((name).wait_list),		\
}
static inline void sema_init(struct semaphore *sem, int val)
{
	static struct lock_class_key __key;
	*sem = (struct semaphore) __SEMAPHORE_INITIALIZER(*sem, val);
	lockdep_init_map(&sem->lock.dep_map, "semaphore->lock", &__key, 0);
}
#define mutex_init(lock, type, name)		sema_init(lock, 1)

通过mutex_init(&test, MUTEX_DEFAULT, "test");即可完成test初始化.
(2). 申请锁

#define mutex_lock(lock, num)			down(lock)

通过mutex_lock(&test, 1)即可执行加锁申请.

void __sched down(struct semaphore *sem)
{
	unsigned long flags;
	might_sleep();
	raw_spin_lock_irqsave(&sem->lock, flags);
	if (likely(sem->count > 0))
		sem->count--;
	else
		__down(sem);
	raw_spin_unlock_irqrestore(&sem->lock, flags);
}

上述过程可描述为:
a. 申请内部自旋互斥锁,以便互斥访问临界区.
b. 若sem->count大于0,则递减.释放锁.这时我们已经完成互斥锁的申请.
c. 若sem->count小于等于0,意味着此时无法得到锁.__down内部会将当前线程作为一个等待对象放在此互斥锁的等待着链表上.然后设置自身状态为TASK_UNINTERRUPTIBLE,并主动放弃调度.后续锁的使用者执行释放时,若释放后sem->count大于0,会检测是否存在等待者.存在则对其中一个等待着执行唤醒.

所以上述,在立即得到锁时继续运行.在暂时无法得到锁时,阻塞自身放弃调度,直到后续被唤醒,得到锁,再继续.
(3). 释放锁

#define mutex_unlock(lock)			up(lock)

通过mutex_unlock(&test)即可释放锁.

void __sched up(struct semaphore *sem)
{
	unsigned long flags;
	raw_spin_lock_irqsave(&sem->lock, flags);
	if (likely(list_empty(&sem->wait_list)))
		sem->count++;
	else
		__up(sem);
	raw_spin_unlock_irqrestore(&sem->lock, flags);
}

上述过程为:
a. 获取内部自旋锁,以便互斥访问临界区.
b. 若无人等待,递增sem->count即可完成释放.
c. 若存在等待者,唤醒一个.

static noinline void __sched __up(struct semaphore *sem)
{
	struct semaphore_waiter *waiter = list_first_entry(&sem->wait_list,
						struct semaphore_waiter, list);
	list_del(&waiter->list);
	waiter->up = true;
	wake_up_process(waiter->task);
}

(4). 尝试加锁

int __sched down_trylock(struct semaphore *sem)
{
	unsigned long flags;
	int count;
	raw_spin_lock_irqsave(&sem->lock, flags);
	count = sem->count - 1;
	if (likely(count >= 0))
		sem->count = count;
	raw_spin_unlock_irqrestore(&sem->lock, flags);
	return (count < 0);
}

很好解释.先看能否得到锁.得不到,也立即返回.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

raindayinrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值