• 20169215《Linux内核原理与分析》第六周作业


    中断和下半部

    中断机制就是在硬件需要的时候向内核发出信号。中断本质上是一种由硬件向处理器发出的特殊的电信号,不考虑与处理器的时钟同步。不同的设备对应不同的中断,所以每个中断都通过一个唯一的数字标,称为中断请求(IRQ)线。
    异常称为同步中断,是由处理器本身产生的中断;异步中断由其他硬件产生的。

    中断处理程序

    内核执行的用来响应中断的函数叫中断处理程序或者中断服务例程,是设备驱动程序的一部分。每个中断都有相应的中断处理程序,它要负责通知硬件设备中断已被接收。
    驱动程序通过request_irq()注册中断处理程序并激活给定的中断线:

    int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
    

    irq表示要分配的中断号;handler指针指向处理该中断的中断处理程序;flags有几个重要的标志,IRQF_DISABLED表示处理该中断处理程序本身旗舰,禁止所有其他中断,IRQF_SAMPLE_RANDOM表示该设备中断对内核熵池有贡献,IRQF_TIMER是为系统定时器的中断处理准备,IRQF_SHARED表明可在多个中断处理程序之间共享中断线;name是与终端相关的设备的ASCⅡ文本表示;dev用于共享中断线,提供唯一标志信息以便从共享中断线的多个中断处理程序中删除指定那个。若函数返回0则表示成功,返回-EBUSY表示中断线已经在使用。
    卸载驱动程序要注销相应的中断处理程序,并释放中断线,要调用函数:

    void free_irq(unsigned int irq, void *dev)  
    

    对于共享的中断处理程序:

    • request_irq()的参数flags必须设置IRQF_SHARED标志;
    • 对每个注册的中断处理程序,dev参数必须唯一;
    • 中断处理程序必须能区分它的设备是否真的产生了中断。
      内核收到中断后,会依次调用该中断线上注册的每一个处理程序,因此处理程序必须知道它是否应该为这个中断负责,与它相关的设备没有产生中断,那处理程序就必须立刻退出。

    中断控制

    中断控制的原因归根结底是需要提供同步。通过禁止中断确保某个中断处理程序不会抢占当前代码或内核。锁提供保护机制,防止来自其他处理器的并发访问;禁止中断提供保护机制,防止来自其他处理程序的并发访问。

    下半部和退后执行的工作

    鉴于中断处理程序运行速度和完成工作量之间的矛盾,把中断处理分为两部分:上半部和下半部。
    中断处理程序是上半部,接收到中断就立马执行,只做严格的限时工作;能被允许稍后完成的工作会推迟到下半部,在合适的时机,下半部会被开中断执行。
    下半部的任务是执行与中断处理密切相关但中断处理本身不执行的工作。一般情况,需要放在上半部的工作:

    • 对时间非常敏感;
    • 和硬件相关;
    • 要保证不被其他中断打断。

    下半部实现机制有BH、任务队列、软终端、tasklet和工作队列。其中BH和任务队列已经去除。软中断在编译期间就进行静态注册,tasklet可以通过代码进行动态注册。
    软中断由softirq_action结构表示,定义在<linux/interrupt.h>中。最多可能有32个软中断。中断处理程序返回前会标记软中断,注册的软中断必须在被标记后才会执行,称作触发软中断。而软中断都要在do_softirq()中执行。
    tasklet是利用软中断实现的一种下半部机制,由tasklet_struct结构表示:

    struct tasklet_struct{
        struct tasklet_struct *next;//链表中下一个tasklet
        unsigned long state;        //tasklet的状态
        atomic_t count;             //引用计数器
        void (*func)(unsigned long);//tasklet处理函数
        unsigned long data;         //给tasklet处理函数的参数
    }  
    

    工作队列子系统是一个用于创建内核线程的接口,通过它创建的进程负责执行有内核其他部分排到队列里的任务,这些内核线程称作工作者线程。缺省的工作者线程叫event/n,n是处理器编号,每个处理器一个线程。工作者线程用workqueue_struct结构表示:

    struct workqueue_struct{
        struct cpu_workqueue_struct cpu_wq[NR_CPUS];
        struct list_head list;
        const char *name;
        int sinqlethread;
        int freezeable;
        int rt;
    }  
    

    该结构内是一个由cpu_workqueue_struct结构组成的数组:

    struct cpu_workqueue_struct{
        spinlock_t lock;//锁保护这种结构
        struct list_head worklist;//工作列表
        wait_queue_head_t more_work;
        struct work_struct *current_struct;
        struct workqueue_struct *wq;//关联工作队列结构
        task)t *thread;//关联线程
    }
    

    所有工作者线程都是用普通的内核线程实现的,都要执行worker_thread()函数。工作用work_struct结构体表示:

    struct work_struct{
        atomic_long_t data;
        struct list_head entry;
        work_func_t func;
    }
    

    对于现在常用的三种下半部接口,软中断和tasklet都是在中断上下文中,而工作队列是进程上下文;软中断没有顺序执行保障,tasklet同类型不能同时执行,工作队列也没有顺序执行保障,和进程上下文一样被调度。

    网络云课堂学习

    1. 内核态:对应高执行级别,可以执行特权指令,访问任意物理地址;
      用户态:低级别执行状态,代码掌控范围受限,只能在对应级别允许的范围内活动。
    2. x86 CPU有四种不同执行级别0-3,Linux只用了0和3级分别表示内核态和用户态。cs寄存器最低两位表明了当前代码的特权级。在Linux中,地址空间是一个显著的标志,0xc0000000以上的空间只能在内核态下访问,0x00000000-0xbfffffff地址空间两种状态都可以。
    3. 中断处理是从用户态进入内核态的主要方式,系统调用是一种特殊的调用。中断/int指令会在堆栈上保存一些寄存器的值,如用户态栈顶地址、当时状态字、当时cs:eip的值。iret指令与中断信号/int发生时CPU做的动作相反。
    4. API和系统调用不同,API只是一个函数定义,系统调用通过软中断向内核发出一个明确请求。不是每个API都对应一个特定的系统调用。封装例程大部分返回整数,-1多数情况表示内核不能满足进程请求,errno变量包含特定的出错码。
    5. int 0x80中断向量对应system_call内核代码起点,Linux通过执行它来执行系统调用,汇编指令产生向量为128的编程异常。系统调用的三层皮:API、中断向量对应的终端服务程序、系统调用,如:xyz、system_call、sys_xyz()。系统调用号将xyz和sys_xyz关联起来,系统调用号参数通过eax寄存器传递。
    6. 系统调用也需要输入输出参数,如果参数传递超过6个,用某个寄存器存指针指向内存地址。
    7. 嵌入汇编代码
      asm(
             汇编语句模板
             输出部分
             输入部分
             破坏描述部分);

    实验楼实验

    本次实验内容是使用库函数API和C代码中嵌入汇编代码实现同一个系统调用,我选择的是20号系统调用getpid(),功能是获得当前进程的进程标识值pid。用API实现代码如下:

    在终端打印出当前进程的pid:

    同样用嵌入的汇编代码也可以达到相同的效果,寄存器前加两个“%”,第一个“%”是转义字符。数字前加%表示标识的是变量,数字的值表示的是输入/输出的变量的标号;"=m"(pid)表示将与之对应的变量(即标号为0的变量)的值写到内存中pid所在的内存地址中:

    通过eax寄存器将getgid()的系统调用号20传送给内核,并且通过eax将得到的结果返回。通过128号中断陷入内核。执行结果如下:

    问题和解决

    在编译嵌入汇编代码的程序时候老是报错,后来想到可能是之前从使用API实现系统调用的代码中拷贝的代码有错,就纯手打了一遍,就可以编译运行了。运行之后发现两次结果不一样,以为代码有问题,后来一想才知道两个程序虽然做了同一件事,但是是不同的进程,输出结果肯定不同。后来经过测试,知道同一段代码每次运行输出的pid值也都不一样,而且是逐渐增大的,后来查资料知道大多数Linux的进程标识值都是从0开始分配一直到最大值,然后再从某个值开始分配那些没有分配(该pid进程被终结,pid被释放)的。

    http://blog.csdn.net/machineunion/article/details/17434849

  • 相关阅读:
    C#中将dll汇入exe,并加壳
    很不错的在线格式转换网站
    Eclipse快捷键大全
    win7休眠的开启与关闭方法
    C#实现注册码
    Microsoft.CSharp.targets不存在解决方法
    数据库>SQL Server2005>第4季SQL从入门到提高>2SQL Server使用
    main函数名字写错,写成mian等等的错误提示
    CSS选择器
    斐波那契数的实现
  • 原文地址:https://www.cnblogs.com/308cww/p/6013644.html
Copyright © 2020-2023  润新知