Introduction to Computer System
컴퓨터시스템개론 중간고사 정리
Ch1. Introduction
추상화란? : 세부 작동 원리를 블랙박스로 두고 기능만 사용하는 것
Ch2. Data Representation
비트표현 및 연산
컴퓨터는 모든 정보를 비트로 표현한다
레지스터: CPU에 있는 작은 저장공간으로, 연산에 사용된다. 속도가 빠르다
컴퓨터는 2진법(1비트)를 기본으로, 8비트로 이루어진 바이트를 사용한다
불연산(bitwise)
불연산으론 &, |, ~, ^가 있으며, 단항연산자인 ~을 제외하곤 교환법칙과 결합법칙이 성립한다.
불연산을 통해 집합 또한 표현이 가능하다(이를 비트마스킹이라 한다.)
논리연산(logical)
논리연산으론 &&, ||, !가 있다.
0은 False, 나머지는 True로 인식한다. 논리연산의 결괏값은 0 혹은 1밖에 없으며, 처음부터 순서대로 봤을 때 더 판단 필요가 없으면 일찍 종료(Early Termination)한다.
정수와 비트
비트를 이용한 정수표현
Sign-Magnitude representation: 첫 비트를 부호로, 나머지로 수를 표현했다. 하지만 연산 어려움이 크다
Two’s Complement: 첫 비트가 의미하는 값을 음수로 씌우고 표현한다. 이때 첫 비트(가장 큰 자리수 비트)를 MSB(Most Significant Bit)라 한다.
Unsigned는 부호가 없는 정수로, Signed와 서로 변환이 가능하다. 다만 해석이 다르게 된다.
둘을 혼동하여 조건식을 쓸 경우, Unsigned로 캐스팅이 되어 의도치 않은 결과값이 나올 수 있다.
비트를 이용한 연산
비트의 합연산 \(Add(u, v) = (u + v + 2^w) \text{ mod } 2^w\)로 표현한다. unsigned의 경우에도 같다.
비트의 차연산 \(Sub(u, v) = (u + (\sim v + 1) + 2^w) \text{ mod } 2^w\) 로 표현한다.
Zero Extention: Unsigned Integer에서 비트를 확장할때, 앞에 0으로 채운다
Sign Extention: Signed Integer에서 비트를 확장할때, 앞에 MSB로 채운다
Truncation: 앞에 있는 비트를 지운다.
Left Shifting: 왼쪽으로 비트를 밀고 빈 칸은 0으로 채운다. \(x * 2^y\)와 동일함
Right Shifting: 오른쪽으로 비트를 밀고 빈 칸은 0(logical), 또는 MSB(Arithmetic)으로 채운다. \(x / 2^y\) 와 동일함
프로그램은 주소를 이용하여 메모리에 접근한다.
각 실행중인 프로그램은 보호된 접근공간이 있다. 다른 프로그램은 접근 불가.
Machine Word
CPU가 효율적으로 다룰 수 있는 데이터 크기를 의미한다. 보통 레지스터/주소 크기, CPU와 메모리 사이 전달 크기이다.
과거엔 32-bit(4byte)였으나 요즘에는 64-bit 주소공간을 사용를 한다. 그러나 메모리 효율성을 위해 \(2^{48} - 1\)까지만 사용한다.
Byte Ordering
Big Endian: 중요한 비트를 낮은 자리에(순서대로)배치
Little Endian: 중요한 비트를 높은 자리에(역순으로)배치
String 표현
char의 배열로 표현이 된다. 각 char은 ASCII코드로 인코딩된다. 마지막은 \0으로 끝나야만 한다.
배열이기에 Ordering에 영향을 받지 않는다.
Ch3. Assembly Introduction
왜배우나?
컴퓨터 내부 작동구조를 보기위해서 배운다.
CPU/Main Memory: 프로그램 작동시키기 위한 필수요소, 어셈블리에 의해 컨트롤됨
어떻게 작동하는가?
- CPU의 Program Counter(이하 PC)에서 프로그램의 코드 위치를 읽고, 작업을 읽어온다.
- 읽은 작업을 바탕으로 CPU의 레지스터는 일한다.(어셈블리어는 기계어랑 1ㄷ1 매핑이 된다.)
- 실행이 끝났다면 PC는 다음 코드 위치를 읽으러 간다.
Architecture란?
ISA와 microarchitecture 두 의미로 나누어진다. 우리는 보통 ISA를 의미한다.
ISA: Instruction Set Architecture이며, CPU를 만들 때 따라야할 규격을 의미한다.
Microarchitecture: ISA를 하드웨어 레벨에서 작용하는 특별한 방법?
ISA는 x86->IA64 vs x86-64 -> x86-64로 변화해왔다.
우리는 x86-64에 대하여 집중할 것이다.
C코드와 어셈블리어
C코드를 컴파일하면, 우선 어셈블리 코드로 변환 된 뒤에 기계어로 변환하고 이를 링킹하여 실행파일을 만든다
추상화의 정도
- C Programmer는 언어 모델의 이해가 필수적이다.
- Compiler Writer는 ISA에 대한 심도깊은 이해가 필수적이다.
- CPU Designer는 MicroArchitecture에 대한 심도깊은 이해가 필수적이다.
Ch4. Assembly: Basic Instruction
Registers in x86-64
몇 개의 레지스터는 특정 작업을 위해서만 사용된다.
%rip: instruction pointer(program counter), %rsp: stack pointer가 대표적인 예시다.
각 레지스터는 8byte의 크기를 지니고 있으며, 접근은 각 명칭에 따라 원하는 비트만큼 접근 가능하다.
Ex: rax(8byte) -> eax(4byte) -> ax(2byte) -> ah, al(1byte)
Calling Convention
- 함수의 인자 순서대로 ,%rdi, %rsi, %rdx….순서로 인자가 배정된다
- return값은 마지막에 rax에 저장된다.
Data Move Instruction: Mov
Mov (자료) (위치)로 사용한다.
뒤에 접미사에 따라 몇바이트를 옮기는지 결정 가능하다(b, w, l, q).
분명하면 사용 안해도 된다.
Operand types(주소 혹은 값)
- Immediate: 상수값을 의미한다. $300, $0x18 등으로 표현한다.
- Register: 레지스터명을 의미한다
- Memory: 주소를 의미하며, 레지스터에 괄호를 씌우거나 $없이 16진법으로 표현한다.
레지스터에 부분적으로 접근하여 값을 초기화 가능하다.
단, 4byte레지스터로 접근 시 윗비트를 전부 0으로 초기화한다.
이 경우, z, s 접두사를 붙여 앞 비트를 어떻게 채울 지 지정 가능하다.
복잡한 Mov 또한 사용 가능하다.
mov 0x20(%rbx, %rcx, 4), %rax
이는 8바이트 0x20 + %rbx + %rcx * 4로 인식된다. 상수는 Scale Factor로, 1, 2, 4, 8이 사용 가능하다.
간단한 어셈블리어들
add(+=), sub(-=), imul(*=), inc(++), dec(–), neg(-)
이를 응용하여 mov와 같은 효과를 낼 수도 있다. 다만 mov처럼 윗 바이트 초기화를 조심해야한다.
shr(»=, Logical), sar(»=, Archmetic), shl(«=), xor(^=), and(&=), or(|=), not(~=)
쉬프트연산에서 인자 1개일 시 1회 쉬프트라 생각하자.
lea = mov와 유사하나 mov는 주소에 접근까지 하고 lea는 단지 주소를 계산하기만 한다.
Ch5. Assembly: Control Instruction
Flag Registers
특별한 레지스터 추가(%ZF-Zero Flag, %SF-Sign Flag, %CF-Unsigned Overflow Flag, %OF-Signed Overflow Flag)
이 레지스터들의 크기는 1이고, 특정한 연산(mov, lea제외)을 하면 자동으로 업데이트된다.
연산이 끝난 직후 그 연산의 결과를 이 레지스터들에 저장한다.
Condition Operators
- 특정 연산(sub, cmp, and, test….)등으로 Flag를 업데이트한다.
- jump instruction을 이용하여 코드의 흐름을 제어한다. jump instruction은 flag register의 상테에 의하여 결정된다.
sub랑 and는 목적지 register를 업데이트하나 cmp와 test는 업데이트하지 않는다.
cmovx와 setx?????
Translation of loop statements into assembly
loop문은 goto와 if로 짜여져있다고 생각하자.
while은 goto-if로 변환 가능하고 for은 while로 변환 가능하다.
- 변수 선언(in for) 후 조건판별 위치로 이동
- 조건 판별시 충족한다면 1.의 위치 한칸 아래(body)로 이동함
Translation of switch statement into assembly
컴파일러는 Switch문을 읽고 난 뒤에 Jump Table을 제작한다.(항상은 아님, 효과적일때만)
- default case를 계산한다.
- jmp를 이용해 점프 테이블로 이동한다.
- 조건과 일치시에는 해당 위치로 이동 후 ret한다.
- fall-through일 경우 스위치문의 연속적 배치로 인하여 다음 문장까지 실행하고 ret한다.
- multiple label일 경우 그냥 최하단의 구문으로 이동한다. Jump Table은 연속적인 배치를 가지고 있다. 그 중에 해당 케이스가 없는 경우 default로 리턴위치기 잡힌다
Ch6. Assembly: Function
Understanding Basic Memories
메모리는 바이트로 이루어진 큰 배열이다. 각기 다른 구역(stack, data, code)로 나누어진다.(모두 X)
각 구역은 다른 목적으로 사용된다.
- Code Region: machine Instruction를 저장
- Data Region: 전역변수 저장
- Stack Region: 실행되는 함수를 위해 사용됨. Top에 차곡차곡 쌓이는 형식 진짜 왜인진 모르겠는데 전통적으로 높은걸 Bottom 낮은걸 Top으로 설명한다. 심지어 주소도 Bottom이 High Address다.
rsp포인터는 Top을 가르키고 있다.
Push
Push Src = Src의 의미를 계산하고 스택 포인터를 조정한다(주소를 낮춤, 8바이트 단위), 새로 조정된 스택 포인터가 가리키는 위치에 Src값을 64비트로 저장한다.
Pop
Pop Reg = 스택 포인터의 값을 읽어와 Reg에 저장하고 스택 포인터의 값을 증가시킨다.
함수의 흐름제어
Call Dest = 스택에 돌아와야 할 주소를 Push(현재 위치) 후에 Dest로 점프한다.
ret = 스택에서 값을 pop하고 popped해서 나온 값의 위치로 jmp한다.
인자와 데이터의 전달
Calling Convention: 관습적으로 6개의 레지스터(rdi, rsi, rdx, rcx, r8, r9)를 사용함. 리턴값은 rax.
더 많은 인자의 전달은 스택을 이용함
레지스터 백업 의무
Caller는 레지스터의 값 변화를 원치 않는 한 무조건 call하기 전에 이 레지스터의 값들을 저장해놔야 한다.
Callee는 인자로 받은 레지스터들을 무조건적으로 값을 유지해야 함(Calling Convention에 해당하는 레지스터들)
메모리 관리
frame: 스택은 각 부분공간으로 쪼갤 수 있고 이를 frame이라 한다.
각 frame은 실행중인 함수의 상태(return address, local variables, callee-saved register backups etc)를 담고있다.