3️⃣

[운영체제론] Chapter 2.2 Thread(스레드)

작성일자
Oct 4, 2022
태그
SUB PAGE
프로젝트
운영체제론
책 종류
본 게시글은 하단 책을 읽고 학습한 내용을 제 생각으로 요약, 정리한 글입니다.
 
목차

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
    • 자원
      • notion image
      • Per process items
        • 한 프로세스 내 모든 스레드가 공유하는 항목들
      • Per thread items
        • 각 스레드들이 개별적으로 보유하는 항목들
    • 각각의 스레드는 본인의 stack 가짐
      • notion image
      • 스레드마다 실행흐름 다 달라서 콜하는 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되어서 임계구역 들어감.
  • 프로세스와 스레드 예시
    • notion image
    • (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다 끝나서 즉시 표시될 것임
예시) 멀티 스레드 웹 서버
notion image
notion image
  • (a) 디스패처(dispatcher) 스레드 - 한 개 (b) 작업(worker) 스레드 - 여러 개
  • 클라한테 웹 페이지 읽어달란 요청오면 dispatcher 스레드가 일감을 worker 스레드 중 하나한테 전해줌(handoff_work).
  • worker 스레드가 cash에 웹 페이지 있나 체크하고 없으면 디스크에서 읽어오면 됨
  • return_page에 웹 페이지 넣어서 return해줌

3. 사용자 공간에서 스레드의 구현

1) 스레드 구현 방법

  1. user space에서 thread 구현
  1. kernel에서 thread 구현

2) user space에서 thread 구현

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

4. 커널 내부에서 스레드 구현

1) kernel에서 thread 구현

  • 커널 레벨 스레드
    • notion image
  • 정의) 커널이 스레드 존재를 다 알아서, 커널이 스레드 스케줄링을 함.
  • 특징)
    • 스레드테이블을 커널이 관리
      • 스레드패키지를 커널이 관리함
  • 장점)
    • 스레드가 block 시스템콜하면 해당 프로세스의 모든 스레드가 블락되는 일 없음
      • 해당 스레드만 block 상태로 보냄.
      • 멀티스레드 웹서버 구현 제대로 가능
      • nonblocking 시스템콜 요청안해도 됨
  • 단점)
    • 커널 들어가야해서 시간 많이 소요됨

5. 하이브리드 구현

  • 정의) hybrid(혼합한 것) 커널레벨과 유저레벨을.
    • 커널레벨에서 스레드 제공되는데 커널레벨스레드 하나당 다시 유저 스페이스에서 여러개의 스레드가 매핑되어있음
      • notion image

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. 팝업 스레드

  • 정의) 메시지가 도착하면 이를 처리하는 새로운 스레드 생성
    • notion image
    • 기존 구조에선 incomming message 처리하는 스레드들이 대기하고 있다가, 메시지 들어오면 결과적으로 블락되잇는 스레드가 레디로 빠지고 interrupt 처리함. 레디에서 러닝으로 갈 때 예전상태 load해줘야하는데 이 reload가 시간 많이 걸림
    • 팝업 스레드는 이거 시간 줄이기 위해 incomming messag 들어왓을때 대기하지말고 스레드 새로 만듬. 새로 만든건 상태 reload할 필요 없음.
 

8. 단일 스레드 코드를 다중 스레드 형태로 만들기

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

학습테스트