《庖丁解牛》第二章书本知识总结


  • 函数调用框架

    call指令有两个作用:

    (1) 将CS:EIP中下一条指令的地址A保存在栈顶;

    (2)设置CS:EIP指向被调用程序的第一行。

    ret指令在被调用函数完成之后,将地址A恢复到CS:EIP中。

  • 传递参数

    因为存储参数使用堆栈,根据后入先出的规则,传递参数的方法是从右到左依次压栈。

  • 计算机的3大法宝:存储程序计算机、函数调用堆栈机制、中断。

  • 内嵌汇编语法规则

asm volatile (
汇编语句:
输出部分: //需要在行首加上冒号:
输入部分: //需要在行首加上冒号:
破环描述部分

(1)volatile是告诉编译器不要优化代码

(2)汇编代码嵌入时和直接方式有些许不同,体现在%转义符号。寄存器前面会有两个%,而%加数字则表示在输出部分、输入部分、破环描述部分的编号,从零开始,最高到总个数-1。

(3)一些符号

"c"、"d"等符号分别表示寄存器ecxedx

"r"表示将输入变量放到通用寄存器,也就是eaxebxecxedxesiedi中的一个。

"="表示操作数在指令中是只写的,输出操作数。

"+"表示输入输出操作数是读写类型的。

"m"表示内存变量。

实验:mykernel时间片轮转多道程序内核


  • 进入实验楼实验,在终端中分别输入以下命令
cd LinuxKernel/linux-3.9.4
rm -rf mykernel
patch -p1 < ../mykernel_for_linux3.9.4sc.patch //打补丁
make allnoconfig
make //编译内核时间较长
qemu -kernel arch/x86/boot/bzImage

make过程如下图:



make成功后mykernel运行:

  • 在mykernel的基础上添加mypcb.h,修改mymain.cmyinterrupt.c文件,实现一个简单的操作系统内核,设置为时钟中断发生100次时,将需要调度的flagmy_need_sched设置为1。运行结果如下:

mykernel时间片轮转代码分析


实验中,基于mykernel,添加了mypcb.h,修改mymain.cmyinterrupt.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); //声明调度函数

这段代码主要定义了进程控制块PCB,包括:

pid:进程id

state:进程状态,初始化值就是-1,如果被调度运行起来,其值就会变成0 ,被终端后,其值>0

stack:本进程使用的堆栈

thread:当前正在执行的线程信息

task_entry:进程入口函数

next:指向下一个PCB,实验环境中所有的PCB是以链表的形式组织起来的。

  • mymain.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) //mykernel内核代码的入口
{
int pid = 0;
int i;
/* 初始化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;
task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
task[pid].next = &task[pid]; /*fork其他进程 */
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[i].next = task[i-1].next;
task[i-1].next = &task[i];
} /* 用task[0]开始0号进程 */
pid = 0;
my_current_task = &task[pid];
asm volatile(
"movl %1,%%esp\n\t" /* set task[pid].thread.sp to esp */
"pushl %1\n\t" /* push ebp */
"pushl %0\n\t" /* push task[pid].thread.ip */
"ret\n\t" /* pop task[pid].thread.ip to eip */
"popl %%ebp\n\t"
:
: "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) /* input c or d mean %ecx/%edx*/
);
} 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);
}
}
}

本实验环境中,mykernel是内核代码的入口,负责初始化内核的各个组成部分,在实际的Linux内核中,入口为init/main.c中的start_kernel(void)函数。

在本实验环境中,每个进程的函数都是 my_process函数,进程在运行中打印当前进程号,并通过my_need_sched变量判断是否需要调度,这是在myinterrupt.c文件中进行赋值的一个变量。

下面对0号进程的启动进行分析:

1"movl %1,%%esp\n\t" %1指第2个输入输出"d" (task[pid].thread.sp),将进程的栈顶位置存入ESP寄存器

2"pushl %1\n\t" 将task[0].thread.sp压栈,即保存当前EBP值

3"pushl %0\n\t" 将task[0].thread.ip压栈,当前进程的EIP值入栈

4"ret\n\t" 将栈顶值取出到EIP寄存器,即刚刚入栈的0号进程ip值

5 "popl %%ebp\n\t" 在执行完其他进程之后,回到0号进程,释放栈空间

6 : 没有输出

7 : "c" (task[pid].thread.ip),"d" (task[pid].thread.sp) 输入,将0号进程的ip、sp值分别存入ecx、edx寄存器中。

  • 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引用全局变量
extern tPCB * my_current_task;
extern volatile int my_need_sched;
volatile int time_count = 0; void my_timer_handler(void) //时钟中断触发本函数
{
#if 1
if(time_count%100 == 0 && my_need_sched != 1) //当时钟中断发生100次,并且my_need_sched不为1时,赋值为1
{
printk(KERN_NOTICE ">>>my_timer_handler here<<<\n");
my_need_sched = 1;
}
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;
prev = my_current_task;
if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ //下一个进程可运行,执行进程切换
{
my_current_task = next;
printk(KERN_NOTICE ">>>switch %d to %d<<<\n",prev->pid,next->pid);
/* 切换进程 */
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)
); }
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;
}

myinterrupt.c包含my_timer_handler和my_schedule两个函数。 my_timer_handler每隔100次将my_need_sched赋值为1,等待mymain.c中my_process函数的主动调度。my_schedule保存恢复进程上下文。

切换进程的分析:

01 "pushl %%ebp\n\t 保存当前ebp到堆栈中

02 "movl %%esp,%0\n\t" 保存当前ESP到当前进程sp中

03 "movl %2,%%esp\n\t" esp指向下一个进程

04 "movl $1f,%1\n\t" 将1f存储到thread.ip中,$1f是标号“1:\t”处,再次调度到该进程时就会从1:开始执行

05 "pushl %3\n\t" 将下一个进程的ip入栈

06 "ret\n\t" eip指向下一个进程的起始地址,也做了一次出栈操作

07 "1:\t" 标号1:,即next进程开始执行的位置

08 "popl %%ebp\n\t" 待下一个进程执行完后释放栈空间,恢复现场

09 : "=m" (prev->thread.sp),"=m" (prev->thread.ip) %0、%1分别对应,m代表内存变量

10 : "m" (next->thread.sp),"m" (next->thread.ip) %2、%3分别对应

遇到的问题


  1. 实验中修改代码后,make找不到文件。

    解决:修改代码是在/mykernel目录下进行修改的,make编译内核需要在LinuxKernel/linux-3.9.4目录下进行,修改完之后要cd ..
  2. 不懂myinterrupt.c文件中my_timer_handler函数在哪调用。

    解决:仔细看了书上的35页,在实验环境中已经配置好了一个时钟中断,在时钟中断发生的时候就会调用一次该函数。
  3. 不懂$1f的含义。

    解决:查阅资料得知,"movl $1f,%1\n\t"是将进程原来的ip替换为$1f,是at&t的一种语法,f代表向后跳转(b表示向前,f表示向后),1f指的就是下一条指令,即当前进程的eip。

2018-2019-1 20189215《Linux内核原理与分析》第三周作业的更多相关文章

  1. 2019-2020-1 20199329《Linux内核原理与分析》第九周作业

    <Linux内核原理与分析>第九周作业 一.本周内容概述: 阐释linux操作系统的整体构架 理解linux系统的一般执行过程和进程调度的时机 理解linux系统的中断和进程上下文切换 二 ...

  2. 2019-2020-1 20199329《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 一.上周问题总结: 未能及时整理笔记 Linux还需要多用 markdown格式不熟练 发布博客时间超过规定期限 二.本周学习内容: <庖丁解 ...

  3. 20169212《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 这一周学习了MOOCLinux内核分析的第一讲,计算机是如何工作的?由于本科对相关知识的不熟悉,所以感觉有的知识理解起来了有一定的难度,不过多查查资 ...

  4. 20169210《Linux内核原理与分析》第二周作业

    <Linux内核原理与分析>第二周作业 本周作业分为两部分:第一部分为观看学习视频并完成实验楼实验一:第二部分为看<Linux内核设计与实现>1.2.18章并安装配置内核. 第 ...

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

    2018-2019-1 20189221 <Linux内核原理与分析>第九周作业 实验八 理理解进程调度时机跟踪分析进程调度与进程切换的过程 进程调度 进度调度时机: 1.中断处理过程(包 ...

  6. 2017-2018-1 20179215《Linux内核原理与分析》第二周作业

    20179215<Linux内核原理与分析>第二周作业 这一周主要了解了计算机是如何工作的,包括现在存储程序计算机的工作模型.X86汇编指令包括几种内存地址的寻址方式和push.pop.c ...

  7. 2019-2020-1 20209313《Linux内核原理与分析》第二周作业

    2019-2020-1 20209313<Linux内核原理与分析>第二周作业 零.总结 阐明自己对"计算机是如何工作的"理解. 一.myod 步骤 复习c文件处理内容 ...

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

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

  9. 《Linux内核原理与分析》第一周作业 20189210

    实验一 Linux系统简介 这一节主要学习了Linux的历史,Linux有关的重要人物以及学习Linux的方法,Linux和Windows的区别.其中学到了LInux中的应用程序大都为开源自由的软件, ...

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

    读书报告 <庖丁解牛Linux内核分析> 第 1 章 计算工作原理 1.1 存储程序计算机工作模型 1.2 x86-32汇编基础 1.3汇编一个简单的C语言程序并分析其汇编指令执行过程 因 ...

随机推荐

  1. JS:“分享到”之类的悬浮框的运动原理(代码)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  2. nginx代理学习

    一.windows下nginx代理ftp服务器 我所在的开发环境里,nginx和ftp在同一台服务器. ftp根目录: nginx的配置: 在nginx.conf中加入: server { liste ...

  3. Zabbix漏洞汇总

    一.zabbix: zabbix是监控是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案.zabbix能监视各种网络参数,保证服务器系统的安全运营:并提供灵活的通知机制以让 ...

  4. 在ubuntu14.04上漏洞环境vulndocker的DOCKER搭建

    一.下载git包以及安装: git clone https://github.com/leveryd/vulndocker.git cd vulndocker apt-get install uwsg ...

  5. 基于TINY4412的Andorid开发-------简单的LED灯控制

    参考资料: <Andriod系统源代码情景分析> <嵌入式Linux系统开发完全手册_基于4412_上册> 作者:彭东林 邮箱:pengdonglin137@163.com 平 ...

  6. 【gulp】前端自动化工具---gulp的使用(一)------【巷子】

    什么是gulp?   基于node的自动化构建工具   扩展:开发的时候分为2个节点一个是开发阶段  另一个是部署阶段        开发阶段:源文件不会被压缩            部署阶段:所有文 ...

  7. 170721、springboot编程之注解(annotation)列表

    (1)@SpringBootApplication 申明让spring boot自动给程序进行必要的配置,这个配置等同于: @Configuration ,@EnableAutoConfigurati ...

  8. 沈阳网络赛D-Made In Heaven【k短路】【模板】

    One day in the jail, F·F invites Jolyne Kujo (JOJO in brief) to play tennis with her. However, Pucci ...

  9. python tensorflow keras

    pip install tensorflow pip install keras pip install theano http://www.open-open.com/lib/view/open14 ...

  10. redis cluster 集群畅谈(一)

    redis单机在大数据量情况的会出现瓶颈问题,通过redis 主从架构和 哨兵集群结合可以实现99.99% 高可用 .水平扩容支持更高QPS的解决方案. 在大数据量面前,主从架构结合哨兵集群的解决方案 ...