一.函数调用堆栈

存储程序、函数调用堆栈(高级语言起点)和中断机制是计算机工作的三大法宝。其中函数调用堆栈是本次学习的重点。先介绍一些基本的知识点:

1.ebp 在C语言中用作记录当前函数调用的基址;

2.call 将当前cs:eip的值压入栈顶,cs:eip指向被调用函数的入口地址。

pushl    %ebp
movl %esp,%ebp
//被调用者函数体
//do sth
movl %ebp,%esp
popl %ebp
ret

call指令将eip中下一条指令的地址保存在栈顶。然后设置eip指向被调用程序代码开始处。

3.ret 从栈顶弹出原来保存在栈中的cs:eip的值并放进cs:eip中。

4.我们可以把函数调用堆栈分成以下三个部分:

call某个函数之前:

先把cs:eip中下一条指令压栈,目的是为了等函数调用完再返回到这个地址,保证程序的正确执行;然后cs:eip指向函数的入口地址。

进入该函数后:

//创建该函数的堆栈
pushl %ebp
movl %esp,%ebp

退出该函数调用后:

//消除该函数之前的栈空间
movl %ebp,%esp
popl %ebp

栈空间恢复如初(这里的初指调用该函数之前的状态)。

总结:

参数传递:用变址寻址,变量的偏移+(%ebp),然后将其压栈。

局部变量:系统会在该函数的堆栈空间预留一部分空间。

声明一个变量:用变址寻址,例 movl $0x61,0xffffffff3(%ebp)将0x61放进该变量地址。

内核代码分析

mypcb.h

#define MAX_TASK_NUM 10
#define KERNEL_STACK_SIZE 1024*8
#define PRIORITY_MAX 30 struct Thread {
unsigned long ip;
unsigned long sp; struct task_struct *task;
struct exec_domain *exec_domain;
unsigned long flags;
unsigned long status;
mm_segment_t add_limit;
unsigned long previous_esp;
}; typedef struct PCB{
int pid; // pcb id
volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */
char stack[KERNEL_STACK_SIZE];
struct Thread thread;
unsigned long task_entry;
struct PCB *next;
unsigned long priority; }tPCB;
void my_schedule(void);

分析:

在mypcb.h中定义了两个结构体:线程thread和PCB(进程控制盒)。thread主要的属性有:cpu执行指令的地址、线程栈顶地址,另外我补充了课本上的一些属性。

PCB结构体的属性有进程id、进程状态、进程申请栈的大小、进程调用的线程、进程的入口地址、所指向的下一个进程、优先级等。最后声明进程调度函数。

mymain.c

tPCB task[MAX_TASK_NUM];
tPCB * my_current_task = NULL;
volatile int my_need_sched = 0; void my_process(void);
unsigned long get_rand(int ); void sand_priority(void)
{
int i;
for(i=0;i<MAX_TASK_NUM;i++)
task[i].priority=get_rand(PRIORITY_MAX);
}
void __init my_start_kernel(void)
{
int pid = 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-1];
task[pid].next = &task[pid];
for(pid=1;pid<MAX_TASK_NUM;pid++)
{
memcpy(&task[pid],&task[0],sizeof(tPCB));
task[pid].pid = pid;
task[pid].state = -1;
task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1];
task[pid].priority=get_rand(PRIORITY_MAX);
}
task[MAX_TASK_NUM-1].next=&task[0];
printk(KERN_NOTICE "\n\n\n\n\n\n system begin :>>>process 0 running!!!<<<\n\n");
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%10000000 == 0)
{ if(my_need_sched == 1)
{
my_need_sched = 0;
sand_priority();
my_schedule(); }
}
}
}
unsigned long get_rand(max)
{
unsigned long a;
unsigned long umax;
umax=(unsigned long)max;
get_random_bytes(&a, sizeof(unsigned long ));
a=(a+umax)%umax;
return a;
}

分析:

顺序分析这段程序可知:它最先定义了MAX_TASK_NUM个任务(进程)查看头文件可知MAX_TASK_NUM=10。然后开始执行my_process();我们先不看这行代码。接下来初始化进程列表并启动0号进程。在PCB进程中有一条注释/* -1 unrunnable, 0 runnable, >0 stopped */所以void __init my_start_kernel(void)函数中task[pid].state = 0;目的就是启动0号进程,当然启动一个进程光把state属性设置为0还不行,紧接着把0号进程的入口地址给了cpu,让cpu下一条指令指向0号地址,相当于给eip赋值。以此类推,将0号进程的PCB属性全部赋值。值得一提的是这里的next属性指向1号进程,这样做的目的是形成进程链表。其中调用者为被调用者的父进程。函数unsigned long get_rand(max)的目的是给定义的所有进程随机赋一个优先级。最后我们再返回来看my_process()函数,根据其定义我们可以得出这是一个死循环,用来模拟正在运行的cpu,每加一千万个数(相当于cpu时钟运转10000000次)就调用一次之前所定义的进程链表。至此,mymain.c分析完毕。

遇到的问题:

C代码中嵌入的汇编代码的目的,或者说优点是什么?它的执行顺序是符合C语言的执行顺序还是在编译预处理阶段就处理所嵌入的汇编代码。

myinterrupt.c

#include "mypcb.h"

#define CREATE_TRACE_POINTS
#include <trace/events/timer.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 1
if(time_count%2000 == 0 && my_need_sched != 1) //时钟中断2000次并且未调度任何进程
{
my_need_sched = 1; //把my_need_sched赋1,执行一次my_schedule()。
//time_count=0;
}
time_count ++ ;
#endif
return;
}

时钟控制函数主要的目的是模拟cpu运行,然后在固定时间调度一次进程调度函数。

tPCB * get_next(void)
{
int pid,i;
tPCB * point=NULL;
tPCB * hig_pri=NULL;
all_task_print();
hig_pri=my_current_task;
for(i=0;i<MAX_TASK_NUM;i++) //遍历所有进程,并把hig_pri指向优先级最高的进程。
if(task[i].priority<hig_pri->priority)
hig_pri=&task[i];
printk(" higst process is:%d priority is:%d\n",hig_pri->pid,hig_pri->priority);
return hig_pri; }

优先级调度函数的目的是遍历当前进程链表中的所有进程,把优先级最高的进程打印出来(相当于调用一次)。

void my_schedule(void)
{
tPCB * next;
tPCB * prev;
if(my_current_task == NULL //如果当前进程不存在,并且它没有子进程,则报错。
|| my_current_task->next == NULL)
{
printk(KERN_NOTICE " time out!!!,but no more than 2 task,need not schedule\n");
return;
}
/* schedule */ next = get_next(); //当前进程的子进程赋给next;
prev = my_current_task; //prev指向当前进程;
printk(KERN_NOTICE " the next task is %d priority is %u\n",next->pid,next->priority);
if(next->state == 0)/* -1 unrunnable, 0 runnable, >0 stopped */ //如果下一个进程的状态为0,即正在运行,则调用嵌入式汇编代码切换进程
{
asm volatile(
"pushl %%ebp\n\t" /* save ebp */
"movl %%esp,%0\n\t" /* save esp */ //保存的ebp和esp都是当前进程的!
"movl %2,%%esp\n\t" //把下个线程的esp赋值给当前esp寄存器
"movl $1f,%1\n\t" /* save eip ,指向标号为1:的地方,也就是下三行*/
"pushl %3\n\t"
"ret\n\t" /* restore eip */
"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 from %d process to %d process\n >>>process %d running!!!<<<\n\n",prev->pid,next->pid,next->pid); }
else //else与上面if中的内容相呼应,就是如果当前进程的下一个进程不处于运行状态应该执行的操作
{
next->state = 0; //把下一个进程的运行状态赋为0
my_current_task = next;
printk(KERN_NOTICE " switch from %d process to %d process\n >>>process %d running!!!<<<\n\n\n",prev->pid,next->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;
}

my_schedule函数是进程调度的关键函数。它通过当前进程所指向的下一个进程的不同状态分为两类:下一个进程正处在运行状态(即if中的代码),这时候会执行汇编代码,因为它最起码执行过,可能之前只是被优先级更高的进程打断,所以在栈空间中一定保存有属于它栈空间,只要把它恢复出来即可;else中是处理下一个进程未执行过的情况,这时候会新建下一个进程的栈空间,然后再调用下个进程。这是两段代码最大的区别。

总结

本周主要通过分析两个简单的类时间片轮转代码学习了有关进程管理和系统调用的内容。本科操作系统就接触过这部分内容,当时学的时候只是概念堆叠,并没有像现在一样边分析代码,边理解进程的调度,再一次学到这些内容,让我对进程调度有了新的认识,尤其是进程上下文切换时对于没有运行完的进程的压栈保存,以前就是对这块内容留有困惑,这样分析下来,豁然开朗了许多。

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

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

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

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

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

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

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

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

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

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

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

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

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

  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. 伪全栈工程师做的有点简陋的ui设计

    站酷:http://www.zcool.com.cn/work/ZMjEwMDIxMDA=.html 这个app 叫自我时间管理  是一个 工具  管理自己开会 购物 健身 记账等 的提醒与管理,还可 ...

  2. Theam,style

    Theam <!-- Base application theme. --> <!--<style name="AppTheme" parent=" ...

  3. tiny4412 解决内核编译版本号问题

    内核版本: linux-3.5开发板: tiny4412作者:彭东林邮箱:pengdonglin137@163.com 问题: 由于我使用 git 管理内核代码,导致编译完成后内核版本变成了如下形式: ...

  4. CI框架基础知识

    调用一个视图 a.调用一个视图 $this->load->view('视图文件名'); b.调用多个视图 $this->load->view('index_h'); $this ...

  5. GetModuleFileNameA()与GetCurrentDirectoryA()

    头文件: #include <windows.h> GetModuleFileNameA() char moduleFileName[MAX_PATH]; GetModuleFileNam ...

  6. 【GLSL教程】(六)逐顶点的光照 【转】

    引言 在OpenGL中有三种类型的光:方向光(directional).点光(point).聚光(spotlight).本教程将从方向光讲起,首先我们将使用GLSL来模仿OpenGL中的光. 我们将向 ...

  7. matlab修改文件名和删除某类文件

    matlab修改多级文件夹路径下的文件名: % %%%%%%%%%%%%%%批量修改文件名一级文件夹 \路径下直接为文件 % close all;clear all;clc; % path='G:\1 ...

  8. docker入门小结(一)

    入职需要学习docker,记录学习随笔.争取两天大致看完docker学习.博客也算是迁移到cnblogs. 学习的链接参考<docker从入门到实践>http://dockerpool.c ...

  9. 身份证实名认证接口调用实例(PHP)

    基于php的身份证实名认证接口调用代码实例,身份证实名认证接口申请:https://www.juhe.cn/docs/api/id/103 <!--?php // +-------------- ...

  10. 微信小程序实战 购物车功能

    代码地址如下:http://www.demodashi.com/demo/12400.html 一.准备工作 软件环境:微信开发者工具 官方下载地址:https://mp.weixin.qq.com/ ...