10강)
점프앤 링크
함수가 호출되면 호출될 함수의 주소로 점프를 한다.
함수가 리턴될 주소를 저장하는 ra 에다가 점프를 시작한 주소를 저장한다.
ra 에다가 현재 프로그램 카운터 값을 저장하고, 점프한 후 다른 위치로 pc 를 바꾸는 두 연산을 합쳐둔 것이 점프앤 링크이다.
함수 호출은 결국 "리턴 어드레스 저장 - 함수의 시작 위치 점프 - 함수 실행 끝난 후 Jump Register 를 해서 호출된 지점으로 다시 돌아오는" 과정이다.
함수가 다른 함수를 호출하고, 그 함수가 다시 다른 함수를 호출하면 ra 를 덮어쓸 것이다.
함수가 한 번만 호출되면, 첫번째 그림처럼 로직이 진행되겠지만 A-B-C 함수가 연속해서 호출되면 가장 처음의 ra 값은 스택에 저장된다.
실행이 쭉 되다가 이미 ra 레지스터를 덮어썼지만 stack 의 탑의 내용을 읽어서 돌아올 수 있다.
리턴 어드레스를 저장하는 책임은 호출받는 쪽에 있는가? 호출하는 쪽에 있는가?
=> 호출 하는 쪽에 있다.
호출 받는 쪽이 저장하는 저장하는 책임이 있다고 하면, 이미 ra 레지스터가 덮어쓰워 지기 떄문에 소용이 없다.
매번 호출 될 때마다 함수에 대한 정보를 저장하는 것을 stack frame 이라고 한다.
C라는 함수가 저장해야 할 레지스터들이 있으면 자신의 스택 프레임에 변수들을 저장해둔다.
스택에 포인터가 두개가 있다.
하나는 프레임 포인터라고 해서, 현재 스택 프레임의 시작 위치를 가리키고, 스택의 탑을 가리키는 포인터가 하나 있다.
스택의 가장 탑을 스택 포인터 (sp) 가 가리킨다.
아규먼트를 전달하는데, 기본적으로 레지스터 4개를 활용해서 전달을 하는데,
만약 그 이상으로 필요한 경우 4개까지는 레지스터에다가 넣고, 초과되는 부분들을 스택 프레임에 넣어서 전달을 해준다.
그 함수가 책임지고 저장해두어야 할 레지스터들 또한 스택 프레임에 저장해둔다.
레지스터가 빠르기때문에 가능하면 변수들을 최대한 레지스터에 넣지만, 레지스터가 작기 때문에 메모리를 활용.
레지스터를 save 하는 책임은 함수를 호출하는 쪽에 있다.
나머지 레지스터들은 누가 save 를 해야 하는지가 자명하다.
argument 의 경우 호출자가 저장을 해야한다.
나도 하늘에서 뚝 떨어진게 아니라 누군가 호출을 해서 시작을 했다. 즉, argument 를 레지스터에서 넘겨받은 것.
또 호출을 하려면 내가 전달받은 argument는 내 스택에 저장한 다음에, 호출 당하는 쪽에서 필요한 argument 들을 넣어두어야 함.
return value 의 경우
리턴 값을 넣는 레지스터에 리턴값을 넣고 리턴하는데
내가 다른 함수를 호출하는 경우, 다시 돌아올 때 불리워진 함수가 v 에다가 값을 넣고 돌아온 것일 것.
전달받은 return 값은 내가 save 를 해야한다. (자신이 save 하고 변경을 하든 해야함.)
return address 도 마찬가지로,
함수를 호출하는 쪽에서 저장을 해두고, 다른 함수를 호출한다.
사실 Callee 가 저장해야 하는 레지스터는 별로 없는데
프레임 포인터는 생각해보면 Callee 가 저장할 수 밖에 없는 상황.
A - B - C 호출된다고 할 때 내가 새로운 함수이면서, 원래의 fp 였던 것을 "나의 시작 위치"로 바꾸어야 함.
원래의 fp 를 내가 저장해두고 새로운 fp 로 변경후 내가 실행되고 리턴해야 함.
빨간 레지스터 부분들은 어차피 프로그램 내에서 계속 유지되는 레지스터값들이기 때문에
호출당하는 쪽이 "필요하다면" Save 를 해야 할 책임이 있는 레지스터들이다.
파란색쪽은 지워질 것이란 것을 전제로 사용하는 임시 레지스터이, 호출 하는 쪽에서 save 를 하는 레지스터들이다.
함수가 호출되는 일련의 과정들을 정리해보자.
함수를 호출하기 위해서는 내가 save 해야 할 의무가 있는 레지스터들을 나의 스택에 저장해 두어야 한다.
(Caller save register 들에 해당하는 값들 )
그 다음에 내가 다음 함수에 전달해야 할 argument 들을 저장해야 하는데, 앞의 4개 인수들은 a0-3 까지에 넣고, 초과되는 경우 stack 에 저장한다.
그 다음 JAL (Jump And Link) 를 이용해서 되돌아올 리턴 어드레스가 저장되고, 새로운 함수가 시작된다.
새로운 함수가 시작되면
sp 가 top 을 가리키게 해준다. (나의 stack frame 크기를 계산해야 함)
fp 를 나의 시작 위치로 변경해줌.
s0 - s7 까지에 필요한 값들을 저장해둔다.
callee 의 실행이 다 끝나고 리턴하는 경우
리턴 값을 v0, v1 에 넣은 다음에 리턴을 해야 하고
리턴하기 이전에 내가 save 했던 레지스터 값을 다시 복원한다.
C 에서 다시 B 의 프레임 포인터로 복원시키고,
나의 스택 프레임에 있던 내용들을 다 지운다.
그리고 ra 레지스터의 값으로 점프한다.
- 과정을 외우지는 못하더라도 이런 메커니즘이 필요하다는 것은 알아두어야 한다.
'Operating System' 카테고리의 다른 글
[System Software] gcc, compiling, linking, libraries (0) | 2021.02.13 |
---|---|
[System Software] System Software & Program Execution (0) | 2021.02.12 |
[System Software] 프로그램의 메모리 주소 공간 구조 (0) | 2021.02.08 |
[System Software] C언어&어셈블리 예제 (0) | 2021.02.07 |
[System Software] 논리연산 & Data Transfer 연산 / Branch & Jump 연산 (0) | 2021.02.07 |