소프트웨어 라이브러리에 대한 3 가지 종류에 대한 특징과 장단점에 대해서 설명하고 있다. 여기서 언급하고 있는 내용은 'GCC 완전정복' 이라는 서적에서 발췌했다.

정적 라이브러리

정적 라이브러리는 가장 오래되고 가장 간단한 형태의 코드 라이브러리이다. 정적 라이브러리와 링크되는 응용프로그램의 경우 마지막 실행 파일 안에 라이브러리의 복사본이 포함되어 있다. 코드 라이브러리에 있는 함수 호출에 대한 참조는 컴파일 과정에서 결정될 수 있기 때문에 정적 라이브러리를 사용하는 것은 매우 쉽다. 정적 라이브러리를 응용프로그램에 링크할 때, 라이브러리 안에 있는 함수의 주소는 하나로 정해져서 실행 파일에서 그 주소를 사용할 수 있게 된다. 일반적으로 정적 라이블러리의 확장자는 .a(archive) 또는 .sa(static archive)이다.

컴파일 과정이 간단해진다는 것 이외에도, 정적 라이브러리를 사용하는 프로그램은 자체적으로 동작할 수 있는 완전한 프로그램이라는 매우 큰 장점을 가지고 있다. 최종적으로 만들어진 실행파일은 외부의 어떤 것에도 의존성이 없기 때문에, 다른 컴퓨터 시스템으로 옮겨 수행되는 경우에도 정상적으로 동작하는 것이 보장된다. 또한 라이브러리에 정적으로 링크된 프로그램의 경우 공유 라이브러리나 동적 적재 라이브러리를 사용하는 경우와 달리 수행 시간에 함수 또는 클래스의 참조를 결정하는 시간이 포함되지 않기 때문에 나머지 두 가지 형태의 라이브러리를 사용하는 것보다 수행 시간이 짧아진다.

이와 비슷하게, 정적 라이브러리를 사용하는 경우 공유 라이브러리나 동적 적재 라이브러리를 사용하는 경우보다 적은 양의 메모리를 요구하게 되는 경향이 있는데, 이 역시 함수들의 참조를 찾고 호출해야 하는 실행 환경 자체의 부하가 없기 때문이다.
정적 라이브러리는 사용하기 쉽고 이식성이 뛰어나다는 장점이 있지만, 그와 동시에 나름의 단점도 있다.

  1. 응용프로그램 크기의 증가 : 라이브러리를 사용하는 각 실행파일에 라이브러리의 복사본을 끼워 넣게 되면 각각의 실행 파일의 크기는 대개 커지게 된다. 라이브러리에서 사용하는 함수들만 추출하여 응용프로그램에 포함시킬 수는 없기 때문에 각 실행 파일은 사용하는 라이브러리의 일정 부분을 전부 포함시켜야 한다.
  2. 라이브러리 수정 시 응용프로그램을 다시 컴파일해야 함 : 버그 수정 또는 성능 향상을 위하여 라이브러리를 수정했을 경우, 새로운 라이브러리를 이용해서 응용프로그램을 다시 컴파일 하기 전까지는 응용프로그램에 수정 사항이 반영되지 않는다.
  3. 이러한 문제들을 해결하기 위해, 대다수 유닉스 및 유닉스 류의 비슷한 시스템은 공유 라이브러리와 동적 라이브러리를 지원한다.

공유 라이브러리

1990 년대 초에 썬 마이크로시스템즈 사에 의해 처음 소개된 공유 라이브러리는 중앙 집중화된 코드 라이브러리로서, 응용프로그램은 컴파일될 때가 아니라 프로그램이 실행될 때에 라이브러리를 로드하고 링크한다. 공유 라이브러리를 사용하는 프로그램은 라이브러리 루틴에 대한 참조만 갖고 있다.

이 참조는 프로그램이 실행될 때 런타임 링크 편집기에 의해 결정된다. 공유 라이브러리는 일반적으로 .so(shared object) 확장자를 사용하지만, Mac OS X 시스템에서는 .dylib(dynamic shared library)와 같이 다른 종류의 확장자를 사용하기도 한다. 그러나 Mac OS X 의 경우는 공유 라이브러리를 이름 짓는 방법과 참조 방법에 있어서 일종의 예외이고, 남은 부분에서는 리눅스나 솔라리스처럼 “전통적인” 공유 라이브러리 구현을 사용하는 시스템에 대해서 설명하겠다.
공유 라이브러리는 정적 라이브러리에 비해 많은 장점을 갖고 있는데, 다음과 같다.

1. 응용프로그램의 유지 보수가 쉬움 : 공유 라이브러리는 언제든 수정이 가능하며, 버전 정보를 사용함으로써 응용프로그램이 “옳은” 버전의 공유 라이브러리를 로드할 수 있도록 한다. 공유 라이브러리를 사용하도록 컴파일 된 응용프로그램에는 라이브러리의 버그 수정 및 성능 향상이 자동으로 반영된다. 특정 라이브러리나 이전 버전의 공유 라이브러리를 사용하는 응용프로그램은 충돌 없이 해당 라이브러리를 계속해서 사용할 수 있다.

2. 시스템 디스크 공간 요구량 감소 : 공유 라이브러리를 사용하는 응용프로그램의 경우 라이브러리 자체를 포함하지 않기 때문에 정적 라이브러리를 사용하는 응용프로그램보다 그 크기가 작다. 이러한 특징은 X 윈도우 시스템 응용프로그램처럼 상당히 많은 수의 그래픽 라이브러리에 있는 함수들을 사용하는 그래픽 관련 응용프로그램에서 두드러지게 나타난다. 이런 종류의 응용프로그램을 정적 라이브러리를 이용해 컴파일하게 되면 그 크기가 서너배는 증가하게 된다. 이와 유사하게, 공유 라이브러리를 사용하는 GNOME, KDE 또는 몇 가지의 X 윈도우 시스템 응용프로그램과 같은 그래픽 데스크탑 환경을 실행하게 되면 시스템 메모리의 사용을 현저하게 낮출 수 있다. 이는 이 프로그램들이 각자의 라이브러리를 갖고 있는 것이 아니라, 하나의 공유 라이브러리에 대한 접근(access)을 공유해서 사용하고 있기 때문이다.

3. 시스템 메모리 요구량 감소 : 공유 라이브러리에 접근하는 응용프로그램들은 실행 시 여전히 공유 라이브러리를 메모리에 로드해 사용해야 한다. 그러나 메모리에 한번 로드된 라이브러리는 그것을 참조하는 어떠한 응용프로그램에서도 사용이 가능하다. 여러 프로그램들이 하나의 집중화된 공유 라이브러리에 동시에 접근할 수 있기 때문에 하나 이상의 응용프로그램이 공유 라이브러리를 참조할 경우 시스템의 전체 메모리 사용량은 줄어들게 된다.

공유 라이브러리를 사용할 때의 장점이 있듯이 단점도 물론 존재한다. 첫째, 공유 라이브러리를 사용하게 되면 응용프로그램의 외부 환경 의존도가 높아지기 때문에 실행 파일을 호환 가능한 다른 컴퓨터 시스템으로 옮기는 작업이 어려워진다. 각 컴퓨터 시스템은 현재의 공유 라이브러리와 함께 적합한 다른 버전의 공유 라이브럴리를 갖고 있어야 한다. 근래의 리눅스 X 윈도우 시스템의 xterm 응용프로그램을 예로 들어보자.
리눅스 ldd 명령어는 xterm 이 다음과 같은 공유 라이브러리를 필요로 함을 보여준다.

#ldd /usr/X11R6/bin/xterm
        libXft.so.2 => /usr/X11R6/lib/libXft.so.2 (0x40029000)
        libfontconfig.so.1 => /usr/lib/libfontconfig.so.1 (0x4003b000)
        libfreetype.so.6 => /usr/lib/libfreetype.so.6 (0x40060000)
        libexpat.so.0 => /usr/lib/libexpat.so.0 (0x400b1000)
        libXrender.so.1 => /usr/X11R6/lib/libXrender.so.1 (0x400d1000)
        libXaw.so.7 => /usr/X11R6/lib/libXaw.so.7 (0x400d9000)
        libXmu.so.6 => /usr/X11R6/lib/libXmu.so.6 (0x40135000)
        libXt.so.6 => /usr/X11R6/lib/libXt.so.6 (0x4014b000)
        libSM.so.6 => /usr/X11R6/lib/libSM.so.6 (0x4019d000)
        libICE.so.6 => /usr/X11R6/lib/libICE.so.6 (0x401a6000)
        libXpm.so.4 => /usr/X11R6/lib/libXpm.so.4 (0x401bd000)
        libXext.so.6 => /usr/X11R6/lib/libXext.so.6 (0x401cc000)
        libX11.so.6 => /usr/X11R6/lib/libX11.so.6 (0x401db000)
        libncurses.so.5 => /usr/lib/libncurses.so.5 (0x402ba000)
        libutempter.so.0 => /usr/lib/libutempter.so.0 (0x402f9000)
        libc.so.6 => /lib/tls/libc.so.6 (0x42000000)
        libdl.so.2 => /lib/libdl.so.2 (0x402fb000)
        /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0x40000000)

공유 라이브러리를 사용할 때의 두 번째 단점은 약간의 성능 저하를 가져온다는 점이다. 이는 공유 라이브러리 루틴의 참조를 결정짓기 위해서는 실행 시에 해당 루틴을 라이브러리에서 찾는 시간이 필요하기 때문이다.
공유 라이브러리의 참조를 결정하는 일은 공유 라이브러리 로더에 의해 이루어진다. 일반적으로 공유 라이브러리를 사용하는 시스템들은 공유 라이브러리를 사용하는 응용프로그램이 실행될 때마다 공유 라이브러리를 찾는 작업을 수행하는 시간을 최소화 하기 위해 공유 라이브러리의 함수들의 위치 정보를 저장하는 캐시를 유지한다. 예를 들어 리눅스 시스템의 경우에는 공유 라이브러리 로더 /lib/ld.so 를 사용하면서 공유 라이브러리의 위치 정보를 가지고 있는 캐시를 /etc/ld.so.cache 파일에 유지한다. 캐시 파일은 ldconfig 응용프로그램에 의해 생성되고 관리된다. 여기서 ldconfig 는 라이브러리를 검색할 디렉토리들의 목록 정보를 가진 /etc/ld.so.conf 파일을 기반으로 수행된다.

공유 라이브러리를 사용하는 시스템들과 응용프로그램들이 서로 다른 버전의 라이브러리를 지원하고 구분하기 위해, 대부분의 공유 라이브러리 구현들은 공통적인 명명규칙을 따른다. 공유 라이브러리 전체 이름은 순서대로 접두사 “lib”, 라이브러리 이름, 그리고 .so 확장자로 구성되어 있으며, 여기에 이어 마침표와 공유 라이브러리의 버전 번호를 쓰면 된다. 버전 번호에는 둘 또는 세 자리의 숫자가 들어간다. 솔라리스 시스템의 경우에는 버전 번호 libc.so.2.9 에서 처럼, 마침표로 구분된 주(major) 번호와 소(minor) 번호로 구성된다. 리눅스 역시 같은 형식의 명명규칙을 사용하지만, 여기에 배포(release) 번호를 추가하여 libxmms.so.1.2.1 과 같이 사용한다.
공유 라이브러리는 또한 soname 이라는 특수한 이름을 사용한다. soname 은 순서대로 접두사 “lib”, 라이브러리의 이름, 그리고 공유 라이브러리 기본 확장자인 .so 로 구성되어 있으며, 여기에 마침표와 버전 주 번호가 이어진다. soname 은 적절한 공유 라이브러리에 대한 심볼릭 링크이다. 앞에서 소개한 예를 갖고 설명하면, 솔라리스 libc.so.2.9 라이브러리의 soname 은 libc.so.2 가 되고, 리눅스의 ibxmms.so.1.2.1 의 soname 은 libxmms.so.1 이 된다.

공유 라이브러리를 가리키는 마지막 이름은 종종 링커 이름이라 일컬어진다. 컴파일러가 라이브러리를 참조할 때 사용하는 이 이름은 버전 번호를 제외한 soname 이다. 역시 앞에서 소개한 예를 갖고 설명하면, 솔라리스 libc.so.2.9 라이브러리의 링커 이름은 libc.so 가 되며, 리눅스 libxmms.so.1.2.1 라이브러리의 링커 이름은 libxmms.so 가 된다.

공유 라이브러리에서 사용되는 버전 명명 방식은 한 시스템에서 하나의 공유 라이브러리에 대해 호환성이 있거나 없는 여러 버전의 라이브러리를 관리할 수 있게 해준다. 주 번호, 소 번호, 그리고 배포 번호 중 어느 번호가 갱신되는지는 라이브러리들의 수정 내용에 따라 결정된다.
만약 이전 버전의 결함을 수정하기 위해 새로운 버전의 공유 라이브러리를 배포했다면, 배포 번호만이 증가하게 된다. 새로운 함수들이 추가 되었지만 이전 버전의 함수들은 동일한 인터페이스로 그대로 제공된다면 소 번호가 증가되고 배포 번호는 처음부터 다시 시작하게 된다. 공유 라이브러리의 함수들의 인터페이스가 변하여 기존의 응용프로그램이 그 함수들을 불러 사용할 수 없게 된다면, 주 번호가 갱신되고 소 번호와 배포 번호는 처음부터 다시 시작된다. 함수의 특정 인터페이스에 의존하는 응용프로그램은 여전히 적절한 주 번호와 소 번호에 해당하는 라이브러리를 로드할 수 있으며, 새로운 버전의 공유 라이브러리(매개변수가 다르거나 또는 매개변수 순서가 다르다)와 함께 컴파일된 응용프로그램들은 새로운 주 번호에 해당하는 공유 라이브러리를 로드할 수 있다.

동적 적재 라이브러리

일반적으로 많이 사용하는 마지막 종류의 라이브러리는 동적 적재(DL, dynamically loaded) 라이브러리이다. 동적 적재 라이브러리는 실행시간에 모든 라이브러리를 무조건 로드 하지 않고 응용프로그램에서 실행시간 중 언제라도 로드하고 참조할 수 있는 코드 라이브러리이다. 정적 라이브러리와 공유 라이브러리는 각자 서로 다른 형식과 로드되는 방식에 의해 구분되지만, 동적 적재 라이브러리는 프로그램의 실행 환경과 컴파일러가 라이브러리를 사용하는 방식의 차이에 의해 다른 라이브러리들과 구분된다. 정적 라이브러리와 공유 라이브러리 모두 동적 적재 라이브러리로 사용될 수 있다.

DL 라이브러리가 가장 많이 쓰이는 부분은 플러그 인이나 모듈을 사용하는 응용프로그램이다. 동적 적재 라이브러리의 일반적인 사용의 좋은 예로 리눅스, FreeBSD, 솔라리스, HP-UX 시스템들이 인증 방식으로 채택한 PAM(Pluggable Authentication Modules)이다. DL 라이브러리의 최대 장점은 특정 함수의 로드에 의한 부하를 실제로 그 함수가 필요할 때까지 뒤로 미룰 수 있다는 것이다.
또한 DL 라이브러리는 실행 파일이 호출하여 사용하는 플러그 인을 실행 파일 자체와 독립적으로 개발, 유지, 배포하기가 쉽다는 장점이 있다. 뿐만 아니라, CORBA 나 COM 환경에서처럼, 실행시간 전에는 어떤 라이브러리를 포함시켜야 할지 알 수 없는 경우에도 유용하게 사용될 수 있다.

DL 라이브러리는 “필요할 때만” 로드가 되기 때문에, DL 라이브러리를 사용하는 응용프로그램은 라이브러리의 이름, 더 일반적으로는 그것을 찾을 수 있는 위치 이외의 사전 정보는 전혀 요구하지 않는다. 동적으로 적재되는 모듈을 사용하는 대부분의 응용프로그램들은 사용 가능한 DL 라이브러리를 파악하기 위해 일종의 간접적인 방법을 사용한다 - 사용 가능한 플러그 인의 목록을 응용프로그램에 직접 연결하는 것은 플러그 인들이 제공하는 유연성을 크게 떨어뜨린다. 이러한 방법 대신, DL 라이브러리를 사용하는 응용프로그램들은 대체로 DL 라이브러리를 찾기 위해 이미 지정된 디렉토리의 목록을 사용하거나, 환경 변수 또는 설정 파일에서 관련된 디렉토리의 목록을 읽어 들인다.

DL 라이브러리를 사용하는 응용프로그램들은 DL 라이브러리를 열고 내용을 확인하고 오류를 처리하는 등의 작업을 위해 표준 API 를 사용한다. 리눅스 시스템 기반의 C 언어에서는 시스템 헤더 파일인 <dlfcn.h> 를 포함시킴으로써 이 API 를 사용할 수 있다.
API 내의 실제 함수들은 (불행하게도) 플랫폼에 따라 달라진다. - 예를 들면 리눅스, 솔라리스, FreeBSD 시스템은 라이브러리를 사용하기 위해 dlopen() 함수를 사용하거나, HP-UX 시스템은 shl_load() 를, 윈도우 시스템은 (당연하게도) 앞의 예와 근본적으로 다른 자신만의 동적 링크 라이브러리(DLL) 를 사용한다. 플랫폼에 따라 달라지는 DL 라이브러리 구현의 차이를 사용자에게 숨기기 위해 만들어진 GNU 의 Glibc 라이브러리는 DL 라이브러리에 대해 조금 더 일반적인 인터페이스를 제공한다.

  • computer/programming/라이브러리의_이해.txt
  • Last modified: 3 years ago
  • by likewind