컴퓨터 과학에 관한 이런저런 이야기와 소중한 시간을 허비하는 딴생각
컴퓨터 과학에 관한 이런저런 이야기
딴생각으로 소중한 시간을 허비하기
최근 x86 세그멘테이션을 파고들면서2) x86의 진화에서 한 가지 경향을 알아차렸다. 64비트로 전환하는 과정에서, 그리고 그 이전의 빠른 시스템 호출 기능에서도, 세밀한 메모리 보호가 쇠퇴했다는 점이다. 이 두 변화는 286 이후 이 아키텍처의 특징이었던 정교한 세그멘테이션 시스템을 부분적으로 무력화했다. 그 설명은 어쩌면 Unix일 수 있다. 세그먼트형 주소 공간보다 평면 주소 공간을 선호하는 Unix의 지배력은, 논쟁의 여지는 있지만 PDP-11 또는 PDP-7에서 물려받은 것으로, 세그멘테이션 기능의 64비트판을 “원하는 사람이 없었다”는 뜻이다.
한편 나는 WebAssembly 애호가들 사이에서 “세그먼트 메모리가 돌아오고 있다”고 농담하곤 한다. WebAssembly는 평면성이 그토록 절대적이지 않았던 OS/2나 다른 비-Unix 운영체제의 프로그래밍 모델로 돌아가는 것과 다소 비슷하다.
그러한 비평면성은 물론 안전성과 보안에 여전히 매우 중요하다. 나는 최근 언제 읽어도 즐거운 Poul-Henning Kamp의 글 몇 편을 다시 읽었는데, 그는 CHERI를 자신이 “어떤 속도에서도 안전하지 않다”고 묘사한 평면 메모리 모델에 대한 반응으로 규정했다. Kamp는 모든 평면 메모리에서 소프트웨어가 가장 먼저 하는 일은 그 위에 어떤 세분화 구조를 부과하는 것이라고 관찰한다.
이 문제를 바라보는 한 방법은 하드웨어가 이 세분화에 대해 어느 정도까지 알아야 하는지의 문제로 보는 것이다... CHERI는 그렇다고 답하는 반면, 이전 하드웨어는 아니라고 답했다. 물론 특정 경우에는 온갖 세그멘테이션 관련 기능이 예외였다!
하지만 나는 “그렇다 또는 아니다”가 이를 바라보는 잘못된 방식이라고 생각한다. 이것은 재귀적이다! 평면 메모리에 어떤 세분화 구조를 부과하면, 우리는 그것을 다시 부과하고 싶어 한다. 아레나나 메모리 풀을 생각해 보라. 구조체 안의 필드도 생각해 보라(구조체 안의 구조체 안의 필드도). 문제는 평면적인가 아닌가가 아니다. 프로그래머의 정신적 추상화는 결코 평면적이지 않다. 오히려 문제는 프로그래머가 실천하는 근본적으로 재귀적인 현상, 즉 세분화를, 매우 비재귀적인 하드웨어와 어떻게 조화시키느냐이다. 하드웨어는 개념적으로 유한 상태이며, 그 공학적 관행은 고정된 구조와 제한된 깊이를 선호하는 경향이 있다. 초 CISC CPU라면 마이크로코드에서 어느 정도 반복을 제공할 수도 있겠지만, 그것이 한계에 가깝다.
하드웨어가 비재귀적이고 소프트웨어가 재귀적이라면, 이 차이를 어떻게 조화시킬 수 있을까? 순진한 접근법은 깊이에 단순히 상한을 두는 것이다. 하드웨어가 N단계의 분해까지만, 아마도 N=1까지만 안다고 하고 나머지는 소프트웨어에 맡기는 식이다. 그러나 그것은 만족스럽지 않다. 프로그래머가 하는 일을 두고 “하드웨어가 손을 씻는” 셈이다. 이는 비균일성과, 더 높은 N에서 하드웨어가 더하는 모든 가치의 상실을 보장한다. CHERI는 이렇게 하지 않는다. 소프트웨어가 여전히 재귀 단계를 처리하므로 재귀적 분해를 다룰 유연성을 유지한다. 경계는 임의로 좁을 수 있지만, 소프트웨어가 좁히고 명시적으로 전달한다. 따라서 어느 시점에 주소 지정할 수 있는 대상은 하드웨어의 상태보다는 소프트웨어에서 출현하는 것에 의해 결정된다. 즉 현재 실행 중인 코드의 손이 닿는 곳 안으로 무엇이 흘러들어 왔는가, 다시 말해 도달 가능한 역량의 추이적 폐쇄에 접근할 수 있는 메모리인가에 달려 있다. 이러한 출현은 당연히 명백한 감사 어려움을 낳지만, 적절한 도구라면 해결할 수 있는 어려움이다.
역설적으로 CHERI는 한 가지 제약을 통해서도 이 유연성을 얻는다. 주소 지정은 경계가 단조롭게 감소하는 역량 파생 연산의 끊기지 않은 사슬 위에서만 허용된다. 소프트웨어는 소프트웨어이므로, 어떤 프로그램은 예를 들어 기묘한 비단조적 주소 계산을 수행하며 자기 방식을 택할 것이기에, 나는 이 거래에 대해 늘 어느 정도 불편함을 느꼈다. 어떤 식으로든 세분화된 주소 공간을 순회하는 방식을 소프트웨어 안에서, 자신이 좋아하는 바로 그 방식으로 표현해 온 수십 년의 프로그램 유산은, 새롭고 더 규범적인 하드웨어와 마찰을 일으킨다. 이를 극복하는 것은 단지 개발 노력의 문제지만, CHERI 또는 이와 유사한 것이 “승리”해야만 그 노력을 기울이는 일이 정상화될 것이고, 다시 말해 그 비용이 업계 전체에 걸쳐 “성공적으로” 외부화될 것이다. 물론 그 비용은 그에 상응하는 큰 이점과 함께 온다. 그러나 하드웨어 보편성을 달성한다는 의미에서의 “승리”는 위험 부담이 큰 게임이다.
liballocs에서는 보안에 훨씬 덜 신경 써도 되었고, 따라서 주소가 어떻게 파생될 수 있는지에 관한 규칙을 정하는 일에는 대체로 얽매이지 않았다. 대신 실제 소프트웨어가 출발점으로 삼는 평면 주소 공간을 재귀적으로 세분화하면서 만들어 냈을 법한 모든 구조를 기술적으로 포착하는 데 훨씬 더 관심을 두었다. 그 중심에는 재귀적 추상화가 있다. 할당은 다른 할당 안에 중첩되어 트리를 이룬다. 또한 “N단계 절단점”이나 하드웨어/소프트웨어 구분도 없다. 이는 근본적으로 소프트웨어이며, 시스템 내부의 수많고 이질적인 구현에도 불구하고 가능한 한 균일한 반성적 추상화를 사용해 구조를 끝까지 포착하려 한다. 이 “동질적 인터페이스, 이질적 구현”이라는 생각은 물론 흔히 객체 지향과 연관되며, 하드웨어와 연관되는 일은 드물다.
덧붙여 또 다른 객체 지향적 이야기로 마무리하자면, 세분화에 대한 고전적 언어 VM 접근법은 하드웨어와 정반대의 방식으로 책임을 떠넘긴다. 모든 것을 거의 최대한으로 세분화하여, 아주 작은 객체들과 그들 사이의 거대한 명시적 상호 참조(포인터) 관계로 만든다. 프로그래머의 머릿속에는 의심할 나위 없이 더 거친 구조가 있지만, 그것들은 그곳에 머무른다. 시스템은 그 구조를 중심으로 저장소를 구성하겠다고 제안하지 않는다. 그 결과 이런 시스템은 공간 지역성도 포기한다. 이는 바이트나 워드를 더 큰 단위로 함께 묶는 하드웨어의 경험 법칙이며, 따라서 이런 접근법이 오랫동안 성능상 불리했던 이유다.