Process Model
지금까지는 프로세스와 프로세스 스케줄링, 프로세스 통신에 대해서 정리하였다.
이렇게 프로세스를 사용하여 컴퓨터 프로그램의 동작을 기술하는 것을 Process Model 이라고 하며,
이번 글부터는 Thread Model 을 통해서 프로그램의 동작을 기술해보고자 한다.
Process Model 은 크게 2가지 개념을 갖는다.
1. Resource Grouping
2. Thread
리소스 그룹핑은 다음과 같은 데이터를 프로세스 단위로 갖는 것을 말한다.
- Address Space
(메모리에 프로세스가 차지하는 공간, 유닉스의 경우 Text(Code) 세그먼트, Data 세그먼트, Stack 세그먼트로 구분)
- open file (현재 이 프로세스가 open 한 file에 대한 정보)
- child process
- accounting information
스레드는 각 프로세스 내에 존재하는 '실행 흐름' 을 말한다.
각각의 스레드는 program counter, register, stack 데이터를 갖는다.
(스레드는 하나의 프로시저 실행 흐름을 말하는 것과 같기에 프로시저가 갖는 스택 영역을 그대로 갖는다.)
Thread Model
지금까지 배운 프로세스 모델에서는 이 실행 흐름이 1개만 존재했다.
스레드 모델에서는 하나의 프로세스 환경 안에 여러 개의 스레드가 존재할 수 있다. (multithreading)
따라서 스레드 모델에서는 CPU 스케줄링의 단위가 '스레드' 하나이다.
과거에는 스레드를 Lightweigt process 라고도 불렀다.
각각의 스레드는 프로그램 카운터, 레지스터, 스택만 갖고 있으면서 프로세스의 데이터는 다른 스레드와 공유하고
프로세스는 상술한 주소 공간, 파일 정보, 자식 프로세스 등의 모든 정보를 갖고 있으니 무겁다고 생각했기 때문이다.
다음 그림은 프로세스 모델과 스레드 모델의 차이점을 보여준다.
메모리에는 크게 유저 스페이스와 커널 스페이스로 나눠지는데, 유저 프로세스는 유저 스페이스에 존재한다.
유저 프로세스 내에 존재하는 스레드 역시 유저 스페이스에 존재한다.
프로세스 모델은 하나의 프로세스가 하나의 스레드를 가지므로 3개의 실행 흐름이 존재하려면 3개의 프로세스가 필요했다.
반면 스레드 모델은 하나의 프로세스 안에 3개의 실행 흐름을 넣을 수 있다.
이 경우, 각각의 스레드는 하나의 프로세스가 가지는 address space 를 공유한다.
보통 스레드 모델을 사용하는 경우, 각각의 스레드는 하나의 job을 수행하기 위해 역할을 분담하여 각자 일을 수행하고, 서로 돕기도 한다.
스레드 모델에서 각각의 스레드에는 protection 이 존재하지 않는다.
왜냐하면 하나의 프로세스 안에서 서로 역할을 분담하여 협력하고 있기 때문이다.
스레드 모델에서는 스레드 단위로 갖고 있는 정보와 프로세스 단위로 갖고 있는 정보가 나뉜다.
쓱 보면 당연한 말이지만, 한번 더 정리해보자면
스레드는 하나의 실행 흐름으로서 보통 하나의 함수가 스레드로 동작하기 때문에 함수에서 필요한 데이터를 갖는다.
따라서 프로그램 카운터, 레지스터, 스택 데이터를 가지며, 하나의 프로세스에서 여러 개의 스레드가 동시에 실행될 수는 없으므로 프로세스가 가졌던 그 상태를 스레드가 가지게 된다. (running, blocked, ready)
Per process item 은 모든 스레드가 공유하는 데이터로,
Address Spcae, 전역 변수, 현재 이 프로세스가 오픈한 파일, 자식 프로세스 등의 정보를 갖는다.
그림으로 보면 각각의 스레드는 각자의 스택과 상태를 가진다.
스택에는 각 스레드에서 호출되었지만 아직 return 하지 않은 프로시저의 정보, return address, 지역 변수 등이 들어있다.
또 일반적으로 각각의 스레드는 서로 다른 프로시저를 호출하므로, 다른 호출 기록을 갖는다.
스레드 관련 라이브러리 함수들
- thread_create(실행할 프로시저 이름)
보통 프로세스가 시작되면, 초기에 그 자체로 스레드를 하나 갖는다.
그 스레드 내에서 이 함수를 호출하면 새로운 스레드를 시작하면서 multithreading 이 시작된다.
- thread_exit
스레드가 작업을 마치면 이 프로시저를 호출하여 빠져나온다.
(이 메서드는 작업하는 스레드가 호출할까, thread_create를 호출한 스레드가 호출할까..?)
- thread_wait
대상으로 지정한 스레드가 exit 할 때까지 작업을 멈추고 기다린다. (blocked)
대상 스레드가 eixt()을 콜하면 이 스레드를 호출한 스레드가 blocked 상태에서 나와 다시 ready로 들어간다.
- thread_yeild
TSL 명령어 방식의 busy waiting 과 비교되는, 뮤텍스에서 등장한 그 함수이다.
현재 CPU의 사용을 다른 스레드에게 양보하는 프로시저이다.
이 프로시저를 호출한 스레드는 ready로 가고, ready 에 존재하는 스레드 중 스케줄링 알고리즘에 따라 선발된 스레드가 running 으로 바뀐다.
스레드의 사용 이유
- 어플리케이션이 실행되면 그 안에서는 여러가지 작업이 일어나게 되는데, 이 각각의 작업을 스레드로 만들면 프로그래밍 하기가 직관적이고 쉽다.
- 프로세스보다 생성과 삭제가 빠르고 쉽다. 프로세스 생성에 필요한 리소스를 다 만들 필요 없이, 스레드에 필요한 정보만 만들면 되기 때문이다.
- 프로세스 작업의 성능면에서도 좋다. 스레드가 하나일 때는 I/O 작업을 요청하는 순간 프로세스 자체가 Blocked 상태로 빠져서 해당 프로세스는 아무 것도 하지 못하지만, 여러 스레드일 경우에는 I/O 작업을 스레드에게 시키고, 그 스레드가 Blocked 상태에 빠져있는 동안에는 다른 스레드를 통해서 작업을 진행할 수 있으므로 프로세스 입장에서는 쉬지않고 작업을 이어나갈 수 있기 때문이다.
- 만약 하드웨어가 멀티플 CPU를 지원한다면 진정한 의미로 병렬 처리가 가능해진다. 단일 스레드의 프로세스를 여러 CPU가 실행할 수는 없지만, 하나의 프로세스를 구성하는 여러 스레드는 여러 CPU가 실행할 수 있기 때문이다.
스레드 사용 예시
워드프로세서 프로그램을 만든다고 하면, 이 프로그램은 그림과 같이 3개의 스레드로 구성하도록 만들 수 있다.
1. 키보드로 입력 받고 화면에 입력한 글자를 출력하면서 사용자와 상호작용하는 스레드 (interactive thread)
2. 복사 붙여넣기 등, 문자열의 변화에 따라 포맷팅을 그때 그때 바꿔주는 스레드 (reformatting thread)
3. 주기적으로 작업 내용을 디스크에 백업하는 스레드 (disk-backup thread)
2번 스레드의 기능 예시를 조금 더 자세히 든다면, 만약 600페이지의 문서를 작업하다가 1페이지에서 일부 문장을 삭제했다고 해보자.
그러면 2페이지에 있는 문장 중 일부가 1페이지로 오고, 3페이지 문장 중 일부가 2페이지로 오고...
이런 리포맷팅 작업을 2번 스레드가 해준다.
만약 이 와중에 사용자가 1페이지 내용을 읽어보기 위해 스크롤을 내린다면, interactive thread 가 이를 처리해야 하는데,
단일 스레드로 구성된 프로그램이라면 600페이지의 리포맷팅 작업이 끝나기 전에는 스크롤을 내리는 작업을 할 수가 없다.
하지만 다중 스레드로 구성된 프로그램이라면 1페이지 리포맷팅을 마치고 뒷 페이지들의 리포맷팅을 하는 동안 사용자의 1페이지 화면 스크롤을 interactive thread 가 처리할 수 있으므로 사용자 입장에서는 끊김없이 프로그램을 사용하는 느낌을 느낄 수 있다.
이번엔 스레드를 활용하는 두번째 예시를 보자.
이 그림은 웹 서버 프로세스를 나타낸 그림이다.
웹 서버 프로세스는 여러 개의 스레드를 갖고 있다.
먼저 dispatcher thread 는 네트워크 요청이 들어오면 이 요청을 처리하는 worker thread 에게 전달하는 역할을 수행한다.
dispatcher thread 의 코드를 살펴보면 아래와 같다.
코드를 보면 무한 반복을 돌면서 request 를 get_next_request() 를 통해 받아들이고, 받아들인 요청을 handoff_work 메서드를 통해 처리하도록 넘긴다. 요청값은 &buf 변수에 담겨서 전달된다.
worker thread 는 그림과 같은 슈도코드로 동작한다.
먼저 request 처리 작업 요청을 기다리다가 dispatcher process 가 요청을 넘겨주면 받은 요청에 대한 페이지가 캐시에 있는지 먼저 찾아본다. look_for_page_in_chache() 함수는 만약 요청이 캐시에 있다면 page 변수에 응답할 페이지 데이터를 담아주고, 없다면 null 로 둔다고 생각하면 된다.
if 문에서는 이 page 변수에 값이 들어있는지 체크한다.
만약 값이 들어있지 않았다면 page가 캐시에 없었다는 뜻이 된다.
만약 캐시에 페이지가 없다면(page변수가 널이라면) disk 에서 응답할 페이지를 가져오며, 이때는 read() 시스템 콜을 호출하므로 blocked 상태로 빠지게 된다.
read를 마치고 가져온 데이터를 page 변수에 담은 뒤에는 return_page를 통해 페이지를 응답한다.
이 과정이 만약 기존의 프로세스 모델 (단일 스레드) 방식에서 동작했다면 어떻게 되었을까?
캐시에 없는 페이지 요청이 들어와서 디스크에서 페이지를 읽느라 blocked 상태에 빠진 사이, 캐시에 있는 페이지 요청이 들어왔을 때 이 웹서버는 그 요청을 빠르게 처리할 수 없다.
하지만 스레드 모델을 사용한다면, 캐시에 없는 페이지 요청이 들어와서 blocked 상태에 빠지더라도, 다른 worker thread가 캐시에 있는 페이지 요청을 적절하게 처리할 수 있으므로 다수의 요청을 보다 빠르게 처리할 수 있게 된다.
'CS > 운영체제' 카테고리의 다른 글
[운영체제] 14. 스레드 스케줄링 (0) | 2024.10.21 |
---|---|
[운영체제] 13. 스레드의 구현 방법 (0) | 2024.10.21 |
[운영체제] 11. 스케줄링 (5) - Policy vs. Mechanism (0) | 2024.10.21 |
[운영체제] 10. 스케줄링 (4) - Real-Time System 스케줄링 (0) | 2024.10.21 |
[운영체제] 9. 스케줄링 (3) - Interactive System 스케줄링 (0) | 2024.10.20 |