zoukankan      html  css  js  c++  java
  • 从一个简单的时间片轮转多道程序内核代码看操作系统如何运行

    上了网易云课堂MOOC的《Linux内核代码分析》这门课,学到了很多知识,现在做博文记录第二次试验。
    试验要求:
    完成一个简单的时间片轮转多道程序内核代码,需要仔细分析进程的启动和进程的切换机制,总结部分需要阐明自己对“操作系统是如何工作的”理解。


    本次试验使用的工具是QEMU。QEMU是一套由Fabrice Bellard所编写的以GPL许可证分发源码的模拟处理器,在GNU/Linux平台上使用广泛。QEMU除了仿真处理器外,还允许仿真所有必要的子系统,如连网硬件和视频硬件。它还允许实现高级概念上的仿真(如对称多处理系统(多达 255 个 CPU)和其他处理器架构(如 ARM 和 PowerPC)。


    首先进入实验楼,打开shell,输入命令:

    cd LinuxKernel/linux-3.9.4
    进入内核代码目录

    qemu -kernel arch/x86/boot/bzImage
    搭建目标环境

    在QEMU窗口看以看到在程序执行过程中不断有中断程序的输出:

    my_start_kernel here是程序顺序执行过程中的输出

    >>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<是中断程序的输出内容

    具体代码:

    首先是中断处理程序的代码

    /*
     *  myinterrupt.c
     *
     */
    #include <linux/kernel_stat.h>
    #include <linux/export.h>
    #include <linux/interrupt.h>
    #include <linux/percpu.h>
    #include <linux/init.h>
    #include <linux/mm.h>
    #include <linux/swap.h>
    #include <linux/pid_namespace.h>
    #include <linux/notifier.h>
    #include <linux/thread_info.h>
    #include <linux/time.h>
    #include <linux/jiffies.h>
    #include <linux/posix-timers.h>
    #include <linux/cpu.h>
    #include <linux/syscalls.h>
    #include <linux/delay.h>
    #include <linux/tick.h>
    #include <linux/kallsyms.h>
    #include <linux/irq_work.h>
    #include <linux/sched.h>
    #include <linux/sched/sysctl.h>
    #include <linux/slab.h>
    
    #include <asm/uaccess.h>
    #include <asm/unistd.h>
    #include <asm/div64.h>
    #include <asm/timex.h>
    #include <asm/io.h>
    
    #define CREATE_TRACE_POINTS
    #include <trace/events/timer.h>
    
    /*
     * Called by timer interrupt.
     */
    void my_timer_handler(void)
    {
    	printk(KERN_NOTICE "
    >>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<
    
    ");
    }
    

    接着是主函数的代码:

    /*
     *  mymain.c
     *
     */
    #include <linux/types.h>
    #include <linux/module.h>
    #include <linux/proc_fs.h>
    #include <linux/kernel.h>
    #include <linux/syscalls.h>
    #include <linux/stackprotector.h>
    #include <linux/string.h>
    #include <linux/ctype.h>
    #include <linux/delay.h>
    #include <linux/ioport.h>
    #include <linux/init.h>
    #include <linux/initrd.h>
    #include <linux/bootmem.h>
    #include <linux/acpi.h>
    #include <linux/tty.h>
    #include <linux/percpu.h>
    #include <linux/kmod.h>
    #include <linux/vmalloc.h>
    #include <linux/kernel_stat.h>
    #include <linux/start_kernel.h>
    #include <linux/security.h>
    #include <linux/smp.h>
    #include <linux/profile.h>
    #include <linux/rcupdate.h>
    #include <linux/moduleparam.h>
    #include <linux/kallsyms.h>
    #include <linux/writeback.h>
    #include <linux/cpu.h>
    #include <linux/cpuset.h>
    #include <linux/cgroup.h>
    #include <linux/efi.h>
    #include <linux/tick.h>
    #include <linux/interrupt.h>
    #include <linux/taskstats_kern.h>
    #include <linux/delayacct.h>
    #include <linux/unistd.h>
    #include <linux/rmap.h>
    #include <linux/mempolicy.h>
    #include <linux/key.h>
    #include <linux/buffer_head.h>
    #include <linux/page_cgroup.h>
    #include <linux/debug_locks.h>
    #include <linux/debugobjects.h>
    #include <linux/lockdep.h>
    #include <linux/kmemleak.h>
    #include <linux/pid_namespace.h>
    #include <linux/device.h>
    #include <linux/kthread.h>
    #include <linux/sched.h>
    #include <linux/signal.h>
    #include <linux/idr.h>
    #include <linux/kgdb.h>
    #include <linux/ftrace.h>
    #include <linux/async.h>
    #include <linux/kmemcheck.h>
    #include <linux/sfi.h>
    #include <linux/shmem_fs.h>
    #include <linux/slab.h>
    #include <linux/perf_event.h>
    #include <linux/file.h>
    #include <linux/ptrace.h>
    #include <linux/blkdev.h>
    #include <linux/elevator.h>
    
    #include <asm/io.h>
    #include <asm/bugs.h>
    #include <asm/setup.h>
    #include <asm/sections.h>
    #include <asm/cacheflush.h>
    
    #ifdef CONFIG_X86_LOCAL_APIC
    #include <asm/smp.h>
    #endif
    
    void __init my_start_kernel(void)
    {
        int i = 0;
        while(1)
        {
            i++;
            if(i%100000 == 0)
                printk(KERN_NOTICE "my_start_kernel here  %d 
    ",i);
                
        }
    }
    

    这两段代码的开头都是包含一些头文件,用作编译时期。

    下面整理一些常用的Linux内核头文件:

    #include <linux/kernel.h>     // printk()
    #include <linux/slab.h>      // kmalloc()
    #include <linux/fs.h>       // file_operation,inode_operations,super_operations
    #include <linux/errno.h>      // error codes */
    #include <linux/types.h>      // size_t等各种系统typedef的数据类型 */
    #include <linux/fcntl.h>      // O_ACCMODE */
    #include <linux/poll.h>      // COPY_TO_USER */

    #include <linux/init.h>      // #define module_init(x) __initcall(x);  
                      // #define module_exit(x) __exitcall(x); 

    #include <linux/miscdevice.h> // extern int misc_register();  
    // 注册miscdevice结构体成员变量设备
    // extern int misc_deregister();  
    // 注销miscdevice结构体成员变量设备 
      
    #include <linux/cdev.h>      // void cdev_init()
                      // struct cdev *cdev_alloc(void);
                      // void cdev_put(struct cdev *p);
                      // int cdev_add(struct cdev *, dev_t, unsigned);
                      // void cdev_del(struct cdev *);
                      // void cd_forget(struct inode *);

    #include <mach/hardware.h>     // 和GPIO口操作相关的函数定义的头文件对应的源文件在
    // /arch/arm/plat-s3c24xx/gpio.c
    // void s3c2410_gpio_cfgpin(pin,function);
                      // unsigned int s3c2410_gpio_getcfg(pin);
                      // void s3c2410_gpio_pullup(pin,to);
                      // int s3c2410_gpio_getpull(pin);
                      // void s3c2410_gpio_setpin(lin,to);
                      // unsigned int s3c2410_gpio_getpin(pin);
                      // unsigned int s3c2410_modify_misccr(clear,chang)
                      // int s3c2410_gpio_getirq(pin);
    // int s3c2410_gpio_irq2pin(pin);

    #include <mach/regs-gpio.h>     // 和GPIO口相关的一些宏定义
    //(在arch/arm/mach-s3c2410/mach)
    #include <linux/platform_device.h> 
    #include <linux/interrupt.h>   // typedef irqreturn_t (*irq_handler_t)
    // (int, void *);
                      // extern void free_irq(unsigned int, void *);
    // extern void disable_irq(unsigned int irq);
                      // extern void enable_irq(unsigned int irq);
    // 等和中断相关函数的声明

    #include <linux/wait.h>    // wake_up_interruptible唤醒 q 指定的注册在等待队列
    //上的进/程。该函数不能直接的立即唤醒进程,而是由调
    // 度程序转换上下文,调整为可运行状态。
    // DECLARE_WAIT_QUEUE_HEAD(button_waitq);声明队列为
    // 全局变量
    // wait_event_interruptible该函数修改task的状态
    // 为 TASK_INTERRUPTIBLE,意味着改进程将不会继续运
    // 行直到被唤醒,然后被添加到等待队列wq中

    #include <linux/poll.h>       // poll_wait
    #include <asm/uaccess.h>      // copy_to_user和copy_from_user
    #include <asm/io.h>         // __raw_readl __raw_writel
    #include <linux/clk.h>       // struct clk *clk_get
    // (struct device *dev, const char *id);
                      // unsigned long clk_get_rate(struct clk *clk);

    #include <plat/clock.h>       // struct clk;结构体的定义(在arch/arm/plat-
    // s3c/plat/include/clock.arch中,对应的源文件在
    // /arm/plat-s3c/clock.c)

    #include <linux/device.h> //包含了device、class 、device_driver等结构的定义
    #include <mach/regs-mem.h>     // 和bank相关的寄存器的宏定义
    #include <asm/div64.h>       // Linux内核中64位除法函数do_div
    #include <linux/fb.h>        // struct fb_info 结构体的定义
    #include <mach/fb.h>        // struct s3c2410fb_display
    //(在arch/arm/mach-s3c2410/include/mach/fb.h)

    #include <linux/module.h>      //MODULE_LICENSE("GPL");内核认识的特定许可有,
                         "GPL"( 适用 GNU 通用公共许可的任何版本 ),
                         "GPL v2"( 只适用 GPL 版本 2 ),
                         "GPL and additional rights",
                         "Dual BSD/GPL",
                         "Dual MPL/GPL",
                         "Proprietary".
                    除非你的模块明确标识是在内核认识的一个自由许可
    下,否则就假定它是私有的,内核在模块加载时被"弄污
    浊"了. 象我们在第 1 章"许可条款"中提到的, 内核开
    发者不会热心帮助在加载了私有模块后遇到问题的用户.
                    MODULE_AUTHOR ( 声明谁编写了模块 ),
              MODULE_DESCRIPION( 一个人可读的关于模块做什么的声明 ),
    MODULE_VERSION(一个代码修订版本号;看 <linux/module.h>
    的注释以便知道创建版本字串使用的惯例),
            MODULE_ALIAS ( 模块为人所知的另一个名子 ),
          MODULE_DEVICE_TABLE(来告知用户空间, 模块支持那些设备 )

    下面看一些文件中的函数:

    void my_timer_handler(void)
    {
    	printk(KERN_NOTICE "
    >>>>>>>>>>>>>>>>>my_timer_handler here<<<<<<<<<<<<<<<<<<
    
    ");
    }
    这一个函数是内核的中断处理函数,函数调用了printk()内核函数,内核通过 printk() 输出的信息具有日志级别,printk()的使用方法是:printk(日志级别 "消息文本");其中日志级别KERN_NOTICE代表正常但又重要的条件,用于提醒。常用于与安全相关的消息。当发生中断时,CPU转入执行此函数。

    void __init my_start_kernel(void)
    {
        int i = 0;
        while(1)
        {
            i++;
            if(i%100000 == 0)
                printk(KERN_NOTICE "my_start_kernel here  %d 
    ",i);
                
        }
    }
    这个函数是内核开始执行的主函数,while循环中变量i不断增加,每增加100000时内核输出一条消息:my_start_kernel here,代表内核正在执行中,i用于时间片轮转计数,即每100000为一个时间片。


    进程在其生命周期内,由于系统中各进程之间的相互制约关系及系统的运行环境的变化,使得进程的状态也在不断地发生变化(一个进程会经历若干种不同状态)。通常进程有以下五种状态,前三种是进程的基本状态。

    1) 运行状态:进程正在处理机上运行。在单处理机环境下,每一时刻最多只有一个进程处于运行状态。

    2) 就绪状态:进程已处于准备运行的状态,即进程获得了除处理机之外的一切所需资源,一旦得到处理机即可运行。

    3) 阻塞状态,又称等待状态:进程正在等待某一事件而暂停运行,如等待某资源为可用(不包括处理机)或等待输入/输出完成。即使处理机空闲,该进程也不能运行。

    4) 创建状态:进程正在被创建,尚未转到就绪状态。创建进程通常需要多个步骤:首先申请一个空白的PCB,并向PCB中填写一些控制和管理进程的信息;然后由系统为该进程分配运行时所必需的资源;最后把该进程转入到就绪状态。

    5) 结束状态:进程正从系统中消失,这可能是进程正常结束或其他原因中断退出运行。当进程需要结束运行时,系统首先必须置该进程为结束状态,然后再进一步处理资源释放和回收等工作。

    注意区别就绪状态和等待状态:就绪状态是指进程仅缺少处理机,只要获得处理机资源就立即执行;而等待状态是指进程需要其他资源(除了处理机)或等待某一事件。之所以把处理机和其他资源划分开,是因为在分时系统的时间片轮转机制中,每个进程分到的时间片是若干毫秒。也就是说,进程得到处理机的时间很短且非常频繁,进程在运行过程中实际上是频繁地转换到就绪状态的;而其他资源(如外设)的使用和分配或者某一事件的发生(如I/O操作的完成)对应的时间相对来说很长,进程转换到等待状态的次数也相对较少。这样来看,就绪状态和等待状态是进程生命周期中两个完全不同的状态,很显然需要加以区分。

    图2-1说明了五种进程状态的转换,而三种基本状态之间的转换如下:

                                               图2-1 五种进程状态的转换

    就绪状态 -> 运行状态:处于就绪状态的进程被调度后,获得处理机资源(分派处理机时间片),于是进程由就绪状态转换为运行状态。

    运行状态 -> 就绪状态:处于运行状态的进程在时间片用完后,不得不让出处理机,从而进程由运行状态转换为就绪状态。此外,在可剥夺的操作系统中,当有更高优先级的进程就 、 绪时,调度程度将正执行的进程转换为就绪状态,让更高优先级的进程执行。

    运行状态 -> 阻塞状态:当进程请求某一资源(如外设)的使用和分配或等待某一事件的发生(如I/O操作的完成)时,它就从运行状态转换为阻塞状态。进程以系统调用的形式请求操作系统提供服务,这是一种特殊的、由运行用户态程序调用操作系统内核过程的形式。

    阻塞状态 -> 就绪状态:当进程等待的事件到来时,如I/O操作结束或中断结束时,中断处理程序必须把相应进程的状态由阻塞状态转换为就绪状态。

    (以上内容摘自:http://c.biancheng.net/cpp/html/2589.html)

    操作系统有很多种,现在多用的操作系统是多道批处理操作系统,内存中同时存在多个作业,要使得每个作业都能分配到运行时间,操作系统多采用时间片轮转的机制实现进程的调度,即在操作系统内设置一个进程队列,每个进程分配一段时间片,操作系统控制器每次从队列头选择处于就绪态的进程运行,当时间片用完时,把该进程挂在队列尾,在从队列头选择一个程序运行,如此往复。现在计算机的一个重要的机制就是中断机制,可以说现在的计算机是建立于中断机制之上的,当程序执行过程中有中断信号传来,操作系统负责保存当前程序执行状态,将有关程序执行的数据压栈,接着转入中断处理程序执行,当中断处理完成,操作系统吧堆栈中的数据弹出,恢复程序执行现场,继续原程序的执行。

    以上是本次试验的全过程,更加深入的内容有待于继续学习。

    Allen原创作品转载请注明出处《Linux内核分析》MOOC课


  • 相关阅读:
    XMLHttpRequest简介
    BC30138: 无法在路径“C:\WINDOWS\TEMP\”中创建临时文件: 拒绝访问。
    开机explorer无法启动,无法进入桌面
    选择DataGrid中的CheckBox控件后该行背景变色
    CSS菜单制作工具
    ScriptX打印控件的使用
    JS实时预览上传图片缩略图
    readyState的五种状态详解
    xmlHttpRequest的status的值的含义
    C#调用word打印
  • 原文地址:https://www.cnblogs.com/slz-coder150315/p/4376364.html
Copyright © 2011-2022 走看看