3단원
프로세스 (Process)
3.1 프로세스 개념
비공식적으로, 프로세스란 실행 중인 프로그램이다.
메모리 상의 프로세스
스택 - 함수 매개변수, 복귀주소, 지역변수
힙 - 실행중에 동적할당
데이터 - 전역변수
텍스트 - 프로그램 코드
프로그램은 수동적 개체, 프로세스는 능동적 개체
프로그램은 메모리로 로드되어 실행중인 파일이 될때 프로세스가 된다.
하나의 프로그램은 여러개의 프로세스로 작동될 수 있다.
프로세스가 실행될때, 상태(state)가 변한다.
new : 프로세스 생성중
running : 명령어들이 실행되고 있다.
waiting : 프로세스가 어떤 사건(입출력 완료나 신호의 수신 같은)이 일어나기를 기다린다
ready : 프로세스가 처리기에 할당되기를 기다린다
terminated : 프로세스의 실행이 종료되었다.
ex)잡큐, 레디큐,I/O큐
프로세스 제어 블록(Process Control Block)
각 프로세스는 운영체제에서 프로세스 제어 블록에 의해 표현된다.
3.2 프로세스 스케줄링
다중 프로그래밍의 목적은 CPU 이용을 최대화하기 위하여 항상 어떤 프로세스가 실행되도록 하는데 있다.
시분할의 목적은 각 프로그램이 실행되는 동안 사용자가 상호 작용할 수 있도록 프로세스들 사이에서
CPU를 빈번하게 교체하는 것이다.
이 목적을 달성하기 위해 프로세스 스케줄러는 CPU에서 실행 가능한 여러 프로세스들 중에서
하나의 프로세스를 선택한다.
-스케줄링 큐
프로세스가 시스템에 들어오면, 이들은 잡 큐에 놓인다. 이 큐는 시스템 안의 모든 프로세스로 구성된다.
주 메모리에 존재하며, 준비 완료 상태에서 실행을 대기하는 프로세스들은 실행을 대기하는 프로세스들은 레디 큐라 불리는
리스트 상에 유지된다. 특정 입출력 장치를 대기하는 프로세스들의 리스트를 장치 큐라고한다. 각 장치는 그 자신의 장치 큐를 가진다.
새로운 프로세스는 처음에 준비 완료 큐에 놓인다. 프로세스는 실행을 위하여 선택 될 때, CPU를 디스패치 할때까지 준비 완료 큐에서 대기한다.
입출력요청과, fork 의 경우 프로세스는 대기 상태에서 준비 완료 상태로 전환되고, 다시 준비 완료 큐에 넣어지게 된다.
프로세스는 이 주기를 반복하며, 종료되면 모든 큐에서 삭제, 그 자신의 PCB와 자원을 반납한다.
-스케줄러
장기 스케줄러(또는 잡 스케줄러)는 이 풀에서 프로세스들은 선택하여 실행 하기 위해 메모리로 적재한다. 단기 스케줄러(CPU 스케줄러)는
실행 준비가 완료되어 있는 프로세스들 중에서 선택하여, 이들 중 하나에게 CPU를 할당한다.
이들 두 스케줄러의 차이는 실행 빈도이다. 단기 스케줄러는 CPU를 위해 반드시 자주 새로운 프로세스를 선택해야 한다.(ex IO의 경우 스케줄링에 시간낭비)
장기 스케줄러는 실행 빈도수가 훨씬 적다. 다중 프로그램의 정도(메모리에 있는 프로세스들의 수)를 제어
대부분의 프로세스들은 IO중심 또는 CPU중심으로 표현되어진다. IO중심 프로세스는 연산보다는 입출력실행에, CPU중심프로세는 연산에 시간을 더 소요한다.
단기 스케줄러 : 빠른알고리즘사용해야함, 레디큐에서 실행으로
장기 스케줄러 : 어떤작업을 메인메모리에 올릴지 , 잡큐에서 레디큐로
중기 스케줄러 핵심 아이디어 : 메모리에서(CPU를 위해 적극적으로 경쟁하는) 프로세스들을 제거함으로써 다중 프로그래밍의 정도를 완화
차후에 다시 프로세스를 메모리로 불러와서 중단되었던 지점부터 실행을 재개한다. 이러한 기법을 스와핑(swapping) 이라한다.
프로세스는 중기 스케줄러에 의하여 스왑되어 나가고 후에 다시 스왑되어 들어온다.
문맥 교환(Context Switch)
인터럽트가 발생하면 시스템은 인터럽트 처리가 끝난 후에 문맥을 복구할 수 있도록 현재 실행중인 프로세스의 현재 문맥을 저장할 필요가 있다.
문맥은 프로세스의 PCB에 표현된다. 문맥은 CPU레지스터값,프로세스상태,메모리관리 정보 등을 포함
일반적으로 커널,사용자모드 둘다 CPU의 현재 상태를 저장하는 작업을 수행(state save)하고 나중에 연산을 재개하기 위하여 상태 복구 작업을 수행한다(state restore)
CPU를 다른 프로세스로 교환하려면 이전의 프로세스의 상태를 보관하고 새로운 프로세스의 보관된 상태를 복구하는 작업이 필요하다.
이 작업은 문맥 교환(context switch)이라 한다.
문맥 교환이 일어나면, 커널은 과거 프로세스의 문맥을 PCB에 저장하고, 실행이 스케줄된 새로운 프로세스의 저장된 문맥을 복구한다.
문맥 교환이 진행될 동안 시스템이 아무런 유용한 일을 못하기 때문에 문맥 교환 시간은 순수한 오버헤드이다.
따라서 상호적으로 타임쉐어링을통해서 사용자 입장에서 response time을 줄여야한다.
3.3 프로세스에 대한 연산
대부분 시스템 내의 프로세스들은 병행 실행될 수 있으며, 반드시 동적으로 생성 및 제거되어야 한다.
-생성
생성하는 프로세스를 부모 프로세스라고 부르고, 새로운 프로세스는 자식 프로세스라고 부른다.
이 새로운 프로세스들은 각각 다시 다른 프로세스들으 생성할 수 있으며, 그 결과 프로세스의 트리를 형성한다.
유닉스,리눅스,윈도우 같은 대부분 OS에서 유일한 프로세스 식별자(pid)를 사용하여 프로세스 구분 (커널이 유지하는 속성에 접근하기 위한 인덱스로 사용됨)
리눅스의 경우 pid가 1인 init 프로세스가 모든 사용자 프로세스의 루트 부모 프로세스 역할을 수행한다.
일반적으로 프로세스가 자식 프로세스를 생성 할때, 그 자식 프로세스는 자신의 임무를 달성하기 위하여 어떤 자원(CPU 시간,메모리,파일,입출력장치 등)이 필요하다
OS로부터 얻거나, 부모가 부분을 제공하거나, 부모와 자식 서로 공유하지않는 총 3가지가 있다.
프로세스가 새로운 프로세스를 생성할때, 두 프로세스를 실행시키는 데 두 가지 가능한 방법이 존재한다
1. 부모는 자식과 병행하게 실행을 계속한다
2. 부모는 일부 또는 모든 자식이 실행을 종료할 때까지 기다린다
유닉스에서 새로운 프로세스는 fork()시스템 호출로 생성된다. 새로운 프로세스는 원래 프로세스의 주소 공간의 복사본으로 구성된다.
이 기법은 부모 프로세스가 쉽게 자식 프로세스와 통신할수 있게한다.
두개의 프로세스 즉 부모와 자식은 fork()후의 명령어에서부터 실행을 계속하며, 이 때 한 가지 다른점은 fork()의 복귀 코드가 서로 다르다는 것이다.
fork() 함수는 부모프로세스에게 자식 pid 를 반환하고 , 자식에게는 0을 반환한다.
fokr() 시스템 호출 다음에는 두 프로세스 중 한 프로세스가 exec() 시스템 호출을 사용하여 자신의 메모리 공간을 새로운 프로그램을 교체한다.
자식이 실행하는 동안 부모는 할일이 없으면 자식이 종료될때까지 준비 완료 큐에서 자신을 제거하기 위해 wait()시스템 호출을 한다.
exec()을 호출하면 프로세스의 주소 공간을 새 프로그램으로 덮어 쓰기 때문에 exec()시스템 호출은 오류가 발생하지 않는 한 제어를 반환하지 않는다.
윈도우에서는 Windows API의 CreateProcess() 함수를 이용하여 생성되는데 이 함수는 부모 프로세스가 새로운 자식 프로세스를 생성한다는 점에서
fork()와 유사하다. 포크와의 차이점은 포크는 부모에게 주소 공간을 상속받는 자식을 만드는 반면 윈도우에선 프로세스가 생성될때 주소 공간에 명시된
프로그램을 적재한다. wait()과 유사한 것은 WaitForSingleObjects()이고 이 함수는 자식 프로세스의 핸들을 전달받고, 이 프로세스가 종료되기를 기다린다.
자식 프로세스가 종료하면 제어는 부모 프로세스의 WaitForSingleObjects()에서 반환된다.
-프로세스 종료
프로세스가 마지막 문장의 실행을 끝내고, exit 시스템 호출을 사용하여 운영체제에게 자신의 삭제를 요청하면 종료한다.
이 시점에서 프로세스는 자신의 부모 프로세스에게 상태 값을 반환할수 있다. 물리 메모리와 가상 메모리, 버퍼 등 프로세스의 모든 자원이 운영체제로 반납된다.
부모는 자식의 실행을 종료할수 있다. (자식이 자원을 초과사용할때, 자식에게 할당된 태스크가 필요없을때, 등)
부모 프로세스가 종료한 이후에 자식프로세스가 존재할수없다. 그래서 프로세스가 종료되면 비롯된 모든 자식들도 종료되어야한다(=연쇄식종료 cascading termination)
wait()시스템 호출은 부모가 자식의 종료 상태를 얻어낼수있도록 하나의 인자를 전달받는다. 이 시스템 호출은 부모가 어느 자식이 종료되었는지 구별할 수 얻어낼수있도록
종료된 자식의 프로세스 식별자를 반환한다.
3.4 프로세스 간 통신 (IPC = Interprocess Communication)
- 메시지 전달(message passing)
충돌을 회피할 필요가 없기 때문에 적은 양의 데이터를 교환하는데 유용하다. 또한 분산 시스템에서 공유메모리 보다 구현하기가 쉽다.
메시지 전달 시스템은 통상 시스템 호출을 사용하여 구현되므로 커널 간섭 등의 부가적인 시간 소비 작업들이 필요하기 때문에
공유 메모리 모델이 메시지 전달보다 빠르다. 다중 코어에서는 메시지 전달이 공유 메모리보다 빠르다.
동일한 주소 공간을 공유하지 않고도 프로세스들이 통신을 하고, 그들의 동작을 동기화 할 수 있도록 허용하는 기법
서로 통신을 원하는 프로세스 두개가 통신연결(communication link)설정
-직접통신시에 수신자 또는 송신자의 이름을 명시 (주소방식에서 대칭성을 보임)
프로세스 이름 변경을 일일이 사용자가 체크해야함. 이러한 하드 코딩 기법은 바람직하지않음
-간접통신에서 메시지들은 메일박스 또는 포트로 송신되고, 그것으로부터 수신된다.
-동기화
메시지 전달은 봉쇄형이거나 비봉쇄형방식이다. (각각 동기식 비동기식)
봉쇄형 보내기 : 송신하는 프로세스는 메시지가 수신 프로세스 또는 메일 박스에 의해 수신될 때까지 봉쇄한다.
비봉쇄형 보내기 : 송신하는 프로세스가 메시지를 보내고 작업을 재시작한다.
봉쇄형 받기 : 메시지가 이용 가능할때까지 수신 프로세스가 봉쇄된다.
비봉쇄형 받기 : 수신하는 프로세스가 유요한 메시지 또는 널(null)을 받는다
-버퍼링
통신이 직접이든 간접이든 통신하는 프로세스들에 의해 교환되는 메시지는 임시 큐에 들어 있다.
기본적으로 이러한 큐를 구현하는 방식은 세 가지가 있다.
무용량 : 큐의 최대 길이가 0
유한 용량 : 큐는 유한한 길이 n을 가진다
무한 용량 : 큐는 잠재적으로 무한한 길이를 가진다. 따라서 메시지들이 얼마든지 큐안에서 대기할수 있다. 송신자는 결코 봉쇄되지 않는다
- 공유 메모리
공유 메모리 영역을 구축할때만 시스템 호출이 필요함
모든 접근은 일반적인 메모리 접근으로 취급되어 커널의 도움이 필요 없다.
공유 메모리는 공유데이터가 여러 캐시 사이에서 이주하기 때문에 발생하는 캐시 일관성 문제로 인하여 성능 저하 발생
시스템의 처리 코어의 수가 증가하면 할수록 IPC로 메시지 전달이 더 선호된다.
생산자 소비자문제 - 생산자 프로세스는 정보를 생산하고 소비자 프로세스는 정보를 소비(공유메모리를 사용하는것)
버퍼가 생산자와 소비자가 공유하는 메모리 영역에 존재. 서로 동기화되어야함!
무한버퍼와 유한버퍼
원형배열로 구현하여서 in==out 일때 empty , ((in+1)% BUFFER_SIZE) == out) 일땐 full
3.5 IPC 시스템의 사례
POSIX 공유 메모리는 메모리-사상 파일을 사용하여 구현된다.
프로세스는 먼저 shm_open()시스템 호출을 사용하여 공유 메모리 객체를 생성해야 한다.
객체가 설정되면 ftruncate()함수를 사용하여 객체의 크기를 바이트 단위로 설정한다.
마지막으로 mmap() 함수가 공유 메모리 객체를 포함하는 메모리-사상 파일을 구축한다.
shm_unlink() 함수 호출하여 접근이 끝난 공유 메모리를 제거한다.
Mach
Mac OS X 운영체제의 일부인 Mach
마크 대부분의 통신은 메시지에 의해 실행된다. 메시지는 메일박스를 통하여 전송하고 수신되는데, Mach에서는 이를 포트(port)라고 한다.
port_allocate() 시스템 호출은 새로운 메일박스를 생성하고, 그 메일박스의 메시지큐를 위한 공간을 할당한다.
Windows
다중 운영 환경 또는 서브시스템을 지원하며, 응용 프로그램은 메시지 전달 기법을 통해 이들과 통신한다.
3.6 클라이언트 서버 환경에서의 통신
- 소켓(Socket)
소켓은 통신의 극점을 뜻한다. 두 프로세스가 네트워크상에서 통신을 하려면 양 프로세스마다 하나씩, 총 두개의 소켓이 필요하다.
각 소켓은 IP주소와 포트 번호 두 가지를 접합해서 구별한다.
- 원격 프로시저 호출(Remote Procedure Calls, RPC)
RPC는 네트워크에 연결되어 있는 두 시스템 사이의 통신을 프로시저 호출을 추상화하기 위한 방편으로 설계되었다.
프로세스들이 서로 다른 시스템위에서 돌아가기 때문에 원격 서비스를 제공하기 위해서는 메시지 기반 통신해야함
- 파이프(pipes)
파이프는 두 프로세스가 통신할 수 있게 하는 전달자로서 동작한다. 파이프는 초기 유닉스 시스템에서 제공하는
IPC 기법 중의 하나였다. 파이프는 통산 프로세스들 간에 통신하는 더 간단한 방법 중의 하나이지만 통신할때 여러 제약을 가진다.
1. 파이프가 단방향 통신 또는 양방향 통신을 허용하는지
2. 양방향 통신이 허용된다면 반이중 방식인가, 전이중 방식인가(반이중은 한 순간에 한 방향 전송만 가능,전이중은 동시에 양항뱡 전송가능)
3. 통신하는 두 프로세스 간에 부모-자식과 같은 특정 관계가 존재하는지
4. 파이프는 네트워크를 통하여 통신이 가능한지, 아니면 동일한 기계 안에 존재하는 두 프로세스끼리만 통신이 가능한지
-일반파이프는 생산자-소비자 형태로 두 프로세스 간의 통신을 허용한다.
생산자는 파이프의 한종단에 쓰고 , 소비자는 다른 종단에서 읽는다
유닉스와 윈도우시스템 모두에서 통신하는 두 프로세스는 부모-자식 관계를 가져야 한다.
-Named pipe
좀 더 강력한 통신 도구를 제공한다. 통신은 양방향으로 가능하며 부모-자식 관계도 필요로 하지 않는다. 지명 파이프가 구축되면
여러 프로세스들이 이를 사용하여 통신할 수 있다
3.7 요약(Summary)
프로세스는 실행중인 프로그램이다. 프로세스는 실행되면서 상태를 변경한다. 프로세스의 상태는 프로세스의 현재 활동에 의해 정의된다.
각 프로세스의 상태는 new ready running waiting terminated 상태 중 하나이다.
각 프로세스는 운영체제 내에서 자신의 프로세스 제어 블록(PCB)에 의해 표현된다.
한 프로세스는 실행되지 않을 때는 적절한 대기 큐에 놓이게 된다. 운영체제에는 크게 두 가지 부류의 큐가 존재하는데,
하나는 입출력 요청 큐이며, 다른 하나는 준비 완료 큐이다. 준비 완료 큐는 실행할 준비가 완료되어 CPU를 기다리고 있는 모든
프로세스를 가지고 있다. 각 프로세스는 PCB로 나타낸다.
OS는 다양한 스케줄링 큐로부터 프로세스들을 선택해야 한다. 장기 스케줄링이란 CPU를 차지하기 위해 경쟁하도록 허용하기
위해 프로세스를 선택하는 일이다. 통상적으로 장기 스케줄링은 자원 할당 고려 사항, 특히 주 메모리 관리에 의해 많은
영향을 받는다. 단기 스케줄링은 준비 완료 큐로부터 하나의 프로세스를 선택하는 일이다.
부모 자식 프로세스가 병행하게 실행될 때 정보공유, 계산속도증가, 모듈성, 편의성 등의 장점이 생김
협력적인 프로세스는 서로 통신하기 위한 프로세스 간 통신 기법을 필요로 한다.
주로 두 가지 방법이 있다. 1)공유메모리 2)메시지 전달이다.
클라이언트 서버 시스템에서의 통신은 1)소켓 2)원격프로시저호출 3)파이프 를 사용한다
4단원
>스레드 (Threads)
4.1 개요
Pthreads API나 Windows, Java 스레드 라이브러리를 포함한 다중 스레드 시스템과 관련된 여러 가지 개념들을 소개한다. 스레드는 CPU 이용의 기본단위이다. 스레드는 스레드 ID, 프로그램 카운터, 레지스터 집합, 그리고 스택으로 구성된다. 프로세스를 만드는 것보다 스레드를 만들기 쉬움 새 프로세스가 해야할일이 원래 프로세스가 할일과 동일하다면 오버헤드를 감수하면서 프로세스를 만드는 것보다 그 프로세스안에 스레드을 만들어 나가는 것이 효율적 스레드는 원격 프로시저 호출(RPC)에서도 매우 중요한 역할을 담당한다. RPC는 IPC를 마치 일반적인 함수나 프로시저 호출을 하듯 할 수 있게 해준다. 다중 스레드 프로그래밍의 장점- 응답성(Responsiveness) 프로세스의 일부가 차단 된 경우 계속 실행 가능 사용자에 대한 응답성을 증가 시킨다. UI에 중요함
- 자원 공유(Resource sharing) 공유 메모리 또는 메시지 전달보다 스레드가 프로세스의 자원을 공유한다. 한 응용 프로그램이 같은 주소 공간 내에 여러 개의 다른 작업을 하는 스레드를 가질 수 있다.
- 경제성(Economy) 프로세스 생성을 위해 메모리와 자원을 할당하는 것은 비용이 많이 든다. 스레드는 자신히 속한 프로세스의 자원들을 공유하기 때문에, 스레드를 생성하고 문맥 교환하는 것이 보다 더 경제적
- 규모 적응성(Scalability) 다중 스레드의 이점은 다중 처리기 구조에서 더욱 증가할 수 있다. 다중 처리기 구조에서는 각각의 스레드가 다른 처리기에서 병렬로 수행될 수 있기 때문
4.2 다중코어 프로그래밍
극복해야할 5가지 테스크인식 균형 데이터 분리 데이터 종속성 시험 및 디버깅4.3 다중 스레드 모델(Multithreading Models)
- 4.3.1 다대일 모델(Many-to-One Model) 많은 사용자 수준 스레드를 하나의 커널 스레드로 사상한다. 스레드 관리는 사용자 공간의 스레드 라이브러리에 의해 행해진다. 하지만 한 스레드가 봉쇄형 시스템 콜을 할 경우, 전체 프로세스가 봉쇄된다. 또한 한번에 하나의 스레드만이 커널에 접근 할 수 있기 때문에, 다중 스레드가 다중코어 시스템에서 병렬로 실행 될 수 없다.
- 4.3.2 일대일 모델(One-to-One Model) 각 사용자 스레드를 각각 하나의 커널 스레드로 사상한다. 이 모델은 하나의 스레드가 봉쇄적 시스템 콜을 호출하더라도 다른 스레드가 실행될 수 있기 때문에 다대일 모델보다 더 많은 병렬성을 제공한다. 이 모델의 단 하나의 단점은 사용자 수준 스레드를 생성할 때 그에 따른 커널 스레드를 생성해야 한다는 점 커널 스레드를 생성하는 오버헤드가 응용 프로그램의 성능을 저하시킬 수 있으므로, 이 모델의 대부분의 구현은 시스템에 의해 지원되는 스레드의 수를 제한한다. Windows 계열의 OS들과 Linux가 일대일 모델을 구현하고 있다.
- 4.3.3 다대다 모델(Many-to-Many Model) 다대다 모델은 여러 개의 사용자 수준 스레드를 그보다 작은 수 혹은, 같은 수의 스레드로 멀티플렉스 한다. OS가 충분한 수의 커널 스레드를 생성할 수 있게 합니다. 개발자는 필요한 만큼 많은 사용자 수준 스레드를 생성할 수 있다. 그리고 상응하는 커널 스레드가 다중 처리기에서 병렬로 수행될 수 있다. 또한 스레드가 봉쇄형 시스템 콜을 발생시켰을 때, 커널이 다른 스레드의 수행을 스케줄 할 수 있다.
4.4 스레드 라이브러리(Threads Library)
스레드 라이브러리는 프로그래머에게 스레드를 생성하고 관리하기 위한 API를 제공한다. 두 가지 방법이 있는데 첫번째방법은 커널의 지원 없이 완전히 사용자 공간에서만 라이브러리를 제공하는 것이다. 라이브러리를 위한 모든 코드와 자료 구조는 사용자 공간에 존재한다. 라이브러리 함수를 호출하는 것은 시스템 호출이 아니라 사용자 공간의 지역 함수를 호출하게 된다는 것을 의미한다. 두번째방법은 운영체제에 의해 지원되는 커널 수준 라이브러리를 구현하는 것이다. 이 경우 라이브러리를 위한 코드와 자료 구조는 커널 공간에 존재한다. 라이브러리 API를 호출하는 것은 커널 시스템 호출을 부르는 결과를 낳는다. POSIX나 Windows 스레드의 경우, 전역 변수로 선언된 데이터, 즉 함수 외부에 선언된 데이터는 같은 프로세스에 속한 모든 스레드가 공유한다. Java는 전역 데이터의 개념이 없기 때문에 공유 데이터에 대한 접근이 스레드 사이에 명시적으로 조정되어야 한다. 함수에 국한된 지역 변수로 선언된 데이터는 통상 스택에 저장된다. 각 스레드는 자신의 스택을 가지고 있으므로 자신만의 지역 데이터의 복사본을 가진다. 비동기 스레딩 - 부모가 자식 스레드를 생성한 후 부모는 자신의 실행을 재개하여 부모와 자식 스레드가 병행하게 실행된다. 각 스레드는 모두 독립적으로 실행하고 부모 스레드는 자식의 종료를 알 필요가 없다. 스레드가 독립적이기 때문에 스레드 사이의 데이터 공유는 거의 없다. 동기 스레딩 - 부모 스레드가 하나 이상의 자식 스레드를 생성하고 자식 스레드 모두가 종료할 때까지 기다렸다가 자신의 실행을 재개하는 방식을 말한다. 이 방식은 소위 포크-조인(fork-join)전략이라고 불린다. 여기서 부모가 생성한 스레드는 병행하게 실행되지만 부모는 자식들의 작업이 끝날 때까지 실행을 계속할 수 없다. 부모 스레드는 오직 모든 자식 스레드가 조인한 후에야 실행을 재개할 수 있다. 통상 동기 스레딩은 스레드 사이의 상당한 양의 데이터 공유를 수반한다.- Pthreads pthread.h 헤더 파일을 포함하여야 함 사용자 또는 커널 수준 라이브러리로서 제공될 수 있다. 쓰레드 생성과 동기화를 위한 POSIX표준 Pthreads는 POSIX가 스레드 생성과 동기화를 위해 제정한 표준 API이다. Pthreads_t tid 우리가 생성할 스레드를 위한 식별자 Pthreads_attr_t attr 스레드를 위한 속성, 스택의 크기와 스케쥴링 정보를 포함한 속성의 집합을 갖는다. Pthreads_attr_init(&attr) 함수에서 속성 지정 Pthreads_create() 스레드 생성, 스레드 식별자와 스레드의 속성 뿐 아니라 새로운 스레드가 실행을 시작할 함수의 이름도 전달한다(여기선 runner()함수) Pthreads프로그램에서 별도의 스레드는 지정된 함수에서 실행을 시작한다. 그림 4.9에서는 이 함수가 runner()함수이다. 이 프로그램이 실행을 시작하면 하나의 제어 스레드가 main()함수에서 시작한다. 약간의 초기화 후에 main()은 runner() 함수에서 실행을 시작하는 두 번째 스레드를 생성한다. 두 스레드는 전역 변수 sum 을 공유한다. 명령어 라인 상에 제공된 정수 매개변수 argv[1]을 전달 여기서 프로그램은 main()함수의 최초 스레드와 runner() 함수에서 합을 계산하는 합 스레드의 두 개의 스레드를 가진다. 포크-조인 전략을 사용한다. 합 스레드를 생성한 후에 Pthreads_join()함수를 호출하여 runner()스레드가 종료하기를 부모 스레드는 기다린다. runner()스레드는 Pthreads_exit() 함수를 호출하여 종료하게 된다. 자식 스레드가 여러 개일때
#define NUM_THREADS 10
pthread_t workers[NUM_THREADS];
for(int i=0; i<NUM_THREADS; i++)
pthread_join(workers[i],NULL); 여러개가 끝날때까지 기다린다
- Window 스레드 라이브러리 Windows 시스템에서 사용 가능한 커널 수준 라이브러리 windows.h 헤더 파일을 포함하여야 함 별도의 스레드에서 수행될 Summation() 함수도 정의한다. 이 함수는 void 형을 가리키는 포인터 변수를 인자로 전달받는다. Windows API에서 스레드는 CreateThread() 함수에 의해 생성되고 Pthreads와 마찬가지로 이 함수에서 스레드를 위한 속성의 집합이 전달된다. WaitForSingleObjects()함수는 pthread_jon()문을 이용하여 부모 스레드가 합 스레드가 종료될 때까지 기다리게 한것과 동일한 작업을 한다. 이 함수는 합 스레드가 종료될 때까지 생성 스레드가 봉쇄되도록 한다. 여러 스레드의 종료를 기다려야 한다면 WaitForMultipleObjects() 함수가 사용된다. 4개의 매개변수를 받는데, 1. 기다려야 하는 객체의 개수 2. 객체 배열을 가리키는 포인터 3. 모든 객체가 신호를 보내왔는지를 나타내는 플래그 4. 대기해야 하는 타임아웃 시간(또는 INFINITE) ex) WaitForMultipleObjects(N, Thandles, TURE, INFINITE);
- Java 스레드 API Java 프로그램에서 직접 스레드 생성과 관리를 가능하게 한다. 그러나 대부분의 JVM 구현은 호스트 OS에서 실행되기 때문에 Java스레드 API는 통상 호스트 시스템에서 사용 가능한 스레드 라이브러리를 이용하여 구현된다. 윈도우에서 Java 스레드는 윈도우즈 API 사용 유닉스나 리눅스에서 Java 스레드는 Pthreads 사용 Java 프로그램에서 스레드 생성 기법에는 두 가지가 있다. 1. Thread 클래스로부터 파생된 새로운 클래스를 생성하고, Thread 클래시의 run()메서드를 무효화(override)하는 것 2. 흔히 사용되는 방법으로서 Runnable인터페이스를 구현하는 클래스를 정희하는 것이다.
ex)
public interface Runnalbe
{
public abstract void run();
}
클래스가 Runnable을 구현할 때, run()메서드를 구현해야 한다. run()메서드를 구현하는 코드는 별도의 스레드로서 실행된다. Java의 join()메서드가 pthread_join()과 유사한 기능을 제공한다.
4.5 암묵적 스레딩(Implicit Threading)
다중코어 처리의 지속적 성장에 따라 수백 또는 심지어 수천 개의 스레드를 가진 응용이 등장하게 되었다. [테스크 인식, 균형, 데이터 분리, 데이터 종속성, 시험 및 디버깅] 이러한 것들을 극복하고 다중 스레드 응용의 설계를 도와주는 한 가지 방법은 스레딩의 생성과 관리 책임을 응용 개발자로부터 컴파일러와 실행시간 라이브러리에게 넘겨주는 것이다. 암묵적 스레딩이라고 불린다.- 스레드 풀(Thread Pools) 다중 스레드 서버는 여러 문제가 있다. 첫번째 문제는 서비스할때마다 스레드를 생성하는데 소요되는 시간 두번째 문제는 모든 요청마다 새 스레드를 만들어서 서비스해 준다면 시스템에서 동시에 실행할 수 있는 최대 스레드 수가 몇 개까지 가능할 수 있는 것인지 한계를 정해야 한다. 스레드를 무한정 만들면 언젠가는 CPU 시간, 메모리 공간 같은 시스템 자원이 고갈된다. 이러한 문제를 해결해 줄 수있는 방법 중 하나가 스레드 풀이다. 기본 아이디어 : 프로세스를 시작할 때 아예 일정한 수의 스레드를 미리 풀로 만들어 두는 것. 평소에는 하는 일 없이 일감을 기다리게 됨 그러다가 한개의 요청이 들어오면 이 풀에서 한 스레드에게 그것을 할당한다. 요청을 다 서비스해 주었으면 그 스레드는 다시 풀로 돌아가 다음 작업을 기다린다. 요청을 다 서비스 해 주었으면 그 스레드는 다시 풀로 돌아가 다음 작업을 기다린다. 이렇게 하다가 풀에 남아 있는 스레드가 바닥나면 서버는 가용(free) 스레드가 하나 생길 때까지 기다려야 한다. 장점 : 1. 새 스레드를 만들어 주기보다 기존 스레드로 서비스해 주는 것이 더 빠르다 2. 스레드 풀은 임의 시작에 존재할 스레드 개수에 제한을 둔다. 이러한 제한은 많은 수의 스레드를 병렬 처리할 수 없는 시스템에 도움이 된다. 3. 태스크를 생성하는 방법을 태스크로부터 분리하면 태스크를 실행을 다르게 할 수 있다. ex)태스크를 일정 시간 후에 실행되도록 스케쥴하거나 혹은 주기적으로 실행시킬 수 있다.
DWORD WINAPI PoolFunction(AVOID Param){
/* this function runs as a separate thread. */
}
- OpenMP OpenMP는 C,C++ 또는 포트란으로 작성된 API와 컴파일러 디렉티브의 집합이다. 공유 메모리 환경에서 병렬 프로그래밍 지원 OpenMP는 병렬로 실행될 수 있는 코드블록을 찾아 병렬 영역(parallel regions)이라고 부른다.
#pragma opm parallel
위와 같은 컴파일러 디렉티브를 만나게 되면 시스템의 코어 개수만큼 스레드를 생성한다. 개발자는 또한 데이터를 스레드들이 공유할 것인지 혹은 특정 스레드만 사용할 것인지도 정할 수 있다. - Grand Central Dispatch GCD는 Mac OS X 및 iOS 운영 체제 용 Apple 기술 C, C ++ 언어, API 및 런타임 라이브러리에 대한 확장하여 조합한 기술 C와 C++언어를 확장한 블록(block)이라는 것을 식별한다. GCD는 블록을 디스패치 큐에 넣어서 실행될 수 있도록 스케줄, 큐에서 블록을 제거할 때 관리하고 있는 스레드 풀에서 가용 스레드를 선택하여 할당 직렬(serial), 병렬(concurrent) 두 가지 유형의 디스패치 대기열 : 직렬 블록은 FIFO 순서로 제거되고, 블록은 큐에서 제거되면 다른 블록이 제거되기 전에 실행을 반드시 완료해야 한다. 각 프로세스는 각자 직렬 큐를 가진다(메인큐) 병행 큐에 넣어진 큐는 마찬가지로 FIFO 순서로 제거되었지만 한 번에 여러 개가 제거 될 수 있음 우선 순위에따른 세가지 큐가 있다.
4.6 스레드와 관련된 문제들(Threading Issues)
- Fork() 및 Exec() 시스템 호출 한 프로그램의 스레드가 fork()를 호출하면 새로운 프로세스는 모든 스레드를 복제해야하는지 한개의 스레드만 복제해야하는지 결정해야한다. 몇몇 UNIX 기종은 이 두가지 fork()를 다 제공한다. fork()를 부르자마자 exec를 부른다면 모든 스레드를 복제해서 만들어주는 것은 불필요하다 왜냐하면 exec에서 지정한 프로그램이 곧 모든 것을 다시 대체할 것이기 때문 -> fork()시스템 콜을 호출한 스레드만 복사해주는 것이 적절 그러나 새 프로세스가 fork()후 exec를 하지 않는다면 새 프로세스는 모든 스레드들을 복제해야 한다.
- 신호 처리(Signal Handling) 신호는 UNIX 시스템에서 특정 이벤트가 발생했음을 프로세스에 알리기 위해 사용 신호 처리기는 신호를 처리하는 데 사용 신호는 특정 이벤트에 의해 생성 생성된 신호가 프로세스로 전달 모든 신호는 둘 중 하나의 처리기에 의해 처리된다. 1) 디폴트 신호 처리기 - 모든 신호마다 커널이 실행됨. 사용자 정의 처리기에 의해 대체될 수 있다. 2) 사용자 정의 신호 처리기 단일스레드 프로그램에서의 신호 처리는 간단하다. 신호는 항상 프로세스에게 전달된다. 멀티스레드는 복잡하다. 다음과 같은 선택이 존재 1. 신호가 적용될 스레드에게 전달 2. 모든 스레드에게 전달 3. 몇몇 스레드에게만 선택적 전달 4. 특정 스레드가 모든 신호를 전달받도록 지정 동기식 신호는 그 신호를 야기한 스레드에게 전달되어야 하고 다른 스레드에게 전달되면 안된다. 신호를 전달하는데 사용되는 표준 UNIX 함수는
kill(pid_t pid, int signal);
대부분의 다중 스레드 UNIX는 스레드에게 받아들일 신호와 봉쇄할 신호를 지정할 수 있는 선택권을 준다. 따라서 어떤 경우에는 비동기식 신호를 봉쇄하지 않고 있는 스레드들에게만 신호를 전달해야 할 수 있다. 하지만 신호는 오직 한 번만 처리되어야 하기 때문에 그 신호를 봉쇄하지 않고 있는 첫 번째 스레드에게만 신호가 전달된다. POSIX Pthreads는 tid로 지정된 스레드에게만 전달이 되도록 허용하는 다음과 같은 함수를 제공한다.pthread_kill(pthread_t tid, int signal)
Windows는 신호를 명시적으로 지원하지는 않지만 비동기식 프로시저 호출(Asynchronous Procedure Calls, APC)이라는 것을 사용해서 이를 대리 실행 할 수 있다. APC는 사용자 스레드들이 특정 사건의 발생을 전달받았을 때 호출될 함수를 지정할 수 있게 해준다. UNIX의 비동기식 신호와 유사 그러나 UNIX에서는 다중 스레드 환경에서 신호를 어떻게 처리해야 될지를 고민해야 하지만 APC는 프로세스에게 전달되는 것이 아니라 특정 스레드에게 전달되기 때문에 좀 더 간단하다. - 취소(Cancellation) 스레드 취소는 스레드가 끝나기 전에 그것을 강제 종료시키는 작업을 일컫는다. 취소되어야 할 스레드를 목적 스레드(traget thread)라고 부른다. 목적 스레드의 취소는 두 가지 방식이다 1. 비동기식 취소(Asynchronous cancellation) : 한 스레드가 즉시 목적 스레드를 강제 종료시킨다(언제든지, 준비없이 일어날 수 있음) 2. 지연 취소(Deferred cancellation) : 목적 스레드가 주기적으로 자신이 강제 종료되어야 할지를 점검한다. 이 경우 목적 스레드가 질서정연하게 강제 종료될 수 있는 기회가 만들어진다. 스레드 취소를 어렵게 만드는 것은 취소 스레드들에게 할당된 자원 문제이다. 비동기식 취소의 경우 모든 시스템 자원을 다 회수하지 못하는 경우가 있다. 반대로 지연 취소의 경우에는 스레드 자신이 취소되어도 안전하다고 판단되는 시점에서 취소 여부를 검사할 수 있다. Pthreads에서는 pthread_cancel() 함수를 사용해서 스레드를 취소할 수 있다. 목적 스레드의 식별자가 이 함수의 매개변수로 전달된다. Pthreads는 3가지 취소 모드를 지원한다.
모드 상태 유형
Off 사용불가능 -
지연 사용가능 지연
비동기식 사용가능 비동기식
default값은 지연 이 유형에서 취소는 스레드가 취소점(cancellation point)에 도달했을 때에만 취소 작업이 일어나게 된다. 취소점을 만드는 한 가지 방법은 pthread_testcancel() 함수를 호출하는 것이다. 취소 요청이 대기 중이라는 것이 발견되면, 정리 처리기(cleanup handler)라고 알려진 함수가 호출된다. 이 함수는 스레드가 획득한 모든 자원을 스레드가 종료되기 전에 반환하게 만든다. Pthreads는 비동기식 취소를 권장하지 않는다.(신호를 통해서 처리된다) 레디큐가 더블링크드리스트로 이루어져 있음 - 스레드 국지 저장소(Thread-Local Storage) 스레드 국지 저장소 이하 TLS 각 스레드가 자체 데이터 복사본을 가질 수 있음 지역 변수와는 다르다. 지역 변수는 하나의 함수가 호출되는 동안에만 보이는 반면에 TLS는 전체 함수 호출에 걸쳐 보인다. 어떤면에서는 정적 데이터와 유사하다. 차이점은 TLS 데이터는 각 스레드의 고유한 정보를 저장한다는 것이다. (ex 쓰레드 번호, 사용자 질의) 스레드가 프로세스와 공유 다른 스레드와 레지스터값 공유x - PC,함수호출(stack,커널및유저스택) code,global data는 공유
- 스케줄러 액티베이션(Scheduler Activations) M:M 및 Two-level models 모델 모두 응용 프로그램에 할당 된 적절한 수의 커널 스레드를 유지하기위한 통신이 필요하다. 다대다 또는 두 수준 모델(Two-level models)을 구현하는 많은 시스템들은 사용자와 커널 스레드 사이에 중간 자료 구조를 둔다. 이 자료 구조는 통상 경량 프로세스 or LWP라고 불린다. 사용자 스레드 라이브러리에게 LWP 방식은 응용이 사용자 스레드를 수행하기 위하여 스케줄 할 가상 처리기 처럼 보인다. 각 LWP는 하나의 커널 스레드에 부속되어 있으며 물리 처리기에서 스케줄 하는 대상은 바로 이 커널 스레드이다. 입출력이 완료되기를 기다리는 동안 같이 커널 스레드가 봉쇄되면 LWP도 같이 봉쇄된다. 이 연관을 따라 LWP에 부속된 사용자 수준 스레드도 역시 봉쇄된다. 사용자 스레드 라이브러리와 커널 스레드 간의 통신 방법 중의 하나는 스케줄러 액티베이션이라고 알려진 방법이다. 커널은 응용에게 특정 사건에 대해 알려줘야 한다. 이 프로시저를 upcall이라고 부른다. 스케줄러 활성화는 업 콜 (upcalls) - 커널에서 스레드 라이브러리의 upcall 처리기까지의 통신 메커니즘을 제공 이 통신을 사용하면 응용 프로그램이 올바른 수의 커널 스레드를 유지 관리 가능
4.7 운영체제 사례(OS Examples)
- Windows 스레드 커널 수준의 일대일 매핑을 구현 스레드는 스레드 ID, 레지스터 집합, 사용자 스택, 커널 스택, 런타임 라이브러리와 동적 링크라이브러리(DLL)에서 사용하는 개별 데이터 저장영역을 가진다 레지스터 집합, 스택, 개별 데이터 저장영역들은 그 스레드의 문맥으로 불린다 ETHREAD (executive thread block) - 커널 공간에서 스레드가 속한 프로세스 및 KTHREAD에 대한 포인터를 포함 KTHREAD (kernel thread block) - 커널 공간에서의 스케줄링 및 동기화 정보, 커널 모드 스택, TEB에 대한 포인터를 포함 TEB (thread environment block) - 사용자 공간의 스레드 ID, 사용자 모드 스택, 스레드 로컬 저장소 ETHREAD,KTHREAD 는 모두 커널 안에 존재, 커널만이 접근 가능 TEB는 사용자 모드에서 실행될 때 접근되는 사용자 공간 자료구조
- Linux 스레드 프로세스를 복제하는 기능을 가진 fork() 시스템 호출을 제공 Linux는 clone() 시스템 호출을 이용하여 스레드를 생성할 수 있는 기능도 제공 clone()는 둘다 생성가능 프로세스는 fork 스레드는 Pthreads_create