linux指针赋值原子,x86_64处理器的指针赋值是原子操做吗?

如题, x86_64处理器的指针赋值是原子操做吗?web

说实话我很讨厌参与讨论那些彷佛不肯定东西,倒不是说我对未知不敬畏,而是参与讨论的人大多数都是似懂非懂,对,我说的不肯定性指的是参与讨论的人的认知的不肯定,若是你本身都似懂非懂,那么我说什么你均可以反驳我,说些 “貌似,可能,并不绝对” 的词汇来让事情变得混乱。数组

最近写了一篇文章:

https://blog.csdn.net/dog250/article/details/103911008

里面提到了一句:

8d1a0595d411596745d6f46e63e63bf4.png

而后有人提出了质疑,其实我这句话后面还有个引用呢:

6f88311a1cbab8915bfc40c0132fefd4.png

后面还夹杂了个人评价。bash

因此说,个人意思是, 以Intel的手册为准。 我以为我应该收回上面的那句 “which is a pointer assignment operation, on the x86_64 platform which is an atomic operation.” 而后从新说出个因此然来。网络

有了这段 权威描述 ,本文彷佛该结束了。布局

可是…性能

show me the code 是个金句,它鄙视的是 talk, 由于 talk is cheap.优化

那好吧,按照这样看来,上面那一段都是废话,很是cheap,那么下面就用代码说话吧。编码

声明一下:atom

如下无术语,也再也不引用任何cheap的东西。

本文虽然以“指针赋值”为例,但其实任何“int,long,long long”赋值均试用 。

我在代码里强转来强转去的,实际在暗示什么呢?CPU和内存不了解什么是指针,只有在执行的时候,一个unsigned long才会 变成 指针。

网络协议,序列化时,请 谨慎相信任何原子操做的承诺!

考虑一个指针p,两个或者多个线程分别对它进行赋值:

long *p;

// thread 1

p = a1;

// thread 2

p = a2

结果能够预期吗?若是你笃信指针赋值是原子操做,那么最终结果,p不是a1,即是a2,这是肯定的。

然而Intel手册里说,若是指针p跨越了cacheling的边界,便不能保证赋值操做是原子的,为了复现Intel的说法,从而证实指针赋值并不是原子的,只须要给出一个反例,即p既不是a1,也不是a2。

在编码以前,咱们先查一下本身实验的机器上的cacheline的大小:

root@zhaoya-VirtualBox:~/xdp/msvc# cat root@zhaoya-VirtualBox:~# cat /sys/devices/system/cpu/cpu1/cache/index0/coherency_line_size

64

OK,64字节的cacheline,咱们下面就制造一个跨越64字节边界的指针,直接看代码:

#include

#include

// 不让编译器自动扩充结构体对齐特征,这个在网络协议以及序列号操做中很常见。

#pragma pack(1)

struct pack {

// 60字节的padding

char unsued[60];

// cacheline仅剩下4字节,仅够装指针p的4字节,剩余4字节跨越到另外一个cacheline

long *p;

};

#pragma pack()

// 内存对齐粒度最小为1字节,我设置64个元素的数组,总有一个元素刚好是在64字节边界的!

struct pack pa[64];

// 保存64字节边界的元素

struct pack *used;

long a = 0x1122334455667788;

long b = 0x8877665544332211;

void *write_value1(void *param)

{

for (;;) {

used->p = (long *)a;

// 不让编译器优化

asm volatile("" ::: "memory");

}

return NULL;

}

void *write_value2(void *param)

{

for (;;) {

used->p =(long *)b;

asm volatile("" ::: "memory");

}

return NULL;

}

int main(int argc, char **argv)

{

int i;

long *p;

pthread_t t1, t2;

// for循环找到那个64字节边界的pack结构体元素

for (i = 0; i < 64; i++) {

unsigned long addr;

addr = (unsigned long)&pa[i];

if (addr%64 == 0) {

// 赋值给used

used = (struct pack *)addr;

break;

}

}

pthread_create(&t1, NULL, write_value1, NULL);

pthread_create(&t2, NULL, write_value2, NULL);

while (1) {

p = used->p;

// 咱们看能不能找到既不是a,又不是b的p

if (p != (long *)a && p != (long *)b) {

printf("%lx\n", (unsigned long)p);

}

}

return 0;

}

跑一下呗:

0d2dbdbe5ab5fb58c0829c798321b588.png

个人天!

这意味着什么?

这意味着不加锁的指针赋值,极其危险!

什么?难道编译器不帮忙?

编译器只能尽可能帮忙,哦,对了,还有Linux内核的伙伴系统,slab系统,都只是尽可能帮忙,其他的事,只能看造化。谁也不能保证你不会写出上面相似pack的结构体,所以结构体的字段布局很是重要。

即使这样,你也不能保证结构体对象刚好被载入你但愿的位置,只要超过一个cacheline大小的结构体,内部字段的赋值就必定当心再当心:

加锁影响性能

不加锁怕跨越边界

说白了这就是个手艺活。

若是你想快速复现指针跨越cacheline的赋值非原子性,直接加个修饰便可:

// 你也能够直接用aligned来固化地址对齐特征,然则不真实!

struct pack pa __attribute__ ((aligned(64)));

...

int main(...

...

used = &pa;

很少说了,深刻什么单条指令的原子执行,LOCK引脚,cache一致性原子操做粒度等等细节无益于解决实际问题,至于其它体系结构,摸都摸不到(我是说我本身),更显得纸上谈兵,到此为止了。

浙江温州皮鞋湿,下雨进水不会胖!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值