2024-2025-1 20242817《Linux内核原理与分析》第三周作业

一、根据实验楼引导,搭建内核

# 注意路径是区分大小的
$ 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

指令分析:

  1. cd~/LinuxKernel/linux-3.9.4:切换到用户的主目录下的LinuxKernel文件夹中的linux-3.9.4目录。
  2. rm -rf mykernel:删除当前目录下的mykernel目录及其所有内容,-rf参数表示递归删除且不提示确认。
  3. patch -p1 < …/mykernel_for_linux3.9.4sc.patch:应用一个补丁文件到当前目录。-p1参数表示补丁文件相对于源文件的路径偏移量,<是将补丁文件的内容重定向到patch命令。
  4. make allnoconfig:使用make工具来配置内核,allnoconfig目标会生成一个默认的配置文件,这个配置文件会禁用所有可选的内核配置项,但保留所有核心功能。
  5. make:开始编译内核。
  6. qemu-kernel arch/x86/boot/bzImage:使用QEMU模拟器来启动编译好的内核。arch/x86/boot/bzImage是内核映像文件的路径,QEMU会使用这个映像文件来模拟启动一个x86架构的Linux系统。

使用make编译完成后的情况如下图所示。
在这里插入图片描述

使用qmeu启动镜像,运行结果如下。
在这里插入图片描述

二、编写时间片轮转多道程序

进入mykernel目录,使用vim编辑mymain.c、myinterrupt.c,并创建mypcb.h文件。
在这里插入图片描述
修改后的文件代码如下:

1、mypcb.h

#define MAX_TASK_NUM        4
#define KERNEL_STACK_SIZE   1024*8
 
/* CPU-specific state of this task */
struct Thread {
    unsigned long       ip;
    unsigned long       sp;
};
 
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;
    struct PCB *next;
}tPCB;
 
void my_schedule(void);

这段代码是操作系统中用于管理进程的核心部分,它定义了进程控制块(PCB)的结构,并包含了一些基本的进程管理功能。下面是对代码的分析:

  1. 宏定义
  • MAX_TASK_NUM:定义了系统能够支持的最大任务(进程)数量。
  • KERNEL_STACK_SIZE:定义了每个任务的内核栈大小。
  1. 线程状态结构体
  • struct Thread:定义了一个结构体,用于存储与CPU相关的任务状态。
  1. PCB结构体
  • typedef struct PCB:定义了进程控制块的结构体,并给它一个别名tPCB。
  1. 调度函数
  • void my_schedule(void):定义了一个名为my_schedule的函数,这个函数通常用于决定下一个要运行的进程,是操作系统调度算法的一部分。

2、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];
tPCB * my_current_task = NULL;
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;
    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 more process */
    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];
    }
    /* start process 0 by task[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);
        }    
    }
}

代码分析如下:

  1. 定义了一些基本的进程控制块(PCB),每个块都包含了进程的基本信息,如进程ID、状态、内核栈等。
  2. 初始化了内核,创建了几个进程,并将它们链接成一个链表。第一个进程被设置为可运行状态,其他的则设置为不可运行。
  3. 通过汇编语言切换到第一个进程的执行上下文,开始执行一个名为my_process的函数。
  4. my_process函数是一个简单的循环,它会定期打印进程信息,并在需要时调用调度函数my_schedule来切换到其他进程。

3、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;
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)
    {
        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 */
    {
        /* 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;
}

代码分析如下:

  1. 定时器中断处理
  • 代码中的my_timer_handler函数是定时器中断的处理程序。每当定时器触发中断时,这个函数就会被调用。
  • time_count变量用于记录中断发生的次数。每当time_count达到1000的倍数时,my_timer_handler函数会通过printk函数打印一条消息,并设置my_need_sched变量为1,这表示系统需要进行进程调度。
  1. 进程调度准备
  • my_schedule函数是进程调度的核心,它负责选择下一个要执行的进程。
  • 如果当前没有正在运行的进程(my_current_task为NULL)或者没有下一个进程可调度(my_current_task->next为NULL),my_schedule函数将直接返回,不进行调度。
  1. 上下文切换
  • 当确定需要调度时,my_schedule函数会保存当前进程的状态(包括栈指针和指令指针),并加载下一个进程的状态。
  • 这个过程通过内联汇编实现,它直接操作CPU寄存器来完成上下文切换。这相当于告诉CPU停止执行当前进程的代码,转而执行下一个进程的代码。
  1. 更新进程状态和打印日志
  • 一旦完成上下文切换,my_schedule函数会更新my_current_task指针,指向新的当前进程。
  • 同时,它会打印一条消息,显示进程切换的信息,包括从哪个进程切换到了哪个进程。
  • 如果下一个进程的状态不是可运行(状态不是0),则将其状态设置为可运行,并执行相同的上下文切换操作。

返回上级目录,重新进行编译
在这里插入图片描述
编译成功
在这里插入图片描述

三、实验结果

使用qemu -kernel arch/x86/boot/bzImage运行
在这里插入图片描述
执行过程从process1到process2再到process3,最后到precess0;循环往复。

四、实验总结

操作系统(OS)是计算机系统中负责管理硬件资源和提供用户程序运行环境的软件。它的核心功能之一是进程管理,包括进程的创建、调度、同步和通信。下面从进程的角度对操作系统工作原理的描述:

  1. 进程创建:
    操作系统在启动时会创建初始进程,这个初始进程通常负责加载其他程序和创建更多的进程。在mymain.c文件中,展示了如何初始化0号进程,并通过复制0号进程的进程控制块(PCB)来创建新的进程。这些进程被组织成链表,以便操作系统进行管理和调度。

  2. 进程控制块(PCB):
    PCB是操作系统用于跟踪和管理进程状态的数据结构。它包含了进程ID、状态、内核栈、CPU寄存器状态(如程序计数器和栈指针)等信息。操作系统通过PCB来监控进程的生命周期,包括进程的创建、运行、等待和终止状态。

  3. 进程调度:
    操作系统需要在多个进程之间进行切换,以确保每个进程都能获得CPU时间。在myinterrupt.c文件中,展示了一个简单的时钟中断处理程序,它定期触发,以检查是否需要进行进程调度。如果需要,调度函数my_schedule会保存当前进程的状态,并选择下一个进程来执行。这个过程确保了进程能够公平地共享CPU资源。

  4. 进程同步:
    操作系统提供了多种机制来同步进程的执行,以避免竞态条件和数据不一致。这些机制包括互斥锁、信号量、条件变量等。通过这些同步机制,操作系统确保了进程在访问共享资源时的安全性。

  5. 进程通信:
    操作系统还提供了进程间通信(IPC)机制,允许进程之间交换信息。这些机制包括管道、消息队列、共享内存和套接字等。通过IPC,进程可以协调它们的活动,共享数据,并协同工作。

  6. 中断处理:
    操作系统通过中断机制来响应硬件事件,如用户输入、设备完成操作等。中断处理程序运行在当前进程的上下文中,处理完中断后,操作系统可以选择是否进行进程切换。

总结来说,操作系统通过管理进程的生命周期、调度进程的执行、同步进程的访问共享资源、提供进程间通信机制以及处理中断,来实现其核心功能。这些机制确保了计算机系统能够高效地运行多个进程,并在硬件资源之间进行合理分配。通过这些内核文件,我们可以看到操作系统是如何在底层实现这些功能的,从而为应用程序提供稳定和高效的运行环境。

“20242817李臻 原创作品转载请注明出处 《Linux内核分析》《Linux内核分析》MOOC课程Linux内核分析MOOC课程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值