메모리
RAM(Random Access Memory) 임의 접근 메모리
실행 중인 데이터와 명령어가 담겨져있는 휘발성 저장장치
RAM의 용량이 작으면 ?
- 보조기억장치로부터 실행할 프로그램을 가지고 오는 일이 잦아져 실행 시간이 길어짐
- RAM의 용량이 크다고 해서 성능이 향상되는 것은 아님
메모리는 1~100까지 순회할 필요 없이 곧장 100번지로 접근이 가능한 ‘직접 접근’을 할 수 있음
RAM의 종류
DRAM
Dynamic RAM의 약자, 저장된 데이터가 동적으로 변하는 특성
- 시간이 지나면 저장된 데이터가 점차 사라짐
- 일정 주기로 데이터를 재활성화(재저장) 해야함
- 전력효율이 높고, 저렴, 집적도(작아도 효율이 좋음) 높음
SRAM
Static RAM의 약자, 저장된 데이터가 변하지 않는 RAM
- 시간이 지나도 데이터 보존
- 전원이 꺼지면 휘발
- DRAM보다 속도가 빠르지만, 전력 소비가 크고 비쌈, 집적도도 낮음
- 캐시 메모리 등에서 사용
SDRAM
Synchronous Dynamic RAM의 약자, 클럭신호와 동기화된 발전 형태의 DRAM
- 클럭 타이밍에 맞춰 CPU와 정보를 주고받을 수 있음
DDR SDRAM
Double Data Rate SDRAM의 약자, 대역폭을 넓혀 속도를 빠르게 만든 SDRAM
- 데이터를 주고받을 너비인 ‘대역폭’을 두배로 늘려 속도가 두배 더 빠름
- DDR2는 DDR의 2배, DDR3는 DDR2의 두배 …
메모리에 바이트를 밀어 넣는 순서 - 빅 엔디안과 리틀 엔디안
현대의 메모리는 바이트 단위로 데이터 저장 및 관리
- CPU로부터 바이트 단위로 받아들이는 것 X
- 4바이트(32비트), 8바이트(62비트)인 워드 단위로 받아들임
바이트를 어떤 순서로 저장하는지에 따라 빅 엔디안
과 리틀 엔디안
으로 분류 가능
빅엔디안
낮은 번지의 주소에 상위 바이트부터 저장하는 방식
- 이렇게 저장하면 낮은 주소 번지부터 역순으로 들어가게 됨
리틀 엔디안
낮은 번지의 주소에 하위 바이트부터 저장하는 방식
- 이렇게 저장하면 낮은 주소부터 차례대로 들어가게 됨
MSB와 LSB
- MSB는 숫자의 크기에 가장 큰 영향을 미치는 유효 숫자, 즉 가장 왼쪽의 비트
- LSB는 숫자에 크기에 가장 작은 영향을 미치는 유효숫자, 즉 가장 오른쪽의 비트
따라서 빅 엔디안은 MSB, 중요하고 큰 데이터 부터 저장해 나가는 방식이고,
리틀 엔디안은 LSB, 덜 중요하고 작은 데이터 부터 저장해 나가는 방식
캐시 메모리
CPU는 프로그램 실행 과정에서 메모리에 지속적으로 접근해야함
- 메모리에 접근하는 속도는 레지스터 접근 속도 보다 느림
- CPU의 처리속도가 메모리 로드 속도보다 빨라지는 현상 발생
이를 위해 캐시 메모리 도입
캐시 메모리는 연산 속도와 메모리 접근 속도의 차이를 줄이기 위한 SRAM 저장장치
- L1 캐시 > L2 캐시 >>> 코어 경계 >>> L3 캐시
캐시 메모리 크기
- L1 < L2 < L3
메모리 속도
- L1 > L2 > L3
CPU는 L1 캐시에 데이터를 먼저 체크하고 없으면 L2, L3 순으로 체크해본다.
L1 캐시의 구분
- L1l 캐시 : 명령어만을 저장하는 캐시
- L1D 캐시 : 데이터만을 저장하는 캐시
이러한 유형의 캐시 메모리를 ‘분리형 캐시’ 라고도 함
캐시 히트와 캐시 미스
메모리는 ‘실행 중인 것’ 저장, 캐시 메모리는 ‘CPU가 사용할 법한 것’ 저장
- 이렇게 가져온 데이터를 실제로 사용하게 되면
캐시 히트
- 예측이 틀려 메모리에서 가져오게 되면
캐시 미스
참조 지역성의 원리
- 시간 지역성: CPU는 최근 접근했던 메모리 공간에 다시 접근하려는 경향이 있음
- 공간 지역성: CPU는 최근 접근한 메모리 공간의 근처에 접근하려는 경향이 있음
- 실제로 행으로 접근했을 때보다 열로 접근 해서 순회를 돌 경우 시간이 더 걸림
캐시 메모리의 쓰기 정책과 일관성
CPU가 캐시 메모리에 데이터를 쓸 때, 새롭게 쓰여진 데이터와 메모리 상의 데이터가 일관성을 유지해야함
이렇게 캐시 메모리의 존재로 데이터의 일관성이 깨지는 경우가 발생할 수 있음
해결 방법
- 즉시 쓰기(write-through)
- 메모리를 항상 최신 상태로 유지하여 캐시 ↔ 메모리 간의 일관성을 보장
- 데이터를 쓸 때마다 메모리를 참조해야 하므로 버스의 사용 시간과 쓰기 시간 증가
- 지연 쓰기(write-through)
- 캐시 메모리에만 값을 써 두었다가 추후 수정된 데이터를 한 번에 메모리에 반영
- 메모리의 접근 횟수를 줄이기 때문에 속도는 빠르지만 메모리와 캐시 메모리 간의 일관성 깨짐
요약: 캐시는 자주 사용할 법한 대상을 가까위 위치시킴으로써 성능 향상을 꾀한다.
보조기억장치와 입출력장치
RAID
데이터의 안전성 혹은 성능을 확보하기 위한 여러 개의 독립적인 보조 기억 장치를
→ 하나의 보조기억장치처럼 사용하는 기술
RAID를 구성하는 방법에 따라 RAID 레벨로 분류
RAID0
데이터를 보조기억장치에 단순하게 나누어 저장하는 방식
- 빠른 입출력 속도
- 데이터를 한 번에, 동시에 읽고 쓸 수 있음
- 저장된 정보가 안전하지 않음
RAID1
완전한 복사본을 만들어 저장하는 방식 (미러링)
- 복구가 간단하고 안전성이 높음
- 데이터를 쓸 때 원본과 복사본 두 곳에 써야함 → 쓰기 속도가 느려짐
- 사용 가능한 용량이 적음
RAID4
패리티 정보를 저장하는 디스크를 따로 두는 방식
- 패리티는 오류를 검출하는 정보
- RAID1에 비해 적은 하드 디스크로도 안전하게 데이터 보관
- 패리티 저장 장치에 병목 발생
RAID5
패리티를 분산하여 저장하는 방식
- RAID4의 병목현상을 보완
RAID6
기본적인 구성은 RAID5와 같지만 서로 다른 2개의 패리티를 두는 방식
- 오류를 검출하고 복구할 수 있는 수단이 2개
- RAID4, RAID5 보다 안전성이 높음
- 그에 따른 쓰기 속도 trade-off 발생
입출력 기법
장치 컨트롤러와 장치 드라이버
장치 컨트롤러 : CPU와 입출력장치 사이의 통신을 중개하는 중개자 역할의 하드웨어
장치 드라이버 : 장치 컨트롤러의 동작을 알고, 장치 컨트롤러가 컴퓨터 내부와 정보를 주고받을 수 있도록 하는 프로그램
CPU ↔ 장치 컨트롤러가 정보를 주고받는 작업은 3가지 방법이 있다.
프로그램 입출력
프로그램 속 명령어로 입출력 작업을 수행하는 방법
- 입출력 명령어를 실행하여 장치 컨트롤러와 상호작용
- 고립형 입출력(isolated I/O) : 입출력장치에 접근하는 주소와 메모리에 접근하는 주소가 별개
- 별도의 입출력 명령어 필요
- 메모리 맵 입출력(memory mapped I/O) : 입출력장치에 접근하는 주소와 메모리에 접근하는 주소를 구분하지 않고, 메모리에 부여된 주소 공간 일부를 입출력장치를 식별하기 위한 공간으로 사용
- 메모리에 접근하는 명령어로 바로 접근 가능
- 고립형 입출력(isolated I/O) : 입출력장치에 접근하는 주소와 메모리에 접근하는 주소가 별개
인터럽트 기반 입출력 : 다중 인터럽트
키보드, 마우스 등등 앞서 언급한 하드웨어 인터럽트를 말함
→ 인터럽트 발생 시 인터럽트 서비스 루틴을 순차적으로 실행
인터럽트에도 우선순위가 존재하며 이 순위에 따라 순차적으로 처리
- 다중 인터럽트를 처리하기 위해 PIC(프로그래머블 인터럽트 컨트롤러)를 사용
- PIC는 일반적으로 많은 하드웨어 인터럽트를 관리하기 위해 2계층으로 운용됨
DMA 입출력
유일하게 CPU를 거치지 않고도 입출력장치와 메모리가 상호작용할 수 있는 방식
- 직접 메모리에 접근하는 입출력 기능
- DMA 컨트롤러라는 하드웨어를 통해 시스템 버스에 연결, 입출력장치들의 장치 컨트롤러는 I/O 버스에 연결됨
과정
1. CPU가 DMA 컨트롤러에게 입출력에 필요한 정보를 포함하여 명령
2. DMA 컨트롤러는 CPU 대신 장치 컨트롤러와 상호작용하며 입출력 장치 대행
- 필요한 경우 메모리에 직접 접근3. DMA 컨트롤러는 입출력이 종료되면 CPU에게 인터럽트로 작업 완료를 알림
Appendix
자바에서의 '즉시 쓰기'
멀티스레드 환경에서 스레드 각각의 캐시가 존재해 메모리 상의 공유 변수를 사용 시
main 스레드에서 변경한 공유 변수가 실제로는 메인 메모리에 반영되지 않고, 캐시 메모리에만 반영되어
work 스레드에서는 변수가 최신화 되지 않는 상황이 발생한다.
이를 메모리 가시성 문제라고 함
자바에서는 캐시 메모리를 사용하지 않고 메인 메모리에 직접 접근할 수 있는 키워드 'volatile' 가 있다.
volatile 키워드가 붙은 변수는 캐시 메모리에 저장되지 않고 메인 메모리에서 직접 관리되며 변경 및 조회가 일어난다.
volatile의 또다른 기능
메모리의 가시성을 보장해주는 volatile 키워드에는 또다른 기능이 있는데, 바로 '메모리 배리어' 이다.
CPU는 반복되는 코드를 자동으로 최적화 하는 기능이 있다.
이때, CPU는 최적화를 위해 서로 관계가 없다고 자체적으로 판단한 코드의 순서를 임의로 바꾼다.
package thread.control;
import java.util.concurrent.*;
class Program {
static int x = 0;
static int y = 0;
static int r1 = 0;
static int r2 = 0;
static void Thread_1() {
y = 1; // Store
r1 = x; // Load
}
static void Thread_2() {
x = 1; // Store
r2 = y; // Load
}
public static void main(String[] args) throws InterruptedException {
int count = 0;
while (true) {
x = y = r1 = r2 = 0;
count++;
// 두 개의 스레드를 각각 실행
Thread t1 = new Thread(Program::Thread_1);
Thread t2 = new Thread(Program::Thread_2);
// 스레드 시작
t1.start();
t2.start();
// 두 스레드가 모두 끝날 때까지 대기
t1.join();
t2.join();
// r1과 r2가 모두 0이면 루프 탈출
if (r1 == 0 && r2 == 0) {
break;
}
}
System.out.println(count + "번 만에 빠져 나옴!");
}
}
- 다음과 같이 공유자원을 변경하여 if문에 걸려 무한 루프를 돌 것 같은 코드가 있다.
- 하지만 실제로 실행하면 몇번을 실행하든 결국 탈출한다.
- CPU가 코드를 최적화 해버렸기 때문!
- y = 1이 메인 메모리에 즉시 반영되지 않고, r1 = x 가 먼저 반영되어 x 가 0일 수 있다
- 반대의 상황도 마찬가지
- 이 두개가 합쳐져 루프를 탈출
이와 같이 의도하지 않은 코드 최적화를 방지하기 위해 volatile 키워드를 사용하면 write, read 연산에 대해
자동으로 메모리 배리어가 삽입된다.
이외에도
Thread.MemoryBarrier(); 을 사용해 CPU가 최적화 하지 못하도록 막을 수 있음
실행 결과
'Study > CS' 카테고리의 다른 글
3. 운영체제 (2) (0) | 2025.03.06 |
---|---|
3. 운영체제 (1) (0) | 2025.02.27 |
2. 컴퓨터 구조 (1) (0) | 2025.02.12 |