동적 링크
고친 사람 github-actions[bot]
동적 링크는 프로그램이 쓸 라이브러리 코드를 실행을 시작할 때 붙입니다. 빌드할 때는 그 라이브러리의 이름만 실행 파일에 적어 둡니다. 그래서 여러 프로그램이 라이브러리 파일 하나를 나눠 쓸 수 있고, 라이브러리를 고쳐도 프로그램을 다시 빌드하지 않습니다.
쉽고 빠른 이해
동적 링크는 라이브러리 코드를 실행 파일 안에 넣지 않고 프로그램을 켤 때 밖에서 가져다 붙입니다. 화면에 글자를 그리는 라이브러리를 프로그램 백 개가 쓴다면, 그 코드는 컴퓨터에 한 벌만 있으면 됩니다.
이게 없으면 같은 라이브러리 코드가 프로그램마다 복사돼 디스크와 메모리를 따로 차지합니다. 그 라이브러리에 보안 구멍이 발견되면 그것을 쓰는 프로그램을 전부 다시 빌드해야 합니다.
어떻게 도나:
- 빌드 마지막에 코드 조각을 이어 붙이는 도구인 링커가, 라이브러리 이름과 부를 함수 이름만 실행 파일에 적습니다
- 프로그램을 켜면 실행을 준비하는 프로그램인 동적 링커가 그 이름으로 라이브러리 파일을 찾아 메모리에 올립니다
- 비워 둔 주소 칸을 올라간 위치로 채운 다음, 프로그램 본문을 시작합니다
대가는 실패가 실행할 때로 밀린다는 것입니다. 빌드는 멀쩡히 끝났는데 켜는 순간 라이브러리를 못 찾고 죽을 수 있습니다.
상세
연극 대본에는 「여기서 배경은 숲」이라고 이름만 적혀 있습니다. 막이 오르기 전에 무대 담당이 여러 공연이 함께 쓰는 창고에서 그 숲을 찾아와 세웁니다. 배경이 다 서지 않으면 막은 아예 오르지 않습니다.
이 절은 그 배경이 언제 어떻게 서는지를 따라갑니다. 먼저 빌드가 무엇을 안 하고 남기는지 봅니다. 이어서 프로그램을 켤 때 그 빈칸이 채워지는 순서를 봅니다. 마지막으로 이 방식이 주는 이득과 그 대가를 짚습니다.
빌드가 남기는 빈칸
빌드의 마지막 단계는 링커입니다. 컴파일러가 소스 파일마다 만들어 놓은 코드 조각을 모아 실행할 수 있는 프로그램 하나로 잇는 도구입니다. 조각끼리 서로의 함수를 부르는 대목은 이때 진짜 메모리 주소로 채워집니다.
라이브러리를 붙이는 방식은 둘로 갈립니다. 정적 링크는 필요한 라이브러리 코드까지 실행 파일 안으로 복사해 넣습니다. 동적 링크를 고르면 링커가 그 복사를 하지 않습니다.
대신 실행 파일에는 이름만 남습니다. 「이런 라이브러리가 필요하다」는 파일 이름과 「그 안의 이런 함수를 부른다」는 함수 이름입니다. 라이브러리 함수를 부르는 주소 칸은 비어 있습니다.
이 빈칸이 빌드할 때 안 채워지는 까닭은 값을 아직 모르기 때문입니다. 라이브러리가 메모리 어디에 올라갈지는 프로그램을 켜 봐야 정해집니다. 실행 파일만 뜯어 봐서는 알 수 없는 값입니다.
프로그램을 켤 때 채워진다
실행을 시작하면 운영체제는 프로그램 본문보다 동적 링커를 먼저 띄웁니다. 실행 파일에 적힌 라이브러리 이름을 읽어 그 파일을 찾고, 메모리에 올린 다음, 비워 둔 주소 칸을 채우는 프로그램입니다. 이 준비가 끝나야 프로그램의 첫 줄이 돕니다.
이때 찾아 올리는 라이브러리 파일을 공유 라이브러리라고 합니다. 실행 파일 밖에 따로 서서 여럿에게 코드를 내주는 파일이라 그렇게 부릅니다.
sequenceDiagram
participant 운영체제
participant 링커 as 동적 링커
participant 파일 as 공유 라이브러리
participant 앱 as 프로그램 본문
운영체제->>링커: 본문보다 먼저 띄운다
링커->>파일: 적힌 이름으로 파일을 찾는다
파일-->>링커: 메모리에 올라간다
Note over 링커: 비워 둔 주소 칸을 채운다
링커->>앱: 첫 줄부터 시작시킨다
그림의 순서대로 라이브러리가 먼저 메모리에 올라가고, 그다음에 주소 칸이 채워집니다. 프로그램 본문은 맨 마지막에 돕니다. 준비가 중간에 실패하면 프로그램은 한 줄도 못 돌고 끝납니다.
함수 주소를 켤 때 전부 채우지 않고, 그 함수를 처음 부르는 때까지 미루는 방식도 있습니다. 지연 바인딩이라고 부릅니다.
라이브러리 하나를 여럿이 나눠 쓴다
동적 링크가 주는 이득은 코드가 실행 파일 밖에 있다는 것에서 나옵니다. 라이브러리 파일이 컴퓨터에 한 벌만 있으면 되니 디스크를 덜 씁니다. 같은 라이브러리를 쓰는 프로그램이 동시에 여럿 떠 있어도 운영체제는 그 코드를 메모리에 한 번만 올리고, 여러 프로세스가 같은 메모리를 보게 합니다.
flowchart TD
subgraph 돌고 있는 프로그램
A["편집기"]
B["웹 브라우저"]
C["터미널"]
end
subgraph 메모리에 한 번 올라간 코드
L["문자를 그리는 공유 라이브러리"]
end
A --> L
B --> L
C --> L
그림의 화살표 셋이 같은 곳을 가리킵니다. 정적 링크였다면 같은 코드가 세 벌 복사돼 있었을 것입니다.
라이브러리를 고치는 일도 달라집니다. 보안 구멍을 메운 새 파일로 바꿔 두면 다음에 켜지는 프로그램부터 고쳐진 코드를 씁니다. 그 라이브러리를 쓰는 프로그램은 손대지 않습니다.
대가 — 실패가 실행할 때로 밀린다
빈칸을 늦게 채우니 실패도 늦게 드러납니다. 빌드는 통과했는데 켜는 순간 죽는 일이 생깁니다. 라이브러리 파일이 아예 없거나, 있어도 부르려는 함수가 빠진 판이면 그렇습니다.
나눠 쓴다는 것은 한 파일을 고치면 그것을 쓰는 프로그램이 전부 영향을 받는다는 뜻이기도 합니다. 새 판이 함수의 뜻이나 인자를 바꾸면 그 라이브러리를 쓰던 프로그램들이 같이 어긋납니다. 이렇게 얽힌 판 요구가 서로 맞지 않아 어느 판을 깔아도 무언가가 깨지는 상태를 의존성 지옥이라고 부릅니다.
켤 때마다 찾고 올리고 채우는 일이 붙으니 시작도 조금 늦어집니다.
고르는 기준은 둘입니다. 라이브러리를 나눠 쓸 일이 있나, 그리고 실행할 컴퓨터에 무엇이 깔려 있는지 아나입니다.
| 이런 때 | 고르는 쪽 |
|---|---|
| 여러 프로그램이 같은 라이브러리를 쓴다 | 동적 링크 |
| 라이브러리를 프로그램과 따로 고쳐야 한다 | 동적 링크 |
| 실행 파일 하나만 옮겨서 돌려야 한다 | 정적 링크 |
| 켜는 컴퓨터에 무엇이 깔렸는지 모른다 | 정적 링크 |
표의 위 두 줄은 나눠 쓰는 이득을 사는 경우이고, 아래 두 줄은 실행할 컴퓨터를 못 믿는 경우입니다. 컨테이너 이미지처럼 깔린 것을 통째로 고정해 옮기는 방식이 퍼지면서, 아래 두 줄을 고르는 프로그램도 늘었습니다.
관련 항목
동적 링크가 다루는 파일
공유 라이브러리 · 실행 파일 · 목적 파일 · 정적 라이브러리 · ELF · DLL · Mach-O
동적 링크를 수행하는 도구
동적 링커 · 정적 링커 · 링커 · 로더 · 컴파일러 · ld.so · dyld
링크할 때 읽고 고치는 구성 요소
심볼 · 심볼 테이블 · 재배치 · PLT · GOT · 위치 독립 코드 · 섹션
링크 시점으로 갈리는 이웃 방식
정적 링크 · 지연 바인딩 · 링크 타임 최적화 · 클래스 로딩
동적 링크가 올라타는 운영체제 기능
가상 메모리 · 메모리 맵 파일 · 프로세스 · 페이지 캐시 · 주소 공간 배치 무작위화
동적 링크에서 자주 나는 오류·장애
의존성 지옥 · 미정의 참조 · 심볼 충돌 · ABI · 버전 스크립트
동적 링크를 다루는 명령과 설정
ldd · dlopen · rpath · LD_LIBRARY_PATH · pkg-config
다른 이름: dynamic linking · dynamic link · 동적 연결 · 런타임 링크