커널 빌드 시스템을 조사하는 동안 v4.19 이전에는 커널이 증분 링크( )를 사용 하다가 다음과 같이 ld -r
씬 아카이브( )로 이동했다는 사실을 발견했습니다.ar T
vmLinux, vmlinuz, vmlinux.bin, zimage 및 bzimage와 같은 커널 Makefile 용어의 차이점은 무엇입니까?나는 알아차렸다
그런 다음 링크 속도 향상이 상당한지 확인하기 위해 합성 증분 링크 벤치마크를 만들어 보았습니다.https://stackoverflow.com/questions/29391965/what-is-partial-linking-in-gnu-linker/53959624#53959624그러나 그것은 내 기준이 아닙니다.
그래서 내 질문은: 커널이 증분 링크 또는 씬 아카이브를 사용하는 이유는 무엇입니까?
빌드 속도를 높이기 위한 것인가요, 아니면 다른 이유 때문인가요?
증분 연결을 도입한 커밋은 무엇입니까? 이로써 나는 그 근거를 알아낼 수 있었다 git log
. git log --grep 'thin archive'
(a5967db9af51a84f5e181600954714a9e4c69f1f)를 사용하면 컴팩트 아카이브로 이동하지만 증분 연결된 아카이브를 쉽게 얻을 수 없다는 것을 알았습니다 .
빌드 속도를 높이기 위해 존재하는 경우 속도 향상을 확인하기 위해 증분 연결을 요구하지 않고 VS와의 연결을 신속하게 테스트할 수 있는 방법이 있습니까?
답변1
약한 파일의 이유
나는 패치 작성자 중 한 명인 Nicholas Piggin에게 이메일을 통해 연락했는데, 그는 아카이브를 줄이는 것이 디스크 사용량을 줄일 뿐만 아니라 링크 오류도 예방할 수 있다고 설명했습니다.
문제는 증분 링크된 개체 파일이 너무 커져서 링커가 개체 간에 생성된 코드를 가리켜야 하는 트램폴린 재배치를 삽입할 수도 없다는 것입니다.
증분 빌드에 대한 이론적 근거에 대해서는 아직 응답을 받지 못했습니다.
그의 멋진 답변은 다음과 같습니다.
이것은 당신이 얼마나 알고 있는지에 따라 꽤 긴 답변입니다. 몇 가지 이유가 있습니다. 이 패치를 만든 Stephen의 주요 동기는 매우 큰 커널을 성공적으로 연결할 수 있도록 하는 것이었습니다.
다른 이점은 다음과 같습니다.
이는 중간 빌드 아티팩트를 저장하는 "더 나은" 방법입니다. 출력 코드를 한 위치에 저장하고 모두 연결될 때까지 참조(최소 아카이브)를 사용하여 추적합니다. 따라서 특히 대규모 빌드 및 디버깅 정보의 경우 더 적은 IO 및 디스크 공간이 필요합니다.
Linux는 빌드할 출력 디렉터리가 몇 개밖에 없는 평균적인 최신 워크스테이션을 위한 거대한 프로젝트가 아니며 모두 캐시에 저장되며 파일을 증분적으로 연결하는 시간이 매우 빠릅니다. 따라서 Linux의 경우 빌드 속도 이점은 일반적으로 작습니다.
이를 통해 링커는 약간 더 나은 코드를 생성할 수 있습니다. 파일을 재배열하고 링커 스텁을 보다 최적으로 배치합니다.
업스트림 LTO 빌드에 대한 지원이 충분하지 않지만 LTO 빌드에서 더 잘 작동하는 경향이 있습니다.
하지만 원래의 동기로 돌아가 보겠습니다.
최종적으로 연결되지 않은 재배치 가능한 개체 파일을 만들면 다른 곳에 정의된 함수 및 변수에 대한 기호에 대한 참조가 포함된 코드 묶음을 얻게 됩니다.
--- a.S --- bl myfunc ---
로 조립
a.o: file format elf64-powerpcle
.text 섹션 분해:
0000000000000000 <.text>: 0: 01 00 00 48 bl 0x0
따라서 코드에는 NIA+0(즉, 자체)에 대한 분기가 있는데 이는 우리가 요청한 것이 아닙니다. 재배치를 덤프하면 누락된 비트가 표시됩니다.
.text 섹션 분해:
0000000000000000 <.text>: 0: 01 00 00 48 bl 0x0 0: R_PPC64_REL24 myfunc
재배치는 코드가 아닌 .text 섹션에 없지만 해당 위치의 명령어에 myfunc라는 기호의 24비트 상대 오프셋이 있음을 나타내는 일부 ELF 메타데이터가 있습니다.
개체를 "최종적으로 연결"하면 파일이 기본적으로 함께 연결되며 이러한 재배치 문제는 올바른 위치를 가리키도록 코드와 데이터를 조정하여 해결됩니다.
aS를 myfunc 기호가 포함된 bS와 연결하면 다음과 같은 결과가 나타납니다.
c: file format elf64-powerpcle Disassembly of section .text: 00000000100000d8 <_start>: 100000d8: 05 00 00 48 bl 100000dc <myfunc> 00000000100000dc <myfunc>: 100000dc: 01 00 63 38 addi r3,r3,1 100000e0: 20 00 80 4e blr
재배치 메타데이터가 제거되고 분기가 올바른 오프셋을 가리킵니다.
따라서 링커는 실제로 링크 타임에 명령을 조정합니다. 한 단계 더 나아가 지침을 생성합니다. 대규모 빌드가 있고 분기가 24비트 오프셋에서 myfunc에 도달할 수 없는 경우 링커는 24비트에서 액세스할 수 있는 코드에 트램폴린(스텁, PLT, 프로시저 링크 테이블이라고도 함)을 삽입합니다. 그런 다음 트램펄린은 목표에 도달할 수 있는 더 긴 가지를 사용합니다.
링커는 코드 중간에 무언가를 추가하면 중간을 통과하는 상대 참조가 끊어지기 때문에 코드의 어느 곳에도 이러한 트램폴린을 배치할 수 없습니다. 링커는 모른다 모두.o 파일의 참조, 해결되지 않은 참조만 해당됩니다. 따라서 링커는 .o 파일을 함께 링크하는 경우에만 해당 참조를 확인하기 전에 .o 파일 사이에 트램펄린을 배치해야 합니다.
이전 증분 빌드 방법은 빌드 디렉터리의 루트에 가까워질수록 .o 파일을 더 큰 .o 파일로 병합했습니다. 따라서 .o 파일이 너무 커지면 분기가 트램폴린에 도달하기 위해 자체 .o 파일 외부에 도달할 수 없을 때 문제가 발생합니다. 이 참조를 해결할 방법이 없습니다.
씬 아카이브의 경우 최종 연결은 수천 개의 매우 작은 .o 파일에서 수행됩니다. 이렇게 하면 링커가 이러한 트램펄린을 배치할 때 최대한의 유연성을 얻을 수 있으므로 이러한 제한에 부딪히지 않습니다.
답변2
“왜?”라는 질문에 대한 답이 없습니다. 질문의 일부이지만 적어도 Linux 0.97(1992년 8월 1일)부터는 증분 링크가 사용됩니다.
OBJS= namei.o inode.o file.o dir.o misc.o fat.o
msdos.o: $(OBJS)
$(LD) -r -o msdos.o $(OBJS)
또는
OBJS= bitmap.o freelists.o truncate.o namei.o inode.o \
file.o dir.o symlink.o blkdev.o chrdev.o fifo.o
ext.o: $(OBJS)
$(LD) -r -o ext.o $(OBJS)
https://github.com/mpe/linux-fullhistory/commit/e60feb868bfa9d248c71a1a3bdd8c2857f1d433d
그러나 이러한 고대 커밋에서는 근거를 언급하지 않으므로 Linus가 모든 것을 한 번에 연결하는 대신 도대체 왜 이 작업을 수행했는지 물어봐야 할 수도 있습니다. 나는 이것이 하나의 거대한 링크 라인으로 모든 객체를 나열하는 대신 빌드 시스템을 훌륭하고 모듈식으로 유지하기 위한 것이라고 생각합니다.
지금 변경하고 싶다면 매우 강력한 사례를 만들어야 합니다. 왜냐하면 빌드 시스템에 그렇게 큰 구조적 변경을 하는 것은 단지 재미로만 수행되는 것이 아니기 때문입니다.