chill하게 알아보는 메모리 관리

정다빈·2025년 1월 24일
2
post-thumbnail

저는 비전공 프론트엔드 개발자에요. 작년 말부터 컴퓨터 공학에 관심이 생겨서 조금씩 공부를 시작했어요.
최근에는 운영체제론을 공부했는데, 다양한 개념 중에서 인강 선생님이 가장 강조하셨던 메모리에 대해 정리해 보려고 합니다.

💁🏻‍♀️ 비전공자가 넓고 얕게 개념을 익히는 것이 목표이기 때문에, 전공자분들에게는 다소 가벼운 내용일 수 있어요.

📀 메모리

컴퓨터의 뇌 역할을 하는 CPU가 연산을 하려면, 연산에 사용할 데이터가 필요해요. 메모리는 이 데이터를 저장하는 공간을 말하며, 종류도 여러 가지가 있어요.

  • 레지스터 : CPU 내부에 있는 가장 빠른 메모리로, 연산에 필요한 데이터를 잠시 저장해요. 연산이 끝나거나 컴퓨터를 종료하면 데이터가 사라져요.
  • 캐시 메모리 : 레지스터와 RAM 사이에 위치하며, CPU가 RAM보다 빠르게 접근할 수 있게 도와주는 메모리에요. 자주 사용되는 데이터를 캐싱하고, 컴퓨터가 종료되면 모든 데이터가 사라져요.
  • RAM (주 기억장치) : CPU가 연산 중인 데이터를 저장하는 메인 메모리에요. 연산이 끝난 후에는 데이터가 삭제되지 않지만, 컴퓨터가 종료되면 모든 데이터가 사라져요.
  • HDD / SSD (보조 기억장치) : 데이터를 영구적으로 저장하는 저장 장치에요. RAM보다 속도는 느리지만, 컴퓨터 종료 후에도 데이터가 유지돼요.

사실 HDD와 SSD는 메모리가 아닌 저장 장치로 분류되지만, 메모리와 저장 장치 모두 데이터를 저장하는 역할을 하므로 기억 장치라는 큰 범주 안에 포함돼요.

이후 언급되는 메모리는 RAM을 의미해요.

🫙 물리 메모리와 가상 메모리

앞으로의 설명을 이해하려면 프로세스에 대해 알아야 하는데요, 간단히 말해 실행 중인 프로그램을 의미해요.

프로세스를 실행하려면 실행에 필요한 코드나 데이터를 메모리에 저장해야 하는데, 이것들을 곧바로 물리 메모리에 저장하지 않아요. 물리 메모리를 가상화해서 가상 메모리를 만들고, 프로세스 실행에 필요한 만큼 가상 메모리를 할당해 줘요.

여기서 가상화란, 실제 자원인 물리 메모리를 소프트웨어를 통해 가상 자원으로 만들어서 여러 프로세스가 각자 독립적인 메모리를 가진 것처럼 보이게 하는 것을 말해요.

그렇다면, 프로세스에게 얼마나 많은 메모리가 필요한지 어떻게 알 수 있을까요?

저는 크롬을 실행할 때 메모리가 얼마나 필요한지 확인하기 위해 주소창에 chrome://version/을 입력해 보았어요.

위 사진처럼 크롬의 메모리 사용 정보를 확인할 수 있는데요, 여기서 x86_64는 64비트 아키텍처를 사용한다는 것을 의미해요.
64비트 아키텍처는 2642^{64}개의 메모리 주소 공간을 가질 수 있어요. 이를 계산하면 18.4조 Byte, 즉 크롬은 최대 18.4 EB의 메모리를 사용하겠다는 의미예요.
하지만 18.4 EB를 지원하는 하드웨어나 운영 체제는 존재하지 않기 때문에, 실제로는 수십 기가바이트에서 수백 기가바이트 수준의 가상 메모리만 할당해요.

↔️ 메모리 스왑

프로세스를 실행할 때 물리 메모리에 직접 데이터를 저장하지 않고, 각 프로세스마다 가상 메모리를 할당해 데이터를 저장해요.

만약 물리 메모리가 부족해지면, 사용되지 않는 데이터를 디스크의 스왑 영역으로 옮겨서 물리 메모리 공간을 확보할 수 있어요. 이를 스왑 아웃(swap-out)이라고 해요.

반대로, 해당 데이터가 필요해지면 디스크에서 메모리로 데이터를 불러와야 해요. 이를 스왑 인(swap-in)이라고 합니다.

운영 체제는 디스크 스왑 영역까지 사용하여 물리 메모리보다 더 큰 가상 메모리를 사용할 수 있어요. (물리 메모리 + 디스크 스왑 영역 = 총 가상 메모리)
이를 통해 메모리 부족 문제를 해결하고, 물리 메모리의 한계보다 더 많은 프로세스를 실행할 수 있어요.

🏡 논리 주소와 물리 주소

각각의 프로세스는 할당받은 가상 메모리를 사용하지만, 결국에는 데이터를 물리 메모리에 저장해야 CPU가 연산할 수 있어요.

가상 메모리에 저장된 데이터는 논리 주소를 갖고 있고, 이 데이터가 물리 메모리에 저장될 때는 물리 주소로 변환돼요. 이 변환 작업은 메모리 관리 장치(MMU)가 담당하며, 이를 통해 운영 체제가 각 프로세스에 독립적인 메모리 공간을 제공할 수 있어요.

위 그림에서 프로세스3의 가상 메모리 영역은 0부터 100까지이고, 데이터는 30(논리 주소)에 위치해 있어요. 하지만 실제 물리 메모리에 저장된 데이터는 500(물리 주소)에 위치한 것을 확인할 수 있어요.

이처럼 가상 메모리에서의 주소는 물리 메모리의 주소와 다를 수 있으며, 이 두 주소는 메모리 관리 장치가 매핑하여 데이터를 올바르게 처리해요.

🍰 메모리 분할 방식

프로세스는 실제 물리 메모리를 직접 사용하지 않고, 운영 체제로부터 가상 메모리를 할당받아 사용해요.

그런데, 아래 그림처럼 메모리가 사용되고 있다면 어떨까요?

물리 메모리에 남은 공간이 있지만 작게 조각나 있어 프로세스3이 사용할 수 없는 모습이에요.
이러한 상황을 해결하고 메모리를 효율적으로 관리하기 위해 가상 메모리를 분할할 수 있어요.

메모리 분할 방식은 크게 두 가지가 있어요.

페이징(Paging)

가상 메모리를 고정 크기(페이지)로 나누어 관리하는 방식이에요.

페이징은 구현이 간단하지만, 프로세스 실행에 필요한 크기보다 더 큰 공간이 할당되면 낭비가 발생할 수 있어요.

위 그림을 다시 보면,

  • 프로세스1 : 가상 메모리의 크기가 페이지보다 크기 때문에 2개로 분할되었어요.
  • 프로세스2 : 가상 메모리의 크기가 페이지와 동일하기 때문에 1개를 사용해요.
  • 프로세스3 : 가상 메모리의 크기가 페이지보다 작지만 1개를 사용해요.

프로세스3처럼 가상 메모리의 크기가 페이지보다 작을 경우, 할당된 페이지 내부에서 사용되지 않는 공간이 발생해요. 이를 내부 단편화라고 합니다.

이렇게 페이지 단위로 분리된 가상 메모리는 페이지 테이블을 통해 관리돼요.

  • 페이지 : 가상 메모리를 나누는 단위를 말해요.
  • 프레임 : 물리 메모리를 나누는 단위를 말해요.

페이지와 프레임의 크기는 같으며, 1:1로 매칭된다는 특징이 있어요. 또한, 페이지의 순서와 프레임의 순서가 서로 다를 수 있어요.

그런데 위 그림에서 페이지2는 프레임과 정상적으로 매핑되지 못했어요.
어떤 오류로 인해 페이지가 유효하지 않은 프레임을 참조하면, 페이지 폴트(Page Fault) 에러가 발생할 수 있어요.

세그멘테이션(Segmentation)

가상 메모리를 코드, 데이터, 스택 등 의미 있는 크기(세그먼트)로 나누어 관리하는 방식이에요.

세그멘테이션은 메모리를 효율적으로 사용할 수 있지만, 작은 빈 공간이 여기저기 흩어져 있으면 큰 세그먼트를 추가할 수 없어요. 이런 현상을 외부 단편화라고 해요.

페이지와 마찬가지로 세그먼트는 세그먼트 테이블을 통해 관리돼요.

페이징 기법에서는 페이지와 프레임이라는 용어를 사용했지만, 세그멘테이션 기법에서는 모두 세그먼트라고 불러요.
각 세그먼트는 1:1로 매칭되며, 가상 메모리의 세그먼트와 물리 메모리의 세그먼트 순서는 다를 수 있어요.

그런데 위 그림에서 세그먼트2는 정상적으로 매핑되지 못했어요.
어떤 오류로 인해 세그먼트가 유효하지 않은 영역을 참조하면, 세그멘테이션 폴트(Segmentation Fault) 에러가 발생할 수 있어요.

✅ 단편화 문제 해결

페이징과 세그멘테이션은 단편화 문제를 가지고 있어요.

  • 페이징 : 프로세스 실행에 필요한 메모리 공간보다 더 큰 공간을 할당받아 내부 단편화가 발생할 수 있어요.
  • 세그멘테이션: 메모리의 빈 공간이 작게 조각나 있어서 외부 단편화가 발생할 수 있어요.

이러한 단편화 문제들을 해결하기 위해 페이징과 세그멘테이션 기법을 혼용할 수 있어요.

1. 가상 메모리를 세그먼트로 분리

가상 메모리를 세그먼트로 분리해요. 세그멘테이션은 가상 메모리를 코드, 데이터, 스택 등 의미 있는 크기로 나누기 때문에 논리적인 구조를 유지할 수 있어요.

이렇게 분리된 세그먼트는 세그먼트 테이블로 관리돼요.

2. 세그먼트 권한 설정

사용자 이름을 입력받아서 메모리에 저장하는 프로세스가 있는데, 사용자가 악성코드를 입력했다고 가정해 볼게요. 만약 이를 처리하는 세그먼트가 실행 권한을 가지고 있다면 어떻게 될까요? 사용자가 입력한 악성코드가 실행되어 보안 사고가 발생할 수 있어요.

세그먼트 테이블에서 각 세그먼트에 대해 권한 비트를 설정하면 이러한 보안 사고를 방지할 수 있어요.

권한 비트는 4bit 공간을 가지고 있으며, 읽기, 쓰기, 실행 권한을 2진수로 표현할 수 있어요.

아래 표를 보면 7은 읽기, 쓰기, 실행 권한을 의미해요. 7을 2진수로 변환하면 0111이 되고, 이를 권한 비트에 넣어주면 해당 세그먼트는 rwx 권한을 갖게 돼요.
보통 세그먼트는 rw- 또는 r-- 권한을 가져요.

프로세스에서 메모리에 접근하려 할 때, 매핑 테이블에 있는 권한 비트로 허용 여부를 결정해요.

3. 세그먼트를 페이지로 분리

각 세그먼트를 페이지로 분리하고, 이를 페이지 테이블로 관리해요.
페이지는 세그먼트에서 상속받은 권한을 따르거나, 개별적으로 권한을 설정할 수 있어요.

이렇게 세그멘테이션과 페이징을 혼용하면 논리적인 구조를 유지하면서 더 작은 페이지를 사용하기 때문에, 내부 단편화와 외부 단편화를 모두 해결할 수 있어요.

👾 마무리

저는 주로 실무에서 겪었던 경험 위주의 글을 작성하는데, 이번에는 소재가 부족해서 컴퓨터 공학을 찍먹해 보았어요.

실무를 하면서 메모리 관련 처리를 해본 경험은 없지만, 평소에 운영체제나 메모리에 대해 궁금했던 부분들을 조금씩 해결할 수 있었던 것 같아요. 물론 제가 다룬 내용보다 더 깊은 이야기들이 있겠지만, 저는 이 정도에서 만족하고 앞으로 다른 것들을 공부할 예정이에요.

앞으로도 열심히 공부해 보겠습니다... 😀

profile
Frontend Developer

0개의 댓글