有自己的网站做淘宝联盟号做吗网页设计与制作教程psd格式
news/
2025/9/24 23:40:39/
文章来源:
有自己的网站做淘宝联盟号做吗,网页设计与制作教程psd格式,网站如何设计才大气,网站服务器维护 价目表前言 中断服务程序往往都是在CPU关中断的条件下执行的#xff0c;以避免中断嵌套而使控制复杂化。但是CPU关中断的时间不能太长#xff0c;否则容易丢失中断信号。为此#xff0c;Linux将中断服务程序一分为二#xff0c;各称作“Top Half”和“Bottom Half”。前者通常对…前言 中断服务程序往往都是在CPU关中断的条件下执行的以避免中断嵌套而使控制复杂化。但是CPU关中断的时间不能太长否则容易丢失中断信号。为此Linux将中断服务程序一分为二各称作“Top Half”和“Bottom Half”。前者通常对时间要求较为严格必须在中断请求发生后立即或至少在一定的时间限制内完成。因此为了保证这种处理能原子地完成Top Half通常是在CPU关中断的条件下执行的。具体地说Top Half的范围包括从在IDT中登记的中断入口函数一直到驱动程序注册在中断服务队列中的ISR。而Bottom Half则是Top Half根据需要来调度执行的这些操作允许延迟到稍后执行它的时间要求并不严格因此它通常是在CPU开中断的条件下执行的。 但是Linux的这种Bottom Half以下简称BH机制有两个缺点也即1在任意一时刻系统只能有一个CPU可以执行Bottom Half代码以防止两个或多个CPU同时来执行Bottom Half函数而相互干扰。因此BH代码的执行是严格“串行化”的。2BH函数不允许嵌套。 这两个缺点在单CPU系统中是无关紧要的但在SMP系统中却是非常致命的。因为BH机制的严格串行化执行显然没有充分利用SMP系统的多CPU特点。为此Linux2.4内核在BH机制的基础上进行了扩展这就是所谓的“软中断请求”softirq机制。 61 软中断请求机制 Linux的softirq机制是与SMP紧密不可分的。为此整个softirq机制的设计与实现中自始自终都贯彻了一个思想“谁触发谁执行”Who marksWho runs也即触发软中断的那个CPU负责执行它所触发的软中断而且每个CPU都由它自己的软中断触发与控制机制。这个设计思想也使得softirq机制充分利用了SMP系统的性能和特点。 611 软中断描述符 Linux在include/linux/interrupt.h头文件中定义了数据结构softirq_action来描述一个软中断请求如下所示 /* softirq mask and active fields moved to irq_cpustat_t in * asm/hardirq.h to get better cache usage. KAO */ struct softirq_action { void (*action)(struct softirq_action *); void *data; }; 其中函数指针action指向软中断请求的服务函数而指针data则指向由服务函数自行解释的数据。 基于上述软中断描述符Linux在kernel/softirq.c文件中定义了一个全局的softirq_vec[32]数组 static struct softirq_action softirq_vec[32] __cacheline_aligned; 在这里系统一共定义了32个软中断请求描述符。软中断向量i0≤i≤31所对应的软中断请求描述符就是softirq_veci。这个数组是个系统全局数组也即它被所有的CPU所共享。这里需要注意的一点是每个CPU虽然都由它自己的触发和控制机制并且只执行他自己所触发的软中断请求但是各个CPU所执行的软中断服务例程却是相同的也即都是执行softirq_vec数组中定义的软中断服务函数。 612 软中断触发机制 要实现“谁触发谁执行”的思想就必须为每个CPU都定义它自己的触发和控制变量。为此Linux在include/asm-i386/hardirq.h头文件中定义了数据结构irq_cpustat_t来描述一个CPU的中断统计信息其中就有用于触发和控制软中断的成员变量。数据结构irq_cpustat_t的定义如下 /* entry.S is sensitive to the offsets of these fields */ typedef struct { unsigned int __softirq_active; unsigned int __softirq_mask; unsigned int __local_irq_count; unsigned int __local_bh_count; unsigned int __syscall_count; unsigned int __nmi_count; /* arch dependent */ } ____cacheline_aligned irq_cpustat_t; 结构中每一个成员都是一个32位的无符号整数。其中__softirq_active和__softirq_mask就是用于触发和控制软中断的成员变量。 ①__softirq_active变量32位的无符号整数表示软中断向量031的状态。如果biti0≤i≤31为1则表示软中断向量i在某个CPU上已经被触发而处于active状态为0表示处于非活跃状态。 ②__softirq_mask变量32位的无符号整数软中断向量的屏蔽掩码。如果biti0≤i≤31为1则表示使能enable软中断向量i为0表示该软中断向量被禁止disabled。 根据系统中当前的CPU个数由宏NR_CPUS表示Linux在kernel/softirq.c文件中为每个CPU都定义了它自己的中断统计信息结构如下所示 /* No separate irq_stat for s390, it is part of PSA */ #if !defined(CONFIG_ARCH_S390) irq_cpustat_t irq_stat[NR_CPUS]; #endif /* CONFIG_ARCH_S390 */ 这样每个CPU都只操作它自己的中断统计信息结构。假设有一个编号为id的CPU那么它只能操作它自己的中断统计信息结构irq_statid0≤id≤NR_CPUS-1从而使各CPU之间互不影响。这个数组在include/linux/irq_cpustat.h头文件中也作了原型声明。 l 触发软中断请求的操作函数 函数__cpu_raise_softirq()用于在编号为cpu的处理器上触发软中断向量nr。它通过将相应的__softirq_active成员变量中的相应位设置为1来实现软中断触发。如下所示include/linux/interrupt.h static inline void __cpu_raise_softirq(int cpu, int nr) { softirq_active(cpu) | (1nr); } 为了保证“原子”性地完成软中断的触发过程Linux在interrupt.h头文件中对上述内联函数又作了高层封装也即函数raise_softirq()。该函数向下通过调用__cpu_raise_softirq()函数来实现软中断的触发但在调用该函数之前它先通过local_irq_save()函数来关闭当前CPU的中断并保存标志寄存器的内容如下所示 /* I do not want to use atomic variables now, so that cli/sti */ static inline void raise_softirq(int nr) { unsigned long flags; local_irq_save(flags); __cpu_raise_softirq(smp_processor_id(), nr); local_irq_restore(flags); } 613 Linux对软中断的预定义分类 在软中断向量031中Linux内核仅仅使用了软中断向量03其余被留待系统以后扩展。Linux在头文件include/linux/interrupt.h中对软中断向量03进行了预定义 /* PLEASE, avoid to allocate new softirqs, if you need not _really_ high frequency threaded job scheduling. For almost all the purposes tasklets are more than enough. F.e. all serial device BHs et al. should be converted to tasklets, not to softirqs. */ enum { HI_SOFTIRQ0, NET_TX_SOFTIRQ, NET_RX_SOFTIRQ, TASKLET_SOFTIRQ }; 其中软中断向量0即HI_SOFTIRQ用于实现高优先级的软中断如高优先级的tasklet将在后面详细描述。软中断向量1和2则分别用于网络数据的发送与接收。软中断向量3即TASKLET_SOFTIRQ则用于实现诸如tasklet这样的一般性软中断。关于tasklet我们将在后面详细描述。NOTELinix内核并不鼓励一般用户扩展使用剩余的软中断向量因为它认为其预定义的软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ已经足够应付绝大多数应用。 614 软中断机制的初始化 函数softirq_init()完成softirq机制的初始化。该函数由内核启动例程start_kernel()所调用。函数源码如下所示kernel/softirq.c void __init softirq_init() { int i; for (i0; i32; i) tasklet_init(bh_task_veci, bh_action, i); open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL); open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL); } 初始化的过程如下 1先用一个for循环来初始化用于实现BH机制的bh_task_vec32数组。这一点我们将在后面详细解释。 2调用open_softirq()函数开启使用软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ并将它们的软中断服务函数指针分别指向tasklet_action()函数和tasklet_hi_action函数。函数open_softirq()的主要作用是初始化设置软中断请求描述符softirq_vecnr。 615 开启一个指定的软中断向量 函数open_softirq()用于开启一个指定的软中断向量nr也即适当地初始化软中断向量nr所对应的软中断描述符softirq_vecnr。它主要做两件事情1初始化设置软中断向量nr所对应的软中断描述符softirq_vecnr。2将所有CPU的软中断屏蔽掩码变量__softirq_mask中的对应位设置为1以使能该软中断向量。该函数的源码如下所示kernel/softirq.c void open_softirq(int nr, void (*action)(struct softirq_action*), void *data) { unsigned long flags; int i; spin_lock_irqsave(softirq_mask_lock, flags); softirq_vec[nr].data data; softirq_vec[nr].action action; for (i0; iNR_CPUS; i) softirq_mask(i) | (1nr); spin_unlock_irqrestore(softirq_mask_lock, flags); } 616 软中断服务的执行函数do_softirq() 函数do_softirq()负责执行数组softirq_vec32中设置的软中断服务函数。每个CPU都是通过执行这个函数来执行软中断服务的。由于同一个CPU上的软中断服务例程不允许嵌套因此do_softirq()函数一开始就检查当前CPU是否已经正出在中断服务中如果是则do_softirq()函数立即返回。举个例子假设CPU0正在执行do_softirq()函数执行过程产生了一个高优先级的硬件中断于是CPU0转去执行这个高优先级中断所对应的中断服务程序。总所周知所有的中断服务程序最后都要跳转到do_IRQ()函数并由它来依次执行中断服务队列中的ISR这里我们假定这个高优先级中断的ISR请求触发了一次软中断于是do_IRQ()函数在退出之前看到有软中断请求从而调用do_softirq()函数来服务软中断请求。因此CPU0再次进入do_softirq()函数也即do_softirq()函数在CPU0上被重入了。但是在这一次进入do_softirq()函数时它马上发现CPU0此前已经处在中断服务状态中了因此这一次do_softirq()函数立即返回。于是CPU0回到该开始时的do_softirq()函数继续执行并为高优先级中断的ISR所触发的软中断请求补上一次服务。从这里可以看出do_softirq()函数在同一个CPU上的执行是串行的。 函数源码如下kernel/softirq.c asmlinkage void do_softirq() { int cpu smp_processor_id(); __u32 active, mask; if (in_interrupt()) return; local_bh_disable(); local_irq_disable(); mask softirq_mask(cpu); active softirq_active(cpu) mask; if (active) { struct softirq_action *h; restart: /* Reset active bitmask before enabling irqs */ softirq_active(cpu) ~active; local_irq_enable(); h softirq_vec; mask ~active; do { if (active 1) h-action(h); h; active 1; } while (active); local_irq_disable(); active softirq_active(cpu); if ((active mask) ! 0) goto retry; } local_bh_enable(); /* Leave with locally disabled hard irqs. It is critical to close * window for infinite recursion, while we help local bh count, * it protected us. Now we are defenceless. */ return; retry: goto restart; } 结合上述源码我们可以看出软中断服务的执行过程如下 1调用宏in_interrupt()来检测当前CPU此次是否已经处于中断服务中。该宏定义在hardirq.h请参见5.7节。 2调用local_bh_disable()宏将当前CPU的中断统计信息结构中的__local_bh_count成员变量加1表示当前CPU已经处在软中断服务状态。 3由于接下来要读写当前CPU的中断统计信息结构中的__softirq_active变量和__softirq_mask变量因此为了保证这一个操作过程的原子性先用local_irq_disable()宏实际上就是cli指令关闭当前CPU的中断。 4然后读当前CPU的__softirq_active变量值和__softirq_mask变量值。当某个软中断向量被触发时即__softirq_active变量中的相应位被置1只有__softirq_mask变量中的相应位也为1时它的软中断服务函数才能得到执行。因此需要将__softirq_active变量和__softirq_mask变量作一次“与”逻辑操作。 5如果active变量非0说明需要执行软中断服务函数。因此①先将当前CPU的__softirq_active中的相应位清零然后用local_irq_enable()宏实际上就是sti指令打开当前CPU的中断。②将局部变量mask中的相应位清零其目的是让do_softirq()函数的这一次执行不对同一个软中断向量上的再次软中断请求进行服务而是将它留待下一次do_softirq()执行时去服务从而使do_sottirq()函数避免陷入无休止的软中断服务中。③用一个do{}while循环来根据active的值去执行相应的软中断服务函数。④由于接下来又要检测当前CPU的__softirq_active变量因此再一次调用local_irq_disable()宏关闭当前CPU的中断。⑤读取当前CPU的__softirq_active变量的值并将它与局部变量mask进行与操作以看看是否又有其他软中断服务被触发了比如前面所说的那种情形。如果有的话那就跳转到entry程序段实际上是跳转到restart程序段重新执行软中断服务。如果没有的话那么此次软中断服务过程就宣告结束。 6最后通过local_bh_enable()宏将当前CPU的__local_bh_count变量值减1表示当前CPU已经离开软中断服务状态。宏local_bh_enable()也定义在include/asm-i386/softirq.h头文件中。 62 tasklet机制 Tasklet机制是一种较为特殊的软中断。Tasklet一词的原意是“小片任务”的意思这里是指一小段可执行的代码且通常以函数的形式出现。软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ均是用tasklet机制来实现的。 从某种程度上讲tasklet机制是Linux内核对BH机制的一种扩展。在2.4内核引入了softirq机制后原有的BH机制正是通过tasklet机制这个桥梁来纳入softirq机制的整体框架中的。正是由于这种历史的延伸关系使得tasklet机制与一般意义上的软中断有所不同而呈现出以下两个显著的特点 1. 与一般的软中断不同某一段tasklet代码在某个时刻只能在一个CPU上运行而不像一般的软中断服务函数即softirq_action结构中的action函数指针那样——在同一时刻可以被多个CPU并发地执行。 2. 与BH机制不同不同的tasklet代码在同一时刻可以在多个CPU上并发地执行而不像BH机制那样必须严格地串行化执行也即在同一时刻系统中只能有一个CPU执行BH函数。 621 tasklet描述符 Linux用数据结构tasklet_struct来描述一个tasklet。该数据结构定义在include/linux/interrupt.h头文件中。如下所示 struct tasklet_struct { struct tasklet_struct *next; unsigned long state; atomic_t count; void (*func)(unsigned long); unsigned long data; }; 各成员的含义如下 1next指针指向下一个tasklet的指针。 2state定义了这个tasklet的当前状态。这一个32位的无符号长整数当前只使用了bit1和bit0两个状态位。其中bit11表示这个tasklet当前正在某个CPU上被执行它仅对SMP系统才有意义其作用就是为了防止多个CPU同时执行一个tasklet的情形出现bit01表示这个tasklet已经被调度去等待执行了。对这两个状态位的宏定义如下所示interrupt.h enum { TASKLET_STATE_SCHED, /* Tasklet is scheduled for execution */ TASKLET_STATE_RUN /* Tasklet is running (SMP only) */ }; 3原子计数count对这个tasklet的引用计数值。NOTE只有当count等于0时tasklet代码段才能执行也即此时tasklet是被使能的如果count非零则这个tasklet是被禁止的。任何想要执行一个tasklet代码段的人都首先必须先检查其count成员是否为0。 4函数指针func指向以函数形式表现的可执行tasklet代码段。 5data函数func的参数。这是一个32位的无符号整数其具体含义可供func函数自行解释比如将其解释成一个指向某个用户自定义数据结构的地址值。 Linux在interrupt.h头文件中又定义了两个用来定义tasklet_struct结构变量的辅助宏 #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 } 显然从上述源代码可以看出用DECLARE_TASKLET宏定义的tasklet在初始化时是被使能的enabled因为其count成员为0。而用DECLARE_TASKLET_DISABLED宏定义的tasklet在初始时是被禁止的disabled因为其count等于1。 622 改变一个tasklet状态的操作 在这里tasklet状态指两个方面1state成员所表示的运行状态2count成员决定的使能禁止状态。 1改变一个tasklet的运行状态 state成员中的bit0表示一个tasklet是否已被调度去等待执行bit1表示一个tasklet是否正在某个CPU上执行。对于state变量中某位的改变必须是一个原子操作因此可以用定义在include/asm/bitops.h头文件中的位操作来进行。 由于bit1这一位即TASKLET_STATE_RUN仅仅对于SMP系统才有意义因此Linux在Interrupt.h头文件中显示地定义了对TASKLET_STATE_RUN位的操作。如下所示 #ifdef CONFIG_SMP #define tasklet_trylock(t) (!test_and_set_bit(TASKLET_STATE_RUN, (t)-state)) #define tasklet_unlock_wait(t) while (test_bit(TASKLET_STATE_RUN, (t)-state)) { /* NOTHING */ } #define tasklet_unlock(t) clear_bit(TASKLET_STATE_RUN, (t)-state) #else #define tasklet_trylock(t) 1 #define tasklet_unlock_wait(t) do { } while (0) #define tasklet_unlock(t) do { } while (0) #endif 显然在SMP系统同tasklet_trylock()宏将把一个tasklet_struct结构变量中的state成员中的bit1位设置成1同时还返回bit1位的非。因此如果bit1位原有值为1表示另外一个CPU正在执行这个tasklet代码那么tasklet_trylock()宏将返回值0也就表示上锁不成功。如果bit1位的原有值为0那么tasklet_trylock()宏将返回值1表示加锁成功。而在单CPU系统中tasklet_trylock()宏总是返回为1。 任何想要执行某个tasklet代码的程序都必须首先调用宏tasklet_trylock()来试图对这个tasklet进行上锁即设置TASKLET_STATE_RUN位且只能在上锁成功的情况下才能执行这个tasklet。建议即使你的程序只在CPU系统上运行你也要在执行tasklet之前调用tasklet_trylock()宏以便使你的代码获得良好可移植性。 在SMP系统中tasklet_unlock_wait()宏将一直不停地测试TASKLET_STATE_RUN位的值直到该位的值变为0即一直等待到解锁假如CPU0正在执行tasklet A的代码在此期间CPU1也想执行tasklet A的代码但CPU1发现tasklet A的TASKLET_STATE_RUN位为1于是它就可以通过tasklet_unlock_wait()宏等待tasklet A被解锁也即TASKLET_STATE_RUN位被清零。在单CPU系统中这是一个空操作。 宏tasklet_unlock()用来对一个tasklet进行解锁操作也即将TASKLET_STATE_RUN位清零。在单CPU系统中这是一个空操作。 2使能禁止一个tasklet 使能与禁止操作往往总是成对地被调用的tasklet_disable()函数如下interrupt.h static inline void tasklet_disable(struct tasklet_struct *t) { tasklet_disable_nosync(t); tasklet_unlock_wait(t); } 函数tasklet_disable_nosync()也是一个静态inline函数它简单地通过原子操作将count成员变量的值减1。如下所示interrupt.h static inline void tasklet_disable_nosync(struct tasklet_struct *t) { atomic_inc(t-count); } 函数tasklet_enable()用于使能一个tasklet如下所示interrupt.h static inline void tasklet_enable(struct tasklet_struct *t) { atomic_dec(t-count); } 623 tasklet描述符的初始化与杀死 函数tasklet_init()用来初始化一个指定的tasklet描述符其源码如下所示kernel/softirq.c void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long), unsigned long data) { t-func func; t-data data; t-state 0; atomic_set(t-count, 0); } 函数tasklet_kill()用来将一个已经被调度了的tasklet杀死即将其恢复到未调度的状态。其源码如下所示kernel/softirq.c void tasklet_kill(struct tasklet_struct *t) { if (in_interrupt()) printk(Attempt to kill tasklet from interrupt\n); while (test_and_set_bit(TASKLET_STATE_SCHED, t-state)) { current-state TASK_RUNNING; do { current-policy | SCHED_YIELD; schedule(); } while (test_bit(TASKLET_STATE_SCHED, t-state)); } tasklet_unlock_wait(t); clear_bit(TASKLET_STATE_SCHED, t-state); } 624 tasklet对列 多个tasklet可以通过tasklet描述符中的next成员指针链接成一个单向对列。为此Linux专门在头文件include/linux/interrupt.h中定义了数据结构tasklet_head来描述一个tasklet对列的头部指针。如下所示 struct tasklet_head { struct tasklet_struct *list; } __attribute__ ((__aligned__(SMP_CACHE_BYTES))); 尽管tasklet机制是特定于软中断向量HI_SOFTIRQ和TASKLET_SOFTIRQ的一种实现但是tasklet机制仍然属于softirq机制的整体框架范围内的因此它的设计与实现仍然必须坚持“谁触发谁执行”的思想。为此Linux为系统中的每一个CPU都定义了一个tasklet对列头部来表示应该有各个CPU负责执行的tasklet对列。如下所示kernel/softirq.c struct tasklet_head tasklet_vec[NR_CPUS] __cacheline_aligned; struct tasklet_head tasklet_hi_vec[NR_CPUS] __cacheline_aligned; 其中tasklet_vec数组用于软中断向量TASKLET_SOFTIRQ而tasklet_hi_vec数组则用于软中断向量HI_SOFTIRQ。也即如果CPUi0≤i≤NR_CPUS-1触发了软中断向量TASKLET_SOFTIRQ那么对列tasklet_veci中的每一个tasklet都将在CPUi服务于软中断向量TASKLET_SOFTIRQ时被CPUi所执行。同样地如果CPUi0≤i≤NR_CPUS-1触发了软中断向量HI_SOFTIRQ那么队列tasklet_veci中的每一个tasklet都将CPUi在对软中断向量HI_SOFTIRQ进行服务时被CPUi所执行。 队列tasklet_vecI和tasklet_hi_vecI中的各个tasklet是怎样被所CPUi所执行的呢其关键就是软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ的软中断服务程序——tasklet_action()函数和tasklet_hi_action()函数。下面我们就来分析这两个函数。 625 软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ Linux为软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ实现了专用的触发函数和软中断服务函数。其中tasklet_schedule()函数和tasklet_hi_schedule()函数分别用来在当前CPU上触发软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ并把指定的tasklet加入当前CPU所对应的tasklet队列中去等待执行。而tasklet_action()函数和tasklet_hi_action()函数则分别是软中断向量TASKLET_SOFTIRQ和HI_SOFTIRQ的软中断服务函数。在初始化函数softirq_init()中这两个软中断向量对应的描述符softirq_vec0和softirq_vec3中的action函数指针就被分别初始化成指向函数tasklet_hi_action()和函数tasklet_action。 1软中断向量TASKLET_SOFTIRQ的触发函数tasklet_schedule 该函数实现在include/linux/interrupt.h头文件中是一个inline函数。其源码如下所示 static inline void tasklet_schedule(struct tasklet_struct *t) { if (!test_and_set_bit(TASKLET_STATE_SCHED, t-state)) { int cpu smp_processor_id(); unsigned long flags; local_irq_save(flags); t-next tasklet_vec[cpu].list; tasklet_vec[cpu].list t; __cpu_raise_softirq(cpu, TASKLET_SOFTIRQ); local_irq_restore(flags); } } 该函数的参数t指向要在当前CPU上被执行的tasklet。对该函数的NOTE如下 ①调用test_and_set_bit()函数将待调度的tasklet的state成员变量的bit0位也即TASKLET_STATE_SCHED位设置为1该函数同时还返回TASKLET_STATE_SCHED位的原有值。因此如果bit0为的原有值已经为1那就说明这个tasklet已经被调度到另一个CPU上去等待执行了。由于一个tasklet在某一个时刻只能由一个CPU来执行因此tasklet_schedule()函数什么也不做就直接返回了。否则就继续下面的调度操作。 ②首先调用local_irq_save()函数来关闭当前CPU的中断以保证下面的步骤在当前CPU上原子地被执行。 ③然后将待调度的tasklet添加到当前CPU对应的tasklet队列的首部。 ④接着调用__cpu_raise_softirq()函数在当前CPU上触发软中断请求TASKLET_SOFTIRQ。 ⑤最后调用local_irq_restore()函数来开当前CPU的中断。 2软中断向量TASKLET_SOFTIRQ的服务程序tasklet_action 函数tasklet_action()是tasklet机制与软中断向量TASKLET_SOFTIRQ的联系纽带。正是该函数将当前CPU的tasklet队列中的各个tasklet放到当前CPU上来执行的。该函数实现在kernel/softirq.c文件中其源代码如下 static void tasklet_action(struct softirq_action *a) { int cpu smp_processor_id(); struct tasklet_struct *list; local_irq_disable(); list tasklet_vec[cpu].list; tasklet_vec[cpu].list NULL; local_irq_enable(); while (list ! NULL) { struct tasklet_struct *t list; list list-next; if (tasklet_trylock(t)) { if (atomic_read(t-count) 0) { clear_bit(TASKLET_STATE_SCHED, t-state); t-func(t-data); /* * talklet_trylock() uses test_and_set_bit that imply * an mb when it returns zero, thus we need the explicit * mb only here: while closing the critical section. */ #ifdef CONFIG_SMP smp_mb__before_clear_bit(); #endif tasklet_unlock(t); continue; } tasklet_unlock(t); } local_irq_disable(); t-next tasklet_vec[cpu].list; tasklet_vec[cpu].list t; __cpu_raise_softirq(cpu, TASKLET_SOFTIRQ); local_irq_enable(); } } 注释如下 ①首先在当前CPU关中断的情况下“原子”地读取当前CPU的tasklet队列头部指针将其保存到局部变量list指针中然后将当前CPU的tasklet队列头部指针设置为NULL以表示理论上当前CPU将不再有tasklet需要执行但最后的实际结果却并不一定如此下面将会看到。 ②然后用一个while{}循环来遍历由list所指向的tasklet队列队列中的各个元素就是将在当前CPU上执行的tasklet。循环体的执行步骤如下 l 用指针t来表示当前队列元素即当前需要执行的tasklet。 l 更新list指针为list-next使它指向下一个要执行的tasklet。 l 用tasklet_trylock()宏试图对当前要执行的tasklet由指针t所指向进行加锁如果加锁成功当前没有任何其他CPU正在执行这个tasklet则用原子读函数atomic_read()进一步判断count成员的值。如果count为0说明这个tasklet是允许执行的于是1先清除TASKLET_STATE_SCHED位2然后调用这个tasklet的可执行函数func3执行barrier()操作4调用宏tasklet_unlock()来清除TASKLET_STATE_RUN位。5最后执行continue语句跳过下面的步骤回到while循环继续遍历队列中的下一个元素。如果count不为0说明这个tasklet是禁止运行的于是调用tasklet_unlock()清除前面用tasklet_trylock()设置的TASKLET_STATE_RUN位。 l 如果tasklet_trylock()加锁不成功或者因为当前tasklet的count值非0而不允许执行时我们必须将这个tasklet重新放回到当前CPU的tasklet队列中以留待这个CPU下次服务软中断向量TASKLET_SOFTIRQ时再执行。为此进行这样几步操作1先关CPU中断以保证下面操作的原子性。2把这个tasklet重新放回到当前CPU的tasklet队列的首部3调用__cpu_raise_softirq()函数在当前CPU上再触发一次软中断请求TASKLET_SOFTIRQ4开中断。 l 最后回到while循环继续遍历队列。 3软中断向量HI_SOFTIRQ的触发函数tasklet_hi_schedule() 该函数与tasklet_schedule()几乎相同其源码如下include/linux/interrupt.h static inline void tasklet_hi_schedule(struct tasklet_struct *t) { if (!test_and_set_bit(TASKLET_STATE_SCHED, t-state)) { int cpu smp_processor_id(); unsigned long flags; local_irq_save(flags); t-next tasklet_hi_vec[cpu].list; tasklet_hi_vec[cpu].list t; __cpu_raise_softirq(cpu, HI_SOFTIRQ); local_irq_restore(flags); } } 4软中断向量HI_SOFTIRQ的服务函数tasklet_hi_action 该函数与tasklet_action()函数几乎相同其源码如下kernel/softirq.c static void tasklet_hi_action(struct softirq_action *a) { int cpu smp_processor_id(); struct tasklet_struct *list; local_irq_disable(); list tasklet_hi_vec[cpu].list; tasklet_hi_vec[cpu].list NULL; local_irq_enable(); while (list ! NULL) { struct tasklet_struct *t list; list list-next; if (tasklet_trylock(t)) { if (atomic_read(t-count) 0) { clear_bit(TASKLET_STATE_SCHED, t-state); t-func(t-data); tasklet_unlock(t); continue; } tasklet_unlock(t); } local_irq_disable(); t-next tasklet_hi_vec[cpu].list; tasklet_hi_vec[cpu].list t; __cpu_raise_softirq(cpu, HI_SOFTIRQ); local_irq_enable(); } } 3 Bottom Half机制 Bottom Half机制在新的softirq机制中被保留下来并作为softirq框架的一部分。其实现也似乎更为复杂些因为它是通过tasklet机制这个中介桥梁来纳入softirq框架中的。实际上软中断向量HI_SOFTIRQ是内核专用于执行BH函数的。 631 数据结构的定义 原有的32个BH函数指针被保留定义在kernel/softirq.c文件中 static void (*bh_base[32])(void); 但是每个BH函数都对应有一个tasklet并由tasklet的可执行函数func来负责调用相应的bh函数func函数的参数指定调用哪一个BH函数。与32个BH函数指针相对应的tasklet的定义如下所示kernel/softirq.c struct tasklet_struct bh_task_vec[32]; 上述tasklet数组使系统全局的它对所有的CPU均可见。由于在某一个时刻只能有一个CPU在执行BH函数因此定义一个全局的自旋锁来保护BH函数如下所示kernel/softirq.c spinlock_t global_bh_lock SPIN_LOCK_UNLOCKED; 632 初始化 在softirq机制的初始化函数softirq_init()中将bh_task_vec32数组中的每一个tasklet中的func函数指针都设置为指向同一个函数bh_action而data成员也即func函数的调用参数则被设置成该tasklet在数组中的索引值如下所示 void __init softirq_init() { …… for (i0; i32; i) tasklet_init(bh_task_veci, bh_action, i); …… } 因此bh_action()函数将负责相应地调用参数所指定的bh函数。该函数是连接tasklet机制与Bottom Half机制的关键所在。 623 bh_action()函数 该函数的源码如下kernel/softirq.c static void bh_action(unsigned long nr) { int cpu smp_processor_id(); if (!spin_trylock(global_bh_lock)) goto resched; if (!hardirq_trylock(cpu)) goto resched_unlock; if (bh_base[nr]) bh_base[nr](); hardirq_endlock(cpu); spin_unlock(global_bh_lock); return; resched_unlock: spin_unlock(global_bh_lock); resched: mark_bh(nr); } 对该函数的注释如下 ①首先调用spin_trylock()函数试图对自旋锁global_bh_lock进行加锁同时该函数还将返回自旋锁global_bh_lock的原有值的非。因此如果global_bh_lock已被某个CPU上锁而为非0值那个CPU肯定在执行某个BH函数那么spin_trylock()将返回为0表示上锁失败在这种情况下当前CPU是不能执行BH函数的因为另一个CPU正在执行BH函数于是执行goto语句跳转到resched程序段以便在当前CPU上再一次调度该BH函数。 ②调用hardirq_trylock()函数锁定当前CPU确保当前CPU不是处于硬件中断请求服务中如果锁定失败跳转到resched_unlock程序段以便先对global_bh_lock解锁在重新调度一次该BH函数。 ③此时我们已经可以放心地在当前CPU上执行BH函数了。当然对应的BH函数指针bh_basenr必须有效才行。 ④从BH函数返回后先调用hardirq_endlock()函数实际上它什么也不干调用它只是为了保此加、解锁的成对关系然后解除自旋锁global_bh_lock最后函数就可以返回了。 ⑤resched_unlock程序段先解除自旋锁global_bh_lock然后执行reched程序段。 ⑥resched程序段当某个CPU正在执行BH函数时当前CPU就不能通过bh_action函数来调用执行任何BH函数所以就通过调用mark_bh()函数在当前CPU上再重新调度一次以便将这个BH函数留待下次软中断服务时执行。 634 Bottom Half的原有接口函数 1init_bh()函数 该函数用来在bh_base数组登记一个指定的bh函数如下所示kernel/softirq.c void init_bh(int nr, void (*routine)(void)) { bh_base[nr] routine; mb(); } 2remove_bh()函数 该函数用来在bh_base数组中注销指定的函数指针同时将相对应的tasklet杀掉。如下所示kernel/softirq.c void remove_bh(int nr) { tasklet_kill(bh_task_vecnr); bh_base[nr] NULL; } 3mark_bh()函数 该函数用来向当前CPU标记由一个BH函数等待去执行。它实际上通过调用tasklet_hi_schedule()函数将相应的tasklet加入到当前CPU的tasklet队列tasklet_hi_veccpu中然后触发软中断请求HI_SOFTIRQ如下所示include/linux/interrupt.h static inline void mark_bh(int nr) { tasklet_hi_schedule(bh_task_vecnr); } 635 预定义的BH函数 在32个BH函数指针中大多数已经固定用于一些常见的外设比如第0个BH函数就固定地用于时钟中断。Linux在头文件include/linux/interrupt.h中定义了这些已经被使用的BH函数所引如下所示 enum { TIMER_BH 0, TQUEUE_BH, DIGI_BH, SERIAL_BH, RISCOM8_BH, SPECIALIX_BH, AURORA_BH, ESP_BH, SCSI_BH, IMMEDIATE_BH, CYCLADES_BH, CM206_BH, JS_BH, MACSERIAL_BH, ISICOM_BH }; 64 任务队列Task Queue 任务队列是与Bottom Half机制紧密相连的。因为Bottom Half机制只有有限的32个函数指针而且大部分都已被系统预定义使用所以早期版本的Linux内核为了扩展Bottom Half机制就设计了任务队列机制。 所谓任务队列就是指以双向队列形式连接起来的任务链表每一个链表元数都描述了一个可执行的任务以函数的形式表现。如下图所示 任务队列机制实现在include/linux/tqueue.h头文件中。 641 数据结构的定义 Linux用数据结构tq_struct来描述任务队列中的每一个链表元数即一个可执行的任务 struct tq_struct { struct list_head list; /* linked list of active bhs */ unsigned long sync; /* must be initialized to zero */ void (*routine)(void *); /* function to call */ void *data; /* argument to function */ }; 这个数据结构很简单在此就不详述。 然后Linux定义了数据结构task_queue来描述任务队列的头部其实task_queue就是结构类型list_head如下 typedef struct list_head task_queue; 但是Linux又定义了一个宏DECLARE_TASK_QUEUE()来辅助我们更方便地定义任务队列的链表表头 #define DECLARE_TASK_QUEUE(q) LIST_HEAD(q) 一个任务队列是否处于active状态主要取决于其链表表头即task_queue结构是否为空因此Linux定义宏TQ_ACTIVE来判断一个任务队列是否有效 #define TQ_ACTIVE(q) (!list_empty(q)) 显然只要任务队列表头q不为空该任务队列就是有效的。 642 向任务队列中插入一个新任务 1保护自旋锁 由于任务队列是系统全局的共享资源所以面临竞争的问题。为了实现对任务队列链表的互斥访问Linux在kernel/timer.c文件中定义了一个任务队列保护自旋锁tqueue_lock如下 spinlock_t tqueue_lock SPIN_LOCK_UNLOCKED; 该自旋锁在tqueue.h头文件中也有原型声明 extern spinlock_t tqueue_lock; 任何想要访问任务队列的代码都首先必须先持有该自旋锁。 2queue_task函数 实现在tqueue.h头文件中的内联函数queue_task()用来将一个指定的任务添加到某指定的任务队列的尾部如下 /* * Queue a task on a tq. Return non-zero if it was successfully * added. */ static inline int queue_task(struct tq_struct *bh_pointer, task_queue *bh_list) { int ret 0; if (!test_and_set_bit(0,bh_pointer-sync)) { unsigned long flags; spin_lock_irqsave(tqueue_lock, flags); list_add_tail(bh_pointer-list, bh_list); spin_unlock_irqrestore(tqueue_lock, flags); ret 1; } return ret; } 643 运行任务队列 函数run_task_queue()用于实现指定的任务队列。它只有一个参数指针list——指向待运行的任务队列头部task_queue结构变量。该函数实现在tqueue.h头文件中 static inline void run_task_queue(task_queue *list) { if (TQ_ACTIVE(*list)) __run_task_queue(list); } 显然函数首先调用宏TQ_ACTIVE()来判断参数list指定的待运行任务队列是否为空。如果不为空则调用__run_task_queue()函数来实际运行这个有效的任务队列。 函数__run_task_queue()实现在kernel/softirq.c文件中。该函数将依次遍历任务队列中的每一个元数并调用执行每一个元数的可执行函数。其源码如下 void __run_task_queue(task_queue *list) { struct list_head head, *next; unsigned long flags; spin_lock_irqsave(tqueue_lock, flags); list_add(head, list); list_del_init(list); spin_unlock_irqrestore(tqueue_lock, flags); next head.next; while (next ! head) { void (*f) (void *); struct tq_struct *p; void *data; p list_entry(next, struct tq_struct, list); next next-next; f p-routine; data p-data; wmb(); p-sync 0; if (f) f(data); } } 对该函数的注释如下 1首先用一个局部的表头head来代替参数list所指向的表头。这是因为在__run_task_queue函数的运行期间可能还会有新的任务加入到list任务队列中来但是__run_task_queue()函数显然不想陷入无休止的不断增加的任务处理中因此它用局部的表头head来代替参数list所指向的表头以使要执行的任务个数固定化。为此①先对全局的自旋锁tqueue_lock进行加锁以实现对任务队列的互斥访问②将局部的表头head加在表头list和第一个元数之间。③将list表头从队列中去除并将其初始化为空。④解除自旋锁tqueue_lock。 2接下来用一个while循环来遍历整个队列head并调用执行每一个队列元素中的函数。注意任务队列是一个双向循环队列。 644 内核预定义的任务队列 Bottom Half机制与任务队列是紧密相连的。大多数BH函数都是通过调用run_task_queue()函数来执行某个预定义好的任务队列。最常见的内核预定义任务队列有 l tq_timer对应于TQUEUE_BH。 l tq_immediate对应于IMMEDIATE_BH。 l tq_disk用于块设备任务。 任务队列tq_timer和tq_immediate都定义在kernel/timer.c文件中如下所示 DECLARE_TASK_QUEUE(tq_timer); DECLARE_TASK_QUEUE(tq_immediate); BH向量TQUEUE_BH和IMMEDIATE_BH的BH函数分别是queue_bh函数和immediate_bh()函数它们都仅仅是简单地调用run_task_queue()函数来分别运行任务队列tq_timer和tq_immediate如下所示kernel/timer.c void tqueue_bh(void) { run_task_queue(tq_timer); } void immediate_bh(void) { run_task_queue(tq_immediate); }
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/916405.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!