当ltrace
用于跟踪系统调用时,我可以看到 fork() 使用 sys_clone() 而不是 sys_fork()。但我找不到定义它的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()
。在Linux中,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
。该函数调用 static copy_process()
,它创建一个新进程作为父进程的副本,但尚未启动它。copy_process()
复制寄存器,为新任务分配 PID,并复制或共享克隆指定的进程环境的适当部分flags
。copy_process()
返回时,kernel_clone()
将唤醒新创建的进程并安排其运行。
参考
kernel/fork.c
在 Linux v5.19-rc5 中,2022-07-03。请参阅第 2606 行,以及第 2727 行以了解系统调用、、和kernel_clone()
的定义,它们或多或少只是换行。fork()
vfork()
clone()
clone3()
kernel_clone()
答案2
Linux 下负责将用户态系统调用函数转换为内核系统调用的组件是 libc。在 GLibC 中,NPTL 库将其重定向到clone(2)
系统调用。