말 그대로 스레드의 구현 수준이 어디에 있느냐가 핵심으로 가장 큰 차이는
커널에서는 사용자 수준에서 구현 된 스레드의 존재를 알지 못한다는 것
커널 수준 스레드
커널에서 프로세스 : 스레드의 1:1 관계를 가지고, 커널에서 관리되지만 사용자 수준으로 변환의 비용이 필요
사용자 수준 스레드
커널에서 스레드의 존재를 모르기 때문에 I/O 사용 시에 문제가 있음.
스레드의 스케쥴링을 사용자가 하기 때문에 I/O 프로세스 블록이 걸린다면 프로세스의 스레드는 모두 블로킹 됨

프로세스들은 커널로부터 메모리 영역을 할당
stack은 동적 할당이 없는 로컬 데이터에 할당, stack overflow 발생가능
heap은 동적 할당 데이터에 할당, heap overflow 발생 가능
프로세스는 각각 PID를 가짐, 각 프로세스가 메모리 영역을 할당 받음, 각 프로세스는 서로 영향을 주지 않음
스레드는 하나의 PID 내 존재, 각 스레드가 메모리 영역을 공유하여 영향
프로세스 간 복사 시 COW(Copy-on-Write)이용, 실제 쓰기 연산 시 데이터 복사
프로그램 진행 시 함수의 완료 시점이 누구에게 있는가? 목적이 함수의 완료인가?
프로그램 진행 시 함수의 처리 결과는 누구에게 있는가? 목적이 함수의 결과인가?
Sync - Blocking
시스템 I/O를 통한 Read 작업이라고 가정 시
Read() -> Process stop -> System call() -> Task stop -> return -> task run -> return -> process run
I/O 작업 도중 프로그램이 종료되지 않기 위해 Sync
I/O 작업 결과에 따라 함수를 처리하기 위해 Blocking
Sync - nonBlocking
시스템 I/O를 통한 Read 작업이라고 가정 시
Read() -> Process stop -> System call() -> System call() return -> Task run -> System call() -> System call() return -> Process run
I/O 작업 도중 프로그램이 종료되지 않도록 Sync
I/O 작업 결과가 나오지 않더라도 진행 되도록 nonBlocking
Async - nonBlocking Event Driven
시스템 I/O를 통한 Read 작업이라고 가정 시
Read() -> System call() -> return -> register callback handler -> Prcoess run -> system call handler -> complete callback -> Process run
I/O 작업을 요청, 완료되지 않더라도 결과를 반환, 완료 시점을 알기위해 콜백 등록
프로그램 진행 Async
I/O 작업 결과가 나오지 않더라도 반환, 결과가 나오면 핸들러 호출
대표적으로 멀티 스레딩에 따른 메모리 공유로 발생하는 문제로 교착상태라고 함.
Dead Lock은 4가지의 조건을 만족해야함
Mutex(상호배제)
하나의 스레드가 작업을 수행하는 Critical Section(임계구역)에 다른 스레드 가 접근 할 수 없음
Circular Wait(순환대기)
각 스레드가 작업 중인 스레드의 작업이 종료되기를 꼬리를 물며 기다리는 상태
Hold and Wait(점유대기)
이미 작업 중인 스레드가 다른 스레드의 작업이 끝나기를 기다림
Non-Preemption(비선점 상태)
OS가 작업을 컨트롤 할 수 없음
작업 스레드가 스스로 자원을 반납하길 기다림
Dead Lock의 해결
은행가 알고리즘 (회피)
구현이 어려움
우선순위 lock(semaphore) (회피)
기아상태(우선 순위에 밀려 대기)와 우선순위 역전(하위 우선순위가 세마포어를 소유 하고 블로킹 되어 상위 우선순위가 밀리 상태)이 발생 가능
Queue를 이용한 데이터 확인(데이터가 없으면 작업 안함)
우선순위 계승, 라운드로빈 선점 스케쥴링, 스레드 풀을 통한 우선순위 해결
Timeout (탈출)
일정 시간 내 교착상태가 종료되지 않으면 강제로 할당 해제
LiveLock 발생 가능
RollBack (탈출)
되돌리기, 교착상태 다시 발생 가능
이벤트 이용 방식, 비동기, 작업에서 어떤 이벤트가 발생했을 때 처리
Queue이용, 먼저 작업할 스레드가 먼저 끝냄, 스레드 개수를 유지 가능
만약 db connect task가 1000번 일어나면 1000개 스레드 생성할건가?
스레드의 상한선을 정하고 스레드를 돌려막기
프로세서는 메모리를 효율적으로 사용하기 위해 동일한 공간에 빠른 시간 내에 재접근 하려는 경향이 있음
맨 처음에 페이지를 메인 메모리에 적재한 후에는 다시 적재할 필요가 없기 때문에 페이지 결함의 확률이 매우 낮다. 공간적인 지역성은 코드를 읽을 때 현재 코드의 주변에 있는 코드를 읽을 확률이 높다는 것을 의미한다. 페이지 결함이 일어나 하드디스크에서 페이지를 가져올 때 주변 코드들을 block 단위로 가져오게 되면 주변 코드를 읽을 확률이 높으니 다음의 페이지 결함의 확률이 낮아지는 것이다. 따라서 지역성의 원리에 의해 페이지 결함으로 인한 컴퓨터의 효율이 떨어지는 확률이 많이 낮아진다.
또한 L1, L2와 같은 캐시를 이용하여 임시적인 지역성을 확보하여 최근의 데이터에는 빠르게 접근 가능
이는 시간 지역성에 따른 접근이지만 실제로 캐시는 캐싱을 위해 캐시 라인을 사용하기때문에 공간 지역성에 따른 영향도 받음
지역성의 원리를 만족하기 위해선 자주 참조하는 데이터가 밀집되어 있어야 함
이를 위해 디스크의 물리적 메모리를 분할하여 RAM에 논리적 메모리로 적재하는 가상
메모리 기법이 사용
가상 메모리 분할 기법을 통해 적재되는 논리적 메모리는 분할 기법에 따라 랜덤하나
각 배치 테이블을 통해 변환 되므로 프로세서 입장에서는 순차적 메모리로 보임
또한 메모리의 효율성을 위해 적재된 프로세스 중 쓰이지 않는 프로세스를 교체하는 Swapping을 사용
세그멘테이션 - 논리 프로세스 크기 단위 분할
세그멘테이션 테이블을 이용해 가변 크기로 프로세스를 분할
실제 메모리에 따른 고정크기로 분할하는 페이징과 달리 세그먼트 테이블을 작성하여 매핑
실제 메모리 적재 시 할당됨
가변 크기에 따라 메모리는 논리적 분할되므로 세그먼트의 크기는 랜덤하고 이를 통해 남아있음에도 사용하지 못하는 메모리 hole들이 발생, 최적의 fit으로 적재할 수 있는 방법이 없음에 따른 외부 단편화 발생
이를 해결하기 위해 세그멘테이션 페이징 기법을 이용
세그멘테이션 분할 후 페이징 단위로 재분할
페이징 - 물리적 크기 단위 분할
페이징 테이블을 이용해 고정 크기로 프로세스를 분할
실제 메모리가 프레임 단위로 분할 되어 페이지와 매핑되어 있기에 연속적 메모리가 아니라도 할당 가능
고정 크기에 따라 메모리는 분할되므로 논리적 단위가 맞지 않을 수 있어 프로세스보다
메모리 크기가 큰 내부 단편화 발생 가능
15byte 프로세스에 4byte단위 페이징 테이블이 할당 된다면 1byte가 남음
물리 메모리를 가변 크기로 분할하는 건 외부 단편화의 문제가 발생할 수 있어 대부분의 현대 운영체제는 페이징 분할 기법을 통해 물리적 크기로 분할한다.
그렇다면 가변 크기로 메모리를 사용하기 위해선 어떻게 하는걸까?
논리적 프로세스 크기로 분할하되 미리 알자는 방식
각 프로세스가 필요로 하는 메모리 크기는 다르지만 필요한 만큼만 페이지를 이용하여 적재하자는 기법이다.
하지만 이 기법에도 문제가 있는데 적재된 페이지 중 필요로 하는 페이지가 없다면?