Linux의 많은 프로그램과 매뉴얼 페이지에서 fork()
. 왜 우리는 사용해야 fork()
하며 그 목적은 무엇입니까?
답변:
fork()
Unix에서 새로운 프로세스를 만드는 방법입니다. 전화를 걸면 fork
자체 주소 공간 이있는 자체 프로세스의 복사본을 만드는 것 입니다. 이를 통해 여러 작업이 마치 각자가 시스템의 전체 메모리를 가지고있는 것처럼 서로 독립적으로 실행할 수 있습니다.
다음은의 몇 가지 사용 예입니다 fork
.
fork
명령 줄에서 호출하는 프로그램을 실행합니다.fork
각각 자체 주소 공간에서 요청을 처리하는 여러 서버 프로세스를 만드는 데 사용 합니다. 한 사람이 죽거나 메모리가 누수되는 경우 다른 사람은 영향을받지 않으므로 내결함성을위한 메커니즘으로 작동합니다.fork
별도의 프로세스 내에서 각 페이지를 처리 하는 데 사용 합니다. 이렇게하면 한 페이지의 클라이언트 측 코드가 전체 브라우저를 다운시키는 것을 방지 할 수 있습니다.fork
MPI를 사용하여 작성된 것과 같은 일부 병렬 프로그램에서 프로세스를 생성하는 데 사용됩니다 . 이는 자체 주소 공간이없고 프로세스 내에 존재 하는 스레드 사용과 다릅니다 .fork
간접적으로 자식 프로세스를 시작합니다. 예를 들어 subprocess.Popen
Python에서 와 같은 명령을 사용할 때마다 fork
하위 프로세스가되어 출력을 읽습니다. 이를 통해 프로그램이 함께 작동 할 수 있습니다.fork
셸에서의 일반적인 사용법은 다음과 같습니다.
int child_process_id = fork();
if (child_process_id) {
// Fork returns a valid pid in the parent process. Parent executes this.
// wait for the child process to complete
waitpid(child_process_id, ...); // omitted extra args for brevity
// child process finished!
} else {
// Fork returns 0 in the child process. Child executes this.
// new argv array for the child process
const char *argv[] = {"arg1", "arg2", "arg3", NULL};
// now start executing some other program
exec("/path/to/a/program", argv);
}
쉘은를 사용하여 자식 프로세스를 생성하고 exec
완료 될 때까지 기다린 다음 자체 실행을 계속합니다. 이런 식으로 포크를 사용할 필요는 없습니다. 병렬 프로그램처럼 항상 많은 자식 프로세스를 생성 할 수 있으며 각 프로세스는 동시에 프로그램을 실행할 수 있습니다. 기본적으로 Unix 시스템에서 새 프로세스를 만들 때마다 fork()
. Windows에 해당하는 경우 CreateProcess
.
더 많은 예제와 더 긴 설명을 원한다면 Wikipedia 에 적절한 요약이 있습니다. 그리고 여기에 몇 가지 슬라이드는 어떻게 여기에 현대적인 운영 시스템의 프로세스, 스레드 및 동시 작동합니다.
fork()
UNIX에서 새로운 프로세스를 만드는 방법 이라고 말하지만 현명하게 말하면 적어도 다른 하나가 posix_spawn()
있습니다..
fork ()는 유닉스가 새로운 프로세스를 만드는 방법입니다. fork ()를 호출 한 시점에서 프로세스가 복제되고 두 개의 다른 프로세스가 거기에서 실행을 계속합니다. 그 중 하나 인 자식은 fork ()가 0을 반환합니다. 다른 하나는 부모 인 fork ()가 자식의 PID (프로세스 ID)를 반환하도록합니다.
예를 들어 셸에 다음을 입력하면 셸 프로그램은 fork ()를 호출 한 다음 자식에서 전달한 명령 (이 경우 telnetd)을 실행하고 부모는 프롬프트를 다시 표시합니다. 백그라운드 프로세스의 PID를 나타내는 메시지로.
$ telnetd &
새 프로세스를 만드는 이유는 운영 체제가 동시에 많은 작업을 수행 할 수있는 방법입니다. 이것이 바로 프로그램을 실행할 수있는 이유입니다. 프로그램이 실행되는 동안 다른 창으로 전환하여 다른 작업을 수행 할 수 있습니다.
fork ()는 자식 프로세스를 만드는 데 사용됩니다. fork () 함수가 호출되면 새 프로세스가 생성되고 fork () 함수 호출은 자식과 부모에 대해 다른 값을 반환합니다.
반환 값이 0이면 자신이 자식 프로세스임을 알고 있고 반환 값이 숫자 (하위 프로세스 ID)이면 부모임을 알 수 있습니다. (음수이면 포크가 실패하고 자식 프로세스가 생성되지 않은 것입니다)
fork ()는 부모와 동일한 새 자식 프로세스를 만듭니다. 따라서 코드에서 실행하는 모든 것은 두 프로세스 모두에서 실행됩니다. 예를 들어 서버가 있고 여러 요청을 처리하려는 경우 매우 유용합니다.
응용 프로그램을 작성하는 경우 일상적인 프로그래밍에서 포크를 사용할 필요가 없습니다.
프로그램이 어떤 작업을 수행하기 위해 다른 프로그램을 시작하도록하려는 경우에도 C 및 perl의 "system"과 같이 배후에서 포크를 사용하는 다른 간단한 인터페이스가 있습니다.
예를 들어 응용 프로그램에서 bc와 같은 다른 프로그램을 실행하여 계산을 수행하도록하려면 'system'을 사용하여 실행할 수 있습니다. 시스템은 새로운 프로세스를 생성하기 위해 '포크'를 수행 한 다음 해당 프로세스를 bc로 전환하기 위해 'exec'를 수행합니다. BC가 완료되면 시스템이 프로그램에 제어권을 반환합니다.
다른 프로그램을 비동기 적으로 실행할 수도 있지만 그 방법을 기억할 수 없습니다.
서버, 셸, 바이러스 또는 운영 체제를 작성하는 경우 포크를 사용하고 싶을 가능성이 높습니다.
system()
. fork()
내 C 코드가 파이썬 스크립트를 실행하기를 원하기 때문에 읽었습니다 .
시스템 호출 fork ()는 프로세스를 생성하는 데 사용됩니다. 인수를 취하지 않고 프로세스 ID를 반환합니다. fork ()의 목적은 호출자의 자식 프로세스가되는 새 프로세스를 만드는 것입니다. 새 자식 프로세스가 생성되면 두 프로세스 모두 fork () 시스템 호출에 따라 다음 명령을 실행합니다. 그러므로 우리는 부모와 자식을 구별해야합니다. 이는 fork ()의 반환 된 값을 테스트하여 수행 할 수 있습니다.
fork ()가 음수 값을 반환하면 자식 프로세스 생성에 실패한 것입니다. fork ()는 새로 생성 된 자식 프로세스에 0을 반환합니다. fork ()는 부모에게 자식 프로세스의 프로세스 ID 인 양수 값을 반환합니다. 반환 된 프로세스 ID는 sys / types.h에 정의 된 pid_t 유형입니다. 일반적으로 프로세스 ID는 정수입니다. 또한 프로세스는 getpid () 함수를 사용하여이 프로세스에 할당 된 프로세스 ID를 검색 할 수 있습니다. 따라서 fork ()에 대한 시스템 호출 후 간단한 테스트를 통해 어떤 프로세스가 자식인지 알 수 있습니다. Unix는 부모 주소 공간의 정확한 사본을 만들어 자식에게 제공합니다. 따라서 부모 및 자식 프로세스에는 별도의 주소 공간이 있습니다.
위의 사항을 명확히하기 위해 예를 들어 이해합시다. 이 예는 상위 프로세스와 하위 프로세스를 구분하지 않습니다.
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#define MAX_COUNT 200
#define BUF_SIZE 100
void main(void)
{
pid_t pid;
int i;
char buf[BUF_SIZE];
fork();
pid = getpid();
for (i = 1; i <= MAX_COUNT; i++) {
sprintf(buf, "This line is from pid %d, value = %d\n", pid, i);
write(1, buf, strlen(buf));
}
}
위의 프로그램이 fork () 호출 지점까지 실행한다고 가정합니다.
fork () 호출이 성공적으로 실행되면 Unix는 두 개의 동일한 주소 공간 복사본을 만듭니다. 하나는 부모 용이고 다른 하나는 자식 용입니다. 두 프로세스 모두 fork () 호출 다음의 다음 문에서 실행을 시작합니다. 이 경우 두 프로세스 모두 할당에서 실행을 시작합니다.
pid = .....;
두 프로세스 모두 시스템 호출 fork () 직후에 실행을 시작합니다. 두 프로세스 모두 동일하지만 별도의 주소 공간을 가지고 있기 때문에 fork () 호출 전에 초기화 된 변수는 두 주소 공간에서 동일한 값을 갖습니다. 모든 프로세스에는 자체 주소 공간이 있으므로 수정 사항은 다른 프로세스와 독립적입니다. 즉, 부모가 변수의 값을 변경하면 수정은 부모 프로세스의 주소 공간에있는 변수에만 영향을줍니다. fork () 호출에 의해 생성 된 다른 주소 공간은 동일한 변수 이름을 가지고 있어도 영향을받지 않습니다.
printf 대신 쓰기를 사용하는 이유는 무엇입니까? 이는 printf ()가 "버퍼링"되어 있기 때문입니다. 이는 printf ()가 프로세스의 출력을 함께 그룹화한다는 것을 의미합니다. 부모 프로세스에 대한 출력을 버퍼링하는 동안 자식은 또한 printf를 사용하여 버퍼링 될 일부 정보를 인쇄 할 수 있습니다. 결과적으로 출력이 즉시 화면에 전송되지 않으므로 예상 결과의 올바른 순서를 얻지 못할 수 있습니다. 더 나쁜 것은 두 프로세스의 출력이 이상한 방식으로 혼합 될 수 있다는 것입니다. 이 문제를 극복하기 위해 "언 버퍼 드"쓰기 사용을 고려할 수 있습니다.
이 프로그램을 실행하면 화면에 다음이 표시 될 수 있습니다.
................
This line is from pid 3456, value 13
This line is from pid 3456, value 14
................
This line is from pid 3456, value 20
This line is from pid 4617, value 100
This line is from pid 4617, value 101
................
This line is from pid 3456, value 21
This line is from pid 3456, value 22
................
프로세스 ID 3456은 상위 또는 하위에 할당 된 것일 수 있습니다. 이러한 프로세스가 동시에 실행되기 때문에 출력 라인이 예측할 수없는 방식으로 혼합됩니다. 또한 이러한 행의 순서는 CPU 스케줄러에 의해 결정됩니다. 따라서이 프로그램을 다시 실행하면 완전히 다른 결과를 얻을 수 있습니다.
Fork ()는 모든 본문이 작성한 새 프로세스를 만드는 데 사용됩니다.
다음은 이진 트리 형태로 프로세스를 생성하는 코드입니다 ....... 이진 트리에서 프로세스를 생성하려는 수준의 수를 스캔하라는 메시지가 표시됩니다.
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
int main()
{
int t1,t2,p,i,n,ab;
p=getpid();
printf("enter the number of levels\n");fflush(stdout);
scanf("%d",&n);
printf("root %d\n",p);fflush(stdout);
for(i=1;i<n;i++)
{
t1=fork();
if(t1!=0)
t2=fork();
if(t1!=0 && t2!=0)
break;
printf("child pid %d parent pid %d\n",getpid(),getppid());fflush(stdout);
}
waitpid(t1,&ab,0);
waitpid(t2,&ab,0);
return 0;
}
산출
enter the number of levels
3
root 20665
child pid 20670 parent pid 20665
child pid 20669 parent pid 20665
child pid 20672 parent pid 20670
child pid 20671 parent pid 20670
child pid 20674 parent pid 20669
child pid 20673 parent pid 20669
먼저 fork () 시스템 호출이 무엇인지 이해해야합니다. 설명하겠습니다
fork () 시스템 호출은 부모 프로세스의 정확한 복제를 생성하고, 부모 스택, 힙, 초기화 된 데이터, 초기화되지 않은 데이터의 복제를 만들고 부모 프로세스와 읽기 전용 모드로 코드를 공유합니다.
Fork 시스템 호출은 copy-on-write 기반으로 메모리를 복사합니다. 즉, 복사가 필요할 때 자식이 가상 메모리 페이지를 만듭니다.
이제 fork ()의 목적 :
fork()
자식 프로세스를 생성하는 데 사용됩니다. 일반적으로 스레딩과 비슷한 종류의 상황에서 사용되지만 차이점이 있습니다. 스레드와 달리fork()
전체 개별 프로세스를 생성합니다. 즉, 자식과 부모가 서로 직접 복사되는 시점에서fork()
호출 완전히 분리되어 있고, 둘 다 다른 사람의 메모리 공간에 액세스 할 수 없습니다 (정상적인 문제로 이동하지 않음). 다른 프로그램의 메모리에 액세스합니다).
fork()
일부 서버 응용 프로그램에서 여전히 사용되며, 대부분 사용자 요청을 처리하기 전에 권한을 삭제하는 * NIX 시스템에서 루트로 실행되는 응용 프로그램입니다. 여전히 다른 사용 사례가 있지만 대부분 사람들은 이제 멀티 스레딩으로 이동했습니다.
fork ()와 새로운 프로세스를 시작하기 위해 exec () 함수를 사용하는 것에 대한 근거 는 유닉스 스택 교환에 대한 비슷한 질문에 대한 답변 에서 설명됩니다 . .
기본적으로 fork는 현재 프로세스를 복사하기 때문에 프로세스에 대해 가능한 모든 옵션이 기본적으로 설정되므로 프로그래머는이를 제공하지 않습니다.
반대로 Windows 운영 체제에서 프로그래머는 훨씬 더 복잡하고 새 프로세스의 매개 변수를 정의하기 위해 다양한 구조를 채워야하는 CreateProcess 함수를 사용해야합니다.
요약하자면, forking의 이유 (exec'ing과 비교)는 새로운 프로세스 생성의 단순성 때문입니다.
Fork () 시스템 호출을 사용하여 자식 프로세스를 만듭니다. 부모 프로세스의 정확한 복제입니다. Fork는 상위에서 스택 섹션, 힙 섹션, 데이터 섹션, 환경 변수, 명령 줄 인수를 복사합니다.
포크 () 함수가 호출되는 기존의 프로세스를 복제하여 새로운 프로세스를 생성하는 데 사용된다. 이 함수가 호출 된 기존 프로세스가 부모 프로세스가되고 새로 생성 된 프로세스가 자식 프로세스가됩니다. 이미 언급했듯이 자녀는 부모의 사본이지만 몇 가지 예외가 있습니다.
하위에는 운영 체제에서 실행되는 다른 프로세스와 마찬가지로 고유 한 PID가 있습니다.
자식에는 자신
을 만든 프로세스 의 PID와 동일한 부모 프로세스 ID가 있습니다.
리소스 사용률 및 CPU 시간 카운터는 자식 프로세스에서 0으로 재설정됩니다.
하위의 대기중인 신호 세트가 비어 있습니다.
자식은 부모로부터 타이머를 상속하지 않습니다.
예 :
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int var_glb; /* A global variable*/
int main(void)
{
pid_t childPID;
int var_lcl = 0;
childPID = fork();
if(childPID >= 0) // fork was successful
{
if(childPID == 0) // child process
{
var_lcl++;
var_glb++;
printf("\n Child Process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
else //Parent process
{
var_lcl = 10;
var_glb = 20;
printf("\n Parent process :: var_lcl = [%d], var_glb[%d]\n", var_lcl, var_glb);
}
}
else // fork failed
{
printf("\n Fork failed, quitting!!!!!!\n");
return 1;
}
return 0;
}
이제 위의 코드가 컴파일되고 실행되면 :
$ ./fork
Parent process :: var_lcl = [10], var_glb[20]
Child Process :: var_lcl = [1], var_glb[1]