linux同步之原子操作原理_,Activity的6大难点你会几个

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

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));
}


### 2、LOCK\_PREFIX



// arch/x86/include/asm/alternative.h

#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 */
#define LOCK_PREFIX_HERE “”
#define LOCK_PREFIX “”
#endif



\n:表示换行符
\t:表示将输出位置跳到下一个tab(制表)位置


对于UP单处理器,LOCK\_PREFIX宏为空。


对于SMP多处理器,扩展LOCK\_PREFIX宏:



.pushsection .smp_locks,“a”
.balign 4
.long 671f - .
.popsection
671:
lock;


(1)  
 .pushsection .smp\_locks,“a” 下面的代码生成到 smp\_locks section 中,:  
 FLAG:A (alloc) allocatable,表示该section具有可分配内存的属性。



readelf -S acpi_pad.ko //查看一个模块的section headers信息


![在这里插入图片描述](https://img-blog.csdnimg.cn/e8e6442c657741a5ba472c0e2d289aaa.png)  
 smp\_locks section 就是该模块代码段中 所有 lock指令 的信息。


备注:.pushsection和.popsection这对 pseudo instruction通常需要配对使用,把这对 pseudo instruction之间的代码段链接到指定的section中,而其他代码还保留在原来的section中。  
 这里采用 .pushsection 和 .popsection,而不是 .section 是为了避免之后的代码或者数据被错误地加到这里新增的 section 中来。  
 这样就保证了.smp\_locks section中只有lock指令的地址。  
 (2)  
 balign 4 : 四字节对齐


(3)  
 .long 671f - . 将671 label 的地址置于.smp\_locks section中,而 label 671的地址即为:代码段lock指令的地址。(其实就是lock指令的指针)


(4)



671:
lock;


671lable :lock指针的地址。开始生成lock 前缀的指令。  
 上面已经说明 将671 label 的地址置于.smp\_locks section中,也就是将lock指针的地址置于.smp\_locks section中。


(5)  
 这段汇编代码在 .text 段生成一条 lock 指令前缀 0xf0(LOCK指令的操作码是0xF0),在 .smp\_locks section 生成四个字节的 lock 前缀的地址,链接的时候,所有的 .smp\_locks section合并起来,形成一个所有 lock 指令地址的数组,这样统计 .smp\_locks section 就能知道代码里有多少个加锁的指令被生成。


(6)  
 常见的锁前缀在一个单独的表中作为特殊情况处理,这个表是一个纯地址列表,没有替换的ptr和大小信息。这样可以使表的大小保持较小。也就是将text中将lock指针的地址置于.smp\_locks section中。


这样我们就可以从 smp\_locks section中知道 text 代码中所有带有 lock 指令前缀信息了。


### 3、源码分析


#### 3.1 module\_finalize


module\_finalize是一个与体系架构相关的函数,允许不同体系架构的实现执行特定于系统的结束工作。简单点来说就是模块加载的结束时调用的函数。



// /arch/x86/kernel/module.c

int module_finalize(const Elf_Ehdr *hdr,
const Elf_Shdr *sechdrs,
struct module *me)
{
const Elf_Shdr *s, *text = NULL, *alt = NULL, *locks = NULL,
*para = NULL;
char *secstrings = (void *)hdr + sechdrs[hdr->e_shstrndx].sh_offset;

for (s = sechdrs; s < sechdrs + hdr->e_shnum; s++) {
	if (!strcmp(".text", secstrings + s->sh_name))
		text = s;
	if (!strcmp(".altinstructions", secstrings + s->sh_name))
		alt = s;
	if (!strcmp(".smp\_locks", secstrings + s->sh_name))
		locks = s;
	if (!strcmp(".parainstructions", secstrings + s->sh_name))
		para = s;
}

if (alt) {
	/\* patch .altinstructions \*/
	void \*aseg = (void \*)alt->sh_addr;
	apply\_alternatives(aseg, aseg + alt->sh_size);
}
if (locks && text) {
	void \*lseg = (void \*)locks->sh_addr;
	void \*tseg = (void \*)text->sh_addr;
	alternatives\_smp\_module\_add(me, me->name,
				    lseg, lseg + locks->sh_size,
				    tseg, tseg + text->sh_size);
}

if (para) {
	void \*pseg = (void \*)para->sh_addr;
	apply\_paravirt(pseg, pseg + para->sh_size);
}

/\* make jump label nops \*/
jump\_label\_apply\_nops(me);

return 0;

}


#### 3.2 alternatives\_smp\_module\_add


对于上面这段代码我们重点关注这部分,如果模块有.text 和 .smp\_locks section 就调用 alternatives\_smp\_module\_add 函数。



(1)
if (locks && text) {
	void \*lseg = (void \*)locks->sh_addr;
	void \*tseg = (void \*)text->sh_addr;
	alternatives\_smp\_module\_add(me, me->name,
				    lseg, lseg + locks->sh_size,
				    tseg, tseg + text->sh_size);
}

(2)
/\* make jump label nops \*/
jump\_label\_apply\_nops(me);


// arch/x86/kernel/alternative.c

#ifdef CONFIG_SMP
struct smp_alt_module {
/* what is this ??? */
struct module *mod;
char *name;

/\* ptrs to lock prefixes \*/
const s32	\*locks;
const s32	\*locks_end;

/\* .text segment, needed to avoid patching init code ;) \*/
u8		\*text;
u8		\*text_end;

struct list\_head next;

};
static LIST_HEAD(smp_alt_modules);
static DEFINE_MUTEX(smp_alt);
static bool uniproc_patched = false; /* protected by smp_alt */

void __init_or_module alternatives_smp_module_add(struct module *mod,
char *name,
void *locks, void *locks_end,
void *text, void *text_end)
{
struct smp_alt_module *smp;

mutex\_lock(&smp_alt);
if (!uniproc_patched)
	goto unlock;

if (num\_possible\_cpus() == 1)
	/\* Don't bother remembering, we'll never have to undo it. \*/
	goto smp_unlock;

smp = kzalloc(sizeof(\*smp), GFP_KERNEL);
if (NULL == smp)
	/\* we'll run the (safe but slow) SMP code then ... \*/
	goto unlock;

smp->mod	= mod;
smp->name	= name;
smp->locks	= locks;
smp->locks_end	= locks_end;
smp->text	= text;
smp->text_end	= text_end;
DPRINTK("%s: locks %p -> %p, text %p -> %p, name %s\n",
	\_\_func\_\_, smp->locks, smp->locks_end,
	smp->text, smp->text_end, smp->name);

list\_add\_tail(&smp->next, &smp_alt_modules);

smp_unlock:
alternatives_smp_unlock(locks, locks_end, text, text_end);
unlock:
mutex_unlock(&smp_alt);
}

#endif /* CONFIG_SMP */


如果是多处理器:



list_add_tail(&smp->next, &smp_alt_modules);


如果是单处理器,将锁前缀转换为DS段覆盖前缀:



if (num_possible_cpus() == 1)
/* Don’t bother remembering, we’ll never have to undo it. */
goto smp_unlock;

smp_unlock:
alternatives_smp_unlock(locks, locks_end, text, text_end);



static void alternatives_smp_unlock(const s32 *start, const s32 *end,
u8 *text, u8 *text_end)
{
const s32 *poff;

mutex\_lock(&text_mutex);
for (poff = start; poff < end; poff++) {
	u8 \*ptr = (u8 \*)poff + \*poff;

	if (!\*poff || ptr < text || ptr >= text_end)
		continue;
	/\* turn lock prefix into DS segment override prefix \*/
	if (\*ptr == 0xf0)
		text\_poke(ptr, ((unsigned char []){0x3E}), 1);
}
mutex\_unlock(&text_mutex);

}



// /arch/x86/include/asm\nops.h

#define NOP_DS_PREFIX 0x3e



0xf0 -> 0x3E :把 lock prefix 换成 DS override prefix


从函数名我们就可以知道,如果是单处理器,就将加锁前缀的指令解锁。即:即使内核配置了 smp,但是实际运行到单处理器上时,通过运行期间打补丁,根据 .smp\_locks 里的记录,把 lock 指令前缀替换成 DS override prefix(nop指令) 以消除指令加锁的开销。


相对应有一个加锁的函数:



static void alternatives_smp_lock(const s32 *start, const s32 *end,
u8 *text, u8 *text_end)
{
const s32 *poff;

mutex\_lock(&text_mutex);
for (poff = start; poff < end; poff++) {
	u8 \*ptr = (u8 \*)poff + \*poff;

	if (!\*poff || ptr < text || ptr >= text_end)
		continue;
	/\* turn DS segment override prefix into lock prefix \*/
	if (\*ptr == 0x3e)
		text\_poke(ptr, ((unsigned char []){0xf0}), 1);
}
mutex\_unlock(&text_mutex);

}


把 DS segment override prefix 替换成 lock prefix。


Instruction Prefixes  
 指令前缀分为四组,每组有一组可允许的前缀码。对于每条指令,只需要从四组(组1、2、3、4)中的每一组中包含一个前缀码就可以了。这里我只关注 LOCK prefix (F0H)和 DS segment override prefix(3EH)。  
 更多信息可参考:Intel 手册 2.1.1 Instruction Prefixes。



• Group 1
Lock and repeat prefixes:
• LOCK prefix is encoded using F0H.



• Group 2
— Segment override prefixes:
• 3EH—DS segment override prefix (use with any branch instruction is reserved).


#### 3.3 jump\_label\_apply\_nops


遍历该模块的所有jump\_entry条目,传递参数:JUMP\_LABEL\_DISABLE。将模块的jump\_entry条目填充nop空字节。  
 ![在这里插入图片描述](https://img-blog.csdnimg.cn/3ab29ff3d01745459d2e52671d9eea83.png)



W (write), A (alloc)



// /kernel/jump_label.c

/***
* apply_jump_label_nops - patch module jump labels with arch_get_jump_label_nop()
* @mod: module to patch
*
* Allow for run-time selection of the optimal nops. Before the module
* loads patch these with arch_get_jump_label_nop(), which is specified by
* the arch specific jump label code.
*/
void jump_label_apply_nops(struct module *mod)
{
struct jump_entry *iter_start = mod->jump_entries;
struct jump_entry *iter_stop = iter_start + mod->num_jump_entries;
struct jump_entry *iter;

/\* if the module doesn't have jump label entries, just return \*/
if (iter_start == iter_stop)
	return;

//遍历该模块的所有jump\_entry条目,注意这里传递的参数是JUMP\_LABEL\_DISABLE
for (iter = iter_start; iter < iter_stop; iter++) {
	arch\_jump\_label\_transform\_static(iter, JUMP_LABEL_DISABLE);
}

}



// arch/x86/include/asm/jump_label.h

#ifdef CONFIG_X86_64
typedef u64 jump_label_t;
#else
typedef u32 jump_label_t;
#endif

struct jump_entry {
jump_label_t code;
jump_label_t target;
jump_label_t key;
};

// /include/linux/jump_label.h
enum jump_label_type {
JUMP_LABEL_DISABLE = 0,
JUMP_LABEL_ENABLE,
};



// /include/linux/module.h

struct module
{

#ifdef HAVE_JUMP_LABEL
struct jump_entry *jump_entries;
unsigned int num_jump_entries;
#endif



/*
* Update code which is definitely not currently executing.
* Architectures which need heavyweight synchronization to modify
* running code can override this to make the non-live update case
* cheaper.
*/
void __weak __init_or_module arch_jump_label_transform_static(struct jump_entry *entry,
enum jump_label_type type)
{
arch_jump_label_transform(entry, type);
}



// /arch/x86/kernel/jump_label.c

#define JUMP_LABEL_NOP_SIZE 5

#define ASM_NOP_MAX 8
#define NOP_ATOMIC5 (ASM_NOP_MAX+1) /* Entry for the 5-byte atomic NOP */

union jump_code_union {
char code[JUMP_LABEL_NOP_SIZE];
struct {
char jump;
int offset;
} __attribute__((packed));
};

// 由于传递的参数是JUMP_LABEL_DISABLE,模块的jump_entry条目用nop指令替代
static void __jump_label_transform(struct jump_entry *entry,
enum jump_label_type type,
void *(*poker)(void *, const void *, size_t))
{
union jump_code_union code;

//如果type == JUMP\_LABEL\_ENABLE,jump\_entry是jump指令
if (type == JUMP_LABEL_ENABLE) {
	code.jump = 0xe9;
	code.offset = entry->target -
			(entry->code + JUMP_LABEL_NOP_SIZE);
			
//如果type == JUMP\_LABEL\_DISABLE,jump\_entry是nop指令
} else
	memcpy(&code, ideal_nops[NOP_ATOMIC5], JUMP_LABEL_NOP_SIZE);

//替换模块jump\_entry的code成员
(\*poker)((void \*)entry->code, &code, JUMP_LABEL_NOP_SIZE);

}

void arch_jump_label_transform(struct jump_entry *entry,
enum jump_label_type type)
{
get_online_cpus();
mutex_lock(&text_mutex);
__jump_label_transform(entry, type, text_poke_smp);
mutex_unlock(&text_mutex);
put_online_cpus();
}



// /arch/x86/kernelalternative.c

/**
* text_poke_smp - Update instructions on a live kernel on SMP
* @addr: address to modify
* @opcode: source of the copy
* @len: length to copy
*
* Modify multi-byte instruction by using stop_machine() on SMP. This allows
* user to poke/set multi-byte text on SMP. Only non-NMI/MCE code modifying
* should be allowed, since stop_machine() does _not_ protect code against
* NMI and MCE.
*
* Note: Must be called under get_online_cpus() and text_mutex.
*/
void *__kprobes text_poke_smp(void *addr, const void *opcode, size_t len)
{
struct text_poke_params tpp;
struct text_poke_param p;

p.addr = addr;
p.opcode = opcode;
p.len = len;
tpp.params = &p;
tpp.nparams = 1;
atomic\_set(&stop_machine_first, 1);
wrote_text = 0;
/\* Use \_\_stop\_machine() because the caller already got online\_cpus. \*/
\_\_stop\_machine(stop_machine_text_poke, (void \*)&tpp, cpu_online_mask);
return addr;

}


### 4、LOCK指令


![在这里插入图片描述](https://img-blog.csdnimg.cn/bc31b16aa7ae405aabdcb894dd6fae8c.png)  
 asserted:可以理解为发出信号。


使处理器的 LOCK# 信号在伴随指令的执行期间 be asserted(将指令转换为原子指令)。在多处理器环境中,LOCK# 信号确保处理器在信号被 asserted 时独占使用任何共享内存。


X86 CPU 上都具有锁定一个特定内存地址的能力,当这个特定内存地址被锁定后,它就可以阻止其他的系统总线读取或修改这个内存地址。这种能力是通过 LOCK 指令前缀再加上下面的汇编指令来实现的。当使用 LOCK 指令前缀时,它会使 CPU 发送一个 LOCK# 信号,这样就能确保在多处理器系统或多线程竞争的环境下互斥地使用这个内存地址。当指令执行完毕,这个锁定动作也就会消失。


LOCK 前缀只能添加到以下指令,并且只能添加到目标操作数是内存操作数的那些指令形式:ADD、ADC、AND、BTC、BTR、BTS、CMPXCHG、CMPXCH8B、CMPXCHG16B、DEC、INC、 NEG、NOT、OR、SBB、SUB、XOR、XADD 和 XCHG。如果LOCK前缀与这些指令中的一个一起使用,并且源操作数是内存操作数,则可能会生成一个未定义的操作码异常(#UD)。如果 LOCK 前缀与任何不在上述列表中的指令一起使用,也会产生未定义的操作码异常。无论是否存在 LOCK 前缀,XCHG 指令始终 assert LOCK# 信号。


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
![img](https://img-blog.csdnimg.cn/img_convert/b693566f68c649be0432e76206031b44.png)

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

 信号,这样就能确保在多处理器系统或多线程竞争的环境下互斥地使用这个内存地址。当指令执行完毕,这个锁定动作也就会消失。


LOCK 前缀只能添加到以下指令,并且只能添加到目标操作数是内存操作数的那些指令形式:ADD、ADC、AND、BTC、BTR、BTS、CMPXCHG、CMPXCH8B、CMPXCHG16B、DEC、INC、 NEG、NOT、OR、SBB、SUB、XOR、XADD 和 XCHG。如果LOCK前缀与这些指令中的一个一起使用,并且源操作数是内存操作数,则可能会生成一个未定义的操作码异常(#UD)。如果 LOCK 前缀与任何不在上述列表中的指令一起使用,也会产生未定义的操作码异常。无论是否存在 LOCK 前缀,XCHG 指令始终 assert LOCK# 信号。


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
[外链图片转存中...(img-B6gEqjQO-1713307899204)]

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值