리눅스용 프로그램은 C 라이브러리인 libc와 링크가 된다.
libc 링크는 매우 기본적이라서 gcc나 g++에 따로 지시하지 않아도 항상 libc 라이브러리와 링크한다.
그 외 링크하고 싶은 라이브러리는 명시해주어야 한다.
라이브러리 코드 링크 방법
- 정적 링크
- 응용 프로그램의 모든 라이브러리 함수와 의존 관계가 라이브러리 아카이브로부터 추출되어 실행 파일에 복사한다.
- 동적 링크
- 라이브러리 파일과 함수로의 참조가 코드 안에 만들어지지만 실제 링크는 실행 시에 동적으로 이루어진다.
[ 정적 라이브러리 ]
- BusyBox와 스크립트 파일만으로 구성된 작은 시스템을 만든다면, BusyBox를 정적으로 링크해서 런타임 라이브러리 파일과 링커를 복사할 필요가 없는 편이 더 간단하다.
- 전체 C 라이브러리를 제공하기보다 응용 프로그램이 사용하는 코드만 링크하기 때문에 크기도 더 작아진다.
- 런타임 라이브러리를 담을 파일시스템이 준비되기 전에 프로그램을 실행해야 할 때 유용하다.
컴파일할 때, -static 옵션을 추가하면 모든 라이브러리를 정적으로 링크한다.
$ arch64-rpi3-linux-gnu-gcc -static helloworld.c -o helloworld-static
컴파일된 파일을 비교해 보면, static으로 링크한 파일의 크기가 증가한 것을 볼 수 있다.
정적 링크는 보통 이름이 lib[name].a인 라이브러리 아카이브로부터 코드를 복사한다.
여기서는 libc.a이고, [sysroot]/usr/lib에 있다.
아래 명령어는 sysroot의 경로를 셸 변수 SYSROOT에 넣는 것이다.
$ export SYSROOT=$(aarch64-rpi3-linux-gnu-gcc -print-sysroot)
정적 라이브러리 만들기
정적 라이브러리는 ar 명령으로 만들 수 있다.
이름이 test1.c와 test2.c인 두 소스 파일을 이름이 libtest.a인 정적 라이브러리를 만들고 싶다면 아래 명령어를 입력하면 된다.
$ aarch64-rpi3-linux-gnu-gcc -c test1.c
$ aarch64-rpi3-linux-gnu-gcc -c test2.c
$ aarch64-rpi3-linux-gnu-ar rc libtest.a test1.o test2.o
아래 명렁어로 libtest를 helloworld 프로그램에 링크할 수 있다.
$ aarch64-rpi3-linux-gnu-gcc helloworld.c -ltest -L../libs -I../include -o helloworld
[ 공유 라이브러리 ]
- 라이브러리를 사용하는 좀 더 일반적인 방법은 실행 시에 링크되는 공유 오브젝트로 사용하는 것이다.
- 코드를 한 개의 copy본만 로드하면 되기 때문에 저장 공간과 시스템 메모리를 더 효율적으로 사용할 수 있다.
- 라이브러리 파일이 업데이트 됐을 때 해당 라이브러리를 사용하는 모든 프로그램을 다시 링크할 필요가 없어 관리가 쉽다.
- 공유 라이브러리용 오브젝트 코드는 런타임 링커가 메모리의 빈 주소에 자유롭게 위치시킬 수 있어야 한다.
- gcc에 -fPIC인자를 추가하고, -shared 옵션을 이용해 링크해야 한다.
공유 라이브러리 만들기
$ aarch64-rpi3-linux-gnu-gcc -fPIC -c test1.c
$ aarch64-rpi3-linux-gnu-gcc -fPIC -c test2.c
$ aarch64-rpi3-linux-gnu-gcc -shared -o libtest.so test1.o test2.o
응용 프로그램을 이 라이브러리와 링크하려면 정적 링크와 똑같이 -ltest를 붙이면 된다.
코드가 실행 파일에 포함이 되지 않는 대신에, 런타임 링커가 찾을 수 있도록 라이브러리를 가리키는 참조만 포함된다.
아래 명령어로 어떤 라이브러리와 링크되었는지, 런타임링커를 확인해 보자.
$ aarch64-rpi3-linux-gnu-readelf -a helloworld |grep "program interpreter"
$ aarch64-rpi3-linux-gnu-readelf -a helloworld |grep "Shared library"
- 런타임 링커는 /lib/ld-linux-aarch64.so.1으로, Target의 파일 시스템에 있어야 한다.
- 링커는 기본 검색 경로(/lib와 /usr/lib)에서 libtest.so를 찾을 것이다.
- 다른 디렉터리에서도 라이브러리들을 찾고 싶으면, 셸 변수 LD_LIBRARY_PATH에 콜론으로 나뉜 경로 목록을 넣으면 된다.
$ export LD_LIBARAY_PATH=/opt/lib:/opt/usr/lib
공유 라이브러리 버전 번호
soname은 라이브러리가 빌드된 인터페이스 번호를 부호화해서 런타임 링커가 라이브러리를 로드할 때 사용한다.
$ readelf -a libexpat.so.1.6.8 | grep SONAME
이 인터페이스 번호로 컴파일된 프로그램은 실행 시에 lbexpat.so.1을 요구할 것이고, 그 파일은 타깃에서 libexpat.so.1.6.8을 가리키는 심볼릭 링크일 것이다.
libexpat 2.0.0 버전이 설치되면 그 soname은 libexpat.so.2가 되고, 같은 라이브러리의 호환되지 않는 두 버전이 같은 시스템에 설치된다.
libexpat.so.1.*.*와 링크된 프로그램은 libexpat.so.1을 로드할 것이고, libexpat.so.2.*.*와 링크된 프로그램은 libexpat.so.2를 로드할 것이다.
따라서, <sysroot>/usr/lib/libexpat*의 디렉터리 목록을 보면 4개의 파일을 볼 수 있다.
- libexpat.a : 정적 링크용으로 쓰이는 라이브러리 아카이브
- libexpat.so -> libexpat.so.1.6.8 : 동적 링크용으로 쓰이는 심볼릭 링크
- libexpat.so.1 -> libexpat.so.1.6.8 : 실행 시, 라이브러리를 로드할 때 쓰이는 심볼릭 링크
- libexpat.so.1.6.8 : 컴파일 시와 실행 시에 사용되는 실제 공유 라이브러리
첫 번째와 두 번째 파일은 Host PC에서 빌드할 때만 필요하며, 마지막 두 파일은 실행 시 Target에서 필요하다.