linux内核初始化阶段-fork内嵌问题

1.在linux内核初始化程序中fork需要_syscall0(int,fork) 的背景

1.1.背景

  • 内核的main中线进行了所有硬件初始化工作,包括陷阱门、块设备、字符设备和tty,包括人工设置第一个任务(task 0)。待所有初始化工作完成后就设置中断允许标志以开启中断,main()也切换到了任务0中运行。
  • 在整个内核完成初始化后,内核将执行权切换到了用户模式(任务0),也即CPU 从0 特权级切换到了第3 特权级。此时main.c 的主程序就工作在任务0 中。然后系统第一次调用进程创建函数fork(),创建出一个用于运行init()的子进程。
    image
  • 此后程序把自己“手工”移动到任务0(进程0)中运行,并使用fork()调用首次创建出进程1(init 进程)。在init 进程中程序将继续进行应用环境的初始化并执行shell 登录程序。而原进程0 则会在系统空闲时被调度执行,此时任务0 仅执行pause()系统调用,并又会调用调度函数。
  • 在 init 进程中,如果终端环境建立成功,则会再生成一个子进程(进程2),用于运行shell 程序/bin/sh。若该子进程退出,则父进程进入一个死循环内,继续生成子进程,并在此子进程中再次执行shell 程序/bin/sh,而父进程则继续等待。

1.2.重点来了-为啥需要_syscall0(int,fork)

  • 由于创建新进程的过程是通过完全复制父进程代码段和数据段的方式实现的,因此在首次使用fork()创建新进程init 时,为了确保新进程用户态堆栈没有进程0 的多余信息,要求进程0 在创建首个新进程之前不要使用用户态堆栈,也即要求任务0 不要调用函数。因此在main.c 主程序移动到任务0 执行后,任务0 中的代码fork()不能以函数形式进行调用。程序中实现的方法是采用gcc 函数内嵌的形式来执行这个系统调用
static inline _syscall0(int,fork)
// 这是unistd.h 中的内嵌宏代码。以嵌入汇编的形式调用Linux 的系统调用中断0x80。该中断是所有
// 系统调用的入口。该条语句实际上是int fork()创建进程系统调用。
// syscall0 名称中最后的0 表示无参数,1 表示1 个参数。参见include/unistd.h,133 行

image

// 本程序将会在移动到用户模式(切换到任务0)后才执行fork(),因此避免了在内核空间写时复制问题。
// 在执行了moveto_user_mode()之后,本程序就以任务0 的身份在运行了。而任务0 是所有将创建的子
// 进程的父进程。当创建第一个子进程时,任务0 的堆栈也会被复制。因此希望在main.c 运行在任务0
// 的环境下时不要有对堆栈的任何操作,以免弄乱堆栈,从而也不会弄乱所有子进程的堆栈

2.内联函数 + 宏定义的作用

  • 在编译链接前会对源文件进行预处理,它会将源文件中的头文件以及宏(在这里就是把用#define定义的_syscall宏展开)都展开。
  • gcc会把上述“函数”体中的语句直接插入到调用fork()语句的代码处,因此执行fork()不会引起函数调用

3.参考

《Linux内核完全注释》
写时复制(COW)详解
[fork()与_syscall0(int,fork) 关系](fork()与_syscall0(int,fork) 关系)
main.c中关于pause和fork的内嵌问题
Linux内核堆栈使用方法 进程0和进程1

posted @ 2024-05-24 22:02  胖白白  阅读(12)  评论(0编辑  收藏  举报