컴퓨터공부

ARM FAQ - 정렬/정렬안 된 (Aligned/Unaligned) 엑세스및 __packed 사용

achivenKakao 2009. 2. 23. 22:20

▷ 서론

다른 RISC 구조처럼, ARM 프로세스는 '정렬된 데이터'  즉 4의 배수의 주소에 있는 워드(word or fullword : 4바이트)
와 2의 배수에 있는 반워드(halfword : 2바이트) 를 효율적으로 엑세스하도록 설계되었다.
그런 데이터들은 자연 크기 경계(natural size boundary)에 존재한다고 한다.

!) 번역시 워드는 전단어로 반워드는 반단어로 사용하기도 한다.

ARM 컴파일러는 보통 LDR/STR 명령어를 이용 효율적인 데이터 엑세스를 가능하도록 자연 크기 경계에 전역 변수들을 정렬시킨다.

이것은 대부분의 CISC 아키텍쳐들에서 명령어가 '정렬되지 않은 데이터'를 직접적으로 엑세스 가능한 것과는 상반된 것으로, '정렬되지 않은 데이터'를 엑세스하는 기존 코드(legacy code)를 ARM 에 포팅시엔 주의가 요구된다.

 

▷ 정렬되지 않는 포인터

ARM 컴파일러는 보통 'C' 포인터가 정렬된 워드를 가리킨다고 예상하여, 더 효율적인 코드를 생성한다.
예를 들어, 워드를 읽도록 사용된 int 형 포인터의 경우 ARM은 LDR 명령어를 사용한다.

이것은 데이터 주소가 4의 배수 주소(워드 경계)에 존재한다고 가정하고 작동한다. 그러나, 주소가 4의 배수 주소에 있지 않다면, LDR은 실제 '정렬되지 않는 워드'를 로드하지 않고 회전(rotated) 된 결과를 리턴하게 된다.
실제 로테이트된 결과는 옵셋(offset)과 시스템의 엔디언에 따라 달라지게 된다.
예를 들어, 0x8006 번지를 가리키는 포인터가 0x8006, 0x8007, 0x8008, 0x8009에서 값을 로드할 것이라 기대하지만, 실제 ARM에서는 0x8004, 0x8005, 0x8006, 0x8007에서 로테이트된 값을 가져온다.

그러므로, 어떤 주소에도 있을 수 있는 워드(비자연 정렬)를 가리키는 포인터를 정의하고 싶다면 반드시 __packed 지시자를 사용해야 한다.

__packed int *pi; //pointer to unaligned int

이 경우 ARM 컴파일러는 LDR 대신 포인터의 정렬과는 무관하게 올바른 값을 엑세스하는 코드를 생성한다.
코드는 연속적인 바이트(워드 단위아닌) 엑세스를 사용하거나, 가변 정렬 의존적인 쉬프트와 매스킹을 사용하게 되어 시스템 성능과 코드사이즈에 좋지않은 영향을 주게된다.

한가지 주의할 점은 메모리 매핑(Memory-mapped)된 주변 레지스터 참조시에 __packed 를 사용한 포인터를 사용하면 안된다. ARM 컴파일러는 데이터 참조시 다중 메모리 엑세스 명령어를 사용하므로, 다른 주변 레지스터 값까지 참조할 수 있기 때문이다. 비트필트를 사용시 특정 비트필트외에 전체를 엑세스한다.


▷ 정렬되지 않는 구조체(Structure) 필드

전역변수들이 자연 크기 경계에 위치되듯 구조체 필드도 마찬가지다.
컴파일러는 필드의 정렬을 위해 필드사이에 패딩을 추가한다.

이것이 필요하지 않을 경우 정렬되지 않는 필드의 참조를 원할 경우 __packed 지시자를 사용할 수 있다.

컴파일러가 특정 구조체의 정렬을 안다면 팩트된 구조체내에서 엑세스 하려는 필드가 정렬되어 있는지 아닌지를 알 수 있다.
그런 경우, 가능하다면 정렬된 워드나 반워드를 효율적으로 엑세스할 수 있으나, 그렇지 않은 경우에는 고정 쉬프트나 매스킹을 이용한 다중 정렬 메모리 엑세스 명령어(LDR/STR/LDM/STM) 을 이용할 것이다.

이런 정렬되지 않는 메모리 엑세스가 inline으로 행해질지 함수 호출로 행해질 지는 컴파일 옵션 ( -Ospace : ROM 사이즈 절약을 위해 디폴트는 함수 호출) 과 -Otime ( 실행시간을 빠르게 하기 위해 inline 비정렬 엑세스 사용) 에 의해 결정된다.
다음의 예를 보자.

 

__packed struct mystruct
{

int aligned_i;
short aligned_s;
int unalighed_i;
};

struct mystruct S1;

int foo(inta, short b)
{
 S1.aligned_i = a;
 S1.aligned_s = b;
 return S1.unalighed_i;
}

armcc -c _Otime foo.c

비정렬된 구조체를 시간 우선 순위를 주어서 컴파일 했을 경우,

MOV r2,r0
LDR r0,|L1.84|
MOV r12,r2,LSR #8
STRB r2,[r0,#0] ; S1
STRB r12,[r0,#1] ; S1
MOV r12,r2,LSR #16
STRB r12,[r0,#2] ; S1
MOV r12,r2,LSR #24
STRB r12,[r0,#3] ; S1
STRB r1,[r0,#4] ; S1
MOV r12,r1,LSR #8
STRB r12,[r0,#5]
ADD r0,r0,#6
BIC r3,r0,#3
AND r0,r0,#3
MOV r0,r0,LSL #3
LDMIA r3,{r3,r12}
MOV r3,r3,LSR r0
RSB r0,r0,#0x20
ORR r0,r3,r12,LSL r0
MOV pc,lr

컴파일러에게 어느 필드가 정렬되었는지, 되지 않았는지 더 많은 정보를 제공함이 가능하다.
이렇게 하기 위해서는 구조체 자체의 '__packed' 속성을 제거하고, 정렬되지 않은 필드를에만 '__packed'로
선언해 주면된다.

이방법은 추천하는 접근법으로 구조체내 자연적으로 정렬된 멤버들에대한 빠른 엑세스를 보장하는
유일한 방법이다.
또한 개발자로 하여금 구조체로부터 필드를 추가/삭제시 어느 필드가 정렬되지 않아 주의가 필요한지를
더 명확하게 해준다.

변형된 구조체 선언이다.

struct mystruct
{
    int aligned_i;
    short aligned_s;
    __packed int unaligned_d;
};


MOV r2,r0
LDR r0,|L1.32|
STR r2,[r0,#0] ; S1
STRH r1,[r0,#4] ; S1
LDMIB r0,{r3,r12}
MOV r0,r3,LSR #16
ORR r0,r0,r12,LSL #16
MOV pc,lr

이렇게 했을 경우, 훨씬 효율적인 코드를 생성한다.
이 원칙은 유니언(union)에도 동일하게 적용되며, 유니언의 정렬되지 않는 멤버에만 __packed를 사용할 수 있다.

※ 주의: 팩트된 구조체라 할 지라도, 포인터를 통한 팩트된 오브젝트에의 엑세스는 정렬됨의 여부를 알 수 없다.


▷ 반단어(halfword) 엑세스를 위한 정렬되지 않는 LDR

어떤 환경에서 ARM 컴파일러는 의도적으로 정렬되지 않은 LDR 명령어를 생성한다.
특히 메모리에서 반단어를 로드하기 위해 사용되며, 이것은 적절한 주소를 사용함에 의해 요구된 반단어가
레지스터의 상단 바이트(Top Half)에 로드된 후 하단 바이트(Bottom Half)로 쉬프트되게 하기 위해서다.

이 방법은 LDRB를 이용한 2번 엑세스가 아닌 한번의 엑세스만으로 가능할 뿐 아니라, 명령어를 2바이트로 병합할 수 있다.
v3 및 이하 ARM 아키텍쳐에서는 반단어 엑세스를 위해 보편적으로 사용되었으나, v4 이상에서는 반단어 명령어가
존재하므로, 덜 사용된다. 하지만 여전히 정렬되지 않은 LDR 명령은 팩트된  구조체에서 정렬되지 않는 short를
참조할 때 생성될 수 있다.

비정렬 LDR은 '--memaccess +L41' 옵션을 사용해 ADS 에 의해서만 생성될 수 있다.


▷ 비정렬을 발견하고 포팅하는 법

x86 같은 다른 시스템(CISC)에서 정렬되지 않는 데이트를 참조하는 포인터를 가진 C 코드는 ARM에서는 작동하지 않는다.
그런 참조는 RISC에서 작동하기 위해서는 찾아서 수정해야 한다.

비정렬 데이터 엑세스를 발견하는 것은 어렵다. 어는 코드 부분이 문제를 일으키는 지 찾기도 어렵다.
ARM920T 같이 완전한 MMU(메모리 관리 유닛)를 가진 ARM 프로세서는 선택에 의해 정렬 체크 기능을 지원한다.
MMU는 비정렬된 데이터 참조시 데이터 어보트(data abort)를 일으킨다.

ARM7TDMI같은 코어를 가진 ASIC/ASSP의 경우 코어가 아닌 부가적인 하드웨어 블록을 통해 모든 데이터 엑세스시 엑세스
크기와 어드레스 버스의 최하위 비트를 감시하는 정렬 검사를 구현하기 한다.
정렬되지 않는 메모리 엑세스의 경우 ABORT 시그널을 일으키도록 ASIC/ASSP를 구성할 수 있다.
포팅시 이런 기기에 포함한 로직을 추가할 수 있다.

이럴 경우 data abort exception 핸들러를 인스톨하여, 정렬되지 않는 엑세스 발생시 핸들러에 의해 처리되도록 한다.
r14-8 번지를 통해 문제가 발생한 데이터 엑세스 명령어를 확인할 수 있다.

다음 방법으로 코드를 수정할 수 있다.

#ifdef __arm
  #define  PACKED  __packed
#else
  #define  PACKED
#endif
:
  PACKED int *pi;

 

 

위 문서처럼 어떤 구조체의 경우 구조 자체가 __packed 로 정렬되지 않음으로 해서, 포인터를 사용한 필드 데이터 접근시 data abort
가 발생할 수 있으며, 이 경우 가능한 dummy 필드를 삽입하여 워드 정렬을 하거나, 혹은 포인터를 __packed 하여 해결할 수
있음을 보았다. 결론적으로, 코드 사이즈와 성능상 오버헤드를 줄이기위해 정렬되지 않은 메모리 엑세스를 줄이는 것이
최선이다.

출처 : http://cafe.naver.com/05cmusa2.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=220