1. 프로세스 개요

1.1 프로그램과 프로세스

프로그램 : 하드디크스나 USB등 저장 장치에 저장된 실행파일 프로세스 : 프로그램이 메모리에 적재되어 실행중일 때

CPU는 하드디스크에 저장된 상태에서 코드를 실행하지 않고 메모리에 적재된 코드들만 실행함

프로세스의 특징

  • 운영체제는 프로그램을 메모리에 적재하고 이를 프로세스로 다룬다
  • 운영체제는 프로세스에게 실행에 필요한 메모리를 할당하고 이곳에 코드와 데이터 등을 적재한다.
  • 프로세스들은 서로 독립적인 메모리 공간을 가지므로, 다른 프로세스 영역에 접근할 수 없다
  • 운영체제는 프로세스마다 고유한 번호(프로세스 ID)를 할당한다.
  • 프로세스에 관한 정보는 운영체제 커널에 의해 관리된다.
  • 프로세스는 실행-대기-잠자기-실행-대기-잠자기-실행-종료 등의 생명 주기를 가진다
  • 프로세스를 만들고, 수행하고, 대기시키고, 종료시키는 모든 관리는 커널에 의해 수행된다.

1.2 프로세스 관리

프로세스 관리는 커널이 처리

1.3 프로그램의 다중 인스턴스

프로그램의 다중 인스턴스 : 프로그램이 실행될 때마다 생성되는 독립된 프로세스

메모장 프로그램을 3번 실행하면 3개의 메모장 프로세스가 서로 다른 프로세스 번호를 부여받고, 서로 다른 메모리에 적재되어 별개의 프로세스들로 생성됨

1.4 프로세스 주소 공간

CPU 주소공간 : 컴퓨터 내 CPU가 접근 가능한 전체 메모리 공간

프로세스 구성

  • 코드 영역 - 프로세스 코드가 적재되는 영역, 사용자가 작성한 코드와 코드에서 호출하는 라이브러리 함수 코드 적재
  • 데이터 영역 - 프로세스의 전역 변수들과 정적 변수들이 적재되는 영역, 사용자가 선언한 전역변수와 라이브러리에 선언된 전역변수 적재
  • 힙 영역 - 프로세스가 실행 중에 동적 할당받는 영역 (new 연산자를 이용하여 동적으로 할당받는 메모리)
  • 스택 영역 - 함수가 호출될 때 지역변수, 매개변수, 함수의 리턴값 등이 저장되는 영역

프로세스 주소공간 : 프로세스가 실행중에 접근할 수 있도록 허용된 주소의 최대 범위

  • 사용자 공간 + 커널 공간 = CPU가 액세스 할 수 있는 전체 공간
  • 운영체제에서 설정한 사용자 공간의 최대 범위까지 코드, 데이터, 힙, 스택을 늘려갈 수 있음
  • 시스템 호출을 통해 커널 공간까지 접근 가능

프로세스 주소 공간 : 프로세스가 액세스 할 수 있는 영역, 프로세스 주소공간 내에 현재 사용하고 있는 코드, 데이터, 힙, 스택을 합친 크기 동적으로 메모리를 할당 받아 실행중에 계속 변함

프로세스의 주소 공간은 가상 공간이다

  • 물리 메모리 여러 공간에 흩어져있음
  • 프로세스의 가상 주소 공간과 물리 메모리의 물리 주소 공간을 연결하는 매핑 테이블을 두고 두 주소 공간 관리
    • 매핑 테이블을 이용해 가상 주소를 물리 주소로 변환함

가상 주소 공간은 충돌하지 않는다

  • 각 프로세스의 영역은 운영체제에 의해 물리 메모리의 서로 다른 공간에 배치되므로 충돌이 일어나지 않음

2. 커널의 프로세스 관리

2.1 프로세스 테이블과 프로세스 제어 블록

운영체제 커널은 시스템 전체에 하나의 프로세스 테이블을 두고 모든 프로세스의 정보를 관리함 프로세스 생성시 프로세스 제어 블록(PCB, Process Control Block)을 생성하여 프로세스 정보를 저장함 프로세스 테이블의 비어있는 항목에 PID(프로세스 번호)와 함께 PCB 연결 프로세스 테이블과 PCB는 커널 공간에 생성되며 커널만 액세스 할 수 있음

2.2 프로세스 제어 블록

프로세스 번호(PID)

  • 프로세스 생성 시 할당됨
  • 프로세스를 식별하는 고유 번호

부모 프로세스 번호(PPID)

  • 부모 프로세스의 ID
  • 모든 프로세스는 부모 프로세스를 가짐

프로세스 상태 정보(Process State)

  • New : 생성 초기 상태
  • Running : 현재 CPU에 의해 실행되고 있는 실행 상태
  • Ready : 스케줄링을 기다리고 있는 준비 상태
  • Blocked : 입출력 요청 후 입출력 완료를 기다리거나 타이머 알람을 기다리거나 요청 자원이 사용 가능 상태가 되기를 기다리는 상태

프로세스 컨텍스트 정보(Process Context)

  • 현재 프로세스가 실행중인 상황 정보
  • CPU에 들어있는 레지스터 값(PC, SP, 범용 레지스터)

스케줄링 정보

  • 프로세스 우선 순위(prority, nice)
  • 프로세스가 사용한 CPU시간, 최근에 CPU를 할당받아 실행한 시간

종료 코드

  • 프로세스가 종료할 때 종료 이유를 부모 프로세스에게 전달하기 위한 정수값
  • 좀비 프로세스 : 종료되었지만 부모가 종료코드를 읽어가지 않은 상태의 프로세스

프로세스 오픈 파일 테이블

  • 프로세스가 실행 중에 열어놓은 파일에 관한 정보

메모리 관리를 위한 정보, 프로세스 사이의 통신 정보, 회계 정보, 프로세스 소유자 정보 등등 …

2.3 프로세스 생명 주기와 상태 변이

  • New : 새로운 프로세스 생성, 새 프로세스의 코드와 데이터 메모리에 적재, PCB를 만들어 프로세스 테이블의 빈 항목에 등록, 상태를 new로 기록, 프로세스가 완료 시한 내에 처리 가능하다고 판단될 때 ready 상태로 변경
  • Ready : 프로세스가 스케줄링을 기다리는 준비 상태, 준비 큐에 들어가 대기
    • 프로세스(CPU) 스케줄링 : 다음 실행될 프로세스를 선택하는 것, 준비 큐에 오래 머무르면 기아 프로세스가 생김
  • Running : 프로세스가 CPU에 의해 현재 실행되고 있는 상태
  • Blocked, Wait : 프로세스가 자원을 요청하거나 입출력을 요청하고 완료를 기다리는 상태
  • Terminated/Zombie : 프로세스가 모두 종료했을 때, 부모 프로세스가 종료 코드를 읽어가지 않으면 좀비 프로세스 상태가 됨
  • Terminated/Out : 부모 프로세스가 좀비상태인 자식 프로세스 pcb에서 종료코드를 읽어갈 때

2.4 프로세스 스케줄링

다중프로그래밍 운영체제에서 실행 중인 여러 프로세스 중 cpu를 할당할 프로세스를 결정하는 과정

단일 스레드 프로세스(single-threaded process) : 프로세스가 1개의 스레드로 구성 멀티스레드 프로세스(multi-threaded process) : 프로세스가 여러개의 스레드로 구성

오늘날 멀티스레드 운영체제 실행 단위 : 스레드, 프로세스 스케줄링 없이 스레드 스케줄링을 실행함

3. 프로세스의 계층 구조

3.1 프로세스 부모-자식 관계

#0 프로세스 : 시스템 내 스케줄될 프로세스가 하나도 없을 때 실행되는 유휴 프로세스(idle process) #1 프로세스 : 부팅과정에서 시스템을 초기화하여 사용자가 사용할 수 있는 상태까지 부팅 완료, 시스템 종료 과정도 책임짐

  • init 프로세스 : 외부 네트워크로부터 연결 요청을 대기하는 sshd 프로세스 생성
  • sshd 프로세스 : 외부 네트워크로부터 접속을 받으면 로그인 이름과 암호를 확인한 후 쉘 프로세스(bash) 생성
  • bash 쉘 : 사용자 명령을 받아 자식 프로세스를 생성하고 자식에게 명령 실행 #2 프로세스 : kthreadd, 커널 공간에서 커널 모드로 실행되면서 커널의 기능을 돕는 프로세스
  • 프로세스들 사이에 계층 관계는 없음, 모든 프로세스가 동등

$ pstree 0 : 0번 프로세스부터 프로세스들의 계층 구초를 트리 형태로 출력

3.2 #0, #1 프로세스 : idle 프로세스와 init 프로세스

모든 프로세스가 블록 상태여서 ready 상태의 프로세스가 한개도 없는 상황에 빠지지 않도록 하기 위해서 idle 프로세스 실행

3.3 부모 자식 프로세스의 실행 관계

모든 프로세스는 부모 프로세스에 의해 생성, 커널 코드에 의해 이루어짐 시스템 호출을 통해서만 프로세스를 생성하고 시스템 호출을 통해서만 프로세스를 종료

  • fork() : 자식 프로세스를 생성하는 시스템 호출 함수
  • exit() : 현재 프로세스의 종료를 처리하는 시스템 호출 함수
  • wait() : 부모가 자식 프로세스가 종료할 때까지 기다리는 시스템 호출 함수

3.4 좀비 프로세스 : 종료 후 방치된 자식 프로세스

프로세스가 종료할 때 프로세스가 남긴 종료코드를 PCB에 저장하고 프로세스 상태를 Terminated/Zombie 상태로 표시, 부모 프로세스는 자식 프로세스가 남긴 종료코드를 읽고 자식 프로세스의 PCB와 프로세스 테이블 항목이 제거되도록 함

좀비 프로세스 : 종료하였지만 부모가 종료코드를 읽지 않는 상태로 시스템에 남아있는 프로세스

좀비 프로세스 제거 - kill 좀비프로세스의 PPID

3.5 고아 프로세스와 입양

고아 프로세스 : 부모가 먼저 종료한 자식 프로세스

프로세스가 종료하면 자식 프로세스를 모두 종료시키거나 가까운 조상 프로세스에게 입양될 것을 미리 지정

3.6 백그라운드 프로세스와 포그라운드 프로세스

백그라운드 프로세스 : 사용자와의 대화를 필요로 하지 않는 프로세스 포그라운드 프로세스 : 터미널 사용자로부터 입출력을 독접하는 프로세스

3.7 CPU 집중 프로세스와 I/O 집중 프로세스

CPU 집중 프로세스(CPU 바운드 프로세스) : 실행의 많은 부분에 CPU가 활용될 때

  • 연산이 주된 이미지 처리
  • 신경망에서 학습하는 인공지능 I/O 집중 프로세스(I/O 바운드 프로세스) : 프로세스의 작업 중 많은 부분이 파일 입출력이나 네트워크 전송 등 입출력 작업인 경우
  • 파일을 읽고 쓰는 파일 서버 작업

4. 프로세스 제어

4.1 프로세스 생성과 fork()

프로세스 생성은 fork()나 CreateProcess()와 같은 시스템 호출을 통해 생성됨

  • 새로운 pid 번호 할당
  • pcb 구조체 생성
  • 프로세스 테이블에 새 항목 할당
  • 프로세스를 위한 메모리 공간 할당
  • 할당받은 메모리 공간에 프로세스 코드와 데이터 적재
  • pcb에 프로세스 정보 기록
  • pcb에 프로세스 상태를 ready로 표시하고 준비큐에 넣어 차후 스케줄 되게 함

응용 프로그램이 자식 프로세스를 만드는 이유 : 다중 처리

  • 여러개의 작업을 병렬적으로 처리하여 작업 효율을 올리기 위해
  • 스레드(thread)를 이용하여 다중처리 구현
fork() 시스템 호출

현재 프로세스를 복사하여 자식 프로세스 생성 자식 프로세스에게는 0, 부모 프로세스에게는 새로 생성된 자식 프로세스의 pid 리턴

#include <stdoi.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
 
int main() {
	pid_t, pid;
	int i, sum=0;
 
	pid = fork(); // 자식 프로세스 생성
	if(pid>0) {
		printf(pid); // 부모프로세스 fork()리턴값, 자식 프로세스 pid
		printf(getpid()); // 부모프로세스 pid
		wait(NULL); // 자식프로세스가 종료할 때까지 대기
	}
	else if(pid==0) { // 자식프로세스에 의해 실행되는 코드
		printf(pid); // 자식프로세스 fork()리턴값 pid
		printf(getpid(), getppid()); // 자식프로세스 pid, 부모프로세스 pid
		for(i=1; i<100; i++) {
			sum += i;
			printf(sum); // 자식프로세스
		}
	}
}

4.2 프로세스 오버레이와 exec()

프로세스 오버레이

  • 현재 실행중인 프로세스 주소 공간에 새로운 응용프로그램을 적재하여 실행하는 기법
  • exec() 시스템 호출 사용
  • 호출한 프로세스의 주소공간에 새로운 응용프로그램의 코드, 데이터, 힙, 스택을 올리게 되어 호출 프로세스의 모든 영역이 사라짐

4.3 프로세스 종료와 프로세스 종료 대기

프로세스 종료 행위는 exit() 시스템 호출로 이루어짐

  1. exit()을 호출한 프로세스에게 할당된 코드, 데이터, 힙, 스택 등 모든 메모리와 자원을 반환하고 열어높은 파일이나 소켓등을 닫는다
  2. pcb와 프로세스 테이블의 항목은 그대로 두고, pcb내에 프로세스 상태를 terminated/zombie로 바꾼다. 그리고 종료코드를 pcb에 저장한다.
  3. 현재 프로세스의 모든 자식 프로세스들을 init 프로세스에게 입양시킨다.
  4. 현재 프로세스의 부모에게 자식의 죽음을 알리기 위해 sigchld 신호를 보낸다. wait()를 호출하여 자식이 남긴 종료코드를 읽으면 자식이 완전히 종료된다.

종료코드 : 프로세스가 종료한 상태나 이유를 부모에게 전달하기 위한 것

wait() 시스템 호출 : 자식의 종료를 확인하기 위해 이용하는 시스템 호출

  • wexitstatus 매크로를 이용하여 종료코드를 알아냄
int exitcode; // 종료코드만 저장하기 위한 변수
int status; // exit()을 호출하여 자식의 종료 상태를 전달받기 위한 변수
pid_t childpid = wait(&status);
exitcode = WEXITSTATUS(status); // status에서 하위 8비트의 종료코드를 exitcode에 저장
pid_t childpid = wait(NULL); // 자식 프로세스 종료 대기

4.4 좀비 프로세스

좀비 프로세스 : 프로세스가 종료하여 메모리와 할당받은 모든 자원이 반환되었지만 자신이 남긴 종료코드가 부모에게 전달되지 않은 상태로 있을 때

출처 - https://www.booksr.co.kr/product/%EB%AA%85%ED%92%88-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C%EC%88%98%EC%A0%95%ED%8C%90/