SP(stack pointer) 레지스터
-스택에 데이터를 쌓거나 반환하기위해서는 내가 현재 어디까지 썼는가,즉 얼마나 스택을 쌓았는가를 알기위한 데이터가필요하다.
sp레지스터는 다음 스택을 쌓아야하는 위치주소를 가지고있다.



fp(frame pointer) 레지스터
-함수가 끝난뒤 반환될때 어디로 반환되어야 하는지 되돌아갈 위치를 알아야한다.
fp에는 현재함수가끝난뒤에 반환될 위치주소가 저장되어있다.

위의 그림에서 보면 fct2 반환후 sp의 위치가  fct2 호출전으로 바뀌어 덮어씌우게(fct2의 지역변수가 먹었던 자원(메모리)을 사용할수있게) 만들어야한다.
하지만 되돌아갈 위치주소를 모르기때문에 fp에 반환될 위치주소가 저장되어 있는것이다.


!!! 이것만 가지고될까?

문제는 함수안에서 또함수를 부를때 발생한다.
fct1에 들어가서 fp에는 8이라는 반환될주소가 저장되있고 sp는 스택을 쌓아올리고있다.
이때 함수 fct2가불린다면,
fct2의 돌아갈위치 16을 fp에 저장하고(8이라는 정보가 날라감!!) sp는 스택을또 쌓아올릴 것이다.

이제 함수가 반환되는 과정을 보자.
fp에 저장된 반환주소로 sp를 옮긴다.
이제 fct1을 반환시켜야하는데 8이라는 값은 아까 덮여쓰여져 없어져버렸다.

우리는 함수호출이 될때 되돌아갈 주소를 fp에 저장해두었는데 이게 한개만 저장했더니 안되는것이다.

문제해결

스택을 이용한다.


 
--함수 호출될때
-sp가 8일때 함수가 호출되면 그스택(메모리)에 fp값 0을 저장해둔다. 
저장하는것은 1스택을 더하기때문에 sp는 12가된다. 
fp에는 sp - 4 (fp가 32bit라고가정)값을 저장한다.

PUSH fp
ADD fp, sp, -4

PUSH명령어 ? :스택에 피연산자를 저장한다.
알고리즘 - sp위치에 피연산자를 저장한다. sp값을 그피연산자 데이터 크기만큼 증가시킨다.
     정의된 명령어를 통해 구현할수 있다. ADD,STORE,indirect 등...을 이용하면 된다.



--함수 반환될때
-sp를 fp위치로 이동시키고 fp값을 sp위치의 데이터값으로 변경한다.

sp와 fp의변화과정

sp
fp
fct2반환전
32
20
fct2반환
20
8
fc1반환
8
0


블로그 이미지

pringlee

하드코어보단 캐주얼!

,
"사칙연산결과를 레지스터에만 저장할 수 있도록 하겠다." 라는 제약사항이 있는데,
그렇다면 메인메모리의 주소값을 통한 사칙연산은 할수없다.
따라서 메인메모리에 저장된 데이터를 레지스터로 일단 옮겨놓은후 사칙연산을 하여야한다.
[메모리 -> 레지스터 ]: LOAD  [레지스터 -> 메모리 ]: STORE

명령어 디자인


Load 명령어 디자인을 해석하면 "destination(레지스터) 에 source(메모리 주소값)에있는 데이터를 저장해라" 라는 명령어이다.
STORE도 마찬가지로 "destination(메모리)에 source(레지스터)에있는 데이터를 저장해라" 라는명령어이다.

예)

int a=10;  //0x10번지할당
int b=20; //0x20번지할당
int c=0; //0x30번지할당
c= a+b;
위와같이 프로그래밍되었다면 

LOAD r1,0x10
LOAD r2,0x20
ADD r3,r1,r2
STORE r3,0x30

위순서대로cpu는 명령을 진행할것이다.

하지만 여기서 ADD때와같은 문제점이있다.
주소값을 표현할때 해봐야 8비트(0~255)밖에 표현이 안된다는 것이다. 
cpu 데이터버스크기가 16비트인데 메모리 주소공간을 8비트만 쓸것인가? 
이에대한 답은 컴퓨터구조 -(4) 에 실어두었다.

블로그 이미지

pringlee

하드코어보단 캐주얼!

,

특수한 목적으로 사용하기위한 레지스터 r4,r5,r6,r7


레지스터 크기를 16비트라고 해보자 명령어 기본구조도 16비트로 구성하는것이 좋겠다. 2의 16승가지의 명령어를 만들수있다.
하지만 2의 16승인 65536개의 명령어는 낭비이다. 실제로 cpu연산에 있어서 몇개 안되는 명령어가 대부분의 연산을 차지한다.


이에따라 명령어 기본구조를 디자인해보자.



레지스터 r1에있는값과 숫자 7을 더해서 레지스터 r2에 저장하라
피연산자는 숫자나 레지스터가 올수있는데 피연산자가 보시다싶이 4bit면
맨앞비트는 레지스터인지 아닌지
나머지 3비트로 식별하거나(총 8개레지스터 식별가능), 숫자를 표현하는데 이는 숫자 0~7까지밖에 표현을 못한다는 단점이있다.


위와같지 실제 명령어 디자인 하는과정에서 제약사항이 등장한다.
예로 arm이나 x86계열 은 "첫번째 피연산자 위치에는 레지스터 이름이 와야한다는 제약사항이있다."
이유는 그저  명령어 구조가 간단해지고 그에따른 cpu의 종합적 측면(비용,속도 등..)에서 좋기 때문이다.


RISC ? CISC ?
-RISC (reduce ...) : 명령어 길이가 동일하고 명령어를 처리하는 과정이 일정하기 때문에 빠르다 대신 명령어 개수가 적다(위의 그림처럼 설계). 파이프라이닝을 통해 효율적이다.
 Fetch Decode Exe
F D E
   F D E
      F D E
         F D E
-CISC (complex ...) : 명령어 개수가 많고 다양하다.

CISC구조에서 전체명령어중 10%정도의 명령어밖에 주로사용되지 않기때문에 이를 착안하여 명령어 개수를 줄여 효율적으로 만든것이 RISC 요즘 cpu는 모두 RISC구조


블로그 이미지

pringlee

하드코어보단 캐주얼!

,

하드웨어의 구성


- 2와 3을 덧셈한다고 한다면 ?

1.하드에있는 실행파일을 메모리에 로드
2.레지스터에 덧셈/2/3 저장
3. 컨트롤유닛이 덧셈이라는 명령을 해석
4. 컨트롤 유닛이 ALU에게 덧셈을 명령
5. ALU는 레지스터에 저장된 2와 3을 덧셈함

'구성요소들'

CPU : 중앙처리장치, 연산담당

    -ALU :  cpu내부에 실제 연산을 담당하는 블록
    
    -컨트롤유닛 : 바이너리 코드를 해석하여 명령어(add,div...등)를 알고 ALU에게    
       그에  따른 일을 시키는 장치
    
    -레지스터 :  cpu연산할때 필요한 데이터가 저장되는곳 이게없다면 cpu가 연산도        중 들어온 데이터를 처리할수없음 또한 데이터 용도에따라 나뉘는게 일반적
    
    -버스 인터페이스 : 입출력 통신방식을 이해하고 그에따라 맞는 통신을 하기위한         장치 ,cpu뿐만아니라 I/O버스와 연결되는 디바이스들은 모두 인터페이스가 필요

메모리(램) : 프로그램이 실행될때 로드되는곳

입출력버스 :  데이터를 주고받기위해 사용되는 경로
    
메모리와 cpu사이에서의 버스
    데이터버스: 데이터값을 받아온다
    어드레스 버스: 데이터를 읽거나 쓸때 해당하는 물리주소를 전달한다.
    컨트롤 버스: cpu가 원하는 바를 메모리에 전달할때 사용
    ex) a와 b를 더해서 c에쓸때:
    데이터 버스 : Add(명령) , a(숫자) , b(숫자)
    어드레스 버스 : c의주소값
    컨트롤 버스 : write
    이런식일것같다.

-클럭신호란?

한번에 클럭신호가 주어지면 디바이스들은 연산을 한번 진행한다. 클럭속도가 너무빠를경우 데이터 손실이 일어난다.

 초당 10번 동작할수있는 A와 초당 5번 동작할수있는 B가있다고하자.
클럭속도를 초당 10으로 맞추면 A가 2번동작할때 B는 한번동작한다.
B가 A가처리한 데이터를 가지고 연산한다면 데이터손실이 일어날것이다.
(A는 두개보냈는데 B는 하나만연산함)
따라서 클럭속도를 느린 장치에 맞추는것이 일반적이다.

블로그 이미지

pringlee

하드코어보단 캐주얼!

,