linux同步之原子操作(一)

前言

内核版本:linux 3.10.0
硬件平台:x86_64

除了Linux应用层需要保护共享资源,内核也需要保护共享资源,以避免并发访问。防止多个任务之间相互覆盖共享资源,造成访问共享资源不一致的状态。

临界区:访问和操作共享数据,内核全局变量的代码段。
多个任务有可能在同一时刻访问同一临界区(竞争条件,race conditions),因此我们要避免多个任务在临界区并发访问。保证这些代码原子的执行,即:整个临界区的访问不可被打断,是一个不可分割的指令。

内核产生竞争条件的原因:

(1)多处理器:目前处理器都支持对称多处理器,这就导致运行在不同处理器的内核代码完全可能在用一时刻里并发的访问数据。

(2)内核抢占:Linux 2.6以后已经允许内核抢占,这意味着调度程序可以在任何时候抢占正在运行的内核代码,重新调度其它进程运行。这就导致进程与抢占它的进程访问共享资源。

(3)中断(包括软中断):中断几乎可以在任何时候异步发生,这也就意味着可能随时打断正在运行的进程,如果中断服务程序访问进程正在访问的资源,这样也会发生竞争。

一、原子操作

发生了上述竞争条件后该怎样解决?这篇文章我们主要介绍第一种内核同步的方法:原子操作

C语言无法保证原子操作。原子操作可以保证指令以原子的方式执行,即指令执行过程不会被打断。
原子行确保指令执行期间不被打断,要么指令全部执行完,要么指令根本不执行。

原子整数操作经常用于计数器(单个整数变量,一个简单的共享资源),使用锁来实现计数器过于复杂(Useful for resource counting)。
在编写代码时,能使用原子操作地情况下,尽量不要使用锁机制,系统开销更小,对高速缓存行(cache-line)地影响也小。

原子操作通常是内联函数,通常用内联汇编指令来实现。

备注:原子操作主要用于保护一个整数变量这种简单的临界资源。

Linux内核提供一系列函数来实现内核的原子操作,主要提供了两组原子操作接口:原子整数操作和原子位操作。
我主要介绍原子整数操作系列的函数。

二、atomic

2.1 atomic_t 类型定义

atomic_t 类型应定义为有符号整数。
此外,它应该是不透明的,这样任何类型的转换为正常的 C 整数类型都会失败。
比如:

atomic_t value = 0 ;
(int)value;  //失败,不允许这样强制转换
// include/linux/types.h

typedef struct {
    
    
	int counter;
} atomic_t;

#ifdef CONFIG_64BIT
typedef struct {
    
    
	long counter;
} atomic64_t;
#endif

atomic_t是不透明类型数据类型,隐藏了其内部格式或者结构。为了确保这些数据只在特殊的有关原子操作的函数中才会使用。不要将该类型转化为其对应的C标准类型,并且不要假设该类型的长度。

2.2 atomic 初始化

// arch/x86/include/asm/atomic.h 
// 原子操作一系列函数的实现与体系架构有关 ,很多通过内联汇编指令实现
#define ATOMIC_INIT(i)	{
      
       (i) }

//定义value为原子变量,并初始化为0
atomic_t value = ATOMIC_INIT(0)

2.3 atomic read and set

/**
 * atomic_read - read atomic variable
 * @v: pointer of type atomic_t
 *
 * Atomically reads the value of @v.
 */
static inline int atomic_read(const atomic_t *v)
{
    
    
	return (*(volatile int *)&(v)->counter);
}

/**
 * atomic_set - set atomic variable
 * @v: pointer of type atomic_t
 * @i: required value
 *
 * Atomically sets the value of @v to @i.
 */
static inline void atomic_set(atomic_t *v, int i)
{
    
    
	v->counter = i;
}

比如:

atomic_read(&v) //原子的读取整数变量v
atomic_set(&v, 1) //原子地设置v为1

备注:对于读原子变量,读取地时候加了volatile修饰,保证每次都是去从变量的内存地址中读取原子变量,而不是从缓存中读取变量,保证读操作不会被编译器优化,从而每次读取到正确的值。
volatile不能保证原子操作(不具有保护临界资源的作用),只是避免编译器优化而已。

2.4 atomic add and subtract

/**
 * atomic_add - add integer to atomic variable
 * @i: integer value to add
 * @v: pointer of type atomic_t
 *
 * Atomically adds @i to @v.
 */
static inline void atomic_add(int i, atomic_t *v)
{
    
    
	asm volatile(LOCK_PREFIX "addl %1,%0"
		     : "+m" (v->counter)
		     : "ir" (i));
}

/**
 * atomic_sub - subtract integer from atomic variable
 * @i: integer value to subtract
 * @v: pointer of type atomic_t
 *
 * Atomically subtracts @i from @v.
 */
static inline void atomic_sub(int i, atomic_t *v)
{
    
    
	asm volatile(LOCK_PREFIX "subl %1,%0"
		     : "+m" (v->counter)
		     : "ir" (i));
}

比如:

atomic_add(2, &v); //原子地给v加2
atomic_sub(3, &v); //原子地给v减3

2.5 atomic_sub_and_test

/**
 * atomic_sub_and_test - subtract value from variable and test result
 * @i: integer value to subtract
 * @v: pointer of type atomic_t
 *
 * Atomically subtracts @i from @v and returns
 * true if the result is zero, or false for all
 * other cases.
 */
static inline int atomic_sub_and_test(int i, atomic_t *v)
{
    
    
	unsigned char c;

	asm volatile(LOCK_PREFIX "subl %2,%0; sete %1"
		     : "+m" (v->counter), "=qm" (c)
		     : "ir" (i) : "memory");
	return c;
}

比如:
将原子变量减2,如果等于0,返回真;否则返回假。

// include/linux/stddef.h
enum {
    
    
	false	= 0,
	true	= 1
};

//等价于:
if(atomic_sub_and_test(2, &v))
	return true;   //-->v == 2
else
	return false;  // -->v != 2

2.6 atomic increment and decrement

/**
 * atomic_inc - increment atomic variable
 * @v: pointer of type atomic_t
 *
 * Atomically increments @v by 1.
 */
static inline void atomic_inc(atomic_t *v)
{
    
    
	asm volatile(LOCK_PREFIX "incl %0"
		     : "+m" (v->counter));
}

/**
 * atomic_dec - decrement atomic variable
 * @v: pointer of type atomic_t
 *
 * Atomically decrements @v by 1.
 */
static inline void atomic_dec(atomic_t *v)
{
    
    
	asm volatile(LOCK_PREFIX "decl %0"
		     : "+m" (v->counter));
}

比如:

atomic_inc(&v)  //原子地给v加1
atomic_dec(&v)  //原子地给v减1

2.7 atomic inc or dec and test

/**
 * atomic_dec_and_test - decrement and test
 * @v: pointer of type atomic_t
 *
 * Atomically decrements @v by 1 and
 * returns true if the result is 0, or false for all other
 * cases.
 */
static inline int atomic_dec_and_test(atomic_t *v)
{
    
    
	unsigned char c;

	asm volatile(LOCK_PREFIX "decl %0; sete %1"
		     : "+m" (v->counter), "=qm" (c)
		     : : "memory");
	return c != 0;
}

/**
 * atomic_inc_and_test - increment and test
 * @v: pointer of type atomic_t
 *
 * Atomically increments @v by 1
 * and returns true if the result is zero, or false for all
 * other cases.
 */
static inline int atomic_inc_and_test(atomic_t *v)
{
    
    
	unsigned char c;

	asm volatile(LOCK_PREFIX "incl %0; sete %1"
		     : "+m" (v->counter), "=qm" (c)
		     : : "memory");
	return c != 0;
}

//等价于:
if(atomic_dec_and_test(&v))
	return true;   //-->v == 1
else
	return false;  // -->v != 1
	
if( atomic_inc_and_test(&v))
	return true;   //-->v == -1
else
	return false;  // -->v != -1

2.8 atomic add or sub and return

/**
 * atomic_add_return - add integer and return
 * @i: integer value to add
 * @v: pointer of type atomic_t
 *
 * Atomically adds @i to @v and returns @i + @v
 */
static inline int atomic_add_return(int i, atomic_t *v)
{
    
    
	return i + xadd(&v->counter, i);
}

/**
 * atomic_sub_return - subtract integer and return
 * @v: pointer of type atomic_t
 * @i: integer value to subtract
 *
 * Atomically subtracts @i from @v and returns @v - @i
 */
static inline int atomic_sub_return(int i, atomic_t *v)
{
    
    
	return atomic_add_return(-i, v);
}
#define atomic_inc_return(v)  (atomic_add_return(1, v))
#define atomic_dec_return(v)  (atomic_sub_return(1, v))

三、api演示

# include <linux/init.h>
# include <linux/module.h>
# include <linux/kernel.h>
# include <asm/atomic.h>

static int __init lkm_init(void)
{
    
     
	atomic_t v = ATOMIC_INIT(2);

	printk("v = %d\n", atomic_read(&v));

	if(atomic_sub_and_test(2, &v))
		printk("true\n");
	else
		printk("false\n");	
	printk("v = %d\n",  atomic_read(&v));

	atomic_set(&v, 3);
	printk("v = %d\n", atomic_read(&v));

	atomic_inc(&v);  //原子地给v加1
	printk("v = %d\n", atomic_read(&v));

	atomic_dec(&v);  //原子地给v减1
	printk("v = %d\n", atomic_read(&v));

	atomic_add(2, &v); //原子地给v加2
	printk("v = %d\n", atomic_read(&v));

	atomic_sub(3, &v); //原子地给v减3
	printk("v = %d\n", atomic_read(&v));


	atomic_set(&v, 1);
	printk("v = %d\n", atomic_read(&v));
	if(atomic_dec_and_test(&v))
		printk("true\n");   //-->v == 1
	else
		printk("false\n");  // -->v != 1
	printk("v = %d\n",  atomic_read(&v));

	atomic_set(&v, -1);
	printk("v = %d\n", atomic_read(&v));	
	if( atomic_inc_and_test(&v))
		printk("true\n");   //-->v == -1
	else
		printk("false\n");  // -->v != -1
	printk("v = %d\n",  atomic_read(&v));

	atomic_set(&v, 2);
	printk("v = %d\n", atomic_read(&v));

	printk("ret = %d\n", atomic_add_return(3, &v));
	printk("v = %d\n", atomic_read(&v));

	printk("ret = %d\n", atomic_sub_return(2, &v));
	printk("v = %d\n", atomic_read(&v));

	printk("v = %d\n", atomic_inc_return(&v));
	printk("v = %d\n", atomic_dec_return(&v));

	return -1;
}

module_init(lkm_init);

MODULE_LICENSE("GPL");


Makefile
obj-m:=atomic.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

查看结果:
在这里插入图片描述

总结

该文章主要介绍原子整数操作一系列API的使用。

参考资料

Linux内核源码 3.10.0
Linux内核设计与实现

猜你喜欢

转载自blog.csdn.net/weixin_45030965/article/details/125549728