1.linux缺页中断源码分析(基于linux0.11)
2.linux系统调用之write源码解析(基于linux0.11)
3.linux0.11源码分析-fork进程
4.Linux0.11main函数启动初始化(二)
5.通过do_execve源码分析程序的执行(上)(基于linux0.11)
linux缺页中断源码分析(基于linux0.11)
Linux中的缺页中断是在虚拟地址转为物理地址时,若找不到有效物理内存时触发的。初始化时,系统已注册了处理缺页中断的函数,其中断号为。这个中断处理的java源码阅读教程核心部分是通过汇编代码page_fault实现的,具体处理流程在do_no_page函数中进行。
当缺页涉及堆或栈空间时,系统会直接分配一块新的物理地址进行分配。然而,如果问题出在代码执行区域,系统会首先检查是否存在另一个进程正在使用相同的执行文件。若存在,将检查这两个进程是否可以共享内存。如果共享条件不满足,系统就会从硬盘读取缺失页面的内容,更新页表项,然后重新执行引发中断的地址,确保能找到所需的页面信息。
linux系统调用之write源码解析(基于linux0.)
Linux系统的write函数在底层操作上与read函数有相似之处。本文主要关注一般文件的写操作,我们首先从入口函数开始解析。python 随机 源码
进入file_write函数,它的核心逻辑是根据文件inode中的信息,确定要写入的硬盘位置,即块号。如果目标块已存在,就直接返回块号;若不存在,则需要创建新的块。这个过程涉及到bmap函数,它负责根据文件系统状态为新块申请空间并标记为已使用。
创建新块的过程涉及到文件系统的超级块,通过检查当前块的使用情况,申请一个空闲块,并更新超级块以标记其为已使用。接着,超级块信息会被写回到硬盘,同时返回新建的块号。
回到file_write,处理完块的逻辑后,由于是新创建的块,其内容默认为0。这时,阳光白马源码bread函数会读取新块的内容,这部分逻辑可以参考read函数的分析。读取后,用户数据会被写入buffer,同时标记为待写回(脏)状态。重要的是,数据实际上并未立即写入硬盘,而是先存储在缓存中。系统会通过后台线程定期将缓存中的内容刷新到硬盘。
linux0.源码分析-fork进程
在操作系统中,Linux0.源码中的fork函数执行流程分为启动和系统调用两个阶段。启动阶段首先在init/main.c中执行init用于启动shell,让用户执行命令。
在include/unistd.h中定义了宏,表示将__NR_fork的值复制给eax寄存器,并将_res与eax绑定。使用int 0x中断后,系统调用函数system_call被调用,从sys_call_table中找到对应的函数执行。fork函数执行时,操作系统会在内核栈里保存相关寄存器,ssr翻墙源码准备中断返回。
接着,操作系统通过int调用system_call,在kernel/system_call.s中执行call _sys_call_table(,%eax,4)指令。内核栈中,因为是段内跳转,所以cs不需要入栈。ip指向call指令的下一句代码。执行call指令进入系统调用表。
在includ/linux/sys.h中,系统调用表是一个数组,根据eax即系统函数编号找到对应的函数执行。对于fork,__NR_fork值2被放入eax寄存器,%eax * 4找到sys_fork。执行sys_fork后,调用find_empty_process函数找到可用的进程号,并放入eax寄存器返回。
接着,系统调用执行copy_process函数建立新进程结构体并复制数据。新进程的手机源码phpip出栈,执行完copy_process后,系统调用返回,内核栈状态改变。此阶段最后通过iret指令弹出寄存器,恢复中断前状态。
总结,fork函数通过复制当前进程结构体、处理信号并初始化新进程,实现父进程与子进程的创建与共享。子进程返回值为0,父进程返回新子进程的pid。通过fork函数的执行,操作系统能够高效地创建进程,实现多任务处理。
Linux0.main函数启动初始化(二)
在Linux0.的初始化过程中,main函数起到关键的启动作用。首先,sti指令被调用,这表示中断功能的开启,模块初始化工作至此完成。
接着,move_to_user_mode()函数被执行,这标志着程序从内核模式切换至用户模式,即执行任务0,并通过iret指令实现切换。iret指令前,数据被压入堆栈,执行后,数据根据标志位被弹入相应的寄存器。这里需要理解GDT、LDT、GDTR、LDTR和保护模式寻址方式,具体可以参考相关教程。
在切换到用户模式后,所有的进程ss0寄存器被初始化为0x,esp0则设置为PAGE_SIZE+(long)p。理解这一操作对后续学习至关重要。而cs选择子则分别为0x和0x0f,分别对应ss和cs寄存器的初始化。cs选择子从ldt的第1项开始,即进程0的代码段。
进程0主要由ldt段表示,包括代码段的展开与详细内容分析。该段基址为地址0,段限长为kb,DPL为3,说明优先级为用户。
接下来,进程0回到main函数。fork函数执行后,进程0回到main函数中。
init函数在main()中已经进行系统初始化,包括内存管理和硬件设备驱动程序等。init()函数运行在任务0第一次创建的子进程中,对bin/sh程序的环境进行初始化。
setup系统调用在目录kernel/blk_drv/hd.c中实现,对应函数为sys_setup()。此函数主要设置硬盘分区结构、RAMDISK,并挂载安装文件系统。
在执行/bin/sh后,shell程序启动,至此,Linux0.的初始化过程完成。用户可通过控制台输入命令与内核交互。printf函数产生格式化信息并输出至标准输出设备stdout,即屏幕显示。接下来,将详细分析进程管理。
通过do_execve源码分析程序的执行(上)(基于linux0.)
execve函数是操作系统的关键功能,它允许程序转变为进程。本文通过剖析do_execve源码,揭示程序转变成进程的机制。do_execve被视为系统调用,其运行过程在前文已有详细解析,此处不再赘述。分析将从sys_execve函数开启。
在执行_do_execve前,先审视内核栈。接下来,我们将深入理解do_execve的实现。
在加载可执行文件时,存在两种情况:编译后的二进制文件与脚本文件。脚本文件需加载对应解释器,本文仅探讨编译后的二进制文件。解析流程如下:首先验证文件可执行性和当前进程权限,通过后,仅加载头部数据,具体代码在真正运行时通过缺页中断加载。然后,申请物理内存并存储环境变量和参数,该步骤在copy_string函数中实现。
完成上述步骤后,内核栈结构发生变化。接着,执行代码释放原进程页目录和页表项信息,解除物理地址映射,这些信息通过fork继承。随后,调用change_ldt函数设置代码段、数据段基地址和限长,其中数据段限长为MB,代码段限长根据执行文件头部信息确定。完成物理地址映射后,内存布局随之调整。
紧接着,通过create_tables函数分配执行环境变量和参数的数组。执行完毕后,内存布局进一步调整。最后,设置栈、堆位置,以及eip为执行文件头部指定值,esp为当前栈位置,至此,可执行文件加载阶段完成。下文将探讨执行第一条指令后的后续步骤。