반응형

본 글은  컴퓨터 밑바닥의 비밀  2.7 장 내용을 요약한 내용입니다.

 

블로킹와 논블로킹

  • 함수를 호출할 때 주로 사용됨

함수 A, B가 있다고 가정

함수 A가 B를 호출할 때, 함수 B를 호출함과 동시에 OS가 함수 A가 실행중인 스레드나 프로세스를 일시 중지 시킨다 → 블로킹

그렇지 않으면 → 논-블로킹

  • 블로킹 호출 핵심은 스레드/프로세스가 일시 중지되는 것

모든 함수 호출이 호출자의 스레드를 일시 중지 시키는 것은 아님

int sum(int a, int b)
{
	return a + b;
}

void func()
{
	int r = sum(1, 1);
}
  • 위 코드는 운영체제가 func 함수 호출 시 해당 스레드를 중지시키지 않음. 핵심은 입출력

 

블로킹의 핵심 문제: 입출력

  • 입출력 요청 완료시간은 CPU의 클럭 주파수보다 훨씬 느림
  • 입출력 과정이 실행되는 동안 CPU 제어권을 다른 스레드로 넘겨 다른 작업을 할 수 있도록 해야 함
  • 이후 입출력 작업이 완료되면 다시 CPU제어권을 우리 쓰레드/프로세스로 받아와 다음 작업을 실행할 수 있도록 함

  • CPU 제어권을 상실했다가 되찾는 시간 동안 블로킹되어 일시 중지됨
    • 스레드 A가 입출력 작업을 실행하여 블로킹되면 CPU는 스레드 B에 할당됨
    • 스레드 B가 실행되는 동안 OS는 입출력 작업이 완료된 것을 확인하면 다시 스레드 A에 CPU 할당

 

논블로킹과 비동기 입출력

네트워크 데이터 수신을 예로 설명. 데이터를 수신하는 함수인 recv가 논블로킹이면 이 함수를 호출할 때 OS는 스레드를 일시 중지시키는 대신 recv 함수를 즉시 반환

  • 호출 스레드는 자신의 작업을 계속 진행하고 데이터 수신 작업은 커널이 처리

데이터를 언제 수신했는지 알 수 있는 방법은?

  1. 논블로킹 방식의 recv 함수 외에 결과를 확인하는 함수를 함께 제공하고, 해당 함수를 호출하여 수신된 데이터가 있는지 확인
  2. 데이터가 수신되면 스레드에 메시지나 신호등을 전송하는 알림 작동 방식
  3. recv 함수를 호출 할 때 데이터 수신 처리를 담당하는 함수를 콜백 함수에 담아 매개변수로 전달할 수 있음(recv 함수가 콜백 함수를 지원해야 함)

논블로킹 호출이며, 이런 유형의 입출력 작업을 비동기 입출력 이라고도 함

피자 주문에 비유하기

  • 피자를 직접 주문하러 피자가게에 가서 주문하고 기다려 받는것: 블로킹
  • 피자 전화로 주문 후 받는 것: 논 블로킹
    • 피자 완성됬는지 계속 체크하면? 동기
    • 일반적으로는 비동기

동기와 블로킹

  • 블로킹 호출은 확실한 동기 호출
  • 동기호출은? 블로킹 호출이 아닐 수 있음
    • sum 함수에 대한 호출은 동기이지만 funcA가 sum 함수 호출을 했다고 해서 블로킹되거나 하지 않음
int sum(int a, int b)
{
	return a + b;
}

void func()
{
	int r = sum(1, 1);
}

 

비동기와 논블로킹

네트워크 데이터 수신을 예로 들어 설명

  • 데이터를 수신하는 recv 함수를 논블로킹 호출로 설정하기 위해 flag 값 추가(NON_BLOCKING_FLAG)
void handler(void *buf)
{
	//수신된 네트워크 데이터를 처리
}

while(true)
{
	fd = accept();
	recv(fd, buf, NON_BLOCKING_FLAG, handler); // 호출 후 바로 반환, 논블로킹
}
  • recv 함수는 논블로킹 호출 → 네트워크 데이터를 처리해주는 handler 함수를 콜백 함수로 전달, 즉 비동기이자 논 블로킹

하지만 데이터 도착을 감지하는 전용 함수인 check 함수를 제공해서 아래와 같이 구성한다면,

void handler(void *buf)
{
	//수신된 네트워크 데이터를 처리
}

while(true)
{
	fd = accept();
	recv(fd, buf, NON_BLOCKING_FLAG, handler); // 호출 후 바로 반환, 논블로킹
	
	while (!check(fd))
	{
		// 순환 감지
		;
	}
	
	handler(buf);
}
  • while 문에서 끊임없이 데이터를 도착하기 전까지 체크하기 때문에 그 전에 handler 함수를 사용할 수 없음 ⇒ recv 함수는 논블로킹이지만 전체적인 관점에서 이 코드는 동기

정리하면, 논블로킹이더라도 전체적으로 반드시 비동기라는 의미는 아님

반응형

'OS' 카테고리의 다른 글

[책리뷰] 컴퓨터 밑바닥의 비밀: ch3.1 메모리의 본질, 포인터와 참조  (0) 2024.08.25
Event loop와 coroutine  (0) 2024.08.11
동기/비동기  (0) 2024.08.10
파일 시스템  (0) 2024.07.28
가상 메모리(Virtual memory)  (0) 2024.07.28
반응형

본 글은 책 컴퓨터 밑바닥의 비밀  2.6 장 내용을 요약한 내용입니다.

고된 프로그래머 예시를 통한 동기/비동기 비교

동기 요청(왼쪽)과 비동기 요청(오른쪽)의 예

 
  • 상사가 프로그래머에게 일을 시킬 때, 옆에서 계속 기다리고 있으면 동기, 일을 시키고 다른 일을 하면 비동기라고 예를 들어 설명

또 다른 예로 전화통화와 이메일을 비교해보면,

  • 전화통화 : 상대방이 말할 때 들으며 기다려야 함(동기)
  • 이메일: 작성하는 동안 다른 사람들은 다른 일 처리 가능(비동기)

 

동기 호출

  • 일반적인 함수 호출
funcA()
{
	// funcB 함수가 완료될 때까지 기다림
	funcB();
	
	// funcB 함수는 프로세스를 반환하고 계속 진행
}

funcA와 funcB 함수가 동일한 스레드에서 실행됨

입출력 작업의 경우,

...
read(file, buf); // 여기에서 실행이 중지됨
...
// 파일 읽기가 완료될 때가지 기다렸다가 계속 실행함
  • 최하단 계층은 실제로 system call로 운영체제에 요청을 보냄
    • 파일 읽기 작업을 위해 read 호출 스레드를 일시 중지하고, 커널이 디스크 내용을 읽어 오면 일시 중지 되었던 스레드가 다시 깨어남 (Blocking input/output 이라고 함)

Blocking input/output

 

비동기 호출

  • 디스크의 파일 읽고 쓰기, 네트워크 IO, 데이터 베이스 작업처럼 시간이 많이 걸리는 입출력 작업을 백그라운드 형태로 실행
read(file, buf); // 여기에서 실행이 중지되지 않고 즉시 반환
// 이후 내용의 실행을 블로킹하지 않고 바로 
  • read 함수가 비동기 호출되면 파일 읽기 작업이 완료되지 않은 상태에서도 read 함수는 즉시 반환될 수 있음

  • 호출자가 블로킹 되지 않고 read 함수가 즉시 반환되기 때문에 다음 작업을 실행할 수 있음

 

그럼 비동기 호출에서 파일 읽기 작업이 언제 완료되었는지 어떻게 알 수 있을까? 이 경우, 처리에 대한 2가지 상황이 있을 수 있음

  1. 호출자가 실행 결과를 전혀 신경쓰지 않을 때
  2. 호출자가 실행 결과를 반드시 알아야 할 때

 

1. 호출자가 실행 결과를 전혀 신경쓰지 않을 때

void handler(void* buf)
{
	... // 파일 내용 처리 중
}

read(buf, handler)

"계속해서 파일을 읽고, 작업이 완료되면 전달된 함수(handler)를 이용해 파일을 처리해주세요."
⇒ 파일 내용은 호출자 스레드가 아닌 콜백 함수가 실행되는 다른 스레드(호출되는 스레드) 또는 프로세스 등에서 처리

2. 호출자가 실행 결과를 반드시 알아야 할 때

  • notification 작동 방식을 사용하는 것
    • 작업 실행이 완료되면 호출자에게 작업 완료를 알리는 신호나 메시지를 보내는 것
    • 결과 처리는 이전과 마찬가지로 호출 스레드에서 함

 

웹 서버에서 동기와 비동기 작업

아래의 작업을 한다고 가정

  • A, B, C 세 단계를 거친 후 데이터 베이스를 요청
  • 데이터 베이스 요청 처리가 완료 되면 D, E, F 세 단계를 거침
  • A, B, C, D, E, F 단계에는 입출력 작업이 포함되어 있지 않음
// 사용자 요청을 하는 단계
A;
B;
C;
데이터 베이스 요청;
D;
E;
F;

 

먼저 가장 일반적인 동기 처리 방식

// 메인 스레드
main_thread()
{
	while(1)
	{
		요청 수신;
		A;
		B;
		C;
		데이터베이스 요청을 전송하고 결과가 반환될 때까지 대기;
		D;
		E;
		F;
		결과 반환;
	}
}

// 데이터베이스 스레드
database_thread()
{
	while(1)
	{
		요청 수신;
		데이터베이스 처리;
		결과 반환;
	}
}
  • 데이터 베이스 요청 후 주 스레드가 블로킹 되어 일시 중지됨
  • 데이터 베이스 처리가 완료된 시점에서 D, E, F가 계속 실행됨

  • 주 스레드에서 빈공간은 유휴 시간(idle time)으로 기다리는 과정
  • 유휴 시간을 줄이기 위해서 비동기 작업을 활용할 수 있다

 

1. 주 스레드가 데이터 베이스 처리 결과를 전혀 신경 쓰지 않을 때

  • 주 스레드는 데이터 베이스 처리 완료 여부 상관하지 않음
  • 데이터베이스 스레드가 D, E, F 세 단계를 자체적으로 직접 처리
  • 데이터 베이스 처리 후 DB 스레드가 D, E, F 세 단계를 알 수 있는 방법은?
    • 콜백 함수
void handle_DEF_after_DB_query()
{
	D;
	E;
	F;
}

주 쓰레드가 데이터베이스 처리 요청을 보낼 때 위 함수를 매개변수로 전달

DB_query(request, handle_DEF_after_DB_query);
  • 데이터 베이스 쓰레드는 데이터 베이스 요청을 처리한 후 handle_DEF_after_DB_query 함수 호출하기만 하면 됨

이 함수를 데이터 베이스 쓰레드에 정의하고 직접 호출하는 대신 콜백 함수를 통해 전달받아 실행하는 이유은?

⇒ 소프트웨어 조직 구조 관점에서 볼 때 데이터베이스 쓰레드에서 해야 할 작업이 아니기 때문

 

2. 주 쓰레드가 DB 작업 결과에 관심을 가질 때

  • 알림 작동 방식을 이용하여 작업 결과를 주 스레드로 전송
  • 주 스레드는 사용자 요청의 후반부를 계속 처리
  • 주 스레드에는 유휴시간이 없음
    • 비 동기 호출만큼 극단적으로 효율적이진 않지만 동기 호출에 비하면 여전히 효율적
반응형

'OS' 카테고리의 다른 글

Event loop와 coroutine  (0) 2024.08.11
블로킹/논블로킹  (0) 2024.08.10
파일 시스템  (0) 2024.07.28
가상 메모리(Virtual memory)  (0) 2024.07.28
교착상태(Deadlock)  (0) 2024.07.19
반응형

본 글은 책 "혼자 공부하는 컴퓨터 구조+운영체제" 의 Chapter 15. 파일시스템 부분을 읽고 정리한 내용입니다.

파일

  • 하드 디스크나 SSD 같은 보조기억장치에 저장된 관련 정보의 집합, 관련있는 정보를 모은 논리적 단위

파일을 이루는 정보

  • 이름
  • 파일을 실행하기 위한 정보
  • 파일 관련 부가 정보 (속성, 메타데이터)
    • 파일 형식, 위치, 크기 등
 

파일 속성과 유형

속성 이름  의미
유형 운영체제가 인지하는 파일의 종류 (텍스트, 실행, 음성)
크기 현재크기 & 허용 가능한 최대 크기
보호 어떤 사용자가 파일을 읽고/쓰고/실행할 수 있는지
생성 날짜 ...
마지막 접근 날짜  
마지막 수정 날짜  
생성자  
소유자  
위치  

* 속성 이름 자체가 의미를 담고 있는 내용인 경우는 생략

파일 유형

  • 확장자를 이용해 유형을 나타냄

 

파일 연산을 위한 시스템 호출

  • 파일을 다루는 모든 작업은 운영체제에 의해 이뤄지고 응용프로그램은 파일을 조작할 수 없음

파일 연산을 위한 시스템 호출

  • 생성, 삭제, 열기, 닫기, 읽기 ,쓰기

디렉토리

  • 파일들을 정리하기 위해 사용되고 윈도우에서는 폴더라고 부름

옛 운영체제에서는 하나의 디렉토리만 존재했음 (single-level directory)

 

이후, 여러 계층을 가진 tree-structured directory가 생겨남

최상위 디렉토리(root directory, '/'로 표현) 아래 여러 서브 디렉토리가 있을 수 있음

 

절대 경로와 상대 경로

  • 절대 경로: 루트 디렉토리에서 자기 자신까지 이르는 고유한 경로
    • /home/minchul/a.sh
  • 상대 경로: 현재 디렉토리부터 시작하는 경로
    • 현재 디렉토리 경로가 /home이라면 a.sh의 상대 경로는 minchul/a.sh

디렉토리 연산을 위한 시스템 호출

  • 디렉토리 생성, 삭제, 열기, 닫기, 읽기

디렉토리 엔트리

  • 많은 운영체제에서 디렉토리를 그저 ‘특별한 형태의 파일’로 간주
  • 디렉토리는 보조기억장치에 테이블 형태의 정보로 저장되는데 각각의 행을 디렉토리 엔트리라고 함
파일 이름  위치를 유추할 수 있는 정보
   

파일 시스템에 따라 디렉터리 엔트리에 파일 속성을 명시하는 경우도 있음

파일 이름 위치를 유추할 수 있는 정보 생성 시간 수정된 시간     크기
         

 

예시

 

home 디렉토리는 대략 다음과 같이 구성됨

파일 이름  위치를 유추할 수 있는 정보
.. (상위 디렉토리)  
. (현재 작업 디렉토리)  
minchul  
guest  

minchul 디렉토리 엔트리에는 디렉토리에 포함된 파일들의 이름(a.sh, b.c, c.tar)과 이들의 위치를 알 수 있는 정보들이 포함되어있음

 

파일 시스템

  • 파일과 디렉토리를 보조기억장치에 일목요연하게 저장하고 접근할 수 있게 하는 운영체제 내부 프로그램

파티셔닝과 포매팅

  • 보조기억장치를 사용하려면 파티션을 나누는 작업과 포맷 작업을 거쳐야 함
  • 파티셔닝: 저장 장치의 논리적인 영역을 구획하는 작업 (일종의 칸막이로 영역을 나누는 작업)
    • 파티션: 파티셔닝 작업으로 나눠진 영역 하나하나
  • 포매팅: 파일 시스템을 설정하여 어떤 방식으로 파일을 저장하고 관리할 것인지를 결정하고 새로운 데이터를 쓸 준비를 하는 작업

Windows에서 USB의 파일 시스템 예시

  • 포매팅 할 때 파일 시스템이 결정됨
  • 파일 시스템은 여러 종류가 있고 파티션마다 다른 파일 시스템을 설정할 수도 있음

 

파일 할당 방법

  • 운영체제는 파일과 디렉토리를 블록(block) 단위로 읽고 씀
    • 하나의 파일이 보조기억장치에 저장될 떄 하나 이상의 블록에 걸쳐 저장됨
  • 하드 디스크의 가장 작은 저장단위는 섹터지만 운영체제는 하나 이상의 섹터를 블록이라는 단위로 묶어 관리

  • 하드 디스크내에 여러 블록이 있음

 

파일을 보조기억장치에 할당하는 방법

  1. 연속 할당
  2. 불연속 할당
    • 연결 할당
    • 색인 할당

 

연속 할당(contiguous allocation)

  • 가장 단순한 방식
  • 블록을 3, 2, 5개 차지하는 정도의 크기를 가진 파일 a, b, c가 있다면 아래처럼 할당

  • 연속으로 할당된 파일에 접근하기 위해서는 파일의 첫번째 블록 주소와 블록 단위의 길이만 알면됨

 

  • 연속 할당을 사용하는 파일 시스템에서는 디렉토리에 엔트리에 파일 이름과 더불어 첫번째 블록 주소와 블록 단위의 길이를 명시

장점

  • 구현이 단순

단점

  • 외부 단편화를 야기할 수 있음

 

단점 예시

위의 사진과 같이 파일이 연속할당방식으로 저장된 상황에서 파일 D, F가 삭제 된다면?

  • 블록은 총 11개가 남지만, 크기가 블록 7개 이상을 사용하는 파일은 할당될 수 없음

 

연결 할당(linked allocation)

  • 불연속 할당의 종류
  • 파일을 이루는 데이터를 linked list로 관리

  • 4개의 블록으로 구성된 a라는 파일이 있다고 할 때
  • 10→5→13→2 순으로 블록이 연결되어 있고 2번 블록에는 다음 블록이 없다는 특별한 표시자(-1)를 기록

 

  • 디렉토리 엔트리에는 파일 이름과 함께 첫번째 블록 주소와 블록 단위의 길이를 명시

길이가 아닌 마지막 블록의 주소를 기록할 수도 있음

단점

1. 반드시 첫번째 블록부터 하나씩 차례대로 읽어야 함

  • 파일 내 임의의 위치에 접근하는 속도(random access 속도)가 매우 느려 성능면에서 비효율적

2. 하드웨어 고장이나 오류 발생 시 해당 블록 이후 블록은 접근할 수 없음

  • 하나의 블록 안에 파일 데이터와 다음 블록 주소가 모두 포함되어 있다보니, 파일을 이루는 블록에 하나라도 문제가 발생하면 이후 블록에 접근할 수 없음

 

색인 할당(indexed allocation)

  • 파일의 모든 블록 주소를 색인 블록(index block)이라는 하나의 블록에 모아 관리하는 방식

색인 할당 예시

  • 파일 a의 색인 블록은 4번 블록
  • 파일의 데이터는 7, 13, 11 블록이 저장되어 있다고 가정
  • 파일 a에 순차적으로 접근하고 싶다면 생인 블록에 저장된 주소로 차례대로 접근하면 됨

 

  • 임의의 위치에 접근하고 싶다면 색인 블록의 i번째 항목이 가리키는 블록에 접근하면 됨
  • 디렉토리 엔트리에는 파일 이름과 색인 블록 주소를 명시

색인 할당을 기반으로 만든 파일 시스템이 유닉스 파일 시스템

 

파일 시스템 살펴보기

대표적인 파일 시스템 2가지

  1. FAT 파일 시스템: USB, SD 카드등의 저용량 저장 장치에 사용됨
  2. 유닉스 파일 시스템:유닉스 계열 OS에 사용됨

FAT 파일 시스템 (File Allocation Table)

  • 연결 할당 기반 파일 시스템의 단점을 보완
    • 단점의 근본적인 원인은 블록 안에 다음 블록의 주소를 저장했기 때문

 

각 블록에 포함된 다음 블록의 주소들을 한데 모아 테이블 형태로 관리하면? 파일 할당 테이블(FAT)!

  • 파일의 첫번째 블록 주소(4번블록)만 알면 4→8→3→5 순으로 파일의 데이터가 담긴 모든 블록에 접근할 수 있음
  • 이렇게 FAT을 이용하는 파일 시스템이 바로 FAT 파일 시스템
  • MS-DOS에서 사용되었고 최근까지 저용량 저장 장치용 파일 시스템으로 많이 사용됨
  • 버전에 따라 FAT12, 16, 32가 있으며 숫자는 블록을 표현하는 비트수를 의미

FAT(FAT12) 파일 시스템을 사용하는 파티션 도식도

  • FAT 영역: FAT가 저장되고 파티션의 앞부분에 만들어짐
  • 루트 디렉토리 영역: 루트 디렉토리가 저장되는 영역
  • 데이터 영역: 서브 디렉토리와 파일들을 위한 영역

FAT가 메모리에 캐시될 경우 느린 임의 접근 속도도 개선 가능

FAT파일 시스템의 디렉토리 엔트리

시작 블록을 통해 FAT에서 해당 파일의 내용들에 접근할 수 있음

  • 속성은 읽기전용/숨김 파일/시스템 파일/ 일반 파일/ 디렉토리 등을 식별하기 위한 항목

아래 디렉토리 구조를 이루는 FAT 파일 시스템에서 /home/minchul/a.sh 파일을 읽는 과정

 

/home/minchul/a.sh 에 접근하는 과정

  1. 루트 디렉토리에서 home 디렉토리는 몇번 블록에 있는지 살펴봄 → 3번 블록
  2. 3번 블록을 읽어 home 디렉토리 내용을 살펴보고 minchul 디렉토리가 몇번 블록에 있는지 살펴봄 → 15번
  3. 15번 블록을 읽어 a.sh 파일의 첫번째 블록 주소를 확인 → 9번
  4. FAT을 통해 9→8→11→13 블록 순서로 a.sh 파일 내용에 접근 가능

 

유닉스 파일 시스템

  • 색인 할당 기반의 파일 시스템
  • 색인 블록 == i-node
    • 파일의 속성 정보와 15개의 블록 주소가 저장될 수 있음

  • 파일마다 i-node를 가지고 있고 i-node 마다 고유한 번호가 부여되어있음

 

UNIX 파일 시스템을 사용하는 파티션 도식도

  • 파티션 내에 i-node 영역에 i-node들이 있고 데이터 영역에 디렉토리와 파일들이 있음

 

문제: i-node의 크기는 유한한데 블록을 20개, 30개, 그 이상 차지하는 파일은?

첫째, 블록 주소 중 12개에는 직접 블록 주소를 저장

  • i-node가 가리킬 수 있는 열다섯 개의 블록 주소 중 처음 12개에는 파일 데이터가 저장된 블록 주소를 직접 명시
    • direct block: 파일 데이터가 저장된 블록

 

둘째, ‘첫째’ 내용으로 충분하지 않다면 13번째 주소에 단일 간접 블록 주소를 저장

  • 단일 간접 블록(single indirect block): 파일 데이터가 저장된 블록 주소가 아닌 파일 데이터를 저장한 블록 주소가 저장된 블록을 의미

 

 

셋째, ‘둘째’ 내용으로 충분하지 않다면 14번째 주소에 이중 간접 블록 주소를 저장

  • 이중 간접 블록(double indirect block): 데이터 블록 주소를 저장하는 블록 주소가 저장된 블록

넷째, ‘셋째’ 내용으로 충분하지 않다면 열다섯 번째 주소에 삼중 간접 블록 주소를 저장

삼중 간접 블록: 이중 간접 블록 주소가 저장된 블록

 

unix 파일 시스템의 디렉토리 엔트리도 파일 이름과 i-node 번호로 구성됨

UNIX 파일 시스템의 디레고리 엔트리

 

예시

  • 유닉스 파일 시스템에서 /home/minchul/a.sh 파일을 읽는 과정

 

  • 루트 디렉토리 위치는 루트 디렉토리의 i-node를 통해 확인
    • 유닉스 파일 시스템은 루트 디렉토리의 i-node를 항상 기억하고 있음
    • 2번 i-node가 루트 디렉토리의 i-node라고 가정
  1. i-node 2를 통해 루트 디렉토리 위치 파악 → 1번 블록
  2. 1번 블록을 읽으면 루트 디렉토리 내용을 알 수 있음 → home 디렉토리의 i-node 번호는 3
  3. i-node 3에 접근하여 home의 디렉토리 위치 파악 →210번 블록
  4. 210번 블록을 읽으면 home 디렉토리 내용을 알 수 있음 → minchul의 i-node 번호는 8
  5. i-node 8에 접근하면 minchul 폴더의 위치 파악 → 121번 블록
  6. 121번 블록을 읽으면 minchul 디렉토리 내용을 알 수 있음 → a.sh 파일의 i-node 번호는 9번
  7. i-node 9에는 98, 12, 13 블록이 있음

⇒ 결론적으로 a.sh 파일을 읽기 위해 98→12→13 블록에 접근하면 됨, i-node를 통해 위치 정보를 알고, 블록을 통해 내용을 알 수 있음

 

 

반응형

'OS' 카테고리의 다른 글

블로킹/논블로킹  (0) 2024.08.10
동기/비동기  (0) 2024.08.10
가상 메모리(Virtual memory)  (0) 2024.07.28
교착상태(Deadlock)  (0) 2024.07.19
프로세스 동기화  (0) 2024.07.19
반응형

본 글은 책 "혼자 공부하는 컴퓨터 구조+운영체제" 의 Chapter 14. 가상메모리 부분을 읽고 정리한 내용입니다.

연속 메모리 할당

  • 프로세스에 연속적인 메모리 공간을 할당

스와핑

  • 현재 사용되지 않는 프로세스들을 보조기억장치의 일부 영역으로 쫓아내고 생긴 빈공간에 새로운 프로세스를 적재

  • swap space: 프로세스들이 쫓겨나는 보조기억장치의 일부 영역
  • swap-out: 현재 실행되지 않는 프로세스가 메모리에서 스왑 영역으로 옮겨지는 것
  • swap-in: 스왑 영역에 있던 프로세스가 다시 메모리로 옮겨 오는 것

스와핑을 이용하면 실제 메모리 크기보다 프로세스들이 요구하는 메모리 공간 이 더 크더라도 동시에 실행할 수 있음

 

스왑 영역 크기 확인하기 (mac): sysctl vm.swapusage

$ sysctl vm.swapusage
vm.swapusage: total = 4096.00M  used = 2755.62M  free = 1340.38M  (encrypted)

 

메모리 할당

  • 프로세스는 메모리의 빈 공간에 할당되어야 하는데 여러개라면?
  • 최초 적합, 최적 적합, 최악 적합

  • 빈공간 A, B, C가 있다면?

최초 적합(first-fit)

  • 운영체제가 메모리 내의 빈 공간을 순서대로 검색하다 적재할 수 있는 공간을 발견하면 그 공간에 프로세스를 배치하는 방식
    • 순서대로 검색한다는 의미는? 어디서부터 검색?
  • 검색 최소화, 빠른 할당

최적 적합(best-fit)

  • 운영체제가 빈 공간을 모두 검색해본 뒤, 적재 가능한 가장 작은 공간에 배치

최악 적합(worst fit)

  • 운영체제가 빈 공간을 모두 검색해본 뒤, 적재 가능한 가장 큰 공간에 프로세스를 배치

 

외부 단편화(external fragmentation)

  • 프로세스들이 실행&종료 되길 반복하며 메모리 사이사이에 빈 공간이 발생해서 메모리가 낭비되는 현상

 

외부 단편화를 해결할 수 있는 대표적인 방법들

  1. 메모리 압축
  • 여기저기 흩어져 있는 빈 공간들을 하나로 모으는 방식

단점

  1. 작은 빈 공간들을 하나로 모으는 동안 시스템은 하던 일을 중지해야하고, 메모리에 있는 내용들을 옮기는 작업은 많은 오버헤드를 야기
  2. 프로세스 이동 방법에 대한 방법을 결정하기 어려움
  3. 물리 메모리보다 큰 프로세스 실행 불가

 

페이징을 통한 가상 메모리 관리

가상 메모리

  • 실행하고자 하는 프로그램의 일부만 메모리에 적재하여 실제 물리 메모리 크기보다 더 큰 프로세스를 실행할 수 있게 하는 기술

페이징이란?

  • 프로세스를 일정 크기로 자르고, 이를 메모리에 불연속적으로 할당하는 메모리 관리 기법

  • 논리 주소 공간을 페이지(page)라는 일정 단위로 자르고,
  • 메모리의 물리 주소 공간을 프레임(frame)이라는 페이지와 동일한 일정한 단위로 자른 뒤
  • 페이지를 프레임에 할당하는 가상 메모리 관리 기법

 

페이지 교체와 프레임 할당

요구 페이징(Demand paging)

  • 프로세스를 메모리에 적재할 때 처음부터 모든 페이지를 적재하지 않고 필요한 페이지만을 메모리에 적재하는 기법

기본적인 양상

  1. CPU가 특정 페이지에 접근하는 명령어를 실행
  2. 해당 페이지가 현재 메모리에 있을 경우(유효 비트가 1인 경우) CPU는 페이지가 적재된 프레임에 접근
  3. 해당 페이지가 현재 메모리에 없을 경우(유효비트가 0인 경우) 페이지 폴트가 발생
  4. 페이지 폴트 처리 루틴은 해당 페이지를 메모리에 적재하고 유효 비트를 1로 설정
  5. 다시 위의 1번을 수행

* 순수 요구 페이징(pure demand paging)

  • 아무런 페이지도 메모리에 적재하지 않은 채 무작정 실행부터 하는 기법
  • 프로세스의 첫 명령어를 실행하는 순간부터 페이지 폴트가 계속 발생함
  • 실행에 필요한 페이지가 어느 정도 적재된 이후부터는 페이지 폴트 발생 빈도가 떨어짐

 

요구페이징이 안정적으로 작동하기 위해서는 아래의 2가지가 해결되어야 함

  • 페이지 교체
  • 프레임 할당

 

페이지 교체

  • 요구 페이징 기법으로 페이지들을 적재하다보면 언젠가 메모리가 가득 차게 됨
  • 당장 실행에 필요한 페이지를 적재하려면 페이지를 보조 기억장치로 보내야 하는데 이를 결정하는 방법이 페이지 교체 알고리즘

 

페이지 교체 알고리즘

  • 일반적으로 페이지 폴트가 가장 적제 발생하는 알고리즘을 좋은 알고리즘으로 평가
    • 페이지 폴트가 일어나면 보조기억장치로부터 필요한 페이지를 가져와야하기 때문에 성능 저하발생하기 떄문

페이지 폴트 횟수를 어떻게 알 수 있을까?

⇒ 페이지 참조열 (page reference string) 사용

CPU가 참조하는 페이지들중 연속된 페이지를 생략한 페이지열 (연속된 페이지 참조에서는 페이지 폴트가 발생하지 않기 때문)

예를 들어 아래 숫자들의 페이지 번호들을 CPU가 참조했다면,

2 2 2 3 5 5 5 3 3 7

연속된 페이지를 생략한 페이지 참조열은 아래와 같음

2 3 5 3 7

 

FIFO 페이지 교체 알고리즘

  • First-In, First-Out Page Replacement Algorithm
  • 메모리에 가장 먼저 올라온 페이지부터 내쫓는 방식

  • 페이지가 초기에 적재될 때 발생할 수 있는 페이지 폴트는 고려하지 않음

단점

  • 프로그램 실행 내내 사용될 내용을 포함할 수도 있는데 이런 페이지를 메모리 메모리에 먼저 적재되었다고 내쫓을 수 있음

 

2차 기회(second-chance) 페이지 교체 알고리즘

  • 참조 비트 1인 경우: 한번 더 기회를 주기
    • 참조 비트 0으로 초기화 후 적재 시간을 현재 시간으로 설정
  • 참조 비트 0인 경우: CPU가 참조한 적이 없는 페이지로 바로 내쫓기

 

페이지 3의 참조 비트가 1인 경우

 

페이지 3의 참조비트를 0으로 초기화 후 가장 최근에 적재된 페이지로 간주

 

최적 페이지 교체 알고리즘(Optimal p age replacement algorithm)

  • CPU에 의해 참조되는 횟수를 고려하는 페이지 교체 알고리즘
  • 앞으로 사용빈도가 가장 낮은 페이지를 교체하는 방식
2 3 1 3 5 2 3 4 2 3

  • 첫 페이지 폴트: 페이지 1의 경우 한번 사용된 후 이후에 사용되지 않기 때문에 이를 교체함
  • 두번째 페이지 폴트: 2,3,5 페이지가 적재된 상황에서 4 페이지를 적재해야 할 때, 그 이후 페이지 2, 3이 사용되므로 5를 교체

특징

  • 가장 낮은 페이지 폴트율을 보장하는 알고리즘
  • 앞으로 사용되지 않을 페이지 예측관련해 실제 구현이 어려움
  • 다른 페이지 교체 알고리즘 성능을 평가하기 위한 하한선으로 간주

 

LRU 페이지 교체 알고리즘

  • Least Recently Used Replacement Algorithm
  • 가장 오래 사용되지 않은 페이지 교체

첫 페이지 폴트

  • 페이지 2, 3, 1이 적재된 상태
  • 페이지 5를 적재하려는 상황에서 최근에 페이지 1, 3, 이 적재되었으므로 페이지 2를 교체

두번째 페이지 폴트

  • 페이지 5, 3, 1이 적재된 상태
  • 페이지 2를 적재하려는 상황에서 최근에 페이지 5, 3, 이 적재되었으므로 페이지 1을 교체

세번째 페이지 폴트

  • 페이지 5, 3, 2가 적재된 상태
  • 페이지 4를 적재하려는 상황에서 최근에 페이지 2, 3 이 적재되었으므로 페이지 5를 교체

 

스래싱과 프레임 할당

페이지폴트가 자주 발생하는 이유

  1. 나쁜 페이지 교체 알고리즘
  2. 프로세스가 사용할 수 있는 프레임 자체가 적어서
  • 새로운 페이지를 참조할 때마다 페이지 폴트가 발생 → 저조한 CPU 이용율

 

스래싱(Thrashing)

  • 프로세스가 실제 실행되는 시간보다 페이징에 더 많은 시간을 소요하여 성능이 저해되는 문제

  • 동시에 실행되는 프로세스의 수를 늘린다고 CPU의 이용률이 높아지는 것은 아님

* 멀티프로그래밍의 정도: 메모리에 동시에 실행되는 프로세스의 수

  • 각 프로세스가 필요로 하는 최소한의 프레임 수가 보장되지 않았기 때문에 발생
  • 각 프로세스가 필요로 하는 최소한의 프레임 수를 파악하고 프로세스들에게 적절한 프레임을 할당해주어야 함

 

프레임 할당 방식

  1. 균등 할당(equal allocation)
  • 가장 단순한 할당 방식
  • 모든 프로세스들에게 균등하게 프레임을 할당하는 방식
    • 300개의 프레임을 3개의 프로세스에 할당 시 각 프로세스에 100개의 프레임을 할당
  • 실행되는 프로세스들의 크기를 고려하지 못한 방식

 

2. 비례 할당(proportional allocation)

  • 프로세스의 크기에 비례하여 프레임 할당

참고) 균등할당과 비례 할당은 프로세스의 실행 과정을 고려하지 않고 단순히 프로세스의 크기와 물리 메모리의 크기만을 고령한 방식이라는 점에서 정적 할당 방식이라고 함

 

프로세스를 실행하는 과정에서 배분할 프레임을 결정하는 방식의 종류

  1. 작업 집합 모델(working set model)
  • 작업집합: 실행 중인 프로세스가 일정 시간 동안 참조한 페이지의 집합
  • 프로세스가 일정기간동안 참조한 페이지 집합을 기억하여 빈번한 페이지 교체를 방지
  1. 페이지 폴트 빈도(PFF: Page-Fault Frequency)

아래 2가지 가정에서 생겨난 아이디어

  1. 페이지 폴트율이 너무 높으면 프로세스는 너무 적은 프레임을 갖고 있다
  2. 페이지 폴트율이 너무 낮으면 프로세스는 너무 많은 프레임을 갖고 있다
  • 상한선, 하한선 범위 안에서만 프레임을 할당하는 방식

 

참고

 


1. 책 "혼자 공부하는 컴퓨터 구조+운영체제"
2. 유튜브 "혼자 공부하는 컴퓨터 구조 + 운영체제"

반응형

'OS' 카테고리의 다른 글

동기/비동기  (0) 2024.08.10
파일 시스템  (0) 2024.07.28
교착상태(Deadlock)  (0) 2024.07.19
프로세스 동기화  (0) 2024.07.19
CPU 스케쥴링  (0) 2024.07.16
반응형

본 글은 책 "혼자 공부하는 컴퓨터 구조+운영체제" 의 Chapter 13. 교착상태 부분을 읽고 정리한 내용입니다.

 

교착 상태(Deadlock)란?

  • 일어나지 않을 사건을 기다리며 진행이 멈춰버리는 현상

게임 프로세스는 자원 A를 점유한 채 웹 브라우저 프로세스가 점유하고 있는 자원 B의 사용이 끝나길 기다리고, 웹 브라우저 프로세스는 자원 B를 점유한 채 게임 프로세스의 자원 A 사용이 끝나길 기다리는 상황

 

교착 상태는 아주 다양한 상황에서 발생하고 뮤텍스 락에서도 발생할 수 있음

lock1 = true;
while (lock2 == true); # wait
//임계 구역 진입
lock1 = false;
lock2 = true;
while (lock1 == true); # wait
//임계 구역 진입
lock2 = false;
  • 위 두 코드 모두 while 문에서 교착 상태가 발생 
  • 교착 상태를 해결하기 위해서는
    • 발생한 상황을 정확히 표현하고
    • 일어나는 근본적인 이유에 대해서 알아야 함

 

 

자원 할당 그래프(Resource-allocation graph)

  • 어떤 프로세스가 어떤 자원을 사용하고 있고, 어떤 프로세스가 어떤 자원을 기다리는지 표현하는 간단한 그래프


1. 프로세스는 원으로, 자원의 종류는 사각형으로 표현

 

2. 사용할 수 있는 자원의 개수는 자원 사각형 내에 점으로 표현

 

3. 프로세스가 어떤 자원을 할당받아 사용 중이라면 자원에서 프로세스를 향해 화살표를 표시

 

4. 프로세스가 어떤 자원을 기다리고 있다면 프로세스→자원 방향으로 화살표를 표시

프로세스 D가 CPU 할당을 기다리고 있는 상황

 

교착 상태가 발생한 상황은 자원 할당 그래프가 원의 형태를 띄고 있음

 

교착 상태 발생 조건

상호 배제

  • 한 프로세스가 사용하는 자원을 다른 프로세스가 사용할 수 없는 상태

점유와 대기

  • 자원을 할당 받은 상태에서 다른 자원을 할당받기를 기다리는 상태

비선점

  • 프로세스가 자원을 비선점하고 있기 때문

원형 대기

  • 프로세스가 요청 및 할당 받은 자원이 원의 형태를 이루었기 때문 (circular wait)

 

교착 상태 해결 방법

교착 상태 예방

  • 교착 상태 발생 필요 조건 4가지 중 하나를 충족하지 못하게 하는 방법
    • 상호 배제
    • 점유와 대기
    • 비선점
    • 원형 대기

 

자원의 상호 배제 없애기

  • 모든 자원을 공유 가능하게 만듦
  • 현실가능한 방법은 아님

점유와 대기 없애기

  • 특정 프로세스에 자원을 모두 할당하거나 아예 할당하지 않는 방식으로 배분 → 자원의 활용도를 낮출 수 있는 방식임
  • 많은 자원을 필요로 하는 프로세스가 무한정 기다리게 되는 기아현상을 야기할 수 있음

비선점 조건 없애기

  • 선점이 가능한 자원(ex) CPU)에 한해 효과적이지만 모든 자원이 선점 가능한 것은 아님 (ex) 프린터)

원형 대기 조건 없애기

  • 모든 자원에 번호를 붙이고 오름차순으로 자원을 할당

5번 포크를 든 철학자가 다음에 1번 포크를 들 수 없음

  • 하지만 모든 자원에 번호를 붙이고, 어떤 자원에 어떤 번호를 붙이냐에 따라 활용률이 달라짐

 

교착 상태 회피

  • 교착 상태가 발생하지 않을 정도로만 조심히 자원을 할당하는 방식

교착 상태를 회피하는 방법을 학습하기 위해 아래 용어를 알아야 함

안전 순서열(Safe sequence)

  • 교착 상태 없이 안전하게 프로세스들에 자원을 할당할 수 있는 순서를 의미

안전 상태(Safe state)

  • 안전 순서열대로 프로세스들에 자원을 배분하여 교착 상태가 발생하지 않는 상태

불안전 상태(Unsafe state)

  • 안전 순서열이 없는 상황으로 교착 상태가 발생할 수 있는 위험이 있는 상태

예시

  • 컴퓨터 시스템에 총 12개의 자원이 존재
  • 프로세스 P1, P2, P가 실행중
  • 각각 5개, 2개, 2개의 자원을 할당받아 사용중
  • 운영체제가 프로세스에 배분할 수 있는 자원은 3개 (12-5-2-2) 가 남은 상황
  • P1, P2, P3는 각각 10, 4, 9개의 자원을 요구할 수 있음
프로세스 요구량 현재사용량
P1 10 5
P2 4 2
P3 9 2
  • 할당 가능 자원: 12개
  • 할당한 자원: 9개
  • 남은 자원: 3개

 

위의 경우 안전 순서열이 존재: P2→P1→P3

Q. 안전 순서열은 어떻게 알 수 있나? → 은행원 알고리즘 참고

안전 순서열이 존재하지 않는 경우

  • 위의 예시에서 모든 프로세스가 최대의 자원을 요구하는 상황에 P3에 선뜻 하나의 자원을 내줌
프로세스 요구량 현재사용량
P1 10 5
P2 4 2
P3 9 2->3
  • 할당 가능 자원: 12개
  • 할당한 자원: 10개
  • 남은 자원: 2개

위의 상황에서 남은 자원을 P2에 모두 주면, 남은 자원은 0개

프로세스 요구량 현재사용량
P1 10 5
P2 4 4
P3 9 3
  • 할당 가능 자원: 12개
  • 할당한 자원: 12개
  • 남은 자원: 0개

 

위의 예에서 P2가 모든 자원을 반납해도 P1, P3의 요구량을 모두 채울 수 없으므로 서로가 보유하고 있는 자원만을 바라보며 무한정 기다릴 수 밖에 없음 → 불안전 상태로 교착 상태 발생

교착 상태 검출 후 회복

  • 교착 상태가 검출되면
    • 선점을 통한 회복
    • 프로세스 강제 종료를 통한 회복

선점을 통한 회복

  • 교착 상태가 해결될 때까지 한 프로세스씩 자원을 몰아주는 방식

프로세스 강제 종료를 통한 회복

  • 교착 상태에 놓인 프로세스가 모두 강제 종료(→ 작업 내용을 잃음)
  • 교착 상태가 해결될 때까지 한 프로세스씩 강제 종료(→오버헤드가 있지만 잃는 작업 내용 최소화)

 

참고

 


1. 책 "혼자 공부하는 컴퓨터 구조+운영체제"
2. 유튜브 "혼자 공부하는 컴퓨터 구조 + 운영체제"

 
반응형

'OS' 카테고리의 다른 글

파일 시스템  (0) 2024.07.28
가상 메모리(Virtual memory)  (0) 2024.07.28
프로세스 동기화  (0) 2024.07.19
CPU 스케쥴링  (0) 2024.07.16
프로세스와 스레드  (0) 2024.07.14
반응형

본 글은 책 "혼자 공부하는 컴퓨터 구조+운영체제" 의 Chapter 12. 프로세스 동기화 부분을 읽고 정리한 내용입니다.

 

 

동기화의 의미

  • 동시다발적으로 실행되는 많은 프로세스는 서로 데이터를 주고 받으며 협력하며 실행될 수 있고 이 때 데이터의 일관성을 유지해야 함
  • 프로세스 동기화란 프로세스 사이의 수행 시기를 맞추는 것을 의미하고 아래 2가지를 위헌 동기화가 있음
    • 실행 순서 제어: 프로세스를 올바른 순서대로 실행하기
    • 상호 배제: 동시에 접근해서는 안되는 자원에 하나의 프로세스만 접근하게 하기

 

실행 순서 제어를 위한 동기화

  • book.txt 파일에 2가지 프로세스가 동시에 실행중이라고 가정해보자
    • Writer process
    • Reader process
  • 둘은 무작정 아무 순서대로 실행되어서는 안되고 Writer 실행이 끝난 후 Reader가 실행되어야 함

 

상호 배제를 위한 동기화

  • 공유가 불가능한 자원의 동시 사용을 피하기 위해 사용하는 알고리즘

 

예시 (Bank account problem)
  • 계좌에 10만원이 저축되어 있다고 가정
  • 프로세스 A는 2만원 입금
  • 프로세스 B는 5만원 입금

프로세스 A 실행되는 과정

  1. 계좌의 잔액 읽음
  2. 읽어들인 잔액에 2만원 더함
  3. 더한 값 저장

프로세스 B 실행되는 과정

  1. 계좌의 잔액 읽음
  2. 읽어들인 잔액에 5만원 더함
  3. 더한 값 저장

이때 두 프로세스가 동시에 실행되었다고 가정했을 때 동기화가 제대로 이루어지지 않은 경우 아래와 같이 전혀 엉뚱한 결과가 나올 수 있음

프로세스 A가 끝나지 않은 상황에서 프로세스 B가 시작됨

정상적으로 작동하길 기대되는 예시

한 프로세스가 진행중이면 다른 프로세스는 기다려야 제대로된 결과를 얻을 수 있는 예

 

생산자와 소비자 문제

  • 생산자와 소비자는 ‘총합’이라는 데이터를 공유
  • 생산자는 총합에 1을 더하고, 소비자는 총합에 1을 뺌

 

  • 생산자와 소비자를 동시에 실행했을 때 예상되는건 총합의 값은 초기 상태를 유지하는 것
  • 하지만 직접 코드를 돌려보면 예상치 못한 결과 발생
#include <iostream>
#include <queue>
#include <thread>

void produce();
void consume();

int sum = 0;

int main() {

    std::cout << "초기 합계: " <<  sum << std::endl;
    std::thread producer(produce);
    std::thread consumer(consume);

    producer.join();
    consumer.join();
    
    std::cout << "producer, consumer 스레드 실행 이후 합계: " <<  sum << std::endl;
    
    return 0;
}

void produce() {
    for(int i = 0; i < 100000; i++) {
        sum++;
    }
}

void consume() {
    for(int i = 0; i < 100000; i++) {
        sum--;
    }
}

초기 합계 : 10
producer, consumer 스레드 실행 이후 합계: -13750

  • 이는 생산자 프로세스와 소비자 프로세스가 제대로 동기화되지 않았기 때문
    • ‘총합’ 데이터를 동시에 사용하는데 소비자가 생산자의 작업이 끝나기 전에 총합을 수정했고, 생산자가 소비자의 작업이 끝나기 전에 총합을 수정했기 때문

 

공유 자원과 임계 구역

  • shared resource: 여러 프로세스 혹은 쓰레드가 공유하는 자원
    • 전역 변수, 파일, 입출력 장치, 보조기억장치
  • 임계구역: 동시에 실행하면 문제가 발생하는 자원에 접근하는 코드 영역
  • 두 개 이상의 프로세스가 임계 구역에 진입하고자 하면 둘 중 하나는 대기해야 함

 

Process A가 임계 구역에 진입하면, Process B는 대기해야 함

 

Race condition

  • 임계 구역은 두 개 이상의 프로세스가 동시에 실행되면 안되는 영역이지만 여러 프로세스가 동시에 다발적으로 실행하여 자원의 일관성이 깨지는 경우

Race condition의 근본적인 이유

  • 고급언어(C/C++, Python) → 저급언어로 변환되며 코드가 늘어날 수 있음
  • 이 때 context switching이 발생하면 예상치 못한 결과를 얻을 수 있음

 

 

상호 배제를 위한 동기화는 이런 일이 발생하지 않도록 두 개 이상의 프로세스가 임계 구역에 동시에 접근하지 못하도록 관리하는 것을 의미하며 운영체제는 3가지 원칙하에 해결

  1. progress: 임계 구역에 어떤 프로세스도 진입하지 않았다면 임계 구역에 진입하고자 하는 프로세스는 들어갈 수 있어야 함
  2. Mutual exclusion : 한 프로세스가 임계 구역에 진입했다면 다른 프로세스는 임게 구역에 들어올 수 없음
  3. bounded waiting: 한 프로세스가 임계 구역에 진입하고 싶다면 그 프로세스는 언젠가는 임게 구역에 들어올 수 있어야 함 (무한정 대기 X)

 

동기화 기법

동기화를 위한 대표적인 도구 3가지

  1. 뮤텍스 락
  2. 세마포
  3. 모니터

 

뮤텍스 락(Mutex lock)

  • Mutual EXclusion lock, 상호 배제를 위한 동기화 도구로 기능을 코드로 구현한 것
  • 매우 단순한 형태는 하나의 전역 변수와 두개의 함수
    • 자물쇠 역할: 프로세스들이 공유하는 전역 변수 lock
    • 임계구역을 잠그는 역할: acquire 함수
    • 임계 구역의 잠금을 해제하는 역할: release 함수

 

acquire 함수

  • 프로세스가 임계 구역에 진입하기 전에 호출하는 함수
  • 임계 구역이 잠겨있으면 열릴 때까지(lock이 false가 될 때까지) 임계 구역을 반복적으로 확인
  • 임계 구역이 열려있으면 임계 구역을 잠그는 함수

release 함수

  • 임계 구역에서의 작업이 끝나고 호출하는 함수
acquire(){
    while (lock ==true) # 반복해서 확인하는 대기 방식을 busy wait이라고 함
        ;               # 임계 구역이 잠겨 있는지 반복적으로 확인
    lock = true;        # 만약 임계 구역이 잠겨 있지 않다면 임계 구역 잠금
}

release() {
    lock = false; # 임계 구역 작업이 끝나서 잠금 해제
}

 

acquire();
// 임계구역 (ex) '총합' 변수 접근)
release();

 

 

세마포(Semaphore)

  • 사전적 의미: 수기 신호

 

  • 뮤텍스 락과 비슷하지만 좀 더 일반화된 방식의 동기화 도구
  • 공유 자원이 여러개 있는 상황에서도 적용이 가능한 동기화 도구

 

  • 단순한 형태로 하나의 변수와 두개의 함수로 구현가능
    • 임계 구역에 진입할 수 있는 프로세스의 개수를 나타내는 전역 변수 S
    • 임계 구역에 들어가도 좋은지 기다려야 할지를 아려주는 wait 함수
    • 이제 가도 좋다는 신호를 주는 signal 함수
wait()
{
    while( S <= 0) # 임계 구역에 진입할 수 있는 프로세스가 0개 이하라면
    ;              # 사용할 수 있는 자원이 있는지 반복적으로 확인하고
    S--;           # 진입할 수 있는 프로세스 개수가 1개 이상이면 S를 1 감소시키고 진입
}

signal()
{
    S++;           # 임계 구역에서의 작업을 마친 뒤 S를 1 증가 시킴
}
wait()
// 임계 구역
signal()

 

예시

세 개의 프로세스 P1, P2, P3가 2개의 공유 자원(S=2)에 순서대로 접근한다고 가정하면,

  1. 프로세스 P1 wait 호출, S는 2→1로 감소
  2. 프로세스 P2 wait 호출, S는 1→0으로 감소
  3. 프로세스 P3 wait 호출, S는 현재 0이므로 무한히 반복하며 S확인
  4. 프로세스 P1 임계 구역 작업 종료, signal 호출, S는 0→1
  5. 프로세스 P3에서 S가 1이 됨을 확인하고 S 1→0 만들고 임계 구역 진입

 

위의 3 과정에서 busy wait 반복하며 확인하며 CPU 사이클이 낭비됨
* busy wait: 쉴새없이 반복하며 확인해보며 기다리는 과정

해결 방법

  1. 사용할 수 있는 자원이 없을 경우 대기 상태로 만듦 (해당 프로세스의 PCB를 waiting queue에 삽입)
  2. 사용할 수 있는 자원이 생겼을 경우 대기 큐의 프로세스를 준비 상태로 만듦 (해당 프로세스의 PCB를 waiting queue → ready queue 로 이동)
wait()
{
    S--;
    if ( S < 0) {
        add this process to waiting queue;
        sleep();
	}
}

signal()
{
    S++;
    if (S<=0) 
    # remove a process p from waiting Queue;
    wakeup(p); # waiting queue -> ready queue
  }
}

프로세스 4개이고, 공유 자원이 2개라고 가정해보면,

  1. 프로세스 P1 wait 호출, S는 2→1로 감소
  2. 프로세스 P2 wait 호출, S는 1→0으로 감소
  3. 프로세스 P3 wait 호출, S는 0→-1이 되고 waiting queue로 이동
  4. 프로세스 P4 wait 호출, S는 -1→-2가 되고 waiting queue로 이동
  5. 프로세스 P1 임계 구역 작업 종료, signal 호출, S는 -2→-1이 되고 PCB를 waiting queue → ready queue로 이동
  6. 프로세스 P2 임계 구역 작업 종료, signal 호출, S는 -1→0이 되고, PCB를 waiting queue → ready queue로 이동

 

세마포는 프로세스의 실행순서 동기화도 지원

  • 변수 S를 0으로 두고 먼저 실행할 프로세스 뒤에 signal 함수, 다음에 실행할 프로세스 앞에 wait함수를 붙이면 됨
P1 P2
  wait()
// 임계 구역 // 임계 구역
signal()  
  • 위의 경우 프로세스의 실행 순서에 상곤없이 P1→P2 순으로 임계 구역에 진입

 

모니터(Monitor)

  • 세마포는 훌륭한 프로세스 동기화 도구지만 매번 임계 구역 앞뒤로 wait & signal 함수를 명시해야 함
  • 모니터는 사용자(개발자)가 다루기에 편한 동기화 도구

상호 배제를 위한 동기화

  • 공유자원과 공유 자원에 접근하기 위한 인터페이스를 묶어서 관리
  • 프로세스는 반드시 인터페이스를 통해서만 공유 자원에 접근하도록 함

공유자원에는 하나의 프로세스만 접근

 

실행 순서 제어를 위한 동기화

  • 내부적으로 조건 변수(condition variable)를 이용
  • 프로세스나 스레드의 실행 순서를 제어하기 위해 사용하는 특별한 변수

 

조건 변수별 queue가 있고 이를 통해 실행 순서를 결정할 수 있음

  • 조건 변수로 wait과 signal 연산을 수행할 수 있음
    • 조건변수.wait(): 대기 상태로 변경, 조건 변수에 대한 큐에 삽입

 

  • 조건변수.signal(): wait()으로 대기 상태로 접어든 조건 변수를 실행상태로 변경

 

모니터 안에는 하나의 프로세스만 있을 수 있음

  • wait()을 호출했던 프로세스는 signal()을 호출한 프로세스가 모니터를 떠난 뒤에 수행을 재개
  • signal()을 호출한 프로세스의 실행을 일시 중단하고 자신이 실행된 뒤 다시 signal()을 호출한 프로세스의 수행을 재개

 

참고

 


1. 책 "혼자 공부하는 컴퓨터 구조+운영체제"
2. 유튜브 "혼자 공부하는 컴퓨터 구조 + 운영체제"

반응형

'OS' 카테고리의 다른 글

가상 메모리(Virtual memory)  (0) 2024.07.28
교착상태(Deadlock)  (0) 2024.07.19
CPU 스케쥴링  (0) 2024.07.16
프로세스와 스레드  (0) 2024.07.14
운영체제를 공부해야하는 이유와 커널  (0) 2024.07.14
반응형

본 글은 책 "혼자 공부하는 컴퓨터 구조+운영체제" 의 Chapter 11. CPU 스케쥴링 부분을 읽고 정리한 내용입니다.

CPU 스케쥴링이란?

  • 프로세스들에게 공정하게 합리적으로 CPU 자원을 배분하는 것
  • 제대로 안되면 반드시 실행되어야 할 프로세스들이 실행되지 못하거나 당장 급하지 않은 프로세스들만 주로 실행되는 등 무질서한 상태가 발생할 수도 있음

 

프로세스 우선순위

  • 아주 단순하게 생각해 봤을 때 CPU를 사용 요청을 먼저 보낸 프로세스들을 순서대로 CPU 이용하게 하는 방법
  • 합리적인 방식같지만 좋은 방식은 아님
    • 단순히 FIFO 형태로 CPU를 이용하면 비효율적으로 대기 시간이 길 수 있음
    • 또한 프로세스 마다 우선순위가 다르기 때문

 

일반적인 프로세스가 실행되는 과정

  • 대부분의 프로세스들은 CPU와 입출력장치를 모두 사용하며 실행됨 → 실행/대기 상태 반복
    • 예를들어 워드 프로세서에서 CPU를 이용해 명령어 실행, 사용자로부터 입력받은 내용을 보조기억장치에 저장, CPU를 사용하여 명령어를 실행, 사용자가 입력한 내용을 화면에 출력
 

 

  • 프로세스 종류마다 입출력장치를 이용하는 시간과 CPU를 이용하는 시간의 양에 차이가 있음
    • I/O bound process: 비디오 재생이나 디스크 백업 작업을 담당하는 프로세스와 같이 입출력 작업이 많은 프로세스 (실행상태보다 입출력을 위한 대기 상태에 더 많이 머무름)
    • CPU bound process: 복잡한 수학 연산, 컴파일, 그래픽 처리 작업을 담당하는 프로세스와 같이 CPU 작업이 많은 프로세스 (대기 상태보다 실행 상태에 더 많이 머무름)

 

  • I/O bound process와 CPU bound process가 동시에 CPU 자원을 요구한 경우
    • I/O bound process를 빨리 실행시켜 입출력 장치를 끊임없이 작동시켜 대기 상태가 되면 CPU bound process에 집중적으로 CPU를 할당하는 것이 더 효율적
      ⇒ 이를 위해 운영체제는 프로세스마다 priority를 부여 (PCB에 명시)

실행순서: PID 123 → PID 456 → PID 012

참고) 프로세스의 우선순위를 확인할 수 있는 명령어

$ ps -el
  UID   PID  PPID        F CPU PRI NI       SZ    RSS WCHAN     S             ADDR TTY           TIME CMD
    0     1     0     4004   0  46  0 411466656  19712 -      Rs                  0 ??        12:30.24 /sbin/launchd
    0   146     1     4004   0  31  0 410161104   1776 -      Ss                  0 ??         0:00.01 /usr/libexec/iou
    0   298     1     4004   0  31  0 410536368  41248 -      Ss                  0 ??        10:18.55 /usr/libexec/log
    0   299     1     4004   0   4  0 410397920   2368 -      Ss                  0 ??         0:00.22 /usr/libexec/smd
    0   300     1     4004   0  31  0 410401168   8080 -      Ss                  0 ??         0:53.46 /usr/libexec/Use
    0   302     1     4004   0  20  0 410160192   1024 -      Ss                  0 ??         0:05.09 /System/Library/
    0   303     1  1004004   0  50  0 410516816   7200 -      Ss                  0 ??         7:27.37 /System/Library/
  • PRI 컬럼이 process 의 priority 우선순위를 의미

 

스케쥴링 큐

  • PCB에 우선순위가 적혀 있다고 하지만 CPU를 사용할 다음 프로세스를 찾기 위해 운영체제가 모든 프로세스의 PCB를 순회하는건 비효율적
  • 또한 새로운 프로세스들이 언제든 생길 수 있음

⇒ 스케쥴링 큐를 구현해 모든 프로세스들을 줄세워 관리
* 자료 구조 관점에서 큐는 FIFO이지만 스케쥴링 큐는 반드시 FIFO일 필요는 없음

크게 Ready queue와 Waiting queue가 있음

  • Ready queue: CPU를 이용하고 싶은 프로세스들이 서는 줄
  • Waiting queue: 입출력장치를 이용하기 위해 대기 상태에 접어든 프로세스들이 서는 줄

  • 준비 상태에 있는 프로세스들의 PCB는 준비 큐의 마지막에 삽입 되어 CPU를 사용할 차례를 기다림
  • 운영체제는 큐에 삽입된 순서대로 프로세스들을 하나씩 꺼내어 실행하되, 그 중 우선순위가 높은 프로세스를 먼저 실행

 

  • 대기 상태에 있는 프로세스도 같은 장치를 요구한 프로세스들은 같은 대기 큐에서 기다림
  • 입출력이 완료되어 완료 인터럽트가 발생하면 운영체제는 대기 큐에서 작업이 완료된 PCB를 찾아 준비 상태로 변경한 뒤 waiting queue에서 제거하고 ready queue로 이동

 

선점형과 비선점형 스케쥴링

  • 선점: ‘남보다 앞서서 차지함’을 의미

선점형 스케쥴링(preemptive scheduling)

  • 프로세스가 CPU를 비롯한 자원을 사용하고 있더라도 운영체제가 프로세스로부터 자원을 강제로 빼앗아 다른 프로세스에 할당할 수 있는 스케쥴링 방식
  • 프로세스마다 정해진 시간만큼 CPU를 사용하고 정해진 시간을 모두 소비하여 타이머 인터럽트가 발생하면 운영체제가 해당 프로세스로부터 CPU자원을 뺏은 후 다음 프로세스에 할당하는 방식

장점 & 단점

  • 장점: 더 급한 프로세스가 언제든 끼어들어 사용할 수 있고 자원을 골고루 배분할 수 있음
  • 단점: context switch 과정에서 오버헤드 발생

비선점형 스케쥴링(Non-preemptive scheduling)

  • 하나의 프로세스가 자원을 사용하고 있으면 그 프로세스가 종료되거나 스스로 대기 상태에 접어들기 전까지 다른 프로세스가 끼어들 수 없는 스케쥴링 방식을 의미 → 하나의 프로세스가 자원 사용을 독점

대부분의 운영체제는 선점형 스케쥴링 방식을 사용

 

CPU 스케쥴링 알고리즘

스케쥴링 알고리즘의 종류

선입 선처리 스케쥴링 (FCFS 스케쥴링)

  • First Come First Served Scheduling 으로 단순히 준비 큐에 삽입된 순서대로 프로세스들을 처리하는 비선점형 스케쥴링 방식
  • 공정해 보이지만 때때로 프로세스들이 기다리는 시간이 매우 길어질 수 있음

 

프로세스 A, B, C가 순서대로 준비 큐에 삽입된 상태

  • 프로세스 C는 2ms동안 CPU에서 실행되기 위해 22ms라는 긴 시간을 기다려야 함 ⇒ convoy effect (호위효과)

convoy effect를 막기 위해서는 단순히 실행시간이 짧은 프로세스를 먼저 실행하면 됨 ⇒ 최단 작업 우선 스케쥴링

최단 작업 우선 스케쥴링 (Shortest Job First Scheduling)

  • 기본적으로 비선점형 스케쥴링 알고리즘으로 분류되지만, 선점형으로 구현될 수도 있음

 

라운드 로빈 스케쥴링(Round robin scheduling)

  • 선입 선처리 스케쥴링에 타임 슬라이스라는 개념이 더해진 스케쥴링 방식

* 타임 슬라이스: 각 프로세스가 CPU를 사용할 수 있는 정해진 시간을 의미

  • 큐에 삽입된 순서대로 프로세스들을 실행하되 정해진 시간만큼 사용
  • 정해진 시간 내에 프로세스가 완료되지 못하면 다시 큐의 맨 뒤로 삽입됨

 

예시

  • ready queue에 process A, B, C 순서로 삽입되어 있는 경우
  • 타임슬라이스: 4ms

  • 타임 슬라이스의 크기가 중요
    • 크면 들어온 순서대로 process를 처리하기 때문에 선입 선처리 스케쥴링과 차이가 없음
    • 작으면 context switch가 자주 발생해 CPU에 부담

 

최소 잔여 시간 우선 스케쥴링 (Shortest Remaining Time)

  • 최단 작업 우선 스케쥴링 + 라운드 로빈 스케쥴링
  • 정해진 시간만큼 CPU를 이용하되, 다음으로 CPU를 사용할 프로세스는 남은 작업 시간이 가장 적은 프로세스를 선택

 

우선순위 스케쥴링(Priority scheduling)

  • 프로세스들에 우선순위를 부여하고 높은 우선순위를 가진 프로세스부터 실행하는 스케쥴링 알고리즘
    • 우선순위가 같은 프로세스들은 선입 선처리로 스케쥴링

 

발생할 수 있는 문제

Starvation 현상: 우선순위가 높은 프로세스를 우선하여 처리하는 방식이기에 우선순위가 낮은 프로세스는 ready queue에 먼저 삽입되었음에도 불구하고 계속해서 실행이 연기될 수 있음

⇒ 방지하기 위한 대표적인 기법으로 에이징(aging)이 있음

  • 오랫동안 대기한 프로세스의 우선순위를 점차 높이는 방식

 

다단계 큐 스케쥴링 (multilevel queue scheduling)

  • 우선순위 스케쥴링의 발전된 형태로 우선순위별로 준비 큐를 여러 개 사용하는 스케쥴링 방식
  • 우선순위가 가장 높은 큐에 있는 프로세스들을 먼저 처리하고, 우선순위가 가장 높은 큐가 비어있으면 그 다음 우선순위 큐에 있는 프로세스들을 처리

  • 큐별로 다양한 스케쥴링을 적용해서 효율적으로 처리 가능
  • 단점은 기본적으로 프로세스가 큐 간의 이동이 안되서 우선순위가 낮은 큐에 있는 프로세스는 실행되지 않는 starvation 현상이 발생할 수 있음

 

다단계 피드백 큐 스케쥴링 (multilevel feedback queue scheduling)

  • 큐 간의 이동이 가능한 다단계 큐 스케쥴링
  • 우선순위가 가장 높은 큐에 삽입되어 프로세스가 실행되다가 타임 슬라이스 동안 실행을 다 못 끝내면

  • 다음 우선순위 큐에 삽입되고, 결국 CPU를 오래 사용해야 하는 프로세스는 점차 우선순위가 낮아짐

  • 즉 CPU-bound process는 자연스레 우선순위가 낮아지고, I/O Bound process는 자연스레 우선순위가 높은 큐에서 실행이 끝남
  • 낮은 우선순위 큐에서 너무 오래 기다리는 프로세스는 에이징 기법을 적용해 starvation 현상을 예방할 수 있음
  • 구현이 복잡하지만 CPU 스케쥴링의 가장 일반적인 알고리즘 형태로 알려짐
 

 

참고

 


1. 책 "혼자 공부하는 컴퓨터 구조+운영체제"
2. 유튜브 "혼자 공부하는 컴퓨터 구조 + 운영체제"

반응형

'OS' 카테고리의 다른 글

교착상태(Deadlock)  (0) 2024.07.19
프로세스 동기화  (0) 2024.07.19
프로세스와 스레드  (0) 2024.07.14
운영체제를 공부해야하는 이유와 커널  (0) 2024.07.14
캐시메모리  (0) 2024.07.07
반응형

본 글은 책 "혼자 공부하는 컴퓨터 구조+운영체제" 의 Chapter 10. 프로세스와 스레드 부분을 읽고 정리한 내용입니다.

10-1. 프로세스 개요

  • 실행중인 프로그램
  • 실행되기 전까지는 보조기억장치에 있는 데이터 덩어리일 뿐이지만, 메모리에 적재되고 실행되는 순간 프로세스가 됨

ps 명령어로 프로세스 직접 확인하기

  • ps: process status
$ ps -ef        
  UID   PID  PPID   C STIME   TTY           TIME CMD
    0     1     0   0 Tue11PM ??         3:49.05 /sbin/launchd
    0   298     1   0 Tue11PM ??         2:45.95 /usr/libexec/logd
    0   299     1   0 Tue11PM ??         0:00.11 /usr/libexec/smd
    0   300     1   0 Tue11PM ??         0:09.75 /usr/libexec/UserEventAgent (System)

  • -e: 커널 프로세스를 제외한 모든 프로세스를 출력
  • -f: 출력을 풀 포맷으로 표기 (유닉스 스타일) UID, PID , PPID 등이 함께 표시
    • UID: Process owner의 user id

 

프로세스 종류

  • 포그라운드 프로세스(Foreground process): 사용자가 보는 앞에서 실행되는 프로세스
  • 백그라운드 프로세스(Background process): 뒷편에서 실행되는 프로세스로 사용자와 직접 상호작용 할 수있는 백그라운드 프로세스도 있지만 묵묵히 정해진 일만 수행하는 백그라운드 프로세스도 있음
    • daemon: 유닉스 체계의 운영체제에서 부르는 백그라운드 프로세스
    • service: windows 에서 부르는 백그라운드 프로세스

 

프로세스 제어 블록

  • 프로세스들은 차례대로 돌아가며 한정된 시간만큼만 CPU를 이용
  • 시간이 끝났음을 알리는 인터럽트(타이머 인터럽트)가 발생하면 프로세스 자신의 차례를 양보하고 다음 차례가 올때까지 기다림

프로세스 제어 블록 (Process Control Block, PCB)

  • 프로세스와 관련된 정보를 저장하는 자료구조 like 상품에 달린 태그 (커널 영역에 생성됨)
  • 운영체제는 빠르게 번갈아 수행되는 프로세스의 실행 순서를 관리하고 CPU등의 자원을 분배하기 위해 이용
  • 프로세스 생성 시에 만들어지고 실행이 끝나면 폐기됨

PCB에 담기는 정보

  1. Process ID (PID)
    1. 특정 프로세스를 식별하기 위해 부여하는 고유한 번호
  2. 레지스터 값
    1. 프로세스는 자신의 실행차례가 돌아오면 이전까지 사용했던 레지스터의 중간값들을 모두 복원해서 이전까지 진행했던 작업들을 그대로 이어 실행. (프로그램 카운터, 스택 포인터)
  3. 프로세스 상태
    1. 입출력장치를 사용하기 위해 기다리는 상태인지, CPU 사용을 위해 기다리는 상태인지, CPU 이용중인지 등
  4. CPU 스케쥴링 정보
    1. 언제, 어떤 순서로 CPU를 할당받았는지에 대한 정보
  5. 메모리 관리 정보
    1. 프로세스가 어느 주소에 저장되어 있는지에 대한 정보
    2. 베이스 레지스터, 한계 레지스터 값, 페이지 테이블 정보(14장)
  6. 사용한 파일과 입출력장치 목록

 

문맥 교환(Context Switch)

  • 한 프로세스 → 다른 프로세스로 실행 순서가 넘어가면?

문맥: 하나의 프로세스 수행을 재개하기 위해 기억해야 할 정보로 PCB에 표현되어있음

⇒ 기존의 실행중인 context를 백업하고 새로운 프로세스 실행을 위해 context를 복구하는 과정

  • 여러 프로세스가 끊임없이 빠르게 번갈아가며 실행되는 원리

 

프로세스의 메모리 영역

  • 프로세스가 생성되면 커널 영역에 PCB가 생성됨
  • 사용자 영역에는?
    • 코드 영역
    • 데이터 영역
    • 힙 영역
    • 스택 영역

코드 영역 (code segment)

  • 텍스트 영역이라고도 부르고 기계어로 이루어진 명령어가 저장됨
  • 데이터가 아닌 CPU가 실행할 명령어가 담겨 있기 때문에 쓰기가 금지됨 (read-only)

데이터 영역

  • 프로그램이 실행되는 동안 유지할 데이터가 저장되는 공간
  • EX) 전역 변수 (Global variable)

코드 영역과 데이터 영역은 크기가 고정된 영역이라는 점에서 정적 할당 영역이라고도 부름

  • 동적 할당 영역: 힙 & 스택 영역으로 프로세스 실행 과정에서 그 크기가 변할 수 있는 영역

힙 영역 (heap segment)

  • 프로그래머가 직접 할당할 수 있는 저장 공간
  • 프로그래밍 과정에서 메모리 공간을 할당했다면 언젠가는 반환해야하는 공간
  • 반환하지 않으면 메모리 내에 계속 남아 메모리 낭비를 초래 ⇒ memory leak

스택 영역 (Stack segment)

  • 데이터를 일시적으로 저장하는 공간
  • 데이터 영역에 담기는 값과 달리 잠깐 쓰다가 말 값들이 저장되는 공간 (ex) 매개변수, 지역변수)

힙 영역과 스택 영역은 실시간으로 그 크기가 변할 수 있기 때문에 동적 할당 영역이라고 부름

  • 힙: 낮은 주소 → 높은 주소
  • 스택: 높은 주소 → 낮은 주소

 

10-2. 프로세스 상태와 계층 구조

프로세스 상태

  • 프로세스는 여러 상태를 거치며 실행됨

생성 상태 (new)

  • 프로세스를 생성 중인 상태
  • 이제 막 메모리에 적재되어 PCB를 할당받은 상태
  • 생성 상태를 거쳐 실행할 준비가 완료된 프로세스는 곧바로 실행되지 않고 준비 상태가 되어 CPU 할당을 기다림

준비 상태 (ready)

  • 당장이라도 CPU를 할당받아 실행할 수 있지만, 아직 차례가 아니라 기다리고 있는 상태

실행 상태 (running)

  • CPU를 할당받아 실행중인 상태
  • 할당된 일정 시간 동안만 CPU를 사용할 수 있음
  • 할당된 시간을 모두 사용한다면(타이머 인터럽트 발생) 다시 준비 상태가 되고, 실행 도중 입출력 장치를 사용하여 입출력 장치의 작업이 끝날 때까지 기다려야 한다면 대기 상태가 됨
  • 디스패치(dispatch): 준비 상태 → 실행 상태 로 프로세스가 전환되는 것

대기 상태 (blocked)

  • 입출력 작업을 요청한 프로세스가 입출력장치의 작업을 기다리는 상태로 완료되면 다시 준비 상태로 CPU 할당을 기다림

종료(terminated)

  • 프로세스가 종료된 상태로 운영체제는 PCB와 프로세스가 사용한 메모리를 정리

프로세스 상태 다이어그램

 

프로세스 계층 구조

  • 프로세스는 실행 도중 시스템 호출을 통해 다른 프로세스를 생성할 수 있음
    • 부모 프로세스(Parent process) : 새 프로세스를 생성한 프로세스
    • 자식 프로세스(child process) : 부모 프로세스에 의해 생성된 프로세스
    • 둘은 각자 다른 PID를 가지고 일부 운영체제에서는 자식 프로세스의 PCB에 부모 프로세스의 PID인 PPID가 기록되기도 함

 

프로세스 생성 기법

fork: 자기 자신 프로세스의 복사본을 만드는 시스템 호출

  • 부모 프로세스는 fork 시스템 호출을 통해 자신의 복사본을 자식 프로세스로 생성
  • 부모 프로세스의 자원들 (메모리 내의 내용, 열린 파일 목록) 이 상속됨
    • PID값이나 저장된 메모리 위치는 다름

exec: 새로운 프로그램 내용으로 전환하여 실행하는 시스템 호출

  • 메모리 공간을 새로운 프로그램으로 덮어씀
  • 코드/데이터 영역의 내용이 실행할 프로그램의 내용으로 바뀌고 나머지 영역은 초기화됨

from multiprocessing import Process
import os

def foo():
    print('child process', os.getpid())
    print('my parent is', os.getppid())
  
if __name__ == '__main__':
    print('parent process', os.getpid())
    child = Process(target=foo).start()

parent process 6488
child process 27724
my parent is 6488

 

10-3. 스레드(Thread)

프로세스와 스레드

  • 전통적인 관점에서 하나의 프로세스는 한번에 하나의 일만 처리 → 단일 스레드 프로세스

  • 스레드라는 개념이 도입되면서 하나의 프로세스가 한번에 여러 일을 동시에 처리 → 멀티스레드 프로세스

  • 스레드는 프로세스의 자원을 공유함
    • 실행에 필요한 최소한의 정보(프로그램 카운터를 포함한 레지스터, 스택)만을 쓰레드마다 가지며 다른 코드를 실행하고 나머지 힙/데이터 영역은 공유

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

동일한 작업을 수행하는 단일 스레드 프로세스 여러개 실행 vs 하나의 프로세스를 여러 스레드로 실행하면 어떤 차이가?

  • 프로세스끼리는 기본적으로 자원을 공유하지 않음

 

  • 쓰레드끼리는 같은 프로세스 내 자원을 공유 → 협력과 통신에 유리
    • 코드, 데이터, 힙, 파일
    • 때로는 쓰레드가 공유하는 자원에서 문제가 발생해서 다른 쓰레드들에도 영향을 미칠 수 있음쓰레드끼리는 같은 프로세스 내 자원을 공유 → 협력과 통신에 유리

import threading
import os

def foo():
    print('thread id', threading.get_native_id())
    print('process id', os.getpid())

if __name__ == '__main__':
    print('process id', os.getpid())
    thread1 = threading.Thread(target=foo).start()
    thread2 = threading.Thread(target=foo).start()
    thread3 = threading.Thread(target=foo).start()

process id 11912
thread id 20044
process id 11912
thread id 26056
process id 11912
thread id 21764
process id 11912

  • thread의 id는 다르지만 모든 pid는 같음
  •  
 

 

참고

 


1. 책 "혼자 공부하는 컴퓨터 구조+운영체제"
2. 유튜브 "혼자 공부하는 컴퓨터 구조 + 운영체제"
 

반응형

'OS' 카테고리의 다른 글

프로세스 동기화  (0) 2024.07.19
CPU 스케쥴링  (0) 2024.07.16
운영체제를 공부해야하는 이유와 커널  (0) 2024.07.14
캐시메모리  (0) 2024.07.07
메모리  (0) 2024.07.05
반응형

본 글은 책 "혼자 공부하는 컴퓨터 구조+운영체제" 의 "Chapter 9. 운영체제 시작하기"을 읽고 정리한 내용입니다.

 

09-1 운영체제를 알아야 하는 이유

운영체제란

  • 모든 프로그램은 하드웨어가 필요
    • 1+2 계산하는 프로그램은 CPU필요
    • 이미지를 하드 디스크에 저장하는 프로그램은 하드 디스크 필요
  • (시스템) 자원: 프로그램 실행에 마땅히 필요한 요소들로 CPU, 메모리, 보조기억 장치 등이 있음
  • 운영체제(Operating System): 실행할 프로그램에 필요한 자원을 할당하고 프로그램이 올바르게 실행되도록 돕는 특별한 프로그램
  • 운영체제는 매우 특별한 프로그램이기 때문에 항상 컴퓨터가 부팅될 때 메모리 내 커널 영역(kernel space)이라는 공간에 따로 적재되어 실행됨
  • 사용자 영역 (user space): 사용자가 이용하는 응용 프로그램이 적재되는 영역

위의 프로그램들은 누가 메모리에 적재했을까? → 운영체제!

 

응용 프로그램이 실행되려면 CPU가 필요한데 운영체제가 관리함

  • 최대한 공정하게 여러 프로그램에 CPU 자원을 할당

⇒ 운영체제는 응용 프로그램과 하드웨어 사이에서 응용 프로그램에 필요한 자원을 할당하고, 응용 프로그램이 올바르게 실행되도록 관리하는 역할

결론적으로 운영체제를 알면 운영체제를 통해 하드웨어의 상태나 코드의 실행 방식을 알 수 있고 결과적으로 하드웨어와 프로그램을 더 깊이 이해할 수 있음

09-2. 운영체제의 큰 그림

운영체제의 심장, 커널

  • 운영체제의 핵심 서비스를 담당하는 부분

운영체제에는 속하는데 커널에는 속하지 않는 기능은 User Interface

  • GUI
  • CLI

이중모드와 시스템 호출

  • 사용자가 실행하는 프로그램은 자원에 직접 접근할 수 있을까? NO! 자원에 직접 접근은 위험

  • 자원이 무질서하게 관리되고, 응용 프로그램이 조금만 실수해도 컴퓨터 전체에 큰 악영향을 끼칠 수 있음

 

  • 운영체제는 응용 프로그램들이 자원에 접근하려고 할 때 오직 운영체제 자신을 통해서만 접근하도록 하여 자원을 보호

  • 응용 프로그램이 자원에 접근하기 위해서 운영체제에 도움을 요청해야 함
    • 도움을 요청한다 → 운영체제 코드를 실행하려고 한다

 

예를 들어 응용 프로그램이 실행 과정에서 하드 디스크에 접근하여 데이터를 저장하려고 함

  • 응용 프로그램이 운영체제에 도움 요청
  • 운영체제는 커널 영역 내의 하드 디스크에 데이터를 저장하는 코드를 실행하여 응용 프로그램 대신 작업을 수행

 

  • 이중 모드(Dual model) : CPU가 명령어를 실행하는 모드를 크게 사용자 모드와 커널 모드로 구분하는 방식
    • 사용자모드 : 운영체제 서비스를 제공받을 수 없는 실행 모드 (자원 접근 불가)
    • 커널 모드 : 운영 체제 서비스를 제공받을 수 있는 실행 모드

이중 모드는 플래그 레지스터 내의 슈퍼바이저 플래그에 의해 결정됨

슈퍼바이저 플래그를 통해 CPU가 명령어를 실행하는 모드를 파악 후 판단

 

  • 응용 프로그램이 운영체제에게 시스템 호출(System call)을 통해 사용자 모드 → 커널 모드로 전환됨
  • 일종의 소프트웨어 인터럽트

 

하드웨어 인터럽트 처리 방식과 유사

  1. 시스템 호출을 발생시키는 명령어가 실행되면 CPU는 지금까지의 작업을 백업
  2. 커널 영역 내에 시스템 호출을 수행하는 코드(인터럽트 서비스 루틴)을 실행
  3. 다시 기존에 실행하던 응용 프로그램으로 복귀하여 실행

 

일반적으로 응용 프로그램은 실행 과정에서 운영체제 서비스들을 매우 빈번하게 사용

시스템 호출은 운영체제마다 정해져있음

 

운영체제의 핵심 서비스

크게 3가지

  • 프로세스 관리, 자원 접근 및 할당, 파일 시스템 관리

 

프로세스 관리

  • 프로세스 : 실행중인 프로그램
  • 일반적으로 하나의 CPU는 한번에 하나의 프로세스만 실행할 수 있기에 CPU는 이 프로세스들을 조금씩 시간을 나눠 번갈아 가며 실행

 

  • 각 프로세스의 상태와 사용하고자 하는 자원도 다양 → 운영체제는 다양한 프로세스를 일목요연하게 관리하고 실행할 수 있어야 함

 

자원 접근 및 할당

CPU

  • 메모리에 여러 프로세스가 적재되고, 하나의 CPU는 한 번에 하나의 프로세스만 실행
  • 어떤 프로세스부터 CPU를 이용하게 할지, 얼마나 오래 사용할지 결정해야 함 ⇒ CPU 스케쥴링 (11장)

메모리

  • 메모리에 적재된 프로세스들의 크기와 주소도 가지각색이라 운영체제가 어떻게 메모리를 할당하지, 메모리가 부족한 경우에는 어떻게 극복하는지 운영체제가 판단

입출력장치

  • 인터럽트 서비스 루틴은 운영체제가 제공하는 기능으로 커널 영역에 있고 하드웨어 인터럽트도 마찬가지
  • 입출력장치가 cpu에 하드웨어 인터럽트 요청 신호를 보내면 CPU는 하던 일을 잠시 백업한 뒤 커널 영역에 있는 인터럽트 서비스 루틴을 실행

 

파일 시스템 관리

  • 파일 시스템도 운영체제가 지원하는 핵심 서비스 (15장)

참고

  • system call 확인하기위해 linux에서는 strace 명령어 사용
$ strace /bin/ls                                                                                                                                                                                          ok | wschoi@superb-tony | 03:58:16 PM
execve("/bin/ls", ["/bin/ls"], 0x7ffd7c910c40 /* 40 vars */) = 0
brk(NULL)                               = 0x559534620000
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffe470196c0) = -1 EINVAL (Invalid argument)
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=101547, ...}) = 0
mmap(NULL, 101547, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f8e83bfb000
close(3)                                = 0
openat(AT_FDCWD, "/lib/x86_64-linux-gnu/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
read(3, "\\177ELF\\2\\1\\1\\0\\0\\0\\0\\0\\0\\0\\0\\0\\3\\0>\\0\\1\\0\\0\\0@p\\0\\0\\0\\0\\0\\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0644, st_size=163200, ...}) = 0
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8e83bf9000
mmap(NULL, 174600, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f8e83bce000
mprotect(0x7f8e83bd4000, 135168, PROT_NONE) = 0
mmap(0x7f8e83bd4000, 102400, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x6000) = 0x7f8e83bd4000
mmap(0x7f8e83bed000, 28672, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1f000) = 0x7f8e83bed000
mmap(0x7f8e83bf5000, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x26000) = 0x7f8e83bf5000
mmap(0x7f8e83bf7000, 6664, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f8e83bf7000
close(3)                                = 0

 

 

 

참고

 


1. 책 "혼자 공부하는 컴퓨터 구조+운영체제"
2. 유튜브 "혼자 공부하는 컴퓨터 구조 + 운영체제"
 

반응형

'OS' 카테고리의 다른 글

CPU 스케쥴링  (0) 2024.07.16
프로세스와 스레드  (0) 2024.07.14
캐시메모리  (0) 2024.07.07
메모리  (0) 2024.07.05
CPU 성능 향상 기법  (0) 2024.07.02
반응형

본 글은 책 "혼자 공부하는 컴퓨터 구조+운영체제" 의 Chapter 6-3. 캐시메모리 부분을 읽고 정리한 내용입니다.

06-3 캐시 메모리

  • CPU가 메모리에 접근하는 시간은 CPU의 연산속도보다 느림
  • 이를 극복하기 위한 저장 장치로 캐시 메모리가 쓰임
 

 

저장 장치 계층 구조(Memory hierarchy)

  • ‘CPU에 얼마나 가까운가’를 기준으로 계층적으로 나타낼 수 있음

 

캐시 메모리(Cache memory)

  • CPU와 메모리 사이에 위치
  • 레지스터보다 용량이 크고 메모리보다 빠른 S(static)RAM 기반의 저장 장치
캐시 메모리를 대형 마트보다 가깝지만 비싼(?) 편의점에 비유

 

캐시 메모리에 CPU가 필요로 하는 데이터가 있다면 데이터로의 접근 시간을 줄일 수 있음

  • L(Level)1 캐시: 코어와 가장 가까운 캐시 메모리
  • L2 캐시
  • L3 캐시

L1과 L2 캐시는 코어 내부에, L3 캐시는 코어 외부에 위치

  • CPU가 메모리 내에 데이터가 필요하다고 판단하면 우선 L1 캐시에서 데이터를 검색
  • 없다면 L2, L3 를 순차적으로 검색
  • 멀티코어 프로세서에서는 L1,L2 캐시는 코어마다 고유한 캐시 메모리로 할당
  • L3 캐시는 여러 코어가 공유하는 형태로 사용
캐시 메모리 계층을 반영한 저장 장치 계층 구조

 

참조 지역성 원리

  • 캐시 메모리에는 무엇을 저장해야 할까?

⇒ CPU가 사용할 법한 대상을 예측하여 저장

  • Cache hit: 자주 사용될 것으로 예측한 데이터가 실제로 들어맞아 캐시 메모리 내 데이터가 CPU에서 활용되는 경우를 뜻함
  • Cache miss: 예측이 틀려 메모리에서 필요한 데이터를 직접 가져와야 하는 경우로 자주 발생하는 성능이 떨어짐
  • 캐시 적중률(Cache hit ratio) = # Cache hit / (# Cache hit + # Cache miss)
    • 우리가 사용하는 컴퓨터의 캐시 적중률은 대략 85~95% 이상

어떻게 캐시 적중률을 높일 수 있을까?

  • 캐시 메모리는 한가지 원칙에 따라 메모리로부터 가져올 데이터를 결정

 
참조 지역성의 원리(locality of reference, principle of locality)

  1. CPU는 최근에 접근했던 메모리 공간에 다시 접근하려는 경향이 있다 → 시간 지역성
구구단 2단을 출력하는 코드 내용
  • 변수는 num, i
  • 반복사용되는 변수들을 캐시에 저장하면 효율성 높일 수 있음

2. CPU는 접근한 메모리 공간 근처를 접근하려는 경향이 있다 → 공간 지역성

  • 기억장치 내에 서로 인접하여 저장되어 있는 데이터들이 연속적으로 액세스 될 가능성이 높아지는 특성
  • 예를 들어 워드 프로세서 프로그램의 자동 저장 기능에 대한 데이터가 사용되었다면, 해당 공간 근처에 있는 기능들에 대한 메모리 공간을 캐시에 저장하면 효율성을 높일 수 있음

 

참고

 


1. 책 "혼자 공부하는 컴퓨터 구조+운영체제"
2. 유튜브 "혼자 공부하는 컴퓨터 구조 + 운영체제"
 

반응형

'OS' 카테고리의 다른 글

프로세스와 스레드  (0) 2024.07.14
운영체제를 공부해야하는 이유와 커널  (0) 2024.07.14
메모리  (0) 2024.07.05
CPU 성능 향상 기법  (0) 2024.07.02
명령어  (0) 2024.06.30

+ Recent posts