C06 임계구역과 동기화01 임계구역과 동기화02 동기화 도구뮤텍스세마포어모니터동기화에서 발생하는 문제: 우선순위 역전03 동기화 코드 사례C07 교착상태01 교착상태의 개념과 필요조건02 교착상태 처리 방법교착상태 예방교착상태 회피교착상태 검출교착상태 회복C08 메모리와 할당01 메모리 구조와 프로그램의 실행 준비메모리와 프로그램 실행 구조컴파일과 실행 준비02 프로세스의 메모리 배치메모리 주소메모리 오버레이스왑03 메모리 할당 기법가변 분할과 고정 분할가변 분할의 메모리 관리고정 분할의 메모리 관리04 버디 시스템C09 페이징 메모리01 페이징 메모리 관리페이지 매핑 테이블과 주소 변환주소 변환 방식과 페이지 테이블 구조공유 페이지와 쓰기 시점 복사지역성의 개념과 메모리 접근 최적화02 페이지 테이블 관리연관 매핑역 매핑다단계 매핑CPU 캐시의 매핑 방식03 세그멘테이션 메모리 관리
C06 임계구역과 동기화
01 임계구역과 동기화
- 공유 자원 접근 시에는 race condition(경쟁 상태) 문제가 발생할 수 있다
- race condition: 둘 이상의 스레드가 동일한 공유 자원에 동시에 접근하여 실행 결과가 예측할 수 없게 되는 상황
- 그렇기에 critical section(임계구역)을 설정한다
- critical section: 둘 이상의 스레드가 동시에 접근해서는 안되는 공유 영역
- 이 critical section에 대한 접근을 제어 하는 과정을 syncronization(동기화)라고 한다
- 임계 구역이 문제를 가지지 않으려면 다음 3가지 조건을 만족해야 한다
- Mutual exclusion(상호 배제): 당연하게도, 한 임계구역에 스레드가 들어가면 다른 스레드는 들어갈 수 없어야 한다
- Bounded waiting(한정 대기): 임계구역에 들어가려는 스레드중 무한히 기다리는 스레드가 있어선 안된다
- Progress flexibility(진행의 융통성): 한 스레드의 작업이 임계구역에 진입하려는 다른 스레드의 진행을 방해해서는 안된다
- 즉 임계구역에 다른 스레드가 들어가 있지 않다면 들어갈 수 있어야 한다
- Mutual exclusion 위반 사례
- 위 코드에서 두 스레드 모두 while문으로 임계구역 접근여부를 확인하고 들어가며, 들어간 후에는 lock으로 잠궈 들어갈 수 없게 하고 있다
- 그러나 1과 2 사이 또는 3과 4 사이에 타임 슬라이스 아웃이 발생해 컨텍스트 스위칭이 된다면 두 스레드가 모두 임계구역으로 들어가는 문제가 발생할 수 있다
- 해결 방법
- lock을 체크하는 연산과 lock을 true로 바꾸는 연산, 두 개의 연산을 원자적으로 묶어 하드웨어 적으로 동시에 실행하면 된다
- Bounded waiting 위반 사례
- 위 코드는 mutual exclusion 문제를 SW적으로 해결하기 위한 코드로, 두 스레드가 모두 임계구역에 진입하는 문제는 발생하지 않는다
- 그러나 1번 코드가 실행되고 컨텍스트 스위칭이 발생해 3번 코드가 실행된다면 모두 락이 걸려 두 스레드 모두 임계구역에 진입하지 못하는 bounded waiting 문제가 발생한다
- Progress flexibility 위반 사례
- 위 코드는 mutual exclusion 문제와 bounded waiting 문제를 해결하기 위한 코드이다
- 다만 T1에서 임계구역을 모두 이용해서 lock=2가 된다면 스레드 T2가 작동하기 전까지는 다시 T1에서는 임계구역을 사용할 수 없는 문제가 발생한다 → 즉 progress flexibility 문제가 발생한다.
- 결론 → 처음본 1번 구조에서 하드웨어 적으로 뮤텍스 문제를 해결하자
02 동기화 도구
뮤텍스
- 뮤텍스(MUTual EXclusion): critical section에 접근하기 전에 뮤텍스를 이용해 잠금을 설정해 mutual exclusion을 보장한다
- 코드상으로는 lock(), unlock()으로 주로 표기한다
세마포어
- 세마포어: 뮤텍스가 한 자원에 한명만 접근할 수 있다면, 세마포어는 한 자원에 여러명(n개)의 접근을 허용한다
- 특히 순서대로 이를 이용할 수 있게 해주는데, 이는 자원 접근을 위해 대기하는 세마포어 큐가 존재하기 때문이다
- 코드상으로는 wait(), post()로 주로 표기하는데, 각각 뮤텍스의 lock(), unlock()에 대응한다
- 옛날에는 p(), v() 라고 했었고, post()는 signal()이라고도 부른다
- 세마포어의 내부 코드
- S는 현재 사용 가능한 자원의 개수이다
- 즉 자원 사용을 시도하고 이때 자원이 없으면 pause() 한다, 자원이 있다면 S -= 1을 하고 임계구역에 들어간다
- pause() 한 스레드는 세마포어 큐에 들어가 대기한다?
- 자원 사용이 끝나면 S += 1을 하고 wake_up()으로 pause() 중인 스레드를 꺠워준다
- 이때 각 네모안의 코드들은 HW적으로 원자적으로 실행되어야 한다
모니터
- 세마포어를 사용하는 코드는 복잡하기에 휴먼에러가 발생할 수 있다
- 이때 모든 스레드가 세마포어를 사용한다면 wait(), post()를 사용하는 대신 자동으로 임계구역을 보호하는 동기화 도구인 ‘모니터(monitor)’를 사용할 수 있다
- 이때 스레드는 직접 세마포어를 조정하지 않고 모니터를 통해서만 임계구역에 접근할 수 있다
- 모니터는 OS에서 제공하는 도구는 아니고, 언어/라이브러리에서 제공한다
- ex) Java의
synchronized
스핀락도 존재 → 이런것도 알아두면 좋다
요즘은 커널이나 시스템 장치에서 뮤텍스나 세마포어 사용을 권장하지 않는다
현대의 os는 타조? 알고리즘 → 위험에 빠지면 타조가 눈을 그냥 박는다
데드락이 나던말던 신경쓰지 않는다 → 타조 알고리즘
걍 뮤텍스/세마포어를 쓰지 말자
busy waiting 사이에 sleep 줘서 진입을 못하게 한다던가… 굳이 race condition에 빠지게 하지 말자
현대 OS는 데드락을 회복할 생각이 없다, 탐지할 생각도 없다 → 그냥 타조다 ㅋㅋ
os기반 스핀락은 원자성을 지킬 수 있는 구조로 되어있다
동기화에서 발생하는 문제: 우선순위 역전
- 위 코드에서 T1 > T5 > T9 순으로 우선순위를 가진다
- 이 때 T9가 critical section을 차지하고 있어 T1은 대기한다. 그리고 T5가 들어오면 T5는 우선순위가 T1보다 낮지만, T9보다는 높기에 T5가 실행된다
- 결론적으로 T1이 가장 우선순위가 높지만 T5 → T9 → T1 순으로 실행되는 문제가 발생한다
- 이를 priority inversion(우선순위 역전) 문제라고 한다
- 우선순위 역전 해결 방안
- priority inheritance(우선순위 상속)
- T1이 T9를 기다릴 때 T9의 우선순위를 T1까지 올려 T5가 침범할 수 없게 한다
- priority celing(우선순위 상한)
- critical section을 사용하는 모든 스레드의 우선순위를 일정한 수준까지 높인다
- 단점: 얼마나 높일지 계산하기가 어렵다. 그리고 우선순위 역전이 발생하지 않을때도 우선순위가 높아지는 문제가 있다
실제 OS에서는 어떤 방식을 사용하는지?
실제 OS에서는 → OS마다 다르다 →
실제로는 상속하는듯?
03 동기화 코드 사례
C07 교착상태
01 교착상태의 개념과 필요조건
- Deadlock(데드락, 교착상태): 둘 이상의 프로세스가 각기 자원을 점유하며 다른 자원을 기다려 작업을 더 이상 진행하지 못하는 상태
- Starvation(기아 현상)과 유사해보이지만, starvation은 우선순위가 낮은 프로세스가 자원을 계속 할당받지 못하는 상태를 의미한다
- 데드락 발생의 필요조건
- Mutex exclusion(상호 배제): 한 자원은 하나의 프로세스만 사용할 수 있다
- No preemption(비선점): 한 프로세스가 점유한 자원을 강제로 뺴앗을 수 없다
- Hold and wait(점유와 대기): 자원을 보유하고 있는 프로세스가 다른 자원을 기다리고 있어야 한다
- Circular wait(순환 대기): 프로세스 간에 자원을 기다리는 형태가 순환 구조를 이루어야 한다
- 물론 4가지 조건이 만족됐다고 무조건 데드락이 발생하는 것은 아니다(필요조건)
02 교착상태 처리 방법
교착상태 예방
데드락 발생의 필요조건을 방지함으로서 데드락을 예방할 수 있다.
- Mutex exclusion 예방
- 자원을 공유해서 사용한다 → 불가능
- No Preemption 예방
- 자원을 OS가 강제로 뺴앗을 수 있게 한다 → 어떤 기준으로 빼앗을지 결정하기 어려우며 starvation 현상이 일어난다
- Starvation 방지를 위해 aging 기법(여러번 자원을 빼앗기면 자원을 사용할 수 있게 보장하는 기법)을 사용할 수 있다 → 그럼 또 다시 No preemption이 발생해 데드락 발생이 가능하다
starvation이 일어나는게 유저 app이면 그냥 뻗은거니깐 ㅇㅇ….
trust zone: 엑세스를 전역적으로 critical section으로 관리하는 경우…
ex) netflix → audio/video를 trust zone에 밀어넣어야 한다, 효과음도 trust zone에 넣으면 ~ 뭐 그런 일들
starvation은 생각보다 흔하게 나는데, 사용자 입장에서 그냥 버그가 되버리니..
- Hold and wait 예방
- 모든 자원을 확보한 상태에서만 실행을 할 수 있게 한다
- 단점: 모든 자원이 어떤건지 미리 알기 어려우며
- 자원을 미리 점유하면 자원 활용률이 낮아지고
- 많은 자원을 사용하는 프로세스는 실행 자체가 어려워지며
- 일괄 작업 방식이되서 시스템 효율이 떨어진다
- Circular wait 예방
- 모든 자원에 번호를 붙이고 작은 번호의 자원부터 사용하게 한다 → 자원이 한 방향으로만 할당되기에 circular wait가 발생하지 않는다
- 다만, 프로세스 작업의 유연성이 떨어지고, 자원에 번호를 부여하는 규칙을 정하기 어렵다
- 예방 방법들은 이와같이 문제가 있어 실제 OS에 적용되기 어려웠다
교착상태 회피
- Deadlock avoidance(교착상태 회피)는 프로세스에 자원을 할당할 때 데드락이 발생할 가능성을 먼저 평가하고 안전하다면 자원을 할당하는 방식이다
- 안정상태를 찾아내기 위한 알고리즘 → banker’s algorithm(은행원 알고리즘)
- n개의 프로세스
- m가지의 리소스
- i번째 리소스는 Ri개의 인스턴스를 가지고 있다
- Safe sequence: <Pa, Pb, … > 의 순서로 자원을 할당해줬을 때 모두가 안전하게 자원을 할당받을 수 있는 시퀀스가 존재
- j< i 에서 Pi가 리소스를 요청했을 때 못준다면 적어도 Pj가 자원을 가지고 일을 끝낼 수 있을 정도의 안정적인 시퀀스가 있는가
- 회피 방법의 단점
- 실행전 모든 자원을 미리 선언해야함 → 그러나 실행중 어떤 자원이 필요할지 미리 알기 어려움
- 시스템 전체 자원의 수가 고정적이여야 한다
교착상태 검출
- 데드락을 검출할 수 있지만, 이를 검출하고 해결하는데는 많은 비용과 시간이 소요된다
- → 일반 OS에서는 그냥 데드락이 발생하면 프로세스를 끄거나 시스템을 재부팅하는게 낫다
- 그리고 무기 제어 시스템, 원자로 제어등과 같은 중요한 시스템에서만 데드락 검출을 사용한다
- 타임아웃 기반 검출
- 일정시간동안 응답하지 않는 프로세스를 데드락이라고 간주한다
- 자원할당 그래프 기반 검출
- 자원할당 그래프를 그려 데드락을 검출한다
- 단점: 자원할당 그래프를 그리는 오버헤드가 발생한다
교착상태 회복
- 교착상태를 검출하면 이를 회복해야 한다
- 교착상태 강제 종료
- 모든 프로세스를 종료하거나
- 데드락을 일으킨 프로세스를 하나씩 종료하며 해결하는 방법도 있다
- 이때 종료하는 순서는 다음과 같은 기준을 고려한다
- 우선순위를 우선 고려한다
- 우선순위가 같으면 작업 시간이 짧았던 것을 종료한다
- 둘다 같으면 자원 사용량이 많은 프로세스를 종료한다
- 데드락을 꼭 회복해야하는 시스템 → DB
- 꼭꼭꼭
- 그래프를 그린다. 의존성 그래프를 그린다
- 데드락이 난 것 같아 → 그래프를 보고 하나씩 죽인다. victim and rollback
C08 메모리와 할당
01 메모리 구조와 프로그램의 실행 준비
메모리와 프로그램 실행 구조
- 모든 프로그램은(OS 포함) 메모리에 올라와야 실행 될 수 있다
- CPU가 메모리에 접근할 때
- 메모리 주소를 MAR(메모리 주소 레지스터)에 저장 → 해당 주소는 주소 버스를 통해 메모리에 전달됨 → 메모리는 메모리 버스를 통해 메모리 주소를 MBR(메모리 버퍼 레지스터)로 보냄 → MBR의 데이터가 레지스터로 이동
- 이 과정에는 10클록 이상이 소요된다
- 이는 CPU가 레지스터에 접근할 때 1~3 클록이 소요되는걸 감안할 때 느린 장치이다 → CPU 내부에 캐시를 마련
- 커널의 메모리 배치
- 메모리에서 커널 영역을 높은 메모리 주소에 배치함으로서 사용자 영역과 떨어져 해킹 위험에 덜 취약하게 하고, 사용자 프로세스가 언제나 0에서 시작할 수 있게 되어 메모리 할당이 단순해지게 한다
- 메모리 보호 방식
- OS는 프로세스의 메모리 시작 주소를 경계 레지스터(bound register)에 저장하고 마지막 주소를 한계 레지스터(limit register)에 접근해 이 밖으로는 접근이 불가능하게 하고, 접근시 프로세스를 강제 종료한다
컴파일과 실행 준비
- 컴파일
- 과정
- 소스 코드 분석: 소스 코드의 문법 오류 분석
- 최적화 및 목적 코드(object code) 생성: 불필요한 변수, 중복된 명령 제거로 코드 최적화 → 목적 코드.obj가 나온다
- 연결 단계: 목적 코드에 라이브러리 코드를 삽입해 실행 파일을 생성
- 컴파일은 목적 코드를 만드는 단계와, 목적 코드를 라이브러리와 연결하는 링킹 단계로 나뉜다
- 목적 코드는 기계어 코드로 변환되기 직전의 중간 형태 코드이고, 라이브러리 기계어 코드와 연결되며 최종 실행 파일이 된다
- 분할 컴파일과 링킹
- 분할 컴파일: 여러개의 소스 파일을 하나의 실행 파일로 만드는 것, 다중 소스 코드(multiple source code)라고도 한다
- 각각의 개발자가 자기의 코드를 컴파일해 목적 코드를 공유하면 이를 하나로 링킹하여 실행 파일을 만든다
- 동적 라이브러리
- 실행 시점에서 라이브러리의 해당 함수를 불러와 실행하는 방식
02 프로세스의 메모리 배치
메모리 주소
- 논리 주소와 물리 주소
- 컴파일시 지정된 논리 주소와, OS가 실제로 배정해주는 물리 주소가 다르다
- 이 주소의 변환 작업은 CPU의 MMU(Memory Management Unit, 메모리 관리 유닛)이 수행한다
- 32비트 CPU와 64비트 CPU의 메모리 주소
- 32비트는 최대 2^32B → 4GB의 메모리 주소를 나타낼 수 있다
- 64비트는 1600백만 TB를 나타낼 수 있다
메모리 오버레이
- 프로그램이 물리 메모리보다 클 때 프로그램을 여러개의 모듈로 나누어 메모리에 가져오는걸 메모리 오버레이라고 한다
- 다만 이는 과거에 사용되던 방식이고 현대 메모리 시스템에서는 가상 메모리 기술을 사용한다
스왑
- 사용하지 않는 메모리를 일시적으로 저장장치로 옮겨 메모리 공간을 확보하는 기법
- 데이터를 내보내는 것을 swap-out, 메모리를 다시 메모리로 불러오는걸 swap-in이라고 한다
03 메모리 할당 기법
가변 분할과 고정 분할
- 가변 분할: 프로세스의 크기에 따라 메모리를 나누는 방식
- 단점: 관리하기 어렵다
- 하나의 프로세스는 물리 메모리의 연속된 공간에 할당된다 → 연속 메모리 할당(contiguous memory allocation)
- → 이로 인해 빈 공간을 합쳐야 하는 작업이 발생하기에 관리가 복잡해진다
- 고정 분할: 프로세스의 크기와 관계없이 메모리를 같은 크기로 나누는 방식
- 크기가 큰 프로세스는 여러 개의 메모리 공간을 사용한다
- 프로세스의 메모리가 나뉘긴 하지만, 메모리 오버레이와 같이 메모리가 나뉘거나 스왑 영역에 나뉘어도 문제는 없다
가변 분할의 메모리 관리
- 가변 분할의 메모리 관리
- 가변 분할에서 중간에 빈 메모리 조각이 발생할 수 있는데, 이런 조각에는 다른 프로세스를 배치하기 애매해진다 → 메모리 공간의 활용도가 떨어짐 → 이를 fragmentation(단편화)라고 한다
- 특히 가변 분할에서는 이러한 fragmentation이 프로세스 외부에서 벌어지기에 external fragmentation이라고 한다
- 메모리 배치 전략
- fragmentation이 발생하지 않게 하는 전략
- 최초 배치
- fragmentation을 고려하지 않고 그냥 처음 발견한 공간에 배치한다
- 장점: 빈공간을 찾아다닐 필요가 없음
- 최적 배치
- 가장 프로세스의 크기와 비슷한 공간에 프로세스를 배치한다
- 단점: 아주 작은 공간이 남아 external fragmentation이 발생할 확률이 높다
- 최악 배치
- 가장 큰 공간에 프로세스를 배치한다
- 단점: 빈 공간의 크기가 클때는 효율적이지만 점점 최적 배치와 같은 문제가 발생한다
- 조각 모음(defragmentation)
- external fragmentation을 해결하기 위해 프로세스를 다시 하나의 큰 덩어리로 만드는 과정
- 단점: 프로세스를 중지하고 이동하고 주소를 바꾸고 다시 프로세스를 시작하는 과정이 복잡한다
- 이러한 단점으로 가변 분할은 잘 사용하지 않는다
고정 분할의 메모리 관리
- 고정 분할의 메모리 관리
- 모두 같은 크기만큼 메모리를 배정하기에 내부에 남는 메모리가 생길 수 있다 → internal fragmentation 발생
04 버디 시스템
- 가변 분할의 external fragmentation을 완화하는 메모리 배치 전략중 하나
- 메모리를 계속 반으로 나눠 적절한 위치에 배치하는 방식
- 가변 분할처럼 메모리를 프로세스 크기대로 나누지만, 고정 분할처럼 하나의 구역에 다른 프로세스가 들어갈 수 없어 fragmentation이 발생한다
- 이렇게 fragmentation이 발생해도 기존의 가변 분할보다 효율적인것은 비슷한 크기의 빈 조각들이 모여있기 때문이다 → 쉽게 종료되는 블록을 합쳐 큰 덩어리를 만들 수 있다
- 고정 분할만큼 효율적이나 이렇게 복잡하게 관리하는 것 보다 그냥 모든 블록을 동일한 크기로 관리하는 것이 더 단순해 버디 시스템 보다는 고정 분할이 주로 사용된다
2^k만큼 할당해서 분할하는 방식 → 그 위에 뭘 올리냐
최신에는 slab slub을 쓴다
요즘에는 malloc 같은것도 jemalloc 이런거 쓰고
은근 빈번하게 질문으로 나온다
그렇게 막 버라이어티 하지 않다 → 2^n 그냥 잘 쑤셔넣으면 된다
C09 페이징 메모리
01 페이징 메모리 관리
페이지 매핑 테이블과 주소 변환
- 매핑 테이블이 필요한 이유: 논리 주소를 물리 주소에 맵핑하기 위한 테이블 자료구조가 매핑 테이블(mapping table)
- Sementation(세그먼테이션): 메모리를 크기가 가변적인 논리 단위(세그먼트)로 나눈다
- Paging(페이징): 메모리를 고정 크기의 블록(페이지) 단위로 나눈다
가변 분할 → segmentation, 고정 분할 paging 인지?
page 사이즈가 정해져있다면
- Paging이 관리가 간편하고 변환 속도가 빨라 대부분의 OS에서 사용된다
- 페이지 테이블의 구조와 역할
- 논리 주소의 공간을 페이지(page), 물리 주소의 공간을 프레임(frame)이라고 하며 이 둘이 매칭된다
- 그리고 페이지 매핑 테이블이 이를 맵핑한것을 저장한다
- Invalid라고 되어있는 칸은 페이지가 스왑 영역에 있거나 아직 저장장치에서 불러오지 못한 것이다
접근하면 안되는데 맵핑한걸수도 있고... 아에 존재하지 않는 주소이거나... <P, D>에서 P를 안알려준다 보통. D만 알려줌. 64k~80k?는 접근하면 안되는 영역일 수 있다 -> 그게 5번
주소 변환 방식과 페이지 테이블 구조
- 논리 주소를 보고 해당 논리 주소의 페이지가 위치한 프레임을 찾는다 → 이후 해당 프레임 내에서 해당 논리 주소와 매칭되는 물리 주소를 정확히 찾는다
- 이런 방식을 정형화하여 논리 주소는 LA = <P, D>로 나타낼 수 있다
- P는 페이지 번호, D는 페이지 내에서의 거리를 의미한다
- 마찬가지로 FA = <F, D> 로 나타낼 수 있다. F는 프레임 번호이다
- ex) 위의 그림에서 LA = <4, 3> 은 FA =<0, 3> 으로 변환될 수 있다
- 논리 주소 변환 공식
- 논리 주소를 위와 같이 P와 D로 분리하려면,
- P = (논리 주소 / 페이지 크기)의 몫
- D = (논리 주소 / 페이지 크기)의 나머지
- 가 된다
공유 페이지와 쓰기 시점 복사
- 여러개의 프로세스가 같이 사용하는 라이브러리를 메모리에서 공유될 수 있다
- 왜냐하면 이를 개별적으로 모두 올리면 메모리 낭비이기 때문
- 이때 페이지 매핑 테이블에서 공유할 라이브러리를 동일한 프레임 번호로 지정하면 된다
- 쓰기 시점 복사의 원리
- 프로세스 A에서 fork()를 실행해 B가 만들어졌을 때 A의 모든 데이터가 새로운 프레임으로 복사된다
- 다만 fork()후 바로 exec()이 일어나는 일이 많은데, 그러면 A의 데이터를 복사한 것이 소용없게 된다
- 그렇기에 fork()를 호출하면 즉시 메모리가 복사가 되는 것이 아니라, 두 프로세스 중 하나가 쓰기 연산을 수행할 때 비로소 기존 프레임이 복사되어 두 프로세스의 메모리 영역이 분리 된다
- 이를 쓰기 시점 복사(copy on write)라고 한다
- 즉 위와같이 쓰기가 일어난 페이지만 새로운 프레임으로 쓰기 시점에 복사가 된다
지역성의 개념과 메모리 접근 최적화
- 앞서 나왔듯이 CPU에는 캐시가 존재한다
- 이때 메모리에 접근하지 않고 캐시에 적중하는 것을 캐시 히트라고 한다
- 캐시 히트율이 높아야 시스템 성능이 향상된다
- 캐시 히트율을 높이는 방법
- 캐시의 크기를 늘이기 → 비싸다
- 사용할 가능성이 높은 데이터를 미리 캐시에 가져오기
- 2번을 위해 사용되는 이론이 지역성 이론이다
- 지역성은 메모리 접근이 메모리 전체에 균등하게 일어나는 것이 아닌 특정 부분에 집중되어 일어나는 경향을 의미한다
- 공간의 지역성: 현재 접근한 데이터와 근처에 있는 데이터에 접근을 할 확률이 높다
- 시간의 지역성: 최근에 접근한 데이터에 접근할 확률이 높다
- 순차적 지역성: 방금 10행이 실행됐다면 다음에 11행이 실행될 가능성이 높다는 것
- 이렇게 지역성 이론을 바탕으로 지역성이 높은 데이터를 가져와 캐시 히트율을 높인다
02 페이지 테이블 관리
- 프로세스마다 페이지 매핑 테이블이 만들어진다
- 페이지 테이블은 메모리 영역에서 커널 영역에 위치한다
- 페이지 테이블의 시작 주소를 PTBR(Page table base register, 페이지 테이블 기준 레지스터)에 저장한다
- 이건 CPU 레지스터의 일종이며, 이 값은 PCB에 저장된다
- 페이지 테이블의 크기(D)에 따라 남은 페이지를 나타내는 P가 결정된다
- 예를들어 페이지의 크기가 2^12 = 4KB라면 위의 그림처럼 된다
- 이때 페이지의 개수는 P에 따라 2^20개가 된다
- 그러면 이때 PTE(페이지 테이블 엔트리)는 2^20개의 페이지를 저장해야 하는데, 각 페이지별로 20비트(P가 20비트니) 를 차지하니 PTE는 20*2^20 = 2.62MB를 차지하게 된다
- 프로세스당 PTE가 하나가 생성되니 프로세스가 100개라며 262MB라는 어마한 크기를 잡아먹게 된다
- 페이지의 크기(D)를 늘이면 이 값은 줄어들겠지만 그럼 internal fragementation 문제가 커진다
연관 매핑
- 논리 주소를 이용해 실제 물리 주소에 접근 할 때 2번의 메모리 접근이 필요하다
- 페이지 테이블 접근
- 실제 물리 주소 접근
- 메모리 접근은 느린 과정이기에 페이지 테이블 일부를 CPU에 캐싱해둔다
- 이를 TLB(Translation Lookahead Buffer, 변환 색인 버퍼)라고 한다
- 페이지 맵핑 테이블을 이용해 접근하는 것을 직접 매핑이라 불렀는데, TLB를 이용해 테이블을 CPU에 저장하는 방식을 연관 매핑(associative mapping)이라고 부른다
- 즉 페이지 테이블 접근전에 이제 TLB를 찾아보게 되고, 이는 TLB 히트나 TLB 미스로 나타나진다
- 페이지 맵핑 테이블에서는 그냥 페이지 번호가 순서대로 나열되어 있어 한개의 열로만 나타내지지만
- TLB는 페이지 번호, 프레임 번호를 모두 저장해야하니 2개의 열로 나타내진다
- 컨텍스트 스위칭이 발생하면 TLB 데이터를 새로운 테이블의 TLB 정보로 변경한다 → 즉 새로운 프로세스여도 TLB는 유효하다
TLB는 그럼 컨텍스트 스위칭시 PCB에 저장되는건지…? → 그러기엔 용량이 너무 클 것 같은데
→ 아마? → TLB가 주르르 있다. MMU → cache니간 context switching 할정도의 정보는 아닌 것 같다.
같은 프로세스의 스레드라면 같은 페이지 맵핑 테이블을 공유할테니 같은 프로세스 내에서 컨텍스트 스위칭 발생시 TLB 스위칭은 발생안하는것이 맞는지?
mmap < 방식이다 ← 뇌피셜
→ 그렇지 않다? → 리스트로 관리함 ← 팩트다
역 매핑
- 기존의 페이징 방식과 반대로 프레임을 기준으로 페이지를 구성하는 방식
- 역 맵핑의 특징으로는 시스템 전체에서 1개의 테이블만 유지된다는 점
- 장점: 하나의 테이블만 시스템 전체에서 사용되기에 메모리 사용량이 적다
- 단점: 논리 주소를 변환하는 과정에서 추가로 탐색 연산이 필요하다
그리고 프레임을 공유할 수 없는 단점도 있을 것 같은데…
그러면 매번 O(n)을 검색하는지?
다단계 매핑
- 64비트 시스템에서는 페이지 크기가 4KB일때 페이지의 개수는 2^52개다
- → PTE 1000조개 관리는 불가능하다…
- 다단계 매핑(multi-level mapping): 큰 페이지 테이블을 일정 크기로 나누고 이를 관리하는 상위 테이블을 추가하는 방식
- 다단계 매핑 방식에서는 모든 페이지 테이블을 한번에 만들지 않고, 필요할 때마다 2차 테이블을 동적으로 구성한다
- 2단계 매핑에서 논리 주소는 LA = <P1, P2, D> 방식으로 표현된다
- 테이지 테이블의 크기가 너무 클 떄는 3~4단계 매핑 방식을 사용할 수 있다
- 다단계 매핑 장점: 2차 테이블을 동적으로 생성하여 메모리를 효율적으로 사용할 수 있다
- 또한 페이지 테이블이 너무 커지는 문제를 방지 가능하다
- 단점: 주소를 단계별로 접근하니 느려진다 → TLB가 그만큼 더 중요해짐
실제로 4단 이상의 페이지 테이블을 쓰는걸로 알고 있는데, 그만큼 메모리 절약이 효율적인지?
TLB가 미스나면 말 그대로 *4 *5배로 느려지는건데 접근이…
작은 기계일수록 치열하게 돌아가기에
hadoop 머신 이런건 메모리 절약에 목숨을 거는 경향이 있다. TLB miss등에
현대 시스템은 fault가 나는 경우도 적다… 폰정도 빼면
threashing이 날정도로 막 나는게 아니라 ㅇㅇ 그냥 적당히 난다
CPU 캐시의 매핑 방식
- 즉시 쓰기와 지연 쓰기
- 즉시 쓰기(write through): 캐시에 있는 데이터가 변경되면 이를 즉시 메모리에 반영하는 방식
- 장점: 갑작스러운 정전에도 데이터를 잃어버리지 않는다
- 단점: 데이터 전송이 잦아져 느려진다
- 지연 쓰기(write back, copy back): 캐시에 있는 데이터가 변경되어도, 나중에 주기적으로 반영하는 방식
- 장점: 시스템 성능 향상
- 단점: 메모리와 캐시된 데이터 사이의 불일치가 발생할 수도 있다
- 캐시 직접 매핑
나중에 추가
- 캐시 연관 매핑
나중에 추가
- 캐시 집합-연관 매핑
나중에 추가
굉장히 중요한 부분이 아닌가
즉시 쓰기 지연 쓰기는 확실히 알아두기
사람 인생 모르기 때문에 이쪽은 잘 알고 있는게 좋다 → 어디든지 나오기 때문에
03 세그멘테이션 메모리 관리
- paging 방식이 아닌, segmentation 방식에서의 메모리 관리
- 세그멘테이션 방식에서는 세그멘테이션 매핑 테이블을 사용한다
- 해당 테이블에는 메모리상의 시작주소와 세그먼트의 크기(limit)를 저장한다
- 세그멘테이션 기반 주소 변환 방식
- 논리 주소를 LA = <S, D>로 표현한다
- S는 세그먼트 번호
- D는 세그먼트 시작지점에서 해당 주소까지의 거리이다
- 이때 메모리에 접근할 때 마다 D가 limit보다 작은지 매번 검증한다
- 만약 더 큰 주소에 접근하려고 하면 해당 프로세스를 강제 종료 시킨다. 이를 trap이 발생했다고 한다
- 스풀 버퍼
- 스풀: 여러 프로세스가 공유해서 자원을 쌓아놓는 개념이여야 한다
- 버퍼: 단일 프로세스에서
- 하이퍼스레드: 장비에 하이퍼스레드용 레지스터 pc를 추가해야하는데, 이게 ALU, APU의 유후시간을 이용하는건데, 3개부터는 가성비가 안나온다
- TCB/PCB → 자원 소유권을 PCB를 가지고 있기에 스레드가 가지고 있으면 자원 효율성이 떨어져 경량 프로세스 역할을 할 수 없다고 합니다
P. 243
임계 구역 문제는 공유 변수를 사용하는 스레드 간의 동기화에서 발생하는 문제이다.
그런데 프로세스 1개 이상의 스레드를 가지므로, 결국 프로세스 간에도 동기화 문제가 발생할 수 있다.
즉, 스레드의 동기화가 프로세스의 동기화 문제로 확장될 수 있다.
목적코드와 링킹
→ 목적코드 만드는건 재사용성, 플랫폼 독립성
→ 링킹 → 그냥 연결해주는거
→ 과정의 필요성? 걍 하면 되는거 아닌가?
→ 목적 코드: ELF 기준 코드, 데이터, symbol list가 들어감. → + relocation 정보가 들어감
링킹에서 relocation 정보에
→ 링킹: symbolic linking, relocation
- 목적 코드 == 중간 언어?
- → 컴파일러마다 다름
- jvm이 중간코드가 바이트코드 → 그다음 obj로 만듦
- TLB의 시간복잡도가 검색에 대해서 O(1)
- T look aside buffer는 구조는 O(n)이다 ㅇㅇ
- 근데 O(1) 어캐 돎 → HW상 병렬처리
- 메모리 오버레이와 현대 컴퓨터 virtual memory를 넘어간다로 지나갔는데
- 모니터 → 뭔지?
- SW 공학에서 관점 지형 프로그래밍 → AOP → Spring boot?
- @Aspect?
- AOP + DI(IOC) + PSA?
- AOP: proxy 방식 기능 추가. decorator만 추가해서 annotation으로 추가하기
- 이게외댐 → AOP 공부
- 캐리어 스레드
- 가장 스레드 - 스프링
- 스프링이 안해주냐? 는 중요한게 아니다
- 정식 버전이 된지 2년정도 됨
- 스프링, 은 webflux 이런거 있으니깐 대응이 다 되어 있음
- 스프링이 문제가 아니라, 커넥션 풀을 HikariCP를 쓴다 → 아… 히카리CP가 버추얼 스레드 지원을 안해줌.. ㅋ
- 스프링은 loom을 쓴다
- loom에는 이미 가상 스레드와 비슷한 개념이 들어 있었다 ㅇㅇ
- node에서 cpu bound job
- 흠 그정돈가? 그렇다고 ㅈㄴ 약하진 않음…
- 노드나 fastapi나 그게 그건데 둘다 cpu를 많이 안쓴다
- 그래서 약하고 말고를 고민할게 없다
- node로 게임짜는놈들… 이 있나? → 생각보다 많다 → 유니티 웹같은게 C# 노드 지원해서 ㅇㅇ…
- 스트리밍 서버
- webRTC가 내부 모듈이 c이다
- mpeg-dash hls가 스트리밍 엔진인데
- 요즘 gpu가 중요한 시대… 인코딩을 gpu로 하기에
- 서버가 비동기가 될수록 짜기가 어려워진다
- 짜는건 똑같은데, 이게 진짜 비동기야? 이런게 어려움
- 서비스의 문제 상황에 봉착하면 비동기가 ㄹㅇ 어렵다…
- 사실상 동기인 비동기 서버가뎀…
- 장고는 비효율적인가?
- 장고의 철학
- 통상의 웹서버와 똑같이 구현하면 성능이 떨어지는데, 애초에 장고로 제공하는 웹서버는 굉장히 stateless하고, 내가 다른 프로세스 상태를 전혀 알 필요가 없고,
- 장고가 사이즈가 대따 크다, 라이브러리도 대따많다.
- 이걸 워커마다 올려야 한다는게 손해다 ㅇㅇ
- 장고는 느리다. instagram? →
- 페이스북이 php?
- 이벤트 드리븐
- io 작업때문에 역량이 딸려서 tps에 영향을 주려면 단일 머신 tps 2만?
- 네트워크 들낙, request/response 쏴주기, log 찍기로 io bandwith가 터지려면 tps 2만이 나와야 한다
- io? cpu? 한계?
- sink를 만듦→ 카프카에 쏴주는 서버 → 이런게 tps 4~6만 나와야 한다
- 이런게 io가 먼저 터짐
- 받아서 가공해야 하는 애들이 cpu가 먼저 터짐
- elastic search 이런거에 인덱스 여러개를 거는거에 초당 2~3만씩 쏘면 cpu 99% 찍음
- 앵간해선 cpu가 먼저 터짐
- 터지기전에 DB/redis 커넥션이 먼저 터짐
- cpu 1/v4를 뛰우는게 낫다 ㅇㅇ ! ! !
- 장고가 느린게 2개 있음
- 어드민이랑 orm
- 어드민 → 장고의 꽃
- 서비스 운영하면서 어드민 페이지가 필요할 것 같은데
- 만드는게 낫나? 아니면 외부에 어드민 페이지
- → 커서로 ‘딸칵’
- 프론트 개발자는 많이 만들어보는게 좋다!
- 문서를 쓰고 들고오세요 . . . !
- 취준생이 착각하는 것들 → 경력 기술서는 쓰려는 포지션 갯수만큼 쓰는 것
- 맞는 포지션만 써라?
- 공고를 3개를 쓴다면 경력 기술사는 다 달라야 한다
- 관리자 도메인
- admin.gyohwan.com?
- ㅈㄴ 싫군
- sexydynamitekyungsikzzang123.gyohwan.com