본 게시글은 하단 책을 읽고 학습한 내용을 제 생각으로 요약, 정리한 글입니다.
목차
1. 스레드 모델1) 프로세스 모델 vs. 스레드 모델 2. 스레드의 사용3. 사용자 공간에서 스레드의 구현1) 스레드 구현 방법2) user space에서 thread 구현4. 커널 내부에서 스레드 구현1) kernel에서 thread 구현5. 하이브리드 구현6. 스케줄러 활성화(activations)7. 팝업 스레드8. 단일 스레드 코드를 다중 스레드 형태로 만들기학습테스트
1. 스레드 모델
- CPU에서 수행을 위해 스케줄되는 단위에 따라 나뉨
1) 프로세스 모델 vs. 스레드 모델
- 프로세스 모델
- 정의) 프로세스 모델에서 하나의 프로세스에 수행의 흐름(thread)은 하나만 존재
- resource grouping: address space(text, data, stack segment), open file, child process 등
- 스레드 모델
- 정의) 스레드 모델에선 하나의 프로세스 환경에 수행의 흐름(thread)이 여러 개.
- 특징)
- 다중스레딩(multithreading): 다수의 스레드가 하나의 프로세스에서 수행 가능
- 스레드는 각각이 스케줄링의 대상이 될 수도 있음(Ready, Block, Running)
- 경량 프로세스: 프로세스에 비해 관리하는 자원 적음
- 수행의 흐름(thread)과 관련된 자원: program counter, register, stack
- program counter: 현재 수행중인 명령어의 주소
- register: 상태
- stack: 아직 return되지 않은 함수의 return address, local variable
- 자원
- Per process items
- 한 프로세스 내 모든 스레드가 공유하는 항목들
- Per thread items
- 각 스레드들이 개별적으로 보유하는 항목들
- 각각의 스레드는 본인의 stack 가짐
- 스레드마다 실행흐름 다 달라서 콜하는 procedure도 다 다를 수 있어서 고유의 스택 필요함
- 스레드 관련 라이브러리 프로시저
- thread_create
- 인자로 만들어진 스레드가 수행할 procedure 줌
- thread_exit
- 스레드가 자신의 일 마치고 종료할 때 콜함
- thread_wait
- wait의 대상이 되는 상대방 스레드가 exit할 때까지, 콜한 스레드는 block.
- 서연이가 기우에 대해 thread_wait(기우) call 하면 기우가 종료될 때까지 서연이는 block
- thread_yield
- 콜한 스레드는 CPU를 다른 스레드 수행할 수 있게 양보하고 ready로 감
- mutex에서 등장 (mutex가 언락이면 thread 양보하고 ready 맨끝으로 감. 언젠가 다시 running되면 mutex 또 검사함. 또 언락이면 반복함. 언락이 아니라면 return되어서 임계구역 들어감.


- 프로세스와 스레드 예시
- (a) 프로세스 모델: 각기 하나의 스레드 가지는 세 개의 프로세스
- 세 개 프로세스는 서로 관련 없음
- (b) 스레드 모델: 세 개의 스레드 가지는 하나의 프로세스
- 세 개 스레드는 같은 job의 부분이라 서로 긴밀히 협조함.
- 세 개 스레드는 자원(ex. address space)을 공유함
- 세 개 스레드는 협조하게 코딩되어 있어 서로 간에 protection 필요하지 않음.

2. 스레드의 사용
- 스레드를 사용하는 이유
- 응용프로그램 속에서 여러 개의 activity가 동시에 일어나는 경우 많음
- 워드 프로시저의 경우 세 개 activity가 동시에 일어남. 세 activity 각각을 하나의 thread로 매핑시키면 프로그래밍 하기 쉬워짐
- 스레드가 프로세스보다 create, distroy가 쉬움
- 프로세스 생성하려면 여러 자원들 만들어줘야해서 시간 오래 걸리는데, 스레드는 필요 없음
- 프로세스 성능 이득
- 어떤 일 처리하는데 여러 스레드로 구현해두어 한 스레드가 block되어도 같은 프로세스의 다른 스레드들이 cpu 사용하면 프로세스의 성능이 좋아진 것임
- 각 스레드가 각각의 CPU에서 수행되면 진정한 병렬이 가능해짐
예시) 워드 프로세서
- interactive thread: 사용자와 interaction
- 사용자가 키보드, 스크롤로 명령 내리는 거 받아들임
- reformatting thread: 사용자 입력 바탕으로 reformat
- 사용자 요청(문장 첨가, 삭제, 줄바꿈, 페이지 경계)대로 페이지의 내용을 바꿔줌
- 사용자 입력 바탕으로 버퍼에 page-break, line-break같은거 넣어줌
- disk-backup thread: 주기적으로 버퍼 내용을 디스크에 save
- ex) 사용자가 800페이지 문서 중 1페이지의 한 문장 삭제하고 600페이지로 가고 싶음
- 싱글 스레드
- 워드는 사용자가 1페이지 수정한 대로 책 전체 reformatting다 해야 600페이지 보여줄 수 있음 그 동안엔 다른 동작 못함(delay 발생)
- 멀티 스레드
- interactive 스레드가 1페이지 문장 수정됐으니 책 전체 reformatting하라고 reformatting 스레드에게 알려줌
- reformatting 스레드는 백그라운드에서 reformatting함.
- 그동안 interactive 스레드는 사용자와 interaction함. 스크롤 같은 간단한 명령에 응답.
- 사용자가 600페이지로 이동하라 요청했을 때 운 좋다면 reformatting다 끝나서 즉시 표시될 것임
예시) 멀티 스레드 웹 서버


- (a) 디스패처(dispatcher) 스레드 - 한 개 (b) 작업(worker) 스레드 - 여러 개
- 클라한테 웹 페이지 읽어달란 요청오면 dispatcher 스레드가 일감을 worker 스레드 중 하나한테 전해줌(handoff_work).
- worker 스레드가 cash에 웹 페이지 있나 체크하고 없으면 디스크에서 읽어오면 됨
- return_page에 웹 페이지 넣어서 return해줌
3. 사용자 공간에서 스레드의 구현
1) 스레드 구현 방법
- user space에서 thread 구현
- kernel에서 thread 구현
2) user space에서 thread 구현
- 유저 레벨 스레드

- 정의) 스레드가 Run-time system에서 수행
- 커널은 스레드 존재를 모르고, 프로세스만 스케줄링함.
- Run-time system: 스레드 관리하는 프로시저 모음
- thread_create, exit, wait, yield
- 특징)
- 스레드테이블을 run-time system이 관리
- 스레드가 유저스페이스에서 관리됨
- 각 프로세스는 자신만의 스레드 테이블 가짐
- 장점) [중요]
- user level thread는 thread 지원하지 않는 os에서도 구현이 가능함
- 커널이 스레드 존재 모르고, os는 스레드를 지원하지 않아도 유저 레벨 스레드는 유저 레벨에서 thread package 구현하기에 문제 없이 사용 가능
- thread switching이 kernel level thread보다 빠름
- 커널 레벨 스레드는 커널이 직접 스레드 스케줄링하기 때문에 커널에 한 번 들어가서 스레드 스위칭 일어나는데 시간 많이 걸림
- 유저 레벨 스레드는 스레드 스위칭이 커널로 안들어가고 일어나서 빠름
- ex) 서연이가 기우가 일 마칠 때까지 block되려하면, 시스템콜하는 게 아니라 thread_wait(run-time system의 procedure) 콜함. 따라서 run-time system이 wait 콜한 스레드 block으로 놓고 ready상태의 스레드를 running으로 올림
- [참고만] 각각의 프로세스가 자신만의 customized된 스케줄링 알고리즘 가질 수 있음
- [참고만] 스케일이 더 잘됨
- 커널 레벨 스레드는 커널이 스레드 테이블 가짐. 얼마나 많은 프로세스가 스레드 만들지 모름. 스레드 얼마나 많이 존재할지 모르니 처음부터 스레드 테이블 세팅시 크게 잡아야함. 스레드 세팅 부담됨.
- 유저 레벨 스레드는 프로세스 생길 때마다 그 안에서 스레드 테이블 생기니 스케일 더 잘됨. 딱 맞는 스레드 테이블 세팅 더 용이함. 각자 테이블 관리하고 커널엔 따로 스레드테이블 세팅할 필요 없음.
- 단점)
- block 시스템콜 발생 시 문제 생김
- 프로세스의 스레드 세 개 중 하나 수행하다 Read 시스템콜하면 프로세스가 block상태로 감. 스레드가 블록상태로 간건데, 커널 입장에선 프로세스를 통쨰로 block상태로 보냄. 동료 스레드들은 수행기회 못갖게 됨
- 해결 방법 2가지
- blocking 시스템 콜을 nonblocking 시스템콜로 바꾸자
- read 시스템콜이 사용자가 키보드에 아직 입력안해서 block되는건데, 이를 nonblocking으로 코드 바꿈. 커널을 고침.
- read 시스템콜을 했을 때 키보드 버퍼에 아무것도 없으면 0같은거로 바로 리턴함. block하지 않고. 물론, 마침 사용자가 버퍼에 뭐 넣으면 그거 리턴함. 어떤 경우든 블락 안되게 시스템콜 커널 코드를 고쳐버림.
- 유저 레벨 스레드의 장점이 커널 안해도 되는 거였기에 좋은 해결책이 아님.
- wrapper 씌어서 콜했을 때 block될지 미리 체크하자 [참고로만 알기]
- 프로그래머가 read 시스템콜을 할 때, 정확힌 read 라이브러리 procedure를 콜하는 건데, 이 read 라이브러리 procedure를 감싸는 리드를 새로 구현함. 사용자는 이 새로 구현한 wrapper read를 콜함.
- wrapper read는 read콜하기 전에 select를 콜함. select는 read를 콜했을 때 block이 될지 안될지를 알려줌. block된다하면 read콜안하고 select-illd를 콜해서 cpu를 일단 다른 애한테 넘겨주고 본인은 ready로 감. wrapper 코드는 loop에 있어서 언젠가 다시 running으로 오면 select를 다시 콜해 여전히 block될지 확인함.(버퍼에 뭔가 들어왔으면 block안됨)
- 프로세스 안(run-time system)에서 clock interrupt 받아 처리하는 건 구현 상 어려움
- 스레드가 어떤 프로세스 속에서 CPU 할당받아 수행하면 스레드가 자발적으로 CPU 넘기지 않는 이상(give up) 그 프로세스의 다른 스레드들은 CPU 차지할 기회 없음
- 프로세스의 quantum 쪼개서 스레드마다 나눠주고 퀀텀 다 차면 clock interrupt 통해 해당 프로세스의 다른 스레드들에게 CPU 주는 것 구현이 어려움
- 한 스레드는 양보안하면 프로세스의 할당량 혼자 다써버릴 수 있음
4. 커널 내부에서 스레드 구현
1) kernel에서 thread 구현
- 커널 레벨 스레드

- 정의) 커널이 스레드 존재를 다 알아서, 커널이 스레드 스케줄링을 함.
- 특징)
- 스레드테이블을 커널이 관리
- 스레드패키지를 커널이 관리함
- 장점)
- 스레드가 block 시스템콜하면 해당 프로세스의 모든 스레드가 블락되는 일 없음
- 해당 스레드만 block 상태로 보냄.
- 멀티스레드 웹서버 구현 제대로 가능
- nonblocking 시스템콜 요청안해도 됨
- 단점)
- 커널 들어가야해서 시간 많이 소요됨
5. 하이브리드 구현
- 정의) hybrid(혼합한 것) 커널레벨과 유저레벨을.
- 커널레벨에서 스레드 제공되는데 커널레벨스레드 하나당 다시 유저 스페이스에서 여러개의 스레드가 매핑되어있음

6. 스케줄러 활성화(activations)
- 정의) user, kernel 레벨 스레드 장점 혼합함
- user 레벨 스레드의 장점 수용
- 유저 레벨에서 스레드 activity(ex. thread wait) 처리함 → 필요 없이 커널 들어가는 걸 피함 (유저 레벨의 장점)
- kernel 레벨 스레드의 장점 수용
- 어떤 프로세스속의 스레드 하나가 read같은 io시스템콜하면 일단 시스템콜햇으니 커널로 들어감.
- 커널이 프로세스 통째로 block안하고 스레드 존재 알고잇기에(프로세스 a의 스레드 2번이 disk io 요청하는 read 콜한걸 알기에), 이 정보 가져다가 유저 레벨에 있는 런타임 시스템에게 알려줌
- 알려주는 방법: 커널이 유저시스템쪽으로 콜해서 runtime system불러다가 너가 관리하는 프로세스의 어떤 스레드가 리드시스템콜을 했다고 알려줌 (up call)
- 런타임시스템이 스레드를 block상태로 보냄. 커널이 보내는게 아님.
- 시간 흘러 io요청 완료되어 interrupt걸리면 커널이 수행되면서 디스크 리드 요청햇던거 누군지 알기에 커널이 다시 런타임시스템콜에게 이를 알려줌. 그러면, 유저스페이스에 잇던 런타임시스템콜이 블락상태에 보내논 그놈을 레디상태로 올려줌.
- io 관련한 리드 시스템콜 발생할지라도 프로세스 전체가 블락될 일 없음. (커널레벨의 장점)
7. 팝업 스레드
- 정의) 메시지가 도착하면 이를 처리하는 새로운 스레드 생성
- 기존 구조에선 incomming message 처리하는 스레드들이 대기하고 있다가, 메시지 들어오면 결과적으로 블락되잇는 스레드가 레디로 빠지고 interrupt 처리함. 레디에서 러닝으로 갈 때 예전상태 load해줘야하는데 이 reload가 시간 많이 걸림
- 팝업 스레드는 이거 시간 줄이기 위해 incomming messag 들어왓을때 대기하지말고 스레드 새로 만듬. 새로 만든건 상태 reload할 필요 없음.

8. 단일 스레드 코드를 다중 스레드 형태로 만들기
- 정의) 스레드마다 private한 global 변수 저장하는 부분 만듬
- 기존 프로세스모델: 스레드 하나
- 시스템콜 처리중 에러 발생하면 global 변수 errno에 에러 저장됨.
- 기존 프로세스 모델이 아니라 스레드 모델, 멀티 스레드 생각하면 스레드 두 개 잇을 수 잇음. 둘다 error 값이 errno에 저장됨. 글로벌변수라서 errno값이 overwritten되어버림. 낭패.
- access: 해당파일 잇나 체크하는 시스템콜. 파일 없으면 에러


