Linux第四周作业

  1. 堆栈知识

    --

首先回顾了下堆栈相关的知识,堆栈机制是高级语言可以运行的一个基础,这一块需要重点掌握。函数发生调用时,如图

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内核原理与分析》第四周作业的更多相关文章

  1. 2019-2020-1 20199303<Linux内核原理与分析>第二周作业

    2019-2020-1 20199303第二周作业 1.汇编与寄存器的学习 寄存器是中央处理器内的组成部份.寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令.数据和位址.在中央处理器的控制部件中 ...

  2. 20169219 linux内核原理与分析第二周作业

    "linux内核分析"的第一讲主要讲了计算机的体系结构,和各寄存器之间对数据的处理过程. 通用寄存器 AX:累加器 BX:基地址寄存器 CX:计数寄存器 DX:数据寄存器 BP:堆 ...

  3. 2019-2020-1 20199314 <Linux内核原理与分析>第二周作业

    1.基础学习内容 1.1 冯诺依曼体系结构 计算机由控制器.运算器.存储器.输入设备.输出设备五部分组成. 1.1.1 冯诺依曼计算机特点 (1)采用存储程序方式,指令和数据不加区别混合存储在同一个存 ...

  4. 20169219linux 内核原理与分析第四周作业

    系统调用 系统调用是用户空间访问内核的唯一手段:除异常和陷入外,它们是内核唯一的合法入口. 一般情况下,应用程序通过在用户空间实现的应用编程接口(API)而不是直接通过系统调用来编程. 要访问系统调用 ...

  5. Linux内核原理与分析-第一周作业

    本科期间,学校开设过linux相关的课程,当时的学习方式主要以课堂听授为主.虽然老师也提供了相关的学习教材跟参考材料,但是整体学下来感觉收获并不是太大,现在回想起来,主要还是由于自己课下没有及时动手实 ...

  6. 2019-2020-1 20199314 <Linux内核原理与分析>第一周作业

    前言 本周对实验楼的Linux基础入门进行了学习,目前学习到实验九完成到挑战二. 学习和实验内容 快速学习了Linux系统的发展历程及其简介,学习了下的变量.用户权限管理.文件打包及压缩.常用命令的和 ...

  7. Linux内核原理与分析-第二周作业

    写之前回看了一遍秒速五厘米:如果

  8. 2018-2019-1 20189221《Linux内核原理与分析》第一周作业

    Linux内核原理与分析 - 第一周作业 实验1 Linux系统简介 Linux历史 1991 年 10 月,Linus Torvalds想在自己的电脑上运行UNIX,可是 UNIX 的商业版本非常昂 ...

  9. 20169212《Linux内核原理与分析》课程总结

    20169212<Linux内核原理与分析>课程总结 每周作业链接汇总 第一周作业:完成linux基础入门实验,了解一些基础的命令操作. 第二周作业:学习MOOC课程--计算机是如何工作的 ...

  10. 2018-2019-1 20189221《Linux内核原理与分析》第四周作业

    2018-2019-1 20189221<Linux内核原理与分析>第四周作业 教材学习:<庖丁解牛Linux内核分析> 第 3 章 MenuOS的构造 计算机三大法宝:存储程 ...

随机推荐

  1. HTML如何让文本两端对齐

    <p style="text-align:justify; text-justify:inter-ideograph;>日本驻华大使丹羽宇一郎:日中关系比夫妻还紧密日本驻华大使丹 ...

  2. maven国内镜像(maven下载慢的解决方法)

    Maven是当前流行的项目管理工具,但官方的库在国外经常连不上,连上也下载速度很慢.国内oschina的maven服务器很早之前就关了.今天发现阿里云的一个中央仓库,亲测可用. <mirror& ...

  3. 通过GCC编译器编译c语言

    GCC编译C源代码的四个步骤 GCC编译C源代码有四个步骤:预处理---->编译---->汇编---->链接. 可以利用GCC的参数来控制执行的过程,这样就可以更深入的了解编译C程序 ...

  4. Web App适配不同屏幕的几点建议

    安卓设备在屏幕尺寸和像素密度上差别很大,因此在使用WebView加载网页时就需要考虑到这种差别,对我们的网页做出精心的设计以在不同的屏幕上都能得到合适的展现.通常情况下,我们需要考虑到两个因素:1.视 ...

  5. laravel框架总结(十一) -- 集合

      创建集合: collect 辅助函数会利用传入的数组生成一个新的 Illuminate\Support\Collection 实例. $collection = collect([1, 2, 3] ...

  6. Android Fragment 真正的完全解析(下)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/37992017 上篇博客中已经介绍了Fragment产生原因,以及一些基本的用法和 ...

  7. JavaScript parseInt() 函数

    定义和用法 parseInt() 函数可解析一个字符串,并返回一个整数. 语法 parseInt(string, radix) 参数 描述 string 必需.要被解析的字符串. radix 可选.表 ...

  8. sizeof、strlen、字符串、数组,整到一块,你还清楚吗?

    写在前面 sizeof.strlen.字符串.数组,提到这些概念,相信学过C语言的人都能耳熟能详,也能谈得头头是道,但是,在实际运用中,当这些内容交织在一起时,大家却不一定能搞地清清楚楚,本文的目的正 ...

  9. json接口

    http://api.rottentomatoes.com/api/public/v1.0/lists/movies/in_theaters.json?apikey=7waqfqbprs7pajbz2 ...

  10. 3.UIViewController详解

     一. UIViewController,视图控制器,它是UIKit中非常重要的组成部分.它由控制器+View两部分组成. 控制器功能: ->实现代码逻辑,决定它自带的View的界面显示. -& ...