같은 프로그램의 처리를 여러 개의 프로세스가 나눠서 처리한다.
전혀 다른 프로그램을 생성한다.
위의 생성 목적에 fork()
와 execve()
함수를 사용한다. 시스템 내부에서는 clone()
과 execve()
시스템 콜을 호출한다.
'같은 프로그램의 처리를 여러 개의 프로세스가 나눠서 처리한다.' 에는 fork()
함수만 사용한다.
fork()
함수를 실행하면 실행한 프로세스와 함꼐 새로운 프로세스가 1개 생성된다.
fork()
함수의 리턴값이 각기 다른 것을 이용하여 부모 프로세스와 자식 프로세스가 서로 다른 코드를 실행하도록 분가한다.fork()
함수를 리턴할 때 부모 프로세스는 자식 프로세스의 프로세스 ID를, 자식 프로세스는 0을 리턴한다. 이를 이용하여 부모 프로세스와 자식 프로세스의 처리를 나눠서 실행한다.
fork.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <err.h>
static void child()
{
printf("child: %d.\n", getpid());
exit(EXIT_SUCCESS);
}
static void parent(pid_t pic_c){
printf("parent: %d, child of parent: %d\n", getpid(), pic_c);
exit(EXIT_SUCCESS);
}
int main(void)
{
pid_t ret;
ret = fork();
if(ret==-1)
err(EXIT_FAILURE, "fork() failed");
if(ret==0){
child();
}
else{
parent(ret);
}`ㅏ
err(EXIT_FAILURE, "shouldn't reach here");
}
실행 결과
이 결과로 프로세스 ID가 7076인 프로스세가 분기 실행되어 부모 프로세스에게 프로세스 ID 7077번의 자식 프로세스가 생성되었다는 점과 fork()
함수 실행 뒤에 두 프로세스의 처리가 분기되어 실행되고 있음을 알 수 있다.
전혀 다른 프로그램을 생성할 때에는 execve()
함수를 사용한다.
커널이 각각의 프로세스를 실행되기까지의 흐름
1. 실행 파일을 읽은 다음 프로세스의 메모리 맵에 필요한 정보를 읽어 들인다.
2. 현재 프로세스의 메모리를 새로운 프로세스의 데이터로 덮어쓰인다.
3. 새로운 프로세스의 첫 번째 명령부터 실행한다.
전혀 다른 프로그램을 생성하는 경우 프로세스의 수가 증가하는 것이 아니라 기존의 프로세스를 별로의 프로세스로 변경하는 방식으로 수행된다.
별도의 프로그램 생성 순서
전체 순서를 구체적으로 살펴본다. 일단, 실행 파일을 읽고 프로세스의 메모리 맵에 필요한 정보를 읽어 들인다. 실행 파일은 프로세스의 실행 중에 사용하는 코드와 데이터 이외에도 다음과 같은 필요하다
엔트리 포인트(entry point) 또는 진입점은 제어가 운영 체제에서 컴퓨터 프로그램으로 이동하는 것을 말하며, 프로세서는 프로그램이나 코드에 진입해서 실행을 시작하는 것이다.
리눅스의 실행 파일은 단순한 것이 아니라 ELF(Executabke and Linkable Format)
라는 형식을 사용한다. ELF 형식의 각종 정보는 readelf
명령어로 자세히 살펴 볼 수 있다.
-h
옵션을 지정하면 시작 주소를 얻을 수 있다.
readelf -h /bin/sleep
이 프로그램의 엔트리 포인트는 0x2b90
이다
-S
를 통해서는 데이터 영역의 파일상의 오프셋, 사이즈, 메모리 맵 시작 주소를 얻을 수 있다.
이해 해야하는 사항
.text
이면 코드 영역의 정보를 .data
면 데이터 영역의 정보를 의미한다.프로그램 실행 시에 작성된 프로세스 메모리 맵은 /proc/pid/maps
파일을 통해 알 수 있다.
이 글을 보고 리눅스 개발자가 되기로 마음 먹었습니다.