内核分析 第二周

2024-03-25 20:30
文章标签 分析 内核 第二周

本文主要是介绍内核分析 第二周,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

基本数据结构

/* CPU-specific state of this task */
struct Thread {unsigned long       ip;unsigned long       sp;
};typedef struct PCB{int pid;volatile long state;    /* -1 unrunnable, 0 runnable, >0 stopped */char stack[KERNEL_STACK_SIZE];/* CPU-specific state of this task */struct Thread thread;unsigned long   task_entry;struct PCB *next;
}tPCB;

内核启动

  1. 在 linux-3.9.4/include/linux/start_kernel.h 增加外部声明 void __init my_start_kernel(void)
  2. 在 linux-3.9.4/init/main.c 中的 start_kernel 函数中调用 my_start_kernel

由此可见, start_kernel 函数担任了内核初始化工作.

my_start_kernel 函数具体实现是在 mymain.c 进行初始化工作.

void __init my_start_kernel(void)
{int pid = 0;//初始j进程 pid 为 0task[pid].pid = pid;//状态为 runnabletask[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped *///进程处理函数task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;//堆栈指向该进程栈的栈顶task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];//该进程的下一个进程为本身task[pid].next = &task[pid];//类似上面初始化工作. 用 task[0] 初始化 task[1] task[2] ..//task[MAX_TASK_NUM-1]. 但修改进程 pid, state, stack, nextint i;for(i=1;i<MAX_TASK_NUM;i++){memcpy(&task[i],&task[0],sizeof(tPCB));task[i].pid = i;task[i].state = -1;task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1];/*task[1].next = task[0].next; task[0].next = task[1]task[2].next = task[1].next; task[1].next = task[2]...将所有进程通过有环链表连接起来.*/task[i].next = task[i-1].next;task[i-1].next = &task[i];}/* start process 0 by task[0] */pid = 0;//初始化 my_current_task 为 task[0]my_current_task = &task[pid];asm volatile("movl %1,%%esp\n\t"     /* 栈的 esp 指向 task[0].thread.sp 的地址 */"pushl %1\n\t"          /* task[0].thread.sp 压栈 */"pushl %0\n\t"          /* task[0].thread.ip 压栈 */"ret\n\t"               /* 将 task[0].thread.ip 保存到 cs:eip 下一条指令执行就从task[0].thread.ip 处开始执行, 即执行 my_process 函数*/"popl %%ebp\n\t"        /* 将 task[0].thread.sp 保存到 ebp */:: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp)   /* input c or d mean %ecx/%edx*/);
}

执行完 my_start_kernel 及其他系统启动函数, 最终系统启动.

问题: start_kernel 如何与 mymain.c 中的 my_start_kernel 关联的?

进程运行

目前每个 task 的入口都是 my_process.

void my_process(void) {int i = 0;while(1) {//i 会溢出. 需要在一定大小进行清零i++;//该值越大, 进程切换越不及时.int sched_check_feq = 10000000;//每执行 sched_check_feq 检查一次是否进行内核切换.if(i%sched_check_feq == 0) {printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);if(my_need_sched == 1){my_need_sched = 0;my_schedule();}printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);}if (i == sched_check_feq * 1000) {i = 0;}}
}

中断

在 arch/x86/kernel/time.c 中 setup_default_timer_irq 调用 setup_irq(0, &irq0),
而 irq0.handler = timer_interrupt. timer_interrupt 中调用了 my_timer_handler

因此, 每次时钟中断都会调用 my_timer_handler 函数.

/** Called by timer interrupt.* it runs in the name of current running process,* so it use kernel stack of current running process*/
void my_timer_handler(void)
{
#if 1//该值越大, 进程切换速度越慢. 越小, 进程切换越快.int sched_feq = 1000.//每次时钟中断, time_count 加 1, 当 time_count 整除 1000 时, 重置 my_need_sched 为 1,//my_need_sched = 1 表明下次要进行进程切换. 注意这里 time_count//在制定数量时要重置为 0, 否则 int 类型溢出if(time_count%sched_feq == 0 && my_need_sched != 1) {printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");my_need_sched = 1;}time_count ++ ;if (time_count == 1000000) {time_count = 0;}
#endifreturn;
}

进程调度

以下以 task[0] 切换到 task[1] 为例.

void my_schedule(void)
{tPCB * next;tPCB * prev;//由于 my_current_task 已经在 my_start_kernel 初始化了, 因此, 这里条件不成立.if(my_current_task == NULL|| my_current_task->next == NULL){return;}printk(KERN_NOTICE ">>>my_schedule<<<\n");//next 指向 task[1]next = my_current_task->next;//prev 指向 task[0]prev = my_current_task;//显然条件成立, 因为 task[1].[state] = 0if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */{/* switch to next process */asm volatile(//保持现场"pushl %%ebp\n\t"       /* ebp 压栈, 保存 task[0] 的栈基址 */"movl %%esp,%0\n\t"     /* 保持 esp 到内存变量 task[0]->thread.sp *///切换到 task[1] 的栈"movl %2,%%esp\n\t"     /* 将内存变量 task[1]->thread.sp 赋值给 esp,此时完成了进程栈的切换*///task 下次执行开始地址."movl $1f,%1\n\t"       /* 将 1: 的地址保存到 task[0]->thread.ip *///进行进程切换"pushl %3\n\t"          /* task[1]->thread.ip 压栈."ret\n\t"               /* 将 task[1]->thread.ip 从栈弹出放入 cs:eip,调用 task[1] 的 my_process 函数 *///下次 切换到 task[0] 从此处开始执行"1:\t"                  /*  *///如果切换到 task[0], 先将 ebp 出栈, 恢复 task[0] 的栈"popl %%ebp\n\t": "=m" (prev->thread.sp),"=m" (prev->thread.ip): "m" (next->thread.sp),"m" (next->thread.ip));//已经执行完 task[1] 的 entry 函数. my_current_task 指向 task[1]my_current_task = next;printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);}else{//标记 task[1]->state = 0 可以执行next->state = 0;//my_current_task 指向 task[1]my_current_task = next;printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);/* switch to new process */asm volatile("pushl %%ebp\n\t"       /* 保存 task[0] 栈的 ebp, 压栈, 是否可以保存在内存? */"movl %%esp,%0\n\t"     /* task[0] 的 esp 保存在 task[0]->thread.sp */"movl %2,%%esp\n\t"     /* task[1] 的 esp 保持在 esp */"movl %2,%%ebp\n\t"     /* 将 task[1]->thread.sp 保持在 ebp*/"movl $1f,%1\n\t"       /* 将 1: 地址保存在 task[0]->thread.ip, 下次task[0] 从 1: 处开始执行 */"pushl %3\n\t"          /* task[1]->thread.ip 压栈*/"ret\n\t"               /* cs:eip = task[1]->thread.ip, 从 task[1]->thread.ip处开始执行, 即调用 my_process */: "=m" (prev->thread.sp),"=m" (prev->thread.ip): "m" (next->thread.sp),"m" (next->thread.ip));}return;
}

运行结果图

总结

内核启动后, 调用 start_kernel, 其中调用了 my_start_kernel 函数. 而
my_start_kernel 的内联汇编部分将系统的 cs:eip 指向 task[0].task_entry
(my_process 的地址), 系统栈指向 task[0].thread.sp. cpu 于是开始执行
my_process. 而 my_process 是死循环while(1), 因此一直执行.

此外, 系统每过一段时间会发生一次时钟中断, time_count 加 1. 当
time_count % 1000 == 1 且 my_need_sched != 1 时, 重置 my_need_sched 为 1.
这样, my_process 中的下次执行 while(1) 发现 my_need_sched == 1, 于是根据
my_schedule 调度算法进行进程切换. 将系统当前栈指向 task[1] 的 esp, 将 cs:eip 指向task[1].task_entry(my_process 地址). 于是系统从 my_process 开始执行. (注意我们可以为每个 task 分配不同的处理函数). 即通过时钟中断进行进程切换.

如此往复, 进程从 task[0] -> task[1] -> task[2] -> task[3] -> task[0] 这样进行
无限循环. 当然, 我们可以增加 task 个数, 对 task 进行优先级调整, 权重调整, 需要
改变的仅仅是 my_schedule 部分.

至此, 整个系统就运行起来了.

进程调度算法(my_schedule):

开始先执行 task[0] 第一次调度执行 my_schedule 的 else 部分, 然后切换到 task[1] 执行它的 my_process 函数. 执行完之后, 再次调度, 执行 my_schedule 的 else 部分. 然后切换到 task[2] 执行它的my_process 函数. 之后执行 task[0]. 执行 my_schedule 的 if 部分,切换到 task[1], 执行 my_schedule 的 if 部分; 切换到 task[1], 执行 my_schedule 的 if 部分,如此往复.

刘文学 原创作品转载请注明出处 http://blog.csdn.net/wdxz6547/article/details/50815957 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

这篇关于内核分析 第二周的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

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

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

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

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

衡石分析平台使用手册-单机安装及启动

单机安装及启动​ 本文讲述如何在单机环境下进行 HENGSHI SENSE 安装的操作过程。 在安装前请确认网络环境,如果是隔离环境,无法连接互联网时,请先按照 离线环境安装依赖的指导进行依赖包的安装,然后按照本文的指导继续操作。如果网络环境可以连接互联网,请直接按照本文的指导进行安装。 准备工作​ 请参考安装环境文档准备安装环境。 配置用户与安装目录。 在操作前请检查您是否有 sud

线性因子模型 - 独立分量分析(ICA)篇

序言 线性因子模型是数据分析与机器学习中的一类重要模型,它们通过引入潜变量( latent variables \text{latent variables} latent variables)来更好地表征数据。其中,独立分量分析( ICA \text{ICA} ICA)作为线性因子模型的一种,以其独特的视角和广泛的应用领域而备受关注。 ICA \text{ICA} ICA旨在将观察到的复杂信号

【软考】希尔排序算法分析

目录 1. c代码2. 运行截图3. 运行解析 1. c代码 #include <stdio.h>#include <stdlib.h> void shellSort(int data[], int n){// 划分的数组,例如8个数则为[4, 2, 1]int *delta;int k;// i控制delta的轮次int i;// 临时变量,换值int temp;in

三相直流无刷电机(BLDC)控制算法实现:BLDC有感启动算法思路分析

一枚从事路径规划算法、运动控制算法、BLDC/FOC电机控制算法、工控、物联网工程师,爱吃土豆。如有需要技术交流或者需要方案帮助、需求:以下为联系方式—V 方案1:通过霍尔传感器IO中断触发换相 1.1 整体执行思路 霍尔传感器U、V、W三相通过IO+EXIT中断的方式进行霍尔传感器数据的读取。将IO口配置为上升沿+下降沿中断触发的方式。当霍尔传感器信号发生发生信号的变化就会触发中断在中断

kubelet组件的启动流程源码分析

概述 摘要: 本文将总结kubelet的作用以及原理,在有一定基础认识的前提下,通过阅读kubelet源码,对kubelet组件的启动流程进行分析。 正文 kubelet的作用 这里对kubelet的作用做一个简单总结。 节点管理 节点的注册 节点状态更新 容器管理(pod生命周期管理) 监听apiserver的容器事件 容器的创建、删除(CRI) 容器的网络的创建与删除