퇴근 후 운영체제 시리즈 - 프로세스 관리

퇴근 후 운영체제 시리즈 - 프로세스 관리

운영체제 스터디 기록

해당 썸네일은 Wonkook Lee 님이 만드신 Thumbnail-Maker 를 이용하였습니다

퇴근하고 공부하기가 참 쉽지 않다… 특히 지점 근무 후 집에 오면 굉장히 졸린데.. 그럼에도 글을 조금 써보고자 한다. 특히 이번 장은 재미있는데 스레드의 관한 글 를 보면서 많이 배웠다. 저 글에서는 회원들끼리 조금 싸우고 있지만,, 정말 많은 지식들을 가지고 있어서 그만큼 더더 배울 수 있었다 ㅋㅋㅋㅋ(감사합니다 ㅎㅎ)

여튼 바로 시작해보도록 하자!

프로그램의 실행 (메모리 Load)

우리가 생각했을 때의 프로그램의 실행

image

실제 컴퓨터가 진행하는 프로그램의 실행

image

  • 각 프로세스마다 독자적인 메모리 주소가 생김 → Virtual Space
  • 말 그대로 서로 다른 메모리 주소(Virtual vs Physical)이기에 이를 물리적 메모리(DRAM) 에서 처리하기 위해서는 주소 변환이 필요함

Address Space

스택, 코드 데이터 로 존재 → Virtual 메모리 / 커널의 구성

코드 : 실제 CPU가 실행해야될 기계어 코드들

데이터 : 프로그램이 실행하다가 필요한 메모리 데이터 (전역변수)

스택 : 모든 프로그램이 함수를 호출할 때 필요한 정보들 (매개변수, 지역변수)

커널 주소 공간의 내용

image

✔︎ 데이터에는 각 프로세스들의 PCB를 가지고 있음 → 운영체제가 다 관리해야 하기 때문

✔︎ 스택은 조금 특이한 구조를 가지고 있음

  • 각 프로세스마다 커널의 스택을 따로 두고 있음
  • 호출 및 리턴의 용이함을 위해
  • 커널 코드를 위해 존재하는데 어떤 프로세스가 System 콜을 했는지 알아야 하기에

프로세스의 개념

실행되고 있는 프로그램 → 작업 단위

프로세스의 문맥(Context)

image

✔︎ CPU 수행 상태를 나타내는 하드웨어 문맥 (현재 활동의 상태)

  • Program Counter
  • 각종 레지스터

✔︎ 프로세스의 주소 공간

  • code : 실행 코드 → 크기 고정
  • data : 전역 변수 → 크기 고정
  • stack : 함수 호출할 때 임시 데이터 저장장소 (매개변수, 지역변수, 복귀 주소 등)
  • heap : 실행 중 동적으로 할당되는 메모리

✔︎ 프로세스 관련 커널 자료 구조

  • PCB
  • Kernel Stack

프로세스의 상태

프로세스는 상태가 변경되며 수행됨.

✔︎ Running

CPU를 잡고 명령어를 수행 중

✔︎ Ready

CPU에게 할당되기를 기다리는 상태 (메모리 등 다른 조건을 모두 만족하고)

따라서 필요한 부분들은 이미 메모리에 올라와있어야 하는 것

✔︎ Blocked(wait, sleep)

CPU를 주어도 당장 실행할 수 없는 상태

프로세스 자신이 요청한 event(ex:I/O) 가 즉시 만족되지 않아 이를 기다리는 상태

(예) 디스크에서 파일을 읽어와야 하는 경우

✔︎ Suspended (stopped)

외부적인 이유로 프로세스의 수행이 정지된 상태

프로세스는 통째로 디스크에 swap out 됨

(메모리에 너무 많은 프로세스가 올라와 있을 때)

**Blocked** : 자신이 요청한 event가 만족되면 ready
**Suspened** : 외부에서 resume  주어야 active

만약 Blocked가 풀리는 이벤트가 일어났다면 인터럽트를 통해 CPU에게 알림

✔︎ New

프로세스가 생성 중 (생성 전 프로세스가 아님)

✔︎ Terminated

수행이 끝나는 중인 상태 (마찬가지)

image

프로세스 생성

💡 부모 프로세스가 자식 프로세스를 생성 → fork()

✓ 프로세스의 트리(계층 구조) 형성

✓ 프로세스는 자원을 필요로 함 (부모와 자식은 서로 다른 프로세스임)

  • 운영체제로부터 받음
  • 부모와 공유

✓ 자원의 공유

  • 부모와 자식이 모든 자원을 공유하는 모델
  • 일부를 공유하는 모델
  • 전혀 공유하지 않는 모델

✓ 수행(execution)

  • 부모와 자식은 공존하며 수행되는 모델 (경쟁)
  • 자식이 종료될 때까지 부모가 기다리는(wait) 모델 → 부모는 blocked 상태

✓ 주소 공간

  • 자식은 부모의 공간을 복사함(코드, 데이터, 스택을 그대로→ 변수 & 실행 위치) → 과거
  • 현대의 운영체제는 COW(쓰기시복사) 라는 기술을 이용하여 별도의 메모리 공간을 가질 수 있도록 함
    • 메모리 복사 시간이 줄어들음
    • 메모리 사용량을 줄임
  • 자식은 그 공간에 새로운 프로그램을 올림

✓ 유닉스의 예

  • fork() 시스템 콜이 새로운 프로세스를 생성
    • 부모를 그대로 복사 (pid 는 제외)
    • 주소 공간 할당
  • fork() 다음에 이어지는 exec() 시스템 콜을 통해 새로운 프로그램을 메모리에 올림

image

fork의 단계

  • fork를 하는 순간 하나의 프로세스가 하나 더 생기고 실행 위치까지 복사함 (그렇기에 무한 자식 생성(fork())이 안됨)
  • 그러기에 누가 부모고 자식인지 모름
  • pid 를 통해 이를 해결

exec() 시스템 콜

💡 새로운 프로그램으로 덮어씌우는 함수

image

  • exec 후에는 아예 다른 프로그램으로 덮어씌우는 원리이기에 printf 실행 x

프로세스 종료

✓ 프로세스가 마지막 명령을 수행 후 운영체제에게 이를 알림(exit)

  • 자식이 부모에게 output data를 보냄(via wait)
  • 프로세스의 각종 자원들이 운영체제에게 반납됨

✓ 부모 프로세스가 자식의 수행을 종료시킴 (abort)

  • 자식이 할당 자원의 한계치를 넘어섬
  • 자식이 더 이상 할 태스크가 없을 때
  • 부모가 종료(exit)하는 경우
    • 운영체제는 부모 프로세스가 종료하는 경우 자식이 더 이상 수행되도록 두지 않음
    • 단계적인 종료 (tree 삭제)

wait() 시스템콜

image

프로세스 A가 wait() 시스템 콜을 호출하면

✓ 커널은 자식이 종료될 때까지 프로세스 A를 sleep 시킴

자식 프로세스가 종료되면 커널은 프로세스 A를 깨운다. → ready 상태

프로세스 간 협력 (IPC)

독립적 프로세스

✓ 프로세스는 각자의 주소 공간을 가지고 수행되므로 원칙적으로 하나의 프로세스는 다른 프로세스의 수행에 영향을 미치지 못함

→ 다른 프로세스의 주소 공간을 보지 못함

협력 프로세스

✓ 프로세스 협력 메커니즘을 통해 하나의 프로세스가 다른 프로세스의 수행에 영향을 미칠 수 잇음

프로세스 간 협력 메커니즘(IPC) 🌞

메시지 전달

커널을 통해 메시지 전달하는 방법

소량의 데이터를 교환할 때 유용 → 동기화가 필요없고 피해야 할 충돌이 없기 때문

구현이 더 쉬움

주소 공간 공유

shared memroy : 서로 다른 프로세스 간에도 일부 주소 공간을 공유

메모리 전송 속도로 진행할 수 있어 최대 속도와 편리한 통신 허용

메모리 보호와 동기화 부분에서 어려운 부분 존재

thread : 스레드는 사실상 하나의 프로세스이므로 프로세스 간 협력으로 보기는 어렵지만 동일한 프로세스를 구성하는 스레드들 간에는 주소 공간을 공유하므로 협력이 가능

메시지 전달

💡 프로세스 사이에 공유 변수를 일체 사용하지 않고 통신하는 시스템

image

각 프로세스는 커널에 메시지를 전달하겠다는 시스템 콜을 호출하게 된다 → 메시지를 주고 받을 때 매번 커널의 호출

공유 메모리는 커널 간의 통신이 없어도 된다. → 이때는 커널이 처음 메모리를 공유할 때만 상정을 해주면 됨

  • 메모리를 공유하는 만큼 믿을 수 있는 당자끼리 해야됨

Direct Communication(message)

image

✓ 통신하려는 프로세스의 이름을 명시적으로 표시

Indirect Communication (message)

image

✓ mailbox (또는 port)를 통해 메시지를 간접 전달

PCB (Process Control Block) ⭐️

🧶 운영체제가 각 프로세스를 관리하기 위해 프로세스 당 유지하는 정보! → 커널 주소공간에 프로세스마다 가지고 잇음

image

  1. OS가 관리상 사용하는 정보
    1. Process State, PID
    2. scheduling info, priority(스케쥴링을 위한)
  2. CPU 수행 관련 하드웨어 값
    1. Program Counter

      프로세스가 다음에 실행할 명령어의 주소

    2. registers

      인터럽트 발생 시 추후 다시 올바르게 실행되기 위해 저장되는 정보들

  3. 메모리 관련
    1. code, data, stack의 위치 정보
  4. 파일 관련
    1. Open file descriptors

왜 메모리에 레지스터, PC가 있음에도 각 프로세스의 PCB 안에 값들을 가지고 있을까?

Context Switch

🎨 CPU를 한 프로세스에서 다른 프로세스로 넘겨주는 과정

image

CPU가 다른 프로세스에게 넘어갈 때 일어나는 흐름

✔︎ 커널이 CPU를 내어주는 프로세스의 상태를 그 프로세스의 PCB에 저장

✔︎ CPU를 새롭게 얻는 프로세스의 상태를 그 PCB에서 읽어옴

→ 이 과정에서 CPU는 아무 일도 못하기에 순수한 오버헤드임

문맥 교환 vs. 문맥 교환이 아닌 것!

🎙️ System Call 이나 인터럽트 발생 시 반드시 context swtich가 일어나는 것은 아님

image

1) 인터럽트가 불리고 커널모드로 바뀌긴 하지만 문맥 교환은 아님!

2) A → B : 인터럽트나 시스템콜이 걸리고 프로세스 B로 바꿔야할 때 비로소 문맥 교환

ex) 타이머 인터럽트

1의 경우에도 CPU 수행 정보 등 context의 일부를 저장하긴 해야하지만 2의 경우는 그 부담이 훨씬 큼(cache memory flush를 해야됨)

프로세스를 스케줄링하기 위한 큐

image

✔︎ Job Queue : 현재 시스템 내에 잇는 모든 프로세스의 집합

✔︎ Ready Queue : 현재 메모리 내에 있으면서 CPU에 배치되기를 기다리는 큐 (포인터는 각각의 프로세스의 PCB를 가르키고 있다)

✔︎ Device Queue : I/O 디바이스의 처리르 기다리는 프로세스의 집합

프로세스들은 각 큐를 오가며 처리된다.


스레드

💡 A thread is a basic unit of CPU utilization → 프로세스 중에서 CPU 수행단위

image

Thread의 구성

✓ program counter → CPU 수행단위이기에

✓ register set → CPU 수행단위이기에

✓ stack space → 함수 호출과 관련된 스택 부분은 개별적으로 가져야됨

Thread가 동료 thread와 공유하는 부분(=task)

✓ code section

✓ data section

✓ OS resources

⇒ 전통적인 개념의 heavyweight process는 하나의 thread를 가지고 있는 task로 볼 수 있다.

스레드의 장점

  • 다중 스레드로 구성된 태스크 구조에서는 하나의 서버 스레드가 blocked 상태인 동안에도 동일한 태스크 내의 다른 스레드가 실행되어 빠른 처리를 할 수 있다. (응답성)
    • 브라우저에서 네트워크를 처리하고 잇을 때, blocked 상태일 때도 화면에 보여줄 수 있는 것들은 렌더링으로 처리되는 것
  • 동일한 일을 수행하는 다중 스레드가 협력하여 높은 처리율과 성능 향상을 얻을 수 있음
  • 병렬성을 높일 수 잇음
  • 경제적임 → 프로세스 하나 만드는 것보다 스레드 하나 만드는게 훨씬 더 오버헤드가 작음 / 훨씬 빠름
  • 멀티 프로세싱은 IPC를 통해 통신을 했어야했다면 멀티 스레드는 이미 한 프로세스 내에서 공유하고 있기에 신경 안써도 됨 ⇒ 코드 / 데이터 공유의 이점은 같은 주소 공간 내에서 여러 일을 할 수 있다는 점

스레드의 구현

커널 스레드(OS가 존재를 알고 있음) vs 유저 스레드(프로세스 통째로 인식)

⇒ 이 둘의 관계가 정립되어야 함

다대일 모델

다수의 사용자 수준 스레드를 하나의 커널 스레드로 사상하는 것

image

장점

  • 스레드 관리는 사용자 스레드 라이브러리에 의해 행해짐 ⇒ 효율적

단점

  • 한 스레드가 봉쇄형 시스템 콜을 할 경우, 전체 프로세스가 봉쇄됨 (모든 스레드들이 봉쇄되기 때문)
  • 병렬 처리 이득을 가져갈 수 없음

일대일 모델

각 사용자 스레드를 각각 하나의 커널 스레드로 사상하는 것

image

장점

  • 하나의 스레드가 봉쇄형 시스템 콜을 호출한다고 해도 병렬성 유지 가능
  • 다중 스레드가 병렬로 수행되는 것을 허용

단점

  • 사용자 스레드만큼의 커널 스레드를 만들어야 되고 이는 시스템 성능에 부담을 줄 수 있음

다대다 모델

여러 개의 사용자 스레드를 그보다 작거나 같은 수의 커널 스레드로 사상

image

장점

  • 일대일 모델 / 다대일 모델의 단점을 어느 정도 보완함
  • 이를 발전한게 두 수준 모델(two-level) 이긴 한데 이게 구현이 까다로움

현대의 운영체제는 처리 코어 수의 증가에 따라 커널 스레드 수를 제한하는 것이 의미 없어짐 ⇒ 일대일 모델 활용

스레드와 관련된 문제들

Fork() 및 exec() 시스템 콜

한 스레드가 fork()를 호출했다면 모든 스레드를 복제해야 하는가?

→ 두 개의 fork()를 구축해둠

스레드 취소

취소되어야 할 목적 스레드를 어떻게 취소할 것인가?

  1. 비동기식 취소 : 한 스레드가 즉시 목적 스레드를 강제 종료한다.
  2. 목적 스레드가 주기적으로 자신이 강제 종료되어야 할지를 검사한다. 이때 질서정연하게 강제 종료 될 수 있다.

⇒ 해당 문제가 어려운 이유는 할당된 자원을 어떻게 할 것인가 에 있다.

다중 스레딩이 다중 프로세스보다 빠르고 오버헤드가 적다는 것은 사실이지만, 모든 프로그램을 다중 스레딩으로 구축하지 않는 이유를 한번 GPT에게 물어보았다.

1. 공유 데이터 문제:

여러 스레드가 동일한 데이터에 접근할 경우, 데이터 손상이나 일관성 문제가 발생할 수 있습니다. 이러한 문제를 해결하기 위해 동기화 메커니즘을 사용해야 하며, 이는 오버헤드를 증가시킵니다.

2. 개발 어려움:

다중 스레드 프로그램은 개발 및 디버깅이 훨씬 더 어렵습니다. 스레드 간 동기화 및 상호 작용을 정확하게 관리해야 하기 때문입니다.

3. 스케줄링 문제:

운영 체제는 스레드를 스케줄링하여 CPU 시간을 할당합니다. 하지만 스레드 스케줄링은 복잡한 문제이며, 모든 스레드에게 적절한 CPU 시간을 제공하지 못할 수 있습니다.

4. 모든 프로그램이 다중 스레딩으로 이득을 얻는 것은 아닙니다:

일부 프로그램은 본질적으로 병렬 처리가 어렵습니다. 이러한 프로그램에서는 다중 스레딩을 사용해도 성능 향상을 기대할 수 없습니다.

5. 하드웨어 지원:

모든 하드웨어가 다중 스레딩을 지원하는 것은 아닙니다. 하드웨어가 다중 스레딩을 지원하지 않거나 지원 수준이 낮은 경우, 다중 스레딩을 사용해도 성능 향상이 미미할 수 있습니다.

따라서 프로그램을 구축할 때는 다중 스레딩의 장점과 단점을 신중하게 고려해야 합니다. 다음과 같은 경우 다중 스레딩을 사용하는 것이 유리합니다.

  • 프로그램이 본질적으로 병렬 처리가 가능한 경우
  • 프로그램에서 CPU 사용 시간이 중요한 경우
  • 하드웨어가 다중 스레딩을 지원하는 경우

연습문제

프로세스가 fork() 연산을 사용하여 새로운 프로세스를 생성할 때 어떤 상태가 부모 프로세스와 자식 프로세스 간에 공유되는가?

  1. 스택
  2. 공유 메모리 세그먼트
C. 공유 메모리 세그먼트

fork() 연산은 부모 프로세스의 메모리 공간을 복제하여 자식 프로세스를 만듬.
이러한 과정에서 스택과 힙은 각각 부모와 자식 프로세스에게 별도의 메모리 공간을 할당하게 됨.
그러나 공유 메모리 세그먼트는 부모와 자식 프로세스가 동일한 메모리 영역을 공유할 수 있다.

사용자 스레드와 커널 수준 스레드의 차이점

[사용자 수준 스레드]
* 말 그대로 커널은 해당 스레드의 존재를 모름
* 스레드 간의 context switch의 오버헤드가 적음(없지는 않음)
	why? 
	커널 스케줄러를 호출할 필요가 없기에 
	커널 스케줄러를 호출한다면 이를 PCB에 저장하고 
    커널 레지스터들을 복구시키는 작업들 때문에 굉장히 오래 걸림
* 한 스레드가 커널로 진입하는 순간 나머지 스레들들이 모두 봉쇄되며 
이에 따라 프로세스가 봉쇄됨 (프로세스 단위로 스케줄링을 진행하기에

[커널 수준 스레드]
* 스레드 간의 context switching 의 오버헤드가 큼
* 멀티 프로세서의 활용할 수 있는 큰 장점이 있음 -> 
커널 레벨에 있는 스레드는 스레드 단위로 스케줄링이 되기에 
특정 스레드의 blocking이 전파되지 않음