本文介绍一个例子,linux软中断是谁触发谁执行,这有点各司其职的意思,可是到了触发软中断的时候往往已经丢失触发这个“触发软中断”事件的源头,因此这种各司其职不是那么完善,于是2.6.28在bio的完成通知中完善了它。
到了触发软中断的时候往往已经丢失触发这个“触发软中断”事件的源头,这句话什么意思呢,有点拗口,还好这不是一篇文学类的文章,我们没有必要咬文嚼字, 只要明白意思就可以了。在smp中考虑一种情况,一个进程运行在一个cpu上,该进程执行了一个读文件操作,该操作一步一步向低层推进,终于到了 block层进而接触到了磁盘驱动,到了硬件层cpu就管不着了,在计算机体系结构中,cpu和io设备实际上是平级的,平等的关系,于是这个执行读操作 的进程不得不等待在一个等待队列上,进程开始睡眠,到此为止该进程有百分之九十五以上的可能性还在那个cpu上运行,进程睡眠以后,磁盘操作交给了磁盘硬 件,操作中硬件通过中断来通知操作的执行情况,很显然操作执行完毕后也是通过中断来通知的,可是被中断的cpu还是执行读文件的进程所在的那个cpu吗? 这个就不能保证了,不是说保证这点有多难,而是保证了这一点后linux的模块之间的偶合性将大大加强,中断的路由是linux内核的一个完整的模块,没有必要和进程睡眠和smp进程迁移强加联系,于是这里被中断的cpu就有百分之五十的可能不是那个读文件进程所在的cpu,我们假定不是原先的那个 cpu。
我们知道io完成操作是通过软中断来执行的,完成操作也就是唤醒原始的进程,如果是被磁盘中断的cpu来触发io完成软中断,那么由linux软中断谁触发谁执行的原则,就应该由此被中断的cpu来执行io完成软中断,实际上就是在这个cpu上唤醒了在不同的cpu上睡眠的进程,如果我们看看 try_to_wake_up函数就会发现唤醒不同的cpu上的进程的开销很大,涉及到迁移,计数,负载均衡等细节,既然不能让进程的睡眠和唤醒于中断的路由耦合,那么能否用另外一种方式让软中断路由与进程的睡眠和唤醒耦合呢?答案是肯定的,我们只需要记住原始的睡眠的进程所在的cpu,就可以在硬件中断完毕后触发软中断的时刻将软中断路由到这个被记住的cpu上,这样的话,最终的操作就是一个软中断唤醒了睡眠在当前cpu上的进程,这个开销是很小的,直 接唤醒,负载均衡几乎是没有必要的。
了解到这点以后,代码就非常容易理解了,关键就是跟踪进程睡眠时所在的cpu编号,以往我们的操作是以睡眠->软中断->唤醒这个过程为中心 的,可是这个以静态过程为中心的结构很难在smp上扩展,它只在单cpu上串行处理的的时候十分有效,在smp上必须以数据为中心,简单看一下代码:

static void trigger_softirq(void *data)

{

       struct request *rq = data;

       unsigned long flags;

       struct list_head *list;

       local_irq_save(flags);

       list = &__get_cpu_var(blk_cpu_done);

       list_add_tail(&rq->csd.list, list);

       if (list->next == &rq->csd.list)

               raise_softirq_irqoff(BLOCK_SOFTIRQ);

       local_irq_restore(flags);

}

static int raise_blk_irq(int cpu, struct request *rq)

{

       if (cpu_online(cpu)) {  //只在特定的cpu上进行路由操作

               struct call_single_data *data = &rq->csd;

               data->func = trigger_softirq;

...

               __smp_call_function_single(cpu, data);  //smp路由函数执行的函数

               return 0;

       }

       return 1;

}

void blk_complete_request(struct request *req)

{

...

       local_irq_save(flags);

       cpu = smp_processor_id();

       group_cpu = blk_cpu_to_group(cpu);

       if (test_bit(QUEUE_FLAG_SAME_COMP, &q->queue_flags) && req->cpu != -1)

               ccpu = req->cpu;

       else

               ccpu = cpu;

       if (ccpu == cpu || ccpu == group_cpu) { //本cpu就是原始cpu

               struct list_head *list;

do_local:

               list = &__get_cpu_var(blk_cpu_done);

               list_add_tail(&req->csd.list, list);

               if (list->next == &req->csd.list)

                       raise_softirq_irqoff(BLOCK_SOFTIRQ); //直接在本cpu上触发软中断

       } else if (raise_blk_irq(ccpu, req))    //否则路由软中断

               goto do_local;

        local_irq_restore(flags);

}

void init_request_from_bio(struct request *req, struct bio *bio)

{

       req->cpu = bio->bi_comp_cpu;  //追踪了原始的cpu

...

}

bio_set_completion_cpu函数可以手工设定软中断的路由目的地:

static inline void bio_set_completion_cpu(struct bio *bio, unsigned int cpu)

{

       bio->bi_comp_cpu = cpu;

}

以 上就是简要的代码逻辑,实际上代码本身并不重要,重要的是思想,和上一篇文章一样,本文也是一个集中走向分离的例子,各司其职的另一种表达方式就是分工,在分布式大行其道的今天分工中隐含的意思就是协作。看看linux的新内核,都在向协作也就是各司其职的方向进化,以往串行的时代已经过去,再也不要指望将什么事情都交给一个中央机构了。

Logo

更多推荐