news 2026/10/1 17:10:09

20262809 2026-2027-1《Linux内核原理与分析》第七周作业

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
20262809 2026-2027-1《Linux内核原理与分析》第七周作业

2026《Linux内核原理与分析》第七周作业

作者:叶丁再(学号:20262809)
实验主题:分析 Linux 内核创建一个新进程的过程
实验环境:Linux 3.18.6,x86 32 位内核,QEMU、GDB 与 MenuOS

本文根据实际实验截图和 Linux 3.18.6 源码整理。GDB 在copy_thread入口读取到的部分字段值尚未逐步执行到最终赋值处,文中会明确区分动态观察与源码分析。

一、实验目标与方法

本次实验关注fork()如何进入内核,内核怎样创建子进程的task_struct,以及子进程第一次获得 CPU 时从哪里开始执行。实验在 MenuOS 中增加了fork测试命令,并用 GDB 跟踪 Linux 3.18.6 内核中的sys_clone、do_fork、copy_process、dup_task_struct、copy_thread、wake_up_new_task和ret_from_fork。

QEMU 使用-s -S暂停 CPU 并开放 GDB 连接;GDB 加载同一构建的vmlinux后设置断点。MenuOS 的help输出确认了fork命令已加入。启动和断点设置见图 1、图 2。

图 1:使用-s -S启动 Linux 3.18.6 内核,为 GDB 跟踪作准备。

图 2:GDB 加载vmlinux并设置进程创建路径上的断点。

二、从 fork 到 sys_clone

在用户程序中调用fork()时,C 库通过 Linux 的进程创建系统调用进入内核。在本次 32 位实验中,GDB 命中了sys_clone,调用栈显示其后进入do_fork。这说明实际跟踪到的是fork对应的内核创建路径;本实验没有单步记录用户态库函数内部的入口指令,因此不把入口汇编写成动态观察结果。

sys_clone将创建参数交给do_fork。截图中 GDB 显示了clone_flags、用户栈参数和 TID 指针等参数。do_fork随后调用copy_process;创建成功后取得子进程 PID,并调用wake_up_new_task(p)让新任务进入可运行状态。

图 3:GDB 在sys_clone路径中查看调用栈及参数。

图 4:do_fork将创建请求交给copy_process。

三、task_struct 如何创建和初始化

task_struct是内核描述任务的核心结构,其中包含任务状态、进程号、调度信息、地址空间、文件表、信号处理信息和体系结构相关线程上下文等。它不是一个单纯的“进程号记录”:调度器依靠任务状态和调度字段管理任务,内存管理和文件系统则通过相应指针访问进程资源。

Linux 3.18.6 的copy_process首先调用dup_task_struct(current)。dup_task_struct为新任务分配task_struct和独立的内核栈,再复制父任务的基础状态。随后copy_process通过copy_files、copy_fs、copy_sighand、copy_mm等函数,按 clone 标志选择复制或共享各类资源。因而“复制 task_struct”不等于父子进程共享同一个任务结构,也不表示所有内存页都立即逐字节复制;普通fork的地址空间由内存管理路径建立,页面通常采用写时复制机制。

GDB 在copy_process中命中dup_task_struct调用位置,随后进入体系结构相关的copy_thread。这些断点把通用进程创建与 x86 线程上下文准备连接起来。

图 5:copy_process处理新任务,并显示本次调用的参数。

图 6:在copy_process中观察到对dup_task_struct(current)的调用。

四、执行起点与内核栈怎样配合

在 x86 32 位代码中,copy_thread使用task_pt_regs(p)找到子进程内核栈上的寄存器现场childregs。它把父进程当前的用户态寄存器现场复制到该位置,将childregs->ax设为 0;如果系统调用提供了新的用户栈指针,再更新childregs->sp。同时,内核把子进程的thread.sp设置为这份寄存器现场,把thread.ip设置为ret_from_fork。

这两类状态各自承担不同职责:thread.sp指向子进程恢复内核执行时所需的栈内容,thread.ip指定首次恢复时的内核继续执行位置;栈上的pt_regs则保存稍后返回用户态所需的寄存器状态。调度器第一次切换到这个新任务时,内核从预先准备的上下文进入ret_from_fork,执行schedule_tail,然后转入系统调用退出路径,恢复pt_regs并返回用户态。子进程因此从fork()之后继续执行,而不是从父进程正在运行的任意内核指令开始。

源码核心逻辑可概括为(Linux 3.18.6arch/x86/kernel/process_32.c):

struct pt_regs *childregs = task_pt_regs(p); p->thread.sp = (unsigned long) childregs; p->thread.sp0 = (unsigned long) (childregs + 1); *childregs = *current_pt_regs(); childregs->ax = 0; if (sp) childregs->sp = sp; p->thread.ip = (unsigned long) ret_from_fork;

图 7 是copy_thread断点和参数;图 11 展示源码中的寄存器现场处理,并记录了在函数入口读取任务字段的结果。此时p->pid仍显示为 1、p->comm为init,不能据此认定子进程最终 PID 也是 1。核对kernel/fork.c可知,copy_process在copy_thread返回后才分配并写入新 PID;任务名则继承自父进程。图 11 中的sp/ip是函数入口处读取的值,未单步到初始化赋值之后,因此不作为子进程最终栈字段的动态测量值。子进程栈与入口的结论来自对应版本源码,并由后续wake_up_new_task和ret_from_fork断点佐证执行路径。

图 7:GDB 命中 x86 32 位copy_thread,可见子任务指针和函数参数。

图 8:寄存器现场复制、子进程ax=0的源码;下方字段查询发生在函数入口,PID 与栈字段不作最终状态证据。

五、唤醒子进程并从 ret_from_fork 开始

copy_process完成任务结构和上下文准备后,do_fork调用wake_up_new_task(p)。这一步使新任务具备被调度运行的条件。截图显示 GDB 命中wake_up_new_task,随后命中汇编入口ret_from_fork。这条动态路径与copy_thread中预先设置的执行入口相互印证。

图 9:新任务准备完成后进入唤醒路径。

图 10:子进程首次执行时命中ret_from_fork。

六、运行结果与父子返回值

MenuOS 测试命令在fork()返回后分别打印父子进程信息。截图记录父进程 PID 为 1、子进程 PID 为 867;父进程获得子进程 PID,子进程输出fork() returns 0。这验证了父子进程从同一个fork()调用点继续执行,但通过各自寄存器现场观察到不同返回值。

图 11:实际运行输出区分父进程 PID、子进程 PID 以及子进程的返回值。

七、实验流程

MenuOS 调用 fork

sys_clone

do_fork

copy_process

dup_task_struct 分配 task_struct 与内核栈

复制/共享进程资源

copy_thread 准备 childregs 和 thread 上下文

分配并写入子进程 PID

wake_up_new_task

调度器首次切换到子进程

ret_from_fork / schedule_tail

恢复子进程寄存器并返回用户态

子进程 fork 返回 0;父进程得到子 PID

八、总结:我对 Linux 创建新进程的理解

我现在把fork理解为一次由内核协调完成的“任务对象创建、资源关系建立和执行现场准备”。task_struct让内核能够识别并调度任务;copy_process将通用资源复制或共享策略组织起来;copy_thread则把体系结构相关的寄存器现场、内核栈和首次执行入口接好。wake_up_new_task之后,子进程才有机会被调度运行。

子进程能从ret_from_fork顺利执行,是因为它的内核栈与恢复入口在创建阶段已经配套准备好;栈上的pt_regs又让它最终返回到父进程调用fork()后的位置。父进程看到子 PID、子进程看到 0,并非fork函数在用户态凭空分出两条结果,而是内核为两个执行上下文准备并恢复了不同的返回状态。

本次 GDB 中copy_thread的部分字段只读到了函数入口状态,没有单步验证赋值后的数值;因此我依据 Linux 3.18.6 源码说明最终的thread.sp、thread.ip设置,并用实际命中ret_from_fork和 MenuOS 父子输出验证整体执行路径。后续若要补足字段级动态证据,应在copy_thread执行过相关赋值语句后再读取字段。

参考资料

  1. Linux 3.18.6include/linux/sched.h:task_struct 定义
  2. Linux 3.18.6kernel/fork.c:copy_process、dup_task_struct、do_fork 与 sys_clone
  3. Linux 3.18.6arch/x86/kernel/process_32.c:copy_thread
  4. Linux 3.18.6arch/x86/kernel/entry_32.S:ret_from_fork

叶丁再(学号:20262809)
原创作品转载请注明出处
《Linux内核分析》MOOC课程:http://mooc.study.163.com/course/USTC-1000029000

发布后请将博客 URL 提交到网易云课堂 MOOC 平台;本文目前为 Markdown 草稿,尚未发布,故不填写博客 URL。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 17:09:46

UE5.8 AI落地指南:从官方演示到Behavior Tree实战复现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 17:08:42

ONNX Runtime 深度解析:从模型导出到生产级推理部署

面向:已有 PyTorch / TensorFlow 训练经验、需要把模型部署到生产环境(服务端 / 边缘设备 / 工业现场)的开发者。 技术栈:Python 3.8 / C17,ONNX Runtime 1.17,Windows / Linux。1. 背景1.1 深度学习部署的…

作者头像 李华
网站建设 2026/10/1 17:08:21

艾思控多轴电机驱动器选型与工程落地避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 17:07:46

射频功率放大模块采购指南:如何选择可靠的购买渠道

摘要:射频功率放大模块采购避坑,三步决策框架(明确需求、渠道分析、评估决策)助您选对供应商。 采购射频功率放大模块时,一次参数虚标可能让整机发射功率不达标,渠道混杂可能买到翻新件,售后缺…

作者头像 李华
网站建设 2026/10/1 17:07:09

CGroups 资源控制组

通过命名空间可以实现进程间在文件系统、网络以及宿主进程之间的相互隔离,但是对于宿主机的物理资源比如 CPU、内存、块设备等,如果同一机器上运行了多个相互独立的容器,这些容器在执行高负载任务时必定会因此产生严重的资源争用问题。比如&a…

作者头像 李华
网站建设 2026/10/1 17:04:43

用Godot接入大模型开发《AI侦探所》:提示词、记忆与工具调用全解析

AI游戏开发这个方向,我是从一个很偶然的念头开始的。去年我翻到一条玩家评论,说现在的游戏NPC就像提线木偶,对话永远只有三句。当时我就想:如果把大模型接进游戏里,让NPC真的会说话、会推理、会怀疑,会是什…

作者头像 李华