프로그램 번역과 실행
이번 장에서는 비휘발성 저장장치 파일에 저장되어 있는 C프로그램을 컴퓨터가 실행할 수 있는 프로그램으로 변환하기 위한 4단계를 설명한다. 밑의 그림은 이러한 번역 단계를 계층적으로 보여줍니다. 어떤 시스템은 번역 시간을 줄이기 위해 이 중 몇단계를 하나로 합치기도 하지만, 논리적으로는 이 4단계를 거쳐야 합니다. 이 장에서는 번역 계층 에 따라 설명을 진행합니다.
컴파일러
컴파일러는 C프로그램을 엄셈블리 언어 프로그램으로 바꿉니다. 어셈블리 언어 프로그램은 컴퓨터가 이해할 수 있는 명령의 기호 형태입니다. 상위 수준 언어 프로그램은 어셈블리 언어보다 문장 수가 훨씬 적으므로 프로그래머의 생산성이 높아집니다. 1975년 당시에는 메모리가 부족하고 컴파일러가 비효율적이였기 때문에 운영체제와 엄셈블러는 어셈블리 언어(assembly language)로 작성했습니다. 오늘날에는 DRAM칩의 용량이 커져서 프로그램 크기 문제도 완화되었고, 최적화 컴파일러가 어셈블리 프로그램 전문가에 필적하거나 오히려 더 우수한어셈블리 언어 프로그램을 생성하고 있습니다.
어셈블러
어셈블리 언어는 상위 수준 소프트웨어와의 인터페이스이므로 원래는 없는 명령어를 어셈블러가 독자적으로 제공할 수 있습니다. 이 명령어들은 하드웨어로 구현이 되어 있지 않더라도 어셈블러가 알아서 처리하여 번역과 프로그래밍을 간편하게 해줍니다. 이런 명령어들을 의사명령어(pseudoinstruction)라 합니다. MIPS하드웨어는 레지스터 $zero의 값이 항상 0이 되어야합니다. 즉 $zero를 사용하면 그 값은 항상 0이며, $zero의 값은 바꿀 수 없습니다. 그러므로 $zero는 한 레지스터의 내용을 다른 레지스터로 복사하는 move명령어를 구현하는 데 사용할 수 있습니다. MIPS하드웨어에는 move명령어가 없지만 MIPS 어셈블러는 이 명령을 받아드립니다.
move $t0 , $t1 # register $t0 gets register $t1
어셈블러는 이 명령어를 다음 명령에 해당하는 기계어로 바꿉니다.
add $t0, $zero, $t1 # register $t0 gets 0 + register $t1
MIPS어셈블러는 blt(branch on less than)명령어를 slt와 bne3개의 명령어로 바꿉니다.이외에도 bgt, bge, ble명령어가 이렇게 처리됩니다. 명령어의 수치 필드 크기는 16비트로 제한되어 있지만 MIPS어셈블러는 레지스터에 32비트 상수를 넣는 일도 해 줄수 있습니다.
결과적으로 의사명령어는 실제의 하드웨어 구현보다 훨씬 더 풍부한 어셈블리언어 명령어 집합을 제공합니다.이에 대한 대가는 레지스터 하나를 어셈블러 진용으로 유보해 두어야 한다는 것뿐입니다. 어셈블리 프로그램을 작성할 때 의사명령어를 적절히 사용하면 일이 쉬워집니다.
링커
이제까지 설명한 대로라면 어떤 프로시저를 한 줄이라도 고치면 전체 프로그램을 다시 컴파일하고 어셈블해야 합니다. 이렇게 처음부터 다시 한다면 컴퓨터의 자원이 심각하게 낭비됩니다. 이것을 피하는 방법은 각 프로시저를 따로따로 컴파일, 어셈블하는 것입니다. 어떤 프로시저가 바뀌면 바뀐 프로시저만 다시 번역하면 됩니다. 이렇게 하려면 링크 에디터(link editor)또는 링커(linker)라 부르는 시스템 프로그램이 추가로 필요합니다. 이 프로그램은 따로따로 어셈블된 기계어 프로그램을 하나로 연결해 주는 일을 합니다. 링커의 동작은 세단계로 이루어집니다.
1. 코드와 데이터 모듈을 메모리에 심벌 형태로 올려놓는다.
2. 데이터와 명령어 레이블의 주소를 결정한다.
3. 외부 및 내부 참조를 해결합니다.
링커는 각 목적 모듈의 재배치 정보와 심벌 테이블을 이용해서 미정의 레이블의 주소를 결정합니다. 분기 명령어, 점프 명령어, 데이터 주소등에 나타나는 구주소를 신주소로 바꾸는 일을 하므로 에디터와 유사한 점이 있습니다. 프로그램 전체를 다시 컴파일하고 어셈블하는 대신 링커를 써서 번역된 모듈을 연결하면 시간이 절약됩니다.
링커가 외부 참조를 모두 해결하고 나면 각 모듈의 메모리 주소를 결정합니다. 각 파일을 독립적으로 어셈블하기 때문에 어셈블러는 어떤 모듈의 명령어와 데이터가 다른 모듈과 상대적으로 어떤 위치에 있게 되는지 알 수 없습니다. 링커가 모듈을 메모리에 적재할 때 절대참조(레지스터에 더해지는 것이 아닌 실제 메모리 주소는 모두 실제위치에 해당하는 값으로 재설정되어야 합니다.
링커는 컴퓨터에서 실행될 수 있는 실행파일을 생성합니다. 이파일은 대개 목적 파일과 같은 형식을 갖는데, 다만 미해결된 참조는 없습니다. 라이브러리 루틴같이 일부만 링크된 파일 이 있을 수도 있습니다. 이런 파일은 아직도 미해결 주소를 갖고 있으므로 목적 파일에 속합니다.
로더
디스크에 실행 파일이 준비되면 운영체제가 디스크에서 실행 파일을 읽어서 메모리에 넣고 이를 시작시킬 수 있습니다. UNIX시스템의 로더(loader)는 이 일을 다음 순서로 진행합니다.
1. 실행파일 헤더를 읽어서 텍스트와 데이터 세그먼트의 크기를 알아냅니다.
2. 텍스트와 데이터가 들어갈 만한 주소 공간을 확보합니다.
3. 실행파일의 명령어와 데이터를 메모리에 복사합니다.
4. 주 프로그램에 전달해야 할 인수가 있으면 이를 스택에 복사합니다.
5. 레지스터를 초기화하고 스택 포인터는 사용 가능한 첫 주소를 가리키게 합니다.
6. 기동 루틴(start up routine)으로 점프합니다. 이 기동 루틴에선느 인수를 인수 레지스터에 넣고 프로그램의 주 루틴을 호출합니다. 주 프로그램에서 기동 루틴으로 복귀하면 exit시스템 호출을 사용하여 프로그램을 종료시킵니다.
'Computer Architecture > 컴퓨터 구조' 카테고리의 다른 글
[16] CH2 명령어:컴퓨터 언어 < ARMv8 > (0) | 2022.01.19 |
---|---|
[15] CH2 명령어:컴퓨터 언어 < ARMv7 > (0) | 2022.01.19 |
[13] CH2 명령어:컴퓨터 언어 < MIPS 버전 5 > (0) | 2022.01.19 |
[12] CH2 명령어:컴퓨터 언어 < Arm 버전 2 > (0) | 2022.01.19 |
[11] CH2 명령어:컴퓨터 언어 < MIPS 버전 4 > (0) | 2022.01.19 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!