(클릭 후 이동하기)
이번 포스트는 깃허브에서 마크다운 버전으로도 확인 가능 합니다.
- 이 책은 추천을 받아서 학습한 책이며
- 이후 공유하고자 핵심 내용들을 정리해보았습니다.
- 아래 내용들을 "이미" 알고 계시는 분들은 이번 내용으로 복습한다 생각하시고 훑고나서 다른 책을 읽는 것을 추천드립니다.
목차 Table of Contents
- CHAPTER 1 컴퓨터 시스템의 개요
- CHAPTER 2 사용자 모드로 구현되는 기능
- CHAPTER 3 프로세스 관리
- CHAPTER 4 프로세스 스케줄러
- CHAPTER 5 메모리 관리
- CHAPTER 6 메모리 계층
- CHAPTER 7 파일시스템
- CHAPTER 8 저장 장치
- 마치며
1장 컴퓨터 시스템의 개요
컴퓨터 시스템이 동작할 때 하드웨어가 동작하는 순서
- 입력장치 혹은 네트워크 어댑터를 통해서
- 컴퓨터에 무언가 처리요청이 들어옵니다.
- 메모리에 있는 명령을 읽어 CPU에서 실행하고
- 그 결과값을 다시 메모리의 다른 영역에 기록합니다.
- 그리고
- 메모리의 데이터를 하드디스크나 SSD 등의 저장 장치에 기록 또는
- 네트워크를 통해 다른 컴퓨터에 전송하거나
- 디스플레이 등의 출력장치를 통해 사람에게 결과값을 보여줍니다.
- 1번부터 반복해서 실행합니다.
프로그램의 종류
- 애플리케이션
- 미들웨어
- OS : 여러가지 프로그램을 프로세스라고 하는 단위로 실행한다.
리눅스
- 리눅스의 중요한 역할은 외부 장치 (이하 디바이스)를 조작하는 일
- 리눅스에서는 디바이스 드라이버를 통해서만 프로세스가 디바이스를 조작할 수 있다.
- 리눅스는 디바이스의 종류가 같으면 같은 인터페이스로 조작하도록 되어있다.
- 리눅스는 CPU 에 있는 기능을 이용하여 프로세스가 직접 하드웨어에 접근하는 것을 차단합니다.
리눅스의 모드
- 커널모드
- 디바이스 접근가능
- 디바이스 드라이버는 커널 모드로 동작
- 프로세스 관리 시스템
- 프로세스 스케줄링
- 메모리 관리 시스템
- 사용자모드
- 프로세스 실행
- 프로그램 작성
- 시스템 콜
- ***프로세스가 디바이스 드라이버를 포함한 커널이 제공하는 기능을 사용하려 할 때는 시스템 콜이라고 하는 특수한 처리를 통해 요청합니다.
커널이란?
- 커널 모드에서 동작하는 OS의 핵심부분이 되는 처리를 모아 담당하는 프로그램
- 커널은 시스템에 탑재된 CPU 나 메모리등의 리소스를 관리하고 있으며 리소스의 일부를 시스템에 존재하는 각 프로세스에 적절히 분배.
OS는
- 커널만을 지칭하지 않습니다.
- 커널 이외 에도 사용자 모드에서 동작하는 다양항 프로그램으로 구성되어 있습니다.
2장 사용자모드로 구현되는 기능
시스템콜
- 프로세스는 프로세스의 생성이나 하드웨어의 조작 등 커널의 도움이 필요한 경우 시스템 콜을 통해 커널에 처리를 요청합니다.
- 다음은 시스템 콜의 종류입니다.
- 프로세스 생성, 삭제
- 메모리 확보, 해제
- 프로세스 간 통신(IPC)
- 네트워크
- 파일시스템 다루기
- 파일 다루기 (디바이스 접근)
CPU의 모드 변경
- 시스템 콜은 CPU의 특수한 명령을 실행해야만 호출됩니다.
- 프로세스는 보통 사용자 모드로 실행되고 있지만 커널에 처리를 요청하고자 시스템 콜을 호출하면 CPU 에서는 인터럽트 이벤트가 발생된다.
- 인터럽트 이벤트가 발생시 CPU는 사용자 모드에서 커널 모드로 변경되어 요청한 내용을 처리하기 위해 커널은 동작하기 시작합니다.
- 요청한 내용의 처리가 끝나면 시스템 콜 처리가 종료되어 다시 사용자 모드로 돌아가 프로세스의 동작을 계속 진행합니다.
리눅스의 지표 분석 커맨드 : sar & sysstat
$ sar -p ALL 1 #---- 실행해보면
[root@vincekim ~]# sar
Linux 2.6.32.59-0.7-xen (jmnote) 10/24/12 _x86_64_
00:00:01 CPU %user %nice %system %iowait %steal %idle
00:10:01 all 20.63 0.00 11.65 2.17 0.08 65.46
00:20:01 all 9.32 0.00 4.34 1.99 0.05 84.30
00:30:01 all 11.32 0.00 6.61 1.17 0.13 80.77
... (생략)
Average: all 10.28 0.00 6.03 2.03 0.12 81.53
결과값 설명
- %user 와 %nice
- 사용자모드에서 프로세스를 사용하고 있는 시간의 비율
- %system
- CPU 코어가 커널 모드에서 시스템 콜 등의 처리를 실행하고 있는 시간의 비율
- %idle
- CPU 상의 프로세스도 커널도 움직이지 않고 있는 아이들의 상태를 의미
- 시스템 콜의 OS Wrapper 함수
- 리눅스에 는 프로그램의 작성을 도와주기 위해 프로세스 대부분에 필요한 여러 라이브러리 함수가 있습니다.
- 시스템 콜은 보통의 함수 호출과는 다르게 C언어 등의 고급언어에서는 직접 호출이 불가능 합니다. 아키텍처에 의존하는 어셈블리 코드를 사용해 호출할 필요가 있습니다.
3장 프로세스 관리
프로세스 생성의 목적
- 리눅스에서는 여러가지 목적으로 프로세스를 생성합니다.
- 목적 1 :
- 같은 프로그램의 처리를 여러 개의 프로세스가 나눠서 처리.
- 예를들어 웹서버 처럼 요청이 여러 개 들어왔을 때 동시에 처리해야 하는 경우
- 목적 2 :
- 전혀 다른 프로그램을 생성합니다.
- 예를들어 bash 로부터 각종 프로그램을 새로 생성하는 경우
- 그리고 해당 목적에
- fork와 execve() 함수를 사용합니다.
- 시스템 내부에서는 clone 과 execve 시스템 콜을 호출합니다.
fork() 함수
- 같은 프로그램의 처리를 여러개의 프로세스가 나눠서 처리합니다.
- 위 목적 1에는 fork() 함수만을 사용합니다.
- fork 함수에서는 새로운 프로세스가 생성됩니다.
- 생성전의 프로세스를 부모 프로세스
- 새롭게 생성된 프로세스를 자식 프로세스라고 부릅니다.
- 순서
- 자식 프로세스용 메모리 영역을 작성하고 거기에 부모 프로세스의 메모리를 복사.
- fork() 함수의 리턴 값이 각기 다른 것을 이용하여 부모 프로세스와 자식 프로세스가 서로 다른 코드를 실행하도록 분기.
execve() 함수
- 전혀 다른 프로그램을 생성할 때에는 execve 함수를 사용합니다.
- 커널이 각각의 프로세스를 실행하기까지의 흐름을 살펴보면
- 실행 파일을 읽은 다음 프로세스의 메모리 맵에 필요한 정보를 읽어 들입니다.
- 현재 프로세스의 메모리를 새로운 프로세스의 데이터로 덮어씁니다.
- 새로운 프로세스의 첫 번째 명령부터 실행합니다.
ELF ( Excectuable and Linkable Format )
- 리눅스의 실행 파일은 실제로는 위에서 설명한 것 같은 단순한 것이 아니라 ELF 라는 형식을 사용합니다.
- ELF 형식의 각종 정보는 readelf 명령어로 자세히 살펴 볼 수 있습니다.
#----- /bin/sleep의 정보를 예로 살펴보겠습니다.
#----- "-h" 옵션을 지정하면 시작 주소를 얻을 수 있습니다.
readelf -h /bin/sleep
- 코드와 데이터 영역의 파일상의 오프셋, 사이즈, 메모리 맵 시작 주소를 얻으려면 -S 옵션을 사용합니다.
- 출력된 내용은 두 줄이 하나의 정보세트입니다.
- 수치는 전부 16진수 입니다.
- 세트 중 첫 줄의 두번째 필드가 .text 이면 코드 영역 .data 이면 데이터 영역의 정보를 의미합니다.
- 세트의 다음 위치를 보면 알 수 있는 정보들입니다.
- 코드 영역의 파일상 오프셋
- 코드 영역의 사이즈
- 코드 영역의 메모리 맵 시작 주소
- 데이터 영역의 사이즈
- 데이터 영역의 메모리 맵 시작 주소
- 엔트리 포인트
fork and exec
- fork and exec 전혀 다른 프로세스를 새로 생성할 때 사용하는 방식
- 부모가 될 프로세스로부터 fork() 함수를 호출한 다음
- 돌아온 자식 프로세스가 exec() 함수를 호출
- 프로그램 종료
- _exit() 함수를 사용
- / 내부에서는 exit_group() 시스템 콜을 호출합니다.
4장 프로세스 스케줄러
프로세스 스케줄러
- 리눅스 커널의 기능
- 여러 개의 프로세스를 동시에 동작 시킨다. (정확히는 동작시키는 것처럼 보이게 한다.)
- 설명
- 하나의 CPU 는 동시에 하나의 프로세스만 처리할 수 있습니다.
- **타임 슬라이스 : 하나의 CPU 에 여러 개의 프로세스를 실행해야 할 때 각 프로세스를 적절한 시간으로 쪼개서 번갈아 처리하는 것
- 예
- 하나의 CPU에 p0, p1, p2, p 3개의 프로세스가 있다면
- 그러면 CPU는 여러 프로세스를 횡단하며 한 순간에 하나의 프로세스 씩 동작시킵니다.
논리 CPU
- 리눅스에서 멀티코어 CPU 는 1개의 코어가 1개의 CPU 로 인식됩니다.
- 이번 책에서는 논리 CPU 라고 합니다.
- 더불어 하이퍼스레드 기능이 있으면 각 코어 내의 각각의 하이퍼스레드가 논리 CPU 로 인식됩니다.
예시
- 실험 : 프로세스를 1, 2, 4개로 나눠서 하나의 논리 CPU 에서 동작시킨다.
- 설명 :
- 동시에 프로세스를 여러 개 실행하더라도 특정 순간에 논리 CPU 에서 동작되는 프로세스는 1개뿐
- 논리 CPU 에는 여러 개의 프로세스가 순차적으로 1개씩 동작합니다.
- *** 첫 번째 프로세스부터 마지막 프로세스까지 프로세스가 한 바퀴 다 돌고 나면 다시 첫 번째 프로세스 부터 동작하는 라운드로빈 방식으로 동작하고 있습니다.
- 각 프로세스는 대략 같은 타임 슬라이스를 가집니다.
- 프로세스를 종료할 때까지의 경과 시간은 프로세스 수에 비례하여 증가합니다.
컨텍스트 스위치
- 논리 CPU 상에서 동작하는 프로세스가 바뀌는 것을 "컨텍스트 스위치"라고 부릅니다.
- 컨텍스트 스위치에서는 프로세스가 어떤 프로그램을 수행 중이더라도 타임 슬라이스를 모두 소비하면 발생합니다.
- 특정 프로그램을 처리중에 중간에 컨텍스트 스위치가 발생해서 다른 프로세스가 움직였을 가능성도 있어 라는 다른 관점을 가져야합니다.
프로세스의 상태
- ps ax 명령어로 시스템에 존재하는 프로세스를 한 줄씩 목록으로 확인할 수 있습니다.
- 각각의 환경마다 결과값은 다르게 출력되며 실행할 때마다 미묘하게 수치가 바뀜.
- ' | wc -l ' 파이프로 갯수를 새어볼 수 있습니다.
$ ps ax | wc -l
364
프로세스의 상태들
- 상태 종류
- 실행 상태 : 현재 논리 CPU 를 사용하고 있다.
- 실행 대기 상태 : CPU시간이 할당 되기를 기다리고 있습니다.
- 슬립상태 : 이벤트가 발생 하기를 기다리고 있으며 이벤트 발생 까지는 CPU 시간을 사용하지 않습니다.
- 이벤트의 예
- 정해진 시간이 경과하는 것을 기다린다. (예, 3분 대기)
- 키보드나 마우스 같은 사용자 입력을 기다립니다.
- HDD나 SSD 같은 저장 장치의 읽고 쓰기의 종료를 기다립니다.
- 네트워크의 데이터 송수신의 종료를 기다립니다.
- 이벤트의 예
- 좀비상태 : 프로세스가 종료한 뒤 부모 프로세스가 종료 상태를 인식할 때까지 기다리고 있습니다.
프로세스의 상태 확인하기
- 각 프로세스의 상태는 ps ax 를 실행했을 때 출력되는 결과의
- 세 번째 필드인 'STAT' 필드의 첫 문자를 보면 알 수 있습니다.
$ ps ax
PID TT STAT TIME COMMAND
1 ?? Ss 0:50.20 /sbin/launchd
69 ?? Ss 0:01.29 /usr/sbin/syslogd
70 ?? Ss 0:01.35 /usr/libexec/UserEv
- STAT 필드 타입
- R : 실행 상태 혹은 실행 대기 상태
- S 혹은 D
- 슬립 상태,
- S 시그널에 따라 실행상태로 되돌아오는 것이 D
- D 그렇지 않은것 / 후자는 주로 저장장치의 접근 대기
- D 상태에 있는 프로세스는 일반적으로 수 밀리초정도 지나면 다른 상태로 바뀝니다.
- 장시간을 D 상태로 있다면 다음과 같은 원인을 생각해볼 수 있습니다.
- 스토리지의 I/O가 종료되지 않은 상태로 되어있습니다.
- 커널 내에 뭔가 문제가 발생하고 있습니다.
- Z : 좀비 상태
프로세스의 상태변환의 순서
- 프로세스 생성
- 대기 / 실행 / 슬립 상태를 오고간 후
- 좀비상태
- 프로세스 종료
idle 상태
- 어떤 논리 CPU에서 동작하지 않은 시간이 있다면
- 이때 논리 CPU에서는 idle 프로세스라고 하는 아무것도 하지 않는 특수한 프로세스가 동작하고 있습니다.
idle 프로세스의 가장 단순한 구현
- CPU의 특수한 명령어를 이용하여 논리 CPU를 휴식 상태로 만들어 하나 이상의 프로세스가 실행가능한 상태가 될 때까지 아무 의미 없는 루프를 하는 것
- 위 처럼 만들면 전력만 낭비하기 때문에 이렇게 구현하지는 않습니다.
- 그 대신 CPU의 특수한 명령을 이용하여 논리 CPU를 휴식 상태로 만들어 하나 이상의 프로세스가 실행 가능한 상태가 될 때까지 소비 전력을 낮춰 대기상태로 만듭니다.
- 논리 CPU가 소비 전력이 낮은 idle 상태로 오래 있기 때문입니다.
- 노트북이나 스마트폰으로 아무것도 하지 않는 상태일 때 배터리가 오래가는 이유
sar 커맨드
- 커맨드를 사용하면 단위 시간당 논리 CPU가 얼마나 idle 상태가 되는지, 어느 정도 계산 리소스에 여유가 있는지를 확인할 수 있습니다.
- 이전예제 참고
sar -P ALL 1
중요 포인트
- 논리 CPU 로 한 번에 실행할 수 있는 프로세스는 1개뿐 입니다.
- 슬립 상태에서는 CPU 시간을 사용하지 않습니다.
성능지표 : Throughput & Latency
- 스루풋 :
- 단위 시간당 처리된 일의 양
- 높을수록 좋다
- 완료한 프로세스의 수 / 경과시간
- idle 상태의 시간이 적어지면 적어질 수록 쓰루풋이 높아진다.
- 프로세스를 여러개 사용하면 프로세스의 슬립상태 (논리 CPU의 idle 상태)가 줄어들며 스루풋이 높아진다.
- 레이턴시 :
- 각각의 처리가 시작부터 종료까지의 경과된 시간으로 짧을수록 좋습니다.
- 처리 종료 시간- 처리 시작 시간
- 요점 :
- 이러한 성능지표는 논리 CPU 뿐만 아니라 저장 장치 등의 다른 성능에서도 중요합니다.
- 논리 CPU의 능력을 전부 활용, 즉 모든 CPU가 idle 상태가 되지 않는 경우에는 프로세스 갯수를 늘려도 스루풋은 변하지 않습니다.
- 프로세스를 늘릴 수록 레이턴시는 악화된다.
- 각 프로세스의 평균 레이턴시는 비슷합니다.
실제시스템도 같나요?
- 이상적인 상황에서는 CPU가 항상 움직이는, 즉 idle 상태가 없는 경우와 실행 대기 상태의 프로세스가 없는 경우에 스루풋과 레이턴시 모두 최대가 됨을 알 수 있습니다.
- 하지만 현실에서는 실제 시스템에 돌아가는 논리 CPU는 아래과 같은 상태를 정신없이 오고 갑니다.
- idle :
- 논리 CPU가 쉬고 있기때문에 스루풋이 떨어진다.
- 프로세스가 동작중 :
- 실행 대기의 프로세스가 없기 때문에 이상적인 상태입니다.
- 이러한 상태는 다음의 프로세스가 실행 가능한 상태가 되면, 2개의 프로세스의 레이턴시가 양쪽 다 길어집니다.
- 프로세스가 대기 중 :
- 실행 대기 프로세스가 있습니다.
- 스루풋은 높지만 레이턴시가 길어지는 경향이 있습니다.
- idle :
- 그러므로 스루풋과 레이턴시는 서로 trade-off 관계 에 있는 경우가 많습니다.
- 실제로 시스템을 설계할 때에는 성능 목표로 필요한 스루풋과 레이턴시를 정의한 뒤에, 특정 목표치 설정 후 이를 달성할 수 있는 시스템을 튜닝해야합니다.
논리 CPU가 여러 개일 때 스케줄링
- 로드밸런서 혹은 글로벌 스케줄 : 여러 개의 논리 CPU에 프로세스를 공평하게 분배해주는 역할을 한다.
실험 : 1개, 2개, 그리고 4개의 프로세스를 하나의 CPU에서 실행 시
- 정리
- 1개의 CPU에 동시에 처리되는 프로세스는 1개입니다.
- 여러 개의 프로세스가 실행 가능한 경우 각각의 프로세스를 적절한 길이의 시간(타임슬라이스) 마다 CPU에서 순차적으로 처리합니다.
- 멀티코어 CPU 환경에서는 여러 개의 프로세스를 동시에 동작시키지 않으면 스르풋이 오르지 않습니다.
- '코어가 n 개 있으므로 성능이 n배' 라고 말할 수 있는건 어디까지나 최선의 케이스인 경우입니다.
경과 시간과 사용 시간
- time 명령어를 통해서 프로세스를 동작시키면 프로세스의 시간부터 종료까지의 시간 사이에 경과시간과 사용시간이라는 두 가지 수치를 얻을 수 있습니다.
- 경과시간 :
- 프로세스가 시작해서 종료할 때 까지의 시간
- 사용시간 :
- 프로세스가 실제로 논리 CPU 를 사용한 시간
# 프로세스 하나를 실행시키기는 예재
$ time taskset -c 0 ./sched 1 1000 10000
- time 의 아웃풋
- 경과시간 : 'real' 의 값
- 사용시간 : 그 아래 'user' 와 'sys' 의 수치의 합
- 'user' 의 값은 프로세스가 실행 중인 사용자 모드에서 CPU를 사용한 시간.
- 'sys'의 값은 프로세스의 실행중에 사용자 모드의 처리로부터 호출된 커널이 시스템 콜을 실행한 시간
경과 시간과 사용 시간
- 경과시간은 동일한데 사용시간이 두배가 될 수 있는 케이스
- CPU1개에서 2개의 프로세스를 작업한것과
- CPU2개에서 4개의 프로세스를 작업
- 경과시간 모든 CPU 에 1개의 CPU 당 2개의 프로세스가 할당되기 때문에 동일하다
- 사용시간은 2배
- CPU가 사용된 시간은
- 1개가 10초걸린다면
- 2개면 10초 +10초
- 슬립을 10초 사용하면?
- 경과시간은 10초 더 늘어나지만
- 사용시간은 변하지 않는다.
time 외에 다른 방법으로 프로세스의 경과 시간과 사용 시간을 얻는 방법
## 프로세스아이디, 명령어이름, 경과시간, 사용시간
ps -eo pid,comm,etime,time
- 논리 CPU 1개에서 같은 프로세스 2개를 실행하기
$ taskset -c 0 python3 ./loop.py &
[1] 21304
$ taskset -c 0 python3 ./loop.py &
[2] 21306
$ ps -eo pid,comm,etime,time | grep python3
21304 python3 00:19 00:10 #------- 사용시간이 경과 시간의 약 절반
21306 python3 00:19 00:10 #------- 사용시간이 경과 시간의 약 절반
$ kill 21304 21306
- 프로세스당 사용시간이 경과 시간의 약 절반이 된다.
- 논리 CPU 0을 프로세스 두 개가 나누고 있기 때문에
nice() : 프로세스의 우선순위 변경
- nice() 시스템 콜
- 특정 프로세스에 우선 순위를 부여하는 것
- 기본값은 0
- 범위는 -19 ~ 20까지
- -19가 가장 우선순위가 높고 20이 가장 낮음.
- 우선순위가 높은 프로세스는 평균보다 더 많은 CPU시간을 배정 받고
- 반대로 낮은 프로세스는 적게 받는다.
- 우선 순위 변경 권한
- 우선 순위를 내리는 것은 모든 사용자가 가능하지만
- 낮추는것은 root 유저만 가능
sar 출력시 나오는 %nice 필드는 우선순위를 디폴트 값인 0부터 변경한 프로그램을 실행한 시간의 할당량을 나타냅니다.
5장 메모리 관리
- 리눅스는 커널의 메모리 관리 시스템으로 시스템에 탑재된 메모리를 관리합니다.
- 메모리는 각 프로세스가 사용하는 것은 물론이고 커널 자체도 메모리를 사용합니다.
free
- 메모리의 통계 정보
- '시스템의 총 메모리 양'과 '사용 중인 메모리의 양'은 free 명령어로 확인할 수 있습니다.
$ free
total used free shared buff/cache available
Mem: 2036732 422452 555552 400688 1058728 1147872
Swap: 1048572 0 1048572
free 출력에서 중요한 필드들 (KB 킬로바이트)
- total
- 시스템에 탑재된 전체 메모리 용량.
- 위의 예에서는 2 기가바이트
- free
- 표기상 이용하지 않는 메모리 (available 필드 참조)
- buff/cache
- 버퍼 캐시 또는 페이지 캐시(6장 참고)가 이용하는 메모리 시스템의 빈 메모리(free 필드 값)가 부족하면 커널이 해제합니다.
- available
- 실직적으로 사용 가능한 메모리.
- free 필드 값의 메모리가 부족하면 해제되는 커널 내의 메모리 영역 사이즈를 더한 값으로,
- 해제될 수 있는 메모리에선 버퍼 캐시나 페이지 캐시의 대부분 혹은 다른 커널내의 메모리 일부가 포함됩니다.
- Swap: 나중에 설명
free 와 sar 커맨드 비교하기
- sar -r 명령어를 사용하면 두 번째 파라미터에 지정한 간격으로 메모리에 관련된 통계 정보를 얻을 수 있습니다. (초단위)
- free 명령어 | total | free | buff & cache | available |
- sar -r 명령어 | 없음 | kbmemfree | kbbuffers + kbcached | 없음 |
메모리 부족
- 메모리 사용량이 증가하면 비어있는 메모리가 점점 줄어듭니다.
- 메모리 관리 시스템은 커널 내부의 해제 가능한 메모리 영역을 (복잡한 과정을 거쳐) 해제합니다.
- 이후에도 메모리 사용량이 계속 증가하면 시스템은 메모리가 부족해 동작할 수 없는 OOM(Out of memory) 상태가 됩니다.
- 이러한 경우, 메모리 관리 시스템에는 적절한 프로세스를 선택해 강제 종료 ( kill ) 하여 메모리 영역을 해제시키는 OOM Killer라는 무서운 기능이 있습니다.
- 개인용 컴퓨터라면 이러한 동작이 일어나도 큰 문제가 되지 않겠지만 업무용 서버라면 어떤 프로세스가 강제 종료 될지 모른다는 것은 매우 심각한 문제입니다.
- 물론 특정 프로세스에 OOM Killer가 실행되지 않게 조치할 수도 있습니다.
- 하지만 업무용 서버에서 강제 종료 했을 때 문제가 없는 프로세스를 판별하는 일은 거의 불가능한 일입니다.
- 따라서 systemctl 의 'vm.panic_on_oom" 파라미터의 기본값을 변경해서 메모리가 부족하면 프로세스가 아니라 시스템을 강제종료하는 일도 있습니다.
단순한 메모리할당
- 커널이 프로세스에 메모리를 할당하는 일은 크게 두 가지 타이밍에 벌어집니다.
- 프로세스를 생성할 때 ( 이미 설명했으므로 생략 )
- 프로세스를 생성한 뒤 추가로 동적 메모리를 할당할 때
프로세스를 생성한 뒤 추가로 동적 메모리를 할당할 때
- 프로세스가 생성된 뒤 추가로 메모리가 더 필요하면 프로세스는 커널에 메모리 확보용 시스템 콜을 호출해서 메모리 할당을 요청합니다.
- 커널은 메모리 할당 요청을 받으면 필요한 사이즈를 빈 메모리 영역으로부터 잘라내 그 영역의 시작 주소값을 반환합니다.
- 위와 같은 메모리 할당 방법에는 다음과 같은 문제점이 있습니다.
- 메모리 단편화 memory fragmentation
- 다른 용도의 메모리에 접근 가능
- 여러 프로세스를 다루기 곤란.
단순 메모리 할당 방법의 문제점 : 메모리 단편화
- 프로세스가 생성된 뒤 메모리의 획득, 해제를 반복하면 메모리 단편화 문제가 발생합니다.
- 메모리 단편화 : 메모리에 빈 메모리 영역이 쪼개져서 위치하여 이보다 하나로 크게 연결된 메모리 영역이 필요할 경우 메모리 할당이 불가
- 예를들어
- 비어있는 메모리 100바이트가 여러군데에 존재하면 100바이트 보다 큰 영역을 확보하려고 하면 실패.
- 3개의 영역을 하나처럼 다룬다면 괜찮겠지만 다음과 같은 이유로 불가능함.
- 프로그램은 메모리를 획득할 때마다 얻은 메모리가 몇 개의 영역으로 나누어져 있는지 확인해야 하므로 매우 불편합니다.
- 100바이트의 빈 메모리 영역이100바이트보다 큰 하나의 데이터, 예를들어 300바이트의 배열을 만드는 용도로는 사용할 수 없습니다.
단순 메모리 할당 방법의 문제점 : 다른 용도의 메모리에 접근
- 프로세스가 커널이나 다른 프로세스가 사용하고 있는 주소를 직접 지정하면 그 영역에 직접 접근할 수 있습니다.
- 이 방식은 데이터가 오염되거나 파괴될 위험성이 있습니다.
- 어떤 식으로든 커널의 데이터가 망가지면 시스템은 정상적으로 작동하지 못합니다.
단순 메모리 할당 방법의 문제점 : 여러 프로세스를 다루기 곤란
- 여러 개의 프로세스를 동시에 움직이는 경우를 생각해보면 각 프로그램이 각자 동작할 주소가 겹치지 않도록 프로그래밍할 때마다 주의해서 만들어야합니다.
가상 메모리
- 가상메모리는 오늘날 CPU의 세가지 문제 (메모리 파편화, 다른 용도의 메모리에 접근, 여러 프로세스를 다루기 곤란) 를 해결하고자 탑재된 기능입니다.
- 간단하게 설명하면 프로세스는
- 시스템에 탑재된 메모리에 직접 접근하지 않고
- 실제 물리적 메모리의 주소가 매핑된 "가상 주소" 라는 주소를 사용하여 간접적으로 접근합니다.
- 정의
- 프로세스에 보이는 메모리 주소를 "가상주소"
- 시스템에 탑재된 메모리의 실제주소를 "물리주소"
- 주소에 따라서 접근 가능한 범위를 "가상 주소 공간"이라고 부릅니다.
페이지 테이블
- 가상주소에서 물리주소로 변환하는 과정은 커널 내부에 보관되어 있는 "페이지 테이블"이라는 표를 사용해서 진행됩니다.
- 가상 메모리는 전체 메모리를 페이지라는 단위로 나눠서 관리하고 있어서 변환은 페이지 단위로 이루어집니다.
- 페이지 테이블에서 한 페이지에 대한 데이터를 "페이지 테이블 엔트리"라고 부르며, 이 페이지 테이블 엔트리에는 가상 주소와 물리 주소의 대응 정보가 들어 있습니다.
- 페이지 사이즈는 CPU 아키텍처 마다 다르고 흔히 사용하는 x86_64 아키텍처의 페이지 사이즈는 4킬로바이트입니다.
- 프로세스가 가상주소에 접근하면 CPU 는 커널의 물리 주소 변환을 통해 자동으로 페이지 테이블의 내용을 참조하여 매핑 된 물리 주소로 접근하도록 변환합니다.
페이지 폴트 (Page Fault)
- 프로세스가 점유하는 가상 주소 외의 가상 메모리에 접근하면 Page Fault 라는 인터럽트가 발생합니다.
- 페이지 폴트에 의해 현재 실행중인 명령이 중단되면 커널 내의 "페이지 폴트 핸들러" 라는 인터럽트 핸들러가 동작합니다.
- 커널은 프로세스의 메모리 접근이 잘못 되었다는 내용을 페이지 폴트 핸들러에 알려줍니다.
- 그 뒤에 "SIGSEGV" 시그널을 프로세스에 통지합니다.
- 이 시그널을 받은 프로세스는 강제로 종료됩니다.
- (보통 메모리 주소를 직접 다루는 C언어에서 발생, 나머지는 라이브러리나 언어의 처리부분에 문제가 있으면 발생 가능합니다)
프로세스에 메모리를 언제, 어떻게 할당하는가?
프로세스에 메모리를 언제, 어떻게 할당하는가? : 1 프로세스를 생성할때
- 프로그램을 실행하는데 필요한 사이즈는 (코드 영역 사이즈 + 데이터 영역 사이즈)의 합 입니다.
- ***리눅스에서 실제 물리 메모리 할당은 좀 더 복잡한 "디맨드 페이징"이라는 방식을 사용합니다.
- 계속해서 프로세스를 위한 페이지 테이블을 만들고 가상 주소 공간을 물리 주소 공간에 매핑합니다.
- 마지막으로 엔트리 포인트의 주소에서 실행을 시작하면 됩니다.
프로세스에 메모리를 언제, 어떻게 할당하는가? : 2 추가적인 메모리 할당
- 프로세스가 새 메모리를 요구하면, 커널은 새로운 메모리를 할당하는데,
- 해당 작업은
- 대응하는 페이지 테이블을 작성한 후
- 할당된 메모리(물리주소)에 대응 하는 가상 주소를 반환하며 진행됩니다.
고수준 레벨에서의 메모리 할당
- C 언어의 표준 라이브러리에 있는 malloc 함수가 메모리 확보 함수입니다. (memory allocation)
- 리눅스에서는 내부적으로 malloc() 함수에서 mmap() 함수를 호출하여 구현하고 있습니다.
- mmap 함수는 페이지 단위로 메모리를 확보하지만 malloc 함수는 바이트 단위로 메모리를 확보합니다.
- 바이트 단위로 메모리를 확보하기 위해서 glibc에서는 사전에 mmap() 시스템 콜을 이용하여 커다란 메모리 영역을 확보하여 메모리 풀을 만듭니다.
- 프로그램에서 malloc이 호출되면 메모리 풀로부터 필요한 양을 바이트 단위로 잘라내서 반환하는 처리를 합니다.
- 풀로 만들어 둔 메모리에 더 이상 빈 공간이 없어지면 다시 한번 mmap()을 호출하여 새로운 메모리 영역을 확보합니다.
리눅스의 단순 메모리 할당 방법의 문제점에 대한 해결 방법 정리
- 1 메모리 단편화의 문제
- 프로세스 페이지 테이블을 효율적으로 설정하여 물리 메모리에 단편화되어 있는 영역을 프로세스의 가상 주소 공간에서는 하나의 큰 영역처럼 보이게 할 수 있다.
- 2 다른 용도의 메모리에 접근 가능한 문제
- 가상 주소 공간은 프로세스 별로 만들어집니다.
- 페이지 테이블도 프로세스 별로 만들어집니다.
- 실제로는 구현상의 이유로 커널의 메모리에 대해 모든 프로세스가 가상주소 공간에 매핑되어 있습니다.
- 그러나 커널 자체가 사용하는 메모리에 대응하는 페이지 테이블 엔트리는 CPU가 커널 모드로 실행할 때만 접근이 가능한 "커널 모드 전용" 이라는 정보가 추가되어 있습니다.
- 그러므로 사용자 모드로 동작하는 프로세스는 접근할 수 없습니다.
- 3 여러 프로세스를 다루기 곤란한 문제
- 가상 주소 공간은 프로세스마다 존재합니다.
- 따라서 각 프로그램은 다른 프로그램과 주소가 겹치는 것을 걱정할 필요 없이 각자 전용 주소 공간에서 동작하는 프로그램을 만들면 됩니다.
- 또한 자기 자신이 사용할 메모리가 물리 메모리의 어디에 놓이는가는 전혀 신경 쓰지 않아도 됩니다.
가상 메모리의 응용
- 가상 메모리를 응용한 중요한 동작 방식
- 파일맵
- 디맨드 페이징
- Copy on Write 방식의 고속 프로세스 생성
- 스왑
- 계층형 페이지 테이블
- Huge Page
파일 맵
- 일반적으로 프로세스가 파일에 접근할 때는 파일을 연 뒤, read, write, lseek 등의 시스템 콜을 사용합니다
- 뿐만아니라 리눅스에서는 파일의 영역을 가상 주소 공간에 메모리 매핑하는 기능이 있습니다.
- mmap 함수를 특정한 방법으로 호출하면 파일의 내용을 가상 주소 공간에 매핑 할 수 있습니다.
- 매핑된 파일은 메모리 접근과 같은 방식으로 접근이 가능합니다.
- 접근한 영역은 나중에 적절한 타이밍에 저장 장치 내에 파일에 써집니다.
디맨드 페이징
- 앞에서 프로세스가 생성될 때 혹은 그 뒤에 mmap 시스템 콜로 프로세스에 메모리를 할당 했을 때 다음과 같은 순서로 진행된다고 설명했습니다.
- 커널이 필요한 영역을 메모리에 확보합니다.
- 커널이 페이지 테이블을 설정하여 가상 주소 공간을 물리 주소 공간에 매핑합니다.
- 이러한 방법은 메모리를 낭비하는 단점이있습니다.
- 왜냐하면 확보한 메모리 중에는 메모리를 확보한 때부터 혹은 프로세스가 종료할 때 까지 사용하지 않는 영역이 있기 때문입니다.
- 그러한 영역들 중 대표적으로 아래 두가지가 있습니다.
- 커다란 프로그램 중 실행에 사용하지 않는 기능을 위한 코드 영역과 데이터 영역
- glibc가 확보한 메모리 맵중 유저가 malloc 함수로 확보하지 않은 부분
- 이러한 문제를 해결하기 위해 리눅스는 디맨드 페이징 (요구 페이징) 방식을 사용하여 메모리를 프로세스에 할당합니다.
- 디맨드 페이징을 사용하면 프로세스의 가상 주소 공간 내의 각 페이지에 대응하는 주소는 페이지에 처음 접근할 때 할당됩니다.
디맨드 페이징의 작동 순서
- 일단 프로세스를 생성할 때에는 가상주소공간 안에 코드영역이나 데이터영역에 대응하는 "페이지에 프로세스가 이 영역을 얻었음"이라는 정보를 기록합니다.
- 그러나 물리 메모리는 이 시점에는 할당되지 않습니다.
- 다음에 프로그램이 엔트리 포인트로부터 실행을 시작할 때에는 엔트리포인트에 대응하는 페이지용 물리메모리가 할당됩니다. 이때의 처리흐름은 다음과 같습니다.
- 프로그램이 엔트리포인트에 접근합니다.
- CPU가 페이지테이블을 참조해서 엔트리포인트가 속한 페이지에 대응하는 가상주소가 물리주소에 아직 매핑되지 않음을 검출합니다.
- CPU에 페이지폴트가 발생합니단.
- 커널의 페이지폴트 핸들러가 1번에 의해 접근된 페이지에 물리메모리를 할당하여 페이지폴트를 지웁니다.
- 사용자모드로 돌아와서 프로세스가 실행을 계속합니다.
- 프로세스는 자신이 실행하는 동안 페이지폴트 발생사실을 모르고 넘어갑니다.
- 페이지용 물리메모리가 할당된 다음 마찬가지로 아직 할당되지 않은 다른영역에 접근하게되면 다시 이전과정(메모리접근>페이지폴트>폴트지우고>메모리 할당)이 반복되며 메모리가 할당됩니다.
몇 가지 표현의 정의
- "가상 메모리를 확보했음"
- 프로세스가 mmap 함수 등을 이용하여 "가상 메모리를 확보했음" 이라고 표현합니다.
- "물리 메모리를 확보했음"
- 확보한 가상 메모리에 접근하여 물리 메모리를 확보하고 매핑하는 것을 "물리 메모리를 확보했음" 이라고 표현합니다.
- 물리 메모리 부족
- mmap 함수의 성공 여부와 상관없이 디맨드 페이징에의해 메모리를 획득하고 획득한 메모리에 쓰기를 하는 시점에 물리 메모리가 충분히 비어 있지 않으면 물리 메모리 부족이 발생하게 됩니다.
짚고가기
- 메모리를 획득하면 가상 메모리 사용량은 증가하지만 물리 메모리 사용량은 증가하지 않습니다.
- 획득한 메모리에 접근 했을때 중간에 페이지 폴트가 발생하고 물리 메모리 사용량이 증가합니다.
- 메모리 영역을 획득해도 그 영역에 실제로 접근할 때까지는 시스템의 물리 메모리 사용량은 거의 변화가 없습니다.
- 메모리에 접근이 시작되면서부터 초당 10메가바이트 정도 메모리 사용량이 증가합니다.
- 메모리 접근이 종료된 뒤에는 메모리 사용량이 변화하지 않습니다.
- 프로세스가 종료되면 메모리 사용량은 프로세스를 시작하기 전으로 돌아갑니다.
가상 메모리 부족과 물리 메모리 부족
- 프로세스는 실행 중에 메모리획득에 실패하면 종종 이상 종료를 합니다.
- 이때의 원인으로 가상 메모리 부족과 물리 메모리 부족을 들 수 있습니다.
- 가상 메모리 부족은 미사용 물리 메모리 영역에 무관하게 발생합니다.
- 가상 메모리는 가상 주소 공간이 128테라바이트나 있기 때문에 거의 발생하지 않습니다.
- 물리 메모리 부족은 시스템에 탑재된 물리 메모리를 전부 사용하면 발생합니다.
Copy on Write
- 이전에 설명한 fork 시스템콜도 가상메모리의 방식을 사용해 고속화됩니다.
- fork 시스템콜을 수행할 때는 부모 프로세스의 메모리를 자식 프로세스에 전부 복사하지 않고 페이지 테이블만 복사합니다.
- 페이지 테이블 엔트리 안에는 쓰기 권한을 나타내는 필드가 있지만 이 경우 부모도 자식도 전체 페이지에 쓰기 권한을 무효화 합니다.
- 부모 프로세스 혹은 자식 프로세스의 어느쪽이든 페이지의 어딘가를 변경하려고하면 다음과 같은 흐름으로 공유를 해제 합니다.
- 페이지에 쓰기는 허용하지 않기 때문에 쓰려고 할 때 CPU 에 페이지 폴트가 발생합니다.
- CPU 가 커널모드로 변경되어 커널의 페이지 폴트 핸들러가 동작합니다.
- 페이지 폴트 핸들러는 접근한 페이지를 다른 장소에 복사하고, 쓰려고한 프로세스에 할당한 후 내용을 다시 작성합니다.
- 부모 프로세스, 자식 프로세스 각각 공유가 해제된 페이지에 대응하는 페이지 테이블 엔트리를 업데이트합니다.
- 쓰기를 한 프로세스 쪽의 엔트리는 새롭게 할당된 물리 페이지를 매핑하여 쓰기를 허가합니다.
- 다른 쪽 프로세스의 엔트리에도 쓰기를 허가합니다.
- Copy On Write 의 특성상 fork 시스템 콜이 성공했더라도 나중에 쓰기가 발생한 뒤 페이지 폴트 핸들러가 동작할 시점에 물리 페이지에 빈 공간이 없을 경우 물리 메모리 부족이 발생합니다.
- 부모 프로세스와 자식 프로세스의 메모리 중 공유된 부분은 각 프로세스에 이중으로 계산됩니다.
- 그렇기 때문에 전체 프로세스의 물리 메모리 사용량을 다 더하면 전체 프로세스가 실제 사용량 보다 값이 더 큽니다.
스왑
- 메모리가 부족하게 되면 OOM 메모리 부족 상태가 됩니다.
- 다행히 리눅스에는 메모리 부족에 대응하는 구제 장치가 있는데 바로 가상 메모리 방식을 응용한 SWAP 입니다.
- 스왑은 저장장치의 일부를 일시적으로 메모리 대신 사용하는 방식입니다.
- 구체적으로는
- 시스템의 물리 메모리가 부족한 상태가 되어 물리 메모리를 획득할 시점에 기존에 사용하던 물리 메모리의 일부분을 저장 장치에 저장하여 빈 공간을 만들어냅니다.
- 이때 메모리의 내용이 저장된 영역을 스왑 영역이라고 부릅니다.
- 스왑 영역은 시스템 관리자가 시스템을 구축할 때 만들어 둡니다.
스왑 아웃
- 커널이 사용 중인 물리 메모리의 일부를 스왑 영역에 임시 보관하는 것
- 어느 영역이 스왑 아웃될 것인가는 앞으로 한동안 사용되지 않을 듯한 것을 커널이 특정한 알고리즘을 통해 결정합니다.
- 이 알고리즘까지는 자세히 알 필요 없기 때문에 여기서는 설명을 생략하겠습니다.
스왑인
- 시간이 흘러 프로세스가 스왑 아웃 된 페이지에 접근했을 때 커널은 스왑 영역에 임시 보관했던 데이터를 물리 메모리에 되돌립니다.
- 이것을 스왑인 이라고 부릅니다.
스와핑 = 스왑인 + 스왑아웃
- 리눅스에는 스왑의 단위가 페이지 단위이므로 페이징이라고도 부릅니다.
- 스왑인 : 스왑아웃 = 페이지인 : 페이지아웃
- *** 스래싱
- 시스템의 메모리 부족이 일시적 현상이 아니라 만성적으로 부족하다면 메모리에 접근할 때 마다 스왑인 스왑 아웃이 반복되는 스래싱 상태가 됩니다.
- 스왑핑은 문제다
- 스와핑이 자주 발생할 때에는 시스템을 다시 설계 하는 것이 좋습니다.
- 스와핑은 특히 서버에서 발생하지 않는게 좋습니다.
- 그래서 메모리 사용량을 낮추기 위해 workload 를 줄이거나, 메모리를 증설하는 방법 등으로 설계를 수정해야합니다.
- Major Fault : 스와핑과 같이 저장장치에 접근이 발생하는 페이지 폴트
- Minor fault : 저장장치에 접근이 발생하지 않는 페이지 폴트
계층형 페이지 테이블
- 앞에서 프로세스의 페이지 테이블에는 가상 주소 공간 페이지들이 전부에 걸쳐서 대응되는 물리 메모리가 존재하는가를 나타내는 데이터를 가지고 있다고 설명했습니다.
- x86_64 아키텍처의 페이지 테이블은 위에 적은 대로 1차원적인 구조로 구현되어 있지 않고 계층 구조로 되어 있습니다.
- 이를 통해 페이지 테이블에 필요한 메모리의 양을 절약하고 있습니다.
- x86_64 아키텍처에서는 페이지 테이블의 구조가 훨씬 더 복잡한 4단 구조로 되어있습니다.
- 시스템이 메모리 부족을 겪는 이유가 프로세스가 직접 사용하는 물리 메모리양의 증가 때문이 아니라, 프로세스를 너무 많이 만들었다거나 가상 메모리를 대량으로 사용하고 있는 프로세스 때문에 페이지 테이블 영역의 증가였던 경우가 종종 있습니다.
- 이 가운데 프로세스를 너무 많이 만든 경우는 프로그램의 병렬화를 줄이거나 해서 시스템에 동시에 존재하는 프로세스의 갯수를 낮추는 방법으로 처리가 가능합니다.
Huge Page
- 프로세스의 가상 메모리 사용 사이즈가 증가하면 이에 따라 그 프로세스의 페이지 테이블에 사용하는 물리 메모리양도 증가합니다.
- 이 경우 메모리 사용량의 증가 뿐만 아니라 fork 시스템 콜도 늦어집니다.
- fork 시스템콜은 copy on write로 최소한의 메모리만 복사하고 있지만 페이지 테이블은 부모의 프로세스와 같은 사이즈로 새로만들기 때문입니다.
- Huge Page는 이름 그대로 커다란 사이즈의 페이지입니다.
- 이것을 사용함으로써 프로세스의 페이지 테이블에 필요한 메모리의 양을 줄일 수 있습니다.
- Huge Page를 사용하면 가상 메모리를 많이 사용하는 프로세스에 대해서 페이지 테이블에 필요한 메모리 양이 줄어든다는 점만 기억해두시면 됩니다.
HugePage 의 사용 방법
- C언어에는 mmap 함수의 flags 파라미터에 MAP_HUGETLB 플래그 등을 넣어서 Hugepage를 획득합니다.
- 데이터베이스나 가상머신 시스템 등에는 가상 메모리를 대량으로 사용하는 설정이 들어 있는 경우가 있기 때문에 필요에 따라 사용을 검토해보길 바랍니다.
- 이런 기능을 사용하면 페이지 테이블의 메모리 사용량을 줄이고 fork 를 더 빠르게 동작하게 할 수 있습니다.
Transparent Huge Page
- 리눅스의 기능
- 가상 주소 공간에 연속된 여러 개의 4 킬로바이트 페이지가 특정 조건을 만족하면 이것을 묶어서 자동으로 Huge Page로 바꿔주는 기능.
6장 메모리 계층
컴퓨터 메모리 장치의 계층 구조
- 레지스터 < 캐시 메모리 < 메모리 < 저장 장치
( 사이즈 작음 < 큼 )
( 가격비쌈 < 저렴 )
( 접근속도 빠름 < 느림)
캐시 메모리
- 컴퓨터의 동작흐름
- 명령어를 바탕으로 메모리에서 레지스터로 데이터를 읽습니다.
- 레지스터에 있는 데이터를 바탕으로 계산합니다.
- 계산 결과를 메모리에 씁니다.
- 요즘 하드웨어는 메모리에 접근하는데 걸리는 시간(레이턴시)이, 레지스터에서 계산하는 평균시간보다 수십배로 느립니다.
- 이런점 때문에 컴퓨터 시스템에서 2번째 처리(레지스터에서 계산)가 아무리 빠르더라도
- 처리1과 처리3이 속도상 병목지점이 되어버려 전체 처리속도는 메모리에 읽고 쓰는 레이턴시와 별로 차이가 없게 됩니다.
- 캐시 메모리는 이런 레지스터 안에서 계산하는 것과 메모리에 접근하는 것, 양쪽의 처리 시간의 차이를 메우는 데 필요합니다.
- 캐시 메모리로 부터 레지스터에 접근할 때의 레이턴시는 메모리에 접근할 때와 비교해보면 수십배에서 수십배 빠른 점을 이용하여 처리1과 처리3의 속도를 고속화 합니다.
- 캐시메모리는 일반적으로 CPU 에 내장되어 있지만 CPU 밖에 있을 수도 있습니다.
캐시메모리가 가득 찬 경우
- 캐시 메모리가 가득 찬 경우, 캐시메모리에 존재하지 않는 데이터를 추가로 읽으면 기존의 캐시메모리 중 1개를 파기합니다.
계층형 캐시 메모리
- x86_64 아키텍처의 CPU는 캐시 메모리가 계층형 구조로 되어있습니다.
- 각 계층은 사이즈, 레이턴시, 어느 논리 CPU 사이에 공유하는가 등이 다릅니다.
- L1 L2 L3 등 의 이름으로 구성되어 있습니다.
- L1 가장 레지스터와 가깝고 용량이 적으며 빠른것이 L1 캐시,
- 번호가 늘어날수록 레지스터로부터 멀어지며 용량이 커지고 속도가 느려집니다.
메모리 참조의 국소성
- 프로세스의 데이터가 전부 캐시에 있는 동안에는 데이터에 접근하는 속도는 메모리에 접근하는 속도가 아니라 이보다 빠른 캐시에 접근하는 속도입니다.
- 프로그램은 대부분 메모리 참조의 국소성 이라고 하는 특성이 있습니다.
- 시간 국소성 : 특정시점에서 접근하는 데이터는 가까운 미래에 다시 접근할 가능성이 큽니다.
- 예) 루프 처리 중인 코드 영역
- 공간 국소성 : 특정 시점에 어떤 데이터에 접근하면 그 데이터와 가까운 주소에 잇는 데이터를 접근할 확률이 높다.
Translation Lookaside Buffer
- 프로세스는 다음과 같은 순서에 따라 가상 주소의 데이터에 접근합니다.
- 물리 메모리 상에 존재하는 페이지 테이블을 참고하여 가상 주소를 물리 주소로 변환합니다.
- 1에서 구한 물리 메모리에도 접근합니다.
페이지 캐시
- 캐시메모리가 메모리의 데이터를 캐싱하는 것과 유사하게 페이지 캐시는 저장장치의 데이터를 메모리에 캐싱,페이지 단위로 데이터를 다룬다.
정리
- 파일의 데이터가 페이지 캐시에 있다면 없는 경우와 비교해서 파일 접근이 매우 빨라집니다.
- 그렇게 하기 위해서는 시스템이 접근하는 파일의 사이즈나 물리 메모리의 양을 비교하여 맞추는 것이 중요합니다.
- 설정 변경이나 시간이 지나면서 시스템의 성능이 갑자기 느려졌다면 파일의 데이터가 페이지 캐시에 제대로 들어가지 못했을 수 있습니다.
- 여러가지 sysctl 파라미터를 잘 튜닝한다면 페이지 캐시의 라이트백이 자주 발생하여서 생기는 I/O 의 부하를 막을 수 있습니다.
하이퍼 스레드
- 앞에서 설명한 대로 CPU 의 계산 처리소요 시간에 비해 메모리 접근의 레이턴시가 매우 느립니다.
- 하이퍼 스레드 기능은 CPU 코어 안의 레지스터와 같은 일부 자원을 여러개 준비해두고, 시스템 입장에서는 각각 논리 CPU로 써 인식되는 하이퍼 스레드라는 단위로 분할 되는 하드웨어 기능입니다.
7장 파일 시스템
- 리눅스에서는 저장장치 안의 데이터에 접근할 때 일반적으로 직접 저장장치에 접근하지 않고 편의를 위해 파일 시스템을 통해 접근합니다.
- 파일 시스템은 사용자에게 의미가 있는 하나의 데이터를 이름, 위치 사이즈 등의 보조정보를 추가하여 파일이라는 단위로 관리합니다.
- 어느 장소에 어떤 파일을 배치할지 등의 데이터 구조는 사양으로 미리 정합니다.
리눅스의 파일 시스템
- 디렉터리
- 파일을 보관하는 특수한 파일
- 트리구조로 되어있다.
- 리눅스가 다루는 파일 시스템은 여러가지 ext4, XFS, Btrfs 등.....
- 어떠한 파일 시스템이라도 사용자가 읽기, 쓰기, 이동하기 등의 시스템 콜을 호출한다면 통일된 인터페이스로 접근이 가능합니다.
파일 시스템 접근 순서
- 프로세스 시스템콜
- 파일 시스템 공통처리
- (ext4용처리, XFS용처리 )
- 디바이스드라이버
- 저장장치
파일 시스템의 데이터
- 데이터 : 사용자가 작성한 내용
- 메타데이터 : 파일의 이름이나 위치, 사이즈 등의 보조정보
- 종류
- 시간정보
- 권한 정보
용량 제한
- quota : 파일 시스템의 용량을 용도별로 사용할 수 있게 제한하는 기능
- 종류
- 사용자 quota : 파일 소유자인 사용자 별로 제한
- 디렉터리 or 프로젝트 quota
- 서브 볼륨 quota
저널링
- 파일시스템이 깨지는 것을 방지
- 순서
- 업데이트에 필요한 아토믹한 처리 목록을 우선 저널 영역에 작성 (저널로그)
- 저널 영역의 내용을 바탕으로 실제로 파일시스템의 내용을 업데이트
- 업데이트 도중에 강제로 전원이 끊어진 경우 저널로그를 처음부터 다시 수행하면 파일 시스템의 처리는 완료된다.
- Copy on Write
블록 장치
- 단순히 읽고 쓰기 이외의 랜덤 접근이 가능
- 대표적으로 HDD, SSD 등의 저장 장치가 있다.
- 블록장치는 직접 접근하지 않고 파일시스템을 작성해서 그것을 마운트 함으로써 파일시스템을 경유해서 사용.
여러 가지 파일 시스템
메모리 기반 파일 시스템
- tmpfs : 저장장치 대신 메모리에 작성하는 파일 시스템.
- 고속으로 사용가능
- 재부팅 후에 남아 있을 필요가 없는 /tmp나 /var/run 에 사용하는 경우가 많습니다.
네트워크 파일 시스템
- 네트워크를 통해 연결된 원격 호스트에 잇는 파일에 접근
- 윈도우의 cifs
- 리눅스 등의 UNIX 계열에서 사용하는 nfs
가상 파일시스템
- procfs (process filesystem)
- /proc 이하에 마운트
- /proc/pid/ 이하의 파일에 접근함 으로써 각 프로세스의 정보를 얻을 수 있다.
- 지금까지 ps, sar , top free, 등 OS가 제공하는 각종 정보를 표시하는 명령어는 procfs 로부터 정보를 얻고 있습니다.
- sysfs (system filesystem)
- procfs 를 마구잡이로 사용하는 것을 막기 위해 이러한 정보를 배치하는 정소를 어느 정도 정하기 위해 만든 것.
- /sys 이하에 마운트
- /sys/devices 이하의 파일 : 시스템에 탑재된 디바이스에 대한 정보
- /sys/fs 이하의 파일 : 시스템에 존재하는 각종 파일시스템에 대한 정보
- cgroupfs (control group filesystem)
- 하나의 프로세스 혹은 여러 개의 프로세스로 만들어진 그룹에 대해 여러 가지 리소스 사용량의 제한을 가는 cgroup이라는 기능.
- root 만 가 접근 가능
- /sys/fs/cgroup 이하에 마운트
- cgroup 으로 제한을 걸 수 있는 리소스
- CPU
- 메모리
- 가상화에 사용
- cgroup 은 docker 등의 컨테이너 관리 소프트웨어나 virt-manager 등의 가상 시스템 관리 소프트 웨어등에 각각의 컨테이너나 가상 시스템의 리소스를 제한하기 위해 사용할 수 있습니다.
- 특히 하나의 시스템 상에 여러개의 컨테이너나 가상 시스템이 공존하는 시스템에 사용에 말이지요.
Btrfs (B-tree filesystem)
btrf의 기능
- 멀티 볼륨
- 여러 개의 저장장치 또는 파티션으로부터 거대한 스토리지 풀을 만들고 거기에 마운트 가능한 서브볼륨이란 영역을 작성합니다.
- 스토리지 풀은 LVM 으로 구현된 볼륨그룹, 서브 볼륨은 LVM 으로 구현된 논리 볼륨과 파일 시스템을 더한 것과 비슷.
- 스냅샷
- 서브볼륨단위로 스냅샷을 만들 수 있습니다.
- 스냅샷의 작성은 데이터를 전부 복사하는 것이 아닌, 데이터를 참조하는 메타 데이터의 작성 혹은 스냅샷 내의 더티 페이지의 라이트백을 하는 것만으로 처리할 수 있기 때문에 일반적인 복사보다 훨씬 빠르게 이루어 집니다.
- RAID
- RAID는 모든 데이터를 N개의 스토리지에 저장되게 함.
- 데이터의 파손 검출, 복구, 스토리지 내의 일부 데이터가 파괴된 경우 이것을 검출해 몇 가지의 RAID 구성으로 복구가 가능합니다.
8장 저장 장치
HDD의 데이터 읽기 쓰기의 동작 방식
- HDD는 데이터를 자기(마그네틱) 정보로 변환하여 그것을 플래터라고 불리는 자기 장치에 기록하는 저장장치입니다.
- 데이터는 바이트단위가 아닌 섹터라고 불리는 단위로 읽고 씁니다.
- 원판 같은 플래터를 돌리며 스윙암 범위에 들어오면 데이터 접근이 가능합니다.
SSD의 데이터 읽기 쓰기의 동작방식
- 전기적으로 읽고 쓰기를 진행함.
- HDD 보다 빠름.
블록장치 계층
- 리눅스에서는 HDD 나 SSD 등의 랜덤 접근이 가능하며 일정 단위로 접근 가능한 장치를 합쳐 블록장치라는 이름으로 분류
- 블록장치에는
- 디바이스 파일이라는 특수한 파일을 가지고 직접 접근하던가 또는 그 위에 구축된 파일시스템을 통해서 간접적으로 접근합니다.
- 대부분의 소프트웨어는 간접적으로 접근합니다.
- 각종 블록 장치에는 공통된 처리가 많기 때문에 이러한 처리는 각각의 장치에 대한 디바이스에서 구현하지않고 커널의 블록장치 계층이라는 곳에서 담당합니다.
I/O 스케줄러
- 블록장치 계층의 I/O 스케줄러 기능은 블록장치에 접근하려는 요청을 일정기간 모아둔 뒤, 다음과 같은 가공을 한 다음 디바이스 드라이버에 I/O 요청을 함으로써 I/O 성능을 향상시키는 것을 목표로 합니다.
- 병합 : 여러 개의 연속된 섹터에 대한 요청을 하나로 모음
- 정렬 : 여러개의 불연속적인 섹터에 대한 I/O 요청을 섹터번호 순서대로 정리합니다.
마치며...
김주혁에 대하여 더 알아보기
- 🌱 Blog (Medium) : https://medium.com/@beanskobe
- 🌱 Blog (티스토리) : https://vince-kim.tistory.com/
- 📫 Portfolio (포트폴리오) : https://romantic-golick-a520aa.netlify.app
- 📫 Website (웹사이트) : https://romantic-golick-a520aa.netlify.app
- ✏️ LinkedIn (링크드인) : https://www.linkedin.com/in/joo-hyuk-kim/
- 🌎 Contact me : beanskobe@gmail.com
읽어주셔서 감사합니다!
'Books' 카테고리의 다른 글
[학습 후기 & 내용 정리] "토비의 스프링" - 이일민 (0) | 2021.10.20 |
---|---|
[ 전문서적 핵심정리 ] "자바의 신" vol2 책 - by 로드북 (0) | 2021.10.10 |
[ 전문서적 핵심정리 ] "자바의 신" vol1 책 - by 로드북 (0) | 2021.10.10 |
[ 전문서적 핵심정리 ] "소프트웨어 세상을 여는 컴퓨터 과학" - by 김종훈 (0) | 2021.10.10 |
[ 전문서적 핵심정리 ] "그림으로 배우는 네트워크 원리" 책 - by 영진닷컴 (0) | 2021.10.09 |