• ybgwon@gmail.com

리눅스 커널의 container_of 매크로



이 글은 리눅스 커널의 container_of 매크로 함수를 설명하는 글입니다. 요즘 리눅스에서 container를 이야기하자면 저는 container 가상화 시스템 docker가 떠오릅니다. 그만큼 요즘 가상화 관련 분아갸 hot하기 때문이어서 인데요, 제가 이야기하고자 하는 커널의 container_of 는 가상화와 관련 없습니다. 차라리 커널의 범용적인 매크로에 가깝다고 보시면 됩니다.


container_of(ptr, type, member)

위 매크로의 이름을 직역을 하자면 "what container of this member?" 정도로 될 것 같네요. 즉,

포인터(ptr)가 가리키는 구조체 멤버(member)를 포함하는 구조체 (type)의 주소를 반환하는 함수

입니다. 써놓고 저도 이게 뭔 말이야 싶네요. container_of 를 제대로 이해하기 위해서는 리눅스 커널의 자료 구조들이 어떻게 사용되는지 알아야 합니다. 그 중 대표적인 자료구조인 연결 리스트를 통해 어떻게 사용되는지 알아보도록 하겠습니다.


우선, 리눅스 커널의 리스트에 대해 알아보도록 하겠습니다.


리눅스 커널의 리스트

리눅스 커널의 리스트 와 일반 연결 리스트(C 언어 학습할 때 흔히 배운)와는 약간의 차이가 있습니다. 지금부터 그 차이를 알아보겠습니다.


일반적인 연결 리스트

노드와 링크로 구성되고 노드( _node)는 데이터와 링크를 담고 있는 구조체로 나타내어 집니다. 링크(next)는 인접노드의 위치를 저장하는 포인터입니다.

일반적인 리스트 구조는 노드에 data를 내장하고, 다음 노드(_node)를 가르키는 포인터를 가집니다. next는 다음 노드의 주소를 가리킵니다.

위와 같은 구조는 특정 타입의 데이터 밖에 저장하지 못합니다. 따라서 다른 데이터 타입의 리스트를 사용하기 위해선 새로운 타입의 리스트를 정의해주거나 데이터 타입을 void*로 설정해야 합니다.


커널 스타일 연결 리스트

노드를 나타내는 구조체 자체가 위와 비슷다고 할 때, 커널에서는 연결 리스트를 조금 다른 관점으로 서로 연결합니다.

링크 구조는 list_head라는 자료 구조에서만 관여합니다. 그리고 연결 관계가 필요한 자료 구조(위 예에서는 _node)에서 list_head를 멤버로 가집니다. 그러면 위와 같은 모습으로 연결 관계가 구성됩니다. 이렇게 되면 데이터 타입에 상관없이 범용적으로 사용할 수 있습니다. 하지만 link.next로부터 _node의 주소를 어떻게 획득하냐는 문제가 발생합니다. 이때 사용하는 것이 container_of 매크로입니다. 이 매크로는 offsetof를 이용합니다. 따라서 offsetof를 먼저 살펴보도록 하겠습니다.


offsetof 매크로

컴파일러가 지원하는 경우는 컴파일러의 내장 offsetof 함수를 사용하게 됩니다. 그렇지 않을 경우는 구조체 주소를 0으로 설정하여 member 변수까지의 offset을 구해옵니다.

(TYPE *)0 에서 null 포인터 exception 에러가 나지 않을까 생각할 수 있는데, MEMBER 주소만 가져오는 것이라 문제되지 않습니다.

그럼 이제 offsetof 함수로 구조체에서 next 멤버 변수의 offset을 구할 수 있으니 next 의 주소에서 offset을 빼면 컨테이너인 _node 구조체의 주소를 알아 낼 수 있습니다.


container_of 매크로

이제 container_of의 코드를 살펴보겠습니다.

에러를 검사하는 부분은 생략하였습니다.

매크로의 3개 인자에 대해 설명해 보겠습니다.

  1. ptr: 타입이 "type"인 구조체의 "member"라는 멤버 변수의 주소입니다.

  2. type: member를 포함하는 구조체의 타입입니다.

  3. member: type 구조체의 멤버 변수 이름입니다.

매크로의 내용은 복잡해 보이지만 자세히 살펴보면 다음과 같습니다

  • Line 1

먼저 ptr을 void 타입으로 형변환 합니다. 이는 포인터 연산시 void 포인터를 사용하여 산술 계산하기 위해서입니다. 이는 C 표준에서는 허용되지 않은 일이지만, gcc에서는 허용됩니다. gcc에서는 void의 사이즈를 1로 계산합니다.


GCC: Arithmetic on void- and Function-Pointers


  • Line 2

ptr의 주소에서 type 구조체의 member 변수까지의 offset을 빼서 type 구조체 주소를 반환합니다.


매크로가 연결된 것 뿐이데 어떻게 마지막 값을 return 하지 생각하시는 분들도 계실 겁 니다. 이 부분은 compound statement 라는 gcc 확장 기능으로 마지막 표현식이 전체 표현식의 값으로 제공됩니다.


GCC: Statements and Declarations in Expressions


list_entry 매크로

리스트에서는 container_of를 list_entry 매크로로 래핑하여 사용합니다.



조회수 1,023회댓글 2개

관련 게시물

전체 보기