[3일차] | 컴퓨터 밑바닥의 비밀 | 책너두

Heechan Kang·2024년 4월 11일
post-thumbnail

1.3 링커의 말할 수 없는 비밀

  • 정적 라이브러리와 동적 라이브러리를 사용할 때, 내부적으로 링크 과정이 진행된다.

1.3.1 링커는 이렇게 일한다

  • 링커도 컴파일러와 마찬가지로 일반적인 프로그램일 뿐이다.
  • 링커는 컴파일러가 만들어낸 여러 파일을 하나로 묶어 하나의 실행 파일을 만든다.
  • 이러한 과정은 저자 여러명이 작성한 챕터를 하나로 묶어 하나의 책을 출간하는 것과 유사하다.
    • 도서에서도 특정 장이 다른 장의 내용에 의존하는 경우가 있는데, 이런 종속성을 확인하는 것이 링크의 역할이다.
    • 또한 참조된 페이지가 아직 정확하지 않으므로 컴파일러가 임시로 N쪽이라고 표시한 것을 실제 페이지 번호로 변경하는 것도 링크의 역할이다.

1.3.2 심벌 해석: 수요와 공급

  • 심벌(symbol)은 변수나 함수 등의 이름을 의미한다.
  • 링커는 이러한 심벌을 해석하여 참조된 심벌이 존재하는지, 하나만 존재하는지를 확인한다.
    • 링커 혼자서는 이러한 정보를 알 수 없고, 컴파일러가 이 정보를 제공한다.
      • 컴파일러가 생성한 대상 파일에는 여러 목적을 가진 영역이 존재한다.
        • 코드 영역: 소스 파일의 함수가 변환된 기계 명령어가 저장되는 부분
        • 데이터 영역: 소스 파일의 전역 변수가 저장되는 부분
        • 심벌 테이블: 공급과 수요, 즉 외부에서 참조 가능한 심벌의 목록과, 참조하고 있는 외부 심벌의 목록이 저장되는 부분
  • 즉, 심벌 해석은 대상 파일에서 사용할 외부 심벌을 심벌 테이블에서 찾을 수 있는지 확인하는 과정이다.

1.3.3 정적 라이브러리, 동적 라이브러리, 실행 파일

  • 정적 라이브러리(.a, .lib)

    • 의존성을 가진 여러 파일을 각각 컴파일하여 하나의 실행 파일로 만드는 방식
    • 정적 링크(static linking)를 통해 실행 파일을 만든다.
    • 따라서 전체 코드를 매번 컴파일하는 것에 비해 빠르다.
    • 그러나 표준 라이브러리 등을 사용할 때에는 모든 프로그램이 동일한 라이브러리를 사용하므로 중복되는 코드가 많아진다.
      • 그만큼 저장 공간도 낭비하게 되고, 업데이트가 필요할 때 모든 프로그램을 업데이트해야 한다.
  • 동적 라이브러리(.so, .dll)

    • 최소한의 정보(이름, 심벌 테이블, 재배치 정보 등)만을 실행 파일에 저장함.
    • 동적 링크(dynamic linking) 과정을 통해 실제 라이브러리를 실행 파일에 연결한다.
    • 동적 링크에는 두 가지 방식이 존재한다.
      • 첫 번째 방식은 프로그램이 메모리에 적재될 때 동적 링크를 수행하는 방식이다.
      • 두 번째 방식은 프로그램의 실행 시간(runtime)에 동적 링크를 수행하는 방식이다.

1.3.4 동적 라이브러리의 장단점

  • 장점

    • 동적 라이브러리에 의존하는 프로그램의 개수와 상관없이 동적 라이브러리는 하나만 저장된다.
      • 이런 특징으로 인해 공유 라이브러리(shared library)라고도 불린다.
    • 코드가 수정되는 경우에도 하나의 라이브러리만 업데이트하면 되므로 관리가 편리하다.
    • 플러그인이나 확장 모듈을 만들 때 유용하다.
  • 단점

    • 실행 시간에 동적 링크를 수행하므로 정적 링크에 비해 실행 시간이 느리다.
    • 메모리 주소와 독립되어 있어 독립 코드(position-independent code)라고도 불린다.
    • 메모리에 단 하나의 복사본만 존재하고, 이것이 공유되므로 임의의 절대 주소를 참조할 수 없다.
    • 동적 라이브러리를 사용한 경우, 실행파일만으로는 실행이 불가능하다. 별도의 종속성 라이브러리의 설치가 필요하다.

1.3.5 재배치: 심벌의 실행 시 주소 결정하기

  • 어셈블리어로 작성된 코드를 보면, 변수 정보가 없고 메모리 주소를 직접 참조한다.
  • 링커는 프로그램 실행 시 함수가 적재될 메모리 주소를 결정해야 한다. 그러나 이를 혼자서는 알 수 없다.
  • 그렇기에 컴파일 과정에서 컴파일러가 임시로 메모리 주소를 할당한다.
    • 이를 .relo.text, .relo.data 등의 재배치 정보(relocation information)라고 한다.
  • 링커는 이 정보를 이용하여 실행 시 메모리 주소를 결정한다. 이 과정을 재배치(relocation)라고 한다.
  • 그러나 이 과정에서도 링커가 절대 주소를 알 수 없다. 따라서 가상 메모리(virtual memory)를 사용한다.

1.3.6 가상 메모리와 프로그램 메모리 구조

  • 프로그램이 실행된 후 메모리의 상태

    • 커널, 스택, 여유 공간, 힙, 데이터, 코드 등의 영역이 존재한다.
      • 스택: 메모리의 상위 영역에 위치하며, 함수 호출 시 사용되는 지역 변수, 함수의 인자, 함수의 반환 주소 등이 저장된다.
      • 힙: 메모리의 하위 영역에 위치하며, 동적 할당된 메모리가 저장된다.
      • 데이터: 전역 변수, 정적 변수, 문자열 등이 저장된다.
      • 코드: 프로그램 코드가 저장된다.
  • 모든 프로그램은 예외 없이, 코드 영역의 시작주소가 0x400000이다. 이는 동시에 여러 프로그램을 실행해도 동일하다.

    • 이는 운영체제의 가상 메모리를 사용하기 때문으로, 모든 프로그램은 자신이 전체 메모리를 독점하는것 처럼 착각하게 된다.
  • 이로인해 링커는 실제 데이터가 물리 메모리의 어디에 위치하는지 알 필요가 없어진다.

  • 운영체제는 이러한 가상메모리 관리를 위해 메모리 페이지(memory page) 단위로 매핑한다.

    • 이렇게 기록된 자료를 페이지 테이블(page table)이라고 한다.
  • 다시 정리하자면,

    • 모든 프로세스의 가상 메모리는 표준화되어있고, 동일한 크기를 가진다.
    • 가상 메모리의 각 영역의 크기는 다를 수 있어도, 배치되는 순서는 동일하다(스택, 힙, 데이터, 코드).
    • 실제 물리 메모리와 가상 메모리의 크기는 무관하다.
    • 물리 메모리에는 스택, 힙, 데이터, 코드 등이 구분되지 않는다.
    • 모든 프로세스는 자신만의 페이지 테이블을 가진다.

1.4 컴퓨터 과학에서 추상화가 중요한 이유

  • 사람의 이름도 일종의 추상화다.
    • 이름이 없다면 한 사람을 특정할 때 수많은 부가정보가 필요하다.
    • 그러나 이름을 통해, 간편하고 빠르게, 불필요한 정보의 노출 없이 한 사람을 특정 할 수 있다.
  • 이전장에서의 가상 메모리도 추상화의 한 예이다.
    • 프로그램은 자신이 전체 메모리를 독점하는 것처럼 착각하게 되지만, 실제로는 가상 메모리를 사용한다.
    • 이러한 추상화로 인해 프로그램은 메모리의 물리적 위치를 알 필요가 없어진다.

1.4.1 프로그래밍과 추상화

  • 프로그래머도 추상화를 통해 많은 이점을 얻을 수 있다.
    • 복잡한 문제를 단순화하여 해결할 수 있다.
    • 원하는 수준으로 프로그램의 복잡도를 제어 할 수 있다.

1.4.2 시스템 설계와 추상화

  • 컴퓨터 시스템은 기본적으로 추상화의 연속이다.
    • CPU를 사용할 때, 트랜지스터의 동작 원리를 알 필요가 없다.
      • 몇가지 추상화된 명령어를 사용하여 프로그램을 작성하면 된다.
    • 입출력 장치는 파일로 추상화되어 있다.
    • 실행중인 프로그램은 프로세스로 추상화되어 있다.
    • 물리 메모리는 가상 메모리로 추상화되어 있다.
    • 네트워크는 소켓으로 추상화되어 있다.
    • 프로세스와 프로세스에 종속적인 실행 환경은 컨테이너로 추상화되어 있다.
    • CPU, 운영체제, 응용프로그램은 가상 머신으로 추상화되어 있다.
  • 그러나 보다 세밀한 조정을 위해서는 추상화를 넘어 기저 계층을 이해할 필요가 있다.
profile
안녕하세요!

0개의 댓글