앞에서 프로세스의 PID에 대해서 알아보았는데요,
굉장히 간단하게 getpid() 시스템 콜을 이용해서 자신의 프로세스 ID를 알 수 있었습니다.
이번에는 정말 새로운 프로세스를 생성하는 시스템 콜에 대해 알아보고, 각 차이점에 대해 알아봅시다.
리눅스에서 시스템 콜을 이용하여 프로세스를 실행하면 두가지 방식이 있습니다. 프로그램 바이너리를 메모리에 적재하고 프로세스의 주소공간에 있는 이전 내용을 대체하고 새로운 프로세스를 실행하거나, 부모 프로세스의 주소공간 등을 그대로 복사 한 후 새로운 프로세스를 실행하는 방식입니다.
총 3가지 시스템 콜에 대해서 알아볼텐데요, exec 계열의 시스템 콜, fork 그리고 vfork 까지 알아볼게요.
먼저, exec 시스템 콜에 대해 알아보겠습니다.
exec 계열의 시스템 콜은 fork나 vfork 시스템 콜처럼 한가지가 아니라 여러가지 함수로 제공되는데요,
이 중에서 가장 단순한 시스템 콜은 execl()입니다.
저는 exec() 계열 시스템 콜은 새로운 프로세스를 생성한다라고 보단 대체한다라고 생각하는데요,
exec() 계열의 시스템 콜의 인자로 실행할 프로세스, 즉 프로그램 이나 쉘 명령어 등이 위치한 경로를
인자로 넘겨주게 되면, 현재 프로세스는 실행이 중단되고 exec() 계열 시스템 콜의 인자로 넘어온 Path의 프로그램이 실행됩니다. (위의 예제는 나중에 fork 시스템 콜 예제와 함께 알아보겠습니다.)
시스템 콜의 형태는 아래와 같습니다.
#include <unistd.h> int execl(const char *path, const char *args, ...); | ||
입력 인자 | const char* path | 실행할 프로그램의 경로 |
const char* args | 프로그램 실행 시 필요한 인자 | |
... | 추가적인 가변인자 (가장 마지막은 항상 NULL 이여야 한다) | |
반환값 | int | 반환값이 일반적으로는 없음. 실패시에 -1 반환함. |
exec 시스템 콜은 반환값이 없습니다. 실패했을 경우 -1을 반환하고 errno 값을 해당 에러에 맞게
변경해서 설정해 줄 뿐입니다. 입력 인자에 경우 첫째 실행할 프로그램의 경로와 필요한 인자가 추가적으로 들어가며, 마지막엔 항상 NULL로 더 이상의 입력 인자가 없음을 알려주어야 합니다.
exec 시스템 콜 호출에 성공하면 위에서 말씀드렸듯 새로운 프로세스로 대체된다고 하였습니다.
프로그램의 진행 시작점을 변경하여 이전의 프로세스는 제거(?)하고 새로운 프로세스를 실행하죠.
exec 관련 시스템 콜은 다음에 한번 더 정리하도록 하죠.
이때 궁금점이 하나 생기지 않으세요?? 프로세스를 설명드릴 때 부모와 자식 프로세스에 대해 말씀드렸는데, 위의 방식은 부모프로세스가 자식프로세스를 생성하고 사라지는(?), 희생하는(?) 모습입니다.
내가 원하는 건 이런게 아니라 부모 프로세스는 자신의 할 일을 하고, 자식프로세스를 생성한 후
자식프로세스가 할일을 하자! 라고 요구하시는 분들이 계십니다. 이 때 많이 사용되는 것이 fork이고
exec 시스템 콜도 병행해서 사용함으로써 위의 요구사항을 해결할 수 있습니다.
그럼 fork 시스템 콜에 대해 한번 알아보겠습니다.!!
아래 예제를 함께 보시죠, fork()를 실행하면 생성한 자식 프로세스의 PID값이 리턴되어 반환됩니다.
하지만 자식프로세스는 0값이 반환되죠, 아래처럼 PID값이 0보다 크거나, 아니면 0이거나 또는 프로세스 생성에 실패한 경우 음수 값을 반환하는 것을 이용하여 부모와 자식의 작업을 나뉘게됩니다.
위 소스코드를 실핸하면 아래와 같은 경우가 나오죠 ^^.
그런데, 위에서 말씀드렸듯이 fork 시스템 콜은 자신의 부모를 복사하여 만든다고 하였습니다.
그렇기에 수행 명령어나 메모리 등이 공유되어 동일은 코드에서 작업을 나뉘게 되죠,
하지만 실제로는 다른 명령어를 직접 실행하긴 원하는 분들이 많으시죠, 이 때 fork 시스템 콜과
exec 시스템 콜을 함께 사용하는 것입니다. 아래의 소스코드를 보시죠,
동일하게 위 소스와 같은데요, 새로 추가된 부분은 자식프로세스의 작업이 포함된 조건문에서 execv 시스템 콜을 사용하여 ls -a란 명령어를 수행합니다. 이렇게 되면 execv가 실행되며 자식 프로세스는 부모에서
복사한 것을 종료하고 execv가 실행한 ls 프로세스가 실행되게 됩니다. 이해 되셨나요?
그럼 이렇게 아래와 같은 결과를 얻게되죠, 자식 프로세스는 대체 되며 printf()로 출력한 "I am child" 라는
문자열을 exec아래로 옮기면 출력되지 않을겁니다.
계속해서 vfork 시스템 콜에 대해 알아보죠,
vfork 시스템 콜은 현재 사용하기에 추천드리지 않습니다. 처음 vfork 시스템 콜의 사용유무로 굉장히 많은
논란이 있었는데요, 그 이유는 fork로 인해 부모 프로세스의 공간을 무조건적인 복사가 굉장히 프로그램 실행에 부하를 많이 준다는 것이죠. 하나 또는 두개가 아닌 수십개의 프로세스가 실행되게 되면 그 복사에 따른 부하는 무시할 수 없겠죠? 그래서 처음 제안된 것이 vfork 시스템 콜입니다.
사용 방법은 간단합니다. fork 시스템 콜과 같죠.
결과 또한 fork 시스템 콜과 같죠??
실제로 vfork 시스템 콜은 부모에서 복사를 하지 않는 대신 부모 프로세스의 동작을 멈춰버립니다. 짧은 수행으로 종료하던지 또는 exit() 시스템 콜로 프로세스를 종료해야지만 다시 부모 프로세스가 동작하는 방식이죠. 이 때 문제가 발생합니다. 만약 자식 프로세스가 중간에 죽어버린다면? 부모 프로세스에 까지
그 영향을 미치게 되는 것이죠.
아래 소스를 한번 보시죠.
위 코드를 실행하면 아래와 같습니다. execv 시스템 콜에 잘못된 인자(존재하는 않은)를 넘겨주면
중간에 자식 프로세스에서 오류가 발생하여 죽어버리고, 그 영향은 부모 프로세스에 까지 미치게 됩니다. 원하는 결과를 출력해 주지 않죠....
이러한 문제점을 해결하기 위한 방안이 나왔습니다.!!
그 방법은 실제 자식 프로세스를 생성했을 당시 부모프로세스의 공간을 복사하지 않는겁니다 !!
그럼 어떻게 하냐구요? 부모프로세스의 공간을 가리키고 있는 포인터를 넘겨주게되죠, 만약 자식프로세스에서 부모프로세스의 공간의 데이터를 읽기만 한다면 포인터로 충분히 가능합니다. 그리고 만약 쓰기작업 즉, 데이터를 수정하게 되면 그때 공간이 복사되는 것이죠. 이렇게 되면 실질적으로 사용하는 프로세스에게만 복사가 이뤄지므로 부하를 줄일 수 있습니다.!!
이해 되셨나요? 모두 쉽게 이해하셨길 바랍니다 ^^
다음에는 프로세스의 종료에 대해 좀 더 알아볼게요 !!
'Linux > System Programming' 카테고리의 다른 글
[프로세스 관리] 3. 프로세스 종료 (0) | 2015.08.19 |
---|---|
리눅스 시스템 에러 - errno (0) | 2015.08.13 |
[프로세스 관리] 1. 프로세스의 ID 얻기 (0) | 2015.04.12 |
[프로세스 관리] 0. 프로그램 / 프로세스 / 스레드 (0) | 2015.04.12 |
[파일입출력] 6. 파일 안의 데이터 잘라내기 (0) | 2015.02.12 |