Linux 中 fork() 调用的来源在哪里?
Where is the source for the fork() call in Linux?
我花了很多时间试图找到 fork()
函数的源代码。我知道 fork()
完成的大部分工作都是由 do_fork()
完成的,可以在 kernel/fork.c
中找到。但是我想看的是 fork()
函数的源代码。
知道在哪里可以找到它吗?我一直在浏览 GCC 和 Linux 源代码,但仍未找到它。
编辑:我试图找到我的系统正在使用的确切实现。正如评论和 Link 中提到的,它显然在 glibc 的一些包装器中。知道在 glibc 的哪个位置可以找到包装器。我已经彻底搜索但找不到它的定义。
来自 http://lxr.free-electrons.com/source/kernel/fork.c#L1787 用于 Linux 4.4:
1787 #ifdef __ARCH_WANT_SYS_FORK
1788 SYSCALL_DEFINE0(fork)
1789 {
1790 #ifdef CONFIG_MMU
1791 return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
1792 #else
1793 /* can not support in nommu mode */
1794 return -EINVAL;
1795 #endif
1796 }
1797 #endif
我相信这是它定义 fork 系统调用的地方。在 Linux 下我相信 glibc fork()
函数直接调用这个系统调用而不做任何其他事情。
这里是 link 到 glibc 文件
fork.c
以x86平台和2.6.23 Linux内核为参考:
创建 test-fork.c
文件:
#include <unistd.h>
int main (void)
{
fork();
return 0;
}
用静态链接编译:gcc -O0 -static -Wall test-fork.c -o test-fork
反汇编:objdump -D -S test-fork > test-fork.dis
打开 test-fork.dis
文件并搜索 fork
:
fork();
80481f4: e8 63 55 00 00 call 804d75c <__libc_fork>
return 0;
80481f9: b8 00 00 00 00 mov [=11=]x0,%eax
}
80481fe: c9 leave
80481ff: c3 ret
然后搜索__libc_fork
:
0804d75c <__libc_fork>:
804d75c: 55 push %ebp
804d75d: b8 00 00 00 00 mov [=12=]x0,%eax
804d762: 89 e5 mov %esp,%ebp
804d764: 53 push %ebx
804d765: 83 ec 04 sub [=12=]x4,%esp
804d768: 85 c0 test %eax,%eax
804d76a: 74 12 je 804d77e <__libc_fork+0x22>
804d76c: c7 04 24 80 e0 0a 08 movl [=12=]x80ae080,(%esp)
804d773: e8 88 28 fb f7 call 0 <_init-0x80480d4>
804d778: 83 c4 04 add [=12=]x4,%esp
804d77b: 5b pop %ebx
804d77c: 5d pop %ebp
804d77d: c3 ret
804d77e: b8 02 00 00 00 mov [=12=]x2,%eax
804d783: cd 80 int [=12=]x80
804d785: 3d 00 f0 ff ff cmp [=12=]xfffff000,%eax
804d78a: 89 c3 mov %eax,%ebx
804d78c: 77 08 ja 804d796 <__libc_fork+0x3a>
804d78e: 89 d8 mov %ebx,%eax
804d790: 83 c4 04 add [=12=]x4,%esp
804d793: 5b pop %ebx
804d794: 5d pop %ebp
804d795: c3 ret
注意这个特定的 hardware/kernel fork
与 系统调用号 2
关联
下载 Linux 内核的副本:wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.23.tar.bz2
打开linux-2.6.23/arch/x86/kernel/syscall_table_32.S
文件
注意系统调用号 2 关联到
sys_fork:
.long sys\_fork /* 2 */
打开 linux-2.6.23/arch/x86/kernel/process.c
文件
搜索sys_fork
:
asmlinkage int sys_fork(struct pt_regs regs)
{
return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL);
}
注意 do_fork()
仅使用 SIGCHLD
参数调用
打开 linux-2.6.23/kernel/fork.c
文件。这里是定义do_fork()
的地方!
do_fork()
然后调用 copy_process()
:
/*
* Ok, this is the main fork-routine.
*
* It copies the process, and if successful kick-starts
* it and waits for it to finish using the VM if required.
*/
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
{
struct task_struct *p;
int trace = 0;
struct pid *pid = alloc_pid();
long nr;
if (!pid)
return -EAGAIN;
nr = pid->nr;
if (unlikely(current->ptrace)) {
trace = fork_traceflag (clone_flags);
if (trace)
clone_flags |= CLONE_PTRACE;
}
p = copy_process(clone_flags, stack_start, regs, stack_size, \
parent_tidptr, child_tidptr, pid);
/*
* Do this prior waking up the new thread - the thread
* pointer might get invalid after that point,
* if the thread exits quickly.
*/
if (!IS_ERR(p)) {
struct completion vfork;
if (clone_flags & CLONE_VFORK) {
p->vfork_done = &vfork;
init_completion(&vfork);
}
if ((p->ptrace & PT_PTRACED) || \
(clone_flags & CLONE_STOPPED)) {
/*
* We'll start up with an immediate SIGSTOP.
*/
sigaddset(&p->pending.signal, SIGSTOP);
set_tsk_thread_flag(p, TIF_SIGPENDING);
}
if (!(clone_flags & CLONE_STOPPED))
wake_up_new_task(p, clone_flags);
else
p->state = TASK_STOPPED;
if (unlikely (trace)) {
current->ptrace_message = nr;
ptrace_notify ((trace << 8) | SIGTRAP);
}
if (clone_flags & CLONE_VFORK) {
freezer_do_not_count();
wait_for_completion(&vfork);
freezer_count();
if (unlikely (current->ptrace & \
PT_TRACE_VFORK_DONE)) {
current->ptrace_message = nr;
ptrace_notify \
((PTRACE_EVENT_VFORK_DONE << 8) | \
SIGTRAP);
}
}
} else {
free_pid(pid);
nr = PTR_ERR(p);
}
return nr;
}
fork 中的大部分工作由 do_fork()
处理,
在 kernel/fork.c
中定义。 do_fork()
:
执行的操作
- 它通过调用
alloc_pid()
为 child 分配一个新的 PID
- 它检查 parent 的
ptrace
字段(即 current->ptrace
)
- 如果不为零,则 parent 进程正在被另一个进程跟踪
它调用copy_process()
,设置进程描述符和child执行所需的任何其他内核数据结构
- 其参数与
do_fork()
相同加上child 的PID
- 它检查在
clone_flags
参数中传递的标志是否兼容
- 它通过调用
security_task_create()
和 security_task_alloc()
执行额外的安全检查
它调用dup_task_struct()
为新进程创建新的内核堆栈、thread_info
和task_struct
结构。
- 新值与当前任务的值相同
- 此时child和parent进程描述符是相同的
- 它执行
alloc_task_struct()
宏为新进程得到一个task_struct
结构,并将其地址存储在tsk
局部变量中。
- 它执行
alloc_thread_info
宏得到一块空闲内存区来存放thread_info
结构和新进程的内核模式堆栈,并将其地址保存在ti
局部变量
- 它将当前进程描述符的内容复制到
tsk
指向的task_struct
结构中,然后将tsk->thread_info
设置为ti
- 它将当前
thread_info
描述符的内容复制到ti
指向的结构中,然后将ti->task
设置为tsk
- 它将新进程描述符(即
tsk->usage
)的使用计数器设置为2,以指定进程描述符正在使用中并且对应的进程处于活动状态(其状态不是EXIT_ZOMBIE
或 EXIT_DEAD
)
- 它returns新进程的进程描述符指针(即
tsk
)
copy_process()
然后检查是否没有超过当前用户的最大进程数(即大于`max_threads)
- 它通过清除或初始化
task_struct
的各个字段来区分 child 和 parent
它调用copy_flags()
更新task_struct
的flags
字段
- 清除
PF_SUPERPRIV
(表示任务是否使用超级用户权限)和 PF_NOFREEZE
标志
- 已设置
PF_FORKNOEXEC
标志(表示任务是否未调用 `exec())
- 它调用 `init_sigpending() 来清除挂起的信号
- 根据传递给
do_fork(),
copy_process()` 的参数,然后复制或共享资源
- 打开文件
- 文件系统信息
- 信号处理器
- 地址space
- 它调用
sched_fork()
将剩余的时间片分割在 parent 和 child 之间
- 最后,它returns一个指向新child
的指针
然后,do_fork()
添加一个挂起的 SIGSTOP
信号,以防 CLONE_STOPPED
标志被设置或必须跟踪 child 进程(即PT_PTRACED
标志设置在 p->ptrace
)
如果未设置CLONE_STOPPED
标志,它会调用wake_up_new_task()
函数,该函数执行以下操作:
- 调整parent和child
的调度参数
- 如果 child 将 运行 与 parent 和 parent 和 child 不共享相同的 CPU页表集(即清除
CLONE_VM
标志),然后通过将 parent 插入到 parent 的 运行在 parent 之前排队。如果 child 刷新其地址 space 并在分叉后立即执行新程序,这个简单的步骤会产生更好的性能。如果我们先让 parent 运行,写入时复制 机制会导致一系列不必要的页面重复。
- 否则,如果 child 不会 运行 在与 parent 相同的 CPU 上,或者如果 parent 和 child 共享同一组页表(即
CLONE_VM
标志设置),它将 child 插入 parent 的 运行 队列 的最后位置
- 否则,如果设置了
CLONE_STOPPED
标志,它会将 child 置于 TASK_STOPPED
状态
如果正在跟踪parent进程,它将child的PID存储在
current
的 ptrace_message
字段并调用
ptrace_notify()
,这实际上停止了当前进程并向它的 parent 发送了一个 SIGCHLD
信号。 child 的 ``grandparent'' 是跟踪 parent 的调试器; SIGCHLD
信号通知调试器当前已经分叉了一个 child,可以通过查看 current->ptrace_message
字段来检索其 PID。
如果指定了CLONE_VFORK
标志,它会将parent进程插入等待队列并挂起,直到child释放其内存地址space(也就是说,直到 child 终止或执行新程序)
- 它通过返回 child 的 PID 终止。
我花了很多时间试图找到 fork()
函数的源代码。我知道 fork()
完成的大部分工作都是由 do_fork()
完成的,可以在 kernel/fork.c
中找到。但是我想看的是 fork()
函数的源代码。
知道在哪里可以找到它吗?我一直在浏览 GCC 和 Linux 源代码,但仍未找到它。
编辑:我试图找到我的系统正在使用的确切实现。正如评论和 Link 中提到的,它显然在 glibc 的一些包装器中。知道在 glibc 的哪个位置可以找到包装器。我已经彻底搜索但找不到它的定义。
来自 http://lxr.free-electrons.com/source/kernel/fork.c#L1787 用于 Linux 4.4:
1787 #ifdef __ARCH_WANT_SYS_FORK
1788 SYSCALL_DEFINE0(fork)
1789 {
1790 #ifdef CONFIG_MMU
1791 return _do_fork(SIGCHLD, 0, 0, NULL, NULL, 0);
1792 #else
1793 /* can not support in nommu mode */
1794 return -EINVAL;
1795 #endif
1796 }
1797 #endif
我相信这是它定义 fork 系统调用的地方。在 Linux 下我相信 glibc fork()
函数直接调用这个系统调用而不做任何其他事情。
这里是 link 到 glibc 文件 fork.c
以x86平台和2.6.23 Linux内核为参考:
创建
test-fork.c
文件:#include <unistd.h> int main (void) { fork(); return 0; }
用静态链接编译:
gcc -O0 -static -Wall test-fork.c -o test-fork
反汇编:
objdump -D -S test-fork > test-fork.dis
打开
test-fork.dis
文件并搜索fork
:fork(); 80481f4: e8 63 55 00 00 call 804d75c <__libc_fork> return 0; 80481f9: b8 00 00 00 00 mov [=11=]x0,%eax } 80481fe: c9 leave 80481ff: c3 ret
然后搜索
__libc_fork
:0804d75c <__libc_fork>: 804d75c: 55 push %ebp 804d75d: b8 00 00 00 00 mov [=12=]x0,%eax 804d762: 89 e5 mov %esp,%ebp 804d764: 53 push %ebx 804d765: 83 ec 04 sub [=12=]x4,%esp 804d768: 85 c0 test %eax,%eax 804d76a: 74 12 je 804d77e <__libc_fork+0x22> 804d76c: c7 04 24 80 e0 0a 08 movl [=12=]x80ae080,(%esp) 804d773: e8 88 28 fb f7 call 0 <_init-0x80480d4> 804d778: 83 c4 04 add [=12=]x4,%esp 804d77b: 5b pop %ebx 804d77c: 5d pop %ebp 804d77d: c3 ret 804d77e: b8 02 00 00 00 mov [=12=]x2,%eax 804d783: cd 80 int [=12=]x80 804d785: 3d 00 f0 ff ff cmp [=12=]xfffff000,%eax 804d78a: 89 c3 mov %eax,%ebx 804d78c: 77 08 ja 804d796 <__libc_fork+0x3a> 804d78e: 89 d8 mov %ebx,%eax 804d790: 83 c4 04 add [=12=]x4,%esp 804d793: 5b pop %ebx 804d794: 5d pop %ebp 804d795: c3 ret
注意这个特定的 hardware/kernel
fork
与 系统调用号 2 关联
下载 Linux 内核的副本:
wget http://www.kernel.org/pub/linux/kernel/v2.6/linux-2.6.23.tar.bz2
打开
linux-2.6.23/arch/x86/kernel/syscall_table_32.S
文件注意系统调用号 2 关联到
sys_fork: .long sys\_fork /* 2 */
打开
linux-2.6.23/arch/x86/kernel/process.c
文件搜索
sys_fork
:asmlinkage int sys_fork(struct pt_regs regs) { return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL); }
注意
do_fork()
仅使用SIGCHLD
参数调用打开
linux-2.6.23/kernel/fork.c
文件。这里是定义do_fork()
的地方!do_fork()
然后调用copy_process()
:/* * Ok, this is the main fork-routine. * * It copies the process, and if successful kick-starts * it and waits for it to finish using the VM if required. */ long do_fork(unsigned long clone_flags, unsigned long stack_start, struct pt_regs *regs, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) { struct task_struct *p; int trace = 0; struct pid *pid = alloc_pid(); long nr; if (!pid) return -EAGAIN; nr = pid->nr; if (unlikely(current->ptrace)) { trace = fork_traceflag (clone_flags); if (trace) clone_flags |= CLONE_PTRACE; } p = copy_process(clone_flags, stack_start, regs, stack_size, \ parent_tidptr, child_tidptr, pid); /* * Do this prior waking up the new thread - the thread * pointer might get invalid after that point, * if the thread exits quickly. */ if (!IS_ERR(p)) { struct completion vfork; if (clone_flags & CLONE_VFORK) { p->vfork_done = &vfork; init_completion(&vfork); } if ((p->ptrace & PT_PTRACED) || \ (clone_flags & CLONE_STOPPED)) { /* * We'll start up with an immediate SIGSTOP. */ sigaddset(&p->pending.signal, SIGSTOP); set_tsk_thread_flag(p, TIF_SIGPENDING); } if (!(clone_flags & CLONE_STOPPED)) wake_up_new_task(p, clone_flags); else p->state = TASK_STOPPED; if (unlikely (trace)) { current->ptrace_message = nr; ptrace_notify ((trace << 8) | SIGTRAP); } if (clone_flags & CLONE_VFORK) { freezer_do_not_count(); wait_for_completion(&vfork); freezer_count(); if (unlikely (current->ptrace & \ PT_TRACE_VFORK_DONE)) { current->ptrace_message = nr; ptrace_notify \ ((PTRACE_EVENT_VFORK_DONE << 8) | \ SIGTRAP); } } } else { free_pid(pid); nr = PTR_ERR(p); } return nr; }
fork 中的大部分工作由
执行的操作do_fork()
处理, 在kernel/fork.c
中定义。do_fork()
:- 它通过调用
alloc_pid()
为 child 分配一个新的 PID
- 它检查 parent 的
ptrace
字段(即current->ptrace
)- 如果不为零,则 parent 进程正在被另一个进程跟踪
它调用
copy_process()
,设置进程描述符和child执行所需的任何其他内核数据结构- 其参数与
do_fork()
相同加上child 的PID
- 它检查在
clone_flags
参数中传递的标志是否兼容 - 它通过调用
security_task_create()
和security_task_alloc()
执行额外的安全检查
它调用
dup_task_struct()
为新进程创建新的内核堆栈、thread_info
和task_struct
结构。- 新值与当前任务的值相同
- 此时child和parent进程描述符是相同的
- 它执行
alloc_task_struct()
宏为新进程得到一个task_struct
结构,并将其地址存储在tsk
局部变量中。 - 它执行
alloc_thread_info
宏得到一块空闲内存区来存放thread_info
结构和新进程的内核模式堆栈,并将其地址保存在ti
局部变量 - 它将当前进程描述符的内容复制到
tsk
指向的task_struct
结构中,然后将tsk->thread_info
设置为ti
- 它将当前
thread_info
描述符的内容复制到ti
指向的结构中,然后将ti->task
设置为tsk
- 它将新进程描述符(即
tsk->usage
)的使用计数器设置为2,以指定进程描述符正在使用中并且对应的进程处于活动状态(其状态不是EXIT_ZOMBIE
或EXIT_DEAD
) - 它returns新进程的进程描述符指针(即
tsk
)
copy_process()
然后检查是否没有超过当前用户的最大进程数(即大于`max_threads)
- 其参数与
- 它通过清除或初始化
task_struct
的各个字段来区分 child 和 parent
它调用
的copy_flags()
更新task_struct
flags
字段- 清除
PF_SUPERPRIV
(表示任务是否使用超级用户权限)和PF_NOFREEZE
标志 - 已设置
PF_FORKNOEXEC
标志(表示任务是否未调用 `exec()) - 它调用 `init_sigpending() 来清除挂起的信号
- 根据传递给
do_fork(),
copy_process()` 的参数,然后复制或共享资源 - 打开文件
- 文件系统信息
- 信号处理器
- 地址space
- 它调用
sched_fork()
将剩余的时间片分割在 parent 和 child 之间
- 最后,它returns一个指向新child 的指针
- 清除
然后,
do_fork()
添加一个挂起的SIGSTOP
信号,以防CLONE_STOPPED
标志被设置或必须跟踪 child 进程(即PT_PTRACED
标志设置在p->ptrace
)如果未设置
CLONE_STOPPED
标志,它会调用wake_up_new_task()
函数,该函数执行以下操作:- 调整parent和child 的调度参数
- 如果 child 将 运行 与 parent 和 parent 和 child 不共享相同的 CPU页表集(即清除
CLONE_VM
标志),然后通过将 parent 插入到 parent 的 运行在 parent 之前排队。如果 child 刷新其地址 space 并在分叉后立即执行新程序,这个简单的步骤会产生更好的性能。如果我们先让 parent 运行,写入时复制 机制会导致一系列不必要的页面重复。 - 否则,如果 child 不会 运行 在与 parent 相同的 CPU 上,或者如果 parent 和 child 共享同一组页表(即
CLONE_VM
标志设置),它将 child 插入 parent 的 运行 队列 的最后位置
- 否则,如果设置了
CLONE_STOPPED
标志,它会将 child 置于TASK_STOPPED
状态 如果正在跟踪parent进程,它将child的PID存储在
current
的ptrace_message
字段并调用ptrace_notify()
,这实际上停止了当前进程并向它的 parent 发送了一个SIGCHLD
信号。 child 的 ``grandparent'' 是跟踪 parent 的调试器;SIGCHLD
信号通知调试器当前已经分叉了一个 child,可以通过查看current->ptrace_message
字段来检索其 PID。如果指定了
CLONE_VFORK
标志,它会将parent进程插入等待队列并挂起,直到child释放其内存地址space(也就是说,直到 child 终止或执行新程序)
- 它通过调用
- 它通过返回 child 的 PID 终止。