精华内容
下载资源
问答
  • 1.linux内核中断处理函数的要求 明确:CPU资源给进程,软中断,硬件中断使用; 明确:在linux系统中,硬件中断的优先级高于软中断,软中断的优先级高于进程; 明确:优先级是指某个任务获取CPU资源的能力; ...

    1.linux内核对中断处理函数的要求


    明确:CPU资源给进程,软中断,硬件中断使用;
    明确:在linux系统中,硬件中断的优先级高于软中断,软中断的优先级高于进程;
    明确:优先级是指某个任务获取CPU资源的能力;
    明确:在linux系统,硬件中断无优先级;软中断有优先级;进程有优先级;
    明确:在linux系统,任务分中断和进程

    中断上下文:中断处理的整个流程,过程;
    进程上下文:从进程的创建,调度,抢占到最后的消亡的整个过程;

    内核对于中断处理函数的要求:
    要求中断处理函数的执行速度要快!如果中断处理函数长时间的占用CPU资源,
    会导致其他任务(硬件中断,软中断,进程)无法及时获取CPU资源,影响系统的并发和响应能力;

    *********内核要求中断处理函数不能进行休眠操作!
    *********中断处理函数不隶属于任何进程,所以不会参与进程的调度!

    问:内核对于中断处理函数的要求是执行速度要快,但是在某些场合,某些设备可能无法满足内核
    的这种要求,对于这种场合和设备的操作势必会影响系统的并发的响应能力;内核如何解决这种问题呢?


    答:对于中断处理函数长时间的占有CPU资源的情况,内核建议要求将原先中断处理函数分拆两部分,
    分别叫顶半部和底半部;

    1.1.内核中断之顶半部

    一旦硬件中断触发,内核首先执行顶半部的内容,其实顶半部就是中断处理函数,此时的中断处理函数
    只做紧急的,耗时较少的内容;目的就是让任务及时释放CPU资源给其它任务使用;

    顶半部不能被中断!

    1.2.内核中断之底半部

    底半部将会做原先中断处理函数中比较耗时,不紧急的内容,此部分内容可以被中断!

    如果底半部要执行,必须在顶半部要进行登记;是登记而不是调用!
    将来CPU在适当的时候去执行底半部的内容:

    问:
    如何实现底半部呢?

    答:
    (新版本内核)
    实现机制有三种:
    tasklet
    工作队列

    软中断

    底半部机制之tasket

    明确:tasklet本质上其实就是延后执行的一种手段!
    ********特点:
    1.tasklet对应的延后处理函数就是将来就做原先中断处理函数中比较耗时,不紧急的内容;
    2.tasklet本身是基于软中断来实现的;
    3.tasklet对应的延后处理函数工作在中断上下文中,不能进行休眠操作!


    数据结构:
    struct tasklet_struct
    {
    unsigned long data;
    void (*func) (unsigned long);
    };

    作用:描述tasklet
    成员:
    data:给延后处理函数传递的参数
    func:延后处理函数,将来里面做原先中断处理函数中比较耗时,不紧急的内容,在适当
    的时候会被执行;形参data保存传递过来的参数

    利用tasklet机制后实现的步骤
    1.定义初始化名称为name的tasklet对象

    DECLARE_TASKLET(name,func,data);
    name:tasklet对象名称
    func:tasklet延后处理函数
    data:给延后处理函数传递的参数,如果不传递给0

    例如:
    DECLARE_TASKLET(btn_tasklet, btn_func, 0);

    2.编写tasklet延后处理函数
    static void btn_func (unsigned long data)
    {
    //data保存传递的参数信息
    //将做比较耗时,不紧急的内容
    //工作在中断上下文中,不能进行休眠
    //硬件中断和高优先级的软中断可以打断
    }


    3.在顶半部进行登记底半部tasklet延后处理函数,将来在适当的时候调用tasklet的延后处理函数
    tasklet_schedule (name);
    name:定义初始化的tasklet对象名称
    例如:
    tasklet_schedule (&btn_tasklet);

    底半部机制之工作队列

    明确:工作队列的本质就是延后执行!
    明确:"休眠"只存在于进程的世界里!

    特点:
    1.工作队列对应的延后处理函数将来做原先中断处理函数中比较耗时,不紧急的内容;
    2.工作队列对应的延后处理函数工作在进程上下文中,可以进行休眠操作;
    3.工作队列重点研究的是队列中每一个节点,每一个节点代表着延后执行的某个事情;工作队列可以采用内核默认的,当然也可以自己创建一个工作队列;

    工作队列每一个节点的数据结构:
    struct work_struct {
    void (*func)(struct work_struct *work);
    ...
    };

    作用:描述工作队列中每一个节点(延后执行的内容)
    成员:
    func:延后处理函数,工作在进程上下文中,可以进行休眠操作;将来在适当的时候被调用;
    形参work指针就是指向自己(节点)

    利用工作队列实现延后执行:
    1.定义初始化工作队列中某个节点对象
    struct work_struct btn_work;
    INIT_WORK(&btn_work, btn_work_function);

    //给btn_work对象添加一个延后处理函数
    2.编写延后处理函数
    static void btn_work_function(struct work_struct *work)
    {
    //做不紧急,耗时较长的内容
    //工作在进程上下文
    //可以进行休眠操作
    //work指针指向btn_work
    }
    3.在顶半部进行登记延后处理函数,在适当的时候会执行此函数
    schedule_work(&btn_work);

    案例:在按键驱动程序中利用工作队列实现延后执行

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/irq.h>
    #include <linux/interrupt.h>
    #include <asm/gpio.h>
    #include <plat/gpio-cfg.h>
    
    //声明描述按键硬件信息的数据结构
    struct btn_resource {
        int gpio;   //GPIO编号
        int irq;   //中断号
        char *name; //按键名称
    };
    
    //定义初始化按键的硬件信息
    static struct btn_resource btn_info[] = {
        [0] = {
            .gpio = S5PV210_GPH0(0),
            .irq = IRQ_EINT(0),
            .name = "KEY_UP"
        },
        [1] = {
            .gpio = S5PV210_GPH0(1),
            .irq = IRQ_EINT(1),
            .name = "KEY_DOWN"
        }
    };
    
    //定义工作队列中某个节点(延后执行)
    static struct work_struct btn_work;
    
    //底半部延后处理函数
    static void btn_work_function(struct work_struct *work)
    {
        printk("%s\n", __func__); 
    }
    
    //顶半部(中断处理函数)
    static irqreturn_t button_isr(int irq, void *dev)
    {
        int state; 
    
        //通过dev获取按键对应的硬件信息
        struct btn_resource *pdata = 
                                (struct btn_resource *)dev;
    
    
        //登记延后处理函数
        schedule_work(&btn_work);
    
        //打印按键的状态值
        state = gpio_get_value(pdata->gpio); 
       
        printk("%s:按键%s的按键状态为%s\n",
                __func__, pdata->name, state ?"松开":"按下");
        
        return IRQ_HANDLED; //中断处理完毕
    }
    
    static int btn_init(void)
    {
        int i;
    
        //申请GPIO资源
        //申请中断资源和注册中断处理函数
        //注意:
        //8个按键共享一个中断处理函数,通过中断号和参数区分
        //GPIO为复用IO,先做中断IO,一旦中断触发,内核帮你切换为输入口
        for (i = 0; i < ARRAY_SIZE(btn_info); i++) {
            gpio_request(btn_info[i].gpio, btn_info[i].name);
            request_irq(btn_info[i].irq, 
                        button_isr,
                IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
                        btn_info[i].name,
                        &btn_info[i]);
        }
        
        //初始化工作队列的某个节点对象
        //给节点添加一个延后处理函数
        INIT_WORK(&btn_work, btn_work_function);
        return 0;
    }
    
    static void btn_exit(void)
    {
        int i;
    
        //释放GPIO资源
        //释放中断资源和删除中断处理函数
        for (i = 0; i < ARRAY_SIZE(btn_info); i++) {
            gpio_free(btn_info[i].gpio);
            free_irq(btn_info[i].irq, &btn_info[i]);
        }
    }
    module_init(btn_init);
    module_exit(btn_exit);
    MODULE_LICENSE("GPL");

    底半部机制之软中断

    特点:
    1.也是延后执行的一种手段
    2.软中断的延后处理函数工作在中断上下文中,不能进行休眠操作
    3.软中断的处理函数可以运行在多CPU中,而基于软中断实现的tasklet的延后处理函数之能运行在一个CPU上;
    4.软中断的处理函数要求具有可重入性;
    函数可重入:
    1.尽量避免访问全局变量
    2.如果要访问全局变量,必须要进行互斥访问,造成代码的执行效率变低
    5.软中断的实现不能用模块的加载和卸载,必须把代码写到内核源码,静态编译;
    如果要更新了代码,后果是需要系统重新启动;
    百度:linux内核软中断编程

    展开全文
  • 寒江独钓(1):内核数据类型函数 零、内核模块所在进程   1、内核模块位于内核空间,而内核空间又被所有的进程共享。因此,内核模块实际上位于任何一个进程空间中。  2、PsGetCurrentProcessId函数...

    寒江独钓(1):内核数据类型和函数

    零、内核模块所在进程  

      1、内核模块位于内核空间,而内核空间又被所有的进程共享。因此,内核模块实际上位于任何一个进程空间中。

      2、PsGetCurrentProcessId函数能得到当前进程的进程号,函数原型为: HANDLE PsGetCurrentProcessId();返回值时间上就是一个进程ID;

      3、当DriverEntry函数被调用时,一般位于系统进程中,因为Windows一般用系统进程来加载内核模块;但是内核模块中分发函数调用时,当前进程一般都不是System进程。


    windows CE 数据类型大全 http://dev.10086.cn/cmdn/wiki/index.php?doc-view-2947.html


    一、基本数据类型

    1、基本类型

    WDK编程规范中不直接使用unsigned long,而是使用已经重新定义过的ULONG。重新定义的好处是,万一有了什么问题,再重新定义一下就行,这些代码不至于产生不可控的问题

    unsigned long重定义为ULONG。

    unsigned char重定义为UCHAR。

    unsigned int重定义为UINT。

    void重定义为VOID。

    unsigned long *重定义为PULONG。

    unsigned char * 重定义为PUCHAR。

    unsigned int *重定义PUINT。

    void *重定义为PVOID

    2、返回状态

    绝大部分内核API的返回值都是一个返回状态,也就是一个错误码。这个类型为NTSTATUS

    3、字符串

    驱动里字符串一般用一个结构来容纳,这个结构的定义如下:

    typedef struct _UNICODE_STRING {
     USHORT  Length;
     USHORT  MaximumLength;
     PWSTR  Buffer;
    } UNICODE_STRING *PUNICODE_STRING;    //将_UNICODE_STRING类型重定为UNICODE_STRING, 指针类型重定为PUNICODE_STRING

    字符串的字符是宽字符,是双字节的。

    在很多情况下不直接使用字符的指针,而是将这个结构作为字符串使用。

    这个结构的指针可以直接在DbgPrint中打印,int用%d来打印,char用%c来打印一样,UNICODE_STRING的指针(注意是指针,结构本身不能打印)可以用%wZ来打印;

    这个结构也是可以之间打印的:

    UNICODE_STRING str = RTL_CONSTANT_STRING(L" first: Hello, my salary! ");
    DbgPrint("%wZ",&str);
    
    UNICODE_STRING只要知道这是宽字符串就可以啦~
    
    二、重要数据类型
    
    1、驱动对象

    一个驱动对象代表了一个驱动程序,或者说一个内核模块。驱动对象的结构如下(这个结构的定义取自WDK中的wdm.h)(其中有一些域被笔者用省略号代替了):

    typedef struct _DRIVER_OBJECT {
    // 结构的类型和大小。
    CSHORT Type;
    CSHORT Size;

    // 设备对象,这里实际上是一个设备对象的链表的开始。因为DeviceObject
    // 中有相关链表信息。阅读下一小节“设备对象”会得到更多的信息
    PDEVICE_OBJECT DeviceObject;
    ……

    // 这个内核模块在内核空间中的开始地址和大小
    PVOID DriverStart;
    ULONG DriverSize;
    ……
    // 驱动的名字
    UNICODE_STRING DriverName;
    ……
    // 快速IO分发函数
    PFAST_IO_DISPATCH FastIoDispatch;
    ……
    // 驱动的卸载函数
    PDRIVER_UNLOAD DriverUnload;
    // 普通分发函数
    PDRIVER_DISPATCH MajorFunction[IRP_MJ_MAXIMUM_FUNCTION + 1];
    } DRIVER_OBJECT;

    实际上,如果写一个驱动程序,或者说编写一个内核模块,要在Windows中加载,则必须填写这样一个结构,来告诉Windows程序提供哪些功能。

        内核模块并不生成一个进程,只是填写一组回调函数让Windows来调用,而且这组回调函数必须符合Windows内核规定

    2、设备对象
    设备对象(DEVICE_OBJECT)是唯一可以接收请求的实体,任何一个“请求”(IRP)都是发送给某个设备对象的。内核程序是用一个驱动对象表
    示的,所以一个设备对象总是属于一个驱动对象。在WDK的wdm.h中可以看到结构定义如下(这里省略了许多读者现在不需要了解的域):
     
    typedef struct DECLSPEC_ALIGN(MEMORY_ALLOCATION_ALIGNMENT) 
    _DEVICE_OBJECT {
    
    // 和驱动对象一样
    CSHORT Type;
    USHORT Size;
    // 引用计数
    ULONG ReferenceCount;
    // 这个设备所属的驱动对象
    struct _DRIVER_OBJECT *DriverObject;
    // 下一个设备对象。在一个驱动对象中有n个设备,这些设备用这个指针连接
    // 起来作为一个单向的链表
    struct _DEVICE_OBJECT *NextDevice;
    // 设备类型
    DEVICE_TYPE DeviceType;
    // IRP栈大小
    HAR StackSize;
    
    ……
    }DEVICE_OBJECT;

    从这个结构来看,读者应该发现驱动对象(DRIVER_OBJECT)和设备对象(DEVICE_OBJECT)之间的联系,这非常重要。驱动对象生成多个设备对象。而Windows向设备对象发送请求,但是这些请求如何处理呢?实际上,这些请求是被驱动对象的分发函数所捕获的。

    当Windows内核向一个设备发送一个请求时,驱动对象的分发函数中的某一个会被调用。分发函数原型如下:

    // 一个典型的分发函数,第一个参数device是请求的目标设备,第二个参数irp是请求的指针
    NTSTATUS MyDispatch(PDEVICE_OBJECT deivce, PIRP irp);
    
    而具体做什么由用户编写啦。
    3、请求
    大部分请求以IRP的形式发送。IRP也是一个内核数据结构,这个结构非常复杂,这是因为这个结构要表示无数种实际请求的缘故。
    
    4、函数调用
      哪些函数可以调用呢?答案是:WDK的帮助中有的函数,在内核编程中我们都可以调用(除去少部分有说明为用户态API的函数)。
        大部分内核API都有前缀,主要的函数以Io-、Ex-、Rtl-、Ke-、Zw-、Nt-和Ps-开头。
        此外,与NDIS网络驱动开发相关的函数几乎都是以Ndis-开头的,与开发WDF驱动相关的函数都是以Wdf-开头的。
        Io:以Io-开头的系列函数非常重要,如表2-5所示。因为涉及到IO管理器,IO管理器就是将用户调用的API函数翻译成IRP或者将等价请求发送到内核各个不同的设备的关键组件;
        Rtl:在进行字符串操作时,使用到大量的Rtl-函数;
        Zw:用于操作文件的Zw-系列函数(对应Nt-系列函数);
        Ex:常用的几个分配内存、获取互斥体等几个函数。
    
      WDK的帮助中没有的函数:

      基本上可以认为,大家常用的C运行时库中的函数,如果只涉及字符串和内存数据(而不涉及内存管理,比如内存的分配和释放),则是可以在内核程序里调用的。但是MS并不提倡这样做。

    如果这些函数涉及内存管理、文件操作、网络操作、线程等,则往往在头文件中虽然有这个函数存在,甚至编译都能通过,但是连接却会出问题。

    5、内核程序的主要调用源

      调用源并不是一个常用的概念,而是笔者为了读者理解的方便而自己设定的一个概念。首先我们假定函数B调用了函数A,则称函数B为A的调用者。

      那么任意指定一段代码,假设这段代码在函数A中。现在我们寻找A的调用者。假设找到一个调用者B,我们再继续寻找B的调用者。如此递归地寻找下去,直到在代码可见的范围内,最后一个调用者函数M。函数M无法在可见范围内的代码内找到调用者,那么就称函数M为函数A的调用源。换句话说,一段代码的调用源是指,调用的这段代码,编程者所能见到的最初始的源头的那个函数。

      同时,从调用源出发,嵌套地调用一系列函数,最后到调用函数B,函数B调用函数A这是一条“路径”,我们称之为函数A的调用路径。

      一个函数的调用路径显然可能不止一条。同时,一个函数的调用源也可能不止一个。  内核编程的一个显著特点是,任意一个函数往往可能有多个调用源。主要可以追溯到的调用源如下:

        (1)入口函数DriverEntry和卸载函数DriverUnload。

        (2)各种分发函数(包括普通分发函数和快速IO分发函数)。

        (3)处理请求时设置的完成函数。也就是说,该请求完成后会被系统调用的回调函数。

        (4)其他回调函数(如各类NDIS驱动程序的特征函数,NDIS驱动程序见本书的的第11~13章)。    还可能包括其他的调用源。

    6、函数的多线程安全性

      一般的说,函数的多线程安全性是指,一个函数在被调用过程中,还未返回时,又再次被其他线程调用的情况下,函数执行结果

    的可靠性。如果结果是可靠的,则称这个函数是多线程安全的;如果结果是不可靠的,则称这个函数是非多线程安全的,原因是多线

    程冲突。

      函数的多线程冲突在内核编程中远比用户态应用程序的编程要常见。因此读者会常常听到忠告:要注意函数的多线程安全性。但

    是严格地去保证每个函数的多线程安全性是浪费的,甚至有时是不可能的。因此我们需要判断何时需要保证函数的多线程安全性。读

    者可以通过下面几条规则来简单地判断:

      规则1:可能运行于多线程环境的函数,必须是多线程安全的。只运行于单线程环境的函数,则不需要多线程安全性。

      这是因为存在多线程同时调用该函数的可能,此时必须保证函数的可靠性。那么如何判断函数的运行环境是否是多线程环境呢?可以根据规则2和规则3进行判断。

      规则2:如果函数A的所有的调用源只运行于同一单线程环境,则函数A也是只运行在单线程环境的。

      规则3:如果函数A的其中一个调用源是可能运行在多线程环境的,或者多个调用源可能运行于不同的可并发的多个线程环境,而且调用路径上没有采取多线程序列化

          成单线程的强制措施,则函数A也是可能运行在多线程环境的。

      规则4:如果在函数A所有的可能运行于多线程环境的调用路径上,都有多线程序列化成单线程的强制措施,则函数A是运行在单线程环境的。

      这里所谓的“多线程序列化成单线程的强制措施”是指如互斥体、自旋锁(读者在后面会见到简单自旋锁的使用)等同步手段。

      从上面的规则中可以得到一条推论,就是如果要在多线程环境下调用一个不可重入的函数,则必须在调用路径上采取多线程序列化成单线程的强制措施。

    从上面的规则可以看出,是否要保证可重入性最终由调用源和调用路径决定。

      下面是内核编程中主要调用源的运行环境,如表2-6所示。

    表2-6 内核代码主要调用源的运行环境

    调用源

    运行环境

    原 因

    DriverEntryDriverUnload

    单线程

    这两个函数由系统进程的单一线程调用。不会出现多线程同时调用的情况

    各种分发函数

    多线程

    没有任何文档保证分发函数是不会被多线程同时调用的。此外,分发函数不会和DriverEntry并发,但可能和DriverUnload并发

    完成函数

    多线程

    完成函数随时可能被未知的线程调用

    各种NDIS回调函数

    多线程

    和完成函数相同

      前面的规则结合这个表,基本上可以判断任意一段代码是否需要多线程安全性。 那么如何保证多线程安全性呢?规则如下:

      规则5:只使用函数内部资源,完全不使用全局变量、静态变量或者其他全局性资源的函数是多线程安全的。

      规则6:如果对某个全局变量或者静态变量的所有访问都被强制的同步手段限制为同一时刻只有一个线程访问,则即使使用了这些全局变量

          和静态变量,对函数的多线程安全性也是没有影响的。可以等同于内部变量,然后根据规则5判定。

     7、代码的中断级
    
      读者现在需要了解的中断级主要有Passive级和Dispatch级两种,Dispatch级比Passive级高。在实际编程时,许多具有比较复杂功能的内核
    API都要求必须在Passive级执行,这一点在WDK的文档上有说明。比如下面的例子:
    Callers of IoCreateFile must be running at IRQL?= PASSIVE_LEVEL.
    

      只有比较简单的函数能在Dispatch级执行。调用任何一个内核API之前,必须查看WDK文档,了解这个内核API的中断级要求。那么如何判断

    我们正在编写的代码可能的中断级呢?读者暂时可以简单地根据下面的规则来处理,可以处理大部分情况;遇到特殊情况再特殊处理。

      规则1:如果在调用路径上没有特殊的情况(导致中断级的提高或者降低),则一个函数执行时的中断级和它的调用源的中断级相同。

      规则2:如果在调用路径上有获取自旋锁,则中断级随之升高;如果调用路径上有释放自旋锁,则中断级随之下降。

         和判断多线程安全性一样,读者会发现当前代码的中断级基本上取决于调用源的中断级和调用路径。表2-7列出了内核代码主要调用源

         的运行中断级。

    表2-7 内核代码主要调用源的运行中断级

    调用源

    一般的运行中断级

    DriverEntryDriverUnload

    Passive

    各种分发函数

    Passive

    完成函数

    Dispatch

    各种NDIS回调函数

    Dispatch

    8、WDK中的特殊代码
      WDK示例代码中见到的特殊形式编码。这些代码在Win32应用程序的编程中很少见到,读者需要首先熟悉一下。

      首先是参数说明宏。参数说明宏一般都是空宏,最常见的是IN和OUT。其实定义很简单,如下所示:

    #define IN
    #define OUT
    这样一来,IN和OUT就被定义成了空。无论出现在代码中的任何地方,对代码都不会有什么实质的影响。在WDK的代码中,用来作为
    函数的说明。IN表示这个参数用于输入;OUT表示这个参数用来返回结果。比如下面的例子:
    NTSTATUS 
    ZwQueryInformationFile(
    IN HANDLE  FileHandle,
    OUT PIO_STATUS_BLOCK  IoStatusBlock,
    OUT PVOID  FileInformation,
    IN ULONG  Length,
    IN FILE_INFORMATION_CLASS  FileInformationClass
    );
    
    IN和OUT是比较传统的参数说明宏。在WDK中到处可见更复杂的参数说明宏,比如下面的例子:
    VOID
    NdisProtStatus(
    IN NDIS_HANDLE                          ProtocolBindingContext,
    IN NDIS_STATUS                          GeneralStatus,
    __in_bcount(StatusBufferSize) IN PVOID  StatusBuffer,
    IN UINT                                 StatusBufferSize
    )
    

    其中的__in_bcount不但说明参数StatusBuffer是一个输入参数,而且说明了StatusBuffer作为一个缓冲区,它的字节长度被另一个

    参数StatusBufferSize所指定。读者再见到类似的说明宏,就以字面意思理解即可。

    然后是指定函数位置的预编译指令。比如下面的例子:

    #pragma alloc_text(INIT, DriverEntry)
    #pragma alloc_text(PAGE, NdisProtUnload)
    #pragma alloc_text(PAGE, NdisProtOpen)
    #pragma alloc_text(PAGE, NdisProtClose)

    #pragma alloc_text这个宏仅仅用来指定某个函数的可执行代码在编译出来后在sys文件中的位置。内核模块编译出来之后是一个PE格式的sys文件,这个文件的代码段(text段)中有不同的节(Section),不同的节被加载到内存中之后处理情况不同。读者需要关心的主要是3种节:INIT节的特点是在初始化完毕之后就被释放。也就是说,就不再占用内存空间了。PAGE节的特点是位于可以进行分页交换的内存空间,这些空间在内存紧张时可以被交换到硬盘上以节省内存。如果未用上述的预编译指令处理,则代码默认位于PAGELK节,加载后位于不可分页交换的内存空间中。

    函数DriverEntry显然只需要在初始化阶段执行一次,因此这个函数一般都用#pragma alloc_text(INIT, DriverEntry)使之位于初始化后立刻释放的空间内。

    为了节约内存,可以把很多函数放在PAGE节中。但是要注意:放在PAGE节中的函数不可以在Dispatch级调用,因为这种函数的调用可能诱发缺页中断。但

    是缺页中断处理不能在Dispatch级完成。为此,一般都用一个宏PAGED_CODE()进行测试。如果发现当前中断级为Dispatch级,则程序直接报异常,让程序

    员及早发现。示例如下:

    #pragma alloc_text(PAGE, SfAttachToMountedDevice)
    ……
    NTSTATUS
    SfAttachToMountedDevice (
    IN PDEVICE_OBJECT DeviceObject,
    IN PDEVICE_OBJECT SFilterDeviceObject
    )
    {        
    PSFILTER_DEVICE_EXTENSION newDevExt =
    SFilterDeviceObject->DeviceExtension;
    NTSTATUS status;
    ULONG i;
        PAGED_CODE();
    …


    展开全文
  • 内核键盘中断处理进阶

    千次阅读 2016-10-09 20:55:37
    上一节,我们实现了键盘中断的响应,但响应的处理比较简单,只是向界面打印一条字符串而已,本节,我们将在屏幕上输出键盘中断更多的相关信息。当键盘上的一个按键按下时,键盘会发送一个中断信号给CPU,与此同时,...

    代码的运行和调试请参看视频:
    Linux kernel Hacker, 从零构建自己的内核

    上一节,我们实现了键盘中断的响应,但响应的处理比较简单,只是向界面打印一条字符串而已,本节,我们将在屏幕上输出键盘中断更多的相关信息。当键盘上的一个按键按下时,键盘会发送一个中断信号给CPU,与此同时,键盘会在指定端口(0x60) 输出一个数值,这个数值对应按键的扫描码(make code),当按键弹起时,键盘又给端口输出一个数值,这个数值叫断码(break code).我们以按键按键’A’为例,当按键’A’按下时,键盘给端口0x60发出的扫描码是0X1E, 当按键’A’弹起时,键盘会给端口0x60发送断码0x9E。

    以下显示的就是键盘每一个按键扫描码和断码列表:
    image

    从上图可以看到,当按键 ‘A’按下时,键盘向端口发送数值0x1E, 弹起时发送数值0x9e, 同理按键’B’按下时,键盘向端口0x60发送数值0x30,弹起时向端口发送0xB0, 我们更改上一节的中断处理代码,使得键盘按键按下和弹起时,在界面上显示出按键的make code 和 break code:

    void intHandlerFromC(char* esp) {
        char*vram = bootInfo.vgaRam;
        int xsize = bootInfo.screenX, ysize = bootInfo.screenY;
        io_out8(PIC_OCW2, 0x21);
        unsigned char data = 0;
        data = io_in8(PORT_KEYDAT);
        char* pStr = charToHexStr(data);
        static int showPos = 0;
        showString(vram, xsize, showPos, 0, COL8_FFFFFF, pStr);
        showPos += 32;
    }
    
    char   charToHexVal(char c) {
        if (c >= 10) {
            return 'A' + c - 10;
        } 
    
        return '0' + c;
    }
    
    char*  charToHexStr(unsigned char c) {
        int i = 0;
        char mod = c % 16;
        keyval[3] = charToHexVal(mod);
        c = c / 16;
        keyval[2] = charToHexVal(c);
    
        return keyval;
    }

    intHandlerFromC 是C语言处理中断的函数,我们注意看语句:
    io_out8(PIC_OCW2, 0x21);
    其中PIC_OCW2 的值是0x20, 也就是主PIC芯片的控制端口,上一节我们解释过,0x21对应的是键盘的中断向量。当键盘中断被CPU执行后,下次键盘再向CPU发送信号时,CPU就不会接收,要想让CPU再次接收信号,必须向主PIC的端口再次发送键盘中断的中断向量号。

    PORT_KEYDAT 的值是0x60, io_in8 是内核汇编部分提供的函数,它从指定端口读入数据,并返回。charToHexStr 作用是将键盘输出的数值转换为16进制的字符串。

    当一个按键被按下然后弹起时,上面的intHandlerFromC会调用两次,从而一次按键使得界面上会连续打印两个16进制数值, 上面代码编译进入内核后,加载入虚拟机,然后按下按键’A’,和’B’, 结果显示如下:
    这里写图片描述

    0x1E 和 0x9E是按键’A’的扫描码和断码,0x30和0xB0是按键’B’的扫描码和断码。

    中断的优化处理

    中断,实际上是将CPU当前正在执行的任务给打断,让CPU先处理中断任务,然后再返回处理原先的任务,这时会有一个问题,就是,如果中断处理过久,就会对CPU原来的任务造成负面影响。

    就以键盘中断为例,我们处理键盘中断时,要获取按键的扫描码和断码的数值,同时将数值转换为字符串,最后再将字符串的每一个字符绘制到界面上。这一系列其实是很耗时的计算。假设这时候有网络数据抵达系统,但是CPU忙于处理键盘中断,不能及时接收网络数据,这样,系统便会丢失网络数据。

    所以,对于中断,我们要尽可能快的处理,然后把控制器交还给原来的任务。对于键盘中断,我们可以把键盘发送的扫描码和断码数值缓存起来,然后把控制器交换给原来任务,等到CPU稍微空闲时再处理键盘事件。因此我们为键盘中断设置一个缓冲区:

    struct KEYBUF {
        unsigned char key_buf[32];
        int next_r, next_w, len;
    };
    
    struct KEYBUF keybuf;

    我们设置了长为32字节的缓冲区,当键盘中断接收到数据时,从next_w指向的位置开始写入,len用来表示当前缓冲区中的有效数据长度,例如,当我们按下’A’和’B’两个按键时,我们会向缓冲区写入4个字节,于是len就等于4.

    以下是改进后键盘中断的代码逻辑:

    void intHandlerFromC(char* esp) {
        char*vram = bootInfo.vgaRam;
        int xsize = bootInfo.screenX, ysize = bootInfo.screenY;
        io_out8(PIC_OCW2, 0x21);
        unsigned char data = 0;
        data = io_in8(PORT_KEYDAT);
        if (keybuf.len < 32) {
           keybuf.key_buf[keybuf.next_w] = data;
           keybuf.len++;
           keybuf.next_w = (keybuf.next_w+1) % 32;
        }
    
    }

    每次键盘中断,代码都将相应的扫描码和断码写入缓冲区,如果缓冲区写满后,也就是next_w的值达到32,那么通过一次求余,next_w会重新设置为0,也就是说一旦缓冲区写满后,下次写入将从头开始。

    键盘数据的输出转移到内核的主函数CMain中,

    void CMain(void) {
       ...
    
       int data = 0;
        for(;;) {
           io_cli();
           if (keybuf.len == 0) {
               io_stihlt();
           } else {
               data = keybuf.key_buf[keybuf.next_r];
               keybuf.next_r = (keybuf.next_r + 1) % 32;
               io_sti();
    
               char* pStr = charToHexStr(data);
               static int showPos = 0;
               showString(vram, xsize, showPos, 0, COL8_FFFFFF, pStr);
               showPos += 32;           
           }
        }
    }

    主函数不再单纯的死循环,而是每次循环的时候查看键盘缓冲区是否有数据,有数据的话就把缓冲区中的数据显示到屏幕上,同时next_r增加,以指向下一个要输出的数据。

    上面代码编译如内核后,效果跟前一节一样,以下结果是按下按键’A’, ‘B’, 和’Ctrl’后的结果:
    这里写图片描述

    ‘Ctrl’ 按键的扫描码和断码分别为: 0x1D 和 0x9D.

    通过这两节研究,我们对中断有了进一步的认识,下一节,我们将让鼠标动起来。

    展开全文
  • request_irq的作用是申请使用IRQ...request_irq()函数的原型如下: /* kernel/irq/manage.c */ int request_irq( unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long irqfl

    request_irq的作用是申请使用IRQ并注册中断处理程序。

    request_irq()函数的原型如下:

    /* kernel/irq/manage.c */
    int request_irq(
    unsigned int irq,
        irqreturn_t (*handler)(int, void *, struct pt_regs *),
        unsigned long irqflags, 
        const char *devname, 
        void *dev_id );

    当使用内核共享中断时, request_irq 必须要提供 dev_id 参数,并且 dev_id 的值必须唯一。那么这里提供唯一的 dev_id值的究竟是做什么用的?

    起先我以为dev_id的值是提供给kernel进行判断共享中断线上的哪一个设备产生了中断(即哪个irqaction产生中断),然后执行相应的中断处理函数(irqaction->handler)。实际上不是的,我们来看看《Linux Kernel Development – Second Edition》第六章中Shared Handlers这一节,其中有段总结性的文字如下:

     

    When the kernel receives an interrupt, it invokes sequentially each registered handler on the line. Therefore, it is important that the handler be capable of distinguishing whether it generated a given interrupt. The handler must quickly exit if its associated device did not generate the interrupt. This requires the hardware device to have a status register (or similar mechanism) that the handler can check. Most hardware does indeed have such a feature.

     

    这段话的大概意思是,发生中断时,内核并不判断究竟是共享中断线上的哪个设备产生了中断,它会循环执行所有该中断线上注册的中断处理函数(即irqaction->handler函数)。因此irqaction->handler函数有责任识别出是否是自己的硬件设备产生了中断,然后再执行该中断处理函数。通常是通过读取该硬件设备提供的中断flag标志位进行判断。

     

    那既然kernel循环执行该中断线上注册的所有irqaction->handler函数,把识别究竟是哪个硬件设备产生了中断这件事交给中断处理函数本身去做,那request_irqdev_id参数究竟是做什么用的?

     

    我总结了一下,实际上dev_id作用主要有两点:

    一.在中断处理程序释放时使用到dev_id

    When your driver unloads, you need to unregister your interrupt handler and potentially disable the interrupt line. To do this, call

    void free_irq(unsigned int irq, void *dev_id)

    ……

    The fifth parameter, dev_id, is used primarily for shared interrupt lines. When an interrupt handler is freed (discussed later), dev_id provides a unique cookie to allow the removal of only the desired interrupt handler from the interrupt line. Without this parameter, it would be impossible for the kernel to know which handler to remove on a given interrupt line.

     

    这里《LKD2》讲了很清楚,当调用free_irq注销中断处理函数时(通常卸载驱动时其中断处理函数也会被注销掉),因为dev_id是唯一的,所以可以通过它来判断从共享中断线上的多个中断处理程序中删除指定的一个。如果没有这个参数,那么kernel不可能知道给定的中断线上到底要删除哪一个处理程序。

     

    下面我们来看一下free_irq的代码:

    void free_irq(unsigned int irq, void *dev_id)
    {
        struct irq_desc *desc;
        struct irqaction **p;
        unsigned long flags;
    
        WARN_ON(in_interrupt());
        if (irq >= NR_IRQS)
            return;
    
        desc = irq_desc + irq;    /* 获取该中断号对应的irq_desc */
        spin_lock_irqsave(&desc->lock, flags);
        p = &desc->action;        /* 找到该irq的irqaction链表 */
        for (;;) {
            struct irqaction *action = *p;
    
            if (action) {
                struct irqaction **pp = p;
    
                p = &action->next;
                if (action->dev_id != dev_id) 
                    continue;   /* 这两句进行循环判断,直到找到相应dev_id设备的irqaction */
    
                /* Found it - now remove it from the list of entries */
                *pp = action->next;   /* 指向下一个irqaction */
    
                /* Currently used only by UML, might disappear one day.*/
    #ifdef CONFIG_IRQ_RELEASE_METHOD
                if (desc->chip->release)
                    desc->chip->release(irq, dev_id);
    #endif
    
                if (!desc->action) {
                    desc->status |= IRQ_DISABLED;
                    if (desc->chip->shutdown)
                        desc->chip->shutdown(irq);
                    else
                        desc->chip->disable(irq);
                }
                recalculate_desc_flags(desc);
                spin_unlock_irqrestore(&desc->lock, flags);
                unregister_handler_proc(irq, action);
    
                /* Make sure it's not being used on another CPU */
                synchronize_irq(irq);
                kfree(action);        /* 删除该设备(dev_id)的irqaction */
                return;
            }
            printk(KERN_ERR "Trying to free already-free IRQ %d\n", irq);
            spin_unlock_irqrestore(&desc->lock, flags);
            return;
        }
    }

    二.将使用该中断处理程序的设备结构体传递给该中断处理程序:

    首先看两个基础条件:

    1)  内核中的各个设备结构体肯定是唯一的,因此满足dev_id唯一这个要求

    2)  dev_id参数会在发生中断时传递给该中断的服务程序。

    典型的中断服务程序定义如下:

     

    static irqreturn_t intr_handler(int irq, void *dev_id, struct pt_regs *regs);

     

    request_irqdev_id参数会传递给该中断服务程序的dev_id。因此也可以将驱动程序的设备结构体通过dev_id传递给中断服务程序。

    这样做作用主要有两个:

    1) 中断服务程序可能使用到设备结构体中的信息

    如s3c2410的iis音频驱动,它是将DMA Channel结构体通过dev_id传递给中断服务程序

    /* arch/arm/mach-s3c2410/dma.c */
    int s3c2410_dma_request(unsigned int channel, struct s3c2410_dma_client *client,void *dev)
    {
         err = request_irq(chan->irq, s3c2410_dma_irq, IRQF_DISABLED, client->name, (void *)chan);
    ……
    }

    目的是中断处理函数s3c2410_dma_irq内需要用到chan结构体的duf成员和load_state成员等。

    使用方法如下:

    /* arch/arm/mach-s3c2410/dma.c */
    static irqreturn_t
    s3c2410_dma_irq(int irq, void *devpw, struct pt_regs *regs)
    {
        struct s3c2410_dma_chan *chan = (struct s3c2410_dma_chan *)devpw; /* 这里强制转化成request_irq时传给dev_id的设备类型,此处即s3c2410_dma_chan ,下面便可以使用它*/
        struct s3c2410_dma_buf *buf;
        buf = chan->curr;
        ……
    }

    因此,我们可以通过将设备结构传递给request_irq的dev_id参数这种机制,在中断处理函数中使用该设备结构体,这是大部分驱动程序通用的一种手法。

     

    2)前面我们讲了若使用共享中断,那么中断处理函数自身需要能识别是否是自己的设备产生了中断。通常这是通过读取该硬件设备提供的中断flag标志位进行判断的。

    而往往驱动程序里定义的设备结构体通常包含了该设备的IO地址,加上偏移就可以算出中断状态寄存器及中断flag标志位的IO地址信息。因此将设备结构体通过dev_id传递给设备中断处理程序的另一个作用就是使用共享中断时,可以在中断处理函数内通过读取该设备结构 (dev_id) 中提供的中断Flag标志位地址信息进行判断,是否是该设备产生了中断,然后再进一步判断是否继续往下执行还是跳到下一个irqaction->handler函数再判断执行。当然,如果本来就知道该设备中断状态寄存器的IO地址,也可以选择直接readl该地址信息进行判断。

    点击打开链接
    展开全文
  • Linux内核中断以及中断处理程序

    千次阅读 2017-04-16 12:13:23
    人生若只如初见,何事秋风悲画扇。  --------纳兰容若 《木兰花令·拟...一、什么是中断:   linux管理所有的硬件设备,要做的第一件事先是通信。然而CPU的处理速度要远快于外围设备,总不能让CPU一直在等待外围设备
  •  内核中很多函数是基于时间驱动的,其中有些函数需要周期或定期执行。比如有的每秒执行100次,有的在等待一个相对时间之后执行。除此之外,内核还必须管理系统运行的时间日期。   周期性产生的时间都是有系统...
  • Linux内核中断子系统

    千次阅读 2013-04-12 14:58:13
    Linux中断子系统主要包括了三个部分,一部分和体系结构相关,位于最底层,主要负责在中断发生之后保护CPU现场,调用内核统一的中断处理入口函数,负责从中断处理过程恢复到中断之前的流程等比较底层的工作。...
  • Linux内核中断、软中断、tasklet

    千次阅读 2015-09-28 16:21:24
    但所用到的中断机制都基本上是用到中断的顶半部,即:编写中断处理函数,通过request_irq函数申请中断,这样当中断来临的时候,就会自动执行中断处理程序里面的内容。之所以没有使用到中断的底半部,是因为我们这些...
  • Linux内核开发常见的函数

    千次阅读 2019-06-18 14:06:05
    做Linux驱动开发经常要使用到内核相关的函数,本篇只要介绍在做驱动开发的过程中用到的内核函数,为以后开发查询提供方便。 本篇覆盖函数如下 copy_from_user与copy_to_user函数 down_interruptible与down函数 ...
  • linux内核-中断和异常

    千次阅读 2013-09-30 10:02:30
    中断通常定义为一个事件,该事件改变处理器执行的指令顺序。这个事件与cpu芯片内外部...同步中断和异步中断通常成为异常和中断,异常是有程序的错误产生的,或者是又内核必须处理的异常条件产生的。第一种情况下,内
  • 内核中断之tasklet机制

    千次阅读 2019-12-22 22:02:14
    中断(SoftIRQ)是内核提供的一种基于中断的延时机制, Linux内核定义的软中断有以下几种: enum { HI_SOFTIRQ=0, /*高优先级的tasklet*/ TIMER_SOFTIRQ, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, BLOCK_SOFTIRQ, ...
  • Linux内核中断机制(一):中断注册方法

    千次阅读 2016-12-27 22:00:29
    在 linux 内核中用于申请中断函数是 request_irq(),函数原型在Kernel/irq/manage.c 中定义: int request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, c
  • GPIO及中断API函数

    千次阅读 2016-01-19 16:07:51
    #include // 标准 GPIO_API  int gpio_request(unsigned gpio, const char *label);  获得并占有 GPIO ...主要是告诉内核这地址被占用了。当其它地方调用同一地址的gpio_request就会报告错误,该地址已被
  • ARM内核寄存器的操作函数

    千次阅读 2017-01-11 17:09:00
    在修改嵌入式底层的参数时需要使用一些内核寄存器的操作,在编译器中往往都封装了对应的函数。 1.__ASM uint32_t __get_PSP(void):获取进程堆栈指针PSP。 2.__ASM void __set_PSP(uint32_t topOfProcStack):...
  • 中断处理函数中自旋锁的应用

    千次阅读 2018-04-03 10:16:20
    中断处理函数中,在获取锁之前,必须先禁止本地中断。否则,持有锁的内核代码会被中断处理程序打断,接着试图去争用这个已经被持有的自旋锁。这样的结果是,中断处理函数自旋,等待该锁重新可用,但是锁的持有者在该...
  • 内核字符串处理函数和IRQL

    千次阅读 2011-12-21 18:44:49
    内核字符串处理函数和IRQL --by 张佩 系统中断级(IRQL) 借助于IRQL机制,系统实现了任务抢占功能。高中断级任务可以任意抢占低中断级任务的系统执行权,而低中断级任务必须等待所有高中断级任务都完成后,...
  • Linux 内核中断

    千次阅读 2015-06-19 10:52:02
    1.中断引入?  硬件如果要和cpu进行通信有那么几种方法?(把这个过程比作是老师分苹果的故事)  有程序查询法,中断,DMA,通道等。  程序查询法就是老师每一个小孩子都去问,他们的苹果有没有吃完了
  • linux内核缺页中断处理

    千次阅读 2018-07-12 22:56:58
    MMU可以做虚拟地址到物理地址的转换,使用MMU我们就可以使用更多的内存空间,因为程序具有局部性原理,我们可以将暂时用不到的数据存放到磁盘,当访问到时会发生缺页中断,从磁盘中将所需要的数据加载到内存。...
  • linux中断相关函数中断上下文理解

    千次阅读 2013-12-24 15:35:08
    local_irq_enable():恢复本地中断,这一对函数只要调用一次就会达到所要功能,而不是嵌套的,这样会带来潜在的危险,所以我要需要一种机制可以恢复的以前的状态,而不是单纯的开关,所以内核提供了另外一对函数
  • Linux内核之禁止中断和禁止内核抢占

    万次阅读 2017-04-02 22:57:42
    禁止中断指的是Linux内核停工了一组接口用于操作机器上的中断状态。这些接口为我们提供了能够禁止当前处理器的中断系统,或者屏蔽掉整个机器的一条中断线的能力。通过禁止中断,可以确保某个中断处理程序不会抢占...
  • Linux内核空间申请内存函数

    千次阅读 2018-05-29 14:30:59
    kmalloc()函数原型:void *kmalloc(size_t size, gfp_t flags); kmalloc() 申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对...
  • 中断申请函数request_irq详解

    万次阅读 2014-05-20 21:11:52
    原地址:... 一、中断注册方法 在linux内核中用于申请中断函数是request_irq(),函数原型在Kernel/irq/manage.c中定义: int request_irq(unsigned int irq, irq_handler_t handler,  
  • 细说Linux内核中断机制(详)

    千次阅读 2018-07-20 23:04:21
    本文着重介绍Linux内核中断处理的始末流程,因此对一些基本的概念应该有所了解。 2.硬件支持 我们知道,CPU有一个INTR引脚,用于接收中断请求信号。 而中断控制器用于提供中断向量,即第几号中断。 3.内核需要...
  • Linux 内核中断内幕

    千次阅读 2016-03-16 18:00:50
    本文对中断系统进行了全面的分析与探讨,主要包括中断控制器、中断分类、中断亲和力、中断线程化与 SMP 中的中断迁徙等。首先对中断工作原理进行了简要分析,接着详细探讨了中断亲和力的实现原理,最后对中断线程化...
  • 中断注册函数分析总结

    千次阅读 2014-04-23 11:49:11
    函数原型如下: 2.4 内核 int request_irq (unsigned int irq, void (*handler)(int, void *, struct pt_regs *), unsigned long frags, const char *device, void *dev_id); 2.6 内核 request_irq(unsigned
  • Linux驱动开发——按键为例介绍Linux内核中断

    千次阅读 多人点赞 2020-12-14 17:49:36
    Linux内核中断1. 简介2. linux内核中断的注册与注销3. 代码4. 测试 1. 简介      linux的中断处理过程和ARM裸板中的中断处理过程是一致的。不同点在于裸板开始时所有的软件都是自行编程完成的...
  • Linux内核延时研究与函数代码分析

    千次阅读 2010-09-02 09:35:00
    jiffies 以前提过,但是用来计算函数调用的时间确实显得粒度太大(一个 jiffies = 1ms or 4ms )gettimeofday (用户态)和do_gettimeofday(内核态)用下来感觉不错。粒度可以达到us级别。试验过一个while 循环...
  • Linux 中的各种栈:进程栈 线程栈 内核中断

    万次阅读 多人点赞 2016-09-01 21:52:02
    获取有点麻烦,我们需要先利用递归函数把栈搞溢出了,然后再 GDB 中把栈溢出的时候把栈指针 esp 打印出来即可。代码如下: /* file name: stacksize.c */ void *orig_stack_pointer; void blow_stack...
  • CPU与linux内核中断的处理

    千次阅读 2014-02-19 17:41:00
    来自:linux内核修炼之道 CPU的工作 如图 6.11 所示为中断或异常发生时,CPU 所要做的工作。 确定中断或异常的中断向量 i(在 0~255 之间)。可屏蔽中断中断向量从中断控制器获得,不可屏蔽中断和异常的中断向量是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 68,587
精华内容 27,434
关键字:

内核获取中断类型函数