7 minute read

프로세스란?

  • 현재 실행중인 프로그램을 의미한다.
    • 다음과 같은 의미로도 사용된다.
      • 메모리의 할당 단위
      • 디스패치의 단위
  • 프로그램은 두가지 방법으로 동작한다.
    • Batch system : 배치 크기만큼 한번에 처리
    • Interactive system : Time-shared 방법으로 처리(응답시간이 짧다)
  • 프로세스가 포함하는 것
    • Program, Data, Stack, Heap, Meta data
  • 프로세스 이미지
    • Kernel Mode : 운영체제가 모든 권한을 가지고 실행됨(전체영역)
    • User Mode : 사용자의 권한으로 실행 (시스템 영역을 제외한 나머지 영역)
    • text(program) -> data -> heap -> stack 영역으로 나누어진 메모리영역을 의미
  • 각각의 영역에서 저장되는 데이터 종류
    • Code(text) : 프로그램코드 그자체, 데이터 X
    • Stack : 지역변수, 매게변수 등 특정함수에만 필요한 데이터
    • Heap : 동적메모리, 할당된 변수 등 사용자가 임의로 받아낸 데이터
    • Data : 전역변수, 상수, 문자열상수 등 프로그램 전체에 필요한 데이터
  • 프로세스가 가지는 상태
    • new : 새롭게 생겨난 프로세스
    • running : 실제로 실행되고 있는 상태
    • waiting : 다음 이벤트가 발생할 때 까지 대기하는 프로세스
    • ready : 프로세스가 되기 위해 할당을 기다리는 상태
    • terminated : 실행이 종료된 상태의 프로세스
  • 프로세스가 동작하는 구체적인 형태
    • 사진
    • 프로그램 -> 메모리에 올라온 프로세스(New) -> CPU에 올릴 준비가 된 프로세스(Ready) -> CPU에서 동작중(Running)-> 종료(Terminated)
    • Running에서 Waiting으로 넘어갈때 : I/O나 정지이벤트가 발생함 (인터럽트를 발생시킴)
    • Waiting에서 Ready로 넘어갈때 : I/O가 끝나서(인터럽트를 처리해서) Ready로 넘어감
  • 프로세스의 문맥교환 (Context Switching)
    • 굉장히 많은 량의 오버해드가 발생함
    • 뭄맥교환이 자주 발생할수록 CPU활용도가 낮아짐
    • 하나의 CPU에서 문맥교환 과정
      1. PCB1에 현재 상태 저장
      2. PCB2의 상태 로드
      3. 동작
      4. PCB2에 현재 상태저장
      5. PCB1의 상태 다시 로드

PCB란 무엇인가?

  • Process Control Block을 의미하며 Meta Data이다.
  • 결국 의미하는 것의 핵심은 데이터를 설명하는 데이터라는 것이다.
    • ex) 엑셀 파일의 파일명 등
  • 다음과 같은 것들이 포함된다.
    • 프로세스 상태
    • 프로세스 카운터 (실행위치)
    • CPU Register
    • CPU 스케줄링 정보
    • 메모리 관리 정보
    • 기본 저장 정보
    • I/O 상태 정보
  • PCB는 어디에 존재하는가? : 메모리의 커널 영역 어딘가
  • 실제로 PCB안에 존재하는 것
    • PID : 프로세스 ID : 크기가 아주 큼
    • Priority : 우선순위
    • Waiting Event : 대기중인 이벤트들
    • State : 현재 프로세스의 상태
    • Location of image in disk and memory : 실제 주소의 포인터
    • 디렉토리, 파일 포인터
    • Parent, Child 프로세스 ### 프로세스가 가지는 데이터
  • 나중에 교환되며, 메모리에 남아있어야 하는 데이터 (Struct Proc)
    • PID, Priority, Waiting Event, State, 실제 주소 포인터 등
    • 이들은 항상 필요하므로 프로세스가 나가도 메모리에 존재한다.
    • 가지는 데이터의 크기가 작다.
    • 프로세스당 하나씩만 있다.
  • 실행할 때 필수적인 데이터(Struct User)
    • Open files, directory, tty, Parent&child 프로세스 등
    • 이들은 프로그램이 동작할 때 사용된다.
    • 가지는 데이터의 크기가 크다.
    • 항상 가상주소에 의해 접근된다. ### 프로세스 스케줄링
  • System에 올라온 모든 Process들은 Job Queue에 존재한다.
  • System에 올라왔지만 실행중이 아닌 프로세스들은 Ready Queue에 존재한다.
  • System에서 프로세스의 I/O를 기다리는 프로세스틀은 Device Queue에 존재한다.
  • 이 밖에도 다양한 Queue가 존재한다.


### Shedulers (스케줄러)

  • Long-term Scheduler : Ready상태에 어떤 프로세스를 넣을지 결정하는 스케줄러
    • Job Scheduler 라고도 불린다.
    • Ready Queue에 프로세스를 넣어주기 때문에 Degree변하게 된다.
    • 상대적으로 느리게 Invoke 된다.
    • 실질적인 Degree를 관리하는데 주로 Degree를 낮추도록 한다.
  • Short-term Scheduler : Ready상태에서 Running 상태로 할당시켜주는 스케줄러
    • CPU Scheduler 라고도 불린다.
    • Ready 상태에 있는 프로세스들중 특정한 정책에 따라 할당한다.
    • 상대적으로 빠르게 Invoke 된다.
  • Medium-term Scheduler : Swap 영역으로 프로세스를 옮겨주는 스케줄러이다.
    • Thrasing 을 방지하기 위해 개발되었다.
    • Degree를 감소시키도록 Swap하여 Thrashing을 방지하는 역할을 수행한다.
  • Thrashing이 무엇인가?
    • 페이지 단위로 실행되는 프로세스가 어떠한 사고로 인하여 페이지 로드에 실패하면 발생한다.
    • 페이지가 부족하면 실행이 불가능하고 CPU는 서로 책임을 전가하기 시작한다.
    • 책임 전가가 진행되면 CPU의 사용도가 급감하며 활용도가 낮아지게 된다.
    • 이런 상황은 Degree가 증가하면 발생하기 때문에 Degree를 감소시키면 해결된다.


  • Process를 나누는 큰 기준 2가지
    • I/O-bound process - I/O에 더 오랜시간이 소모되는 프로세스
    • CPU-bound process - 연산하는데 더 오랜 시간이 소모되는 프로세스

### 문맥교환 (Context Switch)

  • 실행중인 프로세스를 기다리는 상태인 프로세스와 교환하는 작업을 의미한다.
  • 문맥이 교환되면 다음 3가지가 교환된다.
    • System Context : 메모리 테이블이나 실제 저장공간과 같은 커널역영에 존재하는 데이터가 교환
    • Hardware Context : 프로그램 카운터와 같은 레지스터 정보를 교환
    • Memory Context : 메모리 공간이나 디스크 이미지가 교환
  • 스위칭은 하는 동안 아무작업도 할 수 없기 때문에 문맥교환은 큰 오버헤드를 가진다.

## 리눅스에서 프로세스 생성하기

  • 모든 프로세스는 fork()와 exec()순서로 생성된다.


  • fork() : 자기 자신의 PID를 제외한 나머지를 모두 복사하여 새로 생성한다.
  • fork()를 사용하여 프로세스 수를 늘린다.
  • fork()에서는 메모리의 공유가 발생하지 않는다.
  • 메모리나 커널 데이터 스트럭쳐를 할당받는 것이 주 목적이다.


  • exec() : 디스크에서 원본 파일을 가져와 현재 파일에 오버라이딩 한다
  • exec()를 사용하여 실제 파일를 받아오며 처음 시작 위치로 이동한다.
  • 파일을 가져오고, 초기화한 후에 Ready 큐에 넣는 것이 주 목적이다.
main()
{
    int pid;
    pid = fork();
    
    if(pid == 0)
        printf("\n Hello, I am child!\n")
    else
        printf("\n Hello, I am parent!\n")
}
  • 예를 들어 위의 프로그램을 실행하면 아래와 같이 출력 될 것이다.

Hello, I am child!
Hello, I am parent!
  • 이는 자식 PID가 0이고, 부모 PID는 0이 외의 숫자가 되기 때문이다.
  • 출력은 부모, 자식 중 누가 먼저 출력되는지는 스케줄러의 정책에 따라 다르다.
int main() {
    pid_t pid;
    char *args[] = {"ls", "-l", NULL};
    
    pid = fork(); //여기서 포크시작
    
    if (pid < 0) {
        printf("Fork failed\n");
        return 1;
    }
    
    if (pid == 0) {
        // 자식 프로세스에서 ls 실행
        execvp("ls", args);
        printf("Error: execvp failed\n");
        return 1;
    } else {
        // 부모 프로세스에서 자식 프로세스의 종료를 대기
        int status;
        wait(&status);
        printf("Child process exited with status %d\n", WEXITSTATUS(status));
    }
    
    return 0;
}
  • 위는 exec가 포함된 포크 실행문이다.
  • 위의 코드를 실행하면 다음과 같은 일이 발생한다.
    1. fork()를 만나서 두개의 프로세스로 분리됨
    2. 부모 프로세스는 자식 프로세스가 종료될때 까지 wait()상태에 들어간다.
    3. 자식 프로세스는 ls 문을 실행하고 종료된다.
    4. 부모 프로세스에서 자식이 종료되었음을 알리는 출력문을 출력한다.


  • 핵심
    • 포크는프로세스의 복사만 가능하다.
    • exec는 덮어쓰기와 유사하기 때문에 프로세스를 생성한다.
    • 포크로 생성된 프로세스는 포크에서 사라지지 않는다.
    • exec로 실행된 프로세스는 exec로 끝나야한다.

프로세스 종료

  • 프로세스는 exit으로 종료할 수 있다.
  • 반드시 exit이 아니더라도 abort로하여 종료시킬 수도 있다.
  • 종료되는 케이스는 크게 3가지가 있다.
    1. exit() : 프로세스를 정상 종료시킴
    2. abort() : 주로 kill()을 사용하며, 부모 프로세스가 자식 프로세스를 중지시킨다.
      • 자식이 할당된 자원을 모두 사용하였을 때 주로 발생한다.
      • 또는 작업에 더 이상 필요가 없어졌다고 판단했을 때 사용한다.
    3. 어떤 문제로 인하여 부모가 종료됨
      • 이 경우 자식 프로세스는 좀비가 되거나 고아가 된다.
      • 좀비가 되면 마지막 리턴값이 무한히 반복된다.
      • 고아가 되면 자원이 반환되지 않고 그대로 대기한다. ```C int main() { pid_t pid = fork();

    if (pid == 0) { printf(“I’m child process (pid=%d)\n”, getpid()); sleep(10); // 자식 프로세스를 10초 동안 대기시킴 printf(“Child process has finished\n”); } else if (pid > 0) { printf(“I’m parent process (pid=%d)\n”, getpid()); sleep(5); // 부모 프로세스를 5초 동안 대기시킴 printf(“Parent process has finished\n”); kill(pid, SIGTERM); // 자식 프로세스를 종료시킴 } else { perror(“fork failed”); exit(1); }

    return 0; } ```

  • 위 코드는 abort의 간단한 예로, 대기중이던 자식 프로세스를 종료시켜버린다.
  • abort는 부모에서 이루어진다는 것을 알 수 있다.
  • 출력은 다음과 같다.
    I'm parent process (pid=1234)
    I'm child process (pid=0)
    Parent process has finished
    

Cooperating Process

  • 독립된 프로세스는 서로 영향을 줄 수 없다.
  • 하지만 코오퍼레이팅 프로세스는 서로 영향을 주며 함께 동작한다.
  • 다음과 같은 특징들이 있다.
    • 정보공유 : 글로벌 변수나 메모리 포인터의 참조가 가능하다.
    • 연산 속도 증가 : 병렬처리가 가능하여 속도가 증가한다.
    • 모듈성 : 어디든 이식이 가능해진다.
    • 편의성 : 사용자가 사용에 유리해진다

생산자 - 소비자 문제

  • 두 프로세서 사이에서는 데이터를 생성한 프로세스와 사용한 프로세스가 있다.
  • 이 두 프로세스 사이의 데이터 전송 속도 차이로 인해 발생하는 문제가 있다.
  • 보통 버퍼의 크기가 문제가 된다. 특히 고정된 버퍼 사이즈가 그렇다.
  • 따라서 버퍼는 오버플로우 되지 않기 위해서 환형 큐로 동작하며 포인터와 함께 데이터가 움직인다.
  • 위 문제를 해결하기 위해 counter 변수를 만들고 데이터가 생성되면 1씩 증가, 소비되면 1씩 감소시킨다.
  • 이 counter 변수가 하는 일을 동기화 작업이라고 한다.


  • Producer Process
    while(True)
    {
      while(count == BUFFER_SIZE)
          continue;
      // int은 버퍼의 주소이다.
      buffer[in] = nextProduced;
      // 다음 버퍼 주소로 이동
      in = (in + 1) % BUFFER_SIZE;
      // count 증가로 동기화
      count++;
    }
    
  • Consumer Process
    while(True)
    {
      while(count == 0)
          continue;
      // out은 버퍼의 주소
      nextCounsumed = buffer[out];
      // 다음 버퍼의 주소로 이동
      out = (out + 1) % BUFFER_SIZE;
      // count 감소로 동기화
      count--;
    }
    
  • 타임 쉐어링 환경에서 동작하면 실행코드가 꼬이게 된다.
  • 그렇게 되면 공유변수는 사용하기 어려워진다.
  • 따라서 lock을 가지고 동기화 하는 것이 필요하다.(Critical Section)

Interprocess Communication (IPC)

  • 프로세스 간의 데이터 교환이 필요한 상황이 있다.
  • 이런땐 Message를 보내서 정보를 전달한다.
  • 두 프로세스 사이에 링크가 생기고 send()와 receive()로 동작한다.
  • 이걸 IPC라고 부르는데 직접 통신과 간접 통신이 있다.


  • 직접 통신 (Direct Communication)
    • send(P, message) : P에게 메세지를 바로 전송한다.
    • receive(Q, message) : Q에게 메세지를 바로 받는다.
    • 링크는 대체로 물리적으로 연결된다. 따라서 대규모 프로세스에서 비효율적이다.
    • 프로세스 사이에서는 단 하나의 간선(링크)만 존재할 수 있다.
    • 링크가 물리적으로 연결되어 있으므로, 바로 직접적으로 전송하는 것을 의미한다.
  • 간접 통신 (InDirect Coummunication)
    • send(A, message) : A 메일박스에 메세지를 전송한다.
    • receive(A, message) : A 메일박스의 메세지를 받아온다.
    • 링크는 물리적으로 형성되지 않으며 메모리나 Queue의 형태로 생성된다.
    • 자원이 공유되고, 여러 프로세스가 접근 가능하다.
    • 대규모 프로세스들에서 효율적으로 동작할 수 있다.

Synchronization

  • Blocking 방식과 Non-Blocking 방식으로 구분된다.
  • Blocking
    • 동기적이며 메세지 교환 과정에 집중한다.
    • 메세지를 전송하는 쪽에서는 메세지가 전송될 때 까지 기다린다.
    • 메세지를 받는 쪽에서는 메세지가 사용가능할 때 까지 기다린다.
  • Non-blocking
    • 비동기적이며 메세지 교환 과정중 다른 작업이 가능하다.
    • 메세지를 전송하는 쪽에서는 메세지를 전송하고 다른 일을 계속한다.
    • 메세지를 받는 쪽에서는 메세지가 NULL일 때 다른 작업을 계속한다.


😺 오타나 논리적 오류 지적은 언제든지 환영합니다!😺   

항상 읽어주셔서 감사합니다~ 🙏

Leave a comment