포크 (시스템 호출)
"오늘의AI위키"의 AI를 통해 더욱 풍부하고 폭넓은 지식 경험을 누리세요.
1. 개요
`fork`는 1962년 처음 언급된 개념으로, 운영 체제에서 새로운 프로세스를 생성하는 시스템 호출이다. `fork`를 호출하면 부모 프로세스의 복사본인 자식 프로세스가 생성되며, 이후 자식 프로세스는 일반적으로 `exec` 시스템 호출을 통해 다른 프로그램으로 덮어쓰여진다. 유닉스 철학에서 `fork`는 필터 개발을 장려하는 중요한 부분이며, 프로세스 간 통신(IPC)을 구현하는 데 사용된다. `fork`는 다양한 변종이 있으며, 다른 운영 체제에서는 다른 방식으로 구현되기도 한다.
더 읽어볼만한 페이지
- 시스템 호출 - Chroot
`chroot`는 유닉스 계열 운영체제에서 프로세스의 루트 디렉토리를 변경하는 시스템 호출 및 환경으로, 다양한 목적으로 사용되지만 보안 취약점과 제한 사항을 가지며, `fakechroot`는 root 권한 없이 유사한 환경을 제공한다. - 시스템 호출 - Stat (시스템 호출)
`stat` 시스템 호출은 파일의 크기, 접근 권한, 수정 시간 등 다양한 속성 정보를 담은 `stat` 구조체를 반환하여 파일 상태 정보를 가져오는 데 사용되며, C 언어에서 `stat()`, `lstat()`, `fstat()` 함수와 대용량 파일 지원을 위한 `stat64()`, `lstat64()`, `fstat64()` 함수를 통해 호출된다. - C POSIX 라이브러리 - POSIX 스레드
POSIX 스레드는 C 프로그래밍 언어에서 스레드를 생성, 관리, 동기화하는 데 사용되는 함수들의 모음으로, pthread.h 헤더 파일과 스레드 라이브러리로 구현된다. - C POSIX 라이브러리 - 글로브 (프로그래밍)
글로브는 파일 이름 패턴을 해석하고 확장하는 기능으로, 유닉스 와일드카드 확장에서 시작하여 다양한 프로그래밍 환경에서 파일 검색 및 문자열 패턴 매칭에 활용되며, 와일드카드 문자를 사용하여 여러 파일이나 문자열을 선택하는 단순하고 직관적인 구문을 제공한다. - 프로세스 - 문맥 교환
문맥 교환은 운영 체제에서 CPU가 여러 프로세스나 스레드를 번갈아 실행하기 위해 현재 작업 상태를 저장하고 다른 작업 상태를 복원하는 과정으로, 멀티태스킹 환경에서 필수적인 기술이며 프로세스 제어 블록을 통해 관리된다. - 프로세스 - 데몬 (컴퓨팅)
데몬은 운영 체제에서 사용자와 상호 작용 없이 백그라운드에서 실행되며 시스템의 다양한 작업을 처리하는 프로세스이다.
포크 (시스템 호출) | |
---|---|
컴퓨터 과학 | |
종류 | 시스템 호출 |
사용 | 새로운 프로세스 생성 |
반환 값 | 성공 시: 새로운 프로세스의 PID 실패 시: -1 |
관련 시스템 호출 | exec wait clone posix_spawn |
2. 역사
포크 시스템 호출은 초창기 유닉스에 존재했으며,[23] 이는 초기 지니 시분할 시스템에서 가져온 것이다.[24] 포크는 POSIX에 의해 표준화되었다.[25]
멀티태스킹 운영 체제에서 프로세스(실행 중인 프로그램)는 다른 프로그램을 실행하기 위해 새로운 프로세스를 생성해야 할 필요가 있다. 포크와 그 변종들은 일반적으로 유닉스 계열 운영 체제에서 이러한 작업을 수행하는 유일한 방법이다. 프로세스가 다른 프로그램의 실행을 시작하기 위해서는 먼저 자기 자신의 사본을 만든다. 자식 프로세스라고 불리는 이 사본은 exec 시스템 호출을 호출하여 다른 프로그램에 자신을 겹쳐놓는다. 즉, 다른 프로그램을 선호하면서 이전 프로그램의 실행을 중단시킨다.[23]
포크 개념에 대한 초기 언급 중 하나는 1962년 멜빈 콘웨이의 ''다중 프로세서 시스템 설계'' 논문에서 나타났다.[1] 콘웨이의 논문은 L. 피터 도이치가 프로젝트 지니 시분할 시스템에서 포크를 구현하게 했으며, 이 개념은 켄 톰슨이 리서치 유닉스에 처음 도입할 때 차용했다.[2][3][4] 포크는 이후 POSIX에서 표준 인터페이스가 되었다.[5]
3. 작동 방식
포크 동작은 자식을 위해 별도의 주소 공간을 만든다. 자식 프로세스는 부모 프로세스의 정확히 동일한 사본의 메모리 세그먼트를 가지고 있다. SunOS-4.0에서 비롯된 가상 메모리 모델을 따르는 현대의 유닉스 운영 체제에서는 쓰기 시 복사(Copy-on-Write) 시맨틱이 구현되어 물리 메모리는 실제로 복사되지 않아도 된다. 그 대신, 두 프로세스 모두에 존재하는 가상 메모리 페이지가 물리 메모리의 동일 사본을 참조하며, 둘 중 하나가 이러한 페이지에 기록하기 전까지 이러한 일을 할 수 있다. 그 뒤에 복제가 이루어진다. 이러한 최적화는 포크가 exec와 결합하여 새로운 프로그램을 실행하는 일반적인 경우에 매우 중요하다. 보통 자식 프로세스는 다른 프로그램의 실행을 선호하여 자체 프로그램의 실행을 중단하기 이전에 조그마한 집합의 동작만을 수행한다.[24]
프로세스가 포크를 호출할 때, 이를 부모 프로세스로 간주하며 새롭게 작성되는 프로세스는 자식이라 불린다. 포크 이후, 두 프로세스 모두 동일한 프로그램을 실행할 뿐 아니라 둘 다 시스템 호출을 호출한 것처럼 실행을 계속한다. 그 뒤 이 호출의 반환값을 조사하여 상태 및 자식/부모, 동작을 이어서 결정하게 된다.[25] 포크 시스템 호출은 초기 지니 시분할 시스템에서 가져온 것이다. 포크는 POSIX에 의해 표준화되었다.
`fork()` 시스템 호출을 실행하면, 원래 운영체제가 자식 프로세스를 위해 부모 프로세스가 가진 모든 페이지를 물리 메모리의 다른 위치에 복사해야 한다. 그러나 자식 프로세스가 `fork()` 직후 "exec" 시스템 콜(실행 파일을 실행할 때 사용)을 실행하거나 종료하는 경우에는 그럴 필요가 없다. 부모 프로세스가 어떤 명령을 실행하기 위해서만 자식 프로세스를 생성한 경우, 자식 프로세스의 주소 공간은 실행해야 할 명령으로 즉시 대체되므로 부모 프로세스의 페이지들을 복사할 필요가 없다.
쓰기 시 복사에서는 fork 시 부모 프로세스의 페이지들을 자식 프로세스에 복사하지 않는다. 대신 페이지들은 부모 프로세스와 자식 프로세스 간에 공유된다. 부모 또는 자식 프로세스 중 하나가 페이지 내용을 갱신하려고 할 때, 해당 페이지에 대해서만 복사본을 생성하고, 쓰기를 시도한 프로세스의 해당 페이지만 갱신된다. 쓰기를 한 프로세스는 이후 그 새로 복사한 페이지를 사용한다.
3. 1. 변종
`vfork`는 3BSD 버전의 유닉스에서 시작된 fork의 변종으로, 제한적인 상황에서만 사용해야 하는 시스템 호출이다.[7][8] POSIX에서 표준화되었으나, 2004년판에서 사용이 권장되지 않으며,[9] 이후 판에서 posix_spawn()으로 대체되었다.
`vfork` 시스템 호출이 발행되면, 자식 프로세스가 실행을 완료하거나 "exec" 계열의 시스템 호출 중 하나를 통해 새 실행 이미지로 대체될 때까지 부모 프로세스가 일시 중단된다. 자식 프로세스는 부모로부터 메모리 관리 장치 설정을 빌려오며, 메모리 페이지는 부모와 자식 프로세스 간에 공유된다. 특히 쓰기 시 복사 의미 체계가 없어,[9] 자식 프로세스가 공유 페이지에서 수정 사항을 만들 경우 새 페이지가 생성되지 않고, 수정된 페이지는 부모 프로세스에서도 볼 수 있다. 페이지 복사가 전혀 이루어지지 않으므로, 이 기술은 exec와 함께 사용될 때 일반 fork보다 효율적이다. POSIX에서 exec 계열의 함수에 즉시 호출하기 위한 경우가 아니면 vfork를 사용하면 정의되지 않은 동작이 발생한다.[9]
System V는 System VR4가 도입되기 전까지 이 함수 호출을 지원하지 않았다. 이는 해당 함수가 야기하는 메모리 공유가 오류가 발생하기 쉽기 때문이었다.[10] 마찬가지로, vfork에 대한 Linux 매뉴얼 페이지는 그 사용을 강력하게 권장하지 않는다.[7]
`vfork`의 또 다른 문제점으로는 교착 상태가 다중 스레드 프로그램에서 동적 링크와의 상호 작용으로 인해 발생할 수 있다는 점이 있다. `vfork` 인터페이스를 대체하기 위해 POSIX는 fork와 exec의 작업을 결합한 `posix_spawn` 계열의 함수를 도입했다. 이러한 함수는 fork를 기반으로 하는 라이브러리 루틴으로 구현되거나, vfork를 기반으로 더 나은 성능으로 구현될 수 있다.[11][12]
4.4BSD 구현은 vfork 구현을 제거하여 vfork가 fork와 동일한 동작을 하도록 했지만, 나중에 성능상의 이유로 NetBSD 운영 체제에 다시 도입되었다.[14] uClinux와 같은 일부 임베디드 운영 체제는 메모리 관리 장치 부족으로 인해 쓰기 시 복사를 구현할 수 없는 장치에서 작동해야 하기 때문에 fork를 생략하고 vfork만 구현한다.
구현에 따라 `vfork()`는 `fork()`와 동일하다[22]。 `vfork()`와 `fork()`의 유일한 차이점은 부모 프로세스와 자식 프로세스가 코드와 데이터를 공유할 수 있다는 것이다. 이것으로 복제가 빨라지지만, 사용법을 오해하면 부모 프로세스의 일관성이 파괴될 위험이 있다.
직후에 `exec` 또는 `_exit()`를 호출하는 경우 외에는 `vfork()`의 사용은 권장되지 않는다. 특히 리눅스 man 페이지에서는 vfork 자체의 사용이 권장되지 않는다[21]。 `vfork()`로 생성된 자식 프로세스가 `vfork()`를 호출한 루틴에서 더 바깥쪽으로 복귀했을 경우, 부모 프로세스의 콜 스택을 수정해 버리므로, `vfork()`에서 부모 프로세스가 돌아왔을 때의 동작을 보장할 수 없다. 또한, exec 없이 자식 프로세스를 종료하는 경우, `_exit()`가 아닌 `exit()`를 사용하면, 표준 I/O 채널을 플래시하고 닫기 때문에 부모 프로세스의 표준 I/O 구조에 손상을 줄 위험이 있다. `fork()`의 경우에도 자식 프로세스가 `exit()`를 호출하는 것은 위험하다. `vfork()` 후에 자식 프로세스에서 시그널 핸들러가 호출된 경우, 자식 프로세스의 다른 코드와 동일한 규칙을 따라야 한다[22]。
벨 연구소의 유닉스 설계자들이 만든 플랜 9 운영 체제는 "rfork"라는 변형을 포함하고 있다. rfork는 부모 프로세스와 자식 프로세스 간의 주소 공간(각 프로세스에 고유한 스택 세그먼트 제외), 환경 변수 및 파일 시스템 네임스페이스를 포함하여 리소스를 세분화하여 공유할 수 있다.[15] 이는 프로세스 생성과 프로세스 내 스레드 생성을 위한 통합 인터페이스를 만든다.[16] FreeBSD[17] 와 IRIX 둘 다 플랜 9의 rfork 시스템 호출을 채택했으며, 후자는 이를 "sproc"으로 이름을 변경했다.
`clone`은 부모 프로세스와 실행 컨텍스트의 일부를 공유할 수 있는 자식 프로세스를 생성하는 리눅스 커널의 시스템 호출이다. FreeBSD의 rfork와 IRIX의 sproc과 마찬가지로, 리눅스의 clone은 Plan 9의 rfork에서 영감을 받았으며, 스레드를 구현하는 데 사용될 수 있다(하지만 애플리케이션 프로그래머는 일반적으로 clone 위에 구현된 pthreads와 같은 상위 수준 인터페이스를 사용한다). Plan 9과 IRIX의 "별도 스택" 기능은 리누스 토르발스에 따르면 과도한 오버헤드를 유발하기 때문에 생략되었다.[18]
4. 통신
자식 프로세스는 부모 프로세스의 파일 서술자 사본으로 시작한다.[26] 부모 프로세스는 프로세스 간 통신을 위해 하나 이상의 파이프를 만들고, 프로세스를 포크(fork)한 뒤 불필요한 파이프의 끝을 종료시킨다.[26]
유닉스에서 fork는 필터 개발을 장려하는 설계 철학의 중요한 부분이다. 필터는 표준 입력을 입력으로 하고 표준 출력을 출력으로 하는 (보통 작은) 프로그램이다. 셸은 필터를 파이프로 연결하여 복잡한 처리를 실현할 수 있다.
열린 파일의 파일 디스크립터는 ''close-on-exec''가 지정된 경우에만 `exec()` 실행 시 자동으로 닫힌다. 이러한 특징을 활용해 `fork()`를 호출하기 전에 파이프를 생성하고, `exec()`로 지정된 새로운 프로그램과 통신하는 유닉스 특유의 기법이 실현된다.[6]
5. 유닉스 철학과 fork
유닉스에서 fork는 필터 개발을 장려하는 설계 철학의 중요한 부분이다.[23]
유닉스에서의 필터는 표준 입력을 입력으로 하고 표준 출력을 출력으로 하는 (보통 작은) 프로그램이다. 셸은 필터를 파이프로 연결함으로써 복잡한 처리를 실현할 수 있다.
셸은 사용자가 명령행을 입력할 때마다 fork를 수행한다. 셸이 fork를 수행함으로써 자식 프로세스가 생성되며, 자식 프로세스는 `exec`로 오버레이를 수행하여 실행해야 할 프로그램의 코드를 매핑한다.
6. 다른 운영 체제에서의 포크
VMS 운영 체제(1977)의 초기 설계에서는, 포킹과 같이 새로운 프로세스에 대한 몇몇 특정 주소의 내용을 복사하고 변경하는 작업은 위험하다고 여겨졌다. 현재 프로세스 상태의 오류가 자식 프로세스로 복사될 수 있기 때문이다. 여기서는 프로세스 생성의 비유가 사용된다. 즉, 새로운 프로세스의 메모리 레이아웃의 각 구성 요소는 처음부터 새로 구성된다. spawn 비유는 나중에 마이크로소프트 운영 체제(1993)에 채택되었다.
VM/CMS (OpenExtensions)의 POSIX 호환성 구성 요소는 매우 제한적인 fork 구현을 제공하며, 여기서 자식 프로세스가 실행되는 동안 부모 프로세스가 일시 중지되고 자식 프로세스와 부모 프로세스가 동일한 주소 공간을 공유한다.[19] 이것은 본질적으로 ''fork''로 표시된 ''vfork''이다.
유닉스 계열이나 리눅스에서의 fork 기구는 기반이 되는 하드웨어에 일종의 전제를 두고 있다. 선형 메모리 공간과 페이징 기구를 가지고, 연속적인 주소 범위의 메모리 복사를 효율적으로 할 수 있다는 전제이다. VMS(현재의 OpenVMS)의 최초 설계에서는 복사 조작 후에 소수의 구체적인 주소의 내용 수정이 이루어지는 포크는 위험하다고 여겨졌다. 현재 프로세스 상태의 에러가 자식 프로세스에 복사될 수 있기 때문이다. 그래서 프로세스의 spawn (computing)|스폰영어(산란)이라는 메타포가 사용되었다. 즉, 새로운 프로세스의 메모리 레이아웃의 각 컴포넌트를 처음부터 새로 구축한다. 소프트웨어 공학적 관점에서 보면 후자(스폰)의 방법이 더 깨끗하고 안전하지만, 포크가 더 효율적이므로 자주 사용된다. 스폰 방식은 후에 마이크로소프트의 OS에서 채용되었다.
7. 코드 예시
C, Perl, Python 등 다양한 프로그래밍 언어에서 `fork` 시스템 호출을 사용하는 예시를 확인할 수 있다. 각 언어별 예시는 하위 섹션에 자세히 설명되어 있다.
7. 1. C 언어 예시
다음은 C 프로그래밍 언어에서 `fork` 시스템 호출의 메커니즘을 보여주는 "Hello, World!" 프로그램의 변형이다. 이 프로그램은 두 개의 프로세스로 분기되며, 각 프로세스는 `fork` 시스템 호출의 반환 값을 기반으로 수행할 기능을 결정한다.```c
#include
#include
#include
#include
#include
int main(void)
{
pid_t pid;
/* 자식 프로세스와 부모 프로세스의 출력이
- 표준 출력에 기록된다.
- 둘 다 동시에 동작한다.
- /
pid = fork();
if (pid == -1)
{
/* 에러:
- fork()가 -1을 반환하는 경우, 에러가 발생했음을 나타낸다.
- 예를 들어 프로세스 수가 제한에 도달한 경우 등.
- /
fprintf(stderr, "fork를 할 수 없습니다, 에러 %d\n", errno);
exit(EXIT_FAILURE);
}
if (pid == 0)
{
/* 자식 프로세스:
- fork()가 0을 반환하는 경우, 자식 프로세스이다.
- 1초에 1씩, 10까지 센다.
- /
int j;
for (j = 0; j < 10; j++)
{
printf("child: %d\n", j);
sleep(1);
}
_exit(0); /* exit()를 사용하지 않는 점에 주의 */
}
else
{
/* fork()가 양수를 반환하는 경우, 부모 프로세스이다.
- 그 값은 생성한 자식 프로세스의 PID이다.
- 여기에서도 10까지 센다.
- /
int i;
for (i = 0; i < 10; i++)
{
printf("parent: %d\n", i);
sleep(1);
}
exit(0);
}
return 0;
}
```
`main`의 첫 번째 문은 `fork` 시스템 호출을 호출하여 실행을 두 개의 프로세스로 분할한다. `fork`의 반환 값은 프로세스 식별자(PID)에 대한 POSIX 유형인 `pid_t` 유형의 변수에 기록된다. `fork`가 -1을 반환하면 오류가 발생했음을 의미하며, 새 프로세스가 생성되지 않았으므로 오류 메시지가 출력된다.
`fork`가 성공하면 두 개의 프로세스가 생성되고, 둘 다 `fork`가 반환된 시점부터 `main` 함수를 실행한다. 각 프로세스는 `fork`의 반환 값에 따라 분기하여 자신이 ''자식'' 프로세스인지 ''부모'' 프로세스인지 결정한다.
자식 프로세스에서 `fork`의 반환 값은 0이다 (유효하지 않은 프로세스 식별자). 자식 프로세스는 1초 간격으로 1부터 10까지 숫자를 출력한 후 종료한다. 부모 프로세스는 `fork`로부터 자식 프로세스의 PID (항상 양수)를 받는다. 부모 프로세스 역시 1초 간격으로 1부터 10까지 숫자를 출력하고 종료한다.
7. 2. Perl 예시
perl#!/usr/bin/env perl -w
use strict;
if (fork) { # 부모 프로세스
foreach my $i (0 .. 9) {
print "Parent: $i\n";
sleep 1;
}
}
else { # 자식 프로세스
foreach my $i (0 .. 9) {
print "Child: $i\n";
sleep 1;
}
exit(0); # fork한 자식 프로세스의 종료
}
exit(0); # 부모 프로세스의 종료
```
위 코드는 fork를 사용하여 부모 프로세스와 자식 프로세스가 번갈아 가며 메시지를 출력하는 예시이다. 부모 프로세스는 "Parent: 숫자"를, 자식 프로세스는 "Child: 숫자"를 출력하며, 각 출력 사이에 1초간 대기한다. `fork` 호출 후, 부모 프로세스에서는 `if` 블록이 실행되고, 자식 프로세스에서는 `else` 블록이 실행된다. 자식 프로세스는 `exit(0)`를 통해 종료되고, 부모 프로세스도 마지막에 `exit(0)`를 통해 종료된다.
7. 3. Python 예시
python#!/usr/bin/env python3
import os
import sys
import time
def doTask():
# 이 함수는 데몬이 될 작업을 생성합니다.
with open("/tmp/tarefa.log", "w") as log_file:
while True:
log_file.write("{}\n".format(time.ctime()))
log_file.flush()
time.sleep(2)
def createDaemon():
# 이 함수는 특정 작업을 실행할 서비스/데몬을 생성합니다.
try:
pid = os.fork()
if pid > 0:
print("PID: {}".format(pid))
sys.exit()
except OSError as error:
print("fork를 수행할 수 없습니다. 오류: {} ({})".format(error.errno, error.strerror))
sys.exit(1)
else:
doTask()
if __name__ == "__main__":
createDaemon()
```
os.fork()영어를 사용하여 데몬 프로세스를 생성하는 예시이다.
8. Fork-Exec
유닉스에서 `fork`와 `exec`는 새로운 프로그램을 프로세스로 실행하는 일반적인 기법이다. 부모 프로세스는 `wait` 시스템 호출을 사용하여 자식 프로세스의 종료를 기다릴 수 있다.
- Fork: 부모 프로세스는 `fork` 시스템 호출을 사용하여 자신의 복사본인 자식 프로세스를 생성한다. 이때, 부모 프로세스와 자식 프로세스는 동일한 메모리 내용을 가지지만, 별도의 주소 공간을 가진다. 쓰기 시 복사 기법을 통해 효율적인 메모리 관리가 가능하다.
- Exec: 자식 프로세스는 `exec` 시스템 호출을 사용하여 자신의 메모리 공간을 새로운 프로그램으로 덮어쓴다. 이를 통해 자식 프로세스는 부모 프로세스와는 다른 프로그램을 실행하게 된다.
- Wait: 부모 프로세스는 `wait` 시스템 호출을 사용하여 자식 프로세스가 종료될 때까지 기다릴 수 있다. 이를 통해 자식 프로세스의 종료 상태를 확인하고, 좀비 프로세스 생성을 방지할 수 있다.
이러한 fork-exec 모델은 유닉스 계열 운영 체제에서 프로세스 생성 및 관리에 널리 사용되는 방식이다.
마이크로소프트 윈도우는 fork-exec 모델을 사용하지 않고, `spawn` 계열 함수를 통해 유사한 기능을 제공한다.
참조
[1]
논문
Notes on the History of Fork and Join
2016-08-25
[2]
웹사이트
s3.s from Research UNIX
https://github.com/d[...]
1970
[3]
백과사전
SYS FORK (II)
https://www.bell-lab[...]
Bell Laboratories
1971-11-03
[4]
논문
The UNIX Time-Sharing System
https://www.bell-lab[...]
AT&T
2014-04-22
[5]
매뉴얼
fork
sh
[6]
매뉴얼
pipe
sh
[7]
매뉴얼
vfork
sh
[8]
백과사전
vfork(2)
University of California, Berkeley
1979-12
[9]
매뉴얼
vfork
sh
[10]
서적
The Design of The UNIX Operating System
Prentice–Hall
[11]
웹사이트
Minimizing Memory Usage for Creating Application Subprocesses
http://www.oracle.co[...]
Oracle Corporation
2006-05
[12]
문서
The OpenSolaris posix_spawn() implementation
sourceforge:p/schill[...]
[13]
매뉴얼
posix_spawn
sh
[14]
웹사이트
NetBSD Documentation: Why implement traditional vfork()
http://www.netbsd.or[...]
2013-10-16
[15]
매뉴얼
fork
sh
[16]
매뉴얼
intro
sh
[17]
매뉴얼
rfork
sh
[18]
백과사전
The Linux edge
https://archive.org/[...]
O'Reilly
[19]
웹사이트
z/VM > z/VM 6.2.0 > Application Programming > z/VM V6R2 OpenExtensions POSIX Conformance Document > POSIX.1 Conformance Document > Section 3. Process Primitives > 3.1 Process Creation and Execution > 3.1.1 Process Creation
http://www-01.ibm.co[...]
IBM
2015-04-21
[20]
문서
正確には、これはページング方式の仮想記憶の場合である。また、.bssは実行ファイル上では対応するデータが存在せず、単に仮想空間の範囲だけが確保される。
[21]
문서
VFORK
http://www.linuxmanp[...]
[22]
문서
The Open Group Base Specifications Issue 6
http://pubs.opengrou[...]
[23]
백과사전
SYS FORK (II)
http://cm.bell-labs.[...]
Bell Laboratories
2016-12-13
[24]
저널
The UNIX Time-Sharing System
https://www.bell-lab[...]
AT&T
2014-04-22
[25]
매뉴얼
fork
sh
[26]
매뉴얼
pipe
sh
본 사이트는 AI가 위키백과와 뉴스 기사,정부 간행물,학술 논문등을 바탕으로 정보를 가공하여 제공하는 백과사전형 서비스입니다.
모든 문서는 AI에 의해 자동 생성되며, CC BY-SA 4.0 라이선스에 따라 이용할 수 있습니다.
하지만, 위키백과나 뉴스 기사 자체에 오류, 부정확한 정보, 또는 가짜 뉴스가 포함될 수 있으며, AI는 이러한 내용을 완벽하게 걸러내지 못할 수 있습니다.
따라서 제공되는 정보에 일부 오류나 편향이 있을 수 있으므로, 중요한 정보는 반드시 다른 출처를 통해 교차 검증하시기 바랍니다.
문의하기 : help@durumis.com