20169212《Linux内核原理与分析》第四周作业
Linux第四周作业
- 堆栈知识
--
首先回顾了下堆栈相关的知识,堆栈机制是高级语言可以运行的一个基础,这一块需要重点掌握。函数发生调用时,如图
call指令:将eip的按顺序执行的下一条指令(因为在执行call的时候,eip保存的是call语句下一条指令的地址)的地址保存在当前栈顶,然后设置eip的值为要跳转到的函数的开始的地址
ret指令:将之前使用call指令的保存在栈里面的地址恢复到eip中去。
2.实验相关
用自己的Ubuntu来搭建实验所需要的环境。但是在使用用apt-get命令安装软件包时,总报错:E:could not get lock /var/lib/dpkg/lock -open等,于是上网搜寻了一下原因,可能是有另外一个程序正在运行,导致资源被锁不可用。而导致资源被锁的原因,可能是上次安装时没正常完成,而导致出现此状况。解决办法就是输入命令
sudo rm /var/cache/apt/archives/lock
sudo rm /var/lib/dpkg/lock
然后就可以开始安装编译qemu了(linux下一个类似于虚拟机的软件),步骤的命令如下:(因为要输入的命令挺多的,所以先安装了vmtools,这一步骤参考:Ubuntu16安装VM tools
sudo apt-get install qemu # install QEMU
sudo ln -s /usr/bin/qemu-system-i386 /usr/bin/qemu
wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.9.4.tar.xz # download Linux Kernel 3.9.4 source code
wget https://raw.github.com/mengning/mykernel/master/mykernel_for_linux3.9.4sc.patch # download mykernel_for_linux3.9.4sc.patch
xz -d linux-3.9.4.tar.xz
tar -xvf linux-3.9.4.tar
cd linux-3.9.4
patch -p1 < ../mykernel_for_linux3.9.4sc.patch
make allnoconfig
make
在最后的编译环节出错了,没有找到 compiler-gcc5.h 这样一个文件,上网查了查,是我的Ubuntu系统太新的原因。需要我们自己手动去网上下一个compiler-gcc5.h放入要编译内核模块的内核代码的include/linux下,找了很久没有找到compiler-gcc5.h这个文件,重新下载gcc5.1.0这个版本又下载不下来,所以就直接用了另外一种办法—— **将我们下载的/linux-3.9.4/include/linux/中的compiler-gcc4.h复制了一份然后改名为compiler-gcc5.h,最后编译成功。
当我们执行命令
qemu -kernel arch/x86/boot/bzImage
之后,就弹出一个新的窗口,不断的输出信息,表示启动内核了,如图
到/mykernel目录下查看mymain.c文件,如图
void __init my_start_kernel(void)
{
int i = 0;
while(1)
{
i++;
if(i%100000 == 0)
printk(KERN_NOTICE "my_start_kernel here %d \n",i);
}
}
有一个my_start_kernel()函数,故名思义,应该就是内核开始的函数。里面写了一个死循环,变量i不停的自加1,每当加到1000000的整数倍的时候就打印出当前的i的值。然后查看另一个文件myinterrupt.c,如图
定义了一个my_timer_handler()的函数
/*
* Called by timer interrupt.
*/
void my_timer_handler(void)
{
printk(KERN_NOTICE "\n>>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<\n\n");
}
只有一条打印语句, 这里有一点要注意,内核编程的打印语句是printk而不是printf。
由于CPU处理速度很快,所以屏幕不停的交替打印出输出信息,并且一闪而过。
上面这是一个极其简单的时间片,下面分析一个稍微复杂的进程切换的程序代码。一共三个文件,分别是mypcb.h,mymain.c,myinterrupt.c。在学习分析代码的时候都用自己学习到的和自己的理解对代码进行了注释,下面给出相关代码和注释。
先是mypcb.h中的代码,这个头文件的目的是定义一下进程控制块
#define MAX_TASK_NUM 4
#define KERNEL_STACK_SIZE 1024*8
/* CPU-specific state of this task */
struct Thread {
unsigned long ip;//保存eip
unsigned long sp;//保存esp
};
typedef struct PCB{
int pid; //进程的id 进程的状态
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;
void my_schedule(void);
文件的最后声明了一个schedule函数,即调度器。
第二个main.c中的代码如下
#include <linux/types.h>
#include <linux/string.h>
#include <linux/ctype.h>
#include <linux/tty.h>
#include <linux/vmalloc.h>
#include "mypcb.h"
tPCB task[MAX_TASK_NUM]; //PCB的数组task
tPCB * my_current_task = NULL; //当前task的指针
volatile int my_need_sched = 0; //是否需要调度
void my_process(void); //声明了一个my_process函数
void __init my_start_kernel(void)
{
int pid = 0;
int i;
/* Initialize process 0*/
task[pid].pid = pid;
task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */
task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;//初始化为my_process,实际上为mystartkernel
task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
task[pid].next = &task[pid];//刚一启动只有0号进程
/*fork more process */
for(i=1;i<MAX_TASK_NUM;i++)
{
memcpy(&task[i],&task[0],sizeof(tPCB)); //将0号进程状态copy过来
task[i].pid = i;
task[i].state = -1;
task[i].thread.sp = (unsigned long)&task[i].stack[KERNEL_STACK_SIZE-1]; //每个进程有自己的堆栈
task[i].next = task[i-1].next; //指向下一个进程
task[i-1].next = &task[i]; //新fork的进程加到进程列表的尾部
} //创建了MAX_TASK_NUM个进程
/* start process 0 by task[0] */
pid = 0;
my_current_task = &task[pid]; //当前的进程是0号进程
asm volatile(
"movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */ //第一号(task[pid].thread.sp)参数放到esp
"pushl %1\n\t" /* push ebp */ //当前堆栈空,esp==ebp
"pushl %0\n\t" /* push task[pid].thread.ip */ //当前ip压栈
"ret\n\t" /* pop task[pid].thread.ip to eip */ //ret后0号进程正式启动
"popl %%ebp\n\t"
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
);
//构建起来了cpu的运行环境(0号进程设定的堆栈和0号进程的入口)
};内核初始化完成,把0号进程启动起来了
void my_process(void)
{
int i = 0;
while(1)
{
i++;
if(i%10000000 == 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);
}
}
}//主动调度机制
接下来是myinterrupt.c里面的代码
#include<linux/types.h>
#include<linux/string.h>
#include<linux/ctype.h>
#include<linux/tty.h>
#include<linux/vmalloc.h>
#include "mypcb.h"
extern tPCB task[MAX_TASK_NUM];
extern tPCB * my_current_task;
extern volatile int my_need_sched; //把全局的东西extern来
volatile int time_count = 0; //时间计数
void my_timer_handler(void)
{
#if 1
if(time_count%1000 == 0 && my_need_sched != 1) //时钟中断发生1000次并且my_need_sched不为1(设置时间片的大小,时间片用完时设置一下调度标志,时间片变小调度更频繁)
{
printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
my_need_sched = 1; //当进程执行到的时候,发现为1,调度一次,执行一下my_schedule
}
time_count ++ ;
#endif
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");
/* schedule */
next = my_current_task->next; //当前进程的下一个进程赋给next
prev = my_current_task;
if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */
{
/* switch to next process */ //两个正在运行的进程之间做进程上下文切换
asm volatile(
"pushl %%ebp\n\t" /* save ebp */
"movl %%esp,%0\n\t" /* save esp */
"movl %2,%%esp\n\t" /* restore esp */
"movl $1f,%1\n\t" /* save eip */
"pushl %3\n\t"
"ret\n\t" /* restore eip */
"1:\t" /* next process start here */
"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);
/* switch to new process */
asm volatile(
"pushl %%ebp\n\t" /* save ebp */
"movl %%esp,%0\n\t" /* save esp */
"movl %2,%%esp\n\t" /* restore esp */
"movl %2,%%ebp\n\t" /* restore ebp */
"movl $1f,%1\n\t" /* save eip */
"pushl %3\n\t" //把当前进程的入口保存起来
"ret\n\t" /* restore eip */
: "=m" (prev->thread.sp),"=m" (prev->thread.ip)
: "m" (next->thread.sp),"m" (next->thread.ip)
);
}
return;
}
最后将这些代码复制到对应的文件里面去,保存(原本没有mypcb.h文件,所以要另外创建一个mypcb.h),再回到~/linux-3.9.4目录下,输入命令 make
编译一遍,再执行命令
qemu -kernel arch/x86/boot/bzImage
就可运行刚刚的代码了,结果如图所示
碰到的问题
1.__init my_start_kernel(void)这个函数是干什么的,有什么用?第一个程序,代码最精简的那个,我看mymain.c和myinterrupt.c里面也没写什么特别的代码,但是编译运行之后就能实现相互交替的打印出来。所以我在想是__init my_start_kernel(void)这个函数有什么特别的吗?上网查询资料也没有查到想要的结果,加上自己对linux内核这一块的确是很陌生。所以这个问题未解决。
20169212《Linux内核原理与分析》第四周作业的更多相关文章
- 2019-2020-1 20199303<Linux内核原理与分析>第二周作业
2019-2020-1 20199303第二周作业 1.汇编与寄存器的学习 寄存器是中央处理器内的组成部份.寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令.数据和位址.在中央处理器的控制部件中 ...
- 20169219 linux内核原理与分析第二周作业
"linux内核分析"的第一讲主要讲了计算机的体系结构,和各寄存器之间对数据的处理过程. 通用寄存器 AX:累加器 BX:基地址寄存器 CX:计数寄存器 DX:数据寄存器 BP:堆 ...
- 2019-2020-1 20199314 <Linux内核原理与分析>第二周作业
1.基础学习内容 1.1 冯诺依曼体系结构 计算机由控制器.运算器.存储器.输入设备.输出设备五部分组成. 1.1.1 冯诺依曼计算机特点 (1)采用存储程序方式,指令和数据不加区别混合存储在同一个存 ...
- 20169219linux 内核原理与分析第四周作业
系统调用 系统调用是用户空间访问内核的唯一手段:除异常和陷入外,它们是内核唯一的合法入口. 一般情况下,应用程序通过在用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程. 要访问系统调用 ...
- Linux内核原理与分析-第一周作业
本科期间,学校开设过linux相关的课程,当时的学习方式主要以课堂听授为主.虽然老师也提供了相关的学习教材跟参考材料,但是整体学下来感觉收获并不是太大,现在回想起来,主要还是由于自己课下没有及时动手实 ...
- 2019-2020-1 20199314 <Linux内核原理与分析>第一周作业
前言 本周对实验楼的Linux基础入门进行了学习,目前学习到实验九完成到挑战二. 学习和实验内容 快速学习了Linux系统的发展历程及其简介,学习了下的变量.用户权限管理.文件打包及压缩.常用命令的和 ...
- Linux内核原理与分析-第二周作业
写之前回看了一遍秒速五厘米:如果
- 2018-2019-1 20189221《Linux内核原理与分析》第一周作业
Linux内核原理与分析 - 第一周作业 实验1 Linux系统简介 Linux历史 1991 年 10 月,Linus Torvalds想在自己的电脑上运行UNIX,可是 UNIX 的商业版本非常昂 ...
- 20169212《Linux内核原理与分析》课程总结
20169212<Linux内核原理与分析>课程总结 每周作业链接汇总 第一周作业:完成linux基础入门实验,了解一些基础的命令操作. 第二周作业:学习MOOC课程--计算机是如何工作的 ...
- 2018-2019-1 20189221《Linux内核原理与分析》第四周作业
2018-2019-1 20189221<Linux内核原理与分析>第四周作业 教材学习:<庖丁解牛Linux内核分析> 第 3 章 MenuOS的构造 计算机三大法宝:存储程 ...
随机推荐
- ftp协议详解
客户端与服务器之间,需要多条连接才能完成应用的协议,属于复杂协议.如FTP,PPTP,H.323和SIP均属于复杂协议. 这里主要介绍ftp协议的工作原理.首先,ftp通信协议有两种工作模式,被动模式 ...
- Scala学习资源
Scala学习资源: Scala官方网站:http://www.scala-lang.org/ Scala github:https://github.com/scala/scala Twitter ...
- win7安装oracle11g64位提示环境变量Path长度超出
解决办法:安装包以管理员方式运行
- HQL查询——from子句
HQL查询--from子句 1.from是最基本的HQL语句,from关键字后紧跟持久化类的类名: from Person 表示从Person持久化类中选出全部的实例. 2.推荐为持久化类的每个实例起 ...
- 28-React state提升、组件组合或继承
Lifting State Up state提升 对于在React应用程序中更改的任何数据,应该有一个单一的数据源.通常,都是将state添加到需要渲染的组件.如果其他组件也需要它,您可以将其提升到最 ...
- QLPreViewController的初步实用
前一阵项目需要添加一个文档文件的查看功能,于是就各种找资料,一开始想实用webView,然而webView有的格式不支持,而且占内存太大了.找着找着就找到QLPreViewController.用了一 ...
- UVALive 3971 组装电脑
https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_probl ...
- 微信touchmove不生效
最近在写一个微信里面滑动切换图片的功能,发现在chrome下都正常显示,可是在微信和qq浏览器里面就是不行. 经过一番排查,发现了问题: touchmove只触发了一次. 解决方案: 在touchst ...
- 使用PHP的正则抓取页面中的网址
最近有一个任务,从页面中抓取页面中所有的链接,当然使用PHP正则表达式是最方便的办法.要写出正则表达式,就要先总结出模式,那么页面中的链接会有几种形式呢? 链接也就是超级链接,是从一个元素(文字. ...
- Solr内置的字段类型
字段类型在org.apache.solr.schema包下 Class 描述 BCDIntField 二进制整形字段 BCDLongField 二进制长整形字段 BCDStrField 二进制字符型字 ...