[컴퓨터아키텍처] 2주차 수업 내용 정리
강의 목표
- 먼저 노이만형 컴퓨터의 기초를 아는 것
- 명령이나 프로그램, 기계어란 무엇인가
- 단순한 CPU의 구조와 동작
- 이를 간단한 예시를 들어 설명
- C언어와의 대응
- 실제 명령 세트의 예
명령과 프로그램, 기계어란 무엇인가?
프로그램
- 프로그램: 계산의 순서를 나타낸 것
- 실체: 메모리 위에 존재하는, 계산 방법을 지시하는 숫자의 열
- 노이만형 컴퓨터
- 프로그램에 따라 계산하는 커퓨터
- 메모리에 수납되어 있는 명령을 꺼내 순서대로 실행
- 다른 형식도 존재하지만, 노이만형 컴퓨터가 현재는 주류
- ex:
A + B - C
라는 연산- A와 B를 더함
add A, B -> D
- 1번의 결과에서 C를 뺌
sub D, C -> E
-
이를 변환 규칙 표로 작성하면
意味 add sub A B C D E 数字 0 1 2 3 4 5 6 0, 2, 3, 5
라는 수열은A와 B를 add한 값이 D
라는 뜻- 즉 앞에서부터 차례대로 숫자를 읽어, 변환 규칙에 따라 계산함
- A와 B를 더함
프로그램의 표현과 용어
- 이진수 バイナリ
0, 2, 3, 5
와 같이 계산 방법을 나타내는 수열- 컴퓨터가 직접 이해할 수 있는 것은 이진수뿐
- 어셈블리어 アセンブリ言語
add A, B -> D
와 같이 이진수와 1:1 대응하여 기본적으로 상호 변환이 가능- 이진수를 인간이 읽기 쉽도록 한 것
- 기계어
- 상기의 이진수 또는 어셈블리 언어로 표현된 프로그램
- 명령
- 컴퓨터가 해석 가능한 프로그램 내 계산 순서의 최소 단위
0, 2, 3, 5
,add A, B -> D
- 연산자(opcode) オプコード
- 명령으로 어떤 연산을 할 것인지 지정하는 부분
0, 2, 3, 5
의 0,add A, B -> D
의 add 부분
- 피연산자(operand) オペランド
- 계산의 대상이 되는 부분
0, 2, 3, 5
의 2, 3, 5,add A, B -> D
의 A, B -> D 부분- 입력이 되는 부분을 소스 ソース, 출력이 되는 부분을 디스티네이션 ディスティネーション이라고 부름
명령 세트 아키텍처
- 이진수의 숫자와 실제로 수행할 계산의 규칙을 정한 것
- 어떤 연산을 지원할 것인가? → “add, sub …”
- 이진수의 각 숫자에 어떤 의미를 부여할 것인가? → “0이면 add”
- 숫자의 순서가 가지는 의미 → “처음 한 자리는 연산 종류, 그 다음은 입력 값…”
- 각 숫자에 몇 자릿수(비트)를 할당할 것인가? → “10진수로 한 자리씩”
- 규칙은 컴퓨터(CPU)의 종류에 따라 다름
- 이 룰을 명령 세트 아키텍처라고 부름
- 프로그램의 호환성이란 상기의 룰이 동일한 것을 의미함
- CPU 이외에도 OS 등의 요소도 호환성에 영향을 미치긴 하나, 가장 중요한 것은 CPU
단순한 CPU의 구조와 동작
컴퓨터
- 컴퓨터란 ‘프로그램 = 명령의 열 = 숫자의 열’에 따라 계산하는 기계
- 컴퓨터의 구성 요소
- CPU
- 연산기
- 레지스터
- PC
- 메모리
- CPU
메모리
- 메모리란 명령열과 계산할 데이터를 저장함
- 단일의 거대한 배열
- C언어의 배열은, 이를 잘개 쪼개어 유저에게 보여 주고 있음
- 숫자가 저장되는 작은 상자가 여러 개 나열된 이미지
- 어드레스: 상자의 주소, 번호
- 데이터: 상자 안에 든 내용물의 숫자
CPU
- 컴퓨터의 중심이 되는 부분
- 메모리로부터 명령을 읽어 와 계산함
- 구성 요소
- 연산기(FU, Functional Unit)
- 가산기나 AND 연산기 등
- 지시받은 종류의 연산을 수행함
- 레지스터, 파일
- 메모리와 비슷한 기능을 수행, 데이터를 기억함
- 위치를 지정해 읽거나 쓸 수 있음
- CPU의 계산은 레지스터 위에서 이루어짐
- 메모리와 비슷한 기능을 수행, 데이터를 기억함
- PC(Program Counter)
- 현재 실행하고 있는 명령의 어드레스를 기억하는 곳
- 연산기(FU, Functional Unit)
- CPU의 명령 처리 단계
- 초기 상태
- PC의 어드레스는 0을 가리키고 있음
- 레지스터의 초기 값은 1, 2, 3…
- 메모리의 0번지에는
0235(add A, B -> D)
, 1번지에는1546(sub D, C -> E)
1. 명령 읽어 오기(fetch) - PC가 가리키고 있는 어드레스의 메모리의 번지를 읽음
- 해당 메모리의 내용인
0235
를 가져 옴 - CPU 내에 저장함 2. 명령의 해석(decode)
0235
의 의미를 해석함0
: add2
: 레지스터A
를 읽음3
: 레지스터B
를 읽음5
: 결과를 레지스터D
에 씀- 레지스터 읽어 오기
- 디코드의 결과에 따라 A와 B를 레지스터로부터 읽어 옴
- 그 내용인
1
과2
를 가져 옴 0235
는 레지스터를 읽어 올 장소를 가리키고 있음에 주의할 것 4. 연산 실행- 연산기(FU)로 더하기 연산을 실행 5. 레지스터에 결과를 저장
D
에 결과인3
을 저장 6. 다음 명령으로- PC에 1을 더한 후, 위의 과정을 반복
- 초기 상태
- 기타 명령
- 곱셈, 나눗셈, 논리 연산 등: 덧셈, 뺄셈과 동일한 방식으로 작동함
- 메모리에의 읽기 및 쓰기
- 로드 명령: 메모리로부터 데이터를 읽어 오는 것, ld: load
ld(A) -> D
: A의 내용이 가리키고 있는 메모리의 주소를 D에 읽어 들임, 이때 A는 C언어의*A
와 같음
- 스토어 명령: 메모리에 데이터를 써 넣는 것, st: store
st D -> (A)
: A의 내용이 가리키고 있는 메모리의 주소에 D(의 결과)를 써 넣음
- 로드 명령: 메모리로부터 데이터를 읽어 오는 것, ld: load
- 제어 명령
- PC에 1을 더하는 대신, 임의의 값을 써 넣는 것
- 점프 명령: 프로그램 내 임의의 장소로 이동함
j N
: PC에 N의 값을 써 넣어, 다음 실행에는 어드레스 N에 있는 명령이 실행됨
- 분기 명령: 조건에 따라 프로그램 내 임의의 장소로 이동함
b A < B, N
: 레지스터를 2개 읽어,A < B
라면 N으로 이동함
- 레지스터 값의 변경(즉치 即値 immediate value)
- 다른 명령어는 명령어 안의 숫자를 레지스터의 위치로 해석하지만, 즉치 명령은 명령어 안의 숫자를 직접 레지스터에 기록함
- 레지스터의 초기값 설정 등의 목적으로 사용됨
li 2 -> D
: 2라는 값을 레지스터 D에 써 넣음
- 메모리와 레지스터
- 레지스터는 필수적인 존재는 아님
- 명령어에 나오는 레지스터 지정(A, B, C…)을 메모리의 주소라고 생각하면 됨
- 그러나 메모리는 용량은 크지만 속도가 느리기 때문에, 용량은 작으나 고속인 레지스터를 준비하여 한 번 사용한 값을 레지스터에 저장하여 두 번째 접근부터는 고속으로 접근할 수 있도록 함(한 번 사용한 데이터는 다시 사용할 가능성이 높기 때문)
C언어와 기계어의 대응
- 컴파일러의 처리
- C언어로 작성한 명령문을 그에 대응하는 기계어로 변환하는 것
- 기본적으로는 패턴 매칭(고수준 언어의 문장을 분석하고 내부 표현으로 변환할 때, 특정 형태의 코드 구조를 인식해 대응하는 처리나 명령을 선택하는 과정)
C언어의 반복문으로 보는 기계어 대응
// 일반적 반복문
for (int i = 0; i < 10; i++) {
// 루프 본문
}
// goto문을 이용해 재작성한 반복문
i = 0; // 초기화
LABEL: // 루프 시작 지점
i = i + 1; // 카운터 갱신
if (i < 10) // 조건 검사
goto LABEL; // 조건을 만족하면 다시 LABEL로 이동
- 준비 단계: 변수와 명령의 할당
- 가상의 메모리를 상정
- 메모리의 하나의 요소가 4바이트라고 상정
- 각 요소에는 어드레스가 존재
- 하나의 요소가 4바이트인 거대한 배열이 하나 존재한다고 생각해도 좋음
- 변수와 명령을 이 메모리 위에 배치함
- *변수
i
는 메모리의 0x0f4 번지에 할당되어 있음- 전역 변수라고 생각할 것
- 변수는 하나당 4바이트라고 가정한다
- 주소는 임의로 정한 것이며, 주소 숫자 자체에는 특별한 의미 없음
- 명령어는 0x400 번지부터 시작한다고 가정
- 즉, 메모리 상에서 변수와 명령어가 따로 떨어진 공간에 저장됨
- 명령어도 하나당 4바이트로 취급한다
- 가상의 메모리를 상정
- 1행째(
i = 0;
): 변수i
에0
을 대입
// 레지스터 A에 0을 넣는다
0x400: li 0 → A
// B에 0x0f4 (변수 i의 주소)를 넣는다
0x404: li 0x0f4 → B
// A의 값을 (B)가 가리키는 메모리에 저장한다
0x408: st A → (B)
- 전역 변수의 갱신은 기본적으로 변수의 주소를
li
명령으로 읽고, 해당 주소에store
명령으로 값을 저장하는 방식으로 이루어짐- 저장할 값(0)을 A 레지스터에 준비
- 저장할 위치의 주소(0x0f4)를 B 레지스터에 담음
st
명령을 통해 A의 값을 B가 가리키는 메모리에 저장
- 2행째(
LABLE:
): 3행째부터의 명령은 0x40c부터 시작하므로, LABEL = 0x40c 라고 기억해 둠
- 3행째(
i = i + 1;
): 변수 i의 값을 1 증가시키는 연산
// B에 0x0f4 (i의 주소)를 넣는다
0x40C: li 0x0f4 → B
// (B)에 있는 값을 A에 읽어 온다
0x410: ld (B) → A
// 1을 더한다
0x414: add A,1 → A
// A의 값을 (B)에 기록한다
0x418: st A → (B)
- 4~5행째(
if (i < 10); goto LABEL;
): 루프의 계속 판정과 점프
// B에 10을 읽어 온다
li 10 → B
// 직전의 덧셈 결과가 남아 있는 A와 비교해서, 조건이 성립하면 LABEL(0x40C)로 점프
b A < B, 0x40C
// 조건이 성립하지 않으면, 이후의 명령으로 진행
- 전체 연산을 정리하면 아래와 같음
// 1: i = 0;
0x400: li 0 → A // 레지스터 A에 0을 넣는다
0x404: li 0x0f4 → B // B에 0x0f4 (변수 i의 주소)를 넣는다
0x408: st A → (B) // A의 값을 (B)에 저장 (= i를 0으로 초기화)
// 2: LABEL:
// 3: i = i + 1;
0x40C: li 0x0f4 → B // B에 0x0f4 (i의 주소)를 넣는다
0x410: ld (B) → A // (B)에서 값을 읽어 A에 저장 (= i 값을 읽는다)
0x414: add A,1 → A // A에 1을 더한다 (= i + 1)
0x418: st A → (B) // A의 값을 (B)에 저장 (= i 값을 업데이트)
// 4: if (i < 10)
// 5: goto LABEL;
0x41C: li 10 → B // B에 10을 넣는다
0x420: b A < B, 0x40C // A가 B보다 작으면 0x40C(LABEL)로 점프 (= i < 10일 경우 반복)
C언어로의 변환(컴파일러)
- 기본적으로는 한 문장씩 기계어로 치환해 나감
- 실제로는 위의 결과보다 더 최적화됨
- 예시로, 변수
i
를 매번 메모리에서 읽고 쓰는 동작을 생략함 - 단, 디버깅용으로 컴파일된 코드는 앞의 예시와 비슷함(디버거에서 한 줄씩 실행해 보기 위해서는 원래 문장과 1:1로 대응되는 것이 더 보기 편하기 때문)
- 변수, 배열, 구조체 접근
- 변수의 등장
x
⇒*(&x)
- 배열
a[i]
⇒*(a + i)
- 구조체
s.m
⇒*(&s + offset)
sp->m
⇒*(sp + offset)
- 변수의 등장
if ~ else
,for
,while
,do ~ while
,switch ~ break
,continue
,goto
구문은 기본적으로 모두 if ~ goto를 이용해 변환 가능return
은 앞에서 설명한 내용만으로는 구현이 불가능하고, 점프할 떄 PC가 돌아갈 어드레스를 저장하는 명령이 따로 필요함