계층의 다양한 형태를 살펴보고, sysfs와 커널 소스 트리의 사례를 통해 cgroups 계층을 이해하기 위한 배경을 마련한다.
이 글은 LWN 구독자 여러분이 가능하게 했습니다 LWN.net 구독자들이 이 글과 이를 둘러싼 모든 것을 가능하게 했습니다. 우리의 콘텐츠를 가치 있게 생각하신다면 구독을 구매하여 다음 글 묶음이 가능해지도록 해 주세요.
계층은 어디에나 있다. 이것이 우주의 깊은 성질인지, 아니면 단지 인간의 사고 과정이 만들어낸 결과인지는 모르겠지만, 우리는 브라우저가 표시하는 URL 바(혹은 표시하지 않을지도 모르는)에서부터 농장 마당의 서열에 이르기까지, 바라보는 곳마다 계층을 본다. 위키백과 문서의 본문에서 첫 번째 링크를 클릭하고, 이어서 이동한 각 문서에서도 다시 첫 번째 링크를 반복해서 클릭하면 결국 Philosophy에 도달한다는 재미있는 사실이 있는데, 이는 겉보기엔 94.52%의 경우에만 성립한다고 한다. 그럼에도 이는 모든 지식이 "Philosophy"라는 일반적인 표제 아래 계층적으로 배열될 수 있음을 시사한다.
Control groups(cgroups)는 프로세스를 계층적으로 묶을 수 있게 해 주며, 이 계층의 구체적인 세부 사항은 cgroups가 변화도 겪었고 비판도 받은 영역 중 하나다. 정기적으로 불거지는 논쟁을 즐길 만큼 cgroups를 이해하려는 우리의 지속적인 노력에서, 계층이 사용될 수 있는 서로 다른 방식들에 대한 감각을 갖는 것은 필수적이다. 그래야 cgroups의 계층을 평가할 수 있는 배경이 마련된다.
내 경험 속 한 예가 관련된 몇 가지 이슈를 제기한다고 느끼며, 그 이슈들이 보다 일반적으로 익숙한 파일시스템 계층에서 어떻게 나타나는지 보고, cgroup 계층에서도 무엇을 살펴봐야 하는지 준비할 수 있다.
호주의 한 주요 대학에서 적당한 규모의 컴퓨팅 학과에서 시스템 관리자 역할을 맡았던 때, 우리는 매우 다양한 사용자—학부 및 대학원생, 그리고 교수진과 행정·기술직원을 포함한—에게 다양한 접근 제어를 적용하고 자원 할당을 제공할 수 있는 체계가 필요했다. 여기서 이미 계층이 자연스럽게 드러나며, 연구 중심 학생과 수강 중심 학생 사이, 그리고 기술직과 사무직 전문 인력 사이 등 추가적인 세분화의 여지도 보인다.
이 계층과는 대체로 직교하는 형태로, 학과는 연구 그룹과 지원 그룹(나는 Computing Support Group에서 일했다)으로 나뉘었고, 또한 다수의 개설 과목이 있었는데 각각은 특정 프로그램(Computer Engineering, Software Engineering 등)과 특정 학년에 느슨하게 연관되어 있었다. 각 분과와 과목 안에는 서로 다른 역할의 직원들과 학생들이 모두 있을 수 있었다. 어떤 권한은 계정 소유자가 수행하는 역할과 가장 잘 맞았는데, 예를 들어 직원은 학생보다 더 높은 인쇄 할당량을 받았다. 다른 권한은 계정 소유자의 소속과 맞았는데—예컨대 특정 프린터는 물리적으로 접근할 수 있고 기밀 성적표를 출력하는 데 사용하는 학과 사무실 직원에게만 예약될 수 있었다. 마찬가지로 어떤 특정 과목의 학생들은 컬러 출력에 훨씬 더 큰 예산이 실제로 필요했다.
이 모든 것을 관리하기 위해 우리는 결국 "Reason"(컴퓨터 계정이 부여되는 이유인 다양한 역할을 포함)과 "Organisation"(그 역할이 활동하는 학과 내 조직 부분을 식별)이라는 두 개의 별도 계층을 만들었다. 그리고 이 둘로부터, 각 역할과 각 소속에 대해(적어도 잠재적으로는) 사용자 계정의 그룹이 존재하도록 데카르트 곱(cross product)을 구성했다. 각 계정은 여러 그룹에 존재할 수 있었는데, 직원과 학생 모두 여러 과목에 관여할 수 있고, 일부 상급 학생은 하급 과목의 조교가 될 수도 있었기 때문이다. 다양한 권한과 자원은 개별 역할과 소속, 또는 그 교차점에 할당될 수 있었고, 결합된 계층에 속한 어떤 계정이든 이를 상속받았다.
서로 연결된 두 계층을 갖는 것은 내가 바랐던 단일 계층보다 확실히 더 복잡했지만, 한 가지 특별한 장점이 있었다. 동작했다는 점이다. 이는 매우 유연한 구성이었고, 어떤 특정 컴퓨터 계정을 어디에 붙여야 하는지 결정하는 데 전혀 어려움이 없었다. 유용성을 생각하면 복잡성은 작은 대가였다.
더 나아가 그 대가는 वास्तव상 매우 작았다. 두 계층의 데카르트 곱을 손으로 만들었다면 오류가 나기 쉬웠겠지만, 그럴 필요가 없었다. 비교적 단순한 도구가 내부에서 모든 복잡성을 처리해 주었고, 필요에 따라 중간 트리 노드를 생성하고 연결해 주었다. 트리와 함께 작업할 때—권한이나 자원을 할당하거나 사람을 여러 역할이나 소속에 붙일 때—우리는 내부 세부를 생각할 필요가 거의 없었고, 잘못 이해하여 실수할 위험도 없었다.
이 경험은 단순한 계층에 대한 깊은 의심을 내게 남겼다. 단순한 계층은 종종 매력적이지만, 그만큼 자주 과도한 단순화이기도 하다. 그래서 이 이야기의 첫 번째 교훈은, 약간의 복잡성은 충분히 그 비용을 치를 가치가 있을 수 있다는 점이다. 특히 그 복잡성이 잘 선택되었고 단순한 도구로 뒷받침될 수 있다면 더 그렇다.
이 경험에서 얻은 두 번째 교훈은 두 계층이 단지 세부 사항만 다른 것이 아니라, 성격이 꽤 달랐다는 점이다.
"Reason" 계층은 이른바 "분류(classification)" 계층이라 부를 수 있다. 각 개인은 고유한 역할을 갖지만, 유사한 역할을 클래스에 묶고, 관련된 클래스를 다시 상위 클래스로 묶는 것은 유용하다. 같은 성질을 가진 널리 알려진 계층으로는 Biological classification의 Linnaean taxonomy가 있는데, 이는 생물 개체를 계(Kingdom), 문(Phylum), 강(Class), 목(Order), 과(Family), 속(Genus), 종(Species)이라는 일곱 주요 단계로 조직한 생물 형태의 계층이다.
이런 종류의 계층에서는 모든 구성원이 잎(leaf)에 속한다. 생물학의 예에서 모든 생명체는 어떤 종의 구성원이다. 특정 개체가 어느 종에 속하는지 우리가 모를 수도(또는 합의하지 못할 수도) 있지만, 어떤 개체가 어떤 과(Family)의 구성원이라고 하면서도 어떤 속(Genus)이나 종(Species)에도 속하지 않는다고 말하는 것은 말이 되지 않는다. 그것은 기껏해야 최종 분류로 가기 위한 임시 단계일 뿐이다.
"Organisation" 계층은 전혀 다른 성격을 가진다. 서로 다른 연구 그룹은 연구 관심사를 분류한 결과라기보다는, 관리를 분산하기 위해 사람들을 적절한 크기의 그룹으로 조직하는 방식이었다. 물론 가능한 한 사람들의 관심사와 맞추려 했지만, 누군가가 자연스럽게 속해서가 아니라 가장 편리해서 특정 그룹에 배정되는 일도 드물지 않았다. 어떤 면에서 이 묶음은 별도의 목적을 위해 존재하며, 구성원은 그 목적을 충족하기 위해 그룹에 배치된다. 이는 각 "클래스"가 오직 그 구성원을 담기 위해서만 존재하는 "분류"와 대비된다.
조직(organizational) 계층은 또 하나의 중요한 성질을 가진다. 내부 노드가 개인을 포함하는 것은 완전히 타당하다. 학과장은 전체 학과의 책임자이므로 계층의 맨 위에 속한다. 마찬가지로 프로그램 디렉터는 프로그램 전체와 합리적으로 연관될 수 있으며, 프로그램의 각 과목에 개별적으로 연관될 필요는 없다. 많은 조직에서 각 그룹의 리더나 책임자는 조직 계층에서 한 단계 위의 그룹 구성원이기도 한데, 이는 이 패턴을 강화한다.
이 두 가지 서로 다른 계층 유형은 꽤 흔하며, 종종 서로 뒤섞인다. 많은 독자에게 익숙할 만한 곳으로는 Linux의 "sysfs" 파일시스템과 Linux 커널의 소스 코드 트리가 있다.
/sys의 디바이스"sysfs" 파일시스템(보통 /sys에 마운트됨)은 파일시스템이 본질적으로 그렇듯 분명히 계층 구조다. sysfs에는 현재 모듈, firmware 정보, 파일시스템 세부 사항 등 다양한 객체가 들어 있지만, 원래는 디바이스를 위해 만들어졌고 여기서는 디바이스만을 다룰 것이다.
사실 sysfs 안에는 서로 다른 세 가지 디바이스 계층 배치가 함께 들어 있어, 각 디바이스가 부모를 셋 가져야 할 것처럼 보인다. 디바이스는 디렉터리로 표현되므로 이는 명백히 불가능한데, Unix 디렉터리는 부모를 하나만 가질 수 있기 때문이다. 이 난제는 심볼릭 링크("symlink")를 사용하되, 부모에 대한 명시적 링크가 아니라 암묵적 링크를 두는 방식으로 해결된다. 먼저 symlink로 결합된 계층부터 살펴보자.
/sys/dev에 뿌리를 둔 계층은 "레거시 계층"이라 부를 수 있다. Unix 초기부터 디바이스에는 블록 디바이스와 문자 디바이스라는 두 종류가 있었다. 이들은 보통 /dev에서 찾을 수 있는 다양한 디바이스 특수 파일로 표현된다. 각 파일은 블록 디바이스 또는 문자 디바이스로 식별되며, 주(major) 디바이스 번호는 디바이스의 일반적인 클래스(예: 시리얼 포트, 패러렐 포트, 디스크 또는 테이프 드라이브)를 나타내고, 부(minor) 번호는 그 클래스 중 어떤 конкрет 디바이스가 대상인지 나타낸다.
이 3단계 계층은 /sys/dev 아래에서 그대로 발견되는데, 마지막 두 레벨을 구분하는 데 슬래시 대신 콜론을 사용한다. 따라서 /sys/dev/block/8:0(major 8, minor 0인 블록 디바이스)은 "sda"로도 알려진 디바이스를 나타내는 디렉터리로의 심볼릭 링크다. 그 디렉터리에서 시작해 /sys/dev로부터의 경로를 찾고 싶다면, "dev" 파일을 읽어서 마지막 두 구성 요소("8:0")를 알아낼 수 있다. 이것이 블록 디바이스라는 것을 판별하는 일은 덜 직접적이지만, "bdi"(block device info) 디렉터리가 존재한다면 강력한 단서가 된다.
이 계층은 /dev에 있는 디바이스 파일의 이름만 알고 있거나, 그런 디바이스에 대한 열린 파일 디스크립터만 가지고 있을 때 특히 유용하다. stat() 또는 fstat() 시스템 호출은 디바이스 타입과 major/minor 번호를 보고해 주고, 이는 손쉽게 /sys/dev의 경로명으로 변환할 수 있으며, 이를 통해 디바이스에 대한 다른 유용한 정보로 이어질 수 있다.
두 번째 symlink 기반 계층은 아마도 가장 일반적으로 유용할 것이다. 이는 /sys/class와 /sys/bus에 뿌리를 두고 있는데, 이는 사실 둘을 함께 담는 상위 레벨이 하나 더 있어야 함을 시사한다. 이 둘을 새로운 /sys/subsystem 트리로 합치려는 계획이 있지만, 그 계획이 최소 7년은 된 것으로 보이니 나는 큰 기대를 하지 않는다. 다만 이 계획의 유용한 측면 하나는 이미 구현되어 있는데, 각 디바이스 디렉터리에 subsystem symlink가 있어 class 또는 bus 트리로 되돌아가도록 되어 있어, 이 계층 내에서 임의의 디바이스의 부모를 쉽게 찾을 수 있다는 점이다.
/sys/class 계층은 꽤 단순하며, 여러 디바이스 클래스가 있고 각 클래스는 실제 디바이스 디렉터리에 대한 링크를 가진 구체적인 디바이스들을 포함한다. 따라서 개념적으로는 레거시 계층과 상당히 비슷하되, 숫자 대신 이름을 쓴다. /sys/bus 계층도 유사하지만, 디바이스들이 별도의 devices 하위 디렉터리에 모여 있어 각 bus 디렉터리에 드라이버와 기타 세부 정보도 함께 둘 수 있다.
디바이스를 조직하는 세 번째 계층은 symlink에 의존하지 않는, 진짜 디렉터리 기반 계층이다. 이는 /sys/devices에서 찾을 수 있으며, 솔직히 말해 구조를 설명하기가 다소 어렵다.
이 조직의 지배적인 주제는 디바이스의 물리적 연결 관계를 따른다는 점이다. 예를 들어 하드 드라이브가 USB 포트를 통해 접근되고, USB 컨트롤러가 PCI 버스에 붙어 있다면, 해당 하드 드라이브로 가는 계층 경로는 먼저 PCI 버스를 찾고 그다음 USB 포트를 찾는다. 하드 드라이브 아래에는 드라이브의 데이터에 접근을 제공하는 "block" 디바이스가 있고, 이어서 파티션을 위한 하위 디바이스가 있을 수도 있다.
이는 그럴듯해 보이는 구성인데, 일부 디바이스는 제어 신호를 한 곳에서(혹은 별도의 리셋 라인이 있다면 두 곳에서) 받고 전원 공급은 다른 곳에서 받기도 하므로, 단순한 계층만으로는 상호 연결성을 제대로 기술할 수 없다는 점을 깨닫는 순간 문제가 된다. 이 이슈는 올해 Kernel Summit 준비 과정에서 널리 논의되었다.
이 계층들을 "분류" 대 "조직"이라는 관점에서 살펴보면, 꽤 분명한 패턴이 드러난다. /sys/dev 계층은 단순한 분류 계층이지만, 많은 디바이스(예: 네트워크 인터페이스)가 거기에 나타나지 않는다는 점에서 아마도 지나치게 단순하다. subsystem 계층의 /sys/class 부분도 마찬가지로 단순한 분류이지만, 더 완전하다.
subsystem 계층의 /sys/bus 부분 역시 단순한 2레벨 분류이지만, drivers 디렉터리 같은 버스 타입별 추가 정보가 존재해 이를 약간 혼란스럽게 만든다. class 계층의 디바이스는 제공하는 기능(net, sound, watchdog 등)에 따라 분류된다. bus 계층의 디바이스는 접근 방식에 따라 분류되며, 서로 다른 기능 단위라기보다는 서로 다른 주소 지정 가능한 단위를 나타낸다. /sys/bus 서브트리의 추가 엔트리는, 각 주소 지정 가능한 단위에 대해 어떤 기능(드라이버로 표현되고 class 디바이스로 구현되는)이 요청되는지에 대한 некотор의 제어를 가능하게 한다.
이를 이해하고 나면, 계층적으로는 단순한 2레벨 분류다.
/sys/devices 계층은 의심의 여지 없이 조직 계층이다. 이는 모든 class 디바이스와 모든 bus 디바이스를, 디바이스의 물리적 조직에 대한 대략적인 아날로그 형태로 담고 있다. 물리적 디바이스가 없거나, 현재 어떤 종류의 버스에도 표현되지 않는 경우, 디바이스는 /sys/devices/virtual 아래로 조직된다.
여기서도 동일한 객체에 대해 분류 계층과 조직 계층이 모두 유용할 수 있으며, 각각 고유한 방식으로 도움이 됨을 다시 본다. 둘 다를 다루는 데 어느 정도 복잡성이 있을 수 있지만, 규칙을 따르면 그리 나쁘지 않다.
계층에 대한 상당히 다른 관점을 위해 Linux 커널 소스 코드 트리를 볼 수 있는데, 진화하는 많은 소스 코드 트리도 비슷한 예를 제공할 수 있다. 이 계층은 분류보다는 조직에 더 가깝지만, 앞서 논의한 연구 그룹과 마찬가지로, 가능하고 편리하다면 관련된 것들을 함께 두려는 시도는 일반적으로 존재한다.
여기에는 강조할 만한 계층의 두 측면이 있는데, 이는 의식적으로든 무의식적으로든 반드시 내려야 하는 선택을 보여 준다.
최상위 레벨에는 fs(파일시스템 및 nfsd 같은 파일 서버), mm(메모리 관리), sound, block, crypto 등 여러 주요 서브시스템 디렉터리가 있다. 이는 모두 그럴듯한 분류처럼 보인다. 그리고 kernel이 있다. Linux 전체가 운영체제 커널인데, 그러면 이건 커널의 커널일까?
실제로는, 어떤 특정 서브시스템에도 잘 속하지 않는 여러 구분된 조각들, 혹은 파일 한두 개만 필요할 정도로 작은 서브시스템들이 모여 있는 곳이다. time이나 sched 디렉터리처럼, 한때는 kernel에 들어갈 만큼 작았던 서브시스템이 커져서 자기 디렉터리가 필요해졌지만, kernel이라는 우산 아래에서 벗어날 만큼 대담하지는 못했던 경우도 있다.
fs 서브트리도 비슷한 파일 구성을 가진다. fs의 대부분은 여러 파일시스템이고, exportfs(여러 파일 서버를 돕는다)나 dlm(클러스터 파일시스템을 위한 잠금을 지원한다)처럼 일부 지원 모듈은 자기 하위 디렉터리를 가진다. 그러나 fs에는 파일시스템에 서비스를 제공하거나 더 높은 수준의 시스템 호출 인터페이스를 구현하는 C 파일들의 ad hoc 모음도 있다. 이는 최상위의 kernel(그리고 아마 lib) 아래에 있는 코드와 정확히 같은 종류다. 하지만 fs에는 잡다한 것들을 위한 하위 디렉터리가 없어서, 모든 것이 fs의 최상위에 그대로 남아 있다.
모든 것을 각자의 잎 디렉터리로 분류해야 하는지(커널 모델), 아니면 내부 디렉터리에 소스 코드를 두는 것을 허용할지(fs에서 하는 방식)에 대해 반드시 정답이 있는 것은 아니다. 하지만 이는 내려야 하는 선택이며, cgroups의 계층을 논쟁할 때 분명히 의견을 가질 만한 지점이다.
커널 소스 트리에는 또 다른 종류의 분류가 있다. scripts는 scripts 디렉터리에, firmware는 firmware 디렉터리에, 헤더 파일은 include 디렉터리에—그렇지 않은 경우를 제외하면—있다. 최근 몇 년 동안 일부 헤더 파일을 include 디렉터리 트리 밖으로 옮겨, 관련된 C 소스 코드 파일에 더 가깝게 두려는 경향이 있었다. 이를 더 구체적으로 하기 위해 NFS와 ext3 파일시스템의 예를 들어 보자.
이들 파일시스템은 각각 몇 개의 C 언어 파일, 몇 개의 C 헤더 파일, 그리고 기타 여러 파일로 구성된다. 질문은 이것이다. NFS의 헤더 파일은 ext3의 헤더 파일과 함께 있어야 하는가(헤더 파일끼리 모으기), 아니면 NFS의 C 언어 파일과 함께 있어야 하는가(NFS 파일끼리 모으기)? 다른 말로 하면, 계층을 사용해 헤더 파일을 다른 파일과 구분해 분류해야 하는가, 아니면 서로 다른 이름만으로 충분한가?
한때는 대부분의 헤더 파일이(전부는 아니더라도) include 트리에 있었다. 오늘날에는 include 파일이 C 파일과 섞여 있는 경우를 매우 흔히 볼 수 있다. ext3의 경우 Linux 3.4에서 큰 변화가 있었는데, 네 개의 헤더 파일이 모두 include/linux/에서 옮겨져 ext3 코드와 함께 단일 파일 fs/ext3/ext3.h로 들어갔다.
여기서 핵심은, 계층을 사용하지 않고도 분류는 충분히 가능하다는 점이다. 때로는 계층적 분류가 작업에 완벽히 들어맞는다. 때로는 그저 번거롭고 불편한 짐일 뿐이다. 필요할 때에만, 그리고 필요할 때에 한해 계층을 기꺼이 사용하는 태도는 매우 합리적이다.
이 글 연재의 진짜 목표인 cgroups를 이해하려면, 프로세스 그룹을 관리하는 방법과 그 관리에서 계층이 어떤 역할을 할 수 있는지에 대한 이해가 필요하다. 위의 내용은 프로세스에 대해 직접적으로 말하는 것은 아니지만, cgroups의 세부를 보기 시작할 때 고려해볼 유용한 질문이나 이슈를 몇 가지 던져 준다.
miscellaneous 잎에 불과하더라도 잎으로 강제해야 할까?지난번에 살펴본 프로세스 그룹의 계층에서는, 로그인 세션으로 먼저 분류하고 그다음 작업 그룹으로 분류하는 단일하고 단순한 계층을 보았다. 계층에 포함된 프로세스는 모두 잎에 있었지만, 일반적으로 tty를 전혀 열지 않는 시스템 데몬 같은 많은 프로세스는 계층에서 완전히 빠져 있었다.
좀 더 현대적인 설정에서 이 질문들에 대한 답을 찾기 시작하려면, cgroups가 실제로 프로세스에 대해 무엇을 하고 그 그룹들이 무엇에 사용되는지 이해해야 한다. 다음 글에서는 자원 컨트롤러와, 프로세스 집합을 그룹으로 취급해야 하는 여러 다른 연산을 포함하는 cgroups "subsystems"를 자세히 살펴보며 그 질문에 답하기 시작할 것이다.
| 이 글의 인덱스 항목 |
|---|
| Kernel |
| GuestArticles |