컴퓨터구조(2) - 명령어: 컴퓨터 언어(1)
‘컴퓨터 구조 및 설계’를 공부하여 정리한 내용입니다.
서론
- 기본 원리
- 하드웨어가 제공하는 기본적인 연산은 몇 가지 안 된다.
- 컴퓨터 제작 및 설계자들에게는
하드웨어와 컴파일러 제작이 쉽고
, 최소 비용에서 최대 성능을 구현할 수 있는 언어를 찾아내고자 하는 공통의 목표가 있다.
- 앞으로 배울 내용
- 명령어 집합의 하드웨어 표현 방식 및 상위 수준 언어와의 관계
- 내장 프로그램 개념: 여러 종류의 데이터와 명령어를 메모리에 숫자로 저장할 수 있다는 개념
하드웨어 설계
- 설계 원칙
- 간단하게 하기 위해서는 규칙적인 것이 좋다: 하나의 처리 방법으로 여러개의 명령어를 처리할 수 있다.
- 작은 것이 더 빠르다: 레지스터의 개수가 많으면 전기 신호가 더 멀리까지 전달되어야 해서 클럭 사이클 시간이 길어진다. (레지스터 to 레지스터)
- 좋은 설계에는 적당한 절충이 필요하다: 모든 명령어의 길이를 같게 하고 싶은 생각과 모든 명령어 포맷을 하나로 통일하고 싶은 생각에서 적당히 절충안을 생각해 낼 필요가 있다.
- 부호있는 수와 부호없는 수
- 이진 자리수(binary digit): 비트(bit)
- LSB와 MSB
- 부호를 사용할 때 표현 방식
- MSB를 sign bit로 사용
- 모든 자리의 bit를 반전시키고 1bit를 더하면 보수가 된다.
- 16bit to 32 bit 부호 확장 방식: 16bit 정보의 MSB를 복사해서 32bit의 상위 16bit를 채우고, 나머지 하위 16bit는 기존 데이터를 그대로 넣는다.
명령어의 컴퓨터 내부 표현
- 설계의 절충안
- 위의 설계원칙에서도 말했듯이 좋은 설계에는 적당한 절충이 필요하다
- MIPS는 명령어의 길이를 같게 하고, 명령어 종류에 따라 형식을 다르게 하는 것으로 했다. (R, I, jump, etc.): 내부에서는 이를 OPCODE라는 필드를 사용하여 파악한다.
- MIPS 명령어의 필드
- 아래 표 참고
- 아래 명령어 포맷으로 구체적인 명령어들이 어떻게 구현되어 있는지에 대한 내용은 생략하겠다.
- > 참고: inst <-> hex converter
opcode | rs | rt | rd | shift_amount | function |
---|---|---|---|---|---|
6bit | 5bit | 5bit | 5bit | 5bit | 6bit |
명령어가 실행할 연산을 구분하는데 사용 (R, I, jump) | 피연산자1 | 피연산자2 | 연산 결과 저장 레지스터: (register for destination) | 비트 자리이동용으로 사용 | 기능 코드: 구분된 연산에서 구체적인 기능을 지정, EX) opcode로 Rtype인 것을 알았다면 구체적으로 Add를 할지, Sub를 할지 정하는 코드 |
하드웨어의 프로시저 지원
- 프로시저(callee)를 실행할 때 작업하는 단계 (순서대로)
- 프로시저가 접근할 수 있는 곳에 인수를 넣는다: $a0 ~ $a3
- 프로시저로 제어를 넘긴다: jal procedureAddress(동시에 $ra에는 PC+4가 저장됨: Caller로 돌아가기 위해서)
- 프로시저가 필요로 하는 메모리 자원, 레지스터 자원을 획득한다: stack에 Caller에서 사용하던 자원(레지스터에 저장되어 있던 값)을 임시 저장한다.
- 필요한 작업을 수행한다
- 호출한 프로그램(caller)이 접근할 수 있는 장소에 결과 값을 넣는다: $v0 ~ $v1
- 프로시저는 프로그램 내의 여러 곳에서 호출될 수 있으므로 원래 위치로 제어를 돌려준다: jr $ra, stack에 임시 저장해 두었던 자원을 다시 돌려받는다.
- Stack에 값을 후퇴시켜두는 작업: 레지스터 스필링
- 모든 레지스터 값을 후퇴시키는 것은 너무 많은 낭비를 부를 수 있다.
- 따라서, 스필링을 할 레지스터를 관례적으로 정하자
- 그렇게 정해진 것이 $s0~$s7, stack에는 이 레지스터들의 값만 후퇴시킨다.
- 반대로 $t0~$t9의 경우는 피호출 프로그램이 값을 보존해주지 않도록 한다.
- 스필링 시킬 레지스터의 값이 현저히 줄어든다.
- 프로시저 안에서 프로시저를 부르는 경우
- $ra에 저장되는 값이 중첩되는 가능성이 생긴다.
- 그렇기에 이 $ra의 값도, 이러한 상황일 때는 stack에 스필링하여 문제를 해결한다.
- 새 데이터를 위한 스택 공간의 할당
- 특정 프로시저의 저장된 레지스터와 지역 변수를 가지고 있는 스택 영역을 프로시저 프레임(또는 액티베이션 레코드)이라고 한다.
- 그래서 우리는 Stack pointer 뿐만 아니라 Frame pointer도 가지고 있어서 메인 메모리의 어느 공간을 프로시저 프레임으로 사용하고 있는지를 참조하게 된다.
- Stack pointer는 프로그램을 진행하면서 계속해서 바뀌기 때문에 상대 참조로 사용하기에 적합하지 않지만 Frame pointer의 경우는 지정되면 변하지 않기 때문에 이를 사용해서 stack에 저장되어 있는 변수 참조를 쉽게 하는 것이 가능하다.
- Saved argument registers: 파라미터로 넘길 값들이 $a0 ~ $a3의 개수를 넘어갈 때 사용, 프로시저는 이 곳에 나머지 파라미터가 있다고 자동으로 판단한다.
- Saved return address: Caller의 $ra
- Saved saved registers: Caller의 $s0 ~ $s7
- Local arrays and structures: 프로시저를 사용할 때 사용 하는 로컬 변수
- 새 데이터를 위한 힙 공간의 할당 (메인 메모리 공간 - 폰노이만 구조를 따르는 것을 알 수 있다.)
- Reserved: 시스템을 구성하기 위해 기본적으로 필요한 부분 - ARM에서는 이 부분에 ISR 등에 대한 정보가 들어가 있는 것을 확인했다.
- Test: 텍스트화 되어 있는 소스코드가 기계어로 해석되어 올라가는 공간이다.
- Static Data: 전체 프로그램을 사용하면서 변하지 않는 정적 데이터가 들어가는 공간이다.(일반적으로 전역 변수등이 들어간다.)
- Dynamic data: Heap(프로시저의 종료와 관계없이 동적할당 된 데이터는 계속 살아 남는다)
- Stack: 프로시저에서 사용하는 지역 변수, 그리고 위에서 설명한 스필링 등에 사용한다.
- 동적 할당을 사용한다는 것의 의미
- 사용자가 수동으로 메모리에 락을 걸고, 락을 푸는 작업이 있다는 것이다.
- 사용자가 동적할 당으로 락을 걸고 이를 잊게 되면, 프로그램이 끝나기 전까지 락은 플리지 않는다.
- 니렇게 되어 사용가능한 메모리가 점점 줄어드는 현상을 메모리 누출, memory leak이라고 한다.
- 그래서 일반적으로 C 코딩을 할 때 malloc등을 사용하면 함수를 빠져나오기전에 free를 해주거나 하는 원칙을 잘 지켜주어야 한다.
- Java는 이를 자동으로 해주고 이러한 작업을 위해 가비지 컬렉션(garbage collection)을 사용한다.
Leave a Comment