💭 1.1 운영체제(Day 1)


1.1.1 운영체제(OS, Operating System)?⭐️

  • 하드웨어 위에 설치되어 하드웨어 계층과 다른 소프트웨어 계층을 연결하는 소프트웨어 계층
  • 역할 : 컴퓨터 시스템의 자원을 관리, 사용자가 컴퓨터를 사용할 수 있는 환경을 제공, 사용자와 컴퓨터 간 인터페이스를 제공해 사용자가 컴퓨터를 편리하게 사용할 수 있는 환경 제공
  • 대표 OS : Windows, macOS, Linux, Unix 등

1.1.2 운영체제의 목적⭐️

  • 처리 능력(throughput) 향상: OS는 자원 관리를 통해 일정 시간 내에 시스템이 처리하는 일의 양을 향상시킨다.
  • 반환 시간(turnaround time) 단축: OS는 사용자가 시스템에 요청한 작업을 완료할 때까지 소요되는 시간을 단축시킨다.
  • 사용 가능도(availability) 향상: 사용 가능도는 시스템 자원을 얼마나 빨리 제공할 수 있는가를 의미한다. OS는 사용자가 컴퓨터를 사용해야 할 때 자원을 즉시 사용할 수 있게 한다.
  • 신뢰도(reliability): 향상: 신뢰도는 시스템이 주어진 문제를 정확하게 푸는지를 의미한다. OS는 입력 값에 대한 정확한 결과 값을 줄 수 있도록 신뢰도를 향상해야 한다.

→ OS 성능을 평가하는 기준으로 처리 능력, 반환 시간, 사용 가능도, 신뢰도가 사용되기도 한다.


1.1.3 CPU와 메모리 구조⭐️⭐️

  • CPU(Central Processing Unit, 중앙 처리 장치)
    • ‘컴퓨터의 뇌’ 역할, 컴퓨터에서 프로그램을 실행하는 데 필요한 연산을 처리하고 수행
    • = 프로세서(processor)
  • 메모리
    • 데이터를 저장하기 위한 기억 장치: 메인 메모리를 의미, RAM
    • 주 기억장치(휘발성[전원 공급이 중단되면 저장된 정보 삭제됨] 메모리) + 보조 기억장치(비휘발성 메모리): SSD(solid State Drive), HDD(Hard Disk Drive) 등
    • 메모리의 계층 구조
      • register: CPU가 사용자 요청을 처리하는 데 필요한 데이터를 임시로 저장하는 기억장치. CPU 내부에 존재, 접근 속도 빠름
      • cache memory: CPU와 RAM 사이의 속도 차이를 해결하기 위한 기억장치. CPU 내부에 위치, 접근 속도가 레지스터 다음으로 빠름
      • RAM(Random Access Memory): 컴퓨터에서 프로그램을 실행할 때 필요한 정보를 저장. CPU에서 접근 속도가 하드 디스크보다 빠르고, 휘발성 기억장치. 보통 메모리라고 할때 이를 의미
      • hard disk: 사용자가 필요한 데이터와 프로그램을 저장. 비휘발성 기억장치
  • 프로그램을 실행하면 OS가 디스크에 있는 프로그램을 메모리로 load(프로그램을 메모리에 올려 공간을 할당하는 것)
  • 프로세스(process): 메모리에 로드한 프로그램, CPU가 처리함
  • CPU는 하나의 프로세스만 처리 가능 → 멀티 프로세스 환경에서는 OS가 스케줄링을 통해 CPU에 프로세스를 할당

1.1.4 커널과 시스템 콜⭐️⭐️⭐️

  • kernel
    • OS의 핵심 요소
    • 컴퓨터 하드웨어와 프로세스의 보안, 자원 관리, 하드웨어 추상화 같은 중요한 역할 수행
    • 특히 자원관리를 위해 CPU 스케줄링, 메모리 관리, 입출력 관리, 파일 시스템 관리 등 담당
    • 커널의 위치

  • 운영체제의 모드: 운영체제는 커널에서 관리하는 중요 자원에 사용자가 쉽게 접근하지 못하도록 모드를 나눈다.
    • kernel mode(커널 모드): 하드웨어에 직접 접근해 메모리, CPU와 같은 자원을 사용할 수 있음
    • user mode(사용자 모드): 커널 모드의 자원에 접근할 수 없게 제한을 둠.
    • 사용자 모드에서 실행된 프로세스가 자원에 접근하려면 시스템 콜을 호출해 커널에 요청해야 함
  • 시스템 콜(system call)
    • 사용자 모드에서 커널 모드에 접근해 필요한 기능을 수행할 수 있게 하는 시스템 함수
    • 커널은 시스템 콜로 받은 요청을 처리한 후 다시 시스템 콜로 결과 값을 반환 Untitled
    • 시스템 콜을 사용해 프로세스 제어, 파일 조작, 장치 관리, 데이터의 유지 보수, 통신, 보호 가능
    • 시스템 콜 대표 예
      • 프로세스를 생성한느 fork()
      • 부모 프로세스가 자식 프로세스의 수행을 기다리는 wait()
    • 시스템 콜에서 커널에 매개변수를 전달하는 3가지 방법
      • 매개변수를 CPU의 레지스터에 직접 전달하는 방식. 단, 이 방식은 매개변수의 개수가 레지스터의 개수보다 많은 경우에 문제가 될 수 있어 권장 X
      • 매개변수를 메모리에 저장한 후 메모리 주소 값을 레지스터에 저장하는 방식
      • 매개변수를 프로그램의 stack에 push하고 OS에서 pop해 매개변수를 전달하는 방식

💡 사용 가능도 향상의 목적에 따르면, OS는 멀티스레드의 개념이라고 봐도 되는지?

  • 병렬 처리 방식을 사용하기 때문에 일부는 멀티스레드로 봐도 됨. 그러나 전부는 아님.


💭 1.2 프로세스(Day 2)


1.2.1 프로세스와 스레드⭐️⭐️⭐️

  • 프로세스(process)

    • 컴퓨터에서 실행 중인 하나의 프로그램을 의미
    • 프로그램은 특정 작업을 수행하기 위한 명령어의 집합
    • OS는 프로그램을 실행하면서 디스크에 저장된 데이터를 메모리로 로드
    • 프로세스는 OS로부터 독립된 메모리 영역(코드, 데이터, 스택, 힙)을 할당받음, 다른 프로세스의 메모리 영역에 접근할 수 X
    • 프로세스에 할당된 메모리 영역
    • 프로세스의 메모리 영역 구조
      • 스택(stack): 지역 변수, 함수의 매개변수(parameter), 반환되는 주소 값 등이 저장되는 영역. 높은 주소 값에서 낮은 주소 값으로 메모리가 할당되며, 영역 크기는 컴파일 때 결정됨
      • 힙(heap): 사요자에 의해 동적 메모리 할당이 일어나는 영역. C언어에서 malloc() 으로 할당되는 영역이라고 보면 됨. 낮은 주소 값에서 높은 주소 값으로 메모리가 할당되며, 영역 크기는 런타임 때 결정됨
      • 데이터(data): 전역 변수, 정적 변수, 배열, 구조체 등이 저장되는 영역.
        • BSS(Block Stated Symbol) 영역: 초기화하지 않은 변수 저장
        • 데이터 영역: 초기화한 변수 저장
      • 코드(code): 실행할 코드가 기계어로 컴파일되어 저장되는 영역. 텍스트(text) 영역이라고도 함.
    • 스택 영역과 힙 영역 → 동적으로메모리 할당이 가능해 두 영역 사이에 빈 메모리 공간이 존재함.
      • 스택 영역: LIFO(Last In First Out, 후입선출) 방식, 높은 주소 값에서 낮은 주소 값 순서로 사용
      • 힙 영역: FIFO(First In First Out, 선입선출) 방식, 낮은 주소 값에서 높은 주소 값 순서로 사용
    • 문제점: 메모리 영역 공유로 인해 서로의 영역 침범 문제 발생 가능함
      • 스택 *오버플로(stack overflow): 스택 영역이 힙 영역을 침범하는 경우
      • 힙 오버플로(heap overflow): 힙 영역이 스택 영역을 침범하는 경우
        - 오버플로(overflow): 메모리 공간에서 할당할 수 있는 최대 범위를 넘어가는 것을 의미
        - 언더플로(underflow): 메모리 공간에서 할당할 수 있는 최소 범위보다 작은 것을 의미
  • 스레드

    • 프로세스는 한 개 이상의 스레드를 가짐

    • thread: 프로세스에서 실제로 실행되는 흐름의 단위

      • 스레드는 프로세스 안에 존재하므로 프로세스의 메모리 공간을 이용하고, 지역 변수를 저장하는 스택 영역을 할당받음
      • 전역 변수를 저장하는 힙 영역은 다른 스레드와 공유
    • 운영체제에서 프로세스와 스레드의 구조

    • 스레드의 분류

      • 사용자 레벨 스레드(user-level thread): 사용자가 라이브러리를 이용해 생성 및 관리함
      • 커널 레벨 스레드(kernel-level thread): 커널이 스레드를 생성 및 관리함
    • 멀티 스레드 환경에서 사용자 레벨 스레드와 커널 레벨 스레드는 3가지 관계를 맺을 수 있음.

      • 다대일 모델(many-to-one model)
        • 사용자 레벨 스레드 n개에 커널 레벨 스레드 1개가 매핑되어 사용자 레벨에서 스레드를 관리
        • 하나의 사용자 레벨 스레드에서 시스템 콜을 호출하면 나머지 사용자 레벨 스레드는 커널 레벨에 접근할 수 없으므로 멀티 코어의 병렬성을 이용할 수 없음.
      • 일대일 모델(one-to-one model)
        • 사용자 레벨 스레드 1개에 커널 레벨 스레드 1개가 매핑됨
        • 하나의 사용자 레벨 스레드에서 시스템 콜을 호출하면 다른 사용자 레벨 스레드가 모두 실행되지 않는 다대일 모델의 단점을 해결함
        • 하지만, 사용자 레벨 스레드 수만큼 커널 레벨 스레드가 생성되므로 성능 저하 발생

      • 다대다 모델(many-to-many model)
        • 사용자 레벨 스레드 n개에 커널 레벨 스레드 m개가 매핑됨
        • 이때, 커널 레벨 스레드의 수(m)는 사용자 레벨 스레드의 수(n) 이하
        • 다대일 모델과 일대일 모델의 장점을 포함하지만, 구현이 어렵다는 단점 존재

1.2.2 PCB⭐️⭐️

  • PCB(Process Control Block, 프로세스 제어 블록)
    • OS는 프로세스를 제어하기 위해 프로세스 정보를 저장
    • PCB 저장 정보
      • 프로세스의 현재 상태
      • PID(Process ID): 프로세스를 나타내는 고유의 ID
      • 부모 프로세스의 ID
      • 자식 프로세스의 PID
      • 다음 실행할 명령어의 주소인 PC(Program Counter, 프로그램 카운터)
      • 프로세스의 우선순위, 메모리 제한 등을 저장

1.2.3 프로세스의 생성⭐️⭐️⭐️

  • 새로운 프로세스는 기존 프로세스에서 fork() 함수를 호출해 생성

    • fork() 함수: 호출한 프로세스를 복사하는 기능이 있음
    • 기존 프로세스: 부모(parent) 프로세스, 복사된 프로세스: 자식(child) 프로세스
  • 부모 프로세스에서 fork() 함수를 호출하면 → 부모 프로세스는 자식 프로세스의 PID 값을, 자식 프로세스는 0을 반환

  • 자식 프로세스의 생성

  • 운영체제가 프로세스를 종료하는 경우

    • 프로세스가 운영체제의 종료 서비스(exit() )를 호출해 정상 종료하는 경우
    • 프로세스의 실행 시간 또는 특정 이벤트 발생을 기다리는 시간이 제한된 시간을 초과한 경우
    • 프로세스가 파일 검색 또는 입출력에 실패하는 경우
    • 오류가 발생하거나 메모리 부족 등이 발생하는 경우
  • 부모 프로세스가 자식 프로세스를 종료시키는 경우

    • 자식 프로세스가 할당된 자원을 초과해 사용할 때
    • 자식 프로세스에 할당된 작업이 없을 때
  • fork() 함수의 반환값

    • fork() 함수의 반환값이 2개라는 부분을 이해하기 위해 시스템 콜을 호출하는 코드를 보고 이해하기
      //ForkSample.c
      
      #include <stdio.h>
      #include <unistd.h>
      
      int main(){
      	printf("start!\n");
      	int forkRet = fork();
      	if (forkRet == 0) {
      		printf("child process %d\n", getpid());
      	} else {
      		printf("forkRet:%d parent process:%d\n", forkRet, getpid());
      	}
      	return 0;
      }
      
      -------------------------교재 출력-----------------------------
      start!
      forkRet:136 parent process:135
      child process 136
      
      ---------실제 출력(PID는 고유값이므로 똑같지 않을 수 있음)-------------
      start!
      forkRet:40211 parent process:40210
      child process 40211
      //주언어로 코드 작성(교재 X)
      //ForkSample.java
      
      import java.io.IOException;
      
      public class main {
          public static void main(String[] args) {
              System.out.println("start!");
      
              ProcessBuilder processBuilder = new ProcessBuilder("java", "ChildProcess");
              processBuilder.inheritIO();  // 자식 프로세스의 출력을 부모 프로세스의 출력에 연결
      
              int forkRet;
      
              try {
                  Process process = processBuilder.start();
                  forkRet = (int) process.pid();
                  System.out.printf("forkRet:%d parent process:%d\n", forkRet, ProcessHandle.current().pid());
                  process.waitFor();  // 자식 프로세스가 종료될 때까지 대기
              } catch (IOException | InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }
      
      class ChildProcess {
          public static void main(String[] args) {
              System.out.printf("child process %d\n", ProcessHandle.current().pid());
          }
      }
      
      ---------출력(PID는 고유값이므로 똑같지 않을 수 있음)-------------
      start!
      forkRet:28825 parent process:28806
      child process 28825
      • start! 는 부모 프로세스에 의해 한 번 출력됨
      • 부모 프로세스가 fork() 함수를 호출하면 반환값은 자식 프로세스의 PID인 136이 됨
      • 따라서 else 문으로 이동해 forkRet 값과 본인(부모 프로세스)의 PID를 출력함
      • 자식 프로세스는 부모 프로세스가 fork() 함수를 호출한 시점에 생성되고, 반환값이 0이므로 첫 번째 조건문인 forkRet == 0 을 만족
      • 따라서 본인(자식 프로세스)의 PID를 출력하고 종료

1.2.4 프로세스 상태도⭐️⭐️⭐️

  • 모든 프로세스는 CPU에 의해 생성되고 소멸하는 과정을 거침

  • 이 과정에서 프로세스는 생성(new), 준비(ready), 대기(waiting), 실행(running), 종료(terminated)의 5가지 상태로 존재

  • 프로세스 상태도

    • 생성(new): 프로세스가 PCB를 가지고 있지만 OS로부터 승인(admit)받기 전
      • 승인(admit): CPU를 제외한 다른 자원이 준비되어 해당 프로세스가 준비 상태가 될 수 있도록 OS가 허락하는 것을 의미
    • 준비(ready): OS로부터 승인받은 후 준비 큐에서 CPU 할당을 기다림
    • 실행(running): 프로세스가 CPU를 할당받아 실행함
    • 대기(waiting): 프로세스가 입출력이나 이벤트 발생을 기다려야 해서 CPU 사용을 멈추고 기다림
    • 종료(terminated): 프로세스 실행을 종료함
  • 프로세스의 변화(한 상태에서 다른 상태로 변화)

    • 생성 → 준비: 생성 상태의 프로세스가 OS로부터 승인을 받아, 준비 상태의 프로세스가 모여 있는 자료구조인 준비 큐(ready queue)에 추가됨
    • 준비 → 실행: 준비 큐에 잇는 프로세스 중 우선순위가 높은 프로세스가 디스패치(dispatch)되어 실행됨
      • 디스패치(dispatch) : 프로세스에 CPU 자원을 할당해 해당 프로세스가 준비 상태에서 실행 상태가 되는 것을 의미
    • 실행 → 준비: CPU 독점을 방지하기 위해 타임아웃(timeout)되어 준비 상태로 변경됨
    • 실행 → 대기: 입출력 또는 이벤트 때문에 대기 상태로 변경됨
    • 대기 → 준비: 입출력 또는 이벤트가 완료되어 준비 상태로 변경됨
    • 실행 → 종료: 실행 중인 프로세스가 정상적으로 끝나서 종료 상태로 변경됨

1.2.5 멀티 프로세스와 멀티 스레드⭐️⭐️⭐️

  • 동시성과 병렬성

    • 동시성(concurrency): 하나의 코어(싱글 코어)에서 여러 작업을 번갈아 가면서 처리하는 방식
      • 콘텍스트 스위칭(context switching): CPU는 한 번에 하나의 작업만 처리할 수 있어 여러 작업을 돌아가면서 처리하기 때문에, 하나의 CPU에서 여러 작업을 번갈아가면서 처리하기 위해 처리 중인 작업을 교체하는 것
    • 병렬성(parallelism): CPU가 여러 개(멀티 코어) 있어서 각 CPU에서 각 작업을 동시에 처리하는 방식. 즉, 물리적인 시간 관점에서 동시에 여러 작업이 처리 됨.
  • 멀티 프로세스(multi process)

    • 응용 프로그램 하나를 여러 프로세스로 구성하는 것을 의미
    • 멀티 프로세스 환경에서는 한 프로세스가 죽어도 다른 프로세스에 영향을 주지 않음
    • 응용 프로그램을 프로세스 하나로 구성하는 것보다 여러 개로 구성하는 것이 안정적
    • 단점: 시간과 메모리 공간을 많이 사용
      • 오버헤드(overhead): 여러 프로세스를 처리하려면 CPU에서 처리 중인 프로세스를 교체하는 콘텍스트 스위칭 작업이 이루어져야 하고, 이때 CPU에서 기존에 처리하던 프로세스가 할당받은 메모리 영역을 다른 프로세스에서 사용할 수 있게 교체하면서 시간과 메모리가 필요한 것을 의미
      • 프로세스는 독립적인 메모리를 할당받는데, 프로세스 간에 공유할 자원이 있다면 IPC(Inter Process Communication)를 통해 프로세스 간에 자원을 공유해야 함. 그래서 공유할 메모리를 직접 참조하는 것보다 비효율적
      • 멀티 프로세스 구조
  • 멀티 스레드(multi thread)

    • 스레드를 여러 개 생성해 스레드들이 각자 다른 작업을 처리하는 것을 의미
    • 멀티 스레드는 스레드 간 힙, 데이터, 코드 영역을 공유함 → 콘텍스트 스위칭 시 오버헤드가 적게 발생하고, IPC를 사용하지 않아도 되어 멀티 프로세스의 단점 보완 가능
    • 따라서 독립적인 메모리 공간을 갖는 프로세스를 여러 개 생성하는 것보다 스레드를 여러 개 생성하는 것이 자원을 효율적으로 사용할 수 있음
    • 스레드 간 자원 공유가 프로세스 간 자원 공유보다 시스템 처리 비용이 적고 프로그램 응답 시간도 단축됨
    • 단점: 스택 영역을 다른 스레드와 함께 사용하므로 공유 자원에 대한 동기화 필수 + 스레드에 문제가 생기면 프로세스 내 다른 스레드에 영향을 미칠 수 있음
    • 멀티 스레드 구조

💡 멀티 스레드 환경에서의 다대일 모델의 경우 하나의 사용자 레벨 스레드에서 시스템 콜을 호출하면 나머지 사용자 레벨 스레드는 커널 레벨에 접근할 수 없어 멀티 코어의 병렬성을 이용할 수 없다고 했는데, 그렇다면 다대일 모델을 여러 개를 세워두고 병렬로 처리할 수는 없을 것인지?

  • 이러한 경우 다대일 모델이 각각의 커널 레벨 스레드에 할당되는 것이나 마찬가지가 될테니, 별개의 분리된 다대일 모델로 보는 것이 맞고 이것을 동시에 처리하는 것을 가능할 것 같음. 하지만 병렬 처리라고 명확하게 단정하기는 어려울 것으로 보임.

1.2.6 콘텍스트 스위칭⭐️⭐️⭐️

  • 인터럽트(interrupt)

    • 우리말로 ‘방해하다, 중단시키다’라는 뜻
    • CPU에서 프로세스를 처리하다가 입출력 관련 이벤트가 발생하거나 예외 상황이 발생할 때 이에 대응할 수 있게 CPU에 처리를 요청하는 것을 의미
    • 인터럽트가 발생하는 경우: 입출력이 발생할 때, CPU 사용 시간이 만료되었을 때, 자식 프로세스를 생성할 때
  • 콘텍스트(context)

    • CPU는 하나의 프로세스만 처리할 수 있으므로 멀티 프로세스를 처리하려면 CPU 스케줄러에 의해 인터럽트가 발생하면서 콘텍스트 스위칭이 이뤄짐.
    • 여기에서 콘텍스트는 CPU가 처리하는 프로세스의 정보를 의미
    • 즉, 멀티 프로세스 환경에서 CPU가 처리 중인 프로세스의 정보를 바꾸는 것이 콘텍스트 스위칭
  • 콘텍스트 스위칭 과정

    • 처리 중인 프로세스를 P1, 다음에 처리해야 하는 프로세스를 P2라 하자.
    • CPU가 P1을 처리하던 중 운영체제에 의해 인터럽트 발생 → P1은 유휴 상태(idle)로 변하고, 스케줄러는 레지스터에 있는 처리 중인 작업 정보를 P1의 PCB에 저장 → P2의 PCB에 있는 정보를 가져와 레지스터에 로드하고 CPU는 P2를 처리하기 시작
    • P1의 정보를 P1의 PCB에 저장하고, P2의 PCB에 저장된 정보를 레지스터에 로드하는 동안 CPU는 아무 일도 못하게 됨
      • 어떤 처리를 하는 데 간접적인 처리 시간과 메모리가 소요될 경우에 ‘오버헤드가 발생한다’고 함
    • 멀티 스레드를 처리할 때도 콘텍스트 스위칭이 이루어지나, 멀티 프로세스의 콘텍스트 스위칭보다 시간과 메모리 자원을 적게 사용함
    • 멀티 스레드는 스택을 제외한 힙, 데이터, 코드 영역을 공유하므로 레지스터에 저장하고 로드해야 하는 데이터가 상대적으로 적기 때문
  • CPU에서 처리 중인 프로세스가 중간에 변경되어도 이전에 실행하던 코드를 이어서 실행할 수 있는 이유는 PCB에 프로그램 카운터와 스택 포인터 값이 저장되어 있기 때문

    • 프로그램 카운터(PC, Program Counter): 프로세스가 이어서 처리해야 하는 명령어의 주소 값
    • 스택 포인터(stack pointer): 스택 영역에서 데이터가 채워진 가장 높은 주소 값
    • 이어서 실행할 명령어의 주소 값이 무엇인지, 데이터가 스택에 어디까지 채워져 있는지 알고 있으므로 콘텍스트 스위칭이 원활히 이뤄질 수 있음!

1.2.7 프로세스 동기화⭐️⭐️⭐️

  • 경쟁 상태(race condition)

    • 여러 프로세스 또는 스레드에서 하나의 공유 자원에 접근하는 경우가 있는데, 이때 자원에 접근하는 순서에 따라 결과 값이 달라질 수 있는데, 이러한 현상을 공유 자원에 동시에 접근해 경쟁하는 상태라고 함.
    • 대표적인 예) 너무 많은 우유 문제(too much milk problem) → 냉장고에 우유가 다 떨어져서 새로 사야 하는 상황을 가정

      1) 엄마가 냉장고를 열어 우유가 없는 것을 확인한다.
      2) 엄마가 우유를 사러 슈퍼마켓에 간다.
      3) 엄마가 우유를 사서 집에 돌아오는 길에 아빠가 냉장고에 우유가 없는 것을 확인한다.
      4) 아빠가 우유를 사러 슈퍼마켓에 간다.
      5) 아빠가 우유를 사서 집에 돌아온다.
      • 우유는 1개면 충분 → 2개가 되어 문제가 발생
      • 이때, 냉장고는 공유 자원 / 엄마, 아빠는 각각 프로세스를 의미
      • 우유가 0개에서 1개가 되는 것을 기대했지만, 엄마가 우유를 사러 간지 모르고 아빠가 우유를 사러 감으로써 우유는 총 2개가 되는, 의도하지 않은 결과를 초래
      • 문제 해결? : 프로세스 동기화가 이뤄져야 함
  • 임계 영역(critical section)

    • 공유 자원에 접근할 수 있고 접근 순서에 따라 결과가 달라지는 코드 영역
    • 냉장고에 우유 유무를 판단하고 우유를 추가하는 부분에 해당
    • 임계 영역에서 경쟁 상태가 발생하는 것을 방지하려면 여러 프로세스가 공유 자원에 접근해도 데이터의 일관성이 유지되도록 프로세스 동기화(process synchronization)를 해야함
    • 임계 영역에 여러 접근이 동시에 발생하는 것을 방지하는 3가지 조건
      • 상호배제 기법(mutual exclusive): 어떤 프로세스가 임계 영역을 실행 중일 때 다른 프로세스가 임계 영역에 접근할 수 없음. ex)뮤텍스, 세마포어
      • 진행(progress): 임계 영역을 실행 중인 프로세스가 없을 때 다른 프로세스가 임계 영역을 실행함
      • 한정된 대기(bounded waiting): 임계 영역에 접근을 요청했을 때 무한한 시간을 기다리지 않음
  • 뮤텍스(mutex)

    • 락(lock)을 가진 프로세스만이 공유 자원에 접근할 수 있게 하는 방법
    • 예시) toilet example
      • 뮤텍스의 작동 방식은 화장실과 화장실 열쇠가 하나뿐인 식당과 같음

        1) 식당에는 화장실 한 칸과 화장실 문을 열 수 있는 열쇠 한 개가 있다.
        2) A가 열쇠를 가지고 화장실에 간다.
        3) 화장실에 가려던 B는 열쇠가 없어서 기다린다.
        4) A가 화장실에서 나와 열쇠를 반납하면, 기다리던 B가 열쇠를 가지고 화장실에 간다.
        • 화장실 = 공유 자원을 포함한 임계 영역
        • 열쇠 = 락
        • A와 B = 공유 자원에 접근하려는 프로세스
    • 락킹 매커니즘(locking mechanism)
      • 임계 영역에 먼저 접근한 프로세스가 임계 영역에 락을 걸면 다른 프로세스들은 해당 프로세스가 락을 해제하기 전까지 대기해야 함.
      • 임계 영역에 접근한 프로세스가 임계 영역에 락을 건다는 의미
    • 스핀락
      • 바쁜 대기(busy waiting)의 한 종류
        • 바쁜 대기: 프로세스가 공유 자원에 접근할 수 있는 권한을 얻을 때까지 확인하는 과정
      • 임계 영역에 접근하지 못한 프로세스는 락을 얻기 위해 기다리는 동안 락이 풀렸는지 “반복문”을 돌면서 확인
      • 프로세스가 대기 상태가 되지 않고 반복문을 돌면서 자원의 사용 가능 여부를 확인하므로 프로세스가 빠르게 교체될 수 있음
  • 세마포어(semaphore)

    • 공유 자원에 접근할 수 있는 프로세스의 수를 정해 접근을 제어하는 방법

      1) 식당에 화장실 3칸, 화장실을 열 수 있는 열쇠가 3개 있다.
      2) A가 화장실 열쇠 하나를 가지고 화장실에 간다. 열쇠는 2개가 남는다.
      3) B, C가 열쇠를 하나씩 가지고 화장실에 간다. 남은 열쇠가 없어서 D는 화장실에 가지 못한다.
      4) C가 화장실에서 나와 열쇠를 돌려놓으면, D가 화장실에 간다.

      • 화장실 = 공유 자원을 포함한 임계 영역
      • A, B, C, D = 공유 자원에 접근하려는 프로세스 의미
      • 화장실 개수(열쇠 개수) = 공유 자원에 접근할 수 있는 프로세스의 수를 제어하기 위한 정수 변수
    • 임계 영역에 접근할 수 있는 키 n개를 지정하고 이 중 하나를 가진 프로세스만이 임계 영역에 접근하게 하는 방식

    • 시그널링 메커니즘(signaling mechanism): 공유 자원에 접근한 프로세스가 접근을 해제하면 다른 프로세스가 접근할 수 있도록 신호를 보냄

      주의

      • 동기와 비동기는 작업 순서에 대한 개념
        • 동기(synchronization): 여러 작업을 처리할 때 작업 순서를 보장함
        • 비동기(asynchronization): 여러 작업을 처리할 때 작업 순서를 보장하지 않음
      • 블로킹과 넌블로킹은 작업을 위한 대기를 구분하는 개념
        • 블로킹(blocking): 작업을 수행할 때 대기할 수 있다는 것을 의미하여 작업 순서를 보장하지 않음
        • 넌블로킹(non-blocking): 작업을 시작하면 대기 없이 수행한다는 것을 의미

1.2.8 교착 상태⭐️⭐️⭐️

  • 교착 상태(deadlock)
    • 상호배제 기법때문에 2개 이상의 프로세스가 각각 자원을 가지고 있으면서 서로의 자원을 요구하며 기다리는 상태
  • 교착 상태가 발생하는 4가지 필요 충분 조건
    • 상호배제(mutual exclusion): 하나의 공유 자원에 하나의 프로세스만 접근할 수 있다.
    • 점유와 대기(hold and wait): 프로세스가 최소 하나의 자원을 점유하고 있는 상태에서 추가로 다른 프로세스에서 사용 중인 자원을 점유하기 위해 대기한다.
    • 비선점(non-preemption): 다른 프로세스에 할당된 자원을 뺏을 수 없다.
    • 환형 대기(circular wait): 프로세스가 자신의 자원을 점유하면서 앞이나 뒤에 있는 프로세스의 자원을 요구한다.
  • 교착 상태를 막으려면 → 필요 충분 조건 중 1가지 제거하면 됨
    • 상호배제 부정: 여러 프로세스가 동시에 하나의 공유 자원을 사용할 수 있게 한다.
    • 점유와 대기 부정: 프로세스가 실행되기 전에 필요한 모든 자원을 할당함으로써 프로세스 대기를 없앤다. 또는 프로세스가 자원을 점유하지 않은 상태에서만 자원을 요구하게 한다.
    • 비선점 부정: 자원을 점유한 프로세스가 다른 자원을 요구할 때 점유한 자원을 반납하게 한다.
    • 환형 대기 부정: 자원을 선형 순서로 정렬해 고유 번호를 할당한다. 그리고 각 프로세스에서 요구할 수 있는 번호와 방향을 정해서 한쪽 방향으로만 자원을 요구하게 한다.

이해

  • 프로세스 1이 자원 1을, 프로세스 2가 자원 2를 가지고 있는 상황 → 이때 프로세스 1은 자원 2가 필요하고, 프로세스 2는 자원 1이 필요해 교착 상태 발생
    • 상호배제
      • 하나의 공유 자원에 대해 하나의 프로세스만 사용할 수 있으므로, 만약 하나의 공유 자원을 여러 프로세스가 공유할 수 있다면 위의 그림은 성립 X
      • 따라서 자원을 공유할 수 있게 되면 서로의 자원을 기다리는 상황을 예방할 수 있음
    • 점유와 대기
      • 하나의 프로세스가 이미 자원을 가진 상태에서 다른 프로세스의 자원을 갖기 위해 기다리는 상황
      • 프로세스가 자원을 요구할 때 필요한 자원 1과 자원 2를 모두 주거나 가진 자원이 없는 경우에만 자원을 할당함으로써 문제를 해결할 수 있음
    • 비선점
      • 다른 프로세스가 가지고 있는 자원을 뺏을 수 없다는 개념
      • 프로세스 1이 프로세스 2의 자원을 뺏을 수 있게 한다면 서로의 자원을 갖기 위해 대기하는 상황을 예방할 수 있음
    • 환형 대기
      • 프로세스 1이 프로세스 2의 자원을 요구하고, 프로세스 2가 프로세스 1의 자원을 요구함으로써 생김
      • 위 그림과 같이 각자의 자원을 가진 상태에서 상대방의 자원을 요구하는 상황
      • 이때, 작은 번호 프로세스가 큰 번호 프로세스의 자원을 요구하는 것만 가능하게 한다면 프로세스 2가 프로세스 1의 자원을 요구할 수 없게 되므로 환경 대기를 없앨 수 있음.

1.2.9 스레드 안전⭐️⭐️

  • 스레드 안전(thread safe)
    • 멀티 스레드 환경에서 하나의 변수, 함수, 객체에 스레드 여러 개가 동시에 접근해도 문제가 없음을 의미
      var++;
      • var 변수의 값을 메모리에서 CPU 레지스터로 로드 → 연산 처리 → 연산 결과를 메모리에 작성하는 과정을 거침
      • 만약 이 코드에 스레드 2개가 접근하면 잘못된 결과를 초래할 수 있음
        • 스레드 안전이 이뤄지지 않는 경우
          • 이처럼 한 줄짜리 코드에서도 스레드 2개가 접근하면 예상 밖의 결과가 나옴
          • 너무 많은 우유 문제도 이에 해당
      • 스레드 안전을 위한 조건
        • 상호 배제(mutual exclusive)
          • 공유 자원에 접근해야 할 때 뮤텍스 또는 세마포어와 같은 상호배제 기법을 사용해 접근을 통제해야 한다.
        • 원자 연산(atomic operation)
          • 공유 자원에 접근할 때 원자 연산을 이용하거나 원자적으로 정의된 연산을 이용해 연산 도중에 다른 스레드가 접근할 수 없게 한다.
          • 여기서 원자 연산이란 ‘연산했다’와 연산 안 했다’ 두 가지만 존재하는 연산이다.
        • 재진입성(reentrancy)
          • 특정 함수를 하나의 스레드에서 실행 중일 때 다른 스레드가 해당 함수를 실행해도 각 스레드에 올바른 결과가 나올 수 있게 해야 한다.
        • 스레드 지역 저장소(thread local storage)
          • 각 스레드에서만 접근할 수 있는 저장소를 사용해서 공유되는 자원을 줄여야 한다.

1.2.10 IPC⭐️⭐️

  • Inter Process Communication의 약자로, 프로세스 간에 자원을 공유하는 방식을 나타낸다.
  • 종류
    • 공유 메모리(shared memory)
      • 프로세스 간에 공유 가능한 메모리를 구성해 자원을 공유하는 방식
      • 여러 프로세스에서 접근할 수 있으므로 동기화 문제가 발생할 수 있음

    • 소켓(socket)
      • 네트워크 소켓을 이용하는 프로세스 간 통신. 외부 시스템과도 이용할 수 있음
      • 클라이언트(client)와 서버(server) 구조로 자원을 주고받음

    • 세마포어(semaphore)
      • 접근하는 프로세를 제어해 공유 자원 관리
    • 파이프(pipe)
      • FIFO 형태의 메모리인 파이프를 이용해 프로세스 간 자원을 공유하는 방식
      • 파이프는 단방향 통신만 지원하므로 읽기 또는 쓰기 중 하나만 할 수 있음
      • 따라서 양방향 통신을 하려면 읽기 파이프(read pipe)와 쓰기 파이프(write pipe)를 각각 생성해야 함
    • 메시지 큐(message queue)
      • FIFO 형태의 큐 자료구조를 사용해 프로세스 간 메시지를 주고받는 방식

1.2.11 좀비 프로세스와 고아 프로세스⭐️⭐️

  • 좀비 프로세스(zombie process)
    • 자식 프로세스가 종료되었지만 부모 프로세스가 자식 프로세스의 종료 상태를 회수하지 않았을 경우에 남겨진 자식 프로세스를 뜻함
      • 자식 프로세스가 종료될 때 부모 프로세스에 SIGCHLD 라는 시그널을 보내면 부모 프로세스에서 wait() 함수(시스템 콜)를 호출해 자식 프로세스의 상태 정보를 받고 자원을 회수함
      • 이때, 자원 회수에 실패하면 좀비 프로세스가 생기게 됨 → 좀비 프로세스가 쌓이면 자원이 낭비될 수 있음
  • 고아 프로세스(orphan process)
    • 부모 프로세스가 자식 프로세스보다 먼저 종료되는 경우에 자식 프로세스를 뜻함
      • 자식 프로세스의 부모 PID를 init 프로세스(부팅 시 가장 먼저 실행되는 프로세스)의 PID인 1로 바꿔줌 → 이렇게 하면 고아 프로세스의 부모 프로세스는 init 프로세스가 됨
      • 이후에 고아 프로세스가 작업을 종료하면 init 프로세스가 고아 프로세스의 자원을 회수해 좀비 프로세스가 되는 것을 방지할 수 있음


💭 1.3 스케줄링(Day 3)




💭 1.4 메모리 관리 전략(Day 4)




💭 1.5 가상 메모리(Day 5)




💭 1.6 캐시 메모리(Day 6)




💭 1.7 ~ 1.8 면접 전 요약 정리, 예상 면접 질문(Day 7)


profile
언젠가 내 코드로 세상에 기여할 수 있도록, BE 개발 기록 노트☘️

0개의 댓글