요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
=========================
Unaligned Memory Accesses
=========================
:Author: Daniel Drake <dsd@gentoo.org>,
:Author: Johannes Berg <johannes@sipsolutions.net>
:With help from: Alan Cox, Avuton Olrich, Heikki Orsila, Jan Engelhardt,
Kyle McMartin, Kyle Moffett, Randy Dunlap, Robert Hancock, Uli Kunitz,
Vadim Lobanov
Linux runs on a wide variety of architectures which have varying behaviour
when it comes to memory access. This document presents some details about
unaligned accesses, why you need to write code that doesn't cause them,
and how to write such code!
The definition of an unaligned access
=====================================
Unaligned memory accesses occur when you try to read N bytes of data starting
from an address that is not evenly divisible by N (i.e. addr % N != 0).
For example, reading 4 bytes of data from address 0x10004 is fine, but
reading 4 bytes of data from address 0x10005 would be an unaligned memory
access.
The above may seem a little vague, as memory access can happen in different
ways. The context here is at the machine code level: certain instructions read
or write a number of bytes to or from memory (e.g. movb, movw, movl in x86
assembly). As will become clear, it is relatively easy to spot C statements
which will compile to multiple-byte memory access instructions, namely when
dealing with types such as u16, u32 and u64.
Natural alignment
=================
The rule mentioned above forms what we refer to as natural alignment:
When accessing N bytes of memory, the base memory address must be evenly
divisible by N, i.e. addr % N == 0.
When writing code, assume the target architecture has natural alignment
requirements.
In reality, only a few architectures require natural alignment on all sizes
of memory access. However, we must consider ALL supported architectures;
writing code that satisfies natural alignment requirements is the easiest way
to achieve full portability.
Why unaligned access is bad
===========================
The effects of performing an unaligned memory access vary from architecture
to architecture. It would be easy to write a whole document on the differences
here; a summary of the common scenarios is presented below:
- Some architectures are able to perform unaligned memory accesses
transparently, but there is usually a significant performance cost.
- Some architectures raise processor exceptions when unaligned accesses
happen. The exception handler is able to correct the unaligned access,
at significant cost to performance.
- Some architectures raise processor exceptions when unaligned accesses
happen, but the exceptions do not contain enough information for the
unaligned access to be corrected.
- Some architectures are not capable of unaligned memory access, but will
silently perform a different memory access to the one that was requested,
resulting in a subtle code bug that is hard to detect!
It should be obvious from the above that if your code causes unaligned
memory accesses to happen, your code will not work correctly on certain
platforms and will cause performance problems on others.
Code that does not cause unaligned access
=========================================
At first, the concepts above may seem a little hard to relate to actual
coding practice. After all, you don't have a great deal of control over
memory addresses of certain variables, etc.
Fortunately things are not too complex, as in most cases, the compiler
ensures that things will work for you. For example, take the following
structure::
struct foo {
u16 field1;
u32 field2;
u8 field3;
};
Let us assume that an instance of the above structure resides in memory
starting at address 0x10000. With a basic level of understanding, it would
not be unreasonable to expect that accessing field2 would cause an unaligned
access. You'd be expecting field2 to be located at offset 2 bytes into the
structure, i.e. address 0x10002, but that address is not evenly divisible
by 4 (remember, we're reading a 4 byte value here).
Fortunately, the compiler understands the alignment constraints, so in the
above case it would insert 2 bytes of padding in between field1 and field2.
Therefore, for standard structure types you can always rely on the compiler
to pad structures so that accesses to fields are suitably aligned (assuming
you do not cast the field to a type of different length).
Similarly, you can also rely on the compiler to align variables and function
parameters to a naturally aligned scheme, based on the size of the type of
the variable.
At this point, it should be clear that accessing a single byte (u8 or char)
will never cause an unaligned access, because all memory addresses are evenly
divisible by one.
On a related topic, with the above considerations in mind you may observe
that you could reorder the fields in the structure in order to place fields
where padding would otherwise be inserted, and hence reduce the overall
resident memory size of structure instances. The optimal layout of the
above example is::
struct foo {
u32 field2;
u16 field1;
u8 field3;
};
For a natural alignment scheme, the compiler would only have to add a single
byte of padding at the end of the structure. This padding is added in order
to satisfy alignment constraints for arrays of these structures.
Another point worth mentioning is the use of __attribute__((packed)) on a
structure type. This GCC-specific attribute tells the compiler never to
insert any padding within structures, useful when you want to use a C struct
to represent some data that comes in a fixed arrangement 'off the wire'.
You might be inclined to believe that usage of this attribute can easily
lead to unaligned accesses when accessing fields that do not satisfy
architectural alignment requirements. However, again, the compiler is aware
of the alignment constraints and will generate extra instructions to perform
the memory access in a way that does not cause unaligned access. Of course,
the extra instructions obviously cause a loss in performance compared to the
non-packed case, so the packed attribute should only be used when avoiding
structure padding is of importance.
Code that causes unaligned access
=================================
With the above in mind, let's move onto a real life example of a function
that can cause an unaligned memory access. The following function taken
from include/linux/etherdevice.h is an optimized routine to compare two
ethernet MAC addresses for equality::
bool ether_addr_equal(const u8 *addr1, const u8 *addr2)
{
#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
u32 fold = ((*(const u32 *)addr1) ^ (*(const u32 *)addr2)) |
((*(const u16 *)(addr1 + 4)) ^ (*(const u16 *)(addr2 + 4)));
return fold == 0;
#else
const u16 *a = (const u16 *)addr1;
const u16 *b = (const u16 *)addr2;
return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2])) == 0;
#endif
}
In the above function, when the hardware has efficient unaligned access
capability, there is no issue with this code. But when the hardware isn't
able to access memory on arbitrary boundaries, the reference to a[0] causes
2 bytes (16 bits) to be read from memory starting at address addr1.
Think about what would happen if addr1 was an odd address such as 0x10003.
(Hint: it'd be an unaligned access.)
Despite the potential unaligned access problems with the above function, it
is included in the kernel anyway but is understood to only work normally on
16-bit-aligned addresses. It is up to the caller to ensure this alignment or
not use this function at all. This alignment-unsafe function is still useful
as it is a decent optimization for the cases when you can ensure alignment,
which is true almost all of the time in ethernet networking context.
Here is another example of some code that could cause unaligned accesses::
void myfunc(u8 *data, u32 value)
{
[...]
*((u32 *) data) = cpu_to_le32(value);
[...]
}
This code will cause unaligned accesses every time the data parameter points
to an address that is not evenly divisible by 4.
In summary, the 2 main scenarios where you may run into unaligned access
problems involve:
1. Casting variables to types of different lengths
2. Pointer arithmetic followed by access to at least 2 bytes of data
Avoiding unaligned accesses
===========================
The easiest way to avoid unaligned access is to use the get_unaligned() and
put_unaligned() macros provided by the <linux/unaligned.h> header file.
Going back to an earlier example of code that potentially causes unaligned
access::
void myfunc(u8 *data, u32 value)
{
[...]
*((u32 *) data) = cpu_to_le32(value);
[...]
}
To avoid the unaligned memory access, you would rewrite it as follows::
void myfunc(u8 *data, u32 value)
{
[...]
value = cpu_to_le32(value);
put_unaligned(value, (u32 *) data);
[...]
}
The get_unaligned() macro works similarly. Assuming 'data' is a pointer to
memory and you wish to avoid unaligned access, its usage is as follows::
u32 value = get_unaligned((u32 *) data);
These macros work for memory accesses of any length (not just 32 bits as
in the examples above). Be aware that when compared to standard access of
aligned memory, using these macros to access unaligned memory can be costly in
terms of performance.
If use of such macros is not convenient, another option is to use memcpy(),
where the source or destination (or both) are of type u8* or unsigned char*.
Due to the byte-wise nature of this operation, unaligned accesses are avoided.
Alignment vs. Networking
========================
On architectures that require aligned loads, networking requires that the IP
header is aligned on a four-byte boundary to optimise the IP stack. For
regular ethernet hardware, the constant NET_IP_ALIGN is used. On most
architectures this constant has the value 2 because the normal ethernet
header is 14 bytes long, so in order to get proper alignment one needs to
DMA to an address which can be expressed as 4*n + 2. One notable exception
here is powerpc which defines NET_IP_ALIGN to 0 because DMA to unaligned
addresses can be very expensive and dwarf the cost of unaligned loads.
For some ethernet hardware that cannot DMA to unaligned addresses like
4*n+2 or non-ethernet hardware, this can be a problem, and it is then
required to copy the incoming frame into an aligned buffer. Because this is
unnecessary on architectures that can do unaligned accesses, the code can be
made dependent on CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS like so::
#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
skb = original skb
#else
skb = copy skb
#endif
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
정렬되지 않은 memory access 소개
1-18정렬되지 않은 Memory Access
저자: Daniel Drake <dsd@gentoo.org>
저자: Johannes Berg <johannes@sipsolutions.net>
도움을 준 사람: Alan Cox, Avuton Olrich, Heikki Orsila, Jan Engelhardt, Kyle McMartin, Kyle Moffett, Randy Dunlap, Robert Hancock, Uli Kunitz, Vadim Lobanov
Linux는 memory access 동작이 서로 다른 매우 다양한 architecture에서 실행됩니다. 이 문서는 정렬되지 않은 access가 무엇인지, 그런 access를 일으키지 않는 code를 작성해야 하는 이유와 그 작성 방법을 설명합니다.
정렬되지 않은 access의 정의
19-35정렬되지 않은 access의 정의
N byte data를 읽으려는 시작 address가 N으로 나누어떨어지지 않을 때, 즉 `addr % N != 0`일 때 unaligned memory access가 발생합니다. 예를 들어 address `0x10004`에서 4 byte를 읽는 것은 괜찮지만, `0x10005`에서 4 byte를 읽으면 unaligned memory access입니다.
Memory access는 여러 방식으로 일어나므로 위 설명이 다소 모호해 보일 수 있습니다. 여기서 말하는 context는 machine code level입니다. 특정 instruction은 memory에서 정해진 byte 수를 읽거나 씁니다. 예를 들어 x86 assembly의 `movb`, `movw`, `movl`이 이에 해당합니다.
여러 byte를 access하는 machine instruction으로 compile될 C statement는 비교적 쉽게 식별할 수 있습니다. 특히 `u16`, `u32`, `u64` 같은 type을 다룰 때 그렇습니다.
Natural alignment
36-51Natural alignment
위 규칙이 natural alignment를 이룹니다. N byte memory에 access할 때 base memory address는 N으로 나누어떨어져야 합니다. 즉 `addr % N == 0`이어야 합니다.
Code를 작성할 때는 target architecture에 natural alignment requirement가 있다고 가정하십시오.
실제로 모든 memory access 크기에 natural alignment를 요구하는 architecture는 소수뿐입니다. 그러나 지원하는 모든 architecture를 고려해야 하며, natural alignment requirement를 만족하는 code를 작성하는 것이 완전한 portability를 얻는 가장 쉬운 방법입니다.
정렬되지 않은 access가 나쁜 이유
52-75정렬되지 않은 access가 나쁜 이유
Unaligned memory access의 영향은 architecture마다 다릅니다. 일반적으로 발생하는 상황을 요약하면 다음과 같습니다.
- 일부 architecture는 unaligned memory access를 투명하게 수행할 수 있지만 대개 상당한 performance cost가 듭니다.
- 일부 architecture는 unaligned access가 발생하면 processor exception을 일으킵니다. Exception handler가 access를 바로잡을 수 있지만 performance cost가 큽니다.
- 일부 architecture도 processor exception을 일으키지만 exception에 unaligned access를 바로잡기 위한 정보가 충분하지 않습니다.
- 일부 architecture는 unaligned memory access를 수행할 수 없으면서 요청과 다른 memory access를 조용히 수행합니다. 그 결과 발견하기 어려운 미묘한 code bug가 생깁니다.
따라서 code가 unaligned memory access를 일으키면 일부 platform에서는 올바르게 작동하지 않고, 다른 platform에서는 performance 문제를 일으킵니다.
정렬되지 않은 access를 일으키지 않는 code
76-144정렬되지 않은 access를 일으키지 않는 code
처음에는 위 개념을 실제 coding practice와 연결하기 어려워 보일 수 있습니다. 특정 variable의 memory address 등을 programmer가 크게 제어할 수 없기 때문입니다.
다행히 대부분의 경우 compiler가 올바르게 동작하도록 보장하므로 복잡하지 않습니다. 다음 structure를 예로 들겠습니다.
struct foo {
u16 field1;
u32 field2;
u8 field3;
};
이 structure instance가 address `0x10000`에서 시작한다고 가정합니다. 단순하게 생각하면 `field2`가 structure 안의 offset 2 byte, 즉 `0x10002`에 놓여 4로 나누어떨어지지 않으므로 access가 정렬되지 않을 것이라 예상할 수 있습니다. 여기서는 4 byte 값을 읽는다는 점을 기억하십시오.
Compiler는 alignment constraint를 이해하므로 위 경우 `field1`과 `field2` 사이에 padding 2 byte를 넣습니다. 따라서 field를 다른 길이의 type으로 cast하지 않는 한 standard structure type에서는 compiler가 field access를 적절히 정렬하도록 padding을 추가한다고 항상 믿을 수 있습니다.
마찬가지로 compiler는 variable type 크기를 바탕으로 variable과 function parameter를 natural alignment 방식에 맞춰 정렬합니다.
단일 byte인 `u8` 또는 `char` access는 모든 memory address가 1로 나누어떨어지므로 unaligned access를 일으키지 않습니다.
또한 padding이 들어갈 자리에 field를 배치하도록 structure field 순서를 바꾸면 structure instance가 차지하는 전체 memory 크기를 줄일 수 있습니다. 위 예의 최적 layout은 다음과 같습니다.
struct foo {
u32 field2;
u16 field1;
u8 field3;
};
Natural alignment 방식에서 compiler는 structure 끝에 padding 1 byte만 추가하면 됩니다. 이 padding은 이러한 structure의 array에 대한 alignment constraint를 만족시키기 위해 추가됩니다.
Structure type에 `__attribute__((packed))`를 사용하는 점도 언급할 가치가 있습니다. 이 GCC 전용 attribute는 structure 내부에 padding을 절대 넣지 말라고 compiler에 지시합니다. 고정된 배열로 wire를 통해 들어오는 data를 C struct로 표현할 때 유용합니다.
이 attribute를 사용하면 architecture alignment requirement를 만족하지 않는 field에 access할 때 쉽게 unaligned access가 생긴다고 생각할 수 있습니다. 그러나 compiler는 alignment constraint를 알고 있으므로 unaligned access를 일으키지 않는 방식으로 memory에 access하도록 추가 instruction을 생성합니다.
추가 instruction은 non-packed case보다 performance를 떨어뜨리므로 packed attribute는 structure padding을 피하는 것이 중요할 때만 사용해야 합니다.
정렬되지 않은 access를 일으키는 code
145-201정렬되지 않은 access를 일으키는 code
실제로 unaligned memory access를 일으킬 수 있는 function을 살펴보겠습니다. 다음은 `include/linux/etherdevice.h`에서 가져온 두 ethernet MAC address의 동일 여부를 비교하는 최적화 routine입니다.
bool ether_addr_equal(const u8 *addr1, const u8 *addr2)
{
#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
u32 fold = ((*(const u32 *)addr1) ^ (*(const u32 *)addr2)) |
((*(const u16 *)(addr1 + 4)) ^ (*(const u16 *)(addr2 + 4)));
return fold == 0;
#else
const u16 *a = (const u16 *)addr1;
const u16 *b = (const u16 *)addr2;
return ((a[0] ^ b[0]) | (a[1] ^ b[1]) | (a[2] ^ b[2])) == 0;
#endif
}
Hardware에 효율적인 unaligned access capability가 있다면 이 code에는 문제가 없습니다. 그러나 hardware가 임의의 boundary에서 memory에 access할 수 없다면 `a[0]` reference가 `addr1` address에서 시작해 2 byte, 즉 16 bit를 읽습니다.
`addr1`이 `0x10003` 같은 홀수 address라면 어떤 일이 생길지 생각해 보십시오. 정렬되지 않은 access가 됩니다.
잠재적인 문제에도 이 function은 kernel에 포함되어 있지만 16-bit-aligned address에서만 정상적으로 동작한다고 이해해야 합니다. Caller가 이 alignment를 보장하거나 function을 전혀 사용하지 않아야 합니다.
이 alignment-unsafe function은 alignment를 보장할 수 있을 때 좋은 최적화이므로 여전히 유용하며, ethernet networking context에서는 거의 항상 보장할 수 있습니다.
다음 code 역시 unaligned access를 일으킬 수 있습니다.
void myfunc(u8 *data, u32 value)
{
[...]
*((u32 *) data) = cpu_to_le32(value);
[...]
}
`data` parameter가 4로 나누어떨어지지 않는 address를 가리킬 때마다 이 code는 unaligned access를 일으킵니다.
요약하면 unaligned access 문제를 만나는 두 가지 주요 상황은 다음과 같습니다.
- Variable을 길이가 다른 type으로 cast하는 경우
- Pointer arithmetic을 수행한 뒤 최소 2 byte의 data에 access하는 경우
정렬되지 않은 access 피하기
202-242정렬되지 않은 access 피하기
Unaligned access를 피하는 가장 쉬운 방법은 `<linux/unaligned.h>` header file이 제공하는 `get_unaligned()` 및 `put_unaligned()` macro를 사용하는 것입니다.
앞에서 본 잠재적으로 unaligned access를 일으키는 code는 다음과 같습니다.
void myfunc(u8 *data, u32 value)
{
[...]
*((u32 *) data) = cpu_to_le32(value);
[...]
}
Unaligned memory access를 피하려면 다음처럼 다시 작성합니다.
void myfunc(u8 *data, u32 value)
{
[...]
value = cpu_to_le32(value);
put_unaligned(value, (u32 *) data);
[...]
}
`get_unaligned()` macro도 비슷하게 동작합니다. `data`가 memory를 가리키는 pointer이고 unaligned access를 피하려면 다음과 같이 사용합니다.
u32 value = get_unaligned((u32 *) data);
이 macro는 위 예의 32 bit뿐 아니라 모든 길이의 memory access에 동작합니다. 다만 aligned memory의 standard access와 비교하면 이 macro로 unaligned memory에 access하는 것은 performance cost가 클 수 있습니다.
이러한 macro 사용이 편리하지 않다면 source 또는 destination, 혹은 둘 다 `u8*` 또는 `unsigned char*` type인 `memcpy()`를 사용할 수도 있습니다. 이 operation은 byte 단위로 수행되므로 unaligned access를 피합니다.
Alignment와 networking
243-265Alignment와 networking
Aligned load를 요구하는 architecture에서 networking은 IP stack 최적화를 위해 IP header를 4-byte boundary에 정렬해야 합니다. 일반 ethernet hardware에는 constant `NET_IP_ALIGN`을 사용합니다.
일반 ethernet header 길이는 14 byte이므로 올바른 alignment를 얻으려면 `4*n + 2` 형태의 address로 DMA해야 합니다. 따라서 대부분의 architecture에서 `NET_IP_ALIGN` 값은 2입니다.
주목할 예외는 powerpc입니다. 정렬되지 않은 address로 DMA하는 비용이 매우 커 unaligned load 비용을 압도할 수 있으므로 powerpc는 `NET_IP_ALIGN`을 0으로 정의합니다.
`4*n+2`처럼 정렬되지 않은 address로 DMA할 수 없는 일부 ethernet hardware나 non-ethernet hardware에서는 이것이 문제가 될 수 있으며, incoming frame을 aligned buffer로 복사해야 합니다.
Unaligned access가 가능한 architecture에서는 이 복사가 불필요하므로 다음처럼 `CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS`에 따라 code를 구성할 수 있습니다.
#ifdef CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS
skb = original skb
#else
skb = copy skb
#endif
요약과 해설
unaligned-memory-access.rst:1-265N byte를 읽거나 쓰는 address가 N의 배수가 아니면 unaligned access입니다. 일부 architecture는 이를 느리게 처리하고, 일부는 exception을 발생시키며, 일부는 요청과 다른 access를 조용히 수행할 수 있습니다.
일반 variable과 structure field는 compiler가 natural alignment와 padding을 보장합니다. 위험은 길이가 다른 type으로 cast하거나 pointer arithmetic 뒤 여러 byte를 직접 access할 때 주로 생깁니다.
정렬을 보장할 수 없다면 `<linux/unaligned.h>`의 `get_unaligned()`와 `put_unaligned()`를 사용하고, 적절한 경우 byte 단위 `memcpy()`를 사용합니다.
Networking에서는 IP header의 4-byte alignment와 DMA 제약을 함께 고려해야 하며, architecture capability에 따라 `NET_IP_ALIGN`과 `CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS`를 적용합니다.