• 4、中断与定时器的学习


    一、中断

        所谓的中断,是指 CPU 在执行的过程中,出现了某些的突发时间,CPU 必须暂停当前程序的执行,转而去处理突发的事件,当处理完毕之后,又返回源程序继续执行。

    1.1、中断的分类

        按照中断的来源: 可以分为内部和外部的中。外部中断,也就是由外设请求的中断;内部中断,显示就是 CPU 请求的中断,比如软中断,除法错误。

        按照总段屏蔽的情况:分为可以屏蔽和不可屏蔽。

    1.2、中断的常用接口

    1、中断的申请

    request_irq(unsigned int irq,irq_handler_t handler,unsigned long flags,const char * devname,void * dev_id)

        当完成 request_irq 的时候,内部其实已经是调用了  enable_irq 了,就是中断不仅注册而且使能了,后面已经不需要再进行 enable_irq

    irq : 申请的中断号

    handler : 登记的中断处理函数,函数指着来着

    flags : 中断标记(属性标记),指定中断的类型,当为 SA_INTERRUPT : 快速中断,IRQF_SHARED : 共享中断,也就是中断的管脚是被多个管脚共享的,

    devname : 名字,这个名字,当中断注册成功之后,就会在 /proc/interrupt 里面显示,cat 的时候,就可以看到,

    dev_id : 私有的数据,一般是设置为零,当中断设置为共享中断的时候,就可以设置传输的数据,一般传输数据是结构体

        当注册玩中断的时候,可以查看 注册进去的中断,

        cat /proc/interrupts

            CPU0              CPU1              CPU2              CPU3             
    1:          0          0          0          0        Phys-irq  i8042
    8:          3          0          0          0        Phys-irq  rtc
    9:          0          0          0          0        Phys-irq  acpi

    第一列是中断号,而最后一列,就是我们注册进去的 dev_name

    2、中断的释放

    free_irq(unsigned int irq,void * dev_id)

    irq : 释放的中断号

    dev_id : 中断传输的数据,

    3、禁止单个中断

    void disable_irq(int irq);

    void disable_irq_nosync(int  irq);

    void enable_irq(int irq);

        enable 与 disable 是相呼应的,而disable 关闭中断的是,一般是会等到中断处理完毕在之后才返回,而 nosync 则是立即返回。因为不反悔的话有可能中断的清理工作 N 久,导致了 死锁的产生。

    4、禁止所有的中断

          实现屏蔽本 CPU 内,所有的中断

    #define local_irq_save(flags)

    void local_irq_disable(void)

        前者的宏代码,会在屏蔽中断的同时,将中断状态保留在 flags 中,而 后面的则不会。

       要想将中断回复的话,就要用下面的代码:

    #define local_irq_restore(flags)     // 之前保留的状态有用了

    void local_irq_enable(void)

    1.3、中断的机制

        在产生中断以后,中断要去执行长时间的任务;而中断的数量又是有限的,要实现满足大量中断的功能,又要实现快速的反映,Linux 使用了将中断分为上半部下半部的机制中断

        上半部,实现的内核的注册之后,中断发生之后的快速反映部分,其实就是  request_irq 指定的中断函数。中断发生,立马跳转到中断函数里面执行,快速的反映。而后半部分的话,则是在前半部分里面调调度延迟处理的机制,对那些耗时的工作,放在下半部处理。,用延迟处理的函数,来实现。而上部分调度延迟处理的机制,一般是 tasklet 机制 和工作队列机。

    1.3.1、tasklet 机制

           tasklet 机制,实质上,重新指定了一个新的函数,让这个新的函数,在适当的实际去运行,也就是在下半部运行,而上半部的中断就可以释放了,这样上半部空出来资源,就得到释放。

        void my_tesklet_for_func(unsigned long data)   // 函数

        DECLARE_TASKLET(my_tesklet, my_tesklet_for_func,data);  // 初始化

        实现了,将一个执行函数,绑定了 tasklet 机制上面,而第一个参数是名字,随意都可以的,而  data 则是传输给 指定函数的值。

        完成了函数的绑定,tasklet 机制上运行了下半部,但是必须在上半部分,也就是 request_irq 指定的函数里面,进行调用,

        tasklet_schedule(&my_tesklet)

        这里需要知道的是,tasklet 机制,函数的绑定,在函数外就可以被执行,tasklet 的调度运行,则是在上半部被指定,这样才可以跳转到下半部分被得到运行嘛。

    注意:

        在单核的情况下,tasklet 机制,不用加锁;而在多核的情况下, tsaklet 可以保证函数都是运行在第一个调度它们的 CPU 上,因此一个中断处理,可以确保一个 tasklet 在处理结束前不会被执行。但是,另一个中断是存在可能在 tasklet 在运行时被提交,因此,tasklet 和中断之间还是存在需要加锁的需要。

    1.3.2、工作队列

        工作队列的与 tasklet 类似,但是工作队列的执行上下文都是在内核线程,因此工作队列是可以被调度和睡眠,显然 tasklet 是不能睡眠的。

    工作队列比 tasklet 多了一个 struct work_struct 结构体,这个结构体就是专门的过队列

    struct work_struct {
    atomic_long_t data;
    #define WORK_STRUCT_PENDING 0		/* T if work item pending execution */
    #define WORK_STRUCT_STATIC  1		/* static initializer (debugobjects) */
    #define WORK_STRUCT_FLAG_MASK (3UL)
    #define WORK_STRUCT_WQ_DATA_MASK (~WORK_STRUCT_FLAG_MASK)
    	struct list_head entry;
    	work_func_t func;
    #ifdef CONFIG_LOCKDEP
    	struct lockdep_map lockdep_map;
    #endif
    };

    这里主要是

    work_func_t 这个宏定义,其实就是函数指针。

    struct work_struct my_work_struct;

    void my_func_for_work(struct work_struct *work);

        上面定义了工作队列和工作队列的函数。

        需要初始化工作工作队列,

        INIT_WORK(&my_work_struct,my_func_for_work);

        将工作队列与函数进行绑定,

        完成绑定只是前奏,需要在上半部,也就是 request_irq 指定的函数里面,调用工作队列:

        schedule_work(&my_work_struct);

    二、定时器

        之所以将定时器与中断放在一个章节,是因为定时器的实在在硬件也是依赖中断实现的,系统走动的的每一个节拍都是被记录在 jiffies 上。对比两个时间点的 jiffies 就可以得到这段时间走过的节拍数目,也就可以得到时间。

    2.1、定时API实现

    1、timer_list 结构体

    struct timer_list{
    struct list_head entry; //内核使用
    unsigned long expires; //设定的超时的值
    void(*function)(unsigned long); //超时处理函数
    unsigned long data; //超时处理函数参数
    struct tvec_base *base; //内核使用
    }

    expires : 设定定时器的时间

    function : 函数指针,当定时器时间到了,就会调用者这个函数

    data : 传参,当function 被指定的时候,这个 data 就会传给 function 函数。

    2、定时器的初始化

    init_timer(struct timer_list * timer)

        参数为定义好的定时器 timer_list 结构体

    3、定时器增加

    void add_timer(struct timer_list *timer)

        定时器的增加。,其实就是将定时器假如到内核的定时器链表中,假如之后,才是真正开始计时。当定时器的时间到达的时候,就会去执行 function 函数指针指定的函数,

    4、删除计时器

    int del_timer(struct timer_list *timer);

       删除定时器,当然还存在同步的版本: del_timer_sync、。

    5、定时器的修改

    int mod_timer(struct timer_list *timer,unsigned long expires)

        修改定时器的延迟时间,被修改之后,会重新开始计时。

     

    2.2、内核的延迟

    1、短延迟

        内核提供了下面三个函数进行短延迟,分别是纳秒、微秒、毫秒延迟:

    void ndelay(unsigned long nsecs)

    void udelay(unsigned long usecs)

    void mdelay(unsigned long msecs)

        延迟,是让 CPU 进入了忙等待,这个时候, CPU 都给浪费了,所以不建议使用较长时间的延迟,优点是,这个时候这个延迟的时间是准的。

      内核也提供较长时间按的睡眠机制 ,使得 CPU 在等待的时间,可以去执行其他的进程:

    void msleep(unsigned int millisecs);    // 睡眠

    unsigned long msllep_interruptible(unsigned int millisecs)  //可以被中断的睡眠

    void ssleep(unsigned int seconds);   // 秒 睡眠

    2、长时间延迟

         长时间的延迟,可以通过指定的时间节拍与 jiffies 进行对比。

    unsigned long delay = jiffies + 100;   // 延迟 100 个节拍;

    while(time_before(jiffies,delay));

    delay = jiffies + 2 * HZ;   // 延迟两秒,HZ 是一秒钟的节拍

    while(time_before(jiffies,delay));

    #define time_before(a,b)    time_after(b,a)

    #define time_after(a,b)       
        (typecheck(unsigned long, a) &&
         typecheck(unsigned long, b) &&
         ((long)(b) - (long)(a) < 0))

    3、等待队列

        等待队列,一般是用在中断、定时器、进程同步下。一般是,当进程必须在等到(类似阻塞)某种事情发生,才进行后续的工作。阻塞的过程,是睡眠的过程,当等待的条件为真(资源获取到),就由内核唤醒进程。

        等待队列,是由一个“队列头”进行管理的,

    1、队列头初始化

    static DECLARE_WAIT_QUEUE_HEAD(queus_name);

        queus_name ,是队列的头名字,随意设置,

    2、进入睡眠

        进程在还没有等到某种条件,或者资源的时候,就进入睡眠的状态,

    wait_event(queus_name , condition)    // 不可中断

    wait_event_interruptible(queus_name , condition)  // 可以被中断

    queus_name  : 是等待队列头的名字,

    condition : 是一个值,一般是 bool 类型的,必须满足 condition 为真,否则继续睡眠

    3、唤醒

        进入睡眠状态的进行,需要在一定的位置进行唤醒,

    void wake_up(wait_queue_head_t *queue);  

    void wake_up_interruptible(wait_queue_head_t *queue);

        中断与不可中断不同的 API ,要看 进入睡眠的时候,使用哪个  API ,

  • 相关阅读:
    [CentOS7] 安装sogou输入法
    [CentOS7] vncviewer与windows之间的复制粘贴
    linux solr7.2+tomcat8 详细部署整合
    linux solr 安装
    linux dubbo-admin-2.6.0 环境搭建
    linux tomcat安装
    linux jdk安装
    linux Nginx-1.10.2 安装部署教程
    linux技巧---创建应用快捷方式
    linux MySQL 5.7+keepalived 主备服务器自主切换
  • 原文地址:https://www.cnblogs.com/qxj511/p/5462984.html
Copyright © 2020-2023  润新知