Linux第二周学习总结——操作系统是如何工作的

LINUX内核分析第一周学习总结——操作系统是如何工作的

黄韧(原创作品转载请注明出处)

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

【知识点总结】

(一)计算机是如何工作的?(总结)——三个法宝

  • 存储程序计算机工作模型,计算机系统最最基础性的逻辑结构;
  • 函数调用堆栈,高级语言得以运行的基础,只有机器语言和汇编语言的时候堆栈机制对于计算机来说并不那么重要,但有了高级语言及函数,堆栈成为了计算机的基础功能;函数参数传递机制和局部变量存储
  • 中断,多道程序操作系统的基点,没有中断机制程序只能从头一直运行结束才有可能开始运行其他程序。

(二)函数调用堆栈

堆栈

1.堆栈是C语言程序运行时必须的一个记录调用路径和参数的空间。

2.堆栈存在的目的:函数调用框架;传递参数;保存返回地址;提供局部变量空间;等等。

3.C语言编译器对堆栈的使用有一套的规则。

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

堆栈寄存器和堆栈操作

1.堆栈相关的寄存器:esp,堆栈指针(stack pointer):ebp,基址指针(base pointer)

2.堆栈操作:push 栈顶地址减少4个字节(32位) pop 栈顶地址增加4个字节

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

其他关键寄存器

cs : eip:总是指向下一条的指令地址

• 顺序执行:总是指向地址连续的下一条指令

• 跳转/分支:执行这样的指令的时候,cs : eip的值会 根据程序需要被修改

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

• ret:从栈顶弹出原来保存在这里的cs : eip的值,放 入cs : eip中

• 发生中断时

(三)借助Linux内核部分源代码模拟存储程序计算机工作模型及时钟中断

当一个中断信号发生时,CPU把当前的eip,esp,ebp压到内核堆栈中去,并把eip指向中断处理程序的入口。

(四)在mykernel基础上构造一个简单的操作系统内核

【实验指导】

mykernel实验指导

虚拟机打开shell

  1. cd LinuxKernel/linux-3.9.4
  2. qemu -kernel arch/x86/boot/bzImage

然后cd mykernel ,可以看到qemu窗口输出的内容的代码mymain.c和myinterrupt.c

mymain.c

myinterrupt.c

完成一个简单的时间片轮转多道程序内核代码

mypcb.h

/*
 *  linux/mykernel/mypcb.h
 *
 *  Kernel internal PCB types
 *
 *  Copyright (C) 2013  Mengning
 *
 */

#define MAX_TASK_NUM        4
#define KERNEL_STACK_SIZE   1024*8  //定义进程控制块,实际内存叫TASK_STRUCT

/* CPU-specific state of this task */
struct Thread {  //用来存储eip,esp
    unsigned long        ip;  //保存eip
    unsigned long        sp;  //保存esp
};

typedef struct PCB{
    int pid;  //进程状态
    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;  //定义程序入口,一般是main函数,这里指定了
    struct PCB *next;  //进程用链表链起来
}tPCB;

void my_schedule(void);  //调度器

mymain.c

/*
 *  linux/mykernel/mymain.c
 *
 *  Kernel internal my_start_kernel
 *
 *  Copyright (C) 2013  Mengning
 *
 */
#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];  //声明一个TASK数组
tPCB * my_current_task = NULL;  //声明当前TASK的指针
volatile int my_need_sched = 0;  //是否需要调度的标志

void my_process(void);

void __init my_start_kernel(void)
{
    int pid = 0;
    int i;
    /* Initialize process 0*/
    task[pid].pid = pid;  //初始化当前的0号进程
    task[pid].state = 0;/* -1 unrunnable, 0 runnable, >0 stopped */  //状态:正在运行
    task[pid].task_entry = task[pid].thread.ip = (unsigned long)my_process;  //入口实际上是my_process
    task[pid].thread.sp = (unsigned long)&task[pid].stack[KERNEL_STACK_SIZE-1]; //栈顶为stack
    task[pid].next = &task[pid];  //指向自己本身
    /*fork more process */  //初始化更多的进程
    for(i=1;i<MAX_TASK_NUM;i++)
    {
        memcpy(&task[i],&task[0],sizeof(tPCB));
        task[i].pid = i;  //复制0号进程的状态
        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的进程放在进程列表的尾部
    }
    /* start process 0 by task[0] */
    pid = 0;  //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*/
    );
}   //内核初始化完成,启动了0号进程
void my_process(void)
{
    int i = 0;
    while(1)
    {
        i++;
        if(i%10000000 == 0)  //执行1000万次
        {
            printk(KERN_NOTICE "this is process %d -\n",my_current_task->pid);  //输出,主动调度
            if(my_need_sched == 1)  //执行1000万次调度1次
            {
                my_need_sched = 0;
                my_schedule();
            }
            printk(KERN_NOTICE "this is process %d +\n",my_current_task->pid);
        }
    }
}

每循环十万次打印一个

嵌入式汇编

myinterrupt.c

/*
 *  linux/mykernel/myinterrupt.c
 *
 *  Kernel internal my_timer_handler
 *
 *  Copyright (C) 2013  Mengning
 *
 */
#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;  //时间计数

/*
 * Called by timer interrupt.
 * it runs in the name of current running process,
 * so it use kernel stack of current running process
 */
void my_timer_handler(void)
{
#if 1
    if(time_count%1000 == 0 && my_need_sched != 1) //时间中断一千次,并且my_need_sched不等于1时,把my_need_sched赋为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
    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 */  //保存当前进程的ebp
            "movl %%esp,%0\n\t"     /* save esp */  //当前进程的esp赋到0,即thresd.sp
            "movl %2,%%esp\n\t"     /* restore  esp */  //把下一个进程的sp放入esp中
            "movl $1f,%1\n\t"       /* save eip */    //保存eip
            "pushl %3\n\t"   //把下一个进程的eip push到栈里
            "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 */  //保存当前ebp
            "movl %%esp,%0\n\t"     /* save esp */   //保存当前esp
            "movl %2,%%esp\n\t"     /* restore  esp */  //重新记录要跳转进程的esp,%2为next->thread.sp
            "movl %2,%%ebp\n\t"     /* restore  ebp */  //重新记录要跳转进程的ebp,%2为next->thread.sp
            "movl $1f,%1\n\t"       /* save eip */    //保存当前eip,%1为next->thread.ip,%1f指标号:1的代码在内存中存储的地址
            "pushl %3\n\t"
            "ret\n\t"                 /* restore  eip */  //重新记录要跳转进程的eip,%3为next->thread.ip
            : "=m" (prev->thread.sp),"=m" (prev->thread.ip)
            : "m" (next->thread.sp),"m" (next->thread.ip)
        );
    }
    return;
}

总结:

实验中,在上述函数的作用下,成功地实现了时间片轮转的中断处理内核的功能。通过分析源码,我们了解到时间片轮转算法的具体方法,即每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换。调度程序所要做的就是维护一张就绪进程列表,当进程用完它的时间片后,它被移到队列的末尾。这是一种最古老,最简单,最公平且使用最广的算法。

进程是动态执行的实体,内核是进程的管理者。进程不但包括程序的指令和数据,而且包括程序计数器和CPU的所有寄存器以及存储临时数据的进程堆栈。所以,正在执行的进程包括处理器当前的一切活动。进程既可以在用户态下运行,也能在内核下运行,只是内核提供了一些用户态没有的核心服务,因此进程在访问这些服务时会产生中断,必须进行用户态与内核态的切换。

时间: 2024-10-22 17:16:40

Linux第二周学习总结——操作系统是如何工作的的相关文章

Linux第二周学习笔记(8)

2.14 文件或目录权限chmod (1). 权限表示 [[email protected] ~]# ls -l 总用量 12 -rw-------. 1 root root 1418 1月  22 08:19 anaconda-ks.cfg -rw-r--r--  1 root root 4358 1月  29 23:24 anaconda-ks.cfg.1 文件的权限是和所有者.所属组有关系的,一个文件总共有三个权限位总共有9位分成三段,加上第一位表示设备类型总共十位,一下进行解析: -rw

Linux第二周学习笔记(11)

2.17 隐藏权限lsattr_chattr chattr命令:是设置吟唱隐藏权限的命令,更改Linux文件系统上的文件属性. 参数说明: A:表示文件或目录的atime将不可修改 s:会将数据同步写入磁盘中 a:只能追加不能删除,非root用户不能设定该属性 c:自动解压该文件,读取时会自动解压 i:文件不能删除.从命名.设定链接.写入以及新增数据 lsattr(list attribute)命令:用于读取文件或者目录的特殊权限 a:列出所有文件(包含隐藏文件) R:连同子目录一起列出 d:只

Linux第二周学习笔记(12)

2.18 特殊权限set_uid set_uid:这个权限是针对二进制可执行文件,使文件在执行阶段具有文件所有者的的权限. -------------------------------------------------------------------------------------------- 例如passwd命令: [[email protected] ~]# which passwd /usr/bin/passwd [[email protected] ~]# ls -l /u

Linux第二周学习笔记(7)

2.13 文档查看cat_more_less_head_tail (1). cat命令 cat命令:用于查看一个文件的内容并将其显示在屏幕上 cat-A命令:显示所有的内容,包括特殊字符 cat-n命令:显示行号 -------------------------------------------------------------------------------------------- cat命令: [[email protected] tmp]# cat /etc/passwd ro

Linux第二周学习笔记(9)

2.15 更改所有者和所属组chown chown(change owner)命令:更改所有者,也可更改所属组 chown -R命令: chown命令只是对文件或者目录生效的仅仅只是指定的这个目录本身的所属用户及所属组,如果要把目录下面的子目录还有子文件全部一次性批量更改所属人或者所属用户组,就要使用-R参数 -------------------------------------------------------------------------------- chown命令更改文件的所

Linux第二周学习笔记(10)

2.16 umask Umask命令:用于改变文件和目录的默认权限,格式:umask ***(***代表3个数字).查看umask的值,只要在命令行输入umask即可. 默认情况下,文件的权限值是:-rw-r--r--(644),目录的权限值是:drwxr-xr-x(755).着两个权限值是有什么来定的呢?这个就涉及到了一个慨念叫umask,我们通过这个值就可以确定文件的权限值是什么,也可以确定目录的权限值是什么. ----------------------------------------

Linux第二周学习笔记(13)

2.19 特殊权限set_gid 特殊权限set_gid:这个权限可以作用在二进制可执行文件上,还可以作用在目录上.特殊权限set_gid权限位是作用在组权限位上,目录被设置set_gid权限后,任何用户在此目录下创建的文件都具有和该目录所属的组相同的组 ----------------------------------------------------------------------------------------------- [[email protected] ~]# chm

20155336 2016-2017-2《JAVA程序设计》第二周学习总结

20155336 2016-2017-2 <JAVA 程序设计>第二周学习总结 教材学习内容 1: GIT版本检测 2: JAVA中基本类型 整数 字节 浮点数 字符 布尔(▲) 通过API可以得知各个类型可存储的数值范围 public class Range {public static void main(String[] args){ //byte.short.int.long的范围 System.out.printf("%d~%d%n", Byte.MIN_VALU

20145311 《信息安全系统设计基础》第二周学习总结

20145311 <信息安全系统设计基础>第二周学习总结 教材学习内容总结 重新学习了一下上周的一部分命令:grep main wyx.c(grep的全文检索功能)ls > ls.txt :ls内容输出到文本find pathname -mtime -n/+nfind -size -n/+n (find的功能还是比较强大) 简单地学习了一下vim编辑器,跟着vimtutor简单地学了一些,在linux bash中使用vim能够极大地提高效率, vim的用法比较多,只学习了其中简单的一部分