一个超简单的系统内核

编写一个简单的内核模块 #include #include #include static int __init lkp_init(void) { printk(KERN_EMERG"Hello,Kernel!\n"); printk(KERN_ALERT"Hello,Kernel!\n"); printk(KERN_CRIT"Hello,Kernel!\n"); printk(KE 阅读详情

“郭孟琦 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 ”

这一周的作业是“完成一个简单的时间片轮转多道程序内核代码”,其实在接触linux之前曾在stm32平台上跑过ucos-ii,及ucos-iii虽然ucos的内核是抢占式的任务调度但也是一个多任务的操作系统也具有任务控制块(PCB)等概念。其实在移植ucos过程中也需要编写一些汇编语言但自己都是参考手册直接照着写的,这个礼拜在上完课后也算是大致明白如何通过中断使程序从一个任务跳到另一个任务了,相信对我以后进一步学习ucosiii也有很大帮助,因为ucosiii也开始支持这种时间片轮转调度任务。


首先根据老师提供的网址https://github.com/mengning/mykernel配置实验环境,按照步骤下来很顺利。

运行内核 my_start_kernel在执行,同时my_timer_handler时钟中断处理程序周期性执行。

下面编写一个简单的时间片轮转多道程序内核代码

首先是PCB的定义mypcb.h

#define MAX_TASK_NUM		4
#define KERNEL_STACK_SIZE	1024*8

struct Thread {
	unsigned long 	ip;
	unsigned long 	sp;
};

typedef struct PCB{
	int pid;
	volatile long state;  /* -1 unrunnable, 0*/
	char stack[KERNEL_STACK_SIZE];
	struct Thread thread;
	unsigned long task_entry;
	struct PCB *next;
}tPCB;
	
void my_schedule(void);

这里我犯了一个错误,起初没注意把栈大小设置为8 结果运行时出现了错误,很显然是栈爆掉了。。。但这也给我一个启示,栈的大小应该如何确定?

因为在栈里储存着局部变量以及任务的eip等内容,理论上局部变量越多应该分配更多的栈空间。


然后是初始化函数和任务函数mymain.c

#include "mypcb.h"

tPCB task[MAX_TASK_NUM];
tPCB * my_current_task = NULL;
volatile int my_need_sched = 0;

void my_process(void);

void __init my_start_kernel(void)
{
	int pid = 0;
	int i;
	/* Initialize process 0*/
	task[pid].pid = pid;
	task[pid].state = 0;
	task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;
	task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE];/* Modfiy?*/
	task[pid].next = &task[pid];
	/*fork more process */
	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];
		task[i].next = task[i-1].next;
		task[i-1].next = &task[i];
	}
	pid = 0;
	my_current_task = &task[pid];
	asm volatile(
		"movl %1,%%esp\n\t"
		"pushl %1\n\t"
		"pushl %0\n\t"
		"ret\n\t"
		"popl %%ebp\n\t"
		:
		: "c" (task[pid].thread.ip), "d" (task[pid].thread.sp)
		);
}
void my_process(void)
{
	int i = 0;
	while(1)
	{
		i++;
		if(i%100000000 == 0)
		{
			printk(KERN_NOTICE "this is process %d %d-\n", my_current_task->pid,i/100000000);
			if(my_need_sched == 1)
			{
				my_need_sched = -1;
				my_schedule();
			}
			printk(KERN_NOTICE "this is process %d +\n", my_current_task->pid);
		}
	}
}

在初始化中首先是对PCB内容的初始化,pid和state比较容易理解。之前一直不理解入口entry,以及esp的初始问题。在经过上一周对堆栈的理解以及本周所学的内核调度,我总结如下,任务(PCB的每一项)和函数不一定一一对应,很有可能是几个PCB执行一个函数(如本次实验),但是反复的调用的不是同一个函数,有点像同一个电影但是被多个放映机放映,每次只让一台放映机放映,这样一来他们之间的放映进度都是独立的。这里为了验证我的猜想我打印了i(局部变量)的值(因为变得太快了我除了1千万正好是进入里层if的次数)。在明白栈里存的是什么之后其实ebp初始值的问题也就明白了,当然是栈底了,但是根据同学在留言板上的留言指出新的栈必然会push esp 也就是上一个栈(其他进程的栈)的栈底因此初始值可以直接设为
KERNEL_STACK_SIZE
不必减一,避免浪费空间。我也很赞同~


可以看到process2算的数和peocess3是不一样的!!!

时间片调度方式是通过时钟中断进行的在myinterrupt.c中

#include "mypcb.h"

extern tPCB task[MAX_TASK_NUM];
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0;
void my_timer_handler(void)
{
	if(time_count%1000 == 0 && my_need_sched != 1)
	{
		printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
		my_need_sched =1;
	}
	time_count ++;
	return;
}

void my_schedule(void)
{
	tPCB * next;
	tPCB * prev;

	if(my_current_task == NULL
		|| my_current_task->next == NULL)
	{
		return;
	}
	printk(KERN_NOTICE ">>>my_schedule<<<\n");

	next = my_current_task->next;
	prev = my_current_task;
	if(next->state == 0)
	{
		asm volatile(
			"push %%ebp\n\t"
			"movl %%esp, %0\n\t"
			"movl %2,%%esp\n\t"
			"movl $1f,%1\n\t"
			"pushl %3\n\t"
			"ret\n\t"
			"1:\t"
			"popl %%ebp\n\t"
			: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
			: "m" (next->thread.sp),"m" (next->thread.ip)
		);
		my_current_task = next;
		printk(KERN_NOTICE ">>>switch %d to %d<<<\n" , prev->pid,next->pid);
	}else{
		next->state = 0;
		my_current_task = next;
		printk(KERN_NOTICE ">>>switch %d to %d<<<\n" , prev->pid,next->pid);
		asm volatile(
			"push %%ebp\n\t"
			"movl %%esp, %0\n\t"
			"movl %2,%%esp\n\t"
			"movl %2,%%ebp\n\t"
			"movl $1f,%1\n\t"
			"pushl %3\n\t"
			"ret\n\t"
			: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
			: "m" (next->thread.sp),"m" (next->thread.ip)
		);							
	}
	return;
}

这里的难点在于 “调度” 也就是老师说的中断上下文和进程上下文的切换。这里的切换分为两种

1、新进程执行过,对于执行过的进程,只要保存好现在的现场(ebp存入堆栈中,esp(指向刚刚存进去的ebp)放入之前的thread结构体的sp中,eip放入thread结构体的ip中),恢复刚才的现场,也就是将新进程的eip,esp,ebp放出来。esp是直接从sp中赋给esp,eip从ip放入栈再通过ret弹出来(因为eip不能被直接修改),此时esp是指向刚才的ebp的(想不明白可以看esp存到sp里时指的是谁?)。但是弹出ebp这个过程要在ret之后,因为那个eip存的时候是指的1:,新的进程自然就会在它的1:处恢复

2、新进程未执行过,因为没执行过,所以进程的栈是空栈,要给他分配一个ebp而不是弹出来一个ebp因此有了

"movl %2,%%ebp\n\t"
少了

"popl %%ebp\n\t"

总结

在这个实验中所有的进程都很“自觉”,他们会主动去想被调度(my_schedule),但是my_schedule的调用条件还有my_need_sched,相当于多了一个锁头,这把锁头的钥匙就在time的interrupt上,每隔一段时间interrupt会打开一次这个锁头让进程被调度,这就是时间调度了。

操作系统实验一 :向Linux内核增加一个系统调用 实验流程如下: 1.将内核压缩包linux-4.10.14.tar.xz(这个根据你实验年代更新内核包,个人实验指导书提供的是2.0版本的,无法通过,所以提倡用新一点的版本,比如这里的4.10.14)放入Ubuntu系统 2.把压缩包解压缩,使用命令 tar -xvf linux-4.10.14.tar.xz 3.将解压后的文件夹放到usr/src目录,这时候我们需要管理员权限,使用su... 阅读详情

相关推荐

linux编写一个简单的内核模块

编写一个简单的内核模块 (一)实验目的 Linux 操作系统的内核是单一体系结构(monolithic kernel)的,也就是说,整个内核是一个单独的非常大的程序。这样,系统的速度和性能都很好,但是可扩展性和维护性就相对比较差。为了弥补单一体系结构的这种缺陷,Linux操作系统使用了一种全新的机制-模块机制,用户可以根据需要,在不需要对内核重新编译的情况下,模块能动态地载入内核或从内核移出。 本...

qq_41032508的博客 5527

简单内核模块编写

非常简单的内核模块编写

weixin_43908963的博客 1615

超详细!思岚S2激光雷达hector_mapping建图

使用思岚S2激光雷达,在ros平台上利用hector_mapping功能包实现建图功能

m0_50965539的博客 5591

操作系统45讲笔记一:自己实现一个最简单的内核

一、背景 本系列笔记是基于彭东大神的课程操作系统45讲。 入门第一课自己手动做一个最简单的内核,就像任何编程语言一样的第一课,先写一个helloWorld程序,这次我们写一个HelloOS系统(一个最简单的操作系统,没有任何功能,只有输出一句话HelloOS)。 二、环境搭建 思考一下,当我们开启PC机时是不是有一个引导程序,所以这次的实验我们要借助GRUB引导程序(Centos7已经帮我们装好了)。我们需要准备好HelloOS需要的文件,然后配置好相关的信息,让GRUB引导我们安装程序。 我的环境配置

江天飞鸟的博客 3446

【提示学习代码】CoOp代码详读

CoOp是对CLIP的改进工作,主要是对prompt进行学习从而不用来手动设置prompt。自定义CLIP模型:上述两个模块结合而成的CLIP。文本编码器,接收文本的输入,并输出相应的编码表示。将图像和文本进行编码并计算它们之间的相似性。permute后变成了(4,2,3)Prompt学习器,用于学习生成提示。

weixin_51293984的博客 5816

分享一个N-MOS管和P-MOS管驱动应用实例

大家好,我是『芯知识学堂』的SingleYork,今天给大家分享一下MOS管驱动相关的知识。 MOS管在电路设计中是比较常见的,按照驱动方式来分的话,有两种,即:N-MOS管和P-MOS管。MOS管跟三极管的驱动方式有点类似,但又不完全相同,那么今天笔者将会给大家简单介绍一下N-MOS管和P-MOS管的工作原理,并结合自己实际的应用来给大家分享一下如何来驱动N-MOS管和P-MOS管。 首先,我们先来看一下N-MOS管和P-MOS管分别在电路中的电气符号: 咋一看上面两个电气符号,似乎非常的相似,可能让很

『芯知识学堂』SingleYork 的博客 3万+

最简单的内核模块——打印“hello world”

文章目录1、最简单的内核模块由哪些东西构造?2、c文件的编写3、Makefile文件的编写4、让.ko文件跑起来 1、最简单的内核模块由哪些东西构造? 首先,有一个c文件。 然后,一个Makefile文件,通过make命令将c文件编译成一个模块。 最后,将模块加载到你的linux系统中,可以是开发板,或者是qemu模拟器。在这里,我使用qemu模拟器。 2、c文件的编写 c文件要包含什么内容? (1)两个头文件 #include <linux/init.h>和#include <lin

weixin_38956024的博客 1453

项目经理必看:如何建立高效的相关方沟通机制?

你是否遇到过这样的场景?客户突然说“这不是我要的”,开发团队抱怨“需求总变”,高层追问“进度滞后为什么不早说”?这些问题的根源往往是——相关方沟通机制失效。本文聚焦“如何建立高效沟通机制”,覆盖从识别相关方到持续优化的全流程,适用于IT、产品、工程等各类项目。本文将按“理解问题→拆解核心概念→掌握工具方法→实战落地→未来趋势”的逻辑展开,重点讲解“相关方分析矩阵”“沟通计划表”等实用工具,并用“组织生日派对”的案例贯穿全文,让抽象概念变得可感知。相关方识别:用“权力/利益矩阵”找对人(像派对邀请关键客人)

项目管理的博客 1295

Linux内核模块编程入门( 最简单的内核模块编程)

第1章简介 什么是内核模块? 所以,你想编写一个内核模块。 你知道C,你已经编写了一些正常的程序作为进程运行,现在你想要到达实际操作的位置,一个狂野指针可以消灭你的文件系统,核心转储意味着重启。 什么是内核模块? 模块是可以根据需要加载和卸载到内核中的代码片段。 它们扩展了内核的功能,而无需重启系统。 例如,一种类型的模块是设备驱动程序,它允许内核访问连接到系统的硬件。 没有模块,我们必须构建单片内核并将新功能直接添加到内核映像中。 除了拥有更大的内核之外,这还有一个缺点,即每次我们想要新功能时都需要

weixin_45479529的博客 5205

知识图谱-KGE-语义匹配-双线性模型-2018:SimplE

【paper】 SimplE Embedding for Link Prediction in Knowledge Graphs 【简介】 本文是加拿大英属哥伦比亚大学的两位学者发表在 NIPS 2018 上的工作,文章提出了 SimplE(Simple Embedding)。这篇和前面一篇差不多,也是对 1927 年的 CP 进行改进(简化),传统CP为实体分配的两个向量是独立训练的,SimplE利用关系的逆在三元组打分函数中加上了一个对称项,使得每个实体的两个向量依赖学习,并且 SimplE 的复杂度随

u013250861的博客 958

实战:RK3568 Android14 点亮 LVDS 屏幕

4组数据 (D0~D3) + 1组时钟 (CLK)。最大支持 1280x800 @ 60Hz。8组数据 (Even03, Odd03) + 2组时钟。支持 1080P 及以上。/ {/* 背光与电源 *//** 关键配置:数据格式映射* 可选值: "jeida-18", "jeida-24", "vesa-24"* 6位屏选 xx-18, 8位屏选 xx-24* 颜色不对(偏色/底片效果)就换 VESA <-> JEIDA*/

qq_33427070的博客 1165

跟我一起写操作系统(二)——史上最简单的内核

跟我一起写操作系统(二)——史上最简单的内核   转载注明出处:http://www.cnblogs.com/lucasysfeng/p/4847662.html   上一讲地址:http://www.cnblogs.com/lucasysfeng/p/4846119.html   项目地址:https://github.com/lucasysfeng/lucasOS   

zdy0_2004的专栏 2万+

19、CATIA V5:参数化建模与零件目录创建全解析

本文详细解析了在CATIA V5中通过设计表实现参数化建模及零件目录创建的完整流程。以支撑块和钩形夹为例,介绍了参数识别、设计表创建与关联、配置管理以及零件目录的构建方法。同时,提供了实际操作建议和注意事项,帮助设计师高效创建和管理零件族,提升设计效率和质量。

raspberrypi5的博客 446

添加一个简单的系统调用——内核模块法

操作系统作业:添加一个简单的系统调用(内核模块法) 操作系统作业:添加一个简单的系统调用一、实验目的二、实验内容三、实验环境四、操作方法和步骤(1)准备工作(2)修改源程序,增加系统调用(3)编译内核(4)测试系统调用五、总结 一、实验目的 学习Linux的内核的系统调用。 理解、掌握Linux系统调用的实现框架、用户界面、参数传递、进入/返回过程。 二、实验内容 在系统调用中添加一个不用传...

分享日常学习工作中的经验 5316

单片机应用案例:ESP32 搭配 BH1750 光强传感器和 Web 服务器

在该应用案例中,我们将使用 ESP32 开发板作为主控制器,BH1750 光强传感器用于采集环境光强度数据,并通过 Web 服务器展示采集到的数据。步骤一:购买材料步骤二:连接硬件步骤三:编写代码我们将使用 Arduino IDE 编写代码,以下是主要代码:// WiFi 设置const char* ssid = “你的 WiFi 名称”;const char* password = “密码”;// Web 服务器实例// BH1750 实例// 获取当前光强度数据。

RlDart的博客 812

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

Linux内核编译很简单,6步编译一个自己的内核

考虑离开在前面文章中我们反复提到过Linux内核,知道Linux内核才是Linux操作系统最为核心的内容。内核实现了对硬件的管理,为应用软件提供了使用硬件的接口。 对于Linux开发人员而言,内核的学习和开发才是终极目标。因此我们今天先介绍一下内核的编译。 需要知道的是,Linux内核的编译和安装并非想象的那么难,那么复杂,其实只需要几条命令就可以搞定。 本文,我们将一步一步地介绍如何从源代码编译和安装一个Linux内核。需要注意的是本指导基于Ubuntu 18.04版本编译安装,其它发行版可能会有差

资料qun832218493 3万+

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

【实现一个简单的操作系统】01-在虚拟机中运行简单内核

接通电源后,内存中固化了一段代码(这段代码无需从存储器中读取)。这段代码的执行流程如下:① 设置段地址cs=0xFFFF(0x表示16进制),偏移地址ip=0x0000② 系统当前处于实模式,寻址方式为段地址左移四位加上偏移地址,此时pc=0xFFFF0,对应着 ROM BIOS 映射区(BIOS,Basic Input Output System,基本输入输出系统)③ 检查RAM,键盘,显示器,磁盘等等是否正常工作④ 将0磁0扇区(共512字节)读入0x7C00地址处(这就是引导扇区)

weixin_74319382的博客 1855

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

如何给CentOS 7 添加一个简单的系统调用

操作系统作业:添加一个简单的系统调用一、实验目的二、实验内容二、实验环境三、操作方法和步骤(1)下载软件包,并解压(2)修改源程序,增加系统调用 一、实验目的 学习Linux的内核的系统调用。 理解、掌握Linux系统调用的实现框架、用户界面、参数传递、进入/返回过程。 二、实验内容 在系统调用中添加一个不用传递参数的系统调用,实现一个简单的系统调用的添加。执行这个系统调用,在屏幕上显示输出...

分享日常学习工作中的经验 9401

Openwrt系统内核的一个缺陷

本文简要记录了笔者在使用树莓派的openwrt系统遇到的一个内核缺陷,以及一个初步但有效的解决方法。

yeholmes的专栏 1875

构造一个简单的操作系统内核,详解进程切换细节

linux kernel 进程调度

Nemo的博客 1585

实现一个最简单的内核

这篇文章带大家实现一个最简单的操作系统内核,并将其在我们的虚拟机上跑起来。

胤凯的博客 579

编写一个简单的内核模块程序Hello World!

一、Linux内核模块基本原理Linux 内核模块(LKM)是一些在启动的操作系统内核需要时可以载入内核执行的代码块,不需要时由操作系统卸载。它们扩展了操作系统内核功能却不需要重新编译内核、启动系统。如果没有内核模块,就不得不反复编译生成操作系统的内核镜像来加入新功能,当附加的功能很多时,还会使内核变得臃肿。一个Linux 内核模块主要由以下几个部分组成: (1) 模块加载函数(必须):当通过in

2万+

15_02_一个简单的操作系统内核源代码

1 重要结构体介绍(定义进程块) struct Thread { unsigned long ip; //用于存放eip unsigned long sp; //用于存放esp }; ---------------------- //定义进程管理相关的数据结构 typedef struct PCB { int pid; //进程ID volatile long state; //进程状态 char stack[KERNEL_STACK_SIZE]; //内核堆栈 struct Th

weixin_39247141的博客 1334

一个简单的linux内核驱动

一,内核结构简单概述 上层程序操作设备驱动简单概述: 在用户空间使用相关的c库,比如open函数会造成一个中断,系统会去调用sys_call函数(系统调用),然后会去调用相关的sys_open函数,在内核空间的时候会去驱动链表里面查找对应的外设驱动,我们编写完驱动程序,加载到内核,内核会去调用相关的驱动open函数 二,字符设备驱动简单概述 字符设备是3大类设备(字符设备、块设备、网络设备)中较简单的一类设备、其驱动程序中完成的主要工作是初始化、添加和删除 struct cdev 结构体,申请和释放设备号

qq_45652092的博客 570

Linux内核驱动学习(一)编写最简单Linux内核模块HelloWorld

文章目录准备工作什么是内核模块编写 hello.c模块编译相关指令测试结果模块加载模块卸载 准备工作 在进行以下操作前,首先我准备了一台电脑,并且安装了虚拟机,系统是Ubuntu16.04。如果是开发板,那么需要安装交叉编译器,但是目前我只在虚拟机的Ubuntu16.04系统里完成驱动模块的加载和卸载,实现了一个最最简单的驱动模块,并且通过这个最简单的驱动,学习最基本的概念。 什么是内核模块 模块...

一名热爱技术的工程师的博客。分享技术干货,记录美好生活。永远相信美好的事情即将发生。 8764

【Linux内核】如何编译并运行一个Linux内核

编译并运行一个Linux内核。

theaipower的博客 1万+
上一篇: 实践!C语言是怎么变成汇编的
下一篇: 跟踪分析Linux内核的启动过程(从start_kernel到init进程)
oCoreCode
博客等级 码龄14年 1粉丝 9原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值