OS - 프로세스

김인회·2021년 9월 15일
0

OS

목록 보기
1/5

프로세스란

프로세스란 실행 중인 프로그램을 의미한다.

실행 중이라는 것의 의미는 프로그램의 코드가 메모리에 올라가 CPU에 의해 작업되는 것을 뜻한다.

운영체제는 실행 중인 프로그램(프로세스)들을 효율적으로 관리하고 실행하기 위해서 프로세스 리스트에 프로세스를 모으고 상태를 나누어 관리한다.

(프로세스 상태 : running, ready, blocked, final 등등)

코드로 보는 xv6 Proc 구조

//프로세스를 중단하고 이후 재개할 때 불러오기 위하여
//xv6가 저장하고 복원하는 레지스터 값
struct context {
  uint edi;
  uint esi;
  uint ebx;
  uint ebp;
  uint eip;
};

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

// xv6이 추적하는 프로세스에 대한 추가적인 정보
struct proc {
  char *mem                    // 프로세스 메모리 시작주소
  uint sz                      // 프로세스 메모리의 크기
  char *kstack;                // 이 프로세스의 커널 스택 주소(바닥주소)
  enum procstate state;        // 프로세스 상태
  volatile int pid;            // 프로세스 ID
  struct proc *parent;         // 부모 프로세스 
  void *chan;                  // 0이 아니면, chan에서 수면
  int killed;                  // 0이 아니면 종료됨
  struct file *ofile[NOFILE];  // 열린파일
  struct inode *cwd;           // 현재 디렉토리
  struct context *context;     // 프로세스를 재실행시킬때 여기로 교환
  struct trapframe *tf;        // 현재 시스템콜에 해당하는 트랩 프레임
};

제한적 직접 실행 원리

OS에서 제한적 실행 원리라는 것은 말 그대로 프로그램의 코드를 CPU에 직접 올려서 실행시키며(경유를 통한 간접적인 실행 같은 것이 아님), 일부 기능은 제한적으로 실행시키겠다는 것을 의미한다.

여기서 기능을 제한한다는 것의 의미는 컴퓨터 시스템에 막대한 영향을 끼치는 행위를 프로그램단에서 함부로 행하지 못하도록 OS가 막겠다는 것이다.

일반적으로 시스템에 커다란 영향을 끼칠 수 있는 행위에(디스크 입출력, 시스템 자원할당 등) 관련된 다양한 작업들은 OS 단에서 미리 기능으로 구현해 놓았는데 이를 서비스루틴이라 부른다.

그리고 이 서비스루틴을 호출하는 행위를 시스템콜이라고 부른다.

제한적 실행원리로 구현된 OS는 영향력이 높은 작업들을 프로그램단에서 직접 수행하지 못하도록 하고 오로지 서비스루틴을 시스템콜 해서 작업을 진행하도록 유도한다.

프로그램이 컴퓨터에서 행할 수 있는 영향력을 제한시킨 것이다.

시스템콜

시스템콜은 OS가 제공하는 서비스(루틴 서비스)를 실행하여 이용하는 것을 의미한다.

(루틴 서비스: 보안성과 관련된 중요한 작업들을 운영체제가 미리 구현하여 특정 테이블에 등록해놓았는데 해당 서비스 기능들을 루틴서비스라고 한다. 운영체제는 이러한 코드들을 프로그램이 가져다 쓰게끔 한다)

OS가 제공하는(커널이 제공하는) 여러 가지 루틴서비스들은 시스템에 높은 영향력을 끼치는 작업들이므로 함부로 실행되어서는 안된다.

따라서 일반적으로 대부분의 OS는 시스템의 상태를 사용자모드(일반 권한)와 커널모드(특별한 권한)라는 2가지의 권한 수준으로 나누었고, 루틴서비스에 대한 접근은 오로지 커널모드에서만 가능하도록 설계 하였다.

[(현재 시스템모드에 대한 상태 정보는 프로세서(CPU)의 특정 레지스터에서 표기되어 기록된다. ->모드비트(CPL)]

fork()와 exec() 시스템콜로 알아보는 프로세스 생성과정

(UNIX 시스템 기준)

fork() 시스템콜의 경우 현재 프로세스를 복사하여 프로세스를 2개로 분기시키는 기능을 수행한다.

2개로 나눠진 프로세스는 각각 부모와 자식이 된다.

(이 과정은 메모리에 새로운 공간을 할당받아 기존 프로세스 값을 복사하는 방식으로 진행된다. 기존 공간 자체가 분기되는 것이 아니다)

exec()는 새로운 프로그램(프로세스)을 실행할 때 사용되는 시스템콜이다.

만약 fork() 이후 분기된 자식 프로세스가 exec() 시스템콜을 호출하여 새로운 프로그램을 실행시킨다면, 이후 자식 프로세스 메모리는 새로운 프로그램의 내용으로 완전히 덮어 씌워지게 된다.

새로운 프로세스의 내용이 메모리에 덮어 씌워짐과 동시에 기존에 가지고 있던 부모 자식 관계에 관련된 내용들도 당연히 없어지게 되고, 결국 사용자 입장에서 완전히 새로운 프로세스가 생성된 것처럼 보이게 된다.

이렇게 Unix 시스템은 fork()와 exec() 시스템콜을 조합하여 새로운 프로세스를 생성하며, 이 과정 속에서 특정 프로세스와 다른 프로세스를 잇는 파이프라인 같은 것을 구축하기도 한다.

예를들어 한 번 wc (특정파일) > newfile.txt 커맨드가 실행되는 과정으로 fork()와 exec()가 일어나는 과정을 따라가본다면,

(1) wc 프로그램으로 특정 파일을 연다
(2) wc 프로그램의 공정을 처리한다 (출력생성)
(3) fork()를 통해 자식을 생성한다
(4) (부모) 자식을 wait() 한다, (자식) 표준출력(stdout)을 닫는다
(5) (자식) exec()를 통해 '>' 출력재지정 프로그램을 실행한다
(6) 부모 자식 관계가 끊어지면서 부모 프로세스였던 것은 Wait()을 빠져나가고 종료된다
(7) wc 프로그램의 출력은 표준출력이 닫혀있으므로 newfile.txt의 입력으로 향하게 된다. 따라서 기존출력 내용들이 newfile.txt에 담겨진 뒤 '>' 프로그램도 종료된다

위와 같은 과정이 일어나게 된다.

이처럼 fork()와 exec()의 사이에 입출력 관련 작업을 조절하여 입출력 파이프를 설계하는 것도 가능하다.

인터럽트(interrupt)

인터럽터는 운영체제가 프로세서(CPU) 제어권을 확보하기 위한 전략으로 이용된다.

특정 프로세스가 CPU를 독점하는 것을 방지하고자 운영체제는 중간중간 타이머 인터럽트를 걸어서 현재 실행 중인 프로세스로부터 CPU의 사용권을 되찾아 온다.

그 후 OS의 스케줄링 알고리즘을 따라 CPU의 사용권을 재분배하는 식으로 동작하게 된다.

물론 프로그램들이 작업중에 시스템콜을 이용하는 상황에서도 운영체제는 자연스럽게 CPU의 제어권을 다시 획득하게 된다.

하지만 이러한 불특정한 상황이 일어나기만을 낙관적으로 기다리는 것으로는 전체 시스템을 통제할 수 없기 때문에 주기적으로 인터럽트를 걸어 OS가 통제권을 얻어 내고 있는 것이다.

(프로세스 협조방식으로 구성된 구식의 OS 같은 경우, 오로지 프로그램이 호출하는 시스템콜에만 의존하여 CPU 제어권을 획득하고자 하였다. 따라서 특정 프로세스가 무한루프에 빠져버린경우, OS는 CPU 제어권을 되찾아올 방법이 존재하지 않았고 오로지 시스템을 재부팅 하는 것만이 문제를 해결할 수 있는 유일한 방법이었다)

문맥교환(Context Switch)

실행 중인 프로세스를 잠시 중단시키고 다른 프로세스를 실행시키는 과정에서 일어나는 과정이 Context Switch이다.

조금 더 자세히 말하자면 현재 프로세스가 가지고 있는 레지스터 값들을 프로세스의 커널 스택에 저장해두고, 이어서 다음으로 작업할 프로세스가 가지고 있는(작업 중이었던) 레지스터 값들을 새로이 불러오는 일련의 과정을 Context Switch라고 한다. (저장->복원 과정)

Interrupt가 발생하고 Context Switch가 이루어지는 과정

프로세스 A 실행 --> 타이머 인터랩트 --> 커널스택에 A의 레지스터 작업값 저장(PUSH) --> 커널모드로 전환 --> trap(handler) --> 스케줄링 알고리즘 --> 프로세스 B로 Switching --> A의 현재 레지스터 작업값을 A의 proc구조에 캡슐화 --> B의 proc구조를 확인하여 커널 스택 포인터를 B로 전환 --> 커널스택에서 B의 레지스터 이전 작업값을 CPU가 다시 복원(POP) --> 유저모드로 전환 --> B 실행

(커널스택 : 각 프로세스마다 존재하는 메모리영역으로, 유저모드로 다시 복원하기 위해 필요한 임시 값들을 보관되는 곳??)

트랩(trap)

트랩은 내부 인터럽트라고 부르기도 한다.

프로그램에서 시스템콜이 호출되어 운영체제에 CPU의 통제권을 넘겨줘야 할 때 trap 이라는 특수 명령어를 실행한다.

trap은 권한 수준을 커널모드로 상향하면서 작업권을 커널로 분기시켜 운영체제가 CPU의 통제권을 획득하게끔 한다.

이후 작업이 완료되면 운영체제는 return-from-trap이라는 특수 명령어를 호출하는데, 이것은 권한수준을 유저모드로 하향하면서 작업권을 다시 프로그램으로 되돌려 놓는 기능을 수행한다.

profile
안녕하세요. 잘부탁드립니다.

0개의 댓글