“Linux内核分析”实验二报告

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

一、第二周学习内容总结

1、计算机工作“三大法宝”

首先,计算机工作原理最重要的三个内容就是:存储程序计算机工作模型、中断机制和函数调用堆栈。

存储程序计算机工作模型是计算机系统最最基础性的逻辑结构;

中断机制是多道程序操作系统的基点,没有中断机制程序只能从头一直运行结束才有可能开始运行其他程序;

函数调用堆栈是高级语言得以运行的基础,有了高级语言及函数后,堆栈成为了计算机的基础功能,函数参数传递机制和局部变量存储。

2、详细介绍函数调用堆栈

首先,堆栈是C语言程序运行时必须的一个记录调用路径和参数的空间,即CPU内已经集成好了很多功能。

堆栈含以下元素:

函数调用框架、传递参数、保存返回地址、提供局部变量空间、C语言编译器对堆栈的使用有一套的规则。

了解堆栈存在的目的和编译器对堆栈使用的规则是理解操作系统一些关键性代码的基础。

其次,堆栈相关的寄存器常用的共有四种,esp、ebp、push、pop。详细解释如下:

esp,堆栈指针,指向栈顶;

ebp,基址指针,指向栈底,在C语言中用作记录当前函数调用基址;

push,栈顶地址减少4个字节,从低地址向高地址;

pop,栈顶地址增加4个字节,从高地址向低地址;

最后,课程介绍了函数参数的传递与框架——

(1)建立框架,即call指令

     相当于

      push %ebp

      movl %esp,%ebp

     cs:eip原来的值指向call下一条指令,该值被保存到栈顶cs:eip的值指向function的入口地址

(2)执行函数主体代码

(3)拆除框架

     相当于

     movl %ebp,%esp

     pop %ebp

     ret

函数的返回值通过eax寄存器传递

3、参数传递

这里以老师在课堂上举出的例子:

#include <stdio.h>

void p1(char c)

{

printf("%c",c);

}

int p2(int x,int y)

 {

return x+y;

}

int main(void)

{

char c ='a';

int x,y;

x =1;

y =2;

p1(c);

z = p2(x,y);

printf("%d = %d+%d",z,x,y);

}

首先,观察P2的堆栈框架:

这里,使用变址寻址方式,将x+y的值赋给eax。

然后,我们观察main是如何传递参数给P2的:

和main中的局部变量:

可以看到,汇编代码中用变址寻址把y的值和x的值存放到堆栈中,然后进行局部变量调用。

4、C代码中嵌入汇编代码

____asm____

(

汇编语句模板:

输入部分:

输出部分:

破坏描述部分:

);

二、实验二内容

本次实验内容是在mykernel基础上构造一个简单的操作系统内核。

首先我们cd进入LinuxKernel/linux-3.9.4文件,执行qemu -kernel arch/x86/boot/bzImage,可以看到窗口弹出如下:

然后,我们查看mymain.c和myinterrrupt.c文件:

可以发现,mymain是系统中唯一的进程,函数主要部分是my_start_kernel。每循环10万次,就打印一次my_start_kernel here.

myinterrupt是时间中断处理程序,每进行一次就会发生一次时钟处理中断,每次时钟中断都调用printk并输出。

程序分析:

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;

volatile long state; /* 定义进程的状态:-1 不可运行, 0 可运行, >0 停止 */

char stack[KERNEL_STACK_SIZE];

//内核堆栈

struct Thread thread;

unsigned long task_entry; //指定进程入口

struct PCB *next;//进程链表

}tPCB;

void my_schedule(void);//调用了my_schedule,表示调度器

mymain.c

void my_timer_handler(void)

{

#if 1

if(time_count%1000 == 0 && my_need_sched != 1)//设置时间片的大小,时间片用完时设置一下调度标志。当时钟中断发生1000次,并且my_need_sched!=1时,把my_need_sched赋为1。当进程发现my_need_sched=1时,就会执行my_schedule。

{

printk(KERN_NOTICE ">>>my_timer_handler here<<< ");

my_need_sched = 1;

}

time_count ++ ;

#endif return;

}

void my_schedule(void)

{

tPCB * next; //下一个进程

tPCB * prev; //当前进程

if(my_current_task == NULL //task为空,即发生错误时返回

|| my_current_task->next == NULL)

{

return;

}

printk(KERN_NOTICE ">>>my_schedule<<< ");

/* schedule */

next = my_current_task->next; //将当前进程的下一个进程赋给next

prev = my_current_task;//当前进程为prev

if(next->state == 0)

/* -1 unrunnable, 0 runnable, >0 stopped */

{

//在两个正在执行的进程之间做上下文切换

asm volatile( "pushl %%ebp "

/* 保存当前进程的ebp */

"movl %%esp,%0 " /* 保存当前进程的esp */

"movl %2,%%esp " /* 重新记录要跳转进程的esp,将下一进程中的sp放入esp中 */

"movl $1f,%1 " /* $1f指标号1:的代码在内存中存储的地址,即保存当前的eip */

"pushl %3 " //将下一进程的eip压入栈,%3为 next->thread.ip

"ret " /* 记录要跳转进程的eip */

"1: " /* 下一个进程开始执行 */

"popl %%ebp "

: "=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<<< ",prev->pid,next->pid);

}

else /* 与上一段代码不同的是如果下一个进程为新进程时,就运用else中的这一段代码。首先将这个进程置为运行时状态,将这个进程作为当前正在执行的进程。 */

{ next->state = 0;

my_current_task = next; printk(KERN_NOTICE ">>>switch %d to %d<<< ",prev->pid,next->pid);

/* switch to new process */

asm volatile( "pushl %%ebp " /* 保存当前进程的ebp */ "movl %%esp,%0 " /* 保存当前进程的esp */ "movl %2,%%esp " /* 重新记录要跳转进程的esp */

"movl %2,%%ebp " /* 重新记录要跳转进程的ebp */ "movl $1f,%1 " /* 保存当前eip */ "pushl %3 " "ret " /* 重新记录要跳转进程的eip */

: "=m" (prev->thread.sp),"=m" (prev->thread.ip)

: "m" (next->thread.sp),"m" (next->thread.ip) ); }

return; }

 

精简内核运行结果:

 

三、总结

操作系统是管理计算机系统的全部硬件资源包括软件资源及数据资源;控制程序运行;改善人机界面;为其它应用软件提供支持等,使计算机系统所有资源最大限度地发挥作用,为用户提供方便有效的服务界面。

Linux是一个多进程的操作系统,所以,其他的进程必须等到正在运行的进程空闲CPU后才能运行。

当正在运行的进程等待其他的系统资源时,Linux内核将取得CPU的控制权,并将CPU分配给其他正在等待的进程,这就是进程切换。

内核中的调度算法决定将CPU分配给哪一个进程。

进程是动态执行的实体,内核是进程的管理者。

进程不但包括程序的指令和数据,而且包括程序计数器和CPU的所有寄存器以及存储临时数据的进程堆栈。

所以,正在执行的进程包括处理器当前的一切活动。

进程既可以在用户态下运行,也能在内核下运行,只是内核提供了一些用户态没有的核心服务,

因此进程在访问这些服务时会产生中断,必须进行用户态与内核态的切换。

 

原文地址:https://www.cnblogs.com/20135121conan/p/5246575.html