본문 바로가기
IT 서적/Clean Architecture

4부 12장. 컴포넌트 (링킹로더, 링커, 컴포넌트)

by Crystal.k 2022. 4. 10.

4부 컴포넌트 원칙

컴포넌트 원칙은 빌딩에 방을 배치하는 방법을 설명한다. 대규모 소프트웨어 시스템은 작은 컴포넌트들로 만들어진다.
소프트웨어 컴포넌트가 무엇인지, 컴포넌트를 구성하는 요소는 무엇인지 알아보고, 컴포넌트를 결합하여 시스템을 구성하는 방법에 대해 논의한다.

12장. 컴포넌트

  • 컴포넌트?
    • 배포 단위
    • 시스템 구성 요소로 배포할 수 있는 가장 단위 단위.
    • 자바의 경우 jar파일
    • 루비에서는 gem파일
    • 닷넷에서는 DLL파일
    • 컴파일러형 언어에서는 바이너리 파일의 결합체다.
    • 인터프리터형 언어에서는 소스파일의 결합체다.
    • 여러 컴포넌트를 서로 링크하여 실행 가능한 단일 파일로 생성할 수 있다.

잘 설계된 컴포넌트라면 반드시 독립적으로 배포 가능한, 따라서 독립적으로 개발 가능한 능력을 갖춰야 한다.

컴포넌트의 간략한 역사

  • 개발 초창기
    • 메모리에서의 프로그램 위치, 레이아웃을 프로그래머가 직접 케어했다.
    • 즉 프로그램 시작부에는 프로그램이 로드될 주소를 선언하는 오리진(origin) 구문이 나와야 했다.
    • 이 시절에는 프로그램의 메모리 위치가 한번 결정되면 재배치가 불가능했다.
    • 이 시절에 라이브러리 함수에 어떻게 접근했을까?
      • 라이브러리 소스코드를 애플리케이션 코드에 직접 포함시켜 단일 프로그램으로 컴파일했다.
  • 이 시절 장치는 느리고 메모리는 너무 비싸서 자원이 한정적이었다. 컴파일러는 소스코드 전체를 여러 번에 걸쳐서 읽어야 했지만 메모리가 너무 작아서 소스코드 전체를 메모리에 상주시킬 수가 없었다. 결국 컴파일러는 느린 장치를 이용해서 소스코드를 여러 차례 읽어야만 했다.
  • 단일 프로그램 컴파일은 오래 걸렸다.. 컴파일하는데 몇 시간씩 걸리곤 했다.
  • 컴파일 시간을 단축시키기 위해 함수 라이브러리의 소스코드를 애플리케이션 코드로부터 분리했다.
    • 함수 라이브러리를 개별적으로 컴파일하고 컴파일된 바이너리를 메모리의 특정 위치에 로드했다. 함수 라이브러리에 대한 심벌 테이블을 생성하고 이를 이용해 애플리케이션 코드를 컴파일했다. 그리고 애플리케이션을 실행할 때는 바이너리 함수 라이브러리를 로드한 다음 애플리케이션을 로드했다.
    • (애플리케이션과 함수 라이브러리를 아래와 같이 메모리를 배치)
    • 문제점. 프로그래머가 함수 라이브러리에 더 많은 함수를 추가하면 이역시 할당된 메모리 주소를 넘어서게 되고 결국 추가 공간을 할당해야 한다. 프로그램과 라이브러리가 사용하는 메모리가 늘어날수록 이와 같은 단편화는 계속될 수밖에 없다.


재배치성

위의 해결책은 재배치가 가능한 바이너리였다. 지능적인 로더를 사용해서 메모리에 재배치할 수 있는 형태의 바이너리를 생성하도록 컴파일러를 수정하는 것이었다.
이때 로더는 재배치 코드가 자리할 위치 정보를 전달받고 재배치 코드에는 로드한 데이터에서 어느 부분을 수정해야 정해진 주소가 로드할 수 있는지를 알려주는 플래그가 삽입되었다. (대개 이러한 플래그는 바이너리에서 참조하는 메모리의 시작 주소였다.)
함수 라이브러리를 로드할 위치와 애플리케이션을 로드할 위치를 로더에게 지시하고 로더는 여러 개의 바이너리를 입력받은 후 하나씩 차례로 메모리에 로드하면서 재배치하는 작업을 처리했다.
이로 인해 프로그래머는 오직 필요한 함수만을 로드할 수 있게 되었다. 또한 컴파일러는 재배치 가능한 바이너리 안의 함수 이름을 메타데이터 형태로 생성하도록 수정되었다. 외부 정의를 로드할 위치가 정해지기만 하면 로더가 외부 참조를 외부 정의에 링크시킬 수 있게 된다.
이렇게 링킹 로더(Linking loader)가 탄생했다.

링커

링킹 로더로 프로그램을 개별적으로 컴파일하고 로드할 수 있는 단위로 분할할 수 있게 되었다.
하지만 프로그램이 훨씬 커지면서 결국 너무 느려서 참을 수 없는 지경에 다다랐다. 함수 라이브러리는 자기 테이프와 같이 느린 장치에 저장되었기 때문에 바이너리 라이브러리를 읽고 외부 참조를 해석해야 하는데 시간이 오래 걸렸다.
마침내 로드와 링크가 두 단계로 분리되었다.
기존엔 프로그래머가 링크 과정을 맡았었다.(느린 부분) 링커라는 별로의 애플리케이션으로 이 작업을 처리하도록 만들었다. 링커는 링크가 완료된 재배치 코드를 만들어 주었고, 그 덕분에 로더의 로딩 과정이 빨라졌다. 비록 느린 링커를 사용해서 실행 파일을 만들었지만, 한번 만들어둔 실행 파일은 언제라도 빠르게 로드할 수 있게 되었다.
1980년대가 오고 고수준 언어를 사용하기 시작.
소스 모듈은. c파일에서. o파일로 컴파일된 후, 링커로 전달되어 빠르게 로드될 수 있는 실행 파일로 만들어졌다. 각 모듈을 컴파일하는 과정은 상대적으로 빨랐지만, 전체 모듈을 컴파일하는 일은 꽤 시간이 걸렸다. 이후에 링커에서는 더 많은 시간이 소요되었다. 결국 전체 소요시간은 또다시 늘어났고, 한 시간 이상 걸리는 경우가 많아지게 되었다.
로드 시간은 여전히 빨랐지만, 컴파일-링크 시간이 병목 구간이었다.

마치 프로그래머는 하염없이 헛수고를 할 수밖에 없는 운명을 타고난 것처럼 보였다. 1960년대, 70년대, 80년대에 걸쳐서 처리과정을 더 빠르게 만들기 위해서 온갖 시도를 했지만 프로그래머의 야심으로 인해 늘어난 프로그램의 크기로 인해 자초되었다.(😥)

하지만 80년대 후반에 디스크는 작아지기 시작했고, 놀랄 만큼 빨라졌다. 컴퓨터 메모리 저렴해지고, 램에 캐싱할 수 있게 되었다. 컴퓨터 클록 속도는 1MHz → 100MHz까지 증가했다.
오늘날에는 .jar, DLL, 공유라이브러리를 기존 애플리케이션에 플러그인 형태로 배포하는 것이 일상적인 일이 되었다.

결론

런타임에 플러그인 형태로 결합할 수 있는 동적 링크 파일이 이 책에서 말하는 소프트웨어 컴포넌트에 해당한다.
여기까지 오는데 50년이 걸렸다. 이제는 컴포넌트 플러그인 아키텍처를 기본으로 쉽게 사용할 수 있는 지점까지 다다랐다.


🤔 개발 초창기에서부터 오늘날까지 프로그래머는 쉼 없이 고통받았고 계속해서 방법을 찾아내는 과정을 읽게 된 것 같아서 흥미로웠다. 또 본인(프로그래머)이 직접 해결하진 못했지만 노력하면 세상이 돕는다는 느낌을 받았는데 저장장치들의 발전이 프로그래머를 숨통 트이게 해 준 점이었다.

반응형

댓글