OK6410A 开发板 (八) 28 linux-5.11 OK6410A 进程角度 fork的分析

2024-05-27 15:48

本文主要是介绍OK6410A 开发板 (八) 28 linux-5.11 OK6410A 进程角度 fork的分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

fork 是 分叉的意思 , 一个进程分成两个进程
之前实现了一个 多进程os,也必定实现了fork
https://github.com/lisider/learn_os/tree/master/process
看一下 fork 一个进程 XXX 的本质是什么1. 为 TCB 找一块空间2. 填充 sp 成员3. 在 sp 中压栈(压入入口地址,参数,cpsr)4. 将 TCB 插入 调度时 调度器会访问(在该链表中选择下一个进程)的链表
在调度的时候A. 选择 XXX 作为 下一个进程B. 保存上一个进程C. 根据 XXX 获取 XXX 的 sp成员D. 将 XXX 的 sp成员 放入 当前 的 sp 寄存器中E. 弹栈(入口地址到pc寄存器,参数到r0寄存器/r1寄存器...,cpsr成员到cpsr寄存器)F. 开始执行 XXX 进程可以看出, 这个小 os 的 fork 其实比较简单,但是也完成了 task_struct 里面所有成员的初始化
意思是 fork 要完成 新的 task_struct 里面成员的初始化(sp/next)
意思是 task_struct 越大,fork越复杂
现实是 linux 的 task_struct 很大, fork 很复杂
所以要抓住 task_struct 中的关键成员,来捋一下 fork的流程
  • fork 的流程 从 sys_fork
// 我们关注的是 sp 及 sp中压栈 及 插入运行队列
SYSCALL_DEFINE0(fork)kernel_clone(&args);copy_process(NULL, trace, NUMA_NO_NODE, args);copy_iocopy_threadstruct thread_info *thread = task_thread_info(p);struct pt_regs *childregs = task_pt_regs(p);if (likely(!(p->flags & PF_KTHREAD))){ // 用户进程*childregs = *current_pt_regs(); // 复制 18个成员childregs->ARM_r0 = 0; // 重新赋值ARM_r0 成员childregs->ARM_sp = stack_start;// 重新赋值ARM_sp 成员// 注意 : childregs->ARM_lr 在 *childregs = *current_pt_regs(); 时 赋值初值} else {// 内核进程memset(&thread->cpu_context, 0, sizeof(struct cpu_context_save));// 压栈 r5thread->cpu_context.r5 = stack_start;}// 压栈 pcthread->cpu_context.pc = (unsigned long)ret_from_fork;// 压栈 spthread->cpu_context.sp = (unsigned long)childregs;...wake_up_new_task(p);// 插入运行队列为什么要压栈 ret_from_fork ,而不是用户空间的入口函数1.因为下次调入的时候,肯定是在内核,恢复的时候,pc肯定要为内核空间的地址而 ret_from_fork 符合 要求2.内核入口出口需要统一管理,出口 交由 ret_from_fork  管理ret_from_fork  的过程总结
其实就是 从 svc mode 转向 另一个 mode 的一个过程(另一个mode 的 环境 在 sp_svc 指向的 struct pt_regs 中)
过程分为两个子过程1.设置 另一个mode 的全部寄存器(如果是user,则是r0-r15 & cpsr)2.同时设置 svc mode 的 bank寄存器 (R13_svc/R14_svc/SPSR_svc)ret_from_fork get_thread_info tsk // tsk .req    r9ARM(   mov \rd, sp, lsr #THREAD_SIZE_ORDER + PAGE_SHIFT    )mov \rd, \rd, lsl #THREAD_SIZE_ORDER + PAGE_SHIFT// 将sp进行8KB对齐后的值赋给寄存器r9// r9_svc 中存储 thread_info 的值// 用作后面的 work_pending 的判断// work_pending 没有在 本文中显示,请查找源码/*15 struct pt_regs {                                                                 16     unsigned long uregs[18];                                                     17 }; // 从下到上,存了// r0-r15// cpsr// ARM_ORIG_r0 // 也叫作OLD_R0*/// pt_regs  在 task_truct 地址 上 8K的位置ret_slow_syscalldisable_irqrestore_user_regs fast = 0, offset = 0// 第一次的sp_svc ,应该是 struct pt_regs 变量(在task_struct 8K 最上端)的地址-sizeof(struct pt_regs)// 即 struct pt_regs 的底端mov r2, sp // 将 svc mode 下的 sp  放到 r2load_user_sp_lr r2, r3, \offset + S_SP  @ calling sp, lr // 切换到 SYS mode// ********************************************************************************* 重点1 user mode 下的 r13 r14// 将 r2+S_SP地址 的值(struct pt_regs 中的 ARM_sp) 放到 SYS mode 下的sp (同 user mode 下的 sp)   // r2 地址中的值 是 新建用户进程 的栈 , 值 为 stack_start// 将 r2+S_SP+4地址 的值(struct pt_regs 中的 ARM_lr) 放到 SYS mode 下的lr(同 user mode 下的 lr) // r2 +4 地址中的值 是 新建用户进程 的第一条指令的值// 切换到 SVC mode// ********************************************************************************* 重点A svc mode 下的 lrldr r1, [sp, #\offset + S_PSR]// spsr 相关// 将 svc mode 下的 sp 为地址,偏移 S_PSR, cpsr 成员 放到 r1// 即 将 struct pt_regs 变量 中的 cpsr 放到 r1_svc// cpsr 成员 为 user modeldr lr, [sp, #\offset + S_PC]// 将 svc mode 下的 sp 为地址,偏移 S_PC, pc 成员 放到 lr// 即 将 struct pt_regs 变量 中的 pc 放到 lr_svc// 第二次的sp_svc:应该是 struct pt_regs 变量 地址偏移 52 地址 (而不是地址中的值)add sp, sp, #\offset + S_SP// svc mode 的 sp = sp + sp成员值// ********************************************************************************* 重点B svc mode 下的 spsrmsr spsr_cxsf, r1 // spsr 相关// 将 r1中的值 放入 svc mode 下的 spsr// 即 将 struct pt_regs 变量 中的 cpsr 放到 spsr_svcstrex   r1, r2, [sp]// clear the exclusive monitor // 将 struct pt_regs 中的 ARM_sp 置为 该值 // struct pt_regs 变量(在task_struct 8K 最上端)的地址-sizeof(struct pt_regs)// STREX Rx ,Ry,[Rz] // 将Ry寄存器中的值读出来放到Rz指向的内存单元处// 如果Rz内存单元的状态为Exclusive Access state,则Rx的值将会被赋值为0// 而如果Rz内存单元的状态为Open Access state的话,Rx的值将会被赋值为1// 并且Ry的值也不会被加载到Rz指向的内存单元中。也就是指令失败ldmdb   sp, {r0 - r12}// ********************************************************************************* 重点2 user mode 下的 r10-r12// 加载 struct pt_regs 中的 (ARM_r0-ARM_r12变量) 到 svc mode 下的 r0-r12(即usermode下的r0-r12)// 第三次的sp_svc:// ********************************************************************************* 重点C svc mode 下的 spadd sp, sp, #S_FRAME_SIZE - S_SP// 设置 svc mode 下的 sp , 值 为 struct pt_regs 的 顶端movs    pc, lr// ********************************************************************************* 重点3 user mode 下的 pc cpsr (user mode 下 没有 spsr)// 更改 cpsr 为 spsr_svc 中的值(即切换到 usermode)// 将 lr_user 的值 放到 pc_user// ARM处理器相应异常时,会自动完成将当前的PC保存到LR寄存器// 此时 fork 的用户进程开始执行
其他
  • 每个 task_struct 对应的 struct pt_regs 与 struct thread_info 中的 struct cpu_context_save
系统调用对应的数据结构是pt_regs,而进程调度使用的是thread_info -> cpu_context_save用户进程A fork 出 用户进程B 参与了 系统调用(这次系统调用与我们讨论的无关),从而fork完成用户进程B 的执行 是 进程调度的结果,而且调入后处于内核态,需要 从系统调用返回的路径 返回 用户空间 // 该过程涉及到了进程调度和系统调用
所以这两个结构体都涉及到了
1. fork 的时候填充了 pt_regs其中 r0 为 0其中 sp 为 stack_start // 是 函数指针,为入口函数// 为什么 sp 是 stack_start(一个函数指针?)// TODO// 后来被填充 到 新建用户进程的 sp寄存器其中 lr 为 新建用户进程 的第一条指令// copy_thread -> *childregs = *current_pt_regs(); 时赋值初值 // 如果后面有exec ,则会再次赋值填充了 cpu_context_save其中sp 为 pt_regs其中pc 为 ret_from_fork
2. 恢复了 cpu_context_save,从而被调入调入第一句为ret_from_fork调入后的栈是 pt_regs恢复了 pt_regs,从而走向用户空间struct pt_regs 中的 offsetof(struct pt_regs, ARM_sp) 是什么
成员sp是什么
  • fork的消费者
kernel_clone 的调用者 有 1.SYSCALL_DEFINE0(fork) // 系统调用sys_fork ,用户进程创建用户进程调用的函数// 拷贝 mm_struct fs files signal// 写时复制 // 依赖 MMU// 所以 没有MMU的linux不能运行fork2.kernel_thread	// 内核创建内核进程调用的函数3.SYSCALL_DEFINE0(vfork)// 拷贝 fs files signal// 不 拷贝 mm_struct// CLONE_VM (共享)(共享同一个 VM)4.SYSCALL_DEFINE[5/6](clone // 不拷贝 mm_struct fs files signal thread// 共享 所有的资源// 两个 task_struct 的所有资源 是 一样的,是同一个的5.SYSCALL_DEFINE2(clone3// 拷贝什么由系统调用者自己定义// 人妖?// 进程A 不管是调用 clone 还是 fork 还是 vfork 都会创建一个 task_struct(进程B)// A 如果调用fork创建 B , 则 AB的TGID 不同// A 如果调用clone创建 B , 则 AB的TGID 相同// B的TGID 来自于 A// 调用 getpid 获取的 是 task_struct 的 TGID// 调用 gettid 获取的 是 task_struct 的 PIDkernel_clonecopy_processstruct task_struct * p = dup_task_struct(current, node);copy_semundocopy_filescopy_fscopy_sighandcopy_signalcopy_mmcopy_namespacescopy_iocopy_threadstackleak_task_initreturn p;wake_up_new_task__task_rq_lock(p, &rf); activate_task(rq, p, ENQUEUE_NOCLOCK);enqueue_task	p->sched_class->enqueue_task// 将进程添加到具体的运行队列中,以enqueue_task_fair 为例task_rq_unlock(rq, p, &rf);

这篇关于OK6410A 开发板 (八) 28 linux-5.11 OK6410A 进程角度 fork的分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1007907

相关文章

linux-基础知识3

打包和压缩 zip 安装zip软件包 yum -y install zip unzip 压缩打包命令: zip -q -r -d -u 压缩包文件名 目录和文件名列表 -q:不显示命令执行过程-r:递归处理,打包各级子目录和文件-u:把文件增加/替换到压缩包中-d:从压缩包中删除指定的文件 解压:unzip 压缩包名 打包文件 把压缩包从服务器下载到本地 把压缩包上传到服务器(zip

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

Linux_kernel驱动开发11

一、改回nfs方式挂载根文件系统         在产品将要上线之前,需要制作不同类型格式的根文件系统         在产品研发阶段,我们还是需要使用nfs的方式挂载根文件系统         优点:可以直接在上位机中修改文件系统内容,延长EMMC的寿命         【1】重启上位机nfs服务         sudo service nfs-kernel-server resta

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57

【Linux 从基础到进阶】Ansible自动化运维工具使用

Ansible自动化运维工具使用 Ansible 是一款开源的自动化运维工具,采用无代理架构(agentless),基于 SSH 连接进行管理,具有简单易用、灵活强大、可扩展性高等特点。它广泛用于服务器管理、应用部署、配置管理等任务。本文将介绍 Ansible 的安装、基本使用方法及一些实际运维场景中的应用,旨在帮助运维人员快速上手并熟练运用 Ansible。 1. Ansible的核心概念

Linux服务器Java启动脚本

Linux服务器Java启动脚本 1、初版2、优化版本3、常用脚本仓库 本文章介绍了如何在Linux服务器上执行Java并启动jar包, 通常我们会使用nohup直接启动,但是还是需要手动停止然后再次启动, 那如何更优雅的在服务器上启动jar包呢,让我们一起探讨一下吧。 1、初版 第一个版本是常用的做法,直接使用nohup后台启动jar包, 并将日志输出到当前文件夹n