操作系统概念-内核同步-原子操作

       在同一个操作系统中,不同的进程经常需要相互协同工作,协同的方法一般有两种,一是直接共享逻辑地址空间,二是通过文件或消息共享数据。如果共享逻辑地址空间,则在进程执行的时候有可能会发生多个进程同时访问同一个数据的冲突问题,特别是在多处理器的情况下。对于这类冲突,内核采用了一些方法进行进程同步,例如原子操作、自旋锁、信号量等方法。接下来的四篇(包括本文)将分别介绍原子操作、自旋锁、信号量和死锁的一些概念,同时以Linux4.8.1版本的内核代码(x86架构部分)为例进行分析。

临界区

       临界区(critical-section)是解决进程协作的一个方法。将多个进程可能修改同一个共享变量的代码段设为临界区,当有进程进入临界区后,其他进程会被禁止进入,直到前一个进程离开临界区,其他进程才可以进入。即同一时刻只允许一个进程位于临界区内。伪代码形式可以表示为:

do{
	//进入区
	//临界区
	//退出区
	//剩余区
}while(TRUE)

临界区的实现需要满足以下三个条件:

1,   互斥,即同一时刻只能有一个进程位于临界区内;

2,   前进,当多个进程同时等待进入临界区的时候,会有一个进程被选择进入;

3,   有限等待,在进入区等待的进程必须在有限时间后进入临界区。

作为所有软件都可能会调用的操作系统,同一时刻内同一段代码可能会有多个进程在执行,而像文件读写、硬件调用等操作都是排他性的,因此操作系统更应该做好临界区的设置。

对于操作系统的临界区实现,要分为抢占内核和非抢占内核来讨论。显然,非抢占内核不存在竞争的问题,因为在临界区内的进程不会被打断,除非进程主动退出。对抢占内核来说,就需要硬件或者软件(算法)上的支持来实现临界区。

软件支持的一个例子是Peterson算法。Peterson算法的精髓在于用两个变量(或数组)来记录当前是否有进程位于临界区以及哪个进程位于临界区,这样通过在进入区检测并设置标记、退出区恢复标记可以实现临界区排他的特性。

硬件支持的方法是锁,从底层硬件的层面来看则是实现原子操作。进程在进入临界区前检测并申请锁,离开后释放锁。原子操作保证锁的正常运行。

原子操作

       原子操作指的是不可再分的指令操作,即在执行原子操作时不可能被打断,要么原子操作没有执行,要么已经执行完毕。

       原子操作的实现必须需要硬件的支持,操作系统仅仅是在硬件指令的基础之上进行一次封装。对于没有实现原子操作的硬件,则需要操作系统从软件算法层面进行支持。

linux下的实现

       linux下原子操作的数据结构是atomic_t,其定义放在<linux/types.h>下:

typedef struct {
	int counter;
}atomic_t;

可以看出原子操作仅支持int整形变量,为了与正常的整形区分,采用atomic_t来定义这个原子操作。

       原子操作的接口都在Atomic.h中列出(64位版本则在Atomic64.h)。包括:

原子操作接口

说明

int atomic_read(const atomic_t *v)

获取atomic_t类型变量的值

void atomic_set(atomic_t *v, int i)

设置atomic_t类型变量的值

void atomic_add(int i, atomic_t *v)

增加i

void atomic_sub(int i, atomic_t *v)

减少i

bool atomic_sub_and_test(int i, atomic_t *v)

减少i,如果结果为0则返回true

void atomic_inc(atomic_t *v)

等价于 atomic_add(1, v)

void atomic_dec(atomic_t *v)

等价于 atomic_sub(1, v)

bool atomic_dec_and_test(atomic_t *v)

atomic_sub(1, v),如果结果为0则返回true

bool atomic_inc_and_test(atomic_t *v)

atomic_add(1, v),如果结果为0则返回true

bool atomic_add_negative(int i, atomic_t *v)

增加i,如果结果为负则返回true

int atomic_add_return(int i, atomic_t *v)

增加i并返回结果

int atomic_sub_return(int i, atomic_t *v)

减少i并返回结果

atomic_inc_return(v)

宏,增加1并返回结果

atomic_dec_return(v)

宏,减少1并返回结果

int atomic_fetch_add(int i, atomic_t *v)

增加i,并返回原来的结果

int atomic_fetch_sub(int i, atomic_t *v)

减少i,并返回原来的结果

int atomic_cmpxchg(atomic_t *v, int old, int new)

比较old与v中的值,若相等则将new付给v,返回v原来的值

intatomic_xchg(atomic_t *v, int new)

将new付给v,并返回v原来的值

在这些原子操作中,为了调用底层架构实现的原子操作,加入了很多汇编语言及编译限制的关键字。比如 atomic_read中用到的READ_ONCE这个宏。

#defineREAD_ONCE(x) __READ_ONCE(x, 1)
#define__READ_ONCE(x, check)						\
({									\
	union { typeof(x) __val; char __c[1]; } __u;			\
	if (check)							\
		__read_once_size(&(x), __u.__c, sizeof(x));		\
	else								\
		__read_once_size_nocheck(&(x), __u.__c, sizeof(x));	\
	__u.__val;							\
})
void __read_once_size(constvolatilevoid *p, void *res, intsize)
{
	__READ_ONCE_SIZE;
}
void __read_once_size(constvolatilevoid *p, void *res, intsize)
{
	__READ_ONCE_SIZE;
}
#define__READ_ONCE_SIZE						\
({									\
	switch (size) {							\
	case 1: *(__u8 *)res = *(volatile __u8 *)p; break;		\
	case 2: *(__u16 *)res = *(volatile __u16 *)p; break;		\
	case 4: *(__u32 *)res = *(volatile __u32 *)p; break;		\
	case 8: *(__u64 *)res = *(volatile __u64 *)p; break;		\
	default:							\
		barrier();						\
		__builtin_memcpy((void *)res, (constvoid *)p, size);	\
		barrier();						\
	}								\
})

READ_ONCE宏是为了实现变量的一次性读。同理还有WRITE_ONCE宏。

还有一些操作用到了asm关键字内嵌汇编的做法,针对不同架构直接调用汇编语句,比如x86架构下的atomic_add这个函数:

static __always_inline void atomic_add(int i, atomic_t *v)
{
	asm volatile(LOCK_PREFIX "addl %1,%0"
		: "+m" (v->counter)
		: "ir" (i));
}

相当于用汇编语言执行

addl i, v->counter

关键字volatile确保了编译器不会对这个语句进行优化,即不会将这个语句拆分为多个汇编语句。在上面这个例子中,通过添加addl语句的前缀LOCK_PREFIX,保证多处理器的情形下调用x86架构下的原子addl汇编语句,而单处理器的情况下直接调用addl就足够了。顺便说一句,目前绝大多数架构的读-修改-写操作都是支持原子操作的。

LOCK_PREFIX前缀被定义为:

#ifdef CONFIG_SMP
#define LOCK_PREFIX_HERE \
		".pushsection .smp_locks,\"a\"\n"	\
		".balign 4\n"				\
		".long 671f - .\n"/* offset */		\
		".popsection\n"				\
		"671:"
#define LOCK_PREFIX LOCK_PREFIX_HERE "\n\tlock; "
#else/* ! CONFIG_SMP */
#defineLOCK_PREFIX_HERE""
#defineLOCK_PREFIX""
#endif

即,在单处理器的情况下,LOCK_PREFIX为空,不需要前缀(因为单处理器的操作不会被其他处理器打断)。多处理器的情况下,LOCK_PREFIX是lock前缀。即调用x86架构的原子操作lockaddl。

64位的原子操作类似,只是在函数名上加了“64”,功能不变。

除了这些对整形的基本运算,还有原子位操作,接口在<asm/bitops.h>中。

原子位操作接口

说明

void set_bit(long nr, volatile unsigned long *addr)

设置addr地址的第nr位

void clear_bit(long nr, volatile unsigned long *addr)

清空addr地址的第nr位

void change_bit(long nr, volatile unsigned long *addr)

翻转addr地址的第nr位

bool test_and_set_bit(long nr, volatile unsigned long *addr)

设置addr地址的第nr位,并返回原来的值

bool test_and_set_bit_lock(long nr, volatile unsigned long *addr)

同上

bool test_and_clear_bit(long nr, volatile unsigned long *addr)

清空addr地址的第nr位,并返回原来的值

bool test_and_change_bit(long nr, volatile unsigned long *addr)

翻转addr地址的第nr位,并返回原来的值

test_bit(nr, addr)

宏,返回addr的第nr位是否为1


这些函数的实现方法同样是采用内嵌汇编调用底层架构的原子操作,思想与前面介绍的函数一致。

原子操作在操作系统中的使用。

       在<linux/mutex.h>中,互斥体mutex就用到了atomic_t。

structmutex {
	/* 1: unlocked, 0: locked, negative: locked, possible waiters */
	atomic_t		count;
	spinlock_t		wait_lock;
	structlist_head	wait_list;
#ifdefined(CONFIG_DEBUG_MUTEXES) || defined(CONFIG_MUTEX_SPIN_ON_OWNER)
	struct task_struct	*owner;
#endif
#ifdef CONFIG_MUTEX_SPIN_ON_OWNER
	struct optimistic_spin_queue osq; /* Spinner MCS lock */
#endif
#ifdef CONFIG_DEBUG_MUTEXES
	void			*magic;
#endif
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map	dep_map;
#endif
};

在这里,上锁标记位的操作必须是原子的,因此采用atomic_t类型的变量。

       在<linux/Core.h>中,多功能设备(MFD)的结构体mdf_cell也用到了atomic_t。

structmfd_cell {
	constchar		*name;
	int			id;
	/* refcounting for multiple drivers to use a single cell */
	atomic_t		*usage_count;
	int(*enable)(structplatform_device *dev);
	int(*disable)(structplatform_device *dev);

	int(*suspend)(structplatform_device *dev);
	int(*resume)(structplatform_device *dev);
	//...
}

usage_count用于记录该cell被使用的情况。

除此之外,在devices、nfs等很多地方都用到了atomic_t,都是用来保证操作的原子性。下面介绍的自旋锁也同样用到了原子操作。在<linux/Spinlock.h>中:

extern int _atomic_dec_and_lock(atomic_t *atomic,spinlock_t *lock);

该函数的作用是将atomic原子地递减1,如果结果为0,将lock上锁并返回true,否则返回false。

  • 3
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
嵌入式实时操作系统μC/OS-III是一款非常流行且广泛应用的实时操作系统。它被设计用于嵌入式系统中,满足实时性要求高的应用程序的需求。 uCos-III的电子书是对该实时操作系统的详细介绍和使用指南。这本电子书可以帮助读者了解嵌入式系统的基本概念、实时操作系统的工作原理和应用开发方面的技术。它提供了一系列有关uCos-III的知识和实例,使读者能够快速了解和掌握该实时操作系统的使用方法。 这本电子书主要涵盖以下内容: 1. uCos-III的基本概念:介绍了实时操作系统的定义、特点和应用领域,让读者了解操作系统在嵌入式系统中的重要性和作用。 2. uCos-III的架构和特性:介绍了uCos-III的体系结构和主要特性,如任务调度、时钟管理、内存管理、同步与通信等。 3. uCos-III的安装和配置:详细介绍了如何安装和配置uCos-III实时操作系统,包括编译器设置、硬件支持、内核配置等。 4. uCos-III的任务管理:讲解了任务的创建、删除、挂起和恢复等管理操作,同时介绍了任务优先级和实时调度算法。 5. uCos-III的管理服务和通信机制:介绍了信号量、互斥锁、消息队列、事件标志等管理服务和通信机制,帮助读者实现任务间的同步和通信。 6. uCos-III的中断处理和硬件驱动:讲解了中断处理的机制和方法,以及如何编写硬件驱动程序与uCos-III集成。 这本电子书适合嵌入式系统开发人员、学生以及对实时操作系统感兴趣的人阅读。通过学习这本电子书,读者可以准确理解uCos-III实时操作系统概念和工作原理,并掌握如何使用uCos-III开发和调试嵌入式应用程序。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值