새로운 init 시스템 systemd의 배경, 설계 원칙, 기능, 그리고 향후 방향을 설명하는 글.
커넥션이 좋거나 행간을 잘 읽는 분이라면 이 블로그 글이 무엇에 관한 것인지 이미 알고 있을지도 모릅니다. 하지만 그렇다 하더라도 이 이야기는 흥미롭게 느껴질 수 있습니다. 그러니 커피 한 잔을 들고, 자리에 앉아, 이제 이어질 내용을 읽어 보십시오.
이 블로그 글은 깁니다. 그래서 긴 이야기를 읽는 것을 강력히 권하고 싶지만, 한 문장으로 요약하면 이렇습니다. 우리는 새로운 init 시스템을 실험하고 있으며, 그 과정이 즐겁습니다.
코드는 여기 있습니다. 그리고 이제 이야기입니다:
모든 Unix 시스템에는 특별한 프로세스 식별자 1을 가진 프로세스가 하나 있습니다. 이 프로세스는 다른 모든 프로세스보다 먼저 커널에 의해 시작되며, 다른 누구의 자식도 될 수 없는 모든 프로세스의 부모 프로세스가 됩니다. 이 때문에 다른 프로세스가 할 수 없는 많은 일을 할 수 있습니다. 또한 부팅 중 userspace를 올리고 유지하는 일처럼, 다른 프로세스가 책임지지 않는 몇 가지 일도 맡고 있습니다.
역사적으로 Linux에서 PID 1 역할을 해 온 소프트웨어는 유서 깊은 sysvinit 패키지였지만, 꽤 오래전부터 시대의 흐름을 따라가지 못하는 모습이 드러나고 있었습니다. 많은 대체안이 제안되었지만, 실제로 널리 자리 잡은 것은 하나뿐이었습니다. 바로 Upstart이며, 이제는 모든 주요 배포판에 퍼져 있습니다.
앞서 말했듯이, init 시스템의 핵심 책임은 userspace를 올리는 것입니다. 그리고 좋은 init 시스템은 그것을 빠르게 해냅니다. 안타깝게도 전통적인 SysV init 시스템은 특별히 빠르지 않았습니다.
빠르고 효율적인 부팅을 위해서는 두 가지가 중요합니다.
이게 무슨 뜻일까요? 덜 시작한다는 것은 더 적은 서비스를 시작하거나, 실제로 필요해질 때까지 서비스 시작을 미루는 것을 뜻합니다. 어떤 서비스들은 언젠가는 필요하리라는 것을 알고 있습니다. 예를 들면 syslog, D-Bus system bus 등이 그렇습니다. 하지만 많은 다른 서비스는 그렇지 않습니다. 예를 들어 bluetoothd는 실제로 블루투스 동글이 꽂히거나 어떤 애플리케이션이 그 D-Bus 인터페이스와 통신하려고 하지 않는 한 실행될 필요가 없습니다. 프린팅 시스템도 마찬가지입니다. 기계가 물리적으로 프린터에 연결되어 있지 않거나, 어떤 애플리케이션이 무언가를 인쇄하려고 하지 않는다면 CUPS 같은 프린팅 데몬을 실행할 필요가 없습니다. Avahi 역시 기계가 네트워크에 연결되어 있지 않다면, 또는 어떤 애플리케이션이 그 API를 사용하려고 하지 않는다면 Avahi를 실행할 필요가 없습니다. 심지어 SSH도 마찬가지입니다. 누군가 당신의 시스템에 접속하려고 하지 않는 한 그것을 실행할 필요는 없습니다. 첫 연결 시점에 시작되기만 하면 됩니다. (솔직히 말해서, sshd가 리슨하고 있을 만한 대부분의 시스템에서는 누군가가 접속하는 일이 두 달에 한 번 정도일 뿐이라는 점을 인정하시겠지요.)
더 많은 것을 병렬로 시작한다는 것은, 무언가를 실행해야 한다면 그 시작 과정을 직렬화하지 말고(sysvinit가 하듯이), 모두 동시에 실행해서 사용 가능한 CPU와 디스크 IO 대역폭을 최대한 활용하고, 그 결과 전체 시작 시간을 최소화하자는 뜻입니다.
현대 시스템, 특히 범용 OS는 구성과 사용 방식이 매우 동적입니다. 이동성이 있고, 서로 다른 애플리케이션이 시작되고 종료되며, 여러 하드웨어가 추가되었다가 다시 제거됩니다. 서비스를 유지하는 책임을 지는 init 시스템은 이런 하드웨어 및 소프트웨어 변화를 감지해야 합니다. 프로그램을 실행하거나 어떤 하드웨어를 활성화하는 데 필요해질 때 서비스를 동적으로 시작하고, 때로는 중지할 수도 있어야 합니다.
현재 부팅을 병렬화하려는 대부분의 시스템은 여전히 여러 데몬의 시작을 동기화합니다. 예를 들어 Avahi는 D-Bus가 필요하므로 먼저 D-Bus를 시작하고, D-Bus가 준비되었다는 신호를 보낸 뒤에야 Avahi도 시작합니다. 다른 서비스도 비슷합니다. livirtd와 X11은 HAL이 필요합니다. (여기서는 Fedora 13의 서비스를 생각하고 있습니다. HAL이 이미 구식이라는 점은 무시합시다.) 그래서 HAL이 먼저 시작되고, 그 뒤에야 livirtd와 X11이 시작됩니다. 그리고 libvirtd 역시 Avahi가 필요하므로 Avahi도 기다립니다. 또 이들 모두는 syslog가 필요하므로 Syslog가 완전히 시작되고 초기화될 때까지 모두 기다립니다. 이런 식입니다.
이런 종류의 시작 동기화는 부팅 과정의 상당 부분을 직렬화하게 만듭니다. 이 동기화와 직렬화 비용을 없앨 수 있다면 멋지지 않을까요? 사실 가능합니다. 그러려면 각 데몬이 서로에게 정확히 무엇을 요구하는지, 그리고 왜 시작이 지연되는지를 이해해야 합니다. 전통적인 Unix 데몬의 경우 그 답은 하나입니다. 다른 데몬이 서비스를 제공하는 소켓이 연결 가능한 상태가 될 때까지 기다리는 것입니다. 보통은 파일 시스템 안의 AF_UNIX 소켓이지만, AF_INET[6]일 수도 있습니다. 예를 들어 D-Bus 클라이언트는 /var/run/dbus/system_bus_socket에 연결할 수 있게 되기를 기다리고, syslog 클라이언트는 /dev/log를 기다리며, CUPS 클라이언트는 /var/run/cups/cups.sock를, NFS 마운트는 /var/run/rpcbind.sock와 portmapper IP 포트를 기다립니다. 그리고 생각해 보면, 사실 이것만 기다리는 것입니다!
그렇다면 이것이 전부라면, 우리가 그 소켓들을 더 이른 시점에 연결 가능하게 만들고, 전체 데몬 시작이 끝나기를 기다리는 대신 오직 그 사실만 기다리게 할 수 있다면, 전체 부팅을 더 빠르게 하고 더 많은 프로세스를 병렬로 시작할 수 있습니다. 그럼 어떻게 할 수 있을까요? Unix 계열 시스템에서는 사실 꽤 쉽습니다. 데몬을 실제로 시작하기 전에 리슨 소켓을 생성하고, exec() 시점에 그 소켓을 넘겨주면 됩니다. 그렇게 하면 init 시스템에서 한 단계로 모든 데몬의 모든 소켓을 만들고, 두 번째 단계에서 모든 데몬을 한꺼번에 실행할 수 있습니다. 어떤 서비스가 다른 서비스를 필요로 하는데 그 서비스가 아직 완전히 시작되지 않았다 하더라도 전혀 괜찮습니다. 무슨 일이 일어나느냐 하면, 해당 연결은 서비스를 제공하는 쪽에 큐잉되고 클라이언트는 그 한 요청에서만 잠재적으로 블록됩니다. 하지만 블록되는 것은 오직 그 클라이언트 하나이며, 오직 그 요청 하나뿐입니다. 또한 서비스 간 의존성도 더 이상 올바른 병렬 시작을 위해 반드시 구성할 필요가 없어집니다. 모든 소켓을 한 번에 시작하고 어떤 서비스가 다른 서비스를 필요로 한다면, 그 소켓에는 연결할 수 있다고 확신할 수 있기 때문입니다.
이것이 이어질 내용의 핵심이므로, 다른 말과 예시로 다시 설명하겠습니다. syslog와 여러 syslog 클라이언트를 동시에 시작한다고 합시다. 위에서 설명한 구조에서는 클라이언트들의 메시지가 /dev/log 소켓 버퍼에 추가됩니다. 그 버퍼가 가득 차지 않는 한, 클라이언트는 전혀 기다릴 필요 없이 즉시 시작을 계속할 수 있습니다. syslog 자체가 시작을 마치는 즉시 모든 메시지를 꺼내 처리하게 됩니다. 또 다른 예를 들어 봅시다. D-Bus와 여러 클라이언트를 동시에 시작합니다. 동기식 버스 요청이 보내져 응답이 필요하다면, 해당 클라이언트는 블록될 수 있습니다. 하지만 역시 그 클라이언트 하나만, 그리고 D-Bus가 따라잡아 그 요청을 처리할 때까지만 블록됩니다.
기본적으로 커널 소켓 버퍼가 병렬화를 극대화하는 데 도움을 주며, 정렬과 동기화는 userspace의 추가 관리 없이 커널이 처리해 줍니다! 그리고 데몬이 실제로 시작되기 전에 모든 소켓이 사용 가능해지면 의존성 관리도 중복되거나, 적어도 부차적인 것이 됩니다. 어떤 데몬이 다른 데몬을 필요로 한다면 그냥 그쪽에 연결하면 됩니다. 상대 데몬이 이미 시작되어 있다면 즉시 성공합니다. 아직 시작 중이라면 첫 번째 데몬은 동기식 요청을 보내지 않는 한 기다릴 필요조차 없습니다. 그리고 상대 데몬이 전혀 실행 중이지 않더라도 자동으로 시작할 수 있습니다. 첫 번째 데몬의 관점에서는 차이가 없으므로, 의존성 관리는 대부분 불필요하거나 적어도 부차적인 것이 되며, 이 모든 것이 최적의 병렬화와 필요 시 온디맨드 로딩까지 포함한 상태에서 이루어집니다. 그 위에 이것은 더 견고하기도 합니다. 실제 데몬이 일시적으로 사용 불가능해지더라도(예를 들어 크래시로 인해) 소켓은 계속 사용 가능하기 때문입니다. 실제로 이 방식을 사용하면 실행되고, 종료되거나(혹은 크래시 나고), 다시 실행되고, 다시 종료되는 데몬을 쉽게 작성할 수 있습니다. 그리고 이 모든 동안 클라이언트는 눈치채지 못하거나 어떤 요청도 잃지 않을 수 있습니다.
잠깐 쉬어 갈 좋은 시점입니다. 가서 커피잔을 다시 채우십시오. 그리고 안심하십시오. 더 흥미로운 내용이 이어집니다.
하지만 먼저 몇 가지를 분명히 하고 넘어갑시다. 이런 종류의 로직이 새로운 것일까요? 아닙니다. 결코 그렇지 않습니다. 가장 잘 알려진 시스템은 Apple의 launchd입니다. MacOS에서는 소켓 리슨 기능을 모든 데몬에서 빼내어 launchd가 맡습니다. 따라서 서비스 자체는 모두 병렬로 시작할 수 있고 의존성을 따로 구성할 필요가 없습니다. 사실 이것은 정말로 독창적인 설계이며, MacOS가 환상적인 부팅 시간을 제공할 수 있는 주된 이유이기도 합니다. launchd 개발자들이 무엇을 하고 있는지 설명하는 이 영상을 강력히 추천합니다. 안타깝게도 이 아이디어는 Apple 진영 밖에서는 별로 널리 퍼지지 못했습니다.
사실 이 아이디어는 launchd보다도 더 오래되었습니다. launchd 이전에는 유서 깊은 inetd가 상당히 비슷한 방식으로 동작했습니다. 소켓은 중앙 데몬에서 생성하고, 실제 서비스 데몬을 시작할 때 exec() 과정에서 소켓 파일 디스크립터를 넘겨주었습니다. 다만 inetd의 초점은 분명히 로컬 서비스가 아니라 인터넷 서비스였고, 나중의 재구현에서는 AF_UNIX 소켓도 지원했지만요. 또한 이것은 부팅 병렬화를 위한 도구도 아니었고, 암묵적 의존성을 올바르게 맞추는 데도 유용하지는 않았습니다.
TCP 소켓의 경우 inetd는 주로 들어오는 연결마다 새 데몬 인스턴스를 하나씩 생성하는 방식으로 사용되었습니다. 이는 연결마다 새 프로세스를 생성하고 초기화해야 한다는 뜻이었고, 고성능 서버를 만드는 방식으로는 적절하지 않았습니다. 하지만 inetd는 처음부터 또 다른 모드도 지원했습니다. 첫 연결에서 단일 데몬을 생성하고, 그 단일 인스턴스가 이후 연결들도 계속 accept()하는 방식입니다. (inetd.conf의 wait/nowait 옵션이 바로 그것을 위한 것이었는데, 안타깝게도 특히 문서화가 형편없는 옵션이었습니다.) 연결당 데몬을 시작하는 방식이 아마 inetd가 느리다는 나쁜 평판을 얻게 만든 원인일 것입니다. 하지만 그것은 완전히 공정한 평가는 아닙니다.
Linux의 현대적인 데몬들은 단순한 AF_UNIX 소켓 대신 D-Bus를 통해 서비스를 제공하는 경향이 있습니다. 그렇다면 이런 서비스들에도 전통적인 소켓 서비스에서와 같은 부팅 병렬화 로직을 적용할 수 있을까요? 예, 할 수 있습니다. D-Bus는 이미 이를 위한 적절한 훅을 모두 갖추고 있습니다. bus activation을 사용하면 서비스는 처음 접근되는 시점에 시작될 수 있습니다. 또한 bus activation은 D-Bus 서비스를 제공하는 쪽과 사용하는 쪽을 동시에 시작할 때 필요한 최소한의 요청 단위 동기화도 제공합니다. 예를 들어 Avahi와 CUPS를 동시에 시작하고 싶다고 합시다. (덧붙이자면 CUPS는 mDNS/DNS-SD 프린터를 찾아보기 위해 Avahi를 사용합니다.) 그러면 둘을 그냥 동시에 실행하면 됩니다. 그리고 CUPS가 Avahi보다 더 빨리 실행되더라도, bus activation 로직을 통해 D-Bus가 Avahi가 자신의 서비스 이름을 확보할 때까지 해당 요청을 큐에 넣어 둘 수 있습니다.
요약하자면, 소켓 기반 서비스 활성화와 버스 기반 서비스 활성화를 함께 사용하면 모든 데몬을 추가 동기화 없이 병렬로 시작할 수 있습니다. 또한 activation은 서비스의 lazy-loading도 가능하게 해 줍니다. 어떤 서비스가 드물게 사용된다면 부팅 중에 시작하는 대신 누군가 처음으로 해당 소켓이나 버스 이름에 접근할 때만 로드하면 됩니다.
이게 대단하지 않다면, 무엇이 대단한지 저는 모르겠습니다!
현재 배포판들의 부팅 과정 직렬화 그래프를 보면, 동기화 지점은 데몬 시작뿐만이 아닙니다. 가장 두드러지는 것은 파일 시스템 관련 작업입니다. 마운트, fsck, quota 같은 것들입니다. 지금은 부팅 중 많은 시간이 /etc/fstab에 나열된 모든 장치가 장치 트리에 나타나고, 그 후 fsck가 수행되고, 마운트되고, quota 검사가 끝나기를 기다리며 빈둥거리는 데 쓰입니다. 이 모든 것이 완전히 끝난 뒤에야 실제 서비스를 부팅합니다.
이것을 개선할 수 있을까요? 알고 보니 가능합니다. Harald Hoyer가 이를 위해 유서 깊은 autofs 시스템을 사용하는 아이디어를 내놓았습니다.
connect() 호출이 어떤 서비스가 다른 서비스에 관심이 있다는 것을 보여주듯이, open() 호출(혹은 유사한 호출)은 어떤 서비스가 특정 파일이나 파일 시스템에 관심이 있다는 것을 보여줍니다. 따라서 병렬화를 더 끌어올리기 위해, 애플리케이션은 자신이 찾는 파일 시스템이 아직 마운트되어 준비되지 않은 경우에만 기다리게 만들 수 있습니다. 우리는 autofs 마운트 지점을 설정하고, 이후 부팅 과정에서 파일 시스템의 fsck와 quota가 끝나면 이를 실제 마운트로 대체합니다. 파일 시스템이 아직 준비되지 않았다면 커널이 그 접근을 큐잉하고 접근한 프로세스는 블록되지만, 역시 그 데몬 하나와 그 접근 하나만 영향을 받습니다. 이렇게 하면 모든 파일 시스템이 완전히 준비되기 전에라도 데몬 시작을 시작할 수 있습니다. 파일을 놓치는 일 없이, 그리고 병렬화를 극대화하면서 말입니다.
파일 시스템 작업과 서비스 작업을 병렬화하는 것은 /에 대해서는 의미가 없습니다. 결국 서비스 바이너리는 보통 거기에 저장되기 때문입니다. 하지만 /home 같은 파일 시스템은 일반적으로 더 크고, 심지어 암호화되어 있거나 원격일 수 있으며, 보통의 부팅 데몬들이 드물게만 접근하기 때문에 부팅 시간을 상당히 개선할 수 있습니다. 굳이 말할 필요도 없겠지만 procfs나 sysfs 같은 가상 파일 시스템은 autofs로 마운트해서는 안 됩니다.
일부 독자들은 init 시스템에 autofs를 통합하는 것이 다소 취약하고 심지어 이상하게, 어쩌면 약간은 “무리수”처럼 느껴질 수도 있다고 생각할지 모르겠습니다. 하지만 제가 이것을 광범위하게 가지고 놀아 본 결과 말씀드릴 수 있는 것은, 실제로는 이것이 꽤 자연스럽게 느껴진다는 점입니다. 여기서 autofs를 사용한다는 것은 단지 즉시 백엔드 파일 시스템을 제공하지 않고도 마운트 지점을 만들 수 있다는 뜻입니다. 효과적으로는 그저 접근을 지연시킬 뿐입니다. 애플리케이션이 autofs 파일 시스템에 접근하려 할 때 우리가 실제 파일 시스템으로 바꾸는 데 아주 오래 걸린다면, 그 애플리케이션은 인터럽트 가능한 sleep 상태로 걸리게 됩니다. 즉 C-c 같은 방식으로 안전하게 취소할 수 있다는 뜻입니다. 또한 어떤 시점이든, 최종적으로 그 마운트 지점이 마운트될 수 없게 되면(예를 들면 fsck 실패) autofs에게 단순히 깔끔한 오류 코드(예: ENOENT)를 반환하라고 할 수 있습니다. 그러니 제가 말하고 싶은 것은 이렇습니다. init 시스템에 autofs를 통합하는 것은 처음에는 다소 모험적으로 보일 수 있지만, 우리의 실험 코드는 이 아이디어가 올바른 이유와 올바른 방식으로 적용될 경우 실제로 놀랄 만큼 잘 작동함을 보여주었다는 것입니다.
또한 이것은 직접 autofs 마운트여야 한다는 점도 주목하십시오. 즉 애플리케이션 관점에서는 고전적인 마운트 지점과 autofs 기반 마운트 지점 사이에 실질적인 차이가 거의 없습니다.
MacOS의 부팅 로직에서 배울 수 있는 또 다른 점은 셸 스크립트가 악이라는 것입니다. 셸은 빠르면서도 느립니다. 해킹하기에는 빠르지만 실행은 느립니다. 고전적인 sysvinit 부팅 로직은 셸 스크립트를 중심으로 설계되어 있습니다. /bin/bash이든 다른 어떤 셸이든(셸 스크립트를 더 빠르게 실행하려고 만들어졌더라도), 결국 이 접근 방식은 느릴 수밖에 없습니다. 제 시스템에서 /etc/init.d 안의 스크립트는 grep을 최소 77번 호출합니다. awk는 92번, cut은 23번, sed는 74번 호출됩니다. 이런 명령들(그리고 다른 것들)이 호출될 때마다 프로세스가 하나 생성되고, 라이브러리를 찾고, i18n 같은 시작 작업을 설정하고, 그 밖의 여러 일을 합니다. 그리고 나서 대부분 단순한 문자열 작업 하나만 하고 다시 종료됩니다. 물론 이것은 엄청나게 느릴 수밖에 없습니다. 셸을 제외한 다른 어떤 언어도 이런 식으로 하지는 않을 것입니다. 게다가 셸 스크립트는 매우 취약하기도 하며, 환경 변수 같은 것들에 따라 동작이 크게 바뀝니다. 이런 것들은 파악하고 통제하기가 어렵습니다.
그러니 부팅 과정에서 셸 스크립트를 없애 봅시다! 그러기 전에 현재 셸 스크립트가 실제로 어디에 쓰이는지부터 파악해야 합니다. 큰 그림에서 보면, 대부분의 경우 그것들이 하는 일은 사실 꽤 지루합니다. 스크립팅의 대부분은 서비스의 사소한 설정과 해제에 쓰이고 있으며, 이는 C로 다시 작성되어 별도 실행 파일이 되거나, 데몬 자체로 옮겨지거나, 혹은 그냥 init 시스템이 직접 수행해야 합니다.
시스템 부팅 중 셸 스크립트를 완전히 없애는 일은 당분간 현실적이지 않을 것입니다. 그것들을 C로 다시 작성하는 데는 시간이 걸리고, 몇몇 경우에는 별로 의미가 없으며, 때로는 셸 스크립트가 너무 편리해서 포기하기 어렵기 때문입니다. 하지만 적어도 그것들의 존재감을 줄일 수는 있습니다.
부팅 과정이 셸 스크립트에 얼마나 오염되어 있는지 측정하는 좋은 지표는, 시스템이 완전히 부팅된 뒤 사용자가 처음 시작할 수 있는 프로세스의 PID 번호입니다. 부팅하고 로그인한 뒤 터미널을 열어 echo $$를 입력해 보십시오. 여러분의 Linux 시스템에서 그 결과를 확인한 다음 MacOS와 비교해 보십시오! (힌트를 드리자면 대략 이렇습니다. Linux PID 1823, MacOS PID 154. 우리가 가진 테스트 시스템에서 측정한 값입니다.)
서비스를 시작하고 유지하는 시스템의 중심 기능 중 하나는 프로세스 보모 역할이어야 합니다. 즉 서비스를 감시해야 합니다. 종료되면 다시 시작하고, 크래시가 나면 그에 대한 정보를 수집해 관리자에게 남겨 두고, 그 정보를 abrt 같은 크래시 덤프 시스템이나 syslog, audit 시스템의 로그와 연결해 주어야 합니다.
또한 서비스를 완전히 종료할 수 있어야 합니다. 이것은 쉬워 보이지만 생각보다 어렵습니다. 전통적인 Unix에서는 double-fork를 하는 프로세스가 부모의 감독에서 벗어날 수 있고, 원래 부모는 새 프로세스가 자신이 실제로 시작한 프로세스와 어떤 관계인지 알 수 없습니다. 예를 하나 들면, 현재는 double-fork한 문제 있는 CGI 스크립트는 Apache를 종료해도 종료되지 않습니다. 게다가 이름과 용도를 이미 알고 있지 않다면, 그 프로세스가 Apache와 어떤 관계였는지도 알아낼 수조차 없습니다.
그렇다면 어떻게 프로세스를 추적해서, 보모의 감독에서 벗어날 수 없게 하고, 아무리 많이 fork하더라도 하나의 단위로 제어할 수 있게 할 수 있을까요?
이에 대해 사람마다 다른 해법을 내놓았습니다. 여기서 자세히 들어가지는 않겠지만, 적어도 ptrace나 netlink connector(시스템의 어떤 프로세스든 fork()하거나 exit()할 때마다 netlink 메시지를 받을 수 있게 해 주는 커널 인터페이스)에 기반한 접근법은 일부 사람들이 연구하고 구현했음에도 불구하고, 보기 흉하고 확장성이 별로 좋지 않다는 비판을 받아 왔다고는 말해 둘 수 있겠습니다.
그렇다면 무엇을 할 수 있을까요? 꽤 오래전부터 커널에는 Control Groups, 즉 “cgroups”가 있습니다. 기본적으로 이것은 프로세스 그룹의 계층 구조를 만들 수 있게 해 줍니다. 이 계층 구조는 가상 파일 시스템 안에 직접 노출되므로 쉽게 접근할 수 있습니다. 그룹 이름은 그 파일 시스템 안의 디렉터리 이름과 같습니다. 특정 cgroup에 속한 프로세스가 fork()하면 그 자식 역시 같은 그룹의 구성원이 됩니다. 특권이 있거나 cgroup 파일 시스템에 접근할 수 있지 않는 한, 프로세스는 자기 그룹에서 벗어날 수 없습니다. 원래 cgroups는 컨테이너를 위해 커널에 도입되었습니다. 특정 커널 서브시스템이 CPU나 메모리 사용량 제한처럼 특정 프로세스 그룹의 자원 사용 한도를 강제할 수 있게 해 줍니다. 전통적인 자원 제한(setrlimit()으로 구현되는 것들)은 대체로 프로세스별입니다. 반면 cgroups는 프로세스 전체 그룹에 대해 제한을 걸 수 있습니다. cgroups는 직접적인 컨테이너 사용 사례 밖에서도 유용합니다. 예를 들면 Apache와 그 모든 자식이 사용할 수 있는 메모리나 CPU 총량을 제한할 수 있습니다. 그러면 문제 있는 CGI 스크립트가 단순히 fork해 버리는 방식으로 setrlimit() 기반 자원 제어를 빠져나갈 수 없게 됩니다.
컨테이너 및 자원 제한 강제 외에도, cgroups는 데몬을 추적하는 데 매우 유용합니다. cgroup 소속은 자식 프로세스에 안전하게 상속되며, 이들은 벗어날 수 없습니다. cgroup이 비게 되면 감독 프로세스가 통지를 받을 수 있는 알림 시스템도 제공됩니다. 프로세스의 cgroup은 /proc/$PID/cgroup을 읽어 확인할 수 있습니다. 따라서 cgroups는 프로세스를 추적하여 보모 역할을 수행하는 데 아주 좋은 선택입니다.
좋은 보모는 데몬이 언제 시작하고, 끝나고, 크래시 나는지만 감독하는 것이 아니라, 그것을 위한 좋고 최소한이며 안전한 작업 환경도 설정해야 합니다.
그것은 setrlimit() 자원 제한, 사용자/그룹 ID, 환경 블록 같은 명백한 프로세스 매개변수를 설정하는 것을 의미하지만, 거기서 끝나지 않습니다. Linux 커널은 사용자와 관리자에게 프로세스에 대한 많은 제어 수단을 제공합니다. 그중 일부는 현재 거의 사용되지 않습니다. 각 프로세스에 대해 CPU 및 IO 스케줄러 제어, capability bounding set, CPU affinity, 그리고 물론 추가 제한이 설정된 cgroup 환경 등을 지정할 수 있습니다.
예를 들어 ioprio_set()에 IOPRIO_CLASS_IDLE을 사용하는 것은 locate의 updatedb가 시스템 반응성에 미치는 영향을 최소화하는 훌륭한 방법입니다.
그 위에 읽기 전용 bind mount 기반의 읽기 전용 파일 시스템 오버레이 설정 같은 고수준 제어도 매우 유용할 수 있습니다. 이런 식으로 특정 데몬을 실행하면 모든 파일 시스템 혹은 일부 파일 시스템이 그 데몬에게는 읽기 전용처럼 보이게 할 수 있으며, 모든 쓰기 요청에 대해 EROFS가 반환됩니다. 따라서 이것은 일종의 가난한 사람의 SELinux 정책 시스템처럼 데몬이 할 수 있는 일을 제한하는 데 사용할 수 있습니다. (하지만 이것이 SELinux를 대체한다는 뜻은 절대 아닙니다. 제발 이상한 생각은 하지 마십시오.)
마지막으로 로깅은 서비스 실행의 중요한 부분입니다. 이상적으로는 서비스가 생성하는 모든 출력이 빠짐없이 기록되어야 합니다. 따라서 init 시스템은 자신이 생성하는 데몬에 시작부터 로깅 기능을 제공하고, stdout과 stderr를 syslog나 경우에 따라 /dev/kmsg에 연결해야 합니다. /dev/kmsg는 많은 경우 syslog를 대체하는 데 매우 유용합니다. 특히 커널 로그 버퍼가 기본 설정에서 터무니없이 크게 잡혀 있는 시대에는 더욱 그렇습니다. (임베디드 쪽 분들, 주목하십시오!)
먼저 분명히 해두고 싶은 것은, 저는 실제로 Upstart의 코드를 좋아한다는 점입니다. 주석이 아주 잘 달려 있고 따라가기도 쉽습니다. 이것은 분명 다른 프로젝트들이 배워야 할 점입니다. (제 프로젝트도 포함해서요.)
그렇다고 해도, Upstart의 전반적인 접근 방식에는 동의하기 어렵습니다. 하지만 먼저 프로젝트에 대해 조금 더 이야기해 보겠습니다.
Upstart는 sysvinit와 코드를 공유하지 않으며, 그 기능은 sysvinit의 상위 집합이고, 잘 알려진 SysV init 스크립트와도 어느 정도 호환성을 제공합니다. 핵심 기능은 이벤트 기반 접근 방식입니다. 프로세스의 시작과 중지는 시스템에서 일어나는 “이벤트”에 연결되며, 여기서 “이벤트”는 네트워크 인터페이스가 사용 가능해지는 것, 혹은 어떤 다른 소프트웨어가 시작되는 것처럼 매우 다양한 것일 수 있습니다.
Upstart는 이런 이벤트를 통해 서비스 직렬화를 수행합니다. 예를 들어 syslog-started 이벤트가 발생하면 이제 Syslog를 사용할 수 있다는 신호로 해석하여 D-Bus를 시작합니다. 그리고 dbus-started가 발생하면 이제 D-Bus를 사용할 수 있으므로 NetworkManager를 시작하는 식입니다.
이런 방식은 관리자나 개발자가 이해하는 실제 논리적 의존성 트리를 이벤트와 액션 규칙으로 번역하고 인코딩하는 것이라고 말할 수도 있습니다. 즉 관리자가 알고 있는 모든 “a는 b가 필요하다”라는 논리 규칙이 “b가 시작되면 a를 시작하라”와 “b가 멈추면 a를 멈추라”는 규칙으로 변환됩니다. 어떤 의미에서는 이것이 분명 단순화라고도 할 수 있습니다. 특히 Upstart 자체 코드 입장에서는 그렇습니다. 하지만 저는 이 단순화가 실제로는 해롭다고 주장하고 싶습니다. 첫째, 논리적 의존성 시스템은 사라지지 않습니다. Upstart 파일을 쓰는 사람은 이제 의존성을 수동으로 이런 이벤트/액션 규칙으로 번역해야 합니다. (사실 각 의존성마다 규칙이 두 개 필요합니다.) 즉 컴퓨터가 의존성을 바탕으로 무엇을 해야 하는지 판단하게 두는 대신, 사용자가 직접 의존성을 단순한 이벤트/액션 규칙으로 수작업 번역해야 합니다. 또한 의존성 정보가 실제로 인코딩된 적이 없기 때문에 런타임에는 사용할 수 없습니다. 결과적으로 관리자가 어떤 일이 왜 일어났는지, 즉 왜 b가 시작되면 a가 시작되는지를 파악하려 해도 알아낼 방법이 없습니다.
게다가 이벤트 로직은 모든 의존성을 발에서 머리로 뒤집어 놓습니다. 좋은 init 시스템이 초점을 맞춰야 할 것은 작업량을 _최소화_하는 것인데(이 블로그 글의 앞부분에서 지적했듯이), 오히려 이것은 작업량을 _최대화_합니다. 다시 말해, 명확한 목표를 두고 그것에 도달하기 위해 정말 필요한 일만 하는 대신, 한 단계를 수행한 뒤 그 다음에 가능할지도 모르는 모든 다음 단계를 수행합니다.
더 단순하게 말하면 이렇습니다. 사용자가 방금 D-Bus를 시작했다는 사실은 결코 NetworkManager도 시작해야 한다는 신호가 아닙니다. 하지만 Upstart는 그렇게 합니다. 반대로 되어야 맞습니다. 사용자가 NetworkManager를 요청했다면, 그것은 분명 D-Bus도 시작해야 한다는 신호입니다. (대부분의 사용자가 기대하는 것도 바로 이것이겠지요?)
좋은 init 시스템은 필요한 것만, 그리고 필요할 때 시작해야 합니다. lazy하게 하든, 병렬로 미리 하든 말입니다. 하지만 필요 이상의 것을 시작해서는 안 됩니다. 특히 그 서비스를 사용할 수도 있는 설치된 모든 것을 다 시작해서는 안 됩니다.
마지막으로 저는 이벤트 로직의 실제 유용성을 잘 보지 못하겠습니다. 제게는 Upstart에서 노출되는 대부분의 이벤트가 순간적인 성격이 아니라 지속 시간을 가진 것처럼 보입니다. 서비스는 시작되고, 실행 중 상태가 되며, 멈춥니다. 장치는 꽂히고, 사용 가능해지며, 다시 빠집니다. 마운트 포인트는 마운트되는 중이거나, 완전히 마운트되었거나, 언마운트되는 중입니다. 전원 플러그는 꽂히고, 시스템은 AC 전원으로 동작하고, 다시 플러그가 빠집니다. init 시스템이나 프로세스 감독자가 처리해야 할 이벤트 중 실제로 순간적인 것은 소수이고, 대부분은 시작, 상태, 종료의 튜플입니다. Upstart는 단일 이벤트에 초점을 맞추고 지속적인 의존성을 무시하기 때문에, 이런 정보 역시 사용할 수 없습니다.
물론 제가 위에서 지적한 일부 문제는 Upstart의 보다 최근 변화, 특히 start on (local-filesystems and net-device-up IFACE=lo) 같은 조건 기반 문법으로 어느 정도 완화되었다는 것을 알고 있습니다. 하지만 제게 이것은 핵심 설계가 잘못된 시스템을 수리하려는 시도로 보일 뿐입니다.
그 외에 Upstart는 데몬 보모 역할은 그럭저럭 합니다. 물론 몇몇 선택은 의문스러울 수 있고(위 참조), 놓친 기회도 분명 많습니다(이 역시 위 참조).
sysvinit, Upstart, launchd 외에도 다른 init 시스템들이 있습니다. 대부분은 Upstart나 sysvinit보다 본질적으로 더 많은 것을 제공하지 않습니다. 그중 가장 흥미로운 경쟁자는 적절한 서비스 의존성을 지원하는 Solaris SMF입니다. 하지만 여러 면에서 지나치게 복잡하며, 지나친 XML 사용과 익숙한 개념에 새 용어를 붙이는 등, 뭐랄까 약간 _학구적_입니다. 또한 contract 시스템 같은 Solaris 고유 기능과 밀접하게 결합되어 있습니다.
자, 이제 또 잠깐 쉬어 가기 좋은 시점입니다. 위에서 제가 좋은 PID 1이 무엇을 해야 한다고 생각하는지, 그리고 현재 가장 널리 쓰이는 시스템이 무엇을 하는지 설명했다고 생각하니, 이제 본론으로 들어가겠습니다. 그러니 가서 커피잔을 또 한 번 채우십시오. 그럴 가치가 있습니다.
아마 짐작하셨겠지만, 위에서 이상적인 init 시스템의 요구 사항과 기능으로 제안한 것들은 지금 실제로 이용 가능합니다. 이름하여 systemd라는, 아직은 실험적인 init 시스템입니다. 저는 이것을 여기서 발표하고자 합니다. 다시 한 번, 코드는 여기 있습니다. 그리고 아래는 그 기능들과 그 배경에 대한 간략한 정리입니다.
systemd는 전체 시스템을 시작하고 감독합니다. (그래서 이런 이름이 붙었습니다...) 위에서 지적한 모든 기능과 몇 가지를 더 구현합니다. systemd는 _unit_라는 개념을 중심으로 설계되었습니다. unit은 이름과 타입을 가집니다. 이들의 설정은 보통 파일 시스템에서 직접 읽히므로, unit 이름은 사실상 파일 이름입니다. 예를 들어 avahi.service라는 unit은 같은 이름의 설정 파일에서 읽히며, 물론 Avahi 데몬을 캡슐화하는 unit일 수 있습니다. unit에는 여러 종류가 있습니다.
이 모든 unit들은 서로 의존성을 가질 수 있습니다. 긍정적 의존성과 부정적 의존성, 즉 'Requires'와 'Conflicts' 모두 가능합니다. 예를 들어 어떤 장치는 서비스에 의존성을 가질 수 있으며, 이는 장치가 사용 가능해지자마자 특정 서비스가 시작된다는 뜻입니다. 마운트는 자신이 마운트되는 장치에 대한 암묵적 의존성을 얻습니다. 마운트는 또한 자신의 접두 경로인 마운트들에도 암묵적 의존성을 얻습니다. 예를 들어 /home/lennart 마운트는 /home 마운트에 대한 의존성이 자동으로 추가됩니다. 이런 식입니다.
기타 기능을 짧게 나열해 보겠습니다.
systemd는 Linux 고유 기능을 많이 사용하며, POSIX에만 자신을 제한하지 않는다는 점도 언급해야 합니다. 이것은 다른 운영체제로의 이식성을 염두에 둔 시스템이 제공할 수 없는 많은 기능을 가능하게 합니다.
위에 나열한 기능은 이미 모두 구현되어 있습니다. 현재 systemd는 이미 Upstart와 sysvinit의 drop-in replacement로 사용할 수 있습니다. 적어도 아직 네이티브 upstart 서비스가 너무 많지 않은 한에서는 그렇습니다. 다행히 대부분의 배포판은 아직 네이티브 Upstart 서비스를 그렇게 많이 담고 있지 않습니다.
하지만 테스트는 최소한만 이루어졌고, 현재 버전 번호도 자랑스럽게 0입니다. 지금 상태에서 이것을 실행하면 깨지는 일이 생길 것이라고 예상하십시오. 그렇다 하더라도 전반적으로는 꽤 안정적이어야 하며, 우리 중 일부는 이미 VM뿐 아니라 일반적인 개발 시스템도 systemd로 부팅하고 있습니다. 물론 결과는 환경에 따라 다를 수 있습니다. 특히 개발자들이 쓰지 않는 배포판에서 시도하면 더 그렇습니다.
위에서 설명한 기능 집합은 이미 상당히 포괄적입니다. 하지만 우리에게는 아직 더 할 일이 조금 남아 있습니다. 거대한 계획을 너무 길게 말하는 것은 그리 좋아하지 않지만, 우리가 어떤 방향으로 밀고 나갈지 짧게 개요를 적어 보겠습니다.
우리는 최소한 두 개의 unit 타입을 더 추가하고 싶습니다. swap은 마운트를 제어하듯이 스왑 장치를 제어하는 데 사용될 것입니다. 즉 이들이 활성화되는 장치 트리 장치에 대한 자동 의존성 같은 것들을 갖게 됩니다. 그리고 timer는 cron과 유사한 기능을 제공할 것입니다. 즉 시간 이벤트에 따라 서비스를 시작하며, monotonic clock과 wall-clock/calendar 이벤트 둘 다에 초점을 맞출 것입니다. (예: “마지막 실행 후 5시간 뒤에 시작”과 “매주 월요일 오전 5시에 시작”)
하지만 더 중요한 것은, systemd를 단지 부팅 시간 최적화에만 쓰는 것이 아니라 이상적인 세션 관리자 역할도 하게 실험해 보는 것이 우리의 계획이라는 점입니다. gnome-session, kdeinit 같은 데몬을 대체하거나, 혹은 보완할 수도 있을 것입니다. 세션 관리자와 init 시스템의 문제 영역은 매우 비슷합니다. 빠른 시작이 핵심이고, 프로세스 보모 역할이 중심입니다. 따라서 두 용도에 같은 코드를 사용하는 것은 자연스럽습니다. Apple은 이를 인식했고 launchd로 바로 그렇게 하고 있습니다. 우리도 그래야 합니다. 소켓 및 버스 기반 활성화와 병렬화는 세션 서비스와 시스템 서비스 모두에 똑같이 이득이 됩니다.
아마 언급해 두어야 할 것은, 이 세 가지 기능 모두가 이미 현재 코드베이스에서 부분적으로는 이용 가능하지만 아직 완전하지는 않다는 점입니다. 예를 들면 이미 지금도 일반 사용자로 systemd를 잘 실행할 수 있습니다. systemd는 자신이 그런 방식으로 실행된다는 것을 감지하며, 이 모드 지원은 처음부터 존재했고 핵심 설계 한가운데 있습니다. (또한 디버깅에도 아주 유용합니다! 시스템 전체가 systemd 부팅으로 전환되지 않았더라도 이것은 잘 동작합니다.)
하지만 이 작업을 마무리하기 전에 커널과 다른 곳에서 고쳐야 할 것들도 있습니다. 예를 들어 이미 마운트 변경에 대해서는 구독할 수 있는 것과 유사하게, 커널에서 스왑 상태 변경 알림이 필요합니다. 또한 CLOCK_REALTIME이 CLOCK_MONOTONIC에 대해 점프할 때 알림을 받고 싶습니다. 그리고 일반 프로세스가 init와 유사한 권한 일부를 가질 수 있게 하고 싶습니다. 또 사용자 소켓을 둘 수 있는 잘 정의된 위치도 필요합니다. 이런 문제들 중 어느 것도 systemd에 절대적으로 필수는 아니지만, 분명히 상황을 더 좋게 만들 것입니다.
현재는 tarball 릴리스를 제공하지 않지만, 우리 저장소에서 코드를 체크아웃하는 것은 어렵지 않을 것입니다. 추가로, 시작점을 마련하기 위해, unit 설정 파일이 들어 있는 tarball도 제공합니다. 이것을 사용하면 다른 수정이 없는 Fedora 13 시스템도 systemd와 함께 동작할 수 있습니다. 아직 RPM은 제공하지 않습니다.
더 쉬운 방법은 이 Fedora 13 qemu 이미지를 내려받는 것입니다. 이것은 systemd용으로 준비되어 있습니다. grub 메뉴에서 Upstart로 부팅할지 systemd로 부팅할지 선택할 수 있습니다. 이 시스템은 최소한만 수정되었다는 점에 유의하십시오. 서비스 정보는 기존 SysV init 스크립트에서만 읽습니다. 따라서 위에서 설명한 전체 소켓 및 버스 기반 병렬화를 활용하지는 못합니다. 하지만 LSB 헤더의 병렬화 힌트를 해석하므로, 현재 Fedora의 Upstart 시스템이 병렬화를 전혀 사용하지 않는 것에 비하면 더 빠르게 부팅됩니다. 이 이미지는 직렬 콘솔에 디버그 정보를 출력하고, 커널 로그 버퍼에도 기록하도록 설정되어 있습니다. (dmesg로 확인할 수 있습니다.) 가상 직렬 터미널을 사용하도록 qemu를 설정하는 것이 좋을 수 있습니다. 모든 비밀번호는 systemd입니다.
qemu 이미지를 내려받아 부팅하는 것보다 더 쉬운 방법은 멋진 스크린샷을 보는 것입니다. init 시스템은 보통 사용자 인터페이스 아래에 잘 숨어 있으므로, systemadm과 ps의 스크린샷으로 대신하겠습니다.

이것은 모든 로드된 unit을 보여 주는 systemadm이며, getty 인스턴스 중 하나에 대한 더 자세한 정보가 함께 표시됩니다.

이것은 ps xaf -eo pid,user,args,cgroup 출력의 일부로, 프로세스가 각 서비스의 cgroup 안에 얼마나 깔끔하게 정렬되어 있는지 보여 줍니다. (네 번째 열이 cgroup이며, 앞의 debug: 접두사는 앞서 언급했듯이 systemd에 debug cgroup controller를 사용하고 있기 때문에 표시됩니다. 이것은 임시 조치입니다.)
이 두 스크린샷 모두 최소한만 수정된 Fedora 13 Live CD 설치를 보여 준다는 점에 유의하십시오. 서비스는 오직 기존 SysV init 스크립트에서만 읽어옵니다. 따라서 여기서는 기존 서비스에 대해 소켓 또는 버스 활성화를 사용하지 않습니다.
현재로서는 부팅 차트나 시작 시간에 대한 단단한 데이터는 없습니다. Fedora 기본 설치의 모든 서비스를 완전히 병렬화하는 즉시 이를 공개하겠습니다. 그리고 그때 systemd 접근 방식의 벤치마크를 직접 해 보시도록 환영할 것이며, 우리도 자체 벤치마크 데이터를 제공하겠습니다.
아마 모두가 이 부분을 계속 물을 것 같으니, 숫자 두 개는 말씀드리겠습니다. 다만 이것들은 완전히 비과학적입니다. 단일 CPU VM에서 측정했고, 손목시계의 스톱워치를 사용했기 때문입니다. Upstart로 Fedora 13을 부팅하면 27초가 걸리고, systemd로는 24초에 도달합니다. (grub에서 gdm까지, 같은 시스템, 같은 설정, 두 번 부팅 중 더 짧은 값, 두 번은 연달아 수행) 하지만 이것이 보여 주는 것은 init 스크립트 헤더에서 파싱한 LSB 의존성 정보를 병렬화에 사용함으로써 얻은 속도 향상 효과뿐이라는 점에 유의하십시오. 소켓 또는 버스 기반 활성화는 여기에 활용되지 않았으므로, 이 수치만으로는 위에서 설명한 아이디어들을 평가하기에 적합하지 않습니다. 게다가 systemd는 직렬 콘솔에서 디버그 verbosity 수준으로 설정되어 있었습니다. 그러니 다시 말하지만, 이 벤치마크 데이터는 거의 가치가 없습니다.
systemd에서 사용하기에 이상적인 데몬은 전통적인 방식과 몇 가지 다른 점을 가집니다. 나중에 systemd용 데몬 작성 방법과 권장 사항을 설명하는 더 긴 가이드를 공개할 예정입니다. 기본적으로 데몬 개발자에게는 일이 더 단순해집니다.
위 목록은 Apple이 launchd와 호환되는 데몬을 위해 권장하는 내용과 매우 비슷합니다. 이미 launchd activation을 지원하는 데몬이라면 systemd activation도 쉽게 지원하도록 확장할 수 있을 것입니다.
systemd는 이런 스타일로 작성되지 않은 데몬도 매우 잘 지원한다는 점에 유의하십시오. 이미 호환성 이유만으로도 그렇습니다. (launchd는 이에 대한 지원이 제한적입니다.) 앞서 말했듯이 이는 기존 inetd 지원 데몬까지 포함하며, 이런 데몬은 수정 없이도 systemd의 소켓 활성화에 사용할 수 있습니다.
따라서 그렇습니다. systemd가 우리의 실험에서 제 기능을 입증하고 배포판에 채택된다면, 적어도 기본적으로 시작되는 서비스들 정도는 소켓 또는 버스 기반 activation을 사용하도록 포팅하는 것이 타당할 것입니다. 우리는 이를 위한 proof-of-concept 패치를 작성했고, 포팅이 매우 쉬운 작업임을 확인했습니다. 또한 어느 정도는 launchd를 위해 이미 이루어진 작업도 활용할 수 있습니다. 더구나 소켓 기반 activation 지원을 추가한다고 해서 그 서비스가 non-systemd 시스템과 호환되지 않게 되는 것도 아닙니다.
누가 이것을 만들고 있습니까? 현재 코드베이스는 주로 저, Lennart Poettering(Red Hat)의 작업입니다. 하지만 세부 설계 전반은 Kay Sievers(Novell)와 저의 긴밀한 협력의 결과입니다. 그 외에도 Harald Hoyer(Red Hat), Dhaval Giani(전 IBM), 그리고 Intel, SUSE, Nokia 등 여러 회사의 몇몇 다른 사람들이 참여하고 있습니다. 이것은 Red Hat 프로젝트입니까? 아닙니다. 이것은 제 개인적인 사이드 프로젝트입니다. 그리고 이 점을 분명히 하고 싶습니다. 여기에 반영된 의견은 제 개인적인 의견입니다. 제 고용주나 Ronald McDonald나 그 밖의 누구의 견해도 아닙니다. 이것이 Fedora에 들어올까요? 우리의 실험이 이 접근 방식이 통한다는 것을 보여 주고, Fedora 커뮤니티 논의에서 지지가 나온다면, 예, 분명히 Fedora에 넣도록 노력할 것입니다. 이것이 OpenSUSE에 들어올까요? Kay가 그 방향으로 추진하고 있으니, Fedora와 비슷한 이야기가 적용됩니다. 이것이 Debian/Gentoo/Mandriva/MeeGo/Ubuntu/[여러분이 좋아하는 배포판 이름을 넣으세요]에 들어올까요? 그것은 그들의 선택에 달려 있습니다. 우리는 분명 그들의 관심을 환영할 것이고, 통합을 도울 것입니다. 왜 그냥 Upstart에 이것을 추가하지 않고 새로 만들었습니까? 위에서 Upstart를 다룬 부분의 요지는 Upstart의 핵심 설계가, 우리의 의견으로는, 잘못되어 있다는 점을 보여 주기 위한 것이었습니다. 기존 해법의 핵심이 잘못되어 보인다면 완전히 처음부터 다시 시작하는 것이 자연스럽습니다. 물론 Upstart의 코드베이스로부터는 다른 면에서 많은 영감을 받았다는 점은 덧붙입니다. Apple의 launchd를 그렇게 좋아한다면 왜 그것을 채택하지 않습니까? launchd는 훌륭한 발명품이지만, 그것이 Linux에 잘 맞을지, 그리고 엄청난 확장성과 유연성으로 수많은 목적과 사용 사례를 가진 Linux 같은 시스템에 적합할지는 확신하지 못합니다. 이것은 NIH 프로젝트입니까? 위 글에서 왜 Upstart나 launchd 위에 구축하지 않고 새로운 것을 만들었는지 충분히 설명했다고 생각합니다. 우리는 정치적 이유가 아니라 기술적 이유로 systemd를 만들었습니다. 잊지 마십시오. libnih라는 라이브러리ㅡ일종의 glib 재구현ㅡ를 포함하는 것은 Upstart이지 systemd가 아닙니다! 이것이 [여기에 Linux가 아닌 OS 이름을 넣으세요]에서도 돌아갈까요? 가능성은 낮습니다. 앞서 지적했듯 systemd는 epoll, signalfd, libudev, cgroups 등 수많은 Linux 고유 API를 사용합니다. 다른 운영체제로 포팅하는 것은 우리에게 별 의미가 없어 보입니다. 또한 우리, 즉 이 작업에 참여한 사람들은 다른 플랫폼으로의 포트를 병합하고 그에 따라 생기는 제약과 함께 일하는 데 큰 흥미가 없을 가능성이 높습니다. 그렇다 하더라도 사람들이 정말 포팅하고 싶다면 git은 브랜치와 rebase를 아주 잘 지원합니다. 실제로는 이식성 제약이 단지 다른 OS에 한정되는 것도 아닙니다. 우리는 매우 최신의 Linux 커널, glibc, libcgroup, libudev를 요구합니다. 현재 수준보다 오래된 Linux 시스템은 지원하지 않습니다. 죄송합니다. 다른 운영체제를 위해 비슷한 것을 구현하고 싶은 분들이 있다면, 가장 바람직한 협력 방식은 아마 우리가 여러분의 시스템과 공유할 수 있는 인터페이스가 무엇인지 함께 식별해서, 데몬 작성자들이 systemd와 그쪽 대응 시스템을 모두 지원하기 쉽게 만드는 것입니다. 아마 초점은 코드 공유가 아니라 인터페이스 공유에 있어야 할 것입니다. [여기에 Gentoo 부팅 시스템, initng, Solaris SMF, runit, uxlaunch 등을 넣으세요]도 훌륭한 init 시스템이고 병렬 부팅도 지원한다고 들었습니다. 왜 그것을 채택하지 않습니까? 글쎄요, 우리가 이것을 시작하기 전에 실제로 여러 시스템을 아주 면밀히 살펴보았습니다. 그리고 launchd를 제외하면, 그들 중 아무도 우리가 systemd에서 염두에 두고 있던 것을 제공하지 않았습니다. 그게 보이지 않는다면 위에서 제가 쓴 내용을 다시 읽어 주십시오.
우리는 패치와 도움을 매우 환영합니다. 모든 자유 소프트웨어 프로젝트가 가능한 한 폭넓은 외부 기여를 통해서만 이익을 얻을 수 있다는 것은 상식일 것입니다. 이는 init 시스템처럼 OS의 핵심 부분에는 특히 더 그렇습니다. 우리는 여러분의 기여를 소중히 여기므로 저작권 양도를 요구하지 않습니다. (Canonical/Upstart와는 매우 다르게 말입니다!) 그리고 우리는 모두가 좋아하는 VCS인 git을 사용합니다. 좋지요!
우리는 특히 Fedora와 OpenSUSE 외의 다른 배포판에서 systemd가 동작하도록 만드는 데 도움을 주실 분들을 찾고 있습니다. (Debian, Gentoo, Mandriva, MeeGo 쪽 분들, 혹시 할 일을 찾고 계십니까?) 하지만 그뿐만 아니라, 모든 수준의 기여자를 적극적으로 끌어들이고 싶습니다. C 해커, 패키저, 문서 작성에 관심 있는 분들, 로고를 기여하고 싶은 분들 모두를 환영합니다.
현재로서는 소스 코드 저장소와 IRC 채널(Freenode의 #systemd)만 있습니다. 메일링 리스트도, 웹사이트도, 버그 추적 시스템도 없습니다. 아마 곧 freedesktop.org에 뭔가를 마련할 것입니다. 질문이 있거나 다른 방식으로 우리와 연락하고 싶다면 IRC에 와서 함께해 주시기를 초대합니다!
업데이트: 우리 GIT 저장소가 이전했습니다.