development

x86, arm, GCC 및 icc에서 작동하는 Linux에서 원자 연산을 수행하는 방법은 무엇입니까?

big-blog 2021. 1. 8. 22:48
반응형

x86, arm, GCC 및 icc에서 작동하는 Linux에서 원자 연산을 수행하는 방법은 무엇입니까?


모든 Modern OS는 오늘날 몇 가지 원자 적 작업을 제공합니다.

  • Windows에는 Interlocked*API가 있습니다.
  • FreeBSD는 <machine/atomic.h>
  • Solaris는 <atomic.h>
  • Mac OS X에는 <libkern/OSAtomic.h>

Linux에서 이와 비슷한 것이 있습니까?

  • x86, x86_64 및 arm을 포함한 대부분의 Linux 지원 플랫폼에서 작동하려면이 기능이 필요합니다 .
  • 적어도 GCC 및 인텔 컴파일러에서 작업하려면이 파일이 필요합니다.
  • glib 또는 qt와 같은 3rd par 라이브러리를 사용할 필요가 없습니다.
  • C ++에서 작동하려면 필요합니다 (C는 필요하지 않음).

문제 :

  • GCC 원자 내장 기능 __sync_*은 모든 플랫폼 (ARM)에서 지원되지 않으며 인텔 컴파일러에서 지원되지 않습니다.
  • AFAIK <asm/atomic.h>는 사용자 공간에서 사용해서는 안되며 성공적으로 사용하지 않았습니다. 또한 인텔 컴파일러에서 작동하는지 확실하지 않습니다.

어떤 제안?

나는 많은 관련 질문이 있다는 것을 알고 있지만 그중 일부는 __sync*나에게 적합하지 않은 (ARM)을 가리키고 일부는 asm/atomic.h.

GCC를 위해 이것을 수행하는 인라인 어셈블리 라이브러리가있을 수 있습니다 (ICC는 gcc 어셈블리를 지원합니다)?

편집하다:

추가 작업만을위한 매우 부분적인 솔루션이 있습니다 (원자 카운터 구현은 허용하지만 CAS가 필요한 잠금 구조는 허용하지 않음).

당신이 사용하는 경우 libstc++(인텔 컴파일러의 사용을 libstdc++) 당신은 사용할 수 있습니다 __gnu_cxx::__exchange_and_add그 정의 <ext/atomicity.h><bits/atomicity.h>. 컴파일러 버전에 따라 다릅니다.

그러나 여전히 CAS를 지원하는 것을보고 싶습니다.


프로젝트는 이것을 사용하고 있습니다 :

http://packages.debian.org/source/sid/libatomic-ops

CAS와 같은 간단한 작업을 원한다면 커널에서 아치 특정 구현을 사용하고 autotools / cmake를 사용하여 사용자 공간에서 아치 검사를 수행 할 수 없습니까? 라이센스에 관한 한 커널은 GPL이지만 커널에 라이센스가있는 것이 아니라 이러한 작업을위한 인라인 어셈블리가 Intel / AMD에서 제공된다는 것이 논쟁의 여지가 있다고 생각합니다. 커널 소스에서 쉽게 접근 할 수있는 형태 일뿐입니다.


C & C ++의 최근 표준 (2011 년부터)은 이제 원자 연산을 지정합니다.

그럼에도 불구하고 플랫폼 또는 컴파일러는 이러한 최신 헤더 및 기능을 지원하지 않을 수 있습니다.


꿰매다. 나는 GCC 프리미티브를 제안하려고했는데 당신은 그들이 한계를 벗어났다고 말했습니다. :-)

이 경우 #ifdef관심있는 각 아키텍처 / 컴파일러 조합에 대해 수행하고 인라인 asm을 코딩합니다. 그리고 어쩌면 확인 __GNUC__또는 이와 유사한 매크로 그들이 사용할 수있는 경우가 훨씬 더 잘들을 사용하는 느낌 때문에 GCC 프리미티브를 사용합니다. :-)

중복이 많고 정확성을 확인하는 것이 어려울 수 있지만 이것이 많은 프로젝트에서이 작업을 수행하는 방식 인 것 같고 좋은 결과를 얻었습니다.

과거에 비트 날이 몇 개는 : GCC를 사용하는 경우, "잊지 마세요 "를위한 쳤을 asm volatile"memory""cc"


비침 입적 라이선스가있는 Boost와 다른 프레임 워크는 대상 플랫폼에서 지원되는 한 이미 휴대용 원자 카운터를 제공합니다.

타사 라이브러리는 우리에게 좋습니다. 그리고 이상한 이유로 회사에서 사용을 금지 한 경우에도 원하는 것을 구현하기 위해 (라이센스가 허용하는 한) 어떻게 진행되는지 확인할 수 있습니다.


나는 최근에 그런 일을 구현했고 당신과 같은 어려움에 직면했습니다. 내 솔루션은 기본적으로 다음과 같습니다.

  • 기능 매크로로 gcc 내장을 감지하십시오.
  • 다만 같은 구현을 사용할 수없는 경우 cmpxch__asm__다른 아키텍처를 (ARM은 좀 더 복잡보다)입니다. 가능한 한 가지 크기에 대해 수행하십시오 sizeof(int).
  • 모든 다른 하나의 상단에 기능 또는 두 개의 프리미티브 구현 inline기능을

ARM 원자 연산을 지원하는 GCC 용 패치가 있습니다. 인텔에서는 도움이되지 않지만 코드를 살펴볼 수 있습니다. 최신 ARM 아키텍처에 대한 최신 커널 지원이 있고 최신 커널에는 명령어가 내장되어 있으므로 작동하는 것을 빌드 할 수 있습니다.

http://gcc.gnu.org/ml/gcc-patches/2011-07/msg00050.html


__sync*GCC에서 이러한 빌드를 채택했기 때문에 인텔 컴파일러가 확실히 지원하고 있습니다. 이 페이지 의 첫 번째 단락 읽으십시오 . 또한 " Linux * 용 인텔 ® C ++ 컴파일러 참조 ", 198 페이지를 참조하십시오. 2006 년에 작성 되었으며 이러한 내장 기능에 대해 정확히 설명합니다.

ARM 지원과 관련하여, 구형 ARM CPU의 경우 : 사용자 공간에서 완전히 수행 할 수는 없지만 커널 공간에서 수행 할 수 있습니다 (작업 중에 인터럽트를 비활성화하여). 지금은 꽤 오랫동안 지원되는 곳에서 읽은 것 같습니다.

이 PHP 버그 에 따르면 2011-10-08 날짜 __sync_*

  • Linux 이외의 다른 제품을 사용하는 PA-RISC
  • SPARCv7 이하
  • GCC가 4.3 미만인 ARM
  • Linux 이외의 ARMv5 이하
  • MIPS1

따라서 GCC> 4.3 (및 4.7이 현재 버전 임)에서는 ARMv6 이상에서 문제가 없어야합니다. Linux 용으로 컴파일하는 한 ARMv5에서도 문제가 없어야합니다.


Debian / Ubuntu 권장 사항 ...

sudo apt-get libatomic-ops-dev 설치

예 : http://www.hpl.hp.com/research/linux/atomic_ops/example.php4

GCC 및 ICC 호환.

compared to Intel Thread Building Blocks (TBB), using atomic< T >, libatomic-ops-dev is over twice as fast! (Intel compiler)

Testing on Ubuntu i7 producer-consumer threads piping 10 million ints down a ring buffer connection in 0.5secs as opposed to 1.2secs for TBB

And easy to use e.g.

volatile AO_t head;

AO_fetch_and_add1(&head);


See: kernel_user_helpers.txt or entry-arm.c and look for __kuser_cmpxchg. As seen in comments of other ARM Linux versions,

kuser_cmpxchg

Location:       0xffff0fc0

Reference prototype:

  int __kuser_cmpxchg(int32_t oldval, int32_t newval, volatile int32_t *ptr);

Input:

  r0 = oldval
  r1 = newval
  r2 = ptr
  lr = return address

Output:

  r0 = success code (zero or non-zero)
  C flag = set if r0 == 0, clear if r0 != 0

Clobbered registers:

  r3, ip, flags

Definition:

  Atomically store newval in *ptr only if *ptr is equal to oldval.
  Return zero if *ptr was changed or non-zero if no exchange happened.
  The C flag is also set if *ptr was changed to allow for assembly
  optimization in the calling code.

Usage example:
 typedef int (__kuser_cmpxchg_t)(int oldval, int newval, volatile int *ptr);
 #define __kuser_cmpxchg (*(__kuser_cmpxchg_t *)0xffff0fc0)

 int atomic_add(volatile int *ptr, int val)
 {
        int old, new;

        do {
                old = *ptr;
                new = old + val;
        } while(__kuser_cmpxchg(old, new, ptr));

        return new;
}

Notes:

  • This routine already includes memory barriers as needed.
  • Valid only if __kuser_helper_version >= 2 (from kernel version 2.6.12).

This is for use with Linux with ARMv3 using the swp primitive. You must have a very ancient ARM not to support this. Only a data abort or interrupt can cause the spinning to fail, so the kernel monitors for this address ~0xffff0fc0 and performs a user space PC fix-up when either a data abort or an interrupt occurs. All user-space libraries that support ARMv5 and lower will use this facility.

For instance, QtConcurrent uses this.

ReferenceURL : https://stackoverflow.com/questions/2287451/how-to-perform-atomic-operations-on-linux-that-work-on-x86-arm-gcc-and-icc

반응형