LOAD & STORE 명령어 디자인


LOAD & STORE 명령어의 필요성


명령어의 제한

사칙연산의 피 연산자는 숫자 또는 레지스터.

연산결과는 레지스터에 저장.

레지스터를 통해서 모든 연산을 진행 해야 한다.




사칙연산 명령어 구성

 <-예약->

 <-  연산자  ->

 <-  저장소  ->

<-    피연산자1    ->

<-     피연산자2   ->

                

                      ADD                  r2                        r1                            7


저장소 : 레지스터

피연사자1,2:레지스터 or 숫자


챕터①에서 명령어의 구성을 살펴보면  연산결과를 레지스터에만 저장 할 수있도록 제한이 되었다. CPU로 오가는 데이터는 모두 레지스터를 통하기 때문에 일단 레지스터에 저장을 해야한다. 

그럼 메모리를 목표로 할때 레지스터로 통해 나간다 치고 

명령어에 메모리주소를 넣으면 좋지 않을까? 

복잡한 명령어의 CISK에서는 가능하지만 RISK에서는 성능향상을 위해 어쩔수없다.

 


문제


 int a=10; //0x10 번지 할당

int b=20; //0x20 번지 할당

int c=0;  //0x3 0번지 할등


c=a+b;



그러면 !!

왼쪽과같이 메모리에 있는 연산자를 연산하려고한다. 하지만 연산자구성을 보면 피연산자는 레지스터 또는 숫자만 가능하도록 제한하였다.

그래서 메모리에 있는 값을 레지스터로 불러들이고 반대로 메모리에 연산결과를 저장하는 것을 위해

LOAD & STORE 명령어 가 필요한 것이다.








LOAD & STORE 명령어 디자인


LOAD 명령어


 <-예약->

 <-  LOAD  ->

 <destination>

<-                    source                   ->

  

 1

           

                                                                                          

destination : 데이터를 저장할 레지스터 정보        LOAD r1, 0x20

source : 데이터를 읽어올 메모리의 주소 정보



STORE 명령어


 <-예약->

 <-  STORE  ->

 <destination>

<-                    source                   ->

  

 1

1

           

 

source : 데이터를 읽어올 레지스터 정보             STORE r1, ox20

destination : 데이터를 저장할 메모리의 주소 정보


※명령어 2진코드 110, 111은 사칙연산에 겹치지않게 만든것이다.


문제해결

int a=10; //0x10 번지 할당

int b=20; //0x20 번지 할당

int c=0;  //0x3 0번지 할등

c=a+b;

LOAD r1 , 0x10

LOAD r2 , 0x20

ADD r3, r1, r2

STORE r3, 0x30

 r0

 메모리

 r1

←a    10                                           0x10

 r2

 

 r3        

↖b    20                                           0x20

 r4       ir

 

 r5      sp

 
 r6      lr   c    30                                           0x30
  r7     pc 

ㄴㅇㅁㄴㅇㅁㄴㅇㅁㄴㅇ


 c=a+b를 이러한 명령어 조합으로 만들어 볼수있다. 

메모리에 있는 값을 LOAD명령어로 불러들여온후 ADD 명령어로 연산을 하면 사칙연산에서의 피연사자 문제를 해결 할 수있다.




컴퓨터 구조의 접근방법

컴퓨터를 디자인 하자


프로그래머 관점

컴퓨터 구조를 잘 아느 프로그래머도 컴퓨터 디자인에 참여

컴퓨터 디자인은 레지스터와 명령어 디자인

CPU 자체에 대한 표준은 없기 때문에 CPU의 종류는 무궁무진하다.

예를 들어 GPU 도 그래픽처리에 제한된 CPU라 할 수 있다.

하드웨어 전문가 뿐만 아니라 프로그래머도 CPU디자인에 참여한다. 디자인을 할 때 로직,하드웨어,알고리즘,인터페이스 전문가 들이 참여하게되는데 그중에 프로그래머도 포함 될 수있다. 이 과정에서 프로그래머는 주로 레지스터와 명령어 디자인을 맞는다. 



레지스터 디자인의 핵심   

레지스터는 몇 비트로 구성할 것인가?

몇 개 정도로 레지스터를 구성할 것인가?

레지스터 각각을 무슨 용도로 사용할 것인가?

레지스터는 컴퓨터 시스템과 같은 크기로 구성하는것이 보편적이다.

n비트 시스템에서 명령어의 크기가 n비트이기때문에 레지스터에 한번에 데이터를 저장하기위해 레지스터를 같은 크기로 구성하는 것이다.
레지스터의 수는 많으면 많으 수로 좋다.

레지스터는 특별한 용도를 갖고 있는 저장장치이다.

램이나 하드디스크는 범용적으로 사용하지만 레지스터는 용도가있는 저장장치이기때문  용도를 정하고 그로인해 명령어가 단순해지고 속도도 증가하게 된다.




한번 레지스터를 디자인해 보자.


   r0

 






   ir : instruction register

   sp : stack point

   lr : link register

   pc : program 

   r1
   r2
   r3

   r4        ir

   r5        sp

   r6        lr

   r7        pc


16비트 크기의 레지스터 8개를  구성했다.

각가 r0 ~ r7 이름을 붙이고 r4 부터 r7까지는 용도를 지정하였다. 

r0 ~ r3 는 범용레지스터로 연산을 위해 필요한 레지스터라 생각하자.


밑에 계속....




명령어 구조 및 명령어 디자인


명령어의 기본 모델

16비트 명령어


사칙연산 명령어 구성

 <-예약->

 <-  연산자  ->

 <-  저장소  ->

<-    피연산자1     ->

<-     피연산자2    ->

                               

                      ADD                  r2                        r1                            7

저장소 : 레지스터

피연사자1,2:레지스터 or 숫자



연산의 의미 

심볼 

2진 코드 

 

레지스터 심볼 

2진 코드 

덧셈 

ADD 

001 

 

r0 

000 

뺄셈 

SUB

010

 

 r1

001

 곱셈

 MUL

011

 

 r2

010

 나눗셈

DIV

100

   r3

011

       

 r4 , ir

100

         r5 , sp

101

         r6 , lr

110

         r7 , pc

111



레지스터 디자인 쪽에서 16비트 레지스터를 디자인 했다. 시스템과 레지스터의 크기가 같아야 이상적이라 했는데 명령어의 크도 같아야 한다. 그렇기에 16비트 크기의 명령어를 디자인하였다.

명령어 하면 단순한 정보를 담고있을 거라 생각 할 수 있지만 명려어 안에는 

연산자, 저장소, 피연산자 같은 많은 정보를 담고 있다.

위에 디자인된 명령어를 보자. 각가 연산자에 3비트 저장소에 3비트 피연산자에 4비트를 할당 하였다. 

※명령어에 따라 피연산자가 1개가 존재 할 수도 있고 그럴때에는 구조가 달라진다.

저장소는 레지스터만 오도록 하였는데 연산결과는 일다 레지스터에 저장한다.

피연산자는 레지스터 이거나 숫자 가 올 수 있는데 계속....


피연산자에 레지스터나 숫자 모두 올수있는데 만약 0001일때 이것이 숫자인지 레지스터인지를 알 수 없다 그렇기에 맨앞 비트에 따라 레지스터나 숫자인가 하는 구분하는 약속을 하여야한다. 이런식을 제한을 하지않으면 명령어에 낭비가 오고 명령어가 복잡해진다.


명령어는 왜 단순하게 만들어야 하는가?

위와같이 단순한 구조를 가리켜 RISK 라 하고 이와 반대대는 구조로 CISK 가 있다.

CISK 구조는 명령어가 복잡하여 명령어를 다양하게 조합 원하는것을 대부분 반영할 수 있는 구조이다.

RISK 구조는  CISK의 복잡한 명령어를 단순화 시킨것인데 이로인해 제한사항이 많이 생긴다. 명령어가 단순해서 표현을 하는데 있어 CISK에 비해 더 많은 명령어가 필요하게 될 수 있다. 그럼에도 불구하고 오늘날 대부분의 CPU는 RISK 이다.

이는 속도를 향상시키는데 있어서 RISK가 고성능컴퓨터에 적합하다.

명령어를 처리하는 과정을 1장에서 보았듯이 Fetch,Decode,execution 3단계를 거치는데 각각의 단계를 처리하는데 1클럭이 걸린다고 가정한다면 5개의 명령어를 처리한다고 보면 단순계산으로 15클럭이 필요할 것이다. 하지만 각처리과정의 로직이 별도이기때문에 동시에 처리하여도 된다. 

F D E

   F D E

      F D E

이런식으로 첫번째 명령어를 Decode 할때 두번째 명령어를 Fetch 하는 식으로 동시에 진행하게 되고 명령 5개를 실행하는데 있어 7클럭이 필요하게 된다.        CISK 보다 적은 클럭으로 명령어를 처리 할 수 있기에 고성능 컴퓨터에서 RISK구조를 사용하는 것이다.











64비트 기반 프로그래밍

64비트 기반 프로그래밍

64비트 시스템을 고려한 프로그래밍으로 자료형에 대해서 고려해야 한다.


LLP64 vs LP64

32비트 시스템과의 호환성을 중시한 모델


운영체제 

모델 

char 

short 

int 

long 

포인터 

Windows 

LLP64  

1b yte 

2 byte 

4 byte 

4 byte 

8 byte 

UNIX 

LP64 

1 byte 

2 byte 

4 byte 

8 byte 

   8 byte    



Windows 에서는 LLP64 모델을, UNIX 계열에서는 LP64 모델을 채택하고있는데

32비트 시스템과의 호환성을 고려하여 기존과의 차이는 포인터에서만 볼 수있다.



64비트와 32비트 공존의 문제점

데이터 손실의 문제    


#include<stdio.h>

int main(void)

{

int arr[10]={0,};

int arrVal=(int)arr;

printf("pointer : %d \n ",arrVal);

return 0;

}


위 예제를 보면 arr이라는 배열의 주소값을 int형으로 강제형변환을 통하여 출력하려한다. 32비트 시스템이라면 포인터가 4byte이기때문에 문제가 발생하지 않을 것이다.

하지만 64비트 시스템이라면 arr 이 8byte 이고 int형은 4byte 이기때문에 문제가 발생 할수 있다. 

※64비트 시스템에서는 포인터를 기본자료형으로 변환하지말자!



Windows 스타일 자료형

Polymorphic 자료형

#if define(_WN64)

typedef __int64 LONG_PTR;

typedef unsigned __int64 ULONG_PTR;

typedef __int64 INT_PTR;    

typedef unsigned __int64 UNIT_PTR;


#else 

typedef long LONG_PTR;

typedef unsigned long ULONG_PTR;

typedef int INT_PTR;

typedef unsigned int UINT_PTR;


#endif


Polymorphic 자료형은   _WIN64 를 정의 했을때 다형적 자료형이 정의된다.

하나만 예로 들어  UNIT_PTR 은 64비트일때 unsigned __int64로  해석되고

32비트일때는 그냥  unsigned int로 해석된다.

PTR이 들어가서 포인터형으로 오해 할 수 있지 Polymorphic 자료형은 포인터 타입이 아니다.

PTR이 들어간 이유는 포인터 연산을 활용하기 위한 자료형일 뿐이다.

밑의 예제를 보자.




예제3-1]

UINT CalDistance(UINT a, UINT b)

{

return a-b;

}


int _tmain(void)

{

INT val1=10;

INT val2=20;

 _tprintf(_T("Position %d, %d \n"),

(UINT)&val1, (UINT)&val2);

_tprintf(_T("distance : %d \n"),

CalDistance((UINT)&val1,(UINT)&val2)

 

);

return 0;


}


예제를 보면 val1과 val2의 거리를 구하는 예제이다.

하지만 UINT 자료형은 4byte이다. 32비트시스템이면 몰라도 64비트시스템에서는 정상적으로 안될 수 있다.

이럴때 UINT 대신 UINT_PTR을 쓰면 32비트 일 때는 unsigned int 로

  64비트 일 때는  unsigned __int64로 

해석되어 문제가 발생하지 않고 메로리 낭비도 줄일 수 있다.

#if define(_WIN64)

typedef unsigned __int64 UNIT_PTR;

#else

typedef unsigned int UINT_PTR;

#endif



GetLastError 함수와 에러코드


함수호줄의 성공여부 확인의 기본

 GetLastError 함수 호출

windows시스템에서 오류가 발생 할 경우 전역 메모리공간에 오류의 이유가 저장이 된다. 그래서 여기서 가져와서 해석하면되는데 이 공간의 접근을 도와주는 함수가 GetLastError 함수이다.



Constant / value 

Description 

ERROR_ARITHMETIC_OVERFLOW
534 

 Arithmetic result exceeded 32 bits.

 ERROR_PIPE_CONNECTED

535

 There is a process on other end of the pipe.

 ERROR_PIPE_LISTENING
536

Waiting for a process to open the other end of the  pipe. 

ERROR_EA_ACCESS_DENIED

994 

 Access to the extended attribute was denied.

 ERROR_OPERATION_ABORTED

995

 The I/O operation has been aborted because of either a thread exit or an application request.

 ERROR_IO_INCOMPLETE
996

 Overlapped I/O event is not in a signaled state.

ERROR_IO_PENDING

997 

 Overlapped I/O operation is in progress.

 ERROR_NOACCESS

998

Invalid access to memory location. 

 ERROR_SWAPERROR

999

Error performing inpage operation. 


함수에서 반환되는 숫자로 오류를 알 수 있다.

MSDN에 가보면 더 많은 오류설명을 볼 수 있다.

실제 프로젝트에서 많이 쓰인다.




예제3-3]


int _tmain(void)

{

HANDLE hFile=

CreateFile(   //Windows system 함수.

_T("ABC.DAT"),GENERIC_READ,FILE_SHARE_READ,

NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,

NULL);

if(hFile==INVALID_HANDLE_VALUE)

{

_tprintf(_T("error code: %d\n"), GetLastError());


return 0;

}


return 0;                                           실행결과

}                                                     Error code: 2


CreateFile함수는 윈도우시스템함수이므로 크게 신경쓰지말자.

ABC.DAT 파일을 열려고 하는데 이런파일이 없다.  

그래서 오류가 발생했다는것을 알고 GetLastError 함수를 호출하면 에러 코드를 얻을 수 있다.


GetLastError 함수는 에러의 발생 이유를 확인하는 함수이지 에러가 발생했는지를 확인하는 함수는 아니다.

에러가 발생하면 그 원인이 전역공간에 저장된다고 하였는데 다른 함수가 호출되면 그 값이 변할 수 있다. 그렇기 때문에

에러가 발생이 됐는지 확인 이후 어느 함수보다 GetLastError 함수가 먼저 호출해야 정확한 에러이유를 정확히 알 수있다.



+ Recent posts