다음 이전 차례

4. 동적(DL) 라이브러리

동적 라이브러리는 프로그램의 시작시가 아닌 다른때에 적재되는 라이브러리 를 말한다. 플러그인등의 모듈은 그것의 필요시까지 적재를 기다리는 것이 허 용되므로 이들의 구현에 특히 유용하다. 예를들면 PAM(Pluggable Authenti- cation Module:적재가능 인증모듈)등의 시스템에서는 관리자가 인증을 구성또 는 재구성하는데 동적 라이브러리를 사용한다. 또한 동적 라이브러리는 수시로 언어코드를 기계어로 컴파일하고, 컴파일된 모듈들을 효율적으로 멈춤없이 사 용코저하는 인터프리터를 구현하는데도 유용하다. 이러한 방법은 저스트 인타 임 컴파일러(JIT)나 멀티 유저던전(MUD:multi user dungeon)의 개발에도 유 용하다.

리눅스에서는 동적 라이브러리는 포맷만의 관점에서 본다면 특별한 것이 아 니다. 즉, 그것은 위에서 논의한 표준적인 오브젝트 파일이나 표준적인 공유 라이브러리로써 만들어진다. 가장 중요한 차이점은 그것이 프로그램의 링크시 나 시작시에 자동으로 적재되는 것이 아니라는 것이다. 그 대신으로 라이브러 리를 열고 심볼을 살피며 에러를 제어하고 닫는등의 API가 제공된다. C의 사 용자는 이러한 API를 사용하기 위해서 <dlfcn.h>를 include하여야 한다. 리눅스에서 사용되는 인터페이스를 나는 "dlopen() API"라고 부르며 솔라리 스의 그것과 핵심에 있어서는 동일하다. 그러나 이러한 동일한 방식의 인터페 이스가 모든 플랫폼에서 같은 것은 아니다. HP-UX는 shl_load()라는 다른 메 커니즘을 사용하며 윈도우즈에서는 DLL을 완전히 다른 인터페이스로 다룬 다. 이식성에 목표를 둔다면 플랫폼간의 이질성을 숨기게해주는 몇개의 wrapping 라이브러리의 사용을 고려해야한다. 한가지 방법으로 glib 라이브러 리의 동적 모듈적재기능이 있다. 이것은 이러한 함수들의 이식성 구현을 위해 플랫폼의 하부에 있는 동적적재루틴을 사용한다. 이에관한 더 자세한 내용은 [15] developer.gnome.org/doc/API/glib/glib-dynamic-loading-of-modules.html 에서 배울수 있다. 여기 문서에서 잘 설명되어 있으므로 더 이상 의 설명은 하지 않겠다. 다른 방법은 [16]GNU libtool중에 하나인 libltdl을 쓰는 것이다. 이것의 다양한 기능을 쓰기위해서는 아마도 COBRA Object Broker(ORB)를 보길 바란다. 리눅스와 솔라리스에서 제공하는 기능에 여전히 관심이 있다면 계속 읽어주길 바란다.

dlopen()

dlopen(3) 함수는 라이브러리를 열어서 사용준비를 한다. C에서 함수의 원 형은 아래와 같다.

void *dlopen(const char *filename, int flag);

만약에 파일의 이름이 /(즉, 절대경로)로 시작한다면 dlopen()은 라이브러 리를 탐색하지 않을것이다. 그렇지 않다면 dlopen()은 다음과 같은 순서로 라 이브러리를 탐색한다.

  1. 콜론으로 분리된 사용자의 LD_LIBRARY 환경변수의 각 디렉토리 목록.
  2. /etc/ld.so.cache에 명세된 라이브러리들
  3. /lib, 그다음은 /usr/lib
dlopen()에서 플래그의 값은 동적 라이브러리로 부터의 코드 수행시에 미정의 된 심볼값을 해결함을 뜻하는 RTLD_LAZY, dlopen()이 리턴되기전 모든 미 정의된 심볼의 값이 해결되는, 그렇지 않으면 실패를 뜻하는 RTLD_NOW 중의 하나가 되어야 한다. 라이브러리내에서 정의된 외래심볼이 순차적으로 호출되는 라이브러리들에 의해서 유효해짐을 뜻하는 RTLD_ GLOBAL또한 사용되거나 옵션이 될 수 있다. 디버깅 중이라면 RTLD_LAZY는 미해결된 심볼참조에 의한 예측 불가능의 에러를 일으킬수 있으므로 RTLD_NOW를 사용하길 원할 것이다. RTLD_NOW는 라이브러리를 여는데 약간 느리다. 이것이 문제가 된다면 RTLD_LAZY로 나중에 전환하면된다.

만약 라이브러리들이 서로 의존할 경우(예를 들면 X가 Y를 의존한다면), 의 존이 되어주는 라이브러리(이 예에서는 Y)를 먼저 로드하여야 한다. dlopen()의 리턴값은 (적재되는 동적 라이브러리의 일종의 파일) 핸들(handle) 이다. 그것은 다른 동적 라이브러리 루틴에서 사용되어질 어떠한 값이라 고려 될수 있을 것이다. 적재시도가 실패하면 dlopen()은 NULL을 리턴할 것이며 점검해 보길 바란다. 같은 라이브러리가 두번 이상 dlopen()에 의해서 로드된 다면 같은 파일 핸들이 리턴된다.

만약 라이브러리에서 _init로 명명된 루틴을 외부 호출 한다면 그 코드가 dlopen()의 리턴전에 수행된다. 이러한 사실을 이용해 당신의 라이브러리에서 초기화 루틴을 구현하면 될 것이다.

dlerror()

에러는 dlerror()을 호출함으로써 보고되는데, 이 함수는 가장 최근의 dlopen(), dlsym(), 또는 dlclose()로 인해 발생한 에러를 기술하는 스트링 을 리턴한다. 한가지 특별한 점은 dlerror()를 호출하고 난뒤에는 그 다음부 터는 또다른 에러가 발생하기 전까지 dlerror()는 NULL을 리턴한다는 것이 다.

dlsym()

동적 라이브러리를 적재하고 사용하지 못한다면 아무런 의미도 없을 것이다. dlsym(3)은 라이브러리 사용의 가장 주된 루틴이며 이 함수는 오픈된 라이브 러리에서 심볼값을 조사한다. 이함수는 다음과 같이 정의되어있다.

void *dlsym(void *handle, char *symbol);

handle은 dlopen()에 의해 리턴되는 값이며 symbol은 NIL-로 끝나는 문자열 이다. dlsym()의 결과값을 void * 타잎의 포인터에 저장하면 매번 사용시마다 캐스트 연산을 수행해야 하므로(그리고, 프로그램을 유지하려는 많은 다른 사 람들에게 적은 정보밖에 주지못할 것이므로) 피할수 있다면 이렇게 하지말길 바란다.

dlsym()은 심볼이 발견되지 않은경우에 NULL값을 리턴하게된다. 만약에 어 떤 심볼이 결코 제로나 NULL값을 가지지 않을 것을 당신이 알고 있다면 아 무런 문제도 없으나 만약 그렇지 않다면 잠재적인 모호성이 존재하게 된다, 즉 만약에 NULL값을 가지게 된 경우 그것이 그런 심볼이 존재하지 않는다는 것 인지 아니면 실제로 그 심볼의 값이 NULL인지 하는 것이다. 표준적인 해결 책은 dlerror()을 호출(존재할수 있는 모든 에러의 가능성을 해소하는것임) 하고 그다음으로 심볼값을 알기위해 dlsym()을 호출한 뒤 dlerror()을 한번 더 호출하여 에러가 발생했는지를 점검하는 것이다. 이 부분의 코드를 발췌한 다면 아래와 같을 것이다.

dlerror();
 /* 에러코드의 초기화 */
s = (actual_type) dlsym(handle, symbol_being_searched_for); 
 if ((err = dlerror()) != NULL) 
  /* 심볼값을 찾지 못할때 */
  else 
  /* 심볼값을 찾았을 때 그 값이 s에 있음 */
dlclose()

dlclose()는 dlopen()의 역으로써 동적 라이브러리를 닫는데 사용된다. 동적 라 이브러리는 동적 파일핸들에 관한 링크수효를 계속 유지하고 있으므로 적어 도 dlopen()이 호출된 횟수만큼의 dlclose()의 수행이 없기 전에는 실제적으로 메모리에서 삭제되지 않는다. 그래서 같은 프로그램이 같은 라이브러리를 여 러번 호출하는 것은 아무런 문제가 되지 않는다.

동적 라이브러리의 예 dlopen()의 매뉴얼 페이지에 나오는 동적라이브러리의 예가 아래에 있다. 예 에서는 수학라이브러리가 로드되고 2.0의 cosine값이 출력되며 매순간마다 에 러를 검사(권고됨)한다.


    #include <stdio.h>
    #include <dlfcn.h>
    int main(int argc, char **argv) 
        void *handle;
        double (*cosine)(double);
        char *error;
        handle = dlopen ("/lib/libm.so", RTLD_LAZY);
        if (!handle) 
            fputs (dlerror(), stderr);
            exit(1);
        

        cosine = dlsym(handle, "cos");
        if ((error = dlerror()) != NULL)  
            fputs(error, stderr);
            exit(1);
        

        printf ("%f", (*cosine)(2.0));
        dlclose(handle);
    

만약 위의 프로그램의 이름이 foo.c 라면 다음의 명령을 써서 실행프로그램을 만들 수 있다.

gcc -Wl,export-dynamic -o foo foo.c -ldl -WI, export-dynamic 옵션은 실질적으로 필요한 것은 아니나 때때로 유용함 을 알게될 것이다. 그것은 ld에 정의되어 있는데 ELF파일의 생성시에 이옵션 은 모든 심볼을 다이나믹 심볼테이블에 부가한다. 통상적으로 다이나믹 심볼테 이블은 동적 오브젝트에서 사용되는 심볼만을 가지고 있다. 이 옵션은 dlopen()의 어떤 용도에서 필요하다. -WI, export-dynamic 보다 -rdynamic을 쓰라고 말할수도 있겠지만 ELF문서에 의하면 -rdynamic은 비 리눅스 시스 템의 gcc에서는 항상 제대로 동작하는 것은 아니라고 한다.


다음 이전 차례