Univ/System Programming

[CS] Instructions(Assembly Code) - Computer System 3rd Edition by Bryant

Jay, Lee 2022. 3. 10. 16:49

Instruction

Machine Instruction은 Opcode와 Operands로 이루어져 있다. 

Opcode는 실제 오퍼레이션의 타입을 명시해놓은 것이다. 이 Opcode를 통해 메모리  사이의 데이터 교환이 레지스터에서 가능해지며 Opcode를 보고 레지스터나 메모리에서 산술연산이 이루어진다. 

Opcode에는 mov, add, jmp 같은 operation들이 있다. 

Operands는 Opcode의 피연산자이다. 여기에 in/output 데이터와 위치들의 정보가 담겨있다.

clion을 통해 코드를 disassemble한 코드의 예시

Register

레지스터에 대한 얘기를 잠깐 하고 넘어가면, 먼저 레지스터에 대한 정의는 아래와 같다.

레지스터는 컴퓨터의 프로세서 내에서 자료를 보관하는 아주 빠른 기억 장소이다. 일반적으로 현재 계산을 수행중인 값을 저장하는 데 사용된다. (출처 위키피디아)

레지스터와 메모리에도 속도 차이가 상당하다. 

CPU에서 Memory를 접근하것을 최소화 해주는 것이 Cache(캐시)이다. 이 내용은 OS 파트에서 다뤄보도록 하겠다. 

레지스터가 없다면 instruction을 수행하는데에 있어 너무나 많은 시간이 소요 된다.

a = (x + y) - (x * y)라는 식이 있다고 해보자. 

레지스터가 없다면 이 식은 x, y의 load와 덧셈후 결과를 메모리에 저장,  x, y의 load와 곱셈후 결과를 메모리에 저장, 두 개의 결과물들을 load후 뺄셈 연산 수행 후 결과를 메모리에 저장. 이렇게 총 9번의 memory access가 발생해버린다. 

반면 레지스터는 x, y를 load 후 총 결과값만 memory에 저장해주면 되므로 3번의 memory access밖에 발생하지 않는다.

CPU에서 memory를 접근하려면 memory bus 라는 pipe line을 타고 이동해야하는데,

이것을 타는것의 비용은 register에 접근하는 비용보다 매우 비싸다. 

물론 우리가 모든 memory를 register로 바꾸면 엄청 빠르겠네?라고 생각을 해도 이 register의 값의 비용은 매우 비싸다.

 

인텔 x86-64의 레지스터를 살펴보면 아래와 같다. 이 레지스터를 전부 사용하는 것은 아니고, 특수 목적 레지스터도 존재하는데 %rsp는 stack pointer이므로 함부로 사용할 수 없고, 아래 그림에는 나와있지 않지만 %rip는 PC를 가리키고, eflags는 CPU의 status register로서 동작한다. 

Data Movement

먼저 데이터를 옮기는 instruction을 살펴보면 아래와 같다.

mov src, dst

먼저 src에 올 수 있는 것은 상수, 레지스터, 메모리이다.

일반적으로 상수를 표현할때 인텔의 아키텍쳐에서는 달러표시($)를 붙인다.

$0x400, $256 이렇게 사용한다.

레지스터도 올 수 있으며, 메모리는 주소의 번지수로 오기 때문에 상수와 잘 구분을 해야한다. 

 

사용하는 예시는 아래와 같으며, src와 dst를 잘 구분해줘야 한다.

// 0x42에는 5가 저장되어 있고, %rbx에는 8이 저장되어있다.

mov $0x42, %rax
// 이 의미는 rax레지스터에 상수 0x42를 저장하겠다는 의미.
mov 0x42, %rax
// 5를 rax레지스터에 저장
mov %rbx, 0x55
// 55번 메모리 주소에 8을 저장

위의 예시는 정말 간단한 예시이고, indirect addressing mode와 displacement addressing mode, indexed addressing mode, scaled indexed addressing mode가 존재한다.

//indirect addressing mode(memory 주소를 참조)
mov (%rbx), ____
// rbx레지스터에 있는 값을 특정 장소에 저장하겠다는 것
mov _____, (%rbx)
// src의 값을 rbx레지스터에 저장된 메모리 주소에 저장하겠다는 것

// displacement addressing mode(offset을 지정)
mov 0x10(%rax), ______
// rax 레지스터가 저장하고 있는 메모리 주소 + 0x10한 주소의 값을 dst에 저장
mov _____, 0x10(%rax)
// src의 값을 rax 레지스터가 저장하고 있는 메모리 주소 + 0x10한 주소에 저장

//indexed addressing mode
mov (%rax, %rdx), _____
// rax와 rdx의 레지스터의 값을 더해서 dst에 저장
mov ______, (%rax, %rdx)
// src의 값을 rax와 rdx의 레지스터의 값을 더한 메모리 주소에 저장
mov 0x10(%rax, %rdx), _____
// 0x10과 rax, rdx레지스터의 값을 더한 결과를 dst에 저장

//scaled indexed addressing mode
mov 0x4(%rax, %rdx, 2), ______
// rdx의 값의 두배한 결과를 rax의 값, 0x4와 더한 후 dst에 저장
mov _____, 0x4(, %rdx, 4)
// src의 값을 rdx의 네배한 결과에 0x4를 더한 주소에 저장

이를 실제 사용하는 간단한 예시를 보자.

/*
0x10C에는 0X11이 저장
0X104에는 0XAB가 저장
%rax레지스터에는 0x100이 저장
%rdx레지스터에는 0x3이 저장
*/

mov $0x42, (%rax)
// 0x100=$0x42
mov 4(%rax), %rcx
// rcx레지스터에는 0xAB라는 값이 저장
mov 9(%rax, %rdx), %rcx
// rcx레지스터에는 0x11라는 값이 저장
// 주소값에 대한 연산 과정
// %rdx에 0xf000, %rcx에 0x0100의 주소값이라고 하면
0x8(%rdx)의 주소값은 0xf008
(%rdx, %rcx)의 주소값은 0xf100
(%rdx, %rcx, 4)의 주소값은 0xf400
0x80(, %rdx, 2)의 주소값은 0x1e080

따라서 아래의 그림처럼 정리를 할 수 있다.

Bytes and Instruction

명령어들에 있어서 instruction은 중요하다. 또한 그에 못지 않게 레지스터의 크기 또한 중요하다. 우리는 여러 다른 명령어를 명령어 클래스로 그룹화할 수 있다. 여기서 클래스 명령어는 동일한 연산을 수행하지만 피연산자 크기는 다르다는 것이다. 아래의  그림을 보면 알 수 있겠지만 mov 명령어의 클래스는 4가지로 나눌 수 있는데, 이들은 각각 1, 2, 4, 8바이트와 같은 다양한 크기의 데이터에서 작동한다는 점에서 주로 다르다. 여기서 레지스터의 크기는 명령어의 마지막 문자('b', 'w', ' l' 또는 'q')로 결정이 되며, 대부분의 경우 mov 명령어는 대상 피연산자가 나타내는 특정 레지스터 바이트 또는 메모리 위치만 업데이트를 한다. 

 

 

 

 

Reference

Randal E. Bryant, & David R. O’Hallaron. (2016). Computer Systems A Programmer’s Perspective Third edition. Carnegie Mellon University: Pearson.