0

0

fork系统调用分析

看不見的法師

看不見的法師

发布时间:2025-06-26 12:46:01

|

947人浏览过

|

来源于php中文网

原创

linux中,新的进程主要通过fork函数来创建。我们知道,每个进程在内核中对应一个pcb块,内核通过对pcb块的操作来管理进程。在linux内核中,pcb对应的结构体是task_struct,即所谓的进程描述符(process descriptor)。这个数据结构包含了与进程相关的所有信息,包括描述进程属性的多个字段以及指向其他与进程相关的结构体的指针。因此,进程描述符内部结构相当复杂。该结构体的声明位于include/linux/sched.h文件中。

在linux中,新的进程主要通过fork函数来创建。我们知道,每个进程在内核中对应一个pcb块,内核通过对pcb块的操作来管理进程。在linux内核中,pcb对应的结构体是task_struct,即所谓的进程描述符(process descriptor)。这个数据结构包含了与进程相关的所有信息,包括描述进程属性的多个字段以及指向其他与进程相关的结构体的指针。因此,进程描述符内部结构相当复杂。该结构体的声明位于include/linux/sched.h文件中。

task_struct结构体中包含指向mm_struct结构体的指针mm,用于描述进程的内存管理信息;指向fs_struct结构体的指针fs,用于描述进程当前所在的目录;以及指向files_struct结构体的指针files,用于描述该进程已打开的所有文件。我们需要注意的是,进程在运行期间可能处于不同的状态,如TASK_RUNNING、TASK_STOPPED、TASK_TRACED等。

在用户态下,可以通过fork()函数创建进程。此外,还可以通过vfork()和clone()函数来创建新进程。fork、vfork和clone这三个API函数均由glibc库提供,它们分别封装了同名的系统调用fork()。这些函数适用于不同的场景。例如,子进程可能需要复制父进程的整个地址空间,但在创建后立即执行exec族函数会导致效率低下。写时拷贝技术满足了这种需求,减少了地址空间复制的开销。vfork创建的子进程完全共享父进程的地址空间,甚至是父进程的页表项,父子进程对数据的任何修改都会相互影响。clone函数在创建子进程时提供了更大的灵活性,通过传递不同的参数可以选择性地复制父进程的资源。内核中对应的服务例程分别为sys_fork()、sys_vfork()和sys_clone()。例如,sys_fork()的声明如下(位于arch/x86/kernel/process.c):

int sys_fork(struct pt_regs *regs) {
    return do_fork(SIGCHLD, regs->sp, regs, 0, NULL, NULL);
}

int sys_vfork(struct pt_regs *regs) { return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->sp, regs, 0, NULL, NULL); }

sys_clone(unsigned long clone_flags, unsigned long newsp, void user parent_tid, void __user child_tid, struct pt_regs regs) { if (!newsp) newsp = regs->sp; return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid); }

可以看到,do_fork()函数被上述三个服务函数调用。do_fork()是内核创建进程的核心函数。通过分析调用过程如下,其中分析的是最新版4.X Linux源码,在i386体系结构中,通过0x80中断调用syscall:

fork系统调用分析

从图中可以看到,do_fork()和copy_process()是本文的主要分析对象。

do_fork函数的主要任务是复制原来的进程成为一个新的进程。在函数开始时,定义了一个task_struct类型的指针p,用于接收即将为新进程(子进程)分配的进程描述符。此时需要检查clone_flags是否被跟踪,即ptrace。ptrace用于标记一个进程是否被另一个进程所跟踪。跟踪最常见的例子是处于调试状态下的进程被debugger进程所跟踪。如果ptrace字段非零,说明debugger程序正在跟踪父进程,接下来通过fork_traceflag函数检测子进程是否也需要被跟踪。如果trace为1,则将跟踪标志CLONE_PTRACE加入标志变量clone_flags中。否则,可以进行进程创建,即调用copy_process()。

AMiner
AMiner

AMiner——新一代智能型科技情报挖掘与服务系统,能够为你提供查找论文、理解论文、分析论文、写作论文四位一体一站式服务。

下载
long _do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user parent_tidptr, int user child_tidptr, unsigned long tls) {
struct task_struct p;
int trace = 0;
long nr;
if (!(clone_flags & CLONE_UNTRACED)) {
if (clone_flags & CLONE_VFORK)
trace = PTRACE_EVENT_VFORK;
else if ((clone_flags & CSIGNAL) != SIGCHLD)
trace = PTRACE_EVENT_CLONE;
else
trace = PTRACE_EVENT_FORK;
if (likely(!ptrace_event_enabled(current, trace)))
trace = 0;
}
}

这条语句执行的是创建过程中最核心的工作:通过copy_process()创建子进程的描述符,并创建子进程执行时所需的其他数据结构,最终返回这个创建好的进程描述符。由于copy_process()函数非常庞大,单独开辟一篇文章来讲解其实现。

p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace, tls);

如果copy_process函数执行成功,将继续执行下面的代码。定义了一个完成量vfork,并对其进行初始化。如果使用vfork系统调用来创建子进程,那么必然是子进程先执行。这是因为vfork完成量在此处的作用:当子进程调用exec函数或退出时,向父进程发出信号。此时,父进程才会被唤醒;否则一直等待。

if (!IS_ERR(p)) {
struct completion vfork;
struct pid *pid;

trace_sched_process_fork(current, p);
pid = get_task_pid(p, PIDTYPE_PID);
nr = pid_vnr(pid);
if (clone_flags & CLONE_PARENT_SETTID)
    put_user(nr, parent_tidptr);
if (clone_flags & CLONE_VFORK) {
    p-youjiankuohaophpcnvfork_done = &vfork;
    init_completion(&vfork);
    get_task_struct(p);
}

}

接下来,通过wake_up_new_task函数使得父子进程之一优先运行;如果设置了ptrace,则需要通知跟踪器。如果CLONE_VFORK标志被设置,则通过wait操作将父进程阻塞,直到子进程调用exec函数或退出。

wake_up_new_task(p); / forking complete and child started to run, tell ptracer /
if (unlikely(trace))
ptrace_event_pid(trace, pid);
if (clone_flags & CLONE_VFORK) {
if (!wait_for_vfork_done(p, &vfork))
ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
}
put_pid(pid);
如果copy_process()在执行时发生错误,则先释放已分配的pid;再根据PTR_ERR()的返回值得到错误代码,保存于pid中。返回pid。这也就是为什么使用fork系统调用时父进程会返回子进程pid的原因。
} else {
nr = PTR_ERR(p);
}
return nr;
}

参考:linuxsyscallsabids

相关专题

更多
golang结构体相关大全
golang结构体相关大全

本专题整合了golang结构体相关大全,想了解更多内容,请阅读专题下面的文章。

196

2025.06.09

golang结构体方法
golang结构体方法

本专题整合了golang结构体相关内容,请阅读专题下面的文章了解更多。

189

2025.07.04

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

534

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

17

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

16

2026.01.06

磁盘配额是什么
磁盘配额是什么

磁盘配额是计算机中指定磁盘的储存限制,就是管理员可以为用户所能使用的磁盘空间进行配额限制,每一用户只能使用最大配额范围内的磁盘空间。php中文网为大家提供各种磁盘配额相关的内容,教程,供大家免费下载安装。

1349

2023.06.21

如何安装LINUX
如何安装LINUX

本站专题提供如何安装LINUX的相关教程文章,还有相关的下载、课程,大家可以免费体验。

702

2023.06.29

linux find
linux find

find是linux命令,它将档案系统内符合 expression 的档案列出来。可以指要档案的名称、类别、时间、大小、权限等不同资讯的组合,只有完全相符的才会被列出来。find根据下列规则判断 path 和 expression,在命令列上第一个 - ( ) , ! 之前的部分为 path,之后的是 expression。还有指DOS 命令 find,Excel 函数 find等。本站专题提供linux find相关教程文章,还有相关

294

2023.06.30

高德地图升级方法汇总
高德地图升级方法汇总

本专题整合了高德地图升级相关教程,阅读专题下面的文章了解更多详细内容。

43

2026.01.16

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PostgreSQL 教程
PostgreSQL 教程

共48课时 | 7.3万人学习

swoole入门物联网开发与实战
swoole入门物联网开发与实战

共15课时 | 1.2万人学习

swoole项目实战(第二季)
swoole项目实战(第二季)

共15课时 | 1.2万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号