• Linux内核学习笔记五——中断推后处理机制


    一 中断

           硬件通过中断与操作系统进行通信,通过对硬件驱动程序处注册中断处理程序,快速响应硬件的中断。

    硬件中断优先级很高,打断当前正在执行的程序。有两种情况:

      硬件中断在中断处理程序中处理

      硬件中断延后再进行处理

      这个具体硬件相关,在中断处理程序中处理,打断了当前正在执行的程序;所有中断都将被屏蔽;如果占用时间太长不合适,

    造成系统交互性,反应能力都会受到影响。 需要在其中判断平衡:

           如果一个任务对时间非常敏感,将其放在中断处理程序中执行;

           如果一个人和和硬件相关,将其放在中断处理程序中执行;

           如果一个任务要保证不被其他中断打断,将其放在中断处理程序中执行;

           其余情况考虑延后机制中执行——下半部。

    二 中断推后执行机制—— 软中断

           软中断是在编译期间静态分配的,在程序执行前将软中断假如到表中。

    下面看一下这个过程:

    加入软中断类型:

           Linux3.5.3代码:

    enum
    {
           HI_SOFTIRQ=0,
    
           TIMER_SOFTIRQ,
    
           NET_TX_SOFTIRQ,
    
           NET_RX_SOFTIRQ,
    
           BLOCK_SOFTIRQ,
    
           BLOCK_IOPOLL_SOFTIRQ,
    
           TASKLET_SOFTIRQ,
    
           SCHED_SOFTIRQ,
    
           HRTIMER_SOFTIRQ,
    
           RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
     
           NR_SOFTIRQS
    
    };

     

    //软中断表:

    static struct softirq_action softirq_vec[NR_SOFTIRQS]

    //软中断结构体

    struct softirq_action
    {
           void   (*action)(struct softirq_action *);
    };

     

    注册软中断处理函数:

    void open_softirq(int nr, void (*action)(struct softirq_action *))
    {
           //关联表中对应类型
          softirq_vec[nr].action = action;
    }

     

    触发软中断:     

    void raise_softirq(unsigned int nr)
    {
           unsigned long flags;
    
           //停止但保存中断标志
           local_irq_save(flags);
    
           //将相应软中断挂起状态
           raise_softirq_irqoff(nr);
    
           //恢复中断
           local_irq_restore(flags);
    }

     

    执行软中断:   

    void irq_exit(void)
    {            
      invoke_softirq();  //do_softirq();
    }
    
    void __do_softirq(void)
    {
           struct softirq_action *h;
           __u32 pending;
           int max_restart = MAX_SOFTIRQ_RESTART;
           int cpu;
          
           //获取CPU软中断状态标志位 32位代表最多32个软中断
           pending = local_softirq_pending();
     
    restart:
           /* Reset the pending bitmask before enabling irqs */
           set_softirq_pending(0);
           local_irq_enable();
           h = softirq_vec;
           do {
                  //被触发则执行软中断处理程序
                  if (pending & 1) {
              h->action(h);
                  }
    
                  //下一个软中断
                  h++;
    
                  //下一个软中断状态标志位
                  pending >>= 1;
           } while (pending);
     
           local_irq_disable();
           pending = local_softirq_pending();
           if (pending && --max_restart)
                  goto restart;
    
           if (pending)
                  wakeup_softirqd();
           lockdep_softirq_exit();
        __local_bh_enable(SOFTIRQ_OFFSET);
    }

    软中断的基本结构如下图表示:

          

        

     

    三  中断推后执行机制——tasklet

           软中断中表中有一种类型是:TASKLET_SOFTIRQ

    Tasklet就是利用软中断实现中断推后处理机制。通常使用较多的是tasklet而不是软中断。

    Tasklet数据结构:   

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

    state:

    enum
    {
          TASKLET_STATE_SCHED,   /* Tasklet is scheduled for execution */
          TASKLET_STATE_RUN /* Tasklet is running (SMP only) */
    };

    count:为0允许激活执行

    声明tasklet:可以动态或者静态方式

           静态:

    #define DECLARE_TASKLET(name, func, data) \
    struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }
    
    
    #define DECLARE_TASKLET_DISABLED(name, func, data) \
    struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

           动态:    

    void tasklet_init(struct tasklet_struct *t,void (*func)(unsigned long), unsigned long data)
    {
           t->next = NULL;
           t->state = 0;
           atomic_set(&t->count, 0);
           t->func = func;
           t->data = data;
    }

      同时需要编写tasklet处理函数。

    调度tasklet:

           

    void tasklet_hi_schedule(struct tasklet_struct *t)
    {
           unsigned long flags;
           local_irq_save(flags);
           t->next = NULL;
           *__this_cpu_read(tasklet_vec.tail) = t;
           __this_cpu_write(tasklet_vec.tail, &(t->next));
           raise_softirq_irqoff(TASKLET_SOFTIRQ);
           local_irq_restore(flags);
    }

    执行tasklet处理程序:

           继续看上面调度tasklet程序执行:

          

     inline void raise_softirq_irqoff(unsigned int nr)
    {
           __raise_softirq_irqoff(nr);
           if (!in_interrupt())
                  wakeup_softirqd();
    }
    
    //使用ksoftirqd内核线程来处理
    static void wakeup_softirqd(void)
    {
           /* Interrupts are disabled: no need to stop preemption */
           struct task_struct *tsk = __this_cpu_read(ksoftirqd);
           if (tsk && tsk->state != TASK_RUNNING)
                  wake_up_process(tsk);
    }

    Ksoftirqd内核线程:

           软中断才被触发频率很高,在处理过程中还会重新触发软中断;执行会导致用户空间进程无法获得处理时间处于饥饿状态;

    对重新触发的软中断立即处理,会导致占据处理时间过长;不进行立即处理不合适;

    对此解决方法:

      l  只要还有被触发并等待处理和过程中重新触发的软中断的软中断,本次执行就要负责处理;软中断立即处理,用户空间得不到执行时间。

      l  不处理过程中触发的软中断,放到下一个中断执行时机时处理。软中断得不到立即处理,系统空闲时造成不合理;保证用户空间得到执行时间。

    两种方式有存在问题,只能在这其中采取这种的方式:

           内核使用线程处理软中断,线程优先级较低,可以被抢占;能够保证软中断被处理,也能保证用户空间程序得到执行时间。

           每个CPU上有存在这样一个线程:ksoftirqd/0或者ksoftirqd/1……

    static __init int spawn_ksoftirqd(void)
    {
           void *cpu = (void *)(long)smp_processor_id();
           int err = cpu_callback(&cpu_nfb, CPU_UP_PREPARE, cpu);
                  ……
           return 0;
    }
    
    early_initcall(spawn_ksoftirqd);
    static int __cpuinit cpu_callback(struct notifier_block *nfb,
                                  unsigned long action,
                                  void *hcpu)
    {
           int hotcpu = (unsigned long)hcpu;
           struct task_struct *p;
    
           switch (action) {
           case CPU_UP_PREPARE:
           case CPU_UP_PREPARE_FROZEN:
                  p = kthread_create_on_node(run_ksoftirqd,
                                          hcpu,
                                          cpu_to_node(hotcpu),
                                          "ksoftirqd/%d", hotcpu);
                  kthread_bind(p, hotcpu);
                per_cpu(ksoftirqd, hotcpu) = p;
                 break;
           ……
    }

     

    四 中断推后执行机制——工作队列

           工作队列(work queue)通过内核线程将中断下半部分程序推后执行到线程中执行,工作队列可以创建线程来处理相应任务。

           工作队列创建的线程为工作者线程:worker thread;系统提供默认的线程来处理工作者队列。

            

          

    工作者线程数据结构:

           

    struct workqueue_struct {
           unsigned int        flags;            /* W: WQ_* flags */
           union {
                  struct cpu_workqueue_struct __percpu       *pcpu;
                  struct cpu_workqueue_struct         *single;
                  unsigned long                           v;
           } cpu_wq;                          /* I: cwq's */
           struct list_head   list;        /* W: list of all workqueues */
           ……
    }

    CPU工作队列数据结构:

    struct cpu_workqueue_struct {
    //每个CPU工作队列信息 struct global_cwq *gcwq;
    //每个CPU工作队列 struct workqueue_struct *wq; …… };

    工作数据结构:

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

    声明工作队列:

           静态:

    #define DECLARE_WORK(n, f)                             \
           struct work_struct n = __WORK_INITIALIZER(n, f) 

           动态:

    #define INIT_WORK(_work, _func)                                   \
           do {                                           \
                  __INIT_WORK((_work), (_func), 0);              \
           } while (0)  

           需要编写工作队列处理函数:       

    typedef void (*work_func_t)(struct work_struct *work);

    调度工作队列:

    int schedule_work(struct work_struct *work)
    {
           return queue_work(system_wq, work);
    }
    int queue_work(struct workqueue_struct *wq, struct work_struct *work)
    {
          ret = queue_work_on(get_cpu(), wq, work);
    }

           唤醒工作者队列线程处理。

    执行工作者队列处理程序:

           

    static int worker_thread(void *__worker)
    {
           do {
                  struct work_struct *work =
                         list_first_entry(&gcwq->worklist,
                                       struct work_struct, entry);
                  process_one_work(worker, work);
           } while (keep_working(gcwq));
    
    }

    可以创建新的工作者队列和线程来处理。

    平衡是个很关键的问题!

     

     

  • 相关阅读:
    98. 验证二叉搜索树
    236. 二叉树的最近公共祖先
    leetcode 字符串转换整数 (atoi)
    LeetCode 寻找两个正序数组的中位数 (找第k个数的变种)
    Leetcode 面试题 16.18. 模式匹配(逻辑题)(转)
    深入学习Redis(4):哨兵(转)
    【BAT面试题系列】面试官:你了解乐观锁和悲观锁吗?(转)
    CentOS 7.5 使用 yum 方式安装 MySQL 5.7
    CentOS7 安装 PHP7 完全详细教程
    ubunutu 18.04 编译php7.4.1
  • 原文地址:https://www.cnblogs.com/bastard/p/2683771.html
Copyright © 2020-2023  润新知