Skip to content

Latest commit

 

History

History
348 lines (297 loc) · 18.8 KB

3_OS.md

File metadata and controls

348 lines (297 loc) · 18.8 KB

3_OS

목차




OS (Operating System)

컴퓨터를 쉽게 다루게 해주는 인터페이스. 한정된 메모리나 시스템 자원을 효율적으로 분배한다.

운영체제와 컴퓨터

운영체제의 역할과 구조

역할

  1. CPU 스케줄링과 프로세스 관리: CPU 소유권을 어떤 프로세스에 할당할지, 프로세스의 생성과 삭제, 자원 관리 등을 관리
  2. 메모리 관리: 한정된 메모리를 어떤 프로세스에 얼마큼 할당해야할지 관리
  3. 디스크 파일 관리
  4. I/O 디바이스 관리

구조

GUI

  • 사용자가 전자 장치와 상호 작용할 수 있도록 하는 사용자 인터페이스의 한 형태이다.
    • 리눅스 서버에서는 GUI가 아닌 CUI를 사용한다.

커널

  • 프로세스 관리, 메모리 관리, 저장장치 관리와 같은 운영체제의 핵심적인 기능을 모아둔 것이다.
  • 자동차:운영체제 = 엔진:커널

인터페이스

  • 커널에 사용자의 명령을 전달하고, 실행결과를 사용자에게 알려주는 역할
  • 커널과 인터페이스를 분리하여 같은 커널이여도 다른 인터페이스 형태로 제작 가능하다.
  • 시스템 호출과 드라이버가 있다.

시스템 호출

  • 커널이 자신을 보호하기 위해 만든 인터페이스
  • 컴퓨터 자원을 보호하기 위해 자원에 직접 접근하는 것을 차단
  • modebit 변수가 0일 때 커널 모드 1일 때 유저 모드로 설정되며, 유저 모드일 경우 시스템 콜을 못하게 막아서 한정된 일만 하도록 설정

드라이버

  • 커널과 하드웨어의 인터페이스
  • 하드웨어를 제어할 수 있다

컴퓨터의 요소

CPU, DMA 컨트롤러, 메모리, 타이머, 디바이스 컨트롤러로 이루어짐

CPU

  • 인터럽트에 의해 단순히 메모리에 존재하는 명령어를 해석해서 실행
    • 커널이 프로그램을 메모리에 올려 프로세스로 만듬
    • 인터럽트: 어떤 신호가 왔을 때 CPU를 잠깐 정지시키며, 인터럽트 핸들러 함수가 실행됨
      • 하드웨어 인터럽트: IO 디바이스에서 발생하는 인터럽트
      • 소프트웨어 인터럽트: 프로세스 오류 등으로 프로세스가 시스템콜을 호출할 때마다 발동됨
  • 산술 논리 연산 장치, 제어 장치, 레지스터로 구성
    • 제어 장치
      • 프로세스 조작을 지시
      • 입출력 장치 간 통신을 제어하고 명령어들을 읽고 해석, 데이터 처리를 위한 순서를 결정함
    • 레지스터
      • 매우 빠른 임시기억장치
      • 레지스터를 거쳐 CPU에 데이터를 전달
    • 산술 논리 연산 장치
      • 산술 연산과 논리 연산을 계산하는 디지털 회로
      • CPU의 연산처리
        1. 제어장치가 메모리, 레지스터에 계산할 값을 로드.
        2. 제어장치가 레지스터에 있는 값을 계산하라고 산술 논리 연산 장치에 명령.
        3. 제어장치가 계산된 값을 다시 레지스터에서 메모리로 저장.

DMA 컨트롤러

  • I/O 디바이스가 메모리에 직접 접근할 수 있도록 하는 하드웨어 장치
  • CPU 부하를 막아주며, CPU의 일을 부담하는 보조일꾼

메모리 (RAM)

  • 전자회로에서 데이터나 상태, 명령어를 기록.
  • CPU는 계산을 담당하고, 메모리는 기억을 담당
  • 메모리가 크면 많은 일을 동시에 할 수 있음

메모리

CPU는 단지 메모리에 올라온 프로그램의 명령어를 실행할 뿐이다.

메모리 계층

  • 레지스터: CPU 안에 있는 작은 메모리. 휘발성, 속도 가장 빠름. 용량이 가장 적음
  • 캐시: L1, L2, L3 캐시를 지칭함. 휘발성, 속도 빠름. 기억 용량이 적음
  • 주기억장치(RAM): 휘발성, 속도, 기억 용량이 보통임
  • 보조기억장치(HDD,SDD): 휘발성, 속도가 낮음. 기억 용량이 많음

계층이 위로 올라갈 수록 가격이 비싸진다. 이러한 경제성 때문에 계층을 두어 관리함.
게임에서 '로딩 중'이라는 메시지는 하드디스크 또는 인터넷의 데이터가 RAM으로 전송되는 과정이다.

캐시

  • 데이터를 미리 복사해 놓은 임시 저장소
  • 계층과 계층사이에 위치함
    • 예시) 캐시 메모리와 보조기억장치 사이에는 보조 기억 장치라는 캐싱 계층이 있음
  • 빠른 장치와 느린 장치에서 속도 차이에 따른 병목 현상을 줄이기 위함
  • 지역성의 원리: 캐시 계층을 두지 않고 캐시를 직접 선택할 때, 자주 사용하는 데이터를 기반으로 설정해야하는 데 그때의 근거
    • 시간 지역성: 최근 사용한 데이터에 다시 접근하려는 특성 (for 문에서 i를 계속 접근한다)
    • 공간 지역성: 최근 접근한 데이터와 같거나 가까운 공간에 접근하는 특성 (배열 arr에 계속 접근)
  • 웹 브라우저의 캐시: 웹 브라우저의 작은 저장소 쿠키, 로켈 스토리지, 세션 스토리지
  • DB의 캐시: 메인 DB위에 redis DB를 '캐싱 계층'으로 두어 성능을 향상시킴.

캐시히트와 캐시미스

  • 캐시히트: 캐시에서 원하는 데이터를 찾음
  • 캐시미스: 해당 데이터가 없다면 주메모리(RAM)으로 가서 데이터를 찾아옴
  • 캐시매핑: 캐시히트를 위해 매핑하는 방법
    • 직접매핑: 메모리가 1100이 있고, 캐시가 110이 있다면 1:110, 2:1120으로 매핑. 처리가 빠르지만 충돌이 자주 발생.
    • 연관매핑: 순서를 일치시키지 않고 관련 있는 캐시와 메모리를 매핑. 충돌이 적지만 모든 블록을 탐색해야 함으로 속도가 느림.
    • 집합 연관 매핑: 직접매핑과 연관 매핑을 합쳐 놓은 것. 순서는 일치시키지만 집합을 둬서 저장하며 블록화되어 있어 검색이 더 효율적. 메모리가 1100이 있고 캐시가 110이 있다면 캐시 15에는 150 데이터를 무작위로 저장시킴.

메모리 관리

운영체제의 대표적인 할 일 중 하나. 컴퓨터 내의 한정된 메모리를 극한을 활용한다.

가상 메모리

  • 메모리가 실제 메모리보다 많이 보이게 하는 기술
  • 물리 메모리는 한정적이기 때문에 현재 사용하는 페이지만 올리는 것이 효율적이다
  • 가상 주소(가상적으로 주어진 주소), 실제 주소(실제 메모리상에 있는 주소)로 나뉨
    • MMU(Memory Management Unit): 메모리 관리 장치에 의해 가상 주소를 실제 주소로 변환
      • CPU에서 가상주소를 통해 메모리에 접근하면 MMU를 통해 실제주소로 변경된다
    • 페이지 테이블: 가상 주소와 실제 주소가 매핑되어 있고, 프로세스 주소가 존재함
    • TLB: 메모리와 CPU 사이에 주소 변환을 위한 캐시. 속도 향상에 기여.

Page Fault

  • 가상 메모리에는 존재하지만, 실제 메모리인 RAM에는 현재 없는 데이터에 접근할 경우 나타남
  • 페이지 폴트가 전혀 발생하지 않은 것처럼 하기 위해서 스와핑진행
    • 스와핑: Page Falut를 방지하기 위해서 RAM과 하드디스크 사이에 데이터를 조회 정도를 기준으로 올리고 내림을 반복함
  • Page: 가상 메모리를 사용하는 최소 크기 단위
  • frame: 실제 메모리를 사용하는 최소 크기 단위

스레싱

  • 메모리의 Page Fault이 높은 것. 심각한 성능 저하 초래.
  • 메모리에 너무 많은 프로세스가 동시에 올라가면 많은 스와핑이 일어나게 됨
  • 페이지 폴트가 많이 일어남 > CPU 이용률이 낮아짐 > CPU가 한가한가 해서 더 많은 프로세스를 메모리에 올림
  • 해결 방법
    1. 메모리를 늘리거나, HDD를 SDD로 전환
    2. 작업 세트: 프로세스의 과거 사용 이력인 지역성을 통해 페이지 집합을 만들어 미리 로드
    3. PFF: 페이지 폴트 빈도를 상한선과 하한선을 두어 조절. 상한선에 도달한다면 페이지를 늘림.

메모리 할당

  • 메모리에 프로그램을 할당할 때는 시작 메모리 위치, 메모리의 할당 크기를 기반으로 할당
  • 할당 방법
    1. 연속할당
      • 메모리에 연속적으로 공간을 할당
      • 고정 분할 방식: 메모리를 미리 나누어 관리. 내부 단편화(메모리보다 프로그램이 작아서 들어가지 못 하는 공간이 발생)
      • 가변 분할 방식: 프로그램 크기에 맞게 동적으로 메모리를나눠 사용. 외부 단편화(메모리를 나눈 크기보다 프로그램이 커서 들어가지 못하는 공간이 발생)
    2. 불연속할당
      • 페이징: 메모리를 동일한 크기의 페이지로 나누고, 프로그램마다 페이지 테이블을 두어 프로그램을 할당. 주소 변환이 복잡한 단점
      • 세그멘테이션: 페이지 단위가 아닌 의미 단위(코드, 데이터, 스택, 힙)인 세그먼트로 나눔.
      • 페이지드 세그멘테이션: 공유나 보안을 의미 단위인 세그먼트, 물리적 메모리는 페이지

프로세스와 스레드

  • 프로세스: 실행 중인 프로그램으로, OS로부터 주소/공간/자원 등을 할당받음.
  • 스레드: 프로세스 내 작업의 흐름.

프로세스와 컴파일 과정

  • 프로세스는 프로그램이 인스턴스화된 것
    • 프로그램이 메모리에 올라가면 프로세스가 되는 인스턴스화가 일어나고 CPU 스케줄러에 따라 프로세스가 실행된다
  • 컴파일 과정 (프로그램은 컴파일러가 컴파일 과정을 통해 기계어로 번역되어 실행될 수 있는 파일)
    1. 전처리기: 소스 코드의 주석을 제거하고 #include 등 헤더 파일을 병합하여 메크로로 치환.
    2. 컴파일러: 오류 처리, 코드 최적화 작업을 하며 어셈블리어로 변환. (고수준 → 저수준)
    3. 어셈블러: 어셈블리어가 목적 코드로 변환됨. (저수준 → 완전 기계어)
    4. 링커: 라이브러리 함수또는 다른 파일들과 목적 코드를 결합하여 실행 파일을 만듬.

최초 program.c

#include <stdio.h>
#define A 10
#define B 20
int main(){
        int a=A;
        int b=B;
        int c=a+b;
        printf("%d + %d = %d\n",a,b,c);
}

전처리 과정 후 (아직 고수준)

# 1 "program.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 1 "<command-line>" 2
# 1 "program.c"
....
extern int printf (const char *__restrict __format, ...);
...
int main(){
 int a=10;
 int b=20;
 int c=a+b;
 printf("%d + %d = %d\n",a,b,c);
}

컴파일러 후 (저수준)

.file   "program.c"
        .section        .rodata
.LC0:
        .string "%d + %d = %d\n"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        pushq   %rbp
        .cfi_def_cfa_offset 16
        .cfi_offset 6, -16
...
.LFE0:
        .size   main, .-main
        .ident  "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-16)"
        .section        .note.GNU-stack,"",@progbits

어셈블러 후 (완전 기계어)

^?ELF^B^A^A^@^@^@^@^@^@^@^@^@^A^@>^@^A^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@È^B^@^@^@^@^@^@^@^@^@^@@^@^@^@^@^@@^@^M^@

^@UH<89>åH<83>ì^PÇEü

^@^@^@ÇEø^T^@^@^@<8b><8b>^<89><8b><8b><8b><89>Æ¿^@^@^@^@¸^@^@^@^@è^@^@^@^@ÉÃ%d + %d = %d

...

^@^@^@^E^@^@^@^@^@^@^@^@^@^@^@7^@^@^@^@^@^@^@^B^@^@^@

프로세스의 상태

  1. 생성 상태: 프로세스가 생성된 상태. PCB에 할당.
    • fork(): 부모 프로세스의 주소 공간을 그대로 복사.
    • exec(): 새롭게 프로세스 생성
  2. 대기 상태: 메모리가 충분하지 않아, CPU 스케줄러로부터 CPU 소유권이 넘어오기를 기다리는 상태
  3. 대기 중단 상태: 메모리 부족으로 일시 중단된 상태
  4. 실행 상태: CPU소유권과 메모리를 할당받아 수행 중인 상태
  5. 중단 상태: 어떤 이벤트가 발생한 이후 기다리며 프로세스가 차단된 상태 (I/O 인터럽트)
  6. 일시 중단 상태: 중단된 상태에서 메모리 부족으로 실행되지 못하고 일시 중단된 상태 (대기 상태와 유사)
  7. 종료 상태: 메모리와 CPU 소유권을 모두 놓고 가는 상태

프로세스의 메모리 구조

  • OS는 프로세스에 적절한 메모리를 할당하기 위해 다음 구조를 활용

  1. 코드 영역
    • 프로그램에 내장된 소스 코드가 들어가는 영역
    • 정적임. 수정 불가능.
  2. 데이터 영역
    • 전역변수, 정적변수가 저장됨.
    • 프로그램이 종료되면 소멸한다
  3. 힙 영역
    • 동적 할당할 때 사용되며 런타임 시 크기가 결정됨.
    • ex. Array와 같은 동적 배열
  4. 스택 영역
    • 동적 할당할 때 사용되며 런타임 시 크기가 결정됨.
    • 지역변수, 매개변수, 함수가 저장됨.
    • 함수가 함수를 호출하면서 동적으로 크기가 늘언라 수 있는데, 힙과 스택의 메모리 영여깅 겹치면 안 되기 때문에 힙과 스택 사이 공간을 비워놓음

멀티 프로세싱

  • 하나 이상의 일을 병렬로 처리할 수 있음
  • 프로세스가 종료되더라도 다른 프로세스를 이용해서 처리할 수 있으므로 신뢰성이 높음
  • ex. 웹 브라우저, IPC

IPC (Inter Process Communication)

  • 프로세스끼리 데이터를 루족받고 공유 데이터를 관리하는 메커니즘
    • 클라이언트와 서버가 서로 응답하는 것도 IPC의 예
    • 메모리가 완전히 공유되는 스레드보다는 속도가 떨어짐
  • 방법
    • 공유 메모리
      • 여러 프로세스에 동일한 메모리 블록에 대한 접근 및 통신
      • 불필요한 데이터 복사의 오버헤드가 발생하지 않아 가장 빠름
      • 동기화 필요
    • 파일
      • 디스크에 저장된 데이터 또는 파일 서버에서 제공한 데이터를 말함
      • 이를 통해 프로세스간 통신
    • 소켓
      • 동일한 컴퓨터의 다른 프로세스나, 네트워크의 다른 컴퓨터로 네트워크 인터페이스를 통해 전송하는 데이터를 의미.
      • TCP, UDP
    • 메시지 큐
      • 데이터 구조 형태를 관리
      • 전역적으로 관리되며, 사용 방법이 간단

스레드와 멀티스레딩

스레드

  • 프로세스의 실행 가능한 가장 작은 단위
  • 프로세스는 여러 스레드를 가짐
  • 코드, 데이터, 힙을 스레드끼리 공유한다.
    • 스택 영역은 독립적으로 할당됨 (함수 호출 시 전달되는 인자, 되돌아갈 주소 값, 함수 내에서 선언 되는 변수 등)

멀티 스레딩

장점

  • 자원의 생성과 관리의 중복성을 최소화하여 메모리 공간과 시스템 자원 소모가 줄어든다.
  • 스레드 간의 통신도 **Heap 영역(전역변수)**을 이용하여 데이터를 주고받을 수 있다. 따라서 프로세스 간 통신 방법에 비해 훨씬 간단하다. 문제점
  • 스레드 간에 자원을 공유하기 때문에 사용중인 변수에 접근하여 엉뚱한 값을 읽어오거나 수정할 수 있다.
  • 따라서 멀티스레딩 환경에서는 동기화 작업이 필요하다. 이를 통해 작업 처리 순서를 컨트롤하는 것이 가능하다. 하지만 이로 인해 병목현상이 발생하여 성능이 저하될 가능성이 높다.

멀티 스레드 vs 멀티 프로세스

  • 하나가 오류로 인해 종료 → 모두 죽음 / 정상 실행
  • 메모리 공간/CPU 시간 → 적음 / 많음
  • 대상 시스템의 특징에 따라서 적합한 동작 방식을 선택해야 함

공유자원과 임계영역

  • 공유자원
    • 시스템 안에서 프로세스, 스레드가 함께 접근할 수 있는 자원이나 변수
    • 두 개 이상의 프로세스가 동시에 읽거나 쓰는 상황을 경쟁 상태라고 함
  • 임계영역
    • 공유 자원에 접근할 때 순서 등의 이유로 결과가 달라지는 영역
    • 스레드 들이 비동기화 되어 있다면 여러 스레드들이 서로 번갈아가면서 실행된다. CPU는 하나이기 때문에 OS스케줄링 정책에 따라 무작위로 조금씩 실행된다. 따라서 결과가 달라진다.

Thread Safe

한 스레드가 함수를 실행 중일 때, 다른 스레드가 함수를 호출하더라도 각각 옳바른 결과가 도출.

  1. 공유 자원 사용을 최대한 줄인다 (전역 변수) ⇒ 지역 변수만 있다면 Thread Safe
  2. Thread에 lock 매커니즘을 이용한다.
    1. 뮤텍스(mutex)
      • 공유 자원을 사용하기 전에 설정하고, 사용한 후에는 해제하는 잠금
      • 잠금이 설정되면 다른 스레드는 접근할 수 없음
    2. 세마포어(semaphore)
      • 일반화된 뮤텍스
      • 간단한 정수 값과 wait(P 함수), signal(V 함수)로 공유 자원에 대한 접근을 처리함
        • wait(): 자신의 차례가 올 때까지 기다리는 함수
        • signal(): 다음 프로세스로 순서를 넘겨주는 함수
      • 공유 자원에 접근하면 wait() 작업을 수행하고, 공유 작업을 해제하면 signal() 작업을 수행
    3. 모니터
      • 공유 자원을 숨기고, 해당 접근에 대한 인터페이스만 제공
      • 모니터큐를 통해 공유 자원에 대한 작업들을 순차적 처리