---
title: "CSR 원자성: 값이 0이어도 쓰기 접근이 남는 경우"
author: "브레이브브라우저쓰자"
published_at: "2026-10-17T09:23:02.747Z"
updated_at: "2026-10-06T00:52:45.614Z"
tags: ["csr 원자성"]
canonical_url: "https://ariser.io/@writer_a9817d5a8de0/csr-%EC%9B%90%EC%9E%90%EC%84%B1-%EA%B0%92%EC%9D%B4-0%EC%9D%B4%EC%96%B4%EB%8F%84-%EC%93%B0%EA%B8%B0-%EC%A0%91%EA%B7%BC%EC%9D%B4-%EB%82%A8%EB%8A%94-%EA%B2%BD%EC%9A%B0"
ai_assisted: true
---

# CSR 원자성: 값이 0이어도 쓰기 접근이 남는 경우

이 글은 AI가 활용됐습니다.

[원문](https://ariser.io/@writer_a9817d5a8de0/csr-%EC%9B%90%EC%9E%90%EC%84%B1-%EA%B0%92%EC%9D%B4-0%EC%9D%B4%EC%96%B4%EB%8F%84-%EC%93%B0%EA%B8%B0-%EC%A0%91%EA%B7%BC%EC%9D%B4-%EB%82%A8%EB%8A%94-%EA%B2%BD%EC%9A%B0)

레지스터에 저장된 값이 전후로 같은데 쓰기 동작은 한 번 발생했다. 이 차이는 CSR 원자성을 검증할 때 중요하다. CSR 명령은 값을 계산하는 것뿐 아니라 해당 CSR을 읽었는지, 썼는지까지 규정한다. 결과 숫자만 맞춰 보면 명령이 억제해야 할 접근이나 허용한 부작용을 놓칠 수 있다.

## 한 명령에서 읽고 바꾸는 범위

RISC-V Zicsr는 한 CSR을 원자적으로 읽고 변경하는 명령을 정의한다. 설명용으로 현재 값이 0xA0이고 각 비트가 그대로 읽히고 쓰이는 가상 CSR flags를 놓자. t1=0x05에서 다음 명령을 수행한다.

```asm
csrrs t0, flags, t1
```

이 명령은 이전 CSR 값 0xA0을 t0에 돌려주고, t1이 지정한 비트를 set해 flags에 0xA5를 쓴다. 이후 mask=0x04인 CSRRC를 실행하면 이전 값 0xA5를 돌려주고 결과는 0xA1이다. 실제 표준 CSR의 필드 제약을 생략한, 단순한 쓰기 가능 CSR의 계산 예다. [RISC-V Zicsr, CSR Instructions](https://github.com/riscv/riscv-isa-manual/blob/main/src/unpriv/zicsr.adoc)

원자성은 이 읽기와 변경이 한 명령의 동작으로 묶인다는 데 있다. 별도 CSR 읽기와 쓰기로 나누면 두 명령 사이에 같은 hart의 인터럽트 처리기가 실행될 수 있다. 예를 들어 프로그램이 0xA0을 읽은 뒤 처리기가 0x10 비트를 set해 0xB0으로 만들었는데, 프로그램이 이전 0xA0에 0x05를 더해 0xA5를 쓰면 처리기의 변경을 잃는다. 단일 CSRRS에서는 처리기가 그 명령의 읽기와 쓰기 사이에 프로그램 명령으로 끼어들 수 없다.

그렇다고 모든 CSR이 단순 비트 저장소는 아니다. 어떤 필드는 쓰기 가능한 값에 제약이 있고, 읽힌 비트와 내부 값이 다를 수도 있다. 원문도 이런 필드의 read-modify-write 결과를 별도로 설명한다. 예제의 OR 계산을 모든 CSR에 무조건 적용할 수는 없다.

## x0라는 번호와 0이라는 값은 다르다

CSRRS와 CSRRC에서 rs1이 x0이면 CSR 쓰기가 아예 억제된다. 반면 rs1이 t1이고 t1의 값이 0이면, set하거나 clear할 비트는 없어도 CSR 전체에 대한 쓰기 접근은 발생한다. 이 두 경우의 최종 flags 값은 같아도 접근 의미는 다르다.

| 명령 조건 | CSR 읽기 | CSR 쓰기 |
|---|---|---|
| CSRRW, rd=x0 | 억제 | 수행 |
| CSRRW, rd≠x0 | 수행 | 수행 |
| CSRRS/CSRRC, rs1=x0 | 수행 | 억제 |
| CSRRS/CSRRC, rs1=t1이고 t1=0 | 수행 | 수행 |

CSRRS/CSRRC의 읽기는 rd=x0이어도 발생한다. 정수 목적지에 값을 남기지 않는 것과 CSR을 읽지 않는 것은 다르다. CSRRW에서는 반대로 rd=x0이 CSR 읽기 자체를 억제한다. CSRRW의 rs1=x0은 쓰기 억제가 아니라 0을 쓰려는 동작이다. [RISC-V Zicsr, Conditions determining whether a CSR instruction reads or writes](https://github.com/riscv/riscv-isa-manual/blob/main/src/unpriv/zicsr.adoc)

즉시값 형식에서도 명령 종류를 구분한다. CSRRSI와 CSRRCI는 즉시값이 0이면 쓰기를 억제하지만, CSRRWI에 0을 넣는 것은 0을 쓰는 것이다. 단순히 입력 데이터가 0이라는 이유로 모든 CSR 쓰기 신호를 끄면 일부 명령의 계약을 바꾼다.

## 최종값만 보면 쓰기 억제를 검증할 수 없다

가상 custom CSR이 CSR 전체에 대한 쓰기 접근마다 별도 이벤트를 발생시키도록 설계되었다고 하자. CSRRS의 t1=0은 flags를 바꾸지 않아도 그 쓰기 이벤트를 만들 수 있고, rs1=x0은 쓰기가 없어 이벤트를 만들지 않는다. 전후 값 비교만으로 이 차이를 확인할 수 없다.

이 예제를 표준 CSR이 모두 읽을 때마다 이벤트를 만든다는 설명으로 받아들여서는 안 된다. 원문은 표준 CSR에 read side effect가 없다고 구분하며, custom CSR은 읽기나 쓰기 부작용을 가질 수 있다고 설명한다. 또한 특정 비트에 값을 써 생기는 간접 효과와 접근 자체의 side effect를 구별한다. 필드별 side effect에는 어느 mask 비트가 켜졌는지도 관련된다.

읽기 전용 CSR에도 같은 경계가 적용된다. CSRRS의 rs1=x0은 쓰기를 시도하지 않으므로 읽기 권한 등 다른 조건을 만족하면 읽기 전용 CSR을 읽을 수 있다. t1=0인 경우에는 여전히 쓰기 시도이므로, 이를 같은 무해한 읽기로 처리해서는 안 된다. 접근 권한이나 존재하지 않는 CSR에 대한 검사는 데이터 값과 별도로 필요하다.

## 원자성을 전역 메모리 순서로 넓히지 않는다

이 원자성은 단일 hart의 단일 CSR 명령에 대한 계약이다. 다른 hart의 CSR이나 장치와 공유하는 전체 상태를 잠근다는 뜻은 아니다. Zicsr의 접근 순서 규정도 자신의 실행에 영향을 주는 CSR 순서와, 다른 hart·장치가 보는 메모리 접근 순서를 나누며 필요한 경우 FENCE 등의 순서 조건을 요구한다. [RISC-V Zicsr, CSR Access Ordering](https://github.com/riscv/riscv-isa-manual/blob/main/src/unpriv/zicsr.adoc)

CSR 원자성을 확인하려면 이전 값, 쓰려는 값, 읽기·쓰기 허용 신호를 함께 보아야 한다. 특히 rs1=x0과 값이 0인 다른 레지스터를 구별하는 반례가 유용하다. 숫자가 같다는 사실만으로 접근이 같다고 판단하지 않을 때, 한 명령의 원자적인 갱신과 부작용 억제를 모두 지킬 수 있다.

