• 목록
  • 아래로
  • 위로
  • 4
  • Hygon
  • 조회 수 1569

이번 글에서는 프로세스의 메모리 구조와, 부트로더를 만들기 위한 간단한 어셈블리를 살펴보도록 하겠습니다.

여기서 프로세스의 메모리 구조라 함은, 하드디스크에 있던 프로그램이 RAM에 올라오면서 하나의 프로세스가 됩니다.

이 때 운영체제는 자신의 역할에 맞게(첫 시간에 운영체제는 자원을 관리하는 프로그램이라고 했죠?) 프로세스에게 여러 영역의 메모리를

할당하는데요, 이 때 할당되는 CODE, DATA, STACK, HEAP, BSS 영역을 말합니다. 우린 각각의 영역에 관해 알아볼겁니다.


그 전에, 부트로더를 만들기 전에 이걸 왜 배워야 하는지 설명을 드리려고 합니다. 

사실 다른 언어들에 비해 로우레벨에 속하는 C언어를 사용하더라도 각각의 변수가 어떤 메모리 영역을 사용하는지 잘 생각하지

않기 때문에 어셈블리어로 프로그래밍을 할 때에 어려움이 있을 수 있습니다. 

또한, 우리가 우리의 운영체제로 프로세스를 생성할 때, 메모리 영역을 할당해주어야 하므로 이를 위해서도 필요합니다.


1. 프로세스의 메모리 구조


위에서 언급했듯, 일반적으로 운영체제는 프로세스에게 다음의 다섯 개의 메모리 영역을 할당합니다:

1. Code Segment (코드 영역. 다른 말로는 Text Segment)

2. Data Segment (데이터 영역)

3. BSS Segment (BSS: Block Started by Symbol 영역)

4. Heap Segment (힙 영역)

5. Stack Segment (스택 영역)

 ※ 참고로 '데이터 영역'은 Data Segment + BSS Segment인 경우와 Data Segment만을 가리키는 두 가지 경우가 있으니, 맥락상 주의하기 바라며 제 강좌에서는 전자를 데이터 영역이라 하고, Data Segment만을 가리킬 때는 명시적으로 BSS영역을 제외한 데이터 영역이라고 표시할 겁니다. 또한, Section이라는 단어가 종종 Segment라는 단어 대신 쓰이므로 영문으로 된 문서를 보실 때 유의하시기 바랍니다.


1.1 코드 영역


코드 영역은 말 그대로 코드(Code), 즉 프로그램의 기계어 명령어가 저장되는 영역입니다.

주로 코드 영역은 읽기 전용(read-only)이라서 데이터를 쓸 수가 없습니다.

(이와 같이 메모리 영역의 속성을 바꾸는 방법은 이후 메모리 보호 기법에서 살펴봅니다.)


1.2 데이터 영역 (다른 말로는 Initialized Data Segment, 초기화된 데이터 영역)


데이터 영역에는 주로 전역(global)변수, 정적(static)변수를 저장하는 영역으로

코드 영역과는 다르게 일반적으로 읽기-쓰기가 가능하지만 데이터를 실행하는 것이 불가능합니다. 

(이와 관련된 내용이 궁금하시다면 NX : No-Execute 관련 문서를 찾아보세요.)


1.3 스택 영역


스택 영역은 지역변수의 할당을 위한 임시적으로 사용하는 공간입니다. 좀 쉽게 말하면 함수가 끝날 때, 사라져도 되는 변수는

스택에 저장한다고 할 수 있습니다. 스택 영역도 일반적으로 읽기-쓰기가 가능하지만 데이터를 실행하는 것이 불가능합니다. 


1.4 BSS 영역


BSS 영역은 초기화되지 않은 정적 or 전역 변수를 위한 메모리 영역입니다.

BSS 영역은 프로그램의 크기를 줄이기 위한, 일종의 최적화 방법이라고 할 수 있습니다.


관련 링크 : 

https://stackoverflow.com/questions/9535250/why-is-the-bss-segment-required

https://kldp.org/node/122255

(BSS 세그먼트가 필요한 이유)



What I know is that global and static variables are stored in the .data segment, and uninitialized da...

읽기, 쓰기, 실행 속성은 위에서 말한 데이터, 스택 영역과 같습니다. 


1.5 힙(Heap) 영역


 힙 영역은 프로그래머가 말 그대로 동적(Dynamic)으로 할당받을 수 있는 메모리 영역입니다.

동적이라 함은 '정적'이란 말과는 다르게 미리 정해두는 것이 아니라 필요할 때, 그때그때 할당을 받을 수 있다는 말입니다.


이렇게 Code(Text), Data, BSS, Stack, Heap의 다섯가지 영역을 알아보았는데요, 이 영역들은 원래부터 존재하는 영역이 아닙니다.

Code, Data, Stack 영역은 세그먼트 레지스터로 직접 영역을 지정해야 하고, Heap과 BSS영역은 아예 처음부터 만들어야 합니다.

※ 이건 사실 아주 당연한 말이지만, 하드웨어에서 지원하는 기능을 제외하면 컴퓨터는 우리가 코딩한 것 이외는 아무것도 할 줄 모릅니다. 따라서 지금까지 프로그래밍을 하면서 당연하게 쓰던 것들이 우리가 만들 운영체제에는 당연히 없습니다. 다 만들어주어야 합니다.


메모리 영역에 대해 간단하게 알아보았습니다.  이제 부트로더를 만들기 위해 필요한 어셈블리를 간단하게 알아보겠습니다.


2. 어셈블리(Assembly)


컴퓨터는 기계어를 바탕으로 작동합니다. 따라서 데이터를 쓰거나, 읽거나, 실행하는 등 모든 연산을 기계어로 처리합니다.

그리고 마이크로프로세서(앞으로 CPU를 이렇게 부르겠습니다)는 명령어를 실행할 수 있습니다. 물론 이 명령어도 결국에는 기계어죠.

아무튼 컴퓨터는 기계어를 사용합니다. 하지만 사람은 기계어를 이해하기가 어렵죠? 그래서 기계어를 간단하게 바꿔서 표현을 해주는, 어셈블리를 만들었습니다.


어셈블리가 C언어와 같이 나중에 나온 언어와 다른 점은 어셈블리는 기계어와 일대일대응이 된다는 것입니다.

예를 들어 push 1 이라는 명령은 0110 1010 (push)   0000 0001 (1) 이런 식으로 기계어를 단순히 치환했다고 생각할 수 있습니다. 


그리고 어셈블리어는 크게 두 가지 문법이 있습니다.


2.1. Intel 문법


mov eax, 123 (여기서 mov 는 명령어의 이고, eax, 123은 오퍼랜드(피연산자) 입니다)

Intel 문법의 특징은 source 오퍼랜드, 즉 출발지 or 원본 오퍼랜드가 오른쪽에 오고,

destination 오퍼랜드, 즉 도착지 or 대상 오퍼랜드가 왼쪽에 온다는 특징이 있습니다.

위의 mov ax, 123은 ax 레지스터에 123을 넣으라는 의미가 되죠.

하지만 mov 123, ax는 성립하지 않습니다. 상수에 무언가를 넣을 수는 없으니까요.

보통 대상 오퍼랜드에는 레지스터 또는 주솟값이 옵니다. 

또한 포인터 연산을 할 때는 레지스터나 주솟값을 [ax] 이런 식으로 대괄호로 씌워줍니다.


2.2. AT&T 문법


mov $123, %eax

AT&T 문법은 Intel 문법과는 오퍼랜드의 순서가 반대입니다. 

또한 상수 앞에 '$'를 붙이고,  레지스터 앞에는 '%'를 붙입니다. 

'$'가 붙지 않은 상수는 주솟값을 의미하며, 레지스터를 포인터로 사용할 때는 (%eax)처럼 괄호를 씌웁니다. 

레지스터를 사용하지 않는 연산에는 명령어에 접미사를 붙어야 합니다.

등등의 특징이 있습니다.


이 강좌에서 우리는 앞으로 Intel 문법을 사용할겁니다. (AT&T 문법 몰라도 됩니다 XD)

2.3 간단한 어셈블리 (자주 사용되는 명령어) 


ADD A, B : A 에 B 를 더한다.
SUB A, B : A 에 B 를 뺀다.
PUSH A : 스택에 A 를 넣는다.
POP A : 스택의 맨 위 4 바이트를 A 에 넣는다.
MOV A, B : A 에 B 를 복사한다.
LEA A, B : A 에 B 가 가리키는 값을 넣는다. (B 는 주솟값)
INT A : A 번 인터럽트를 발생시킨다. (인터럽트는 차근차근 배울겁니다.)
INC A : A 의 값을 1 증가시킨다 (A++)
DEC A : A 의 값을 1 감소시킨다 (A--)
NOP : 아무것도 하지 않고 다음 명령어로 넘어간다.
CALL : 리턴할 주소를 스택에 넣고 함수를 호출한다.
CMP A, B : A 에서 B 를 빼서 값을 비교한다.
AND|OR|XOR A, B :A 와 B 를 AND | OR | XOR 연산을 한다.
RET : 스택에서 맨 위의 값을 뺀 후 그 주소로 리턴한다.


위의 어셈블리는 암기하셔도 좋지만, 어차피 다음 시간에 위의 명령을 어떤 식으로 사용하는지

자세히 알아볼 것이기 때문에, 이해만 하셔도 좋습니다.


더 공부할 만한 것들


어셈블리 프로그래밍 : https://opentutorials.org/module/1087

 


참고 문헌


https://wiki.osdev.org/Assembly


 이번 시간에 배운 것


1. 메모리 영역(Code, Data, Stack, Heap, BSS)의 용도와 실행 가능 여부, 쓰기 가능 여부

2. 자주 사용되는 어셈블리 명령어들


연습문제


1. BSS 영역의 용도는 무엇인가? (왜 굳이 Data영역과 BSS영역을 구분해야 하는가?) - 힌트 : 메모리 계층 구조

2. 각각의 메모리 영역의 실행, 쓰기, 읽기 가능 여부를 다음과 같이 표현하시오

ex) BSS : RWE (R: Read, W: Write, E: Execute)

3. 텍스트 영역의 데이터에 쓰기를 가능하게 한다면, 어떤 문제가 발생할 것인가?


※ 다음 시간에는 우리가 아는 C언어 구문들을 어셈블리어로 바꾸어보고, 그 다음 시간 부터는 부트로더를 직접 만들어 볼겁니다. 

포함 1명이 추천

추천인 1

작성자
Hygon 11 Lv. (43%) 10480/11520EXP

서산 서일고등학교 3학년 유형곤입니다.

댓글 4

신고

"옴팡이는하나님의 댓글"

이 댓글을 신고 하시겠습니까?

Basix
profile image
다음 글 기대하겠습니다^^
comment menu
2018.07.30. 06:13

신고

"Basix님의 댓글"

이 댓글을 신고 하시겠습니까?

입체그림
오오...종국에는 진짜 운영체제를 만들어 보는 것이 목표인건가요 ㄷㄷ
comment menu
2019.10.26. 18:17

신고

"입체그림님의 댓글"

이 댓글을 신고 하시겠습니까?

신고

"급식프로그래머님의 댓글"

이 댓글을 신고 하시겠습니까?

권한이 없습니다.