Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

보근은 참고 있다

2.8 하드웨어의 프로시저 지원 본문

CS/컴퓨터 구조

2.8 하드웨어의 프로시저 지원

보근 2020. 10. 14. 22:19

 

 

 

 

프로시저

 

 프로시저란, 제공되는 인수에 따라서 특정한 작업을 수행하는 서브 루틴. 프로시저는 지정된 작업 외의 다른 것은 아무 것에도 영향을 주어서는 안된다.

 

 프로그램의 프로시저 호출 후 실행 6단계 :

  • 프로시저가 접근할 수 있는 곳에 인수를 넣는다. ($a0~$a3)
  • 프로시저로 제어를 넘긴다.
  • 프로시저가 필요로 하는 메모리 자원을 획득한다.
  • 필요한 작업을 수행한다.
  • 호출한 프로그램이 접근할 수 있는 장소에 결과 값을 넣는다. ($v0~$v1)
  • 프로시저는 프로그램 내의 여러 곳에서 호출될 수 있으므로 원래 위치로 제어를 돌려준다. ($ra)

 

 

 

 

 

 프로그램 또는 프로시저가 프로시저를 호출 하려면 이동 명령어가 필요하다. MIPS에서는 jal(Jump And Link) 명령어가 있다. jal의 jump는 프로시저로 제어를 넘긴다는 뜻, link는 Caller와 Callee 사이에 링크를 형성해 올바른 주소로 되돌아올 수 있도록 한다는 뜻이다. 프로시저를 호출할 때 들고가야할 인수가 있을 수 있는데, $a0~$a3(Argument)에 담아간다. 

 

ex) jal ProcedureAddress // 원래 Caller의 주소는 $ra에 저장하고, 프로시저 주소로 jump 한다. 

 

 jal 명령어가 Caller와 Callee 사이에 link를 형성하는 방법은 원래 명령어의 주소값을 저장해두는 것인데, 그때 사용되는 레지스터가 $ra(Return Address)이다. Callee의 수행이 끝나고 Caller의 ($ra에 저장된) 주소로 복기하기 위해서 사용하는 명령어가 jr(Jump Register)이다. 복귀하면서 프로시저 수행 결과를 가져가야 하는데, $v0~$v1(return Value)에 담아간다.

 

ex) jr $ra // $ra에 저장된 Caller 주소로 jump한다.

 

 jal 명령어를 사용해 프로시저를 호출할 때, cpu의 PC(Program Counter)에는 프로그램 복귀 후 다음 명령어부터 바로 실행할 수 있도록 PC+4 값을 $ra에 저장한다.

 

 

 

 

 

 

 

 

 프로시저가 제어를 넘겨받으면, 그 전의 수행하던 작업들의 결과 보존 + 프로시저 작업에 필요한 레지스터 확보 등의 이유로 스필링(spilling)이 필요하다. MIPS에서는 그 방식으로 스택(stack)을 사용한다. 기존의 데이터들을 스택에 스필링 해두고 스택의 top 주소를 $sp에 저장해둔다. 스필링하는 레지스터마다 한 워드씩 스택은 낮은 주소로 성장한다.

 

ex) $s0, $s1, $s3을 스필링 해야한다.

     addi $sp, $sp, -12  // 레지스터 하나당 1워드(4바이트)씩 스택 확보 후 저장.

     sw $s3, 8($sp)

     sw $s1, 4($sp)

     sw $s0, 0($sp)

 

    프로시저 작업 종료 후, 다시 원래 값 복구하라.

    lw $s0, 0($sp)

    lw $s1, 4($sp)

    lw $s3, 8($sp)

 

 $t0~$t9(Temporary)는 임시 레지스터로 스필링 하지 않는다. $s0~$s7(Saved)는 저장 레지스터로 필히 스필링 한다. 이런 간단한 관례로 쓸데없는 명령어를 최대한 줄일 수 있다.

 

 

 

 

 

프로시저 호출 과정 정리

 

 1. 프로그램 또는 프로시저가 프로시저를 호출한다. 

        - $ra에 PC+4 값을 저장한다.

        - 전달할 인수를 $a0~$a3에 저장한다.

        - 프로시저로 제어를 넘긴다.

 2. 프로시저 작업에 필요한 레지스터를 확보한다.

        - $fp는 $sp 값으로 초기화 되며, 프로시저 프레임이 생성된다.

        - Caller가 사용하던 레지스터를 스택에 저장한다.

 3. 프로시저가 작업을 수행한다.

        - 반환할 결과 값은 $v0~$v1에 들어간다.

 4. 프로시저 호출 전 원상 복구한다.

        - $sp를 이용해 레지스터를 복원한다.

 5. 원래 프로그램으로 제어를 넘긴다.

        - $ra의 주소값으로 이동한다.

 

 

 

 

중첩된 프로시저

 

 프로그램뿐만 아니라 프로시저도 프로시저를 호출한다. 다른 프로시저를 호출하는 프로시저를 non-leaf 프로시저, 더 이상 다른 프로시저를 호출하지 않는 프로시저를 leaf 프로시저라고 한다. 프로시저들 사이의 호출에서도 기존의 데이터들이 보존 + 레지스터 확보 등을 해결해야 하고, 큰 자료 구조로 된 지역 변수를 저장하는 문제 때문에 스택 사용이 매우 복잡해진다. 

 

 

 새로운 데이터들을 위해서 스택에 공간을 할당해줘야 하는데, 프로시저의 저장된 레지스터와 지역 변수 등을 가지고 있는 스택 영역을 프로시저 프레임(Procedure Frame) 또는 액티베이션 레코드(Activation Record)라고 한다. 프로시저 프레임의 제일 하위이자 스택의 top을 가리키는 레지스터는 $sp이고, 반대로 프로시저 프레임의 제일 상위이자 스택의 시작을 가리키는 레지스터는 $fp(Frame Pointer)이다. 

 

 

 $sp는 프로그램이 실행되면서 값이 변동이 되지만 $fp는 값의 변경이 없기 때문에, 변수 참조는 변하지 않는 $fp를 사용하는 것이 좋다. 프로시저가 호출되어 프로시저 프레임이 생성되면 호출할 때의 $sp로 $fp를 초기화하고, 나중에 $fp로 $sp를 원상 복구한다.

 

 

 

 

 

 

 

 

 

힙 공간의 할당

 

 지역 변수 외에도 정적 변수나 동적 자료구조를 위한 메모리 공간도 필요하다. 전체적인 MIPS의 메모리 할당 방식을 살펴보자. 메모리 최하위는 사용이 유보되어 있고, 그 위는 Text Segment로 MIPS의 기계어 코드가 들어가는 부분이다. 그 위에는 Static 영역으로 정적 변수나 상수 등이 저장되어 있고, 마지막 맨 위를 Stack 영역과 Heap 영역이 공유한다. Stack은 최상위에서 하위로, Heap은 Static 영역 위에서부터 상위로 서로 마주보는 모양으로 성장한다.

 

 C는 지역 변수(= 자동 변수)는 상관없지만, malloc()등으로 힙에 동적으로 공간을 할당 받을 수 있다. 할당 받은 공간은 free()로 해제해줘야 하는데, 사용공간 반납을 안할 경우에 메모리 누출(memory leak)이 발생하여 메모리 공간 부족 문제가 생긴다. 반면에, 너무 빨리 공간 해제를 해버리면 프로그램 의도와 다르게 엉뚱한 것을 가리키는 매달린 포인터(dangling pointer)가 발생하는 문제점이 생긴다.

 

 

 

 

 

 

 

 

 

 

 

 

 

Comments