임베디드 스터디 - volatile과 const
임베디드 스터디 - volatile과 const
이번 글 참고자료:
EmbeddedInterviewlab
Volatile의 역할
volatile은 임베디드 소프트웨어에서 세 가지 역할을 수행한다.volatile변수는 원자적(Atomic)인 동작을 수행하지 않는다.volatile이 없는 변수- 선언 이후 변수의 변화가 없는 경우, 컴파일러는 CPU 레지스터에 해당 변수를 저장한 후 더 이상 변수의 변화를 확인하지 않도록 빌드한다.
volatile변수- 선언 이후 변화가 없어도, 컴파일러는 해당 변수를 매번 호출할 때마다 RAM에서 읽도록 빌드한다.
1
2
3
4
5
6
7
/* Without volatile: compiler may read flag once and loop forever */
uint8_t flag = 0; /* set by ISR */
while (flag == 0) { } /* compiler: "flag is 0, it never changes in this loop" */
/* With volatile: compiler re-reads flag from RAM every iteration */
volatile uint8_t flag = 0; /* set by ISR */
while (flag == 0) { } /* compiler: "flag is volatile, I must re-read it" */
2. Read/Write 재배치
volatile이 없는 변수- CPU 동작 최적화를 위해 컴파일러에서 읽기/쓰기 동작의 순서를 재배치하는 경우도 있다.
volatile변수- CPU 동작 효율성과 관계없이 컴파일러가 무조건 순차적으로 명령을 실행하도록 빌드한다.
- 단, CPU 하드웨어 reordering(out-of-order execution, store buffer)은 막지 못하며, 이를 위해서는 별도의 memory barrier(__DMB(), __DSB() 등)가 필요함.
1
2
/* volatile prevents compiler reordering only.
For hardware ordering (multi-core / cache), use __DMB() / __DSB(). */
3. Dead-Store 제거 방지
volatile이 없는 변수- 컴파일러가 하나의 변수가 읽기 명령 없이 연속적으로 데이터가 변하는 경우, 이전 쓰기 명령을 삭제한다.
volatile변수- 컴파일러가 하나의 변수에 연속적인 쓰기 명령이 있어도 명령을 유지시킨다.
Volatile의 활용
하드웨어 레지스터 접근
- Peripheral register는 메모리에 고정 주소로 매핑되는데, 해당 주소의 읽기, 쓰기 동작이 곧 해당 장치의 명령, 혹은 상태를 확인하 수 있는 요소이다. 따라서 해당 변수는 컴파일러에 의해 최적화되면 안된다.
1
2
3
4
5
6
7
8
9
10
11
/* Typical register pointer: volatile pointer-to-volatile data, const address */
#define GPIOA_ODR (*(volatile uint32_t *)0x40020014) /* output data reg */
#define GPIOA_IDR (*(volatile uint32_t *)0x40020010) /* input data reg */
void toggle_led(uint8_t pin) {
GPIOA_ODR ^= (1U << pin); /* read-modify-write: must hit hardware */
}
uint8_t read_button(uint8_t pin) {
return (GPIOA_IDR >> pin) & 1U; /* must read hardware, not a cached copy */
}
- 각 칩 제조사에서 제공하는 HAL과 같이
struct타입 안에 각각의 변수를volatile로 선언하면 훨씬 깔끔하다.
1
2
3
4
5
6
7
8
9
10
typedef struct {
volatile uint32_t MODER; /* mode register */
volatile uint32_t OTYPER; /* output type register */
volatile uint32_t OSPEEDR; /* output speed register */
volatile uint32_t PUPDR; /* pull-up/pull-down */
volatile uint32_t IDR; /* input data register */
volatile uint32_t ODR; /* output data register */
} GPIO_TypeDef;
#define GPIOA ((GPIO_TypeDef *)0x40020000)
ISR-Shared 변수
- ISR(Interrupt Service Routine)를 사용할 경우,
main()에서 해당 변수의 동작 확인을 위해 무조건volatile변수로 선언해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
volatile uint8_t rx_complete = 0; /* set by UART ISR */
void UART_IRQHandler(void) {
/* ... read data from DR ... */
rx_complete = 1; /* signal main loop */
}
int main(void) {
while (1) {
if (rx_complete) { /* compiler must re-read every iteration */
rx_complete = 0;
process_rx_data();
}
}
}
Volatile Pointer vs Pointer to volatile
- 포인터 변수의 선언 방식에 따라 변수 특징이 달라진다
volatile의PTR: 데이터는volatile하지만, 포인터 자체는volatile이 아님. 해당 변수의 주소는 레지스터에 저장됨.PTR의volatile: 포인터는volatile하지만, 데이터 자체는volatile이 아님. 즉, 외부로부터 포인터가 변경될 수도 있음.
1
2
3
4
5
6
7
8
9
volatile uint32_t *p; /* pointer to volatile data (the DATA is volatile) */
/* p itself can be cached in a register */
uint32_t * volatile p; /* volatile pointer to non-volatile data (the PTR is volatile) */
/* rarely useful -- the pointer itself changes externally */
volatile uint32_t * const p = (volatile uint32_t *)0x40020014;
/* const pointer to volatile data -- most common for register pointers: */
/* the address never changes, but the data at that address can change any time */
Const의 역할
- 변수의 쓰기 명령 방지
- 플래시 메모리와 RAM이 별도로 있는 MCU에서
const변수는.rodata(플래시 메모리)에 저장함.
즉, RAM의 저장공간 확보와 부트타임 개선이 가능함
1
2
3
4
5
/* .rodata (flash) -- zero RAM cost, survives power cycles */
const uint16_t sin_lut[256] = { 0, 402, 804, /* ... */ };
/* .data (RAM, copied from flash at boot) -- costs RAM + boot time */
uint16_t mutable_lut[256] = { 0, 402, 804, /* ... */ };
- 함수의 입력 변수로 사용하여 컴파일러를 통한 에러 확인이 가능함
1
2
3
4
5
/* Caller knows this function will not modify the buffer */
void transmit(const uint8_t *data, size_t len);
/* Without const, caller cannot be sure their buffer is safe */
void transmit(uint8_t *data, size_t len);
Pointer-Const 조합
- 총 네 가지로 구분하여 조합할 수 있다.
| Declaration | Read right-to-left | What is const? | Embedded use case |
|---|---|---|---|
| int *p | “p is a pointer to int” | Nothing | General mutable pointer |
| const int *p | “p is a pointer to const int” | The data | Read-only access to a buffer or LUT |
| int * const p | “p is a const pointer to int” | The pointer | Fixed-address hardware register (writable) |
| const int * const p | “p is a const pointer to const int” | Both | Fixed-address read-only register |
Right-to-Left Rule
const나 volatile로 선언된 변수의 경우, 영어로 해석할 때 변수명부터 왼쪽으로 읽어서 해석한다.
volatile uint32_t * const reg : “reg is a const pointer to volatile uint32_t.”
Volatile const
volatile const는 센서의 출력이나, Read-Only 레지스터에 대해 사용한다.volatile가 없으면 하드웨어에서 변수를 수정해도main()에서 변수 변동에 대해 확인을 못한다.const가 없으면main()에서 해당 레지스터를 수정할 수 있다.
MISRA C 규정
- MISRA C Rule 8.13 : 변수를 수정하지 않는 포인터는 무조건
const로 선언한다. - MISRA C Rule 2.2 :
volatile로 선언된 변수는 읽지 않아도 Dead-Code로 취급하지 않는다.
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.