搜尋此網誌

2011年7月24日 星期日

study kernel(2) -- sync, interrupt

三、 内核同步
1. 原子操作
linux提供了一系列C语言函数来实现内核中的原子操作,这些函数又分为两类,分别针对bit变量和整数变量进行原子操作。它们的共同点是在任何情况下都是原子的,内核代码可以安全的调用它们而不被中断,而且它们都依赖底层CPU的原子操作来实现-所有这些函数都是CPU架构相关的。其实现在atomic.h和bitops.h中
2. 禁止中断
CPU具备屏蔽中断和打开中断的功能,这项功能可以保证正在执行的内核控制路径不被中断处理程序所抢占,防止某些竞争条件的发生。Linux提供了local_irq_disable()/local_irq_enable(),local_irq_save()/local_irq_restone()来实现屏蔽/开启中断的功能。
另外,内核提供的另外一些函数隐含的调用了屏蔽中断的功能,如spin_lock_irq()。中断机制对于内核的正常运行是非常重要的,因为异步I/O,进程调度等很多重要操作都依赖于中断,而在屏蔽中断期间所有的中断都无法得到处理,而必须延迟到解除屏蔽以后。所以长时间的屏蔽中断是很危险的,有可能造成数据丢失乃至系统崩溃等后果。这就要求在屏蔽了中断之后,当前的内核控制路径应当尽快的执行完临界区的代码,然后解除对中断的屏蔽。尤其需要注意的是,屏蔽中断后的内核控制路径不能调用有可能引起阻塞的函数,因为这些函数会使进程睡眠,然后主动调用schedule()来放弃CPU。
3. 自旋锁
自旋锁在试图加锁的时候,如果当前锁已经处于锁定状态,加锁进程就进行"旋转",用一个死循环测试锁的状态,直到成功的取得锁。自旋锁的这种特性避免了调用进程的挂起,用"旋转"来取代进程切换。而上下文切换需要一定时间,并且会使高速缓冲失效,对系统性能影响是很大的,所以自旋锁在多处理器环境中非常方便。当然,被自旋锁所保护的"临界代码"一般都比较短,否则就会浪费过多的CPU资源。
当一个内核控制路径与中断处理程序之间存在竞争条件时,需要使用中断安全版本的自旋锁spin_lock_irq()/spin_lock_irqsave(),这个函数首先禁止本地CPU上的中断,然后试图获得自旋锁,这样在执行临界区代码的时候,本地CPU不会被中断处理程序所抢占。同时,由于禁止的只是本地CPU上的中断,系统中其它CPU仍然可以对外部中断进程处理。
自旋锁是互斥的,同一时刻只能有一个内核控制路径拥有自旋锁。
4. 信号量
信号量为了解决多个内核控制路径竞争资源的问题,这些内核控制路径可能是单处理器系统中分时执行的控制路径,也可能是多处理器系统中的并行执行的控制路径。在试图获得信号量的时候,如果信号量繁忙,相应的内核控制路径会挂起,直到信号量被释放的时候控制路径才恢复运行。使用信号量的时候,先声明一个semaphore类型的对象,然后调用函数sema_init()来初始化它。要想获得信号量的掌握权,需要调用函数down_interruptible()/down(),此时如果信号量忙,调用进程被挂起,加入到信号量的等待队列中去;释放信号量的时候则调用up()函数,系统同时会唤醒正在等待信号量的进程,唤醒的顺序与等待的顺序是一致的。
注:中断上下文只能用自旋锁,而任务睡眠时只能使用信号量。
四、 中断
中断由硬件设备生成,再由中断控制器向处理器发送相应信号。处理器检测到此信号,便中断自己的当前工作转而处理中断。此后处理器会通知系统已经产生中断,操作系统就可以对这个中断进行适当的处理。
IRQs(Interrupt ReQuests):外设引起的中断应该被称为中断请求。中断请求号和中断号中间有一定的映射关系。这个映射关系和平台有关。
1. 中断处理的层次
中断的嵌套会带来很多问题,在中断处理函数重屏蔽其他中断源可以解决防止中断嵌套,但是长时间地屏蔽中断会导致内核遗漏掉一些重要的中断。 因此,中断处理函数重中断的屏蔽时间应该越短越好。
中断处理函数应满足下面两个要求:
a. 代码量应越少越好,以便中断函数的快速执行。
b. 如果中断处理函数也可以并发,那么他们之间不得互相影响。
上述两个条件中,后者可以通过优化中断处理函数的设计和编码来实现,但想实现前者,则不那么容易。 但实际上,并非中断处理程序的所有工作的重要性都是相同的,通常来讲,一个中断服务程序可根据其是否需要马上处理而分为三个部分:
Critical actions:
这个部分必须要在中断发上后马上执行,否则将无法保证系统的稳定性或者数据操作的正确性。 例如,当网卡上有数据包到达后,内核必须马上将数据从网卡挪到内存中,如果这个动作没有在中断发生后马上执行,该数据可能会被后续接收到的数据覆盖。 在进行这个 critical actions 的时候,其他的中断必须被关掉。
Noncritical actions:
这些actions相比前面的critical actions重要性稍低,但是也应该尽快完成。 不同的是,它可以在打开其他中断的前提下进行,因此可能会被其他的中断所"中断"(受其他的中断影响)。
Deferrable actions:
Deferrable actions 的重要性比前两者都低,可以不用放在中断处理函数中进行。内核可以等到没什么事情的时候再处理它。
对中断处理函数的划分,并根据各个部分的不同的重要性,可以将中断函数拆成若干不同的片段,放在不同的上下文中执行, 很大程度上,在保证了系统事件完整性的前提下,防止了中断的嵌套。
2. 中断上半部
a) 中断的处理
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
[0 ... NR_IRQS-1] = {
.handle_irq = handle_bad_irq,
.depth = 1,
.lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
}
};
中断处理函数do_IRQ()根据中断号被存放到irq_desc数组中;从irq_desc中,可以依据中断号,找到相应的中断处理程序。
struct irq_desc用于描述一个IRQ,其中:
handler_data: 指向一些与IRQ处理函数相关的数据。当中断发生时,handle_irq被调用。
action: 这个数据结构提供了当中断发生后需要执行的中断处理程序。
b) 中断处理程序的注册
static inline int __must_checkrequest_irq (unsigned int irq, irq_handler_t handler,
unsigned long flags, const char *name, void *dev)
{
return request_threaded_irq(irq, handler, NULL, flags, name, dev);
}

request_threaded_irq首先从内核中获取了这个irq对应的irq_desc,然后创建结构体irqaction,设置action的handler, flags, name, dev_id。然后调用__setup_irq完成后续工作。
Flags标志:
SA_INTERRUPT:执行中断处理程序时屏蔽所有中断线。而默认没有这个标志时,除了正在运行的中断处理程序对应的中断线被屏蔽外,其他所有中断都是激活的。
SA_SAMPLE_RANDOM: 内核通过一系列的事件的相关信息来生成随机数,如果有这个标志,则调用rand_initialize_irq将IRQ添加到熵池所需的相关数据结构中。
SA_SHIRQ:可以在多个中断处理程序之间共享中断线。
3. 中断下半部
下半部的任务就是执行于中断处理密切相关但中断处理程序本身不执行的工作。提供如下几种机制:
c) 软中断
i. 注册软中断处理程序
软中断项:
struct softirq_action{
void (*action) (struct softirq_action *); //软中断处理程序
void *data;
}
软中断向量表:
static struct softirq_action softirq_vec[NR_SOFTIRQS];
函数open_softirq()通过软中断索引号注册软中断处理函数

ii. 触发/禁止软中断
函数raise_softirq()/raise_softirq_irqoff()通过软中断索引号修改软中断状态寄存器irq_stat

iii. 软中断守护线程函数do_softirq()核心部分代码:
void do_softirq(void){
struct softirq_action *h;
__u32 pending;

if (in_interrupt()) //处于中断上下文则返回
return;

pending = local_softirq_pending(); //查询软中断状态寄存器irq_stat判断事件是否发生
set_softirq_pending(0);
h = softirq_vec; //软中断向量表
do{
if (pending & 1) //事件发生则执行软中断处理程序
h->action(h);
h++;
pending >>= 1;
}while (pending);
}
d) tasklet
Tasklet为一个软中断,考虑到优先级问题,分别占用了软中断向量表中的0号和5号软中断。
i. 创建tasklet
struct tasklet_struct
{
struct tasklet_struct *next;
unsigned long state;
atomic_t count;
void (*func)(unsigned long);
unsigned long data;
};
调用DELCARE_TASKLET创建struct tasklet_struct结构体并注册tasklet处理程序。
调用tasklet_schedule()/tasklet_hi_schedule()将struct tasklet_struct加入tasklet向量表tasklet_vec/tasklet_hi_vec。
ii. tasklet注册软中断处理程序
在softirq_init()中调用注册tasklet软中断处理函数tasklet_action()/tasklet_hi_action()
static void tasklet_action(struct softirq_action *a){
struct tasklet_struct *list;

list = __this_cpu_read(tasklet_vec.head); //读取tasklet向量表

while (list) {
struct tasklet_struct *t = list;
list = list->next;
if (!atomic_read(&t->count))
t->func(t->data); //如果count为0则执行tasklet处理程序。
}
}
e) 软中断与tasklet的区别
软中断必须确保共享数据的安全,因为两个甚至更多相同类别的软中断有可能在不同的处理器上同步执行。而两个同类型的tasklet不能同时执行。

沒有留言:

張貼留言