'Operating System & Computer Architecture/윈도우즈 시스템 프로그래밍'에 해당되는 글 5건

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

하드코어보단 캐주얼!

,
-Immediate Addressing 즉시 주소 지정 방식
operand 가 instruction에 포함된다.
ex)  Add r3 r1 r2 
     : operand인 r3 r1 r2가 함께전달된다

장점 :  여러번 실행없이 한번의 연산으로 읽어서 사용하기 때문에 빠르다.
단점 :  operand를 같이 보내기때문에 operand크기 제약이있다.


-Direct Addressing 직접 주소 지정 방식
instruction의 operand에 직접적인 주소가 들어간다.
ex) Load r1, 0x10
    : 0x10이라는 직접적인 주소가 들어간다.
단점 : 16bit cpu라고하면 8bit정도가 명령어,예약필드로 쓰였다면 8bit주소밖에 표현못한다. 주소공간 제한.

-Indirect Addressing 간접 주소 지정 방식
위의 문제를 해결하기위한 하나의 방식이라고 할수있다.

16bit 레지스터이고

int a=10; //0x0010번지 할당
int b = 20; //0x0100번지 할당
int c = a+b; //0x0020번지 할당
을 하고싶다고 해보자.

LOAD r1,0x10
LOAD r2,0x0100
//안됨!!

여기서 문제가있다. 예약,명령어필드가 4bit이고 첫 operand r1이 4bit라 하자.
0x100이 8bit(0xff)가 넘어가기 때문에 이 주소값은 표현할수없기 때문이다.

RISC cpu의명령어는 대체로 복잡한 연산의 명령어를 제공하지 않는다.
우리가 디자인한 cpu는 LOAD ,STORE ,사칙연산이 전부다. 레지스터에 뭘저장하거나 하는 연산도없다.
따라서 사칙연산으로 0x100을 만들어보자

mul r0,4,4
mul r2, 4,4
mul r3, r0 ,r2
4bit/ 4bit/4bit/4bit
//0x100만들기!

이제 LOAD r2,r3 하고 싶지만(실제값 메모리값 대신 레지스터 지정방식) r3위치에 항상 메모리의 주소가 오게끔 설계했다고 하자. 

//~
잠깐 다른생각을 해보면 r3위치에 레지스터도 오게끔 했다면 어떤 단점이있을지 생각해보자.
레지스터와 메모리주소를 구분하기위해 1bit로 구분지어야한다. 메모리 표현이 3bit로 적어진다. 또한 그것을 구분하기 위해 cpu연산에 있어서 분명 어떠한 비용이 들것이다. 이와같이 설계했을때 장점과 단점을 잘 생각해보아야한다.
~//


r3에는 0x100이 들어있고
나는 LOAD r2,0x100을 하고싶다... 어떻게 하면좋을까?

STORE r3, 0x0030  //임시로 r3에있는 값을 적당한 아무곳에 저장
LOAD r2, [0x0030]  //indirect모드로 LOAD시키기
//메모리를 두번참조한다. 느리다..
0x0030으로가서 0x0100값을 보고 다시 0x0100으로간다.

이제나머지연산...
ADD r3 r1,r2
STORE r3,0x20


레지스터로 주소넘기는 LOAD r2,r3 방법이 편한것같기도 하다. 지금은 추상적이라 모르겠지만 요약하자면 명령어 설계시 다양한 방법이있고 속도측면과 복잡도 측면을 생각하여 설계해야 할것이다.


블로그 이미지

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

하드코어보단 캐주얼!

,