쉽게 배우는 운영체제 6장 ~ 9장 정리

쉽게 배우는 운영체제 6장 ~ 9장 정리

Category
Computer Science
Tags
OS
Published
September 7, 2025
Last updated
Last updated November 11, 2025

C06 임계구역과 동기화

01 임계구역과 동기화

  • 공유 자원 접근 시에는 race condition(경쟁 상태) 문제가 발생할 수 있다
    • race condition: 둘 이상의 스레드가 동일한 공유 자원에 동시에 접근하여 실행 결과가 예측할 수 없게 되는 상황
  • 그렇기에 critical section(임계구역)을 설정한다
    • critical section: 둘 이상의 스레드가 동시에 접근해서는 안되는 공유 영역
  • 이 critical section에 대한 접근을 제어 하는 과정을 syncronization(동기화)라고 한다
 
  • 임계 구역이 문제를 가지지 않으려면 다음 3가지 조건을 만족해야 한다
    • Mutual exclusion(상호 배제): 당연하게도, 한 임계구역에 스레드가 들어가면 다른 스레드는 들어갈 수 없어야 한다
    • Bounded waiting(한정 대기): 임계구역에 들어가려는 스레드중 무한히 기다리는 스레드가 있어선 안된다
    • Progress flexibility(진행의 융통성): 한 스레드의 작업이 임계구역에 진입하려는 다른 스레드의 진행을 방해해서는 안된다
      • 즉 임계구역에 다른 스레드가 들어가 있지 않다면 들어갈 수 있어야 한다
 
  • Mutual exclusion 위반 사례
notion image
  • 위 코드에서 두 스레드 모두 while문으로 임계구역 접근여부를 확인하고 들어가며, 들어간 후에는 lock으로 잠궈 들어갈 수 없게 하고 있다
  • 그러나 1과 2 사이 또는 3과 4 사이에 타임 슬라이스 아웃이 발생해 컨텍스트 스위칭이 된다면 두 스레드가 모두 임계구역으로 들어가는 문제가 발생할 수 있다
  • 해결 방법
    • lock을 체크하는 연산과 lock을 true로 바꾸는 연산, 두 개의 연산을 원자적으로 묶어 하드웨어 적으로 동시에 실행하면 된다
 
  • Bounded waiting 위반 사례
notion image
  • 위 코드는 mutual exclusion 문제를 SW적으로 해결하기 위한 코드로, 두 스레드가 모두 임계구역에 진입하는 문제는 발생하지 않는다
  • 그러나 1번 코드가 실행되고 컨텍스트 스위칭이 발생해 3번 코드가 실행된다면 모두 락이 걸려 두 스레드 모두 임계구역에 진입하지 못하는 bounded waiting 문제가 발생한다
 
  • Progress flexibility 위반 사례
notion image
  • 위 코드는 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()이라고도 부른다
 
  • 세마포어의 내부 코드
notion image
  • 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기반 스핀락은 원자성을 지킬 수 있는 구조로 되어있다
 

동기화에서 발생하는 문제: 우선순위 역전

notion image
  • 위 코드에서 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(은행원 알고리즘)
notion image
  • n개의 프로세스
  • m가지의 리소스
    • i번째 리소스는 Ri개의 인스턴스를 가지고 있다
  • Safe sequence: <Pa, Pb, … > 의 순서로 자원을 할당해줬을 때 모두가 안전하게 자원을 할당받을 수 있는 시퀀스가 존재
    • j< i 에서 Pi가 리소스를 요청했을 때 못준다면 적어도 Pj가 자원을 가지고 일을 끝낼 수 있을 정도의 안정적인 시퀀스가 있는가
 
notion image
 
 
  • 회피 방법의 단점
    • 실행전 모든 자원을 미리 선언해야함 → 그러나 실행중 어떤 자원이 필요할지 미리 알기 어려움
    • 시스템 전체 자원의 수가 고정적이여야 한다
 

교착상태 검출

  • 데드락을 검출할 수 있지만, 이를 검출하고 해결하는데는 많은 비용과 시간이 소요된다
    • → 일반 OS에서는 그냥 데드락이 발생하면 프로세스를 끄거나 시스템을 재부팅하는게 낫다
    • 그리고 무기 제어 시스템, 원자로 제어등과 같은 중요한 시스템에서만 데드락 검출을 사용한다
 
  • 타임아웃 기반 검출
    • 일정시간동안 응답하지 않는 프로세스를 데드락이라고 간주한다
  • 자원할당 그래프 기반 검출
    • 자원할당 그래프를 그려 데드락을 검출한다
    • 단점: 자원할당 그래프를 그리는 오버헤드가 발생한다
 

교착상태 회복

  • 교착상태를 검출하면 이를 회복해야 한다
 
  • 교착상태 강제 종료
      1. 모든 프로세스를 종료하거나
      1. 데드락을 일으킨 프로세스를 하나씩 종료하며 해결하는 방법도 있다
          • 이때 종료하는 순서는 다음과 같은 기준을 고려한다
          • 우선순위를 우선 고려한다
          • 우선순위가 같으면 작업 시간이 짧았던 것을 종료한다
          • 둘다 같으면 자원 사용량이 많은 프로세스를 종료한다
 
  • 데드락을 꼭 회복해야하는 시스템 → 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에서 사용된다
 
  • 페이지 테이블의 구조와 역할
    • notion image
    • 논리 주소의 공간을 페이지(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)라고 한다
      • notion image
    • 즉 위와같이 쓰기가 일어난 페이지만 새로운 프레임으로 쓰기 시점에 복사가 된다

지역성의 개념과 메모리 접근 최적화

  • 앞서 나왔듯이 CPU에는 캐시가 존재한다
    • 이때 메모리에 접근하지 않고 캐시에 적중하는 것을 캐시 히트라고 한다
    • 캐시 히트율이 높아야 시스템 성능이 향상된다
    • 캐시 히트율을 높이는 방법
        1. 캐시의 크기를 늘이기 → 비싸다
        1. 사용할 가능성이 높은 데이터를 미리 캐시에 가져오기
    • 2번을 위해 사용되는 이론이 지역성 이론이다
  • 지역성은 메모리 접근이 메모리 전체에 균등하게 일어나는 것이 아닌 특정 부분에 집중되어 일어나는 경향을 의미한다
    • 공간의 지역성: 현재 접근한 데이터와 근처에 있는 데이터에 접근을 할 확률이 높다
    • 시간의 지역성: 최근에 접근한 데이터에 접근할 확률이 높다
    • 순차적 지역성: 방금 10행이 실행됐다면 다음에 11행이 실행될 가능성이 높다는 것
  • 이렇게 지역성 이론을 바탕으로 지역성이 높은 데이터를 가져와 캐시 히트율을 높인다

02 페이지 테이블 관리

  • 프로세스마다 페이지 매핑 테이블이 만들어진다
  • 페이지 테이블은 메모리 영역에서 커널 영역에 위치한다
  • 페이지 테이블의 시작 주소를 PTBR(Page table base register, 페이지 테이블 기준 레지스터)에 저장한다
    • 이건 CPU 레지스터의 일종이며, 이 값은 PCB에 저장된다
 
notion image
  • 페이지 테이블의 크기(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번의 메모리 접근이 필요하다
      1. 페이지 테이블 접근
      1. 실제 물리 주소 접근
  • 메모리 접근은 느린 과정이기에 페이지 테이블 일부를 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): 큰 페이지 테이블을 일정 크기로 나누고 이를 관리하는 상위 테이블을 추가하는 방식
notion image
  • 다단계 매핑 방식에서는 모든 페이지 테이블을 한번에 만들지 않고, 필요할 때마다 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)를 저장한다
notion image
 
  • 세그멘테이션 기반 주소 변환 방식
    • 논리 주소를 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