Linux设备驱动程序 之 内核定时器
综述
如果需要在将来的某个时间点调度执行某个动作,同时在该时间点到达之前不会阻塞当前进程,则可以使用内核定时器;
内核定时器是一个数据结构,它告诉内核在用户定义的时间点使用用户定义的参数来执行一个用户定义的函数;
被调度运行的函数几乎肯定不会再注册这些函数的进程正在执行时运行,相反,这些函数会异步的运行;当定时器运行时,调度该定时器的进程可能正在休眠或在其他处理器上执行,或已经退出;
内核定时器常常是作为“软中断”的结果而运行的;在这种原子性上下文中运行时,代码会受到很多限制;
许多动作需要在进程上下文中才能执行,如果处于进程上下文之外,则必须遵守以下规则:
1. 不允许访问用户空间;因为没有进程上下文,无法将任何特定进程与用户空间关联起来;
2. current指针在原子模式下是没有任何意义的;也是不可用的,因为相关代码和被中断的进程没有任何关联;
3. 不能执行休眠或者调度;原子代码不可以调用shcedule或者wait_event,也不能调用任何可能引起休眠的函数;例如,调用kmalloc(…,GFP_KERNEL)就不符合本规则;信号量也不能用,因为可能引起休眠;
内核可以通过调用函数in_interrupt()来判断自己是否正运行于中断上下文,该函数无需参数,如果处理器运行在中断上下文,则返回非0值,无论是硬件中断还是软件中断;
和in_interrupt()相关的函数是in_atomic(),当函数返回非0值时,调度不被允,即处于原子上下文,包括硬件和软件中断上下文以及拥有自旋锁的任何时间点,在后一种情况下,current可能是可用的,但是禁止访问用户空间,因为会导致调度的发生;在使用in_interrupt()的时间点,都应该考虑是否真正的该使用的是in_atomic();
内核定时器的另一个重要特征是,任务可以将自己注册以后再稍后的时间重新运行,这种可能性是因为timer_list结构会在运行之前从活动定时器链表中移走,这样就可以立即链入其他链表;
在SMP系统中,定时器函数会由注册它的同一CPU执行,这样可以尽可能的获得缓存的局域性;因此,一个注册自己的定时器始终会在同一CPU上运行;
即使在单处理器系统上,定时器也会是竞态的潜在来源;这是由其异步执行的特点直接导致的;因此,任何通过定时器函数访问的数据结构都应该针对并发访问进行保护;
定时器API
内核为驱动程序提供了一组用来声明、注册和删除定时器的函数,下面为部分摘取:
struct timer_list { /* * All fields that change during normal runtime grouped to the * same cacheline */ struct hlist_node entry; unsigned long expires; /* 超时时间 */ void (*function)(unsigned long); /* 超时回调 */ unsigned long data; /* 回调参数 */ u32 flags; /* 标志 */ #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
/* 静态定义初始化 */ #define DEFINE_TIMER(_name, _function, _expires, _data) /* 只初始化,初始化之后再设置结构的回调,超时等 */ #define init_timer(timer) /* 初始化,设置回调,未设置超时 */ #define setup_timer(timer, fn, data) /* 添加定时器,开始工作 */ void add_timer(struct timer_list *timer) /* 未超时之前,可修改定时器的超时时间 */ int mod_timer(struct timer_list *timer, unsigned long expires) /* 删除定时器 */ int del_timer(struct timer_list * timer)