시스템 호출을 추적하는 데 사용 하면 ltrace
fork()가 sys_fork() 대신 sys_clone()을 사용하는 것을 볼 수 있습니다. 그러나 그것을 정의하는 Linux 소스 코드를 찾을 수 없습니다.
내 프로그램은 다음과 같습니다
#include<stdio.h>
main()
{
int pid,i=0,j=0;
pid=fork();
if(pid==0)
printf("\nI am child\n");
else
printf("\nI am parent\n");
}
출력은 ltrace
다음과 같습니다
SYS_brk(NULL) = 0x019d0000
SYS_access("/etc/ld.so.nohwcap", 00) = -2
SYS_mmap(0, 8192, 3, 34, 0xffffffff) = 0x7fe3cf84f000
SYS_access("/etc/ld.so.preload", 04) = -2
SYS_open("/etc/ld.so.cache", 0, 01) = 3
SYS_fstat(3, 0x7fff47007890) = 0
SYS_mmap(0, 103967, 1, 2, 3) = 0x7fe3cf835000
SYS_close(3) = 0
SYS_access("/etc/ld.so.nohwcap", 00) = -2
SYS_open("/lib/x86_64-linux-gnu/libc.so.6", 0, 00) = 3
SYS_read(3, "\177ELF\002\001\001", 832) = 832
SYS_fstat(3, 0x7fff470078e0) = 0
SYS_mmap(0, 0x389858, 5, 2050, 3) = 0x7fe3cf2a8000
SYS_mprotect(0x7fe3cf428000, 2097152, 0) = 0
SYS_mmap(0x7fe3cf628000, 20480, 3, 2066, 3) = 0x7fe3cf628000
SYS_mmap(0x7fe3cf62d000, 18520, 3, 50, 0xffffffff) = 0x7fe3cf62d000
SYS_close(3) = 0
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7fe3cf834000
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7fe3cf833000
SYS_mmap(0, 4096, 3, 34, 0xffffffff) = 0x7fe3cf832000
SYS_arch_prctl(4098, 0x7fe3cf833700, 0x7fe3cf832000, 34, 0xffffffff) = 0
SYS_mprotect(0x7fe3cf628000, 16384, 1) = 0
SYS_mprotect(0x7fe3cf851000, 4096, 1) = 0
SYS_munmap(0x7fe3cf835000, 103967) = 0
__libc_start_main(0x40054c, 1, 0x7fff47008298, 0x4005a0, 0x400590 <unfinished ...>
fork( <unfinished ...>
SYS_clone(0x1200011, 0, 0, 0x7fe3cf8339d0, 0) = 5967
<... fork resumed> ) = 5967
puts("\nI am parent" <unfinished ...>
SYS_fstat(1, 0x7fff47008060) = 0
SYS_mmap(0, 4096, 3, 34, 0xffffffff
) = 0x7fe3cf84e000
I am child
SYS_write(1, "\n", 1
) = 1
SYS_write(1, "I am parent\n", 12) = -512
--- SIGCHLD (Child exited) ---
SYS_write(1, "I am parent\n", 12I am parent
) = 12
<... puts resumed> ) = 13
SYS_exit_group(13 <no return ...>
+++ exited (status 13) +++
답변1
fork()
glibc의 래퍼는 시스템 호출을 vfork()
통해 clone()
구현 됩니다. fork()
와 의 관계를 더 잘 이해하려면 clone()
Linux의 프로세스와 스레드 간의 관계를 고려해야 합니다.
전통적으로 fork()
상위 프로세스가 소유한 모든 리소스는 복사되고 복사본은 하위 프로세스에 할당됩니다. 이 접근 방식은 상당한 오버헤드를 발생시키며, 아이가 즉시 호출한다면 모두 의미가 없을 수도 있습니다 exec()
. fork()
리눅스 에서는쓰기 중 복사페이지를 사용하여 상위 프로세스와 하위 프로세스 간에 공유할 수 있는 데이터 복사를 지연하거나 완전히 방지할 수 있습니다. 따라서 일반적인 상황에서 발생하는 유일한 오버헤드는 상위 프로세스의 페이지 테이블을 복사하고 fork()
하위 프로세스에 고유한 프로세스 설명자 구조를 할당하는 것입니다.task_struct
Linux는 또한 스레드에 대해 특별한 접근 방식을 취합니다. Linux에서 스레드는 일부 리소스를 다른 프로세스와 공유하는 일반적인 프로세스일 뿐입니다. 이는 프로세스와 스레드가 완전히 다른 유형의 짐승인 Windows나 Solaris와 같은 다른 운영 체제와는 완전히 다른 스레딩 접근 방식입니다. Linux에서 각 스레드에는 고유한 일반 스레드가 있으며 task_struct
, 이는 상위 프로세스와 특정 리소스(예: 주소 공간)를 공유하는 방식으로 설정됩니다.
flags
시스템 호출의 매개변수에는 clone()
상위 프로세스와 하위 프로세스가 공유해야 하는 리소스를 나타내는 플래그 집합이 포함됩니다. 프로세스와 스레드는 모두 를 통해 생성되며 clone()
유일한 차이점은 에 전달된 플래그 세트입니다 clone()
.
법선은 다음 fork()
과 같이 구현할 수 있습니다.
clone(SIGCHLD, 0);
SIGCHLD
이렇게 하면 상위 항목과 리소스를 공유하지 않는 작업이 생성되고 종료 시 상위 항목에 종료 신호를 보내 도록 설정됩니다 .
대신 주소 공간, 파일 시스템 리소스, 파일 설명자 및 신호 처리기를 상위 프로세스와 공유하는 작업, 즉철사는 다음과 같은 방법으로 생성될 수 있습니다.
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
vfork()
그 반대는 별도의 플래그를 통해 CLONE_VFORK
달성 되며, 이는 하위 프로세스가 신호를 통해 깨어날 때까지 상위 프로세스를 절전 모드로 전환합니다. 자식은 호출되거나 종료될 때까지 부모의 네임스페이스에서 유일한 실행 스레드가 됩니다 exec()
. 어린이는 메모리에 쓸 수 없습니다. 해당 clone()
호출은 다음과 같습니다.
clone(CLONE_VFORK | CLONE_VM | SIGCHLD, 0)
구현은 sys_clone()
아키텍처마다 다르지만 대부분의 작업은 kernel_clone()
정의 내에서 발생합니다 kernel/fork.c
. 이 함수는 copy_process()
상위 프로세스의 복사본으로 새 프로세스를 생성하지만 아직 시작하지는 않는 static 을 호출합니다. copy_process()
레지스터를 복사하고, 새 작업에 PID를 할당하고, 지정된 프로세스 환경의 적절한 부분을 복제하거나 공유합니다 flags
. copy_process()
돌아오면 kernel_clone()
새로 생성된 프로세스가 깨어나 실행되도록 예약됩니다.
인용하다
kernel/fork.c
Linux v5.19-rc5, 2022-07-03. kernel_clone()
줄바꿈에 불과한 시스템 호출 , 및 의 정의는 2606행과 2727행을 참조하세요 .fork()
vfork()
clone()
clone3()
kernel_clone()
답변2
Linux에서 사용자 모드 시스템 호출 기능을 커널 시스템 호출로 변환하는 구성 요소는 libc입니다. GLibC에서 NPTL 라이브러리는 이를 clone(2)
시스템 호출로 리디렉션합니다.