프로세스란, 쓰레드란?
* 프로세스 process : 컴퓨터에서 실행되고 있는 프로그램을 말함. CPU 스케줄링의 대상이 되는 task.
* 쓰레드 thread: 프로세스 내 작업의 흐름.
멀티 프로세스 & 멀티 쓰레드
말그대로 각각 여러 개의 프로세스와 쓰레드를 쓴다는 말. 둘 사이 특징이 다르므로, 이를 기억하자.
그런데 CPU (정확히는 코어)의 수는 어차피 정해져 있는데, 멀티 프로세스든 쓰레드든 그게 뭐가 중요하지? 라고 생각할 수 있는데, 여러 개의 프로세스 혹은 쓰레드는 하나의 CPU를 정말 빠른 속도로 사용하고, 비켜줬다가, 다시 사용하는 것을 무한 반복한다. context switching. 그렇기 때문에 여러 개의 프로그램을, 마치 동시에 진행하는 것처럼 사용자에게 보여줄 수 있는 것.
* 멀티 프로세스: 각각의 프로세스는 서로에 대해 독립적이고, 서로를 침해하지 않는다.
그렇기 때문에 좋은 점은, 적어도 각자의 태스크를 진행할 때 서로를 의식하지 않아도 된다는 것.
그러나 나쁜 점은, 만약 서로 간에 소통해야 할 일(IPC)이 있을 때 속도가 쓰레드보다 느리다는 점.
또 다른 나쁜 점은, 각자가 다른 데이터들을 가지고 있기 때문에 컨텍스트 스위칭 시 매우 무거움.
=> 예를 들어, 웹 브라우저의 경우 브라우저, 렌더러, 플러그인, CPU 프로세스 등의 프로세스가 활용됨.
* 멀티 쓰레드: 각각의 쓰레드는, 하나의 몸통 데이터를 공유한다. 그렇기에 컨텍스트 스위칭 시 가볍다.
정확히는, 코드-데이터-힙 이렇게 세 부분을 공유하고, 스택(지역 혹은 임시변수들)만 따로 가짐.
그러나 공유하는 부분이 있는만큼, 쓰레드끼리는 지속적으로 동기화를 해줘야 한다는 단점.
프로세스와 컴파일 과정
1 HDD/SSD에 있던 프로그램을 RAM에 올려서 인스턴스화함 = "프로세스"로 이름 바꿔부름
=> 프로그램은 프로세스가 될 것들을 모두 갖추고 있음. RAM에 올라가면 진행되면서 프로세스가 되는 것임.
2 프로세스가 되면 CPU가 이를 확인하고 CPU 스케줄러가 관리하는 대상에 포함됨
컴파일 과정
컴파일은 인간이 이해할 수 있는 언어로 작성된 C++, JAVA 등의 언어를 CPU가 이해할 수 있는 기계어로 변환하는 작업.
컴퓨터가 실행할 수 있는 상태의 파일이 실행파일이니, 인간의 소스 코드를 실행 파일로 만드는 과정이라고 생각하면 되겠다.
전처리: #include 등의 헤더 파일 병합. 주석도 제거.
컴파일: 오류 처리, 코드 최적화 작업. static 영역 메모리 할당 수행. => 어셈블리어로 변환.
어셈블리: 어셈블리어 => 기계어(오브젝트 코드)로 변환.
링킹: 오브젝트 파일들을 모아서 실행 파일로 만드는 과정이다.
*어셈블리어: 기계어 되기 전 일대일 대응 과정의 코드.
*동적/ 정적 라이브러리: 정적 라이브러리는 빌드 시 라이브러리의 코드를 실행 파일에 실제로 다 때려넣는 것을 뜻하고, 당연히 두번 이상 쓰이는 라이브버리의 경우 반복되고 메모리 효율성도 떨어지지만, 시스템 등 외부 의존도가 낮음. 반면 동적 라이브러리는 프로그램 실행 시 필요할 때만 DLL이라는 함수 정보를 통해 참조하여 라이브러리를 쓰는 방법. 메모리 효율성에서의 장점을 지니지만 외부 의존도가 높아진다는 단점이 있음.
프로세스의 상태
- 생성 new: 프로세스가 생성된 상태. fork() 또는 exec() 등의 시스템 콜을 통해 생성. <- 이때 PCB가 할당됨.
- 준비 ready: 프로세스가 필요한 모든 자원을 할당받고, 준비 큐에 쌓여 실행 대기 중인 상태.
- 실행 running: CPU 소유권과 메모리를 할당받고 인스트럭션을 수행 중인 상태를 의미.
- 차단 blocked / 대기 waiting: 프로세스가 실행을 계속하기 위해 어떤 이벤트나 자원을 기다리는 상태.
만약 I/O를 필요로 한다든가 하는 일이 있다면 대기 상태로 발령을 받고, 그게 아니라 단순히 context switching이라면 인터럽트가 발생한 것이므로 대기상태가 아닌 '준비 큐'에 돌입한다!!
프로세스의 메모리 구조
스택, 힙, 데이터, 코드 中 스택과 힙은 동적 영역, 데이터와 코드는 정적 영역이다. 각 프로세스는 고유한 저 네 개를 가진다.
스택: 지역변수, 매개변수, 실행되는 함수에 의해 늘거나 줄어드는 메모리 영역. 함수가 호출될 때마다 호출될 때의 환경 등 특정 정보가 스택에 계속해서 저장됨.
힙: 동적 할당 변수 - malloc(), free() 등으로 관리할 수 있음. 이를테면 vector는 힙 영역 사용.
데이터: 전역 변수, static, const 등의 값.
코드: 프로그램의 코드.
* 기본적으로 프로세스끼리는 서로의 메모리에 접근 불가하다. 대신 공유 메모리라는 개념이 있다.
PCB Process Control Block
PCB는 OS에서 프로세스에 대한 메타데이터를 저장한 데이터를 말함. 그러니까 Process가 생기면 그 process를 설명해주는 약간의 nametag 같은 존재라고 하면 될 것 같다. 다음과 같은 정보들을 포함한다.
프로세스의 현재 상태, 프로세스 ID, 프로그램 카운터 (다음 명령어의 주소에 대한 포인터), CPU 레지스터 등.
공유 자원과 임계 영역 critical section
* 공유 자원: 시스템 안에서 각 프로세스, 스레드가 함께 접근할 수 있는 자원들 ex 모니터, 프린터, 메모리, 파일, 데이터 등의 자원 및 변수. 두 개 이상의 프로세스가 동시에 읽거나 쓰는 상황을 경쟁 상태 race condition 라고 함.
* 임계 영역: 둘 이상의 프로세스, 스레드가 공유 자원에 접근할 때 순서 등의 이유로 결과가 달라지는 코드 영역.
임계 영역을 해결하기 위한 방법들
임계 영역을 해결하기 위한 방법은 3가지 - 뮤텍스, 세마포어, 모니터 가 있다. 세가지 방법 모두 상호 배제, 한정 대기, 융통성이라는 조건들을 만족한다.
* 상호 배제: 한 프로세스가 임계 영역에 들어갔을 때 다른 프로세스는 들어갈 수 없다.
* 한정 대기: 특정 프로세스가 영원히 임계 영역에 들어가지 못하는 일은 있으면 안 된다.
* 융통성: 한 프로세스가 다른 프로세스의 일을 방해해서는 안 된다.
1 뮤텍스: 프로세스나 스레드가 공유 자원을 lock을 통해 잠금 설정하고 사용한 후에는 unlock을 통해 잠금 해제하는 객체. 잠금 or 잠금 해제 의 2가지 상태만 가짐. - 화장실 똥칸이 하나 있는 상태라고 생각하면 된다.
2 세마포어: 일반화된 뮤텍스. 간단한 정수값들과, wait(P), signal 혹은 release (V)로 공유 자원에 대한 접근을 처리함. wait은 자신의 차례가 올때까지 기다리는 함수, signal은 다음 프로세스로 순서를 넘겨주는 함수. 특이한 점은, 뮤텍스처럼 완전히 보안이 되는 건 아니고 다른 쓰레드가 급습해 사용할 수 있다는 것이다. 물론 대부분의 경우 뮤텍스처럼 진행되지만, 예외적인 경우 굴러온 돌의 난이 가능하다는 것.
- 똥칸이 하나가 아니라 여러 개, 그러나 급한 경우 다른 사람이 몽둥이 들고 들이닥쳐서 쫓아낼 수 있다. 여기서 중요한 점은, 똥칸을 관리하는 사람이 따로 있는 게 아니다. 액티브한 똥칸의 개수를 세는 카운터만 있을 뿐, 서로 간의 신의를 바탕으로 하는 것이기 때문에, 서로가 서로의 상태를 파악하고 있어야한다는 점에 매우 귀찮은 구현이 필요하다.
=> 바이너리 세마포어는 보안이 없는 뮤텍스고, 카운팅 세마포어는 여러 개의 바이너리 세마포어다.
3 모니터: 둘 이상의 쓰레드나 프로세스가 공유 자원에 안전하게 접근할 수 있도록 공유 자원을 숨기고 해당 접근에 대해 인터페이스만 제공함. 한마디로 모니터라는 똥칸 관리자가 있기 때문에, 구현도 상대적으로 쉽고, 뒷단은 추상화가 되어 각각의 쓰레드 혹은 프로세스는 배정받은 자리에 가서 사용하기만 하면 되는 것이다.
교착상태 Deadlock
두 개 이상의 프로세스들이 서로가 가진 자원을 기다리며 중단된 상태를 말함. 조건은 다음과 같다.
1 상호 배제: 한 프로세스가 자원을 독점하고 있으며 다른 프로세스들은 접근이 불가능함.
2 점유 대기: 특정 프로세스가 점유한 자원을 다른 프로세스가 요청하는 상태.
3 비선점: 다른 프로세스의 자원을 강제적으로 가져올 수 없음.
4 환형 대기: 프로세스 A와 프로세스 B가 서로의 자원을 기다리며 씨름하는 상태.
한 마디로, 서로가 서로의 자원을 강제로 가져올 수 없고, 요청만 해둔 상태라서 기약없이 기다리는 상태다.
교착상태를 해결하기 위한 방법으로는, 위의 네 가지 중 각각을 타파하는 방법들이 있겠지만, 애초에 교착상태가 매우 드물게 일어나기 때문에 현실에서는 저 솔루션을 구현하기보다 그냥 교착상태가 생기면 작업 종료를 해버린다. 예를 들어 프로세스 실행시키다 '응답없음' 뜨는 게 바로 저 예시다.
CPU 스케줄링 알고리즘
CPU 스케줄러는 프로세스에서 해야 하는 일을 스레드 단위로 CPU에 할당함.
이때 목표는, CPU 이용률은 높게, 준비 큐에 있는 프로세스는 적게, 응답 시간을 짧게하는 것이다.
1 비선점형 방식 non-preemptive <- 관리자 없음
- 프로세스가 스스로 CPU 소유권을 포기함
- 강제로 프로세스를 중지하지 않음
- 따라서 컨텍스트 스위칭으로 인한 부하가 적음
ex) FCFS, SJF, 우선순위 등.
2 선점형 방식 preemtive <- 관리자 있음
- 현대 OS가 쓰는 방식
- 지금 사용하고 있는 프로세스를 알고리즘에 의해 중단시키고 강제로 다른 프로세스에 CPU 소유권을 할당하는 방식
ex) Round Robin, SRF, 다단계 큐 등.
Round Robin?
공유 시스템에서 각 프로세스에 CPU 시간을 공평하게 분배하기 위해 고안된 선점형 스케줄링 알고리즘.
그냥 별다를 게 없는데? 그냥 queue에 있는 순서대로 정해진 시간 동안 CPU 쓰게 해주고 시간 끝나면 내쫓는 것임.
'CS 전공수업 > 컴퓨터 운영체제' 카테고리의 다른 글
[OS] 운영체제 꼬리물기 질문 대비 (2) | 2024.07.24 |
---|---|
[OS] 메모리, 캐시, 운영체제의 메모리 관리 (0) | 2024.07.18 |
[OS] 운영체제 기본 (0) | 2024.07.14 |