Linux内核学习——2. 原子操作

原子操作

  • 软件平台:运行于VMware Workstation 12 Player下UbuntuLTS16.04_x64 系统

  • 开发环境:Linux 3.4.2内核、arm-linux-gcc 4.3.2工具链


2、原子操作

Linux内核中,原子操作有两种方式:原子变量与原子位

2.1 原子变量在内核中的体现

  1. 原子变量的定义

    在内核中原子变量实际为一个结构体,在include\linux\types.h

    typedef struct {
    	int counter;
    } atomic_t;
    
    #ifdef CONFIG_64BIT
    typedef struct {
    	long counter;
    } atomic64_t;
    #endif
    
  2. 原子变量的操作函数

    具体的定义在arch\arm\include\asm\atomic.hinclude\linux\atomic.h

    函数作用
    atomic_read(atomic_t *v)出原子变量的值,即 v->counter
    atomic_set(atomic_t *v, int i)设置原子变量的值,即 v->counter = i
    atomic_inc(atomic_t *v)v->counter**++**
    atomic_dec(atomic_t *v)v->counter**–**
    atomic_add(int i,atomic_t * v)v->counter += i
    atomic_sub(int i,atomic_t * v)v->counter -= i
    atomic_inc_and_test(atomic_t * v)先加 1,再判断新值是否等于 0;等于 0 的话,返回值为 1
    atomic_dec_and_test(atomic_t * v)先减 1,再判断新值是否等于 0;等于 0 的话,返回值为 1
    atomic_sub_and_test(int i, atomic_t * v)先减i,再判断新值是否为0:是则返回1,否则返回-1;
    atomic_sub_return(int i, atomic_t * v)先减i,并返回指向v的指针
    atomic_add_return(int i, atomic_t * v)先加i,并返回指向v的指针
    atomic_add_negative(int i, atomic_t * v)先加i,在判断结果是否为负数:是则返回1,否则返-

2.2 原子变量的内核实现

2.2.1 ATOMIC_OPS 在 UP 系统中的实现

UP 即 Uni-Processor,系统只有一个单核 CPU

arch\arm\include\asm\atomic.h文件中,对于ARMv6以下的架构(即不支持多核操作系统),原子实现如下

  1. 关中断
  2. 进行相关原子变量的操作
  3. 恢复中断(恢复后中断可能是打开或关闭)

对于上述的操作最终都是通过汇编指令完成,对于单条汇编指令就是原子操作

  • ##:是一种运算符,是将两个运算对象连接在一起,只能出现在带参宏定义的替换文本
#define ATOMIC_OPS(op, c_op, asm_op)					
ATOMIC_OP(op, c_op, asm_op)					
ATOMIC_OP_RETURN(op, c_op, asm_op)				
ATOMIC_FETCH_OP(op, c_op, asm_op)

#ifdef CONFIG_SMP
#error SMP not supported on pre-ARMv6 CPUs
#endif

#define ATOMIC_OP(op, c_op, asm_op)	
static inline void atomic_##op(int i, atomic_t *v)			
{									
    unsigned long flags;						
    raw_local_irq_save(flags);					
    v->counter c_op i;						
    raw_local_irq_restore(flags);					
}

#define ATOMIC_OP_RETURN(op, c_op, asm_op)				
static inline int atomic_##op##_return(int i, atomic_t *v)		
{									
    unsigned long flags;						
    int val;							
    raw_local_irq_save(flags);					
    v->counter c_op i;						
    val = v->counter;						
    raw_local_irq_restore(flags);					

    return val;							
  }
  
#define ATOMIC_FETCH_OP(op, c_op, asm_op)				
static inline int atomic_fetch_##op(int i, atomic_t *v)			
{									
    unsigned long flags;						
    int val;							
    raw_local_irq_save(flags);					
    val = v->counter;						
    v->counter c_op i;						
    raw_local_irq_restore(flags);					
    return val;							
}

ATOMIC_OP(add, +=, add)为例子,进行上述的宏展开

#define ATOMIC_OPS(add, +=, add)					
ATOMIC_OP(add, +=, add)					
ATOMIC_OP_RETURN(add, +=, add)				
ATOMIC_FETCH_OP(add, +=, add)

#define ATOMIC_OP(add, +=, add)					
static inline void atomic_add(int i, atomic_t *v)			
{									
unsigned long flags;						

	raw_local_irq_save(flags);				## ------> 关中断	
	v->counter += i;						
	raw_local_irq_restore(flags);			## ------> 恢复中断
}									

#define ATOMIC_OP_RETURN(add, +=, add)				
static inline int atomic_add_return(int i, atomic_t *v)
{									
	unsigned long flags;						
	int val;							
									
	raw_local_irq_save(flags);					## ------> 关中断	
	v->counter += i;						
	val = v->counter;						
	raw_local_irq_restore(flags);				## ------> 恢复中断									
	return val;							
}

#define ATOMIC_FETCH_OP(add, +=, add)				
static inline int atomic_fetch_add(int i, atomic_t *v)
{									
	unsigned long flags;						
	int val;							
									
	raw_local_irq_save(flags);					
	val = v->counter;						## ------> 关中断	
	v->counter += i;						
	raw_local_irq_restore(flags);			## ------> 恢复中断									
	return val;							
}
2.2.2 ATOMIC_OPS 在 SMP 系统中的实现

SMP 就是 Symmetric Multi-Processors对称多处理器

arch\arm\include\asm\atomic.h文件中,对于ARMv6及以上的架构,原子实现如下:(存在不断循环重复的问题,即自旋

  1. 读出
  2. 修改
  3. 写入
  4. 判断
  5. 重做
#define ATOMIC_OP(op, c_op, asm_op)					
static inline void atomic_##op(int i, atomic_t *v)			
{									
    unsigned long tmp;						
    int result;							
    prefetchw(&v->counter);						
    __asm__ __volatile__(
        "@ atomic_" #op "\n"			
        "1:	ldrex	%0, [%3]\n"						
        "	" #asm_op "	%0, %0, %4\n"					
        "	strex	%1, %0, [%3]\n"						
        "	teq	%1, #0\n"						
        "	bne	1b"							
        : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)		
        : "r" (&v->counter), "Ir" (i)					
        : "cc"
    );							
}					

#define ATOMIC_OP_RETURN(op, c_op, asm_op)				
static inline int atomic_##op##_return_relaxed(int i, atomic_t *v)	
{									
    unsigned long tmp;						
    int result;
    prefetchw(&v->counter);
    __asm__ __volatile__(
        "@ atomic_" #op "_return\n"		
        "1:	ldrex	%0, [%3]\n"						
        "	" #asm_op "	%0, %0, %4\n"					
        "	strex	%1, %0, [%3]\n"						
        "	teq	%1, #0\n"						
        "	bne	1b"							
        : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter)		
        : "r" (&v->counter), "Ir" (i)					
        : "cc"
    );							
    return result;							
}

#define ATOMIC_FETCH_OP(op, c_op, asm_op)				
static inline int atomic_fetch_##op##_relaxed(int i, atomic_t *v)	
{									
    unsigned long tmp;						
    int result, val;
    prefetchw(&v->counter);
    __asm__ __volatile__(
        "@ atomic_fetch_" #op "\n"
        "1:	ldrex	%0, [%4]\n"
        "	" #asm_op "	%1, %0, %5\n"
        "	strex	%2, %1, [%4]\n"
        "	teq	%2, #0\n"
        "	bne	1b"
        : "=&r" (result), "=&r" (val), "=&r" (tmp), "+Qo" (v->counter)
        : "r" (&v->counter), "Ir" (i)
        : "cc"
    );
    return result;
}

ATOMIC_OP(add, +=, add)为例子,把第一个函数进行宏展开后得到

static inline void atomic_add(int i, atomic_t *v) 
{ 	
    unsigned long tmp; 
    int result; 	
    prefetchw(&v->counter); 	
    __asm__ __volatile__(		
        "@ atomic_" "add" "\n" 		
        "1:	ldrex	%0, [%3]\n" 		
        "	" 		
        "add" "	%0, %0, %4\n" 		
        "	strex	%1, %0, [%3]\n" 		
        "	teq	%1, #0\n" 		
        "	bne	1b" 		
        : "=&r" (result), "=&r" (tmp), "+Qo" (v->counter) 		
        : "r" (&v->counter), "Ir" (i) 		
        : "cc"
        ); 		
}

进行执行arm-linux-gcc -S main.c -o main.i获得汇编码如下:

@ atomic_add1:	
	ldrex	r0, [r2]		## 1. 读出	
	add	r0, r0, r3			## 2. 修改	
	strex	ip, r0, [r2]	## 3. 写入	
	teq	ip, #0				## 4. 中途被别人先修改了?	
	bne	1b					## 5. 那就重做一次
  1. 读出ldrex r0, [r2]

    读取 r2 所指内存的数据,存入 r0;并且标记 r2 所指内存为“独占访问”
    如果有其他程序再次执行“ ldrex r0, [r2]”,一样会成功,一样会标记 r2 所指内存为“独占访问”。

  2. 修改r0的值

  3. 写入strex ip, r0, [r2]

    • 如果 r2 的“独占访问”标记还存在,则把 r0 的新值写入 r2所指内存, 并且清除“独占访问”的标记
      ip设为 0 表示成功

    • 如果 r2 的“独占访问”标记不存在了,就不会更新内存,

      并且把 ip设为 1 表示失败

  4. 判断,打断则重做(ip ?= 0)

  5. 重做(如果 ip ≠ 0)

2.3 原子位在内核中的体现

原子位就是操作原子变量的某一位,操作函数在arch\arm\include\asm\bitops.h文件内

函数名作用
set_bit(int nr, volatile unsigned long * p)设置(*p)的 bit nr 为 1
clear_bit(int nr, volatile unsigned long * p)清除(*p)的 bit nr 为 0
change_bit(int nr, volatile unsigned long * p)改变(*p)的 bit nr,从 1 变为 0,或是从 0 变为 1
test_and_set_bit(int nr, volatile unsigned long * p)设置(*p)的 bit nr 为 1,返回该位的老值
test_and_clear_bit(int nr, volatile unsigned long * p)清除(*p)的 bit nr 为 0,返回该位的老值
test_and_change_bit(int nr, volatile unsigned long * p)改变(*p)的 bit nr,从 1 变为 0,或是从 0 变为 1;返回该位的老值

2.4 原子位在内核中的实现

2.4.1 原子位在UP系统中的实现

与原子变量相似,原理为:

  1. 关中断
  2. 进行相关原子位的操作
  3. 恢复中断(恢复后中断可能是打开或关闭)

初始宏为

#ifndef CONFIG_SMP/* * The __* form of bitops are non-atomic and may be reordered. */

#define ATOMIC_BITOP(name,nr,p)	 
	(__builtin_constant_p(nr) ? ____atomic_##name(nr, p) : _##name(nr,p))

#else

#define ATOMIC_BITOP(name,nr,p) _##name(nr,p)#endif

set_bit为原型进行宏展开

#define set_bit(nr,p)			
ATOMIC_BITOP(set_bit,nr,p)// 如下:(_builtin_constant_p 用于判断一个值是否为编译时常数,如果参数EXP 的值是常数,函数返回 1,否则返回 0)
    (__builtin_constant_p(nr) ? ____atomic_set_bit(nr, p) : _set_bit(nr,p))

____atomic_set_bit原型分析

static inline void ____atomic_set_bit(unsigned int bit, volatile unsigned long *p)
{	
	unsigned long flags;	
	unsigned long mask = BIT_MASK(bit);	
	p += BIT_WORD(bit);	
	raw_local_irq_save(flags);	// --------------> 关中断	
	*p |= mask;	
	raw_local_irq_restore(flags);	// --------------> 恢复中断
}
2.4.2 原子位在SMP系统中的实现

与原子变量相似,原理为:(存在不断循环重复的问题,即自旋

  1. 读出
  2. 修改
  3. 写入
  4. 判断
  5. 重做

同样以set_bit为例子,进行宏展开

 _set_bit(nr,p)

定义arch\arm\lib\setbit.S

bitop	_set_bit, orr

实现arch\arm\lib\bitops.h

#if __LINUX_ARM_ARCH__ >= 6	
.macro	bitop, name, instrENTRY(	\name		)UNWIND(	.fnstart	)	
ands	ip, r1, #3	strneb	r1, [ip]		
@ assert word-aligned	
mov	r2, #1	
and	r3, r0, #31		
@ Get bit offset	
mov	r0, r0, 
lsr #5	
add	r1, r1, r0, lsl #2	
@ Get word offset
#if __LINUX_ARM_ARCH__ >= 7 && defined(CONFIG_SMP)	
.arch_extension	mp	ALT_SMP(W(pldw)	[r1])	ALT_UP(W(nop))
#endif	
mov	r3, r2, lsl r31:	
ldrex	r2, [r1]		## --------------> 读出	
instr	r2, r2, r3		## --------------> 操作	
strex	r0, r2, [r1]	## --------------> 写入	
cmp	r0, #0				## --------------> 比较	
bne	1b					## --------------> 重新	
bx	lrUNWIND(	.fnend		)ENDPROC(\name		)	.endm
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值