博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
OK6410A 开发板 (八) 28 linux-5.11 OK6410A 进程角度 fork的分析
阅读量:4286 次
发布时间:2019-05-27

本文共 6761 字,大约阅读时间需要 22 分钟。

fork 是 分叉的意思 , 一个进程分成两个进程之前实现了一个 多进程os,也必定实现了forkhttps://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_io			copy_thread				struct 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)); // 压栈 r5 thread->cpu_context.r5 = stack_start; } // 压栈 pc thread->cpu_context.pc = (unsigned long)ret_from_fork; // 压栈 sp thread->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 r9 ARM( 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_syscall disable_irq restore_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 放到 r2 load_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 下的 lr ldr r1, [sp, #\offset + S_PSR]// spsr 相关 // 将 svc mode 下的 sp 为地址,偏移 S_PSR, cpsr 成员 放到 r1 // 即 将 struct pt_regs 变量 中的 cpsr 放到 r1_svc // cpsr 成员 为 user mode ldr 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 下的 spsr msr spsr_cxsf, r1 // spsr 相关 // 将 r1中的值 放入 svc mode 下的 spsr // 即 将 struct pt_regs 变量 中的 cpsr 放到 spsr_svc strex 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 下的 sp add 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_fork2. 	恢复了 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不能运行fork	2.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 的 PID	kernel_clone		copy_process			struct task_struct * p = dup_task_struct(current, node);			copy_semundo			copy_files			copy_fs			copy_sighand			copy_signal			copy_mm			copy_namespaces			copy_io			copy_thread			stackleak_task_init			return 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);

转载地址:http://acigi.baihongyu.com/

你可能感兴趣的文章
《图像处理实例》 之 疏密程度统计
查看>>
支持向量机(理论+opencv实现)
查看>>
K-means算法(理论+opencv实现)
查看>>
高斯混合模型(理论+opencv实现)
查看>>
VS2015+Python3.5的配置
查看>>
分水岭算法(理论+opencv实现)
查看>>
《图像处理实例》 之 精确寻找一个圆
查看>>
opencv3.1+contrib的配置大总结(配置了两天,遇到问题无数)
查看>>
opencv小问题大智慧
查看>>
《图像处理实例》 之 车牌定位
查看>>
《opencv学习》 之 OTSU算法实现二值化
查看>>
《图像处理实例》 之 答题卡检测
查看>>
图像矩的初步探索(第十一天)
查看>>
《电路学习第一天》 之 电路设计之前的准备
查看>>
《电路学习第三天》 之 线性稳压电源的设计
查看>>
《图像处理实例》 之 目标旋转矫正(基于区域提取、DFT变换)
查看>>
不规则ROI的提取
查看>>
《图像处理实例》 之 提取特殊背景的直线
查看>>
《电路学习第三天》 之 彩扩机项目设计
查看>>
《图像处理实例》 之 物体计数
查看>>