요약

  • TinyOS는 긴 작업을 분할 위상(split-phase)으로 처리했습니다. 명령은 요청을 개시하거나 거부하고 즉시 반환하며, 이후 이벤트가 해당 컴포넌트의 범위 안에서 완료를 보고합니다.
  • 이 분리 덕분에 여러 동작이 하나의 작은 스택을 공유하고 모트가 잠들 수 있었지만, 그 대가로 순서 지정, 버퍼 보관, 타임아웃, 복구가 명시적인 상태 머신으로 옮겨졌습니다.
  • sendDone이벤트는 피어가 패킷을 수신했거나 수락했거나 처리했다는 것을 증명하지 않으면서도 결정적인 로컬 증거가 될 수 있습니다. 완료 경로조차 큐와 실행 컨텍스트에 의존했습니다.

먼저 도착한 반환

몇 킬로바이트의 RAM만 있는 기기에서 패킷 전송을 생각해 봅시다. 애플리케이션이send를 호출합니다. 함수가 반환합니다. 일반적인 동기적 관점에서 반환은 마무리처럼 느껴집니다. 호출이 끝났으니 작업도 끝났을 것 같은 것입니다.

TinyOS는 그 함의를 거부했습니다. TinyOS의SendMsg인터페이스는 명령과 나중의sendDone이벤트를 한 쌍으로 묶었습니다. 즉시 반환은 요청 경계를 다루고, 이벤트는 완료 경계를 다룹니다. 그 사이에는 라디오, 인터럽트, 스케줄링, 버퍼, 시간이 놓여 있습니다.

이 간극이 이 글의 주제입니다. 단순한 프로그래밍 언어의 기교가 아닙니다. 분산 증거가 어떻게 작동해야 하는지에 대한 작으면서도 특히 판독하기 쉬운 모형입니다. 명령 기록은 무언가가 요청되었다는 것을 증명합니다. 성공적인 즉시 결과는 컴포넌트가 요청을 수락했다는 것을 증명할 수 있습니다. 완료 이벤트는 그 이벤트가 명시된 의미만큼을 증명합니다. 이 중 어느 기록도 다음 행위자의 상태를 자동으로 증명하지 않습니다.

2004년 NSDI 논문The Emergence of Networking Abstractions and Techniques in TinyOS는 Philip Levis, Sam Madden, David Gay, Joseph Polastre, Robert Szewczyk, Alec Woo, Eric Brewer, David Culler가 저술했습니다. 이 논문은 명령을 동작 개시 요청으로, 이벤트를 완료 또는 환경에서의 발생으로 규정하며, 양방향 모두에서 오류가 노출될 수 있다고 설명합니다. 이 구분은 장식적 문법이 아니었습니다. 작은 기기가 하나의 호출 안에 숨길 수 없는 시간을 작업이 점유한다는 사실을 인정하는 방식이었습니다.

잠자기가 인터페이스에 진실을 말하게 했다

초기 모트에는 미결 상태인 모든 동작마다 블로킹 스레드와 전용 스택을 줄 메모리 예산이 없었습니다. 라디오나 변환기가 진행되는 동안 프로세서를 깨어 있게 할 여유도 없었습니다. TinyOS는 지연 계산에 태스크를, 비동기 변화에 이벤트를 사용했습니다. 태스크 큐가 비면 인터럽트가 새 작업을 공급할 때까지 기기가 잠들 수 있었습니다.

따라서 긴 작업은 블로킹할 수 없었습니다. 명령은 즉시 반환했고 호출자는 재개했으며, 그만큼 중요하게는 스택 소비를 멈췄습니다. 이후 이벤트가 상태 머신이 계속 진행하도록 허용했습니다.

이 설계는 물리적 제약을 정직한 인터페이스로 바꾸었습니다. 소프트웨어가 단순한 함수를 원한다고 해서 라디오 전송이 즉시 끝나는 것이 아니었습니다. 호출 지점이 다음 줄에 도달했다고 센서 변환이 완료되는 것도 아니었습니다. 하드웨어의 시간은 여전히 하드웨어의 시간이었습니다.

이는 놀랍도록 적은 상태로 동시성을 얻었습니다. 여러 정보 흐름이 진행되는 동안 하나의 스택이 그들을 서비스했습니다. 그러나 절약된 메모리가 논리적 상태를 지우지는 않았습니다. 상태는 옮겨질 뿐이었습니다. 애플리케이션은 어떤 요청이 대기 중인지, 어떤 버퍼가 여전히 점유 중인지, 다음 전이가 무엇인지, 이벤트가 오지 않으면 어떻게 해야 하는지 기억해야 했습니다.

블로킹의 부재는 기다림의 부재가 아니었습니다. 기다림이 명시적으로 만들어진 것입니다.

하나의 인터페이스가 양방향을 담았다

nesC는 이 거래에 구조적 형태를 부여했습니다. nesC의 인터페이스는 양방향입니다. 명령은 제공자 쪽으로 향하고, 이벤트는 다시 사용자 쪽으로 향합니다. 패킷 전송 명령과sendDone이벤트는 하나의 인터페이스 타입에 속했습니다. 배선이 컴포넌트 간 양방향을 연결했습니다.

이는 중요했습니다. 비동기 코드는 흔히 “누가 호출할 수 있는가”와 “누가 응답해야 하는가” 사이의 경계에서 실패하기 때문입니다. 정적 배선은 그 경계를 컴파일러에게 보이게 만들었습니다. 전체 프로그램을 분석하고 최적화할 수 있었고, 언어는 배포 전에 많은 잠재적 경쟁 상태와 누락된 컴포넌트 관계를 잡아낼 수 있었습니다.

그러나 배선은 구성을 증명할 뿐 권한을 증명하지 않았습니다. 어떤 컴파일된 컴포넌트가 어떤 함수에 연결되었는지는 보여주지만, 물리 센서를 인증하거나, 라디오 피어가 의도된 조직임을 증명하거나, 원격 동작에 대한 감사 기록을 제공하지는 않습니다. 전체 프로그램 컴파일러는 자신의 프로그램에 대해서는 많은 것을 알지만, 프로그램 바깥 세계의 진실에 대해서는 마법처럼 아는 것이 없습니다.

이벤트의 방향도 중요했습니다. 어떤 이벤트는 이전 요청을 완료합니다. 다른 이벤트는 환경에서 시작합니다. 수신된 메시지, 경과된 타이머, 센서 조건 등입니다. 모든 이벤트를 영수증으로 다루는 것은 모든 명령을 완료로 다루는 것만큼 잘못된 것입니다. 의미론적 역할이 명명되어야 했습니다.

수락은 완료가 아니다

논문들은 서비스가 사용 중일 때 두 가지 넓은 응답을 허용합니다. 컴포넌트는 동시 요청을 즉시 거부할 수도 있고, 나중 작업을 위해 요청을 큐에 넣을 수도 있습니다. 이 선택들은 서로 다른 상태를 만듭니다.

send가 또 다른 패킷을 수락할 수 없다고 보고한다면, 발명된 대기 작업이 존재해서는 안 됩니다. 호출자는 버퍼를 보유한 채 기다릴지, 버릴지, 재시도할지 결정합니다. 요청이 수락되면sendDone까지 버퍼를 호출자가 사용할 수 없을 수 있습니다. 수락은 인터페이스 계약 아래에서 임시 보관권을 이전할 뿐이며, 전송을 증명하지 않습니다.

이는 흔한 녹색불 설계에 대한 유용한 해독제입니다. 많은 시스템은 제출, 검증, 승인, 실행, 완료를 “성공”이라는 하나의 상태로 압축합니다. TinyOS의 분할 위상은 그 압축을 거부합니다. 즉시 응답과 이후 응답은 서로 다른 질문에 답하기 때문에 서로 다른 시점을 점유합니다.

sendDone(message, success)조차도 경계가 필요합니다. 로컬 맥락에서는 메시지 버퍼를 해제하거나 재사용할 만큼 충분히 강할 수 있습니다. 스택에 따라 완료된 로컬 전송 시도를 보고하고 링크 계층 결과를 반영할 수 있습니다. 그러나 이름 자체만으로는 원격 애플리케이션이 패킷을 수신했거나, 의미를 수락했거나, 저장했거나, 물리 시스템을 올바르게 변경했다는 것을 증명하지 않습니다.

로컬 영수증은 중요하면서도 불완전할 수 있습니다. 올바른 대응은 그것을 무시하는 것이 아니라 범위를 보존하는 것입니다.

버퍼가 숨겨진 자산이었다

제로 카피 규율은 증거 문제를 실체 있게 만듭니다. 패킷 복사는 RAM, 사이클, 에너지를 소모하므로 TinyOS 컴포넌트들은 하나의 버퍼에 대한 포인터를 전달하는 경우가 많았습니다. 라디오가 그것을 사용하는 동안 호출자는 안전하게 다시 쓸 수 없었습니다.sendDone은 그 로컬 보관이 끝날 수 있는 순간을 표시했습니다.

너무 이른 재사용은 여전히 비행 중인 패킷을 오염시킬 수 있습니다. 영구적인 대기는 희소한 메모리를 묶어둘 수 있습니다. 상관되지 않은 이벤트는 잘못된 버퍼를 해제할 수 있습니다. 중복 이벤트는 같은 논리적 자산을 두 번 해제할 수 있습니다.

따라서 완료 이벤트는 장식적인 콜백이 아니었습니다. 보관 영수증이었습니다. 하나의 컴포넌트 관계 안에서, 제공자가 이 버퍼와 연관된 작업을 다음 로컬 전이에 필요한 만큼 끝냈다는 것을 말합니다.

이 틀은 모트를 넘어 확장됩니다. 클라우드 작업에는 임대가 있고, 스토리지 쓰기에는 내구성 수준이 있고, 결제 지시에는 정산 상태가 있고, 네트워크 변경에는 활성화 영수증이 있습니다. 각 경우 자원은 최종 비즈니스 결과가 존재하기 전에 보관권이 바뀝니다. 중간 영수증을 최종 결과로 압축하면 안전하지 않은 재사용이나 무기한 고정이 생깁니다.

완료 경로도 실패할 수 있었다

이후의 T2 보고서는 인터페이스를 미화하지 않기 때문에 가치가 있습니다. 이 보고서는sendDone자체를 전달하는 데 쓰인 메커니즘이 만든 실패를 서술합니다.

상위 컴포넌트들은 버퍼를 재사용하기 전에 라디오가 완료를 신호하기를 기다렸습니다. 이 신호는 보통 태스크로 게시되어야 했습니다. 그러나 태스크 큐는 유한했습니다. 라디오 스택이 그 태스크를 게시할 수 없다면 호출자는 영원히 기다릴 수 있습니다. TinyOS는 때때로 인터럽트 컨텍스트에서sendDone을 직접 신호함으로써 벗어났습니다. 이는 기대된 실행 모델을 깨는 방식으로 진행을 회복했습니다. 태스크 컨텍스트용으로 작성된 코드가 이제 비동기적으로 실행되어 메모리를 오염시킬 수 있게 된 것입니다.

저자들은 이 취약성이 하나의 라디오 스택에 국한되지 않았다고 말합니다. 완료 태스크 게시에 의존하는 모든 분할 위상 컴포넌트가 같은 모양의 실패에 직면할 수 있습니다. 사라진 완료는 영구 블록으로 위로 전파될 수 있고, 잘못된 컨텍스트에서 전달된 완료는 경쟁 상태로 전파될 수 있습니다.

이는 모든 TinyOS 배치가 그런 실패를 겪었다는 증거가 아닙니다. T2는 대규모 팀의 설계 대응이지 현장 사건의 인구조사가 아닙니다. 더 깊은 교훈은 더 좁고 더 강력합니다. 영수증 경로도 시스템의 일부라는 점입니다. 완료 이벤트의 전달이 다른 작업과 같은 유한한 큐와 동시성 규칙을 소모한다면, 그 이벤트를 추상적 확실성으로 다룰 수 없습니다.

상태 머신은 전이를 가진 장부다

분할 위상 프로그래밍은 프로그래머가 블로킹 호출의 단순한 사슬을 쓸 수 없게 만들기 때문에 상위 수준 시퀀스를 더 어렵게 했습니다. 프로그램은 유한 상태 머신이 되었습니다. 요청을 발행하고, 대기 상태를 기록하고, 대응 이벤트를 기다린 다음 다음 전이를 선택합니다.

이는 구현상 불편으로 들립니다. 그러나 책임성의 이점이기도 합니다. 상태들은 유휴, 요청됨, 거부됨, 수락됨, 실행 중, 완료됨, 시간 초과, 취소됨을 구분할 수 있습니다. 타임아웃은 실패로 다시 쓸 필요가 없으며 미해결로 남을 수 있습니다. 재시도는 같은 작업 식별자를 가질 수도 있고 의도적으로 새 식별자를 만들 수도 있습니다. 늦게 도착한 완료는 현재 요청으로 착각하는 대신 조정(reconcile)될 수 있습니다.

물론 나쁜 상태 머신은 여전히 거짓을 말할 수 있습니다. 전이를 생략하거나, 식별자를 재사용하거나, 큐 넘침을 숨기거나, 모든 타임아웃을 작업 복제의 허가로 다룰 수 있습니다. 정적 분석은 경쟁 상태를 줄일 수 있지만 선택된 비즈니스 의미가 올바르다는 것을 증명하지 않습니다. 장부는 상태가 관찰 가능한 런타임 사실과 대응할 때만 유용합니다.

여기서 Lu Heng의 Running-Code Primacy와의 현대적 비교가 도움이 됩니다. 명령 선언은 의도입니다. 실행 중인 컴포넌트와 이후 이벤트가 더 강한 증거를 제공합니다. 그러나 이벤트도 절대적이지 않습니다. 그 의미는 그것을 발행한 계층과 역할에서 멈춥니다. 이 비교는 편집적 관점이며, TinyOS 저자들이 이후의 인터넷 거버넌스 교리를 적용했다는 주장이 아닙니다.

컬러는 집단적 시스템 안에 있다

데이비드 컬러는 버클리 공식 프로필이 TinyOS와 Berkeley Mote를 그의 경력을 규정하는 시스템으로 두고 있고, 그가 이 작업이 자라난 연구 환경을 이끄는 데 기여했기 때문에 적절한 전기적 중심입니다. 그러나 그를 의도적으로 구성적이었던 시스템의 단독 저자로 만들어서는 안 됩니다.

NSDI 논문에는 여덟 명의 저자가 있습니다. nesC 논문은 David Gay, Philip Levis, Robert von Behren, Matt Welsh, Eric Brewer, Culler가 저술었습니다. T2 보고서는 Stanford, Berkeley, Intel Research, Technische Universität Berlin, UCLA, Crossbow, Arch Rock, Moteiv, Washington University에 걸친 훨씬 더 큰 팀을 밝힙니다. Philip Levis의 2012년 회고는 무엇이 나이를 잘 견디고 무엇이 비용이 되었는지에 대한 또 하나의 불가결한 관점을 제공합니다.

집단적 공로가 여기서 예베로서보다 더 중요합니다. TinyOS는 계약들이 서로를 제약하는 컴포넌트들로 조립되었습니다. 그 지적 역사 역시 언어 설계, 운영체제 아키텍처, 라디오 공학, 하드웨어, 배치, 커뮤니티 경험의 구성물입니다. 한 사람을 시스템 전체로 만드는 것은 그 시스템이 가르치는 경계 자체를 위반하게 됩니다.

성공은 그만의 비용을 쌓았다

Levis의 10년 회고는 당시 TinyOS가 주요 연구 플랫폼이 되었고 상용 제품에 등장했다고 보고합니다. 동시에 승리의 결말을 거부합니다. 자원 최소화와 버그 예방은 강력한 추상화를 만들어냈습니다. nesC와 세분화된 컴포넌트는 전문가가 복잡한 시스템을 구축하는 데 도움을 주었습니다. 시간이 지나며 같은 전문화된 생태계는 신규 사용자에게 어려워졌고, 세분화된 구성은 성숙한 시스템 코드를 이해하기 더 어렵게 만들었습니다.

이들은 2012년의 관찰이며 현재의 다운로드 통계가 아닙니다. 그 가치는 아키텍처적입니다. 인터페이스는 국지적으로 우아하면서도 장기적인 조정 비용을 만들 수 있습니다. 정적 선택은 런타임 메모리를 절약하는 동시에 학습과 유지보수 부담을 늘릴 수 있습니다. 전문가가 개척지를 탐색하도록 최적화된 설계는 개척지가 인프라가 되는 순간 장벽이 될 수 있습니다.

분할 위상 구분은 그 비판을 견뎌냅니다. 현대 런타임은 이를 퓨처, 프로미스, 완료 큐, 인터럽트, 내구성 있는 작업 상태로 표현할 수 있습니다. 이름은 바뀌어도 의무는 바뀌지 않습니다. 지시를 결과로 부르지 말고, 로컬 결과를 종단 간 사실로 부르지 마십시오.

좁은 영수증이 유용한 영수증이다

일찍 반환한 명령은 결함이 아니었습니다. 정확했습니다. 요청 경계가 넘어갔고, 이제 시스템의 다른 부분이 다음 사실을 소유한다는 뜻이었습니다.

완료 이벤트는 전지적이지 않았습니다. 이 컴포넌트가 이 인터페이스가 정의한 완료 상태에 도달했다는 뜻이었습니다. 운영자가 라디오 응답, 원격 수신, 애플리케이션 처리, 물리적 효과의 증명이 필요하다면, 시스템은 그 역할들로부터 추가 영수증을 요구했습니다.

이것이 컬러의 TinyOS 작업과 그 주변 협력 논문들의 지속적 가치입니다. 극심한 제약이 인터페이스가 시간, 보관, 불확실성을 드러내도록 강제했습니다. 큰 시스템은 흔히 충분한 메모리와 미들웨어로 그 같은 간극을 하나의 녹색 배지 뒤에 숨길 수 있습니다. 그러나 간극은 그 자리에 남아 있습니다.

정직한 시스템은 간극을 유지합니다. 요청과 완료를 상관시키고, 미해결 상태를 보존하고, 모든 영수증에 범위를 두고, 누군가 일어나길 바랐던 일에 대한 기록보다 실행 증거를 우선시합니다.

출처