14. struct device_node / struct property 내부 구조
디바이스 트리가 바이너리(DTB) 상태를 벗어나 커널 메모리에 올라오면, 각 노드와 속성들은 struct device_node와 struct property라는 C 구조체 객체로 변환됩니다. 이들은 서로 포인터로 연결되어 거대한 양방향 연결 리스트(Linked List) 형태의 트리 구조를 형성합니다.
14.1 struct device_node (노드 객체)
이 구조체는 디바이스 트리의 각 "노드"를 나타냅니다. 단순히 이름만 가진 것이 아니라, 트리 내에서의 위치 정보와 속성 리스트를 모두 포함합니다.
/* include/linux/of.h (주요 필드 요약) */
struct device_node {
const char *name; /* 노드의 이름 (예: "serial") */
const char *full_name; /* 전체 경로 (예: "/soc/serial@1c28000") */
struct property *properties; /* 이 노드가 가진 속성들의 리스트 (헤드) */
struct device_node *parent; /* 부모 노드 포인터 */
struct device_node *child; /* 첫 번째 자식 노드 포인터 */
struct device_node *sibling; /* 형제 노드 포인터 */
phandle phandle; /* 노드 간 참조를 위한 고유 ID */
unsigned long _flags; /* 노드 상태 (활성, 해제 등) */
void *data; /* 드라이버가 임시로 쓸 수 있는 데이터 포인터 */
};
핵심 관계망 (Tree Navigation)
- parent, child, sibling: 이 포인터들 덕분에 커널은 트리 전체를 자유롭게 탐색할 수 있습니다.
- properties: 해당 노드에 정의된 모든 reg, compatible 등의 속성 데이터가 담긴 struct property 리스트의 시작점을 가리킵니다.
14.2 struct property (속성 객체)
노드 내부에 정의된 개별 속성(Key-Value 쌍)을 나타냅니다.
/* include/linux/of.h */
struct property {
char *name; /* 속성 이름 (예: "compatible") */
int length; /* 데이터의 길이 (bytes) */
void *value; /* 실제 데이터 저장 공간 */
struct property *next; /* 다음 속성을 가리키는 포인터 (단방향 리스트) */
unsigned long _flags; /* 속성 상태 플래그 */
};
데이터 해석 방식
- value: 이 포인터는 타입이 void *입니다. DTB에서 넘어온 바이너리 데이터를 그대로 들고 있기 때문에, 드라이버가 문자열로 읽을지(of_property_read_string), 숫자 배열로 읽을지(of_property_read_u32_array)에 따라 다르게 해석됩니다.
14.3 구조체 간 연결 방식 (In-Memory Layout)
커널 메모리에서 이 둘은 다음과 같이 얽혀 있습니다.
- 루트 노드(of_root)에서 시작하여 child 포인터를 타고 내려가면 하위 노드들을 만납니다.
- 각 노드 내부의 properties 포인터는 첫 번째 속성 객체를 가리킵니다.
- 첫 번째 속성의 next 포인터는 두 번째 속성을 가리키는 식으로 단방향 연결 리스트를 구성합니다.
14.4 구조화의 장점
빠른 탐색: 바이너리 형태(FDT)는 특정 노드를 찾으려면 처음부터 끝까지 파싱해야 하지만, 구조체로 펼쳐놓으면(Unflatten) 포인터만 따라가면 되므로 매우 빠릅니다.
- 동적 수정 (Overlays): 시스템 실행 중에 새로운 하드웨어가 추가되면, 새로운 device_node 구조체를 만들어 기존 트리의 sibling이나 child 포인터에 연결만 해주면 됩니다.
- 추상화: 드라이버 개발자는 복잡한 바이너리 오프셋을 계산할 필요 없이, node->properties를 순회하며 필요한 정보를 편하게 꺼내 쓸 수 있습니다.
💡 실무 디버깅 팁
커널 소스에서 특정 노드의 속성을 수동으로 순회하고 싶을 때 다음과 같은 매크로를 자주 사용합니다.
struct property *pp;
// 노드(np)의 모든 속성을 하나씩 순회하며 이름을 출력
for_each_property_of_node(np, pp) {
printk("Property name: %s\n", pp->name);
}
14.5 DTB를 메모리상에서 어떻게 객체화 하는지: 데이터의 구조
unflatten_device_tree() 완료 후 커널 메모리에 존재하는 자료구조입니다. 모든 of_* API는 이 구조체를 통해 DT 정보에 접근합니다.
위 코드는 리눅스 커널이 디바이스 트리(DT) 바이너리를 메모리 상에서 어떻게 객체화하고 관리하는지를 보여주는 가장 핵심적인 데이터 구조입니다.
이 구조체들이 메모리에서 어떻게 얽혀 있고, 실제 드라이버까지 어떻게 이어지는지 구조적으로 설명해 드릴게요.
1. struct device_node: 트리의 중심점
device_node는 트리의 '마디(Node)'입니다. 이 구조체의 가장 큰 특징은 가족 관계 포인터를 통해 거대한 계층 구조를 형성한다는 점입니다.
- 가족 관계 (Tree Navigation): * parent, child, sibling 포인터를 사용하여 상/하/좌/우로 트리를 탐색합니다.
- child는 첫 번째 자식만 가리키며, 나머지 형제들은 sibling을 통해 단방향 리스트로 연결됩니다.
- fwnode (펌웨어 추상화): 최근 커널은 DT뿐만 아니라 ACPI 등 다른 펌웨어 인터페이스도 지원합니다. fwnode_handle은 이들을 통합하여 드라이버가 펌웨어 종류에 상관없이 정보를 읽을 수 있게 돕는 인터페이스입니다.
- data (드라이버의 사물함): 드라이버가 이 노드에 특정 데이터를 임시로 저장해두고 싶을 때 사용하는 자유 포인터입니다.
2. struct property: 노드의 세부 정보
property는 노드 안에 들어있는 '속성(Key-Value)'입니다. device_node 하나에는 여러 개의 property가 붙을 수 있습니다.
- 연결 리스트 구조: device_node의 properties 필드가 첫 번째 속성을 가리키고, 각 속성은 next 포인터를 통해 줄줄이 비엔나 소시지처럼 연결됩니다.
- value (Raw Data): DTB 바이너리에서 추출된 원시 데이터가 저장됩니다.
- status = "okay"라면 문자열 데이터가,
- reg = <0x100 0x10>이라면 바이너리 정수 데이터가 들어있습니다.
- deadprops: 런타임에 디바이스 트리를 수정하는 Overlay 기능 때문에 존재합니다. 속성을 삭제해도 즉시 메모리에서 날리지 않고 여기에 보관했다가, Overlay를 취소(Revert)할 때 다시 복구합니다.
3. 노드 → 디바이스 변환 (Populate) 메커니즘
코드 하단에 설명된 "Populate" 과정은 DT를 실제 리눅스 장치 모델(struct device)로 승격시키는 매우 중요한 단계입니다.
왜 모든 노드가 장치가 되지 않나요?
디바이스 트리에는 하드웨어뿐만 아니라 단순한 설정 정보(예: opp-table, chosen)도 포함되어 있습니다. 커널은 다음과 같은 규칙으로 장치를 생성합니다.
- Compatible 검사: compatible 속성이 있는 노드 위주로 platform_device를 생성합니다.
- Bus 재귀 탐색: 루트 노드의 직계 자식들은 장치로 만들어집니다. 하지만 자식의 자식(손자) 노드들은 기본적으로 생성되지 않습니다.
- 단, 부모 노드가 simple-bus 같은 "버스 성격"의 compatible을 가지고 있다면 커널은 "아, 이 아래에는 내가 관리할 장치들이 더 있구나!"라고 판단하여 재귀적으로 자식 노드들도 장치로 등록합니다.
4. 실무적인 주의사항: 참조 카운팅 (Reference Counting)
코드 예시의 of_node_get / of_node_put은 메모리 관리를 위해 필수적입니다.
- 왜 필요한가? 어떤 드라이버가 특정 노드를 참조하고 있는데, 갑자기 Overlay가 해제되어 노드가 사라지면 커널 패닉이 발생합니다.
- 매크로의 편리함: for_each_child_of_node 같은 매크로는 다음 노드로 넘어갈 때 이전 노드의 카운트를 자동으로 깎아줍니다.
- 🚨 치명적인 실수: 루프 중간에 break나 return으로 빠져나올 경우, 매크로가 마지막 노드의 put을 수행하지 못합니다. 이때는 반드시 수동으로 of_node_put()을 해줘야 메모리 누수를 막을 수 있습니다.
💡정리하자면
- device_node는 트리의 구조(뼈대)를 담당하고,
- property는 노드의 데이터(살점)를 담당하며,
- platform_device는 이들을 바탕으로 커널이 실제 제어할 수 있는 객체가 된 상태입니다.

참고: https://www.minzkn.com/linuxkernel/pages/device-tree.html#dt-compile
'Embedded : : Linux > : : Device Tree' 카테고리의 다른 글
| [Device Tree] 13. FDT 바이너리 포맷 (Flattened Device Tree) (1) | 2026.03.04 |
|---|---|
| [Device Tree] 12. Device Tree 디버깅 (0) | 2026.03.04 |
| [Device Tree] 11. 특수 노드와 고급 패턴 (0) | 2026.03.04 |
| [Device Tree] 10. Device Tree + Platform Driver 통합 (0) | 2026.03.04 |
| [Device Tree] 9. 커널 OF(Open Firmware) API (0) | 2026.03.04 |