일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- askii
- 가성비프로젝터
- 기본 알람
- 실업급여 신청 방법
- 머시멜로
- mainthread
- OSD잠금
- 실업급여온라인교육정답
- git setup
- 인터넷우회
- SBS100
- 에러
- Android
- 무음 설정
- linux
- ClubMed
- 리눅스
- 여행
- Eclipse
- OSD잠금해제
- error
- PJM-F3000
- W2453V
- 안드로이드
- 눈꽃 사진
- 직무 역량
- 실업급여동영상퀴즈
- OSDLock-loosening
- LGFlatronOSD잠금해제
- LGFlatron
- Today
- Total
JJ's blog
linux library 제작(static, shared, dynamic) 본문
출처: https://blog.naver.com/xogml_blog/130138049704
라이브러리와 링킹?
printf 함수... 우리는 이것을 어디서 만들지 않았지만 #include <stdio.h> 를 통해서 사용할수 있다.
우리가 printf를 만들지 않고도 헤더 추가만을 통해 쓸수 있게 해주는것은 크게 두가지 장점이 있을것이다.
1. 남이 만든 메서드를 쉽게 쓸수있다.
2. 코드가 간결해진다.
이미 우리는 이러한 스타일로 코딩을 많이 하고 있다. 그 예를 한번 보자.
//라이브러리 헤더파일에 전역변수 선언 및 초기화
이것을 컴파일 하기 위해서
gcc main.c my_lib.c -o main
요렇게 컴파일 해주면된다.
혹 비주얼 스튜디오에 익숙하면 그냥 프로젝트에 헤더랑 소스 추가해서 작성한담에, 내가 그 헤더 쓰고싶을때 내 코드에
#include "내가만든헤더" 만 해주면 컴파일 잘되길래, 혼동할수도 있는데, 헤더 파일, 즉 *.h 파일은 어느 방법을 써도 그 자체로는
*.c를 가리킬수 없다. 즉
1. 헤더파일에 선언 뿐만 아니라 구현까지 다 해놓던가
2. 각 소스 코드들을 기계어 코드로 바꾼뒤 링킹하던가
3. 아니면 라이브러리를 쓰던가 해야한다
어? my_lib.h에는 1.구현도 않되있고, 또한 위에 컴파일한 gcc명령에서는 어디에도 3.라이브러리를 사용하는 구석이 보이질 않는데?
하지만 위의 gcc 명령은 라이브러리(정적)과 완전히 동일한 일을 실행하고, 다른 라이브러리(공유, 동적)와도 링크 과정만 보면 완전 동일.
즉 위의 방법이 "2. 각 소스 코드들을 기계어 코드로 바꾼뒤 링킹하던가" 를 사용한 방식이다.
하지만 이것을 이해하면 라이브러리를 이해할수 있다. 말했듯 정적라이브러리와는 완전 동일한 방식이며, 공유, 동적 라이브러리와도
링크 과정만 보면 완전 동일하므로.....
저 위 명령 즉 "gcc main.c my_lib.c -o main" 을 살짝 뜯어보면
자세한 과정은 [ GCC 파헤치기(전처리, 컴파일, 어셈블, 링킹) ] 포스트에서...
gcc는 main.c my_lib.c를 전처리(ccp) 시켜 *.i 파일로 만들고, (cc1)으로 어셈블리 코드인 *.s 로 컴파일한다.
이후 어셈블러(as)는 *.s를 받어서 ELF(실행 & 링킹 가능 포맷)인 *.o로 바꾼다.
즉 [main.c, my_lib.c] -> [main.i, my_lib.i] -> [main.s, my_lib.s] -> [main.o, my_lib.o]
이 다음단계가 링킹과정 이다. gcc는 collect2를 통해 ld를 호출하고 이것들이 생성된 *.o 즉 elf파일들을 링크시킨다.
위의 main.c를 보면 전역변수 my_data 선언, 함수 foo 구현이 없다. 즉 main.c를 main.o(기계어)로 바뀌어도 my_data, foo의
심볼(절대 주소를 갖는 최소단위)을 알수 없는것이고, 결국 이것들은 완벽한 기계어로 바뀌지 않고 비어있다.
불완전하게 바뀐 main.o는 ELF 파일인데, 이 파일의 포맷중 .rel.text 라는 섹션이 있다.
즉 이 .rel.text부분에 심볼을 알수없는, 그러니까 위 예제로 보자면 링킹시 my_data나 foo 심볼들을 정의해야 한다는
(사실 비어있음) 내용이 써져있는것이다.
자 쉘에서 # readdir -a main.o 를 입력해보자. 뭐가 쭈룩 나오는데 이것들이 이 ELF 파일의 내부 내용들을 파싱해서 보여주는것이다.
자세한 과정은 [ ELF 구조 ] 포스트에서...
여기서 .rel.text 부분을 보면
마지막 Sym. Name에
my_data
my_data
foo
가 써져있는것을 볼수있다. 즉 이부분에 쓰여있는 심볼들( == relocatable symbol)이 symbol reference resolving이 필요한,
다시 말하면 링킹시 다른 소스? 라이브러리? 여튼 다른데서 가져와야 할 심볼들인것이다.
링커는 링킹시에 이부분을 보고 정의되지 않은 심볼들을 정의해주어 컴파일이 완료되게 하는것이다.
다시말해 이 포스트에서 핵심인 라이브러리에는 위의 심볼들을 정의해줄수 있게 링커에게 정보를 주는 실제 데이터(코드)가 있는것이다.
다시말해 ㅋ 라이브러리 사용으로 그냥 껍데기만 있던 심볼들을 실제 주소들로 대체해주는것이다
자 이것이 라이브러리의 일차원적 필요 이유와 원리이다.
라이브러리에서는 위와같이 링킹시에 정의되지 않은 심볼들을 알아내어 컴파일을 완료되게 하되,
위에서는 링킹시킬 대상(my_lib.h, my_lib.c)를 코드로 가지고 있어서 이것을 내가 컴파일한후 링킹시켰고....
라이브러리를 사용할경우에는 링킹시킬 대상(라이브러리 = 이미 컴파일된 바이너리 코드)를 링킹시키는 것이다.
위 방식(2. 방식임)의 문제점?
위의 방식,
gcc main.c my_lib.c -o main
의 문제점은 무엇일까? 사실 라이브러리를 안쓰고 위와같이 결과는 아주 잘 컴파일 되서 main을 out 한다. 하지만 문제는
1. main이 바뀌어도 my_lib.c 역시 재컴파일 되어야 한다
2. 코드가 공개된다.
우리는 코딩시 아주 아주 엄청나게 많은, 내가짜지 않은 코드들을 헤더파일만 추가해 쓴다. 나는 단순 "HelloWorld"를 출력하는
코드를짜서 컴파일 하려는데 그것을 위해 아주 완벽하게, 이쁘게 짜여진 printf까지 재컴파일 해야할까?
또 위와같이, 즉 라이브러리 대신 코드들을 주고 붙여서 써라~~~ 하면은 코드가 공개되고, 정보 유출 이런걸 떠나서, 호환성의 문제가 생긴다. (다시 말하면 라이브러리는 바이너리 코드임)
그럼 이제 라이브러리를 만들어보자
는 훼이크고 그전에 라이브러리 종류들에 대해 우선 알아본뒤, 만들어보자
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
1. 정적 라이브러리(Static Library) == (*.a) -> archive
정적 라이브러리는 표에서 보듯 컴파일시 라이브러리를 사용해 relocatable symbol 들을 reference resolving 하는거다(즉 링킹)
다시말해 컴파일시 필요한 코드(라이브러리)를 내 프로그램에 적재하고, 완벽한 하나의 프로그램을 만들어낸다.
장점 :
이렇게 컴파일타임에 링킹을 해버리면, 결과 실행파일은 외부 어떤 라이브러리에도 의존성이 없어지기 때문에 이식성이 좋고, 런타임시
외부를 참조할 필요가 없기 때문에 속도에서 장점이 있다.
단점 :
하지만 이렇게 필요한 코드를 내 프로그램에 다 적재해두므로(.text) 프로그램 크기가 약간 커진다.
또한 진짜 문제는 라이브러리 변경이 필요할시(패치), 라이브러리만 재 배포하면 돼는것이 아니라, 변경된 라이브리로 재 컴파일된, 통짜 프로그램을 재 배포해야한다는것이다. 그래서 정적 라이브러니는 슬슬 역사의 뒤 안길로...
정적 라이브러리 만들기(위의 예제 main.c, my_lib.c, my_lib.h 를 사용하여)
∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽
1. 우선 라이브러리의 *.c 파일들을 컴파일하여 *.o 파일들 즉 elf 파일로 변경(elf == Executable and Linkable Format)
2. ar util로 위에서나온 *.o 파일을 *.a 즉 정적 라이브러리 파일로 만든다
3. 사용한다.
∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽
실 예를 보자
1 gcc -c my_lib.c
// object 파일을 생성한다
2 ar rc libmy_lib.a my_lib.o
// ar 유틸로 *.a 파일을 생성한다. 여기서 중요한게 만들어줄 *.a의 이름은 항상 lib(library_name).a 이다.
사용
3 gcc main.c -o main -L./ -lmy_lib
// 위에서 my_lib.a 를 만들었다. 비록 라이브러리 파일의 이름은(*.a) libmy_lib.a 이지만
// 이 라이브러리의 이름은 my_lib이다.(ar 사용시 lib(library_name).a 를 사용했지 않는가?)
// 이제 이 라이브러리를 사용하자. 단 라이브러리가 있는 경로와 라이브러리 이름을 적어 주어야 한다.
// -L(library's path) -> 대문자 엘임, -l(library's name) -> 소문자 엘임
2. 공유 라이브러리(Shared Library - Dynamic Linking(동적 링킹)) == (*.so)
공유 라이브러리는 응용프로그램이 시작되는 순간, 즉 커널이 사용자 주소 공간(VM)을 만들고 응용프로그램(ELF) 을 적재하는 순간 같이 메모리에 적재 된다.
더 자세히 보자면
1. 이 공유 라이브러리를 사용하는 다른 프로그램이 이미 실행되어, 해당 라이브러리가 메모리에 올라가있으면
그것을 참조해서 링킹한다(올라와 있는걸 또 올릴 필요는 없으니깐...)
2. 없으면 라이브러리를 메모리상에 올리고 링킹한다.
공유라이브러리에서는 링킹도 코드를 완전히 붙이는것이 아니다 !
링킹시 일어나는일이 reference resolving 이라 했는데, 즉 relocatable symbol들을 실제 주소로 대체한다고 했다. 실제로 정적라이브러리 사용 시에는 실제 주소로 대체한다. 하지만 공유라이브러리에서는 말그대로 공유 이므로 라이브러리는 메모리에 한번만 올려두고, 프로세스는 그 라이브러리의 원하는 symbol이 있는 주소를 참조만 한다. 이때 이 참조는 run time link editor에 의해 결정된다.
프로세스에서 이 참조를 위한 정보들을 올려두는곳은 힙과 스택 가운데쯤 이당 ㅋㅋㅋ
장점 :
공유라이브러리는 프로그램 변경시 변경된 부분의 공유라이브러리만 재배포하면 되므로 유지보수가 쉽고, 유연하다. 또한 코드를
프로그램에 완전히 붙이는것이아니고 여러 프로그램이 한번 메모리에 올려진것을 공유하므로 디스크, 메모리 사용공간이 정적 라이브러리
사용보다 적다.
단점 :
하지만 외부 의존도가 생기기 때문에 이식성이 어렵다. 또한 공유 라이브러리를 메모리에 올리려면 찾고 올리는데 시간이 걸리므로
성능 저하가 있다. 좀더 자세히 보자면
공유 라이브러리를 사용하는 응용프로그램은 실행시 참조하는 공유 라이브러리를 알아야 한다.
(메모리에 없으면 찾아서 올린뒤에 -> /ib/ld.so 공유 라이브러리 로더에 의해) 실행중 사용해야 하므로....
이때 걸리는 시간을 최소화 하기위해 공유 라이브러리 로더는 /etc/ld.so.cache에 공유 라이브러리 로딩때마다 캐시를 저장/유지 한다.
즉 응용프로그램을 두번 이상째 실행할경우에는 이 캐시를 보고 빠르게 응용프로그램을 실행할수 있는것이다.
이 캐시파일은 ldconfig 응용프로그램에 의해 생성, 관리되고 이 ldconfig 프로그램은 /etc/ld.so.conf 파일을 기반으로 수행된다/.
버전 ?
위에서도 보았듯이 공유 라이브러리의 단점중 하나는 이식성이 낮다는것이다. 이를 해결하기 위해서는 업데이트시 라이브러리의 원활한
재배포와, 더불어 라이브러리들의 버전 관리가 필수일것이다. 즉 같은 이름의 라이브러리도 변경시마다 재배포 되야 하므로 이를 버전으로
관리하는 것이다.
관습적으로 [접두사 lib] + [라이브러리 이름] + [.so.] + [Major 번호.] + [Minor 번호.] + [배포번호] 로 구성된다
예를들어 libmy_lib.so.1.2.3 이라 하면 my_lib이라는 공유라이브러리인데 메이저 번호가 1, 마이너번호가 2, 배포번호가 3 을 뜻하는 것임. 이것은 관습으로 단순히 접두사lib과 라이브러리 이름에 접미사.so 만 있으면 사용할수 있으나 위에서도말한 이식성 문제를 해결하기 위한 방편으로 필요한다.
(완벽히, 제대로 이해하려면 ELF와 collect2->ld, ld-linux.so, ldconfig 등 에 대해서 반드시 알아야 한다..는 포스트 홍보)
공유 라이브러리 만들기(위의 예제 main.c, my_lib.c, my_lib.h 를 사용하여
∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽
1. -fPIC 옵션을 주어 라이브러리를 *.o 파일로 컴파일
2. *.o 파일을 공유라이브러리 *.so 파일로 컴파일
gcc -shared -W1,-soname,lib(library_name).so -o lib(library_name).so.(major num).(minor num).(release num) *.o.....
(W다음 숫자 일 임, -W1,-soname,libmymath.so.1 띄어쓰기 X)
3. 2과정을 거치면 [ lib(library_name).so.(major num).(minor num).(release num) ] 가 나옴. 이것을 /usr/lib으로 mv 즉
mv lib(library_name).so.(major num).(minor num).(release num) /usr/lib/lib(library_name).so.(major num).(minor num).(release num)
4. /etc/ld.so.cache 업데이트를위해 ldconfig 명령 수행
5. 이식성을위해 쉬운이름으로 링크... 즉
ln -s lib(library_name).so.(major num).(minor num).(release num) /usr/lib/lib(library_name).so
6. 사용한다
∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽
실 예를 보자
1 gcc -fPIC -c my_lib.c
// Object 파일(ELF) 생성
2 gcc -shared -W1,-soname,libmy_lib.so -o libmy_lib.so.1.0.1 my_lib.o
// *.so 생성, 즉 공유 라이브러리 생성
3 mv libmy_lib.so.1.0.1 /usr/lib/libmy_lib.so.1.0.1
// /usr/lib으로 옮겨줌
4 ldconfig
// /etc/ld.so.cache 업데이트를 위해 ldconfg 수행
5 ln -s /usr/lib/libmy_lib.so.1.0.1 /usr/lib/libmy_lib.so
// 이식성을위해 쉬운이름으로 링크... 즉
사용
6 gcc -o main main.c lmy_lib
// 여기서 3번과정 즉 라이브러리를 /usr/lib로 옮겨주지 않았다면 라이브러리를 검색할 경로를 찾아주어야 한다.
// gcc는 기본적으로 /usr/lib과 /lib을 뒤지므로 ....
// 즉 옮겨주지 않으면 " gcc -o main main.c lmy_lib -Lpath " 가 될것이다.
3. 동적 라이브러리(Shared Library - Dynamic Loding(동적 적재)) == (*.so)
동적 라이브러리, 즉 동적 적재는 프로그램 도중 응용프로그램에서 특정 라이브러리를 사용할지 말지를 정한다.
즉 정적 = 컴파일타임, 공유 = 프로그램 실행시 reference resolving 을 하는 반면에, 동적 라이브러리는 relocatable symbol을 사용할때 !
reference resolving을 하는것이다.
장점 :
동적 라이브러리는 공유라이브러리와 같이 유연하고 확장성 높은 프로그램을 만들수 있다
단점 :
하지만 역시 이식성은 떨어진다.
동적 라이브러리 만들기(위의 예제 main.c, my_lib.c, my_lib.h 를 사용하여
∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽
1. 라이브러리를 만드는 방식은 공유라이브러리를 만드는 방식과 똑같다. 위의 공유라이브러리를 참조하자
2. 사 용
공유 라이브러리나 동적 라이브러리나 라이브러리를 만드는 방식은 똑같다.
하지만 언제 reference resolving을 하느냐의 차이인데, 공유 라이브러리는 응용프로그램 실행시 runtime link editor가 하는
반면 동적 라이브러리는 응용프로그램이 결정한다(즉 응용프로그램이 특정 라이브러리를 사용할때..)
void *dlopen(const char *library_name, int flag)
library_name 라이브러리를 flag에 맞추어 열고 그 핸들을 리턴
flag : RTLD_NOW : dlopen 실행이 끝나기 전에(즉 리턴전에)
reference resolving 함.
RTLD_LAZY : 실행시간에, 즉 특정 심볼을 사용할때
그 심볼에대한 reference resloving 함
int dlclose(void *dl_handle)
라이브러리 핸들을 닫는다.
void *dlsym(void *dl_handle, char *symbol_name)
라이브러리 핸들에서 특정 심볼을 가져온다
const char *dlerror(void)
에러를 반환한다.
1. 우선 공유라이브러리의 5번까지 해서 *.so룰 만들어낸다 -> 동적 라이브러리
2. 그 라이브러리를 사용할 응용프로그램을 컴파일한다. 단 이때 동적 라이브러리를 사용하기위한 과정을 거친다.
# gcc -o main main.c -ldl -rdynamic
- ldl : dl*관련 함수를 사용하기위한 라이브러리 추가. 즉 dllib을 추가
- rdynamic : (dlopen에서 역추적을 허용하도록) 모든 심볼을 동적 심볼 테이블에 추가하라고 링커에 알려주는 목적
3.소스코드 내에서 dl* 관련 함수를 이용해서 라이브러리를 사용한다.
- dlOpen으로 라이브러리를 열고 그것을 void *인 dlHandle에 담는다.
- dlSym으로 필요한 심볼을 읽어온다
ㄱ. 함수이면 함수 포인터로 받는다
ㄴ. 전역변수이면 ......
- dlClose로 사용한 라이브러리 핸들을 닫느다.
4.응용프로그램을 실행한다
∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽∽
자 그럼 dl관련 함수를 사용해 my_lib을 사용하는 응용 프로그램을 만들어보자
1 #include <stdio.h>
2 #include <dlfcn.h>
3
4 #define LIB "libmymath.so"
5
6 int main(void) {
7 void *dlHandle = NULL;
8 int (*_pAdd)(int, int) = NULL;
9 int (*_pSub)(int, int) = NULL;
10
11 if((dlHandle = dlopen(LIB, RTLD_LAZY)) == NULL)
12 return -1;
13
14 if((_pAdd = dlsym(dlHandle, "_add")) == NULL) {
15 printf("%s\n", dlerror());
16 return -1;
17 }
18
19 if((_pSub = dlsym(dlHandle, "_sub")) == NULL) {
20 printf("%s\n", dlerror());
21 return -1;
22 }
23
24 printf("_add : %d\n", (*_pAdd)(1, 2));
25 printf("_sub : %d\n", (*_pSub)(2, 1));
26
27 dlclose(dlHandle);
28 return 0;
29 }
자 라이브러리 만들고, 사용하는 방식에 대해서는 다 설명한듯 하다.
이제 맘껏 라이브러리를 사용하자. ㅋㅋㅋ
하지만 라이브러리의 깊은이해를 위해서는 링킹과정에 대한 심도있는 이해가 필요하다 !!!
'Technology > LINUX' 카테고리의 다른 글
Ubuntu One account (0) | 2019.06.12 |
---|---|
ELF - Excutable and Linkable Format (0) | 2019.04.25 |
Samba를 이용하여 Ubuntu와 Windows간 파일 공유하기 ( 설정 방법 ) (0) | 2019.04.03 |
make와 Makefile 설명 자료 (0) | 2018.11.21 |
유닉스 및 리눅스 명령어 팁 (0) | 2018.10.16 |