C ++ 14에서 공유 메모리에 멀티 바이트 값을 쓰는 방법은 무엇입니까?

Nov 20 2020

나는 모두 공유 메모리 블록을 사용하여 두 개의 프로세스가 있다고 가정 shm_open하고 mmap및 공유 동기화가 원시적 인 존재 -하자가 세마포어를 말한다 - 그 기억을 보장 단독 액세스가. 즉 경쟁 조건이 없습니다.

내 이해는에서 반환 된 포인터가 mmap캐시 된 읽기를 방지하기 위해 여전히 휘발성으로 표시되어야한다는 것입니다.

이제 어떻게 std::uint64_t메모리의 정렬 된 위치에 a 를 기록 합니까?

당연히 나는 단순히 사용 std::memcpy하지만 휘발성 메모리에 대한 포인터에서는 작동하지 않습니다.

첫번째 시도

// Pointer to the shared memory, assume it is aligned correctly.
volatile unsigned char* ptr;

// Value to store, initialize "randomly" to prevent compiler
// optimization, for testing purposes.
std::uint64_t value = *reinterpret_cast<volatile std::uint64_t*>(nullptr);

// Store byte-by-byte
unsigned char* src = reinterpret_cast<unsigned char*>(&value);
for(std::size_t i=0;i<sizeof(value);++i)
    ptr[i]=src[i];

Godbolt .

이 솔루션이 정확하다고 강력히 믿지만에서도 -O38 개의 1 바이트 전송이 있습니다. 정말 최적이 아닙니다.

두 번째 시도

내가 잠겨있는 동안 아무도 메모리를 변경하지 않을 것이라는 것을 알기 때문에 결국 휘발성이 필요하지 않을까요?

// Pointer to the shared memory, assume it is aligned correctly.
volatile unsigned char* ptr;

// Value to store, initialize "randomly" to prevent compiler
// optimization for testing purposes.
std::uint64_t value = *reinterpret_cast<volatile std::uint64_t*>(0xAA);
unsigned char* src = reinterpret_cast<unsigned char*>(&value);

//Obscure enough?
auto* real_ptr = reinterpret_cast<unsigned char*>(reinterpret_cast<std::uintptr_t>(ptr));

std::memcpy(real_ptr,src,sizeof(value));

Godbolt .

그러나 이것은 작동하지 않는 것 같습니다. 컴파일러는 캐스트를 통해보고 아무것도하지 않습니다. Clang ud2은 왜 내 코드에 UB가 있는지 확실하지 않은 지침을 생성 합니까? value초기화 외에 .

세 번째 시도

이것은 이 답변 에서 나옵니다 . 그러나 나는 그것이 엄격한 앨리어싱 규칙을 위반한다고 생각합니다.

// Pointer to the shared memory, assume it is aligned correctly.
volatile unsigned char* ptr;

// Value to store, initialize "randomly" to prevent compiler
// optimization for testing purposes.
std::uint64_t value = *reinterpret_cast<volatile std::uint64_t*>(0xAA);
unsigned char* src = reinterpret_cast<unsigned char*>(&value);

volatile std::uint64_t* dest = reinterpret_cast<volatile std::uint64_t*>(ptr);
*dest=value;

Godbolt .

Gcc는 실제로 내가 원하는대로 64 비트 값을 복사하는 간단한 명령입니다. 하지만 UB라면 쓸모가 없습니다.

내가 그것을 고칠 수있는 한 가지 방법은 실제로 std::uint64_t그 장소에 물건을 만드는 것입니다. 그러나 분명히 새로운 배치는 volatile포인터에서도 작동하지 않습니다 .

질문

  • 그렇다면 바이트 단위 복사보다 더 나은 (안전한) 방법이 있습니까?
  • 또한 더 큰 원시 바이트 블록을 복사하고 싶습니다. 개별 바이트보다 더 잘 할 수 있습니까?
  • 강제로 memcpy옳은 일 을 할 가능성이 있습니까?
  • 성능에 대해 불필요하게 걱정하고 루프를 따라 가야합니까?
  • 어떤 예제 (대부분 C)도 전혀 사용하지 않습니다. volatile저도 그렇게해야합니까? mmaped 포인터 는 이미 다르게 취급됩니까? 어떻게?

제안 해 주셔서 감사합니다.

편집하다:

두 프로세스 모두 동일한 시스템에서 실행됩니다. 또한 포인터를 어딘가에 저장하는 복잡한 가상 클래스에 대해 이야기하지 않고 값을 바이트 단위로 복사 할 수 있다고 가정하십시오. 모든 정수와 부동 소수점은 괜찮을 것입니다.

답변

5 Useless Nov 20 2020 at 01:03

내 이해는 mmap에서 반환 된 포인터가 캐시 된 읽기를 방지하기 위해 여전히 휘발성으로 표시되어야한다는 것입니다.

이해가 잘못되었습니다. volatile메모리 가시성을 제어 하는 데 사용하지 마십시오 . 그것이 목적이 아닙니다. 불필요하게 비싸거나 엄격하지 않거나 둘 다입니다.

예를 들어 volatile에 대한 GCC 문서를 살펴 보겠습니다 .

비 휘발성 개체에 대한 액세스는 휘발성 액세스와 관련하여 정렬되지 않습니다. 비 휘발성 메모리에 쓰기 순서를 지정하기 위해 휘발성 개체를 메모리 장벽으로 사용할 수 없습니다.

찢어짐, 캐싱 및 재정렬을 피하려면 대신 사용하십시오. 예를 들어 기존 공유가 있고 uint64_t올바르게 정렬 된 경우 std::atomic_ref. 이를 통해 직접 획득, 릴리스 또는 CAS를 사용할 수 있습니다.

정상적인 동기화가 필요한 경우 기존 세마포어가 괜찮습니다. 아래와 같이 이미 필요한 펜스를 제공하고 대기 / 사후 호출에서 재정렬을 방지합니다. 그것은 재정렬 또는 다른 최적화를 방해하지 않는 사이 그들, 그러나 그것은 일반적으로 괜찮습니다.


에 관해서

어떤 예제 (대부분 C)도 휘발성을 전혀 사용하지 않습니다. 저도 그렇게해야합니까? mmaped 포인터가 이미 다르게 취급됩니까? 어떻게?

대답은 적절한 펜스를 적용하기 위해 사용되는 동기화가 무엇이든 필요하다는 것입니다.

POSIX 는 이러한 함수 를 "동기화 메모리"로 나열 합니다. 즉, 필요한 메모리 펜스를 내보내고 부적절한 컴파일러 재정렬을 방지해야합니다. 예를 들어, POSIX 호환이 되려면 구현에서 메모리 액세스를 pthread_mutex_*lock()또는 sem_wait()/ sem_post()호출 간에 이동하지 않아야합니다. 그렇지 않으면 합법적 인 C 또는 C ++ 인 경우에도 마찬가지입니다.

C ++의 기본 제공 스레드 또는 원자 지원을 사용할 때 올바른 의미 체계는 플랫폼 확장 대신 언어 표준의 일부입니다 (하지만 공유 메모리는 그렇지 않습니다).

3 DavidSchwartz Nov 20 2020 at 01:14

shm_open과 mmap을 사용하여 메모리 블록을 공유하는 두 개의 프로세스가 있고 메모리에 대한 배타적 액세스를 보장하는 공유 동기화 기본 요소 (세마포어)가 있다고 가정합니다. 즉 경쟁 조건이 없습니다.

메모리에 대한 배타적 액세스 이상의 것이 필요합니다. 메모리를 동기화해야합니다. 내가 본 모든 세마포는 이미 그것을 수행합니다. 그렇지 않은 경우 잘못된 동기화 기본 요소입니다. 다른 것으로 전환하십시오.

내 이해는 mmap에서 반환 된 포인터가 캐시 된 읽기를 방지하기 위해 여전히 휘발성으로 표시되어야한다는 것입니다.

Well volatile은 캐시 된 읽기를 방지하지 않지만 거의 모든 세마포어, 뮤텍스 및 기타 동기화 기본 요소는 캐시 된 읽기 및 쓰기를 차단 한 것처럼 작동합니다. 그렇지 않으면 사용이 거의 불가능합니다.

어떤 세마포를 사용하고 있습니까? 메모리를 동기화하지 않으면 작업에 잘못된 도구입니다.