정글/Pintos

[pintos] 프로세스 - PID / fork로 자식 스레드가 생성되는 과정

nkdev 2025. 5. 26. 13:21

프로세스 관련 시스템 콜을 구현하기 위해서

프로세스 공부를 좀 하고 넘어가겠당. 

CSAPP 8장 공부하는 주간에 내가 맡은 부분은 9장이었어서 공부가 다소 부족했다.

맞다 핑계다.. ㅠㅠㅠㅠㅠ 한 시간 동안만 투자해서 !!! 열심히 정리해보자.

 

.

.

.

 

CSAPP 8장에서는 프로세스 개념에 대해 설명하기 전에, 프로세스 추상화에 대해 언급한다.

 

프로세스 추상화란, 실제로는 여러 프로세스가 컨텍스트 스위칭을 하며 CPU, 메모리, 디스크와 같은 하드웨어 자원을 나눠 쓰고 있지만

OS가 각 프로세스들이 이 하드웨어 자원을 혼자서 다 사용하고 있는 것처럼 만들어준다.

 

OS가 프로세스를 추상화하기 위해서 제공하는 기능이 두 가지 있는데

  1. Private Address Space 제공
  2. User mode / Kernel mode 분리

이다. 간단히 설명하면 프로세스 하나가 cpu에서 실행될 때 우리는

  1. 그 프로세스가 메모리 전체 공간을 다 사용하고 있다고 착각하게 만들기 위해서, 실제로 본인이 사용할 수 있는 RAM 공간보다 훨씬 큰 크기의 가상 메모리 주소를 사용하고 실제로 RAM에 올릴 때는 가상 메모리 매핑으로 해결한다.
  2. 이렇게 프로세스 하나가 하드웨어를 독점하고 있다는 착각을 실현하기 위해서, 중간에 하드웨어 접근을 통제해줄 존재인 '커널'을 만든 것이다! 프로세스가 직접 하드웨어 자원을 건드리지 못하게 만듦으로써 추상화를 실현할 수 있다.

그렇담 이제 개념을 하나씩 파보자. ㅎㅎ

Private Address Space

각 프로세스는 자신만의 사적 주소 공간을 가진다.

이 공간은 다른 프로세스가 접근해서 읽을 수 없고, 값을 바꿀 수도 없다. 특정 프로세스만을 위한 독립적인 공간이다.

 

PID

pid는 프로세스가 os로부터 할당받는 값이다. 1은 os가 시작되자 마자 실행되는 (os의 실행을 돕는) 프로세스에게 할당되기 때문에 우리가 만들어내는 프로세스는 2부터 할당 받는다. pid는 유저 공간에 저장되는 값은 아니고 커널이 관리하는 값이라서 getpid()로 커널을 호출해서 가져올 수 있다. 커널이 관리하는 걸 보니 쉽게 변경되지 않도록 안정성과 보안 유지가 필요한 값이라는 것을 알 수 있다.

fork로 자식 스레드가 생성되는 과정

 

fork()를 하면 부모 프로세스의 페이지들을 자식 프로세스에 모두 복사하여 자식이 부모와 똑같은 가상 메모리 공간을 가지도록 한다.

  • 부모의 가상 주소 공간 전체를 그대로 복사하므로, 자식과 부모 프로세스의 지역 변수, 전역 변수 가상 주소를 찍어보면 같은 값이 출력된다. 물론 다른 세그먼트에 저장된 값들도 다 똑같은 위치에 있을 것이다. 복사되었으니까 !
  • 그러나 서로 독립된 별도의 가상 메모리 공간이므로 값을 변경하면 한 쪽이 변경되었다고 다른 쪽도 같이 변경되지 않는다.

그렇다면 코드 상으로 자식과 부모의 실행 흐름을 분기시키는 건 어떻게 할까?

fork() 반환 값으로 구분한다. 

pid_t pid = fork();

fork() 반환 값으로 pid_t 타입의 pid값을 받아서 저장해두는데 얘가 0이면 현재 실행 흐름이 자식이고 그렇지 않으면 부모이다.

나는 이걸 이해하는 게 진짜 어려웠다... ㅋㅋ ㅠㅠ

두 프로세스는 fork 함수 호출 이후 실행 흐름이 분기되는데,

부모 프로세스는 리턴값으로 자식 프로세스의 pid를 받아서 저장하고

자식 프로세스는 리턴값으로 0을 받아서 저장한다.

 

그리고 이렇게 구현할 수 있다.

if (pid == 0) {
    // 자식 프로세스
} else {
    // 부모 프로세스 (pid == 자식의 pid)
}

 

내가 제일 헷갈렸던 부분인데, 알고 보니 간단한 개념이었당 ㅎ 

fork()로 부모가 자식 프로세스를 생성하면

  1. 현재 프로세스 실행 흐름과 완전히 동일한 자식 프로세스 하나가 생성되고, 가상 주소 공간도 전체가 똑같이 복사된다.
  2. 자식 프로세스로 실행 흐름이 분기된 후 pid로 둘을 구분할 수 있다.
  3. 독립된 별도의 가상 메모리 공간이므로 한 쪽에 변경을 가한다고 해서 다른 쪽이 함께 변경되지는 않는다. 

예제로 이해하면 다음과 같다.

int main(){
    pid_t pid;
    int x = 1;
    
    pid = Fork(); 
    
    if(pid==0){ /*Child*/
        printf("child : x=%d\n", ++x);
    	exit(0); 
    }
    
    /* Parent */
    printf("parent: x=%d\n", --x);
    exit(0);
}

 

 

 

 

 

 

 

ref:

https://codingdog.tistory.com/entry/%EB%A6%AC%EB%88%85%EC%8A%A4-fork-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EB%A5%BC-%EC%83%9D%EC%84%B1%ED%95%9C%EB%8B%A4

https://devconnected.com/understanding-processes-on-linux/

https://blog.naver.com/sqlmvp/140192006270