分析 Linux 内核创建一个新进程的过程

1.实验过程

下载新menu替换原来的menu并make:

使用gdb设置断点并调试,调试过程如下图所示:

2.实验分析及总结

do_fork关键代码分析

 long do_fork(unsigned long clone_flags,unsigned long stack_start,unsigned long stack_size,int __user *parent_tidptr,int __user *child_tidptr)
{//创建进程描述符指针struct task_struct *p;//……//复制进程描述符,copy_process()的返回值是一个 task_struct 指针。p = copy_process(clone_flags, stack_start, stack_size,child_tidptr, NULL, trace);if (!IS_ERR(p)) {struct completion vfork;struct pid *pid;trace_sched_process_fork(current, p);//得到新创建的进程描述符中的pidpid = get_task_pid(p, PIDTYPE_PID);nr = pid_vnr(pid);if (clone_flags & CLONE_PARENT_SETTID)put_user(nr, parent_tidptr);//如果调用的 vfork()方法,初始化 vfork 完成处理信息。if (clone_flags & CLONE_VFORK) {p->vfork_done = &vfork;init_completion(&vfork);get_task_struct(p);}//将子进程加入到调度器中,为其分配 CPU,准备执行wake_up_new_task(p);//fork 完成,子进程即将开始运行if (unlikely(trace))ptrace_event_pid(trace, pid);//如果是 vfork,将父进程加入至等待队列,等待子进程完成if (clone_flags & CLONE_VFORK) {if (!wait_for_vfork_done(p, &vfork))ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);}put_pid(pid);} else {nr = PTR_ERR(p);}return nr;
}

dup_task_struct关键代码分析 

static struct task_struct *dup_task_struct(struct task_struct *orig)
{struct task_struct *tsk;struct thread_info *ti;int node = tsk_fork_get_node(orig);int err;// 分配一个task_struct结点tsk = alloc_task_struct_node(node);if (!tsk)return NULL;// 分配一个thread_info结点,其实内部分配了一个union,包含进程的内核栈// 此时ti的值为栈底,在x86下为union的高地址处。ti = alloc_thread_info_node(tsk, node);if (!ti)goto free_tsk;err = arch_dup_task_struct(tsk, orig);if (err)goto free_ti;// 将栈底的值赋给新结点的stacktsk->stack = ti;.../** One for us, one for whoever does the "release_task()" (usually* parent)*/// 将进程描述符的使用计数器置为2atomic_set(&tsk->usage, 2);
#ifdef CONFIG_BLK_DEV_IO_TRACEtsk->btrace_seq = 0;
#endiftsk->splice_pipe = NULL;tsk->task_frag.page = NULL;account_kernel_stack(ti, 1);// 返回新申请的结点return tsk;free_ti:free_thread_info(ti);
free_tsk:free_task_struct(tsk);return NULL;
}

copy_thread关键代码分析 

// 初始化子进程的内核栈
int copy_thread(unsigned long clone_flags, unsigned long sp,unsigned long arg, struct task_struct *p)
{// 取出子进程的寄存器信息struct pt_regs *childregs = task_pt_regs(p);struct task_struct *tsk;int err;// 栈顶 空栈p->thread.sp = (unsigned long) childregs;p->thread.sp0 = (unsigned long) (childregs+1);memset(p->thread.ptrace_bps, 0, sizeof(p->thread.ptrace_bps));// 如果是创建的内核线程if (unlikely(p->flags & PF_KTHREAD)) {/* kernel thread */memset(childregs, 0, sizeof(struct pt_regs));// 内核线程开始执行的位置p->thread.ip = (unsigned long) ret_from_kernel_thread;task_user_gs(p) = __KERNEL_STACK_CANARY;childregs->ds = __USER_DS;childregs->es = __USER_DS;childregs->fs = __KERNEL_PERCPU;childregs->bx = sp;  /* function */childregs->bp = arg;childregs->orig_ax = -1;childregs->cs = __KERNEL_CS | get_kernel_rpl();childregs->flags = X86_EFLAGS_IF | X86_EFLAGS_FIXED;p->thread.io_bitmap_ptr = NULL;return 0;}// 将当前进程的寄存器信息复制给子进程*childregs = *current_pt_regs();// 子进程的eax置为0,所以fork的子进程返回值为0childregs->ax = 0;if (sp)childregs->sp = sp;// 子进程从ret_from_fork开始执行p->thread.ip = (unsigned long) ret_from_fork;task_user_gs(p) = get_user_gs(current_pt_regs());p->thread.io_bitmap_ptr = NULL;tsk = current;err = -ENOMEM;// 如果父进程使用IO权限位图,那么子进程获得该位图的一个拷贝if (unlikely(test_tsk_thread_flag(tsk, TIF_IO_BITMAP))) {p->thread.io_bitmap_ptr = kmemdup(tsk->thread.io_bitmap_ptr,IO_BITMAP_BYTES, GFP_KERNEL);if (!p->thread.io_bitmap_ptr) {p->thread.io_bitmap_max = 0;return -ENOMEM;}set_tsk_thread_flag(p, TIF_IO_BITMAP);}...return err;
}

Linux通过复制父进程来创建一个新进程,通过调用do_ fork来实现并为每个新创建的进程动态地分配一个task_ struct结构。进程是处于执行期程序以及其相关资源的总称。在Linux系统中,对于进程和线程并没有明显的区分,线程是一种特殊的进程。Linux系统常用fork()创建子进程。fork()由父进程来进行调用并且由sys_clone系统调用实现,最后通过exit()退出执行。
Linux的进程创建由fork()与exec()来完成,前者拷贝当前进程创建子进程,后者负责读取可执行文件并将其载入地址空间开始运行。
fork只会被调用一次,却能够返回两次,它可能有三种不同的返回值:
在父进程中,fork返回新创建子进程的进程ID;
在子进程中,fork返回0;
如果出现错误,fork返回一个负值;
在进程描述符PID中,state描述了进程当前的状态,TASK_RUNNING标识该进程正在运行或等待运行,是否在运行取决于它是否取得了内核的控制权,这是进程在用户空间执行的唯一可能状态。

新进程是从哪里开始执行的?为什么从那里能顺利执行下去,执行起点与内核堆栈如何保证一致?
dup_task_struct为新进程分配了新的堆栈,copy_process调用sched_fork将新进程的state置为TASK_RUNNING,copy_thread中执行语句*childregs = *current_ pt_ regs()将父进程保存的寄存器上下文复制给子进程,保证了父子进程堆栈信息的一致,从而保证了执行起点与内核堆栈的一致。最后将ret_from_fork的地址设置为eip寄存器的值,新进程即从这里开始执行。

20222817《Linux内核原理与分析》第七周作业相关推荐

  1. 2017-2018-1 20179215《Linux内核原理与分析》第二周作业

    20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...

  2. 2022-2023-1 20222809《Linux内核原理与分析》第一周作业

    Linux内核原理与分析第一周作业 配置环境 1.参考Linux(Ubuntu)系统安装图文教程中第二种借助virtualbox成功配置Ubuntu环境 2.升级更新软件包 可以通过调节分辨率和虚拟机 ...

  3. 实验楼 linux内核原理与分析,《Linux内核原理与分析》第一周作业 20189210

    实验一 Linux系统简介 这一节主要学习了Linux的历史,Linux有关的重要人物以及学习Linux的方法,Linux和Windows的区别.其中学到了LInux中的应用程序大都为开源自由的软件, ...

  4. 《Linux内核原理与分析》第二周作业

    反汇编一个简单的C程序 1.实验要求 使用: gcc –S –o test.s test.c -m32 命令编译成汇编代码,对汇编代码进行分析总结.其中test.c的具体内容如下: int g(int ...

  5. 2018-2019-1 20189218《Linux内核原理与分析》第九周作业

    进程调度的时机 进程调度时机就是内核调用schedule函数的时机.当内核即将返回用户空间时,内核会检查need_resched标志是否设置.如果设置,则调用schedule函数,此时是从中断(或者异 ...

  6. 2021-2022-1 20212820《Linux内核原理与分析》第一周作业

    声明:本文是基于Linux 基础入门_Linux - 蓝桥云课 (lanqiao.cn)这门课学习所写的课程笔记. 实验1 Linux系统简介 Linux主要包括是系统调用和内核两部分 Linux与W ...

  7. 20189220 余超《Linux内核原理与分析》第一周作业

    实验一 Linux系统简介 通过实验一主要是学习到了Linux 的历史简介,linux与windows之间的区别,主要是免费和收费,软件和支持,安全性,使用习惯,可制定性,应用范畴等.linux具有稳 ...

  8. 2018-2019-1 20189201 《LInux内核原理与分析》第九周作业

    那一天我二十一岁,在我一生的黄金时代.我有好多奢望.我想爱,想吃,还想在一瞬间变成天上半明半暗的云.那一年我二十一岁,在我一生的黄金时代.我有好多想法.我思索,想象,我不知该如何行动,我想知道一个城市 ...

  9. 2018-2019-1 20189208《Linux内核原理与分析》第九周作业

    活动 main函数编译有问题,div 函数和系统中某个函数重名,浮点输出有问题,scanf也有问题 修改如下 scanf_s("%d %d", &a, &b); p ...

  10. 2021-2022-1 20212808《Linux内核原理与分析》第一周作业

    一.实验中出现的问题 理解不清二进制数字表示和加减赋值表示文件权限.比如 chmod 600 test.txt 中600是如何计算的. chmod abc file chmod 600 text.tx ...

最新文章

  1. 数据清洗指南完整分享
  2. 【079】用代码来创建 Android 控件
  3. 计算机缺失wininet.dll,xp系统开机提示wininet.dll文件丢失怎么解决
  4. android 如何调用 隐藏的 API 接口
  5. 文献记录(part44)--Skeletonisation algorithms with theoretical guarantees for unorganised point ...
  6. Vision Transformer 论文解读
  7. Ubuntu关闭防火墙
  8. 每个Java程序员必须知道的5个JVM命令行标志
  9. C# 值类型和引用类型
  10. 51Nod 1046 A^B Mod C(日常复习快速幂)
  11. 【使用R语言两行语句将搜狗词库转为csv格式】
  12. 推荐一个项目管理工具:TAPD
  13. 八皇后问题(回溯算法)
  14. 五子棋的实现 Java课程设计
  15. 移动apn接入点哪个快_千兆交换机和快速以太网交换机哪个更好呢?
  16. 转载:深入浅出的讲解傅里叶变换
  17. tsc HPET kvm-clock
  18. Vue packages version mismatch: - vue@2.6.11 vue-template-compiler@2.6.10 的解决办法
  19. 《这么慢,那么美》------ 听见
  20. 敏捷教练的八种失败角色

热门文章

  1. horizon部署linux桌面,7-horizon仪表盘服务部署
  2. linux find 递归搜索文件名
  3. 有一种虫叫蠓虫,蚊子在它面前不值一提
  4. 评委打分(JAVA代码)
  5. 网易考拉等巨头的“电商+直播”模式,或是找到了新的突破口?
  6. codemirror mysql_Angular6 CodeMirror在线编辑sql 智能提示
  7. Trojan/Android.GDownload.jw[exp,gen] 病毒报警解决方案
  8. 制作自己的第一个网页
  9. Android 开发日记 - Bold-Italic在TextView中,最后的字会被截掉
  10. 希捷银河企业级硬盘,智能高效首选 1