본문 바로가기
CS/운영 체제🖥️

[OS] Processes: Process, Process States, Process API

by Mintta 2023. 3. 27.

이전글

https://codingmon.tistory.com/65

 

[OS] OS Overview (CPU가상화, 메모리 가상화, Concurrency, Persistence)

Virtualizing CPU 오늘날 하나의 컴퓨터에서 돌아가는 프로그램은 매우 많습니다. 지금 당장 이 글을 쓰면서 제 노트북에는 Xcode, Chrome, Notion, Discord, 카카오톡 등등 정말 수 많은 프로그램들이 실행되

codingmon.tistory.com


Program

소스프로그램에서 컴파일에서 얻은 실행 파일을 말하고, 이것은 HDD, SDD 저장장치에 저장됩니다.

저장장치에 저장되어있던 프로그램이 실행되려면 메인 메모리로 loading되어야합니다.

어째서???

  • 폰노이만 아키텍쳐이기 때문에
CPU가 메모리에 있는 기계어로 번역되어있는 코드를 읽어서 해독해서 처리하는 일을 반복수행: “프로그램이 실행된다”

Process

프로그램이 실행될 때 나타나는 현상들을 추상화해서 개념적으로 정리해놓은 것이 바로 프로세스.

 

Process는 그 자체로는 running program이라는 단순한 뜻입니다. 그 자체로는 특별해보이지 않지만 이 bytes들을 가져다가 실행시켜 프로그램을 유용한 것으로 변형시키는 것은 운영 체제!! 입니다.

 

(Text, Data, Stack, Heap을 붙어있는 것처럼 모두 표현했는데 실제로는 Page단위로 여기저기 흝어져 있게 됩니다. 추후에 다시 살펴보겠습니다 ! 후반부에 아마..!)

이렇게 두개의 프로그램이 메모리에 loading이 되어있다고 했을 때의 상황을 예로 들어 프로세스에 대해 좀더 살펴보겠습니다.

이 때의 추상화된 공간으로 아래 그림처럼 표현할 수 있습니다.

process (추상화된 관점)

 

Program vs Process

프로그램(program)은 컴퓨터에서 실행 가능한 명령어들의 모음.

소스 코드로 작성된 후 컴파일러나 인터프리터를 통해 실행 파일로 변환됩니다. 이 실행 파일은 컴퓨터에서 실행 가능한 프로그램으로, 메모리에 로드되어 실행됩니다.

반면에 프로세스(process)는 실행 중인 프로그램을 의미합니다. 프로세스는 프로그램의 실행 인스턴스(instance)로, 메모리에 로드된 프로그램의 실행 상태를 나타냅니다. 즉, 프로세스프로그램의 실행 중인 작업을 의미하며, 실행 파일이 메모리에 로드되어 실행되고 있는 상태를 말합니다.


오늘날 CPU칩셋은 하난데 CPU역할을 하는 코어가 통상 2개정도 있는데, 훨씬 많은 수십개의 프로그램이 실행되고 있습니다. 이게 가능한 것은 CPU를 가상화해서 무한대가 있는 것처럼 착각되게끔 하기 때문입니다.

❓ HOW TO PROVIDE THE ILLUSION OF MANY CPUS?
물리적인 CPU가 몇 개 없을지라도, 운영체제는 거의 무한한 양의 CPU가 있는 것처럼 보이게 하는 방법은 무엇인가요?
물리적으로 모자란 CPU를 어떻게 가상화할것이냐 ??

Time sharing, CPU를 번갈아가면서 사용하는 원리를 통해서 cpu를 가상화하고 있습니다.

 

CPU를 가상화해서 프로그램이 동시에 돌아가는 효과를 얻을 수 있습니다.

다시한번 말하지만, 그 때 실행되는 상태를 추상화한 것이 바로 프로세스 !

 

추가로 필요한 개념은 address space(주소 공간), 다시 말하면 메모리도 모자라는데 이것을 여러개의 실행중인 프로그램이 사용하려면 이것 또한 가상화가 되어야합니다 !

address space를 통해서 물리화된 메모리를 가상화해서, virtual memory를 각 프로세스별로 보유하고 있게 되는 것입니다.

💡 프로그램 입장에서 자기(프로그램)가 CPU를 독점하고 메모리 또한 무한대로 가지고 있다고 착각하게 만드는 것이다 !

프로그램씨 당신..

Process API

OS가 제공하는 process관련된 API, system call 형태로 제공하고 있습니다.

  • Create: 프로세스 만들기
  • Destroy: 프로세스 없애기
  • Wait: 특정 프로세스가 다른 프로세스의 결과를 기다리기
  • Control: 프로세스를 제어 (멈추기, 중단, 다시 실행..)
  • Status: 프로세스 상태 확인하기. 이 시스템에서 실행중인 프로세스가 무엇무엇 있는지 확인하기.

Process States

  • Running: 프로세스가 CPU에 의해서 처리되고 있을 때
  • Ready: Timesharing(시분할)로 의해서 나눠서 쓰는데 어떤 프로세스가 CPU를 쓰고 있을때 나머지 프로세스들은 모든 준비가 끝났지만 CPU라는 기계가 없어서 running상태가 아닌 상태가 Ready상태
  • Blocked: I/O요청 (CPU와 메모리 영역 밖). Kernal의 도움을 받아야하기 때문에 오래 걸림.
    그동안 CPU를 그대로 붙잡고 있는 건 엄청난 낭비, 다른 프로세스들이 오랫동안 ready상태가 되어야함.
    따라서 해당 프로세스는 Blocked상태로 두면, 운영체제, 즉 Scheduler가 다른 Ready상태의 프로세스에게 CPU를 주어서 Ready상태에 있던 프로세스를 Running상태로 바꿔줍니다.
    이후에 요청했던 I/O요청을 다 끝내면 다시 Blocked → Ready로 바꿔줍니다. (다음 scheduling될 때까지 대기)

State Transitions

이런 상태를 왔다갔다하는 것을 context switching이라고 하는데,

이는 당시 실행흐름에서의 CPU register값들(PC(Program Counter), SP(Stack Pointer), A, C, D1, D2 ….)을 save and restore하는 것을 말한다.

이 때 저장할 공간, 자료구조가 필요한데, 그것을 PCB(Process Control Block)이라고 한다.

// the registers xv6 will save and restore
// to stop and subsequently restart a process
struct context {
    int eip;
    int esp;
    int ebx;
    int ecx;
    int edx;
    int esi;
    int edi;
    int ebp;
};

// 프로세스 상태
enum proc_state { 
    UNUSED,
    EMBRYO,
    SLEEPING,
    RUNNABLE,
    RUNNING,
    ZOMBIE
};

// the information xv6 tracks about each process
// including its register context and state
struct proc { 
char *mem; // 프로세스 메모리의 시작 주소
    uint sz; // 프로세스 메모리의 크기
    char *kstack; // 해당 프로세스에 대한 kernel stack의 바닥 주소
    enum proc_state state; // Process 상태
    int pid; // Process ID
    struct proc *parent; // Parent process
    void *chan; // If !zero, sleeping on chan
    int killed; // If !zero, has been killed
    struct file *ofile[NOFILE]; // Open files
    struct inode *cwd; // Current directory
    struct context context; // <- context switch를 위해서는 여기를 바꿔야함
    struct trapframe *tf; // 현재 Interrupt에 해당하는 Trap frame
};

Process API - system call

fork()

  • 프로세스를 유일하게 생성하는 방법
  • kernal이 복사하는 일(물리적 공간에서 메모리 copy)을 수행함
  • 반환값 rc 을 이용해서 프로세스 생성 성공/실패 여부와 pid를 검사함
    • 0보다 작다? Error
    • 새로 생성된 child process rc에 0을 넣고, fork()를 실행한 parent쪽에는 생성한 child의 pid가 들어간다
    • 따라서, rc가 0이다? 새로 생긴 child process
    • rc가 0보다 크다? fork()를 실행한 parent process
    • system call이기 때문에 fork()를 호출하는 순간 parent 프로세스는 Blocked상태가 됨

wait()

  • 기다리는 역할
  • 자기가 생성한 child process가 끝나기를 기다리는 역할
  • 반환값 rc
    • 정상적으로 끝나고 돌아왔다면, child process의 id(pid)를 돌려받는다, 즉 fork()하고 난 후에 rc에는 이미 child pid가 들어있기 때문에 같은 값이다.
    • 순서를 보장하는 의미도 있음.
    • 아래 코드의 진행 흐름이라면 child process가 끝나고 난 후에야 아래 print문이 실행되는 것을 wait을 통해 보장할 수 있게 되는 것이다
    int rc_wait = wait(NULL);
    printf(
    	"hello, I am parent of %d (rc_wait: %d) (pid:%d)\\n",
    	rc, rc_wait, (int) getpid()
    );
    
    • wait(NULL)
      • 모든 프로세스는 종료될 때 exit status를 가지는데, 해당 exit status를 확인하는데 역할을 한다
      • wait(NULL) 대신 int s; 를 위에 선언하고 wait(&s) 이렇게 한다면 해당 s변수에 child process의 exit status를 저장하겠다는 것이 된다

exec()

  • 보통 fork 이후에 execution을 한다
  • fork로 메모리에 복사했는데, 완전히 다른 코드로 바꿔버리기 (새로운 실행파일 loading)
  • 조건문에 의해 다른일을 하는 것이 아니라 완전히 다른일 하기
  • exec() 이후에는 완전히 다 새롭게 (천지개벽)

왜 fork와 exec를 분리해놓았을까?

복사(fork)하고 새로 실행할 실행파일은 loading(exec)하는 작업을 하나로 해도 될법한데 왜 분리했을까??

Linux에는 prompt를 처리하는 shell이 있는데, $ 을 출력해놓으면 그 뒤에 사용자가 입력을 하고 enter를 치면 실행하게 되어있다. 그런데 주어진 명령어를 실행하기 전에 처리해야할 일들을 위해서 fork와 exec를 분리해놓은 것이다.

 

예를 들어 어떤 것이 있을까?? 출력을 redirection하는 일이 있을 수 있다

$wc p4.c > p4.out

예약되어있는 file descriptor number (fd)

0 stdin
1 stdout
2 stderror

File Descriptor Table의 1번 stdout에 p4.out을 넣고 출력물이 default인 모니터(터미널)에 뜨는 것이 아니라, p4.out의 파일에 결과물이 찍혀나오게 되는 것을 알 수 있습니다.

 


출처 : https://pages.cs.wisc.edu/~remzi/OSTEP/

다음글

https://codingmon.tistory.com/68

 

[OS] CPU 가상화 메커니즘 (Limited Direct Execution) - 1부

오늘 다룰 주제는 CPU의 가상화입니다. 운영체제는 여러 프로세스들이 동시에 실행되는 것처럼 보이도록 하기 위해 물리적인 CPU를 공유하도록 지원합니다. 바로 Time Sharing(시분할) 방법을 통해서

codingmon.tistory.com

 

댓글