linux2.6.32_软件时钟架构
来源:互联网 发布:知乎广告文案 编辑:程序博客网 时间:2024/05/16 15:58
/*
软件时钟可分为两种类型:
仅仅激活一次
激活多次或者周期性激活
多次激活的实现机制就是要在软件时钟处理函数中重新设置软件时钟的到期时间为将来的一个时间,
这个过程通过调用 mod_timer 函数来实现.
*/
/*
从上面的说明中可以看出:
软件时钟是按照其到期时间相对于当前正在处理的软件时钟的到期时间
( timer_jiffies 的数值)保存在 struct tvec_base 变量中的。
而且这个到期时间的最大相对值(到期时间 - timer_jiffies )为 0xffffffffUL
( tv5 最后一个元素能够表示的相对到期时间的最大值)。
注:一个tick的长度指的是两次硬件时钟中断发生之间的时间间隔
*/
/*
这里所说“软件时钟”指的是软件定时器( Software Timers ),
是一个软件上的概念,是建立在硬件时钟基础之上的。
它记录了未来某一时刻要执行的操作(函数),
并使得当这一时刻真正到来时,这些操作(函数)能够被按时执行
实现软件时钟原理也比较简单:
每一次硬件时钟中断到达时,内核更新的 jiffies ,
然后将其和软件时钟的到期时间进行比较。
如果 jiffies 等于或者大于软件时钟的到期时间,
内核就执行软件时钟指定的函数。
*/
u64 jiffies_64 __cacheline_aligned_in_smp = INITIAL_JIFFIES;
EXPORT_SYMBOL(jiffies_64);
/*
* per-CPU timer vector definitions:
*/
#define TVN_BITS (CONFIG_BASE_SMALL ? 4 : 6)
#define TVR_BITS (CONFIG_BASE_SMALL ? 6 : 8)
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_MASK (TVN_SIZE - 1)
#define TVR_MASK (TVR_SIZE - 1)
/*
可见它们实际上就是类型为 struct list_head 的数组,
其中 TVN_SIZE 和 TVR_SIZE 在系统没有配置宏 CONFIG_BASE_SMALL 时分别被定义为64和256。
*/
struct tvec {
struct list_head vec[TVN_SIZE];
};
struct tvec_root {
struct list_head vec[TVR_SIZE];
};
//软件时钟,记录了软件时钟的到期时间以及到期后要执行的操作。
struct timer_list {
struct list_head entry; //所在的链表
unsigned long expires; //到期时间
void (*function)(unsigned long); //回调函数,到期后执行的操作
unsigned long data; //回调函数的参数
struct tvec_base *base; //记录该软件时钟所在的 struct tvec_base 变量
#ifdef CONFIG_TIMER_STATS
void *start_site;
char start_comm[16];
int start_pid;
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
/*
从图中可以清楚地看出:
软件时钟( struct timer_list ,在图中由 timer 表示)以双向链表( struct list_head )的形式,
按照它们的到期时间保存相应的桶( tv1~tv5 )中。
tv1 中保存了相对于 timer_jiffies 下256个 tick 时间内到期的所有软件时钟;
tv2 中保存了相对于 timer_jiffies 下256*64个 tick 时间内到期的所有软件时钟;
tv3 中保存了相对于 timer_jiffies 下256*64*64个 tick 时间内到期的所有软件时钟;
tv4 中保存了相对于 timer_jiffies 下256*64*64*64个 tick 时间内到期的所有软件时钟;
tv5 中保存了相对于 timer_jiffies 下256*64*64*64*64个 tick 时间内到期的所有软件时钟。
具体的说,从静态的角度看,假设 timer_jiffies 为0,
那么 tv1[0] 保存着当前到期(到期时间等于 timer_jiffies )的软件时钟(需要马上被处理),
tv1[1] 保存着下一个 tick 到达时,到期的所有软件时钟,
tv1[n] (0<= n <=255)保存着下 n 个 tick 到达时,到期的所有软件时钟。
而 tv2[0] 则保存着下256到511个 tick 之间到期所有软件时钟,
tv2[1] 保存着下512到767个 tick 之间到期的所有软件时钟,
tv2[n] (0<= n <=63)保存着下256*(n+1)到256*(n+2)-1个 tick 之间到达的所有软件时钟。
tv3~tv5 依次类推。
TV1 为第一个timer 数组,其中存放着从
timer_jiffies(当前到期的jiffies)到timer_jiffies + 255 共256 个tick 对应的timer list。
TV2 有64 个单元,每个单元都对应着
256 个tick,因此TV2 所表示的超时时间范围从timer_jiffies + 256 到timer_jiffies + 256 *
64 – 1
还需要注意的是软件时钟的处理是局部于 CPU 的,
所以在 SMP 系统中每一个 CPU 都保存一个类型为 struct tvec_base 的变量,
用来组织、管理本 CPU 的软件时钟。
从图中也可以看出 struct tvec_base 变量是 per-CPU 的
*/
struct tvec_base {
spinlock_t lock;
struct timer_list *running_timer; //正在处理的软件时钟
unsigned long timer_jiffies; //当前正在处理的软件时钟到期时间
unsigned long next_timer;
struct tvec_root tv1; //保存了到期时间从 timer_jiffies 到 timer_jiffies + 2^8-1之间(包括边缘值)的所有软件时钟
struct tvec tv2; //保存了到期时间从 timer_jiffies + 2^8到 timer_jiffies +2^14-1之间(包括边缘值)的 所有软件时钟
struct tvec tv3; //从 timer_jiffies +2^14到 timer_jiffies +2^20-1之间(包括边缘值)的所有软件时钟
struct tvec tv4; //从 timer_jiffies +2^20到 timer_jiffies +2^26-1之间(包括边缘值)的所有软件时钟
struct tvec tv5; //从 timer_jiffies +2^26到 timer_jiffies +2^32-1之间(包括边缘值)的所有软件时钟
} ____cacheline_aligned;
/*
在 Linux 内核中要添加一个软件时钟,首先必须分配 struct timer_list 类型的变量,
然后调用函数 add_timer() 将该软件时钟添加到相应调用 add_timer 函数的 CPU 的 base 中。
Add_timer 是对函数 __mod_timer() 的一层包装。
*/
/**
* add_timer - start a timer
* @timer: the timer to be added
*
* The kernel will do a ->function(->data) callback from the
* timer interrupt at the ->expires point in the future. The
* current time is 'jiffies'.
*
* The timer's ->expires, ->function (and if the handler uses it, ->data)
* fields must be set prior calling this function.
*
* Timers with an ->expires field in the past will be executed in the next
* timer tick.
*/
void add_timer(struct timer_list *timer)
{
BUG_ON(timer_pending(timer));
mod_timer(timer, timer->expires);
}
EXPORT_SYMBOL(add_timer);
/**
* mod_timer - modify a timer's timeout
* @timer: the timer to be modified
* @expires: new timeout in jiffies
*
* mod_timer() is a more efficient way to update the expire field of an
* active timer (if the timer is inactive it will be activated)
*
* mod_timer(timer, expires) is equivalent to:
*
* del_timer(timer); timer->expires = expires; add_timer(timer);
*
* Note that if there are multiple unserialized concurrent users of the
* same timer, then mod_timer() is the only safe way to modify the timeout,
* since add_timer() cannot modify an already running timer.
*
* The function returns whether it has modified a pending timer or not.
* (ie. mod_timer() of an inactive timer returns 0, mod_timer() of an
* active timer returns 1.)
*/
int mod_timer(struct timer_list *timer, unsigned long expires)
{
/*
* This is a common optimization triggered by the
* networking code - if the timer is re-modified
* to be the same thing then just return:
*/
if (timer_pending(timer) && timer->expires == expires)
return 1;
return __mod_timer(timer, expires, false, TIMER_NOT_PINNED);
}
/**
* timer_pending - is a timer pending?
* @timer: the timer in question
*
* timer_pending will tell whether a given timer is currently pending,
* or not. Callers must ensure serialization wrt. other operations done
* to this timer, eg. interrupt contexts, or other CPUs on SMP.
*
* return value: 1 if the timer is pending, 0 if not.
*/
static inline int timer_pending(const struct timer_list * timer)
{
return timer->entry.next != NULL;
}
static inline int
__mod_timer(struct timer_list *timer, unsigned long expires,
bool pending_only, int pinned)
{
struct tvec_base *base, *new_base;
unsigned long flags;
int ret = 0 , cpu;
timer_stats_timer_set_start_info(timer);
BUG_ON(!timer->function);
base = lock_timer_base(timer, &flags);//取得软件时钟所在 base 上的同步锁( struct tvec_base 变量中的自旋锁)
//并返回该软件时钟的 base ,保存在 base 变量中
if (timer_pending(timer)) {
detach_timer(timer, 0); //如果该软件时钟处在 pending 状态(在 base 中,准备执行),则卸载该软件时钟
if (timer->expires == base->next_timer &&
!tbase_get_deferrable(timer->base))
base->next_timer = base->timer_jiffies;
ret = 1;
} else {
if (pending_only)
goto out_unlock;
}
debug_activate(timer, expires);
new_base = __get_cpu_var(tvec_bases); //取得本 CPU 上的 base 指针(类型为 struct tvec_base* ),保存在 new_base 中
cpu = smp_processor_id();
#if defined(CONFIG_NO_HZ) && defined(CONFIG_SMP)
if (!pinned && get_sysctl_timer_migration() && idle_cpu(cpu)) {
int preferred_cpu = get_nohz_load_balancer();
if (preferred_cpu >= 0)
cpu = preferred_cpu;
}
#endif
new_base = per_cpu(tvec_bases, cpu);
/*
如果 base 和 new_base 不一样,也就是说软件时钟发生了迁移(从一个 CPU 中移到了另一个 CPU 上),
那么如果该软件时钟的处理函数当前没有在迁移之前的那个 CPU 上运行,
则先将软件时钟的 base 设置为 NULL ,
然后再将该软件时钟的 base 设置为 new_base 。否则,跳到5。
*/
if (base != new_base) {
/*
* We are trying to schedule the timer on the local CPU.
* However we can't change timer's base while it is running,
* otherwise del_timer_sync() can't detect that the timer's
* handler yet has not finished. This also guarantees that
* the timer is serialized wrt itself.
*/
if (likely(base->running_timer != timer)) {
/* See the comment in lock_timer_base() */
timer_set_base(timer, NULL);
spin_unlock(&base->lock);
base = new_base;
spin_lock(&base->lock);
timer_set_base(timer, base);
}
}
timer->expires = expires; //设置软件时钟的到期时间
if (time_before(timer->expires, base->next_timer) &&
!tbase_get_deferrable(timer->base))
base->next_timer = timer->expires;
internal_add_timer(base, timer); //调用 internal_add_timer 函数将软件时钟添加到软件时钟的 base 中(本 CPU 的 base )
out_unlock:
spin_unlock_irqrestore(&base->lock, flags);
return ret;
}
/*
代码解释:
1. 计算该软件时钟的到期时间和 timer_jiffies (当前正在处理的软件时钟的到期时间)的差值,作为索引保存到 idx 变量中。
2. 判断 idx 所在的区间,在
[0, 2^8-1]或者(负无穷大 , 0)(该软件时钟已经到期),则将要添加到 tv1 中
[2^8, 2^14-1],则将要添加到 tv2 中
[2^14,2^20-1 ],则将要添加到 tv3 中
[2^20,2^26-1 ],则将要添加到 tv4 中
[2^26,无穷大 ),则将要添加到 tv5 中,但实际上最大值为 0xffffffffUL
3. 计算所要加入的具体位置(哪个链表中,即 tv1~tv5 的哪个子链表,参考图3-1)
4. 最后将其添加到相应的链表中
从这个函数可以得知,内核中是按照软件时钟到期时间的相对值
(相对于 timer_jiffies 的值)将软件时钟添加到软件时钟所在的 base 中的。
*/
static void internal_add_timer(struct tvec_base *base, struct timer_list *timer)
{
unsigned long expires = timer->expires;
unsigned long idx = expires - base->timer_jiffies; // 1
struct list_head *vec;
if (idx < TVR_SIZE) { // 2
int i = expires & TVR_MASK;
vec = base->tv1.vec + i;
} else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
int i = (expires >> TVR_BITS) & TVN_MASK;
vec = base->tv2.vec + i;
} else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
int i = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
vec = base->tv3.vec + i;
} else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
int i = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
vec = base->tv4.vec + i;
} else if ((signed long) idx < 0) {
/*
* Can happen if you add a timer with expires == jiffies,
* or you set a timer to go off in the past
*/
vec = base->tv1.vec + (base->timer_jiffies & TVR_MASK);
} else {
int i;
/* If the timeout is larger than 0xffffffff on 64-bit
* architectures then we use the maximum timeout:
*/
if (idx > 0xffffffffUL) {
idx = 0xffffffffUL;
expires = idx + base->timer_jiffies;
}
i = (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK; // 3
vec = base->tv5.vec + i;
}
/*
* Timers are FIFO:
*/
list_add_tail(&timer->entry, vec); // 4
}
/**
* del_timer - deactive a timer.
* @timer: the timer to be deactivated
*
* del_timer() deactivates a timer - this works on both active and inactive
* timers.
*
* The function returns whether it has deactivated a pending timer or not.
* (ie. del_timer() of an inactive timer returns 0, del_timer() of an
* active timer returns 1.)
代码解释:
1. 检测该软件时钟是否处在 pending 状态(在 base 中,准备运行),如果不是则直接函数返回
2. 如果处于 pending 状态,则获得锁
3. 再次检测软件时钟是否处于 pending 状态(该软件时钟可能被卸载了),不是则释放锁然后函数返回
4. 如果还是 pending 状态,则将其卸载,之后释放锁,函数返回
*/
int del_timer(struct timer_list *timer)
{
struct tvec_base *base;
unsigned long flags;
int ret = 0;
timer_stats_timer_clear_start_info(timer);
if (timer_pending(timer)) { // 1
base = lock_timer_base(timer, &flags); // 2
if (timer_pending(timer)) { // 3
detach_timer(timer, 1); // 4
if (timer->expires == base->next_timer &&
!tbase_get_deferrable(timer->base))
base->next_timer = base->timer_jiffies;
ret = 1;
}
spin_unlock_irqrestore(&base->lock, flags);
}
return ret;
}
static inline void detach_timer(struct timer_list *timer,
int clear_pending)
{
struct list_head *entry = &timer->entry;
debug_deactivate(timer);
__list_del(entry->prev, entry->next);
if (clear_pending)
entry->next = NULL;
entry->prev = LIST_POISON2;
}
//在smp系统中
#ifdef CONFIG_SMP
/**
* try_to_del_timer_sync - Try to deactivate a timer
* @timer: timer do del
*
* This function tries to deactivate a timer. Upon successful (ret >= 0)
* exit the timer is not queued and the handler is not running on any CPU.
*
* It must not be called from interrupt contexts.
该函数检测当前运行的软件时钟是不是该软件时钟,
如果是,则函数返回-1,表明目前不能删除该软件时钟;
如果不是检测该软件时钟是否处于 pending 状态,如果不是,
则函数返回0,表明软件时钟已经被卸载,
如果处于 pending 状态再把软件时钟卸载,函数返回1,
表明成功卸载该软件时钟。
*/
int try_to_del_timer_sync(struct timer_list *timer)
{
struct tvec_base *base;
unsigned long flags;
int ret = -1;
base = lock_timer_base(timer, &flags);
if (base->running_timer == timer)
goto out;
ret = 0;
if (timer_pending(timer)) {
detach_timer(timer, 1);
if (timer->expires == base->next_timer &&
!tbase_get_deferrable(timer->base))
base->next_timer = base->timer_jiffies;
ret = 1;
}
out:
spin_unlock_irqrestore(&base->lock, flags);
return ret;
}
/*
del_timer_sync 函数无限循环试图卸载该软件时钟,
直到该软件时钟能够被成功卸载。从其实现中可以看出:
如果一个软件时钟的处理函数正在执行时,
对其的卸载操作将会失败。一直等到软件时钟的处理函数运行结束后,
卸载操作才会成功。这样避免了在 SMP 系统中一个 CPU 正在执行软件时钟的处理函数,
而另一个 CPU 则要将该软件时钟卸载所引发的问题。
*/
int del_timer_sync(struct timer_list *timer)
{
#ifdef CONFIG_LOCKDEP
unsigned long flags;
local_irq_save(flags);
lock_map_acquire(&timer->lockdep_map);
lock_map_release(&timer->lockdep_map);
local_irq_restore(flags);
#endif
for (;;) {
int ret = try_to_del_timer_sync(timer);
if (ret >= 0)
return ret;
cpu_relax();
}
}
EXPORT_SYMBOL(del_timer_sync);
#endif
/*
* These inlines deal with timer wrapping correctly. You are
* strongly encouraged to use them
* 1. Because people otherwise forget
* 2. Because if the timer wrap changes in future you won't have to
* alter your driver code.
*
* time_after(a,b) returns true if the time a is after time b.
*
* Do this with "<0" and ">=0" to only test the sign of the result. A
* good compiler would generate better code (and a really good compiler
* wouldn't care). Gcc is currently neither.
*/
#define time_after(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(b) - (long)(a) < 0))
#define time_before(a,b) time_after(b,a)
#define time_after_eq(a,b) \
(typecheck(unsigned long, a) && \
typecheck(unsigned long, b) && \
((long)(a) - (long)(b) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)
jiffies = jiffies_64; //位于/arch/unicore/kernel/vmlinuxlsd.S中
/*
* This function runs timers and the timer-tq in bottom half context.
*/
static void run_timer_softirq(struct softirq_action *h)
{
struct tvec_base *base = __get_cpu_var(tvec_bases); //获得本CPU的tvec_base结构
perf_event_do_pending(); //未知
hrtimer_run_pending(); //先跳过
if (time_after_eq(jiffies, base->timer_jiffies)) //然后检测如果 jiffies大于timer_jiffies
__run_timers(base); //说明可能已经有软件时钟到期了,此时就要进行软件时钟的处理,
}
/**
* __run_timers - run all expired timers (if any) on this CPU.
* @base: the timer vector to be processed.
*
* This function cascades all vectors and executes all expired timer
* vectors.
代码解释:
1. 获得 base 的同步锁
2. 如果 jiffies 大于等于 timer_jiffies (当前正要处理的软件时钟的到期时间,
说明可能有软件时钟到期了),就一直运行3~7,否则跳转至8
3. 计算得到 tv1 的索引,
该索引指明当前到期的软件时钟所在 tv1 中的链表(结构参见3.2节),
代码:int index = base->timer_jiffies & TVR_MASK;
4. 调用 cascade 函数对软件时钟进行必要的调整(稍后会介绍调整的过程)
5. 使得 timer_jiffies 的数值增加1
6. 取出相应的软件时钟链表
7. 遍历该链表,对每个元素进行如下操作
设置当前软件时钟为 base 中正在运行的软件时钟(即保存当前软件时钟到 base-> running_timer 成员中)
将当前软件时钟从链表中删除,即卸载该软件时钟
释放锁,执行软件时钟处理程序
再次获得锁
8. 设置当前 base 中不存在正在运行的软件时钟
9. 释放锁
*/
static inline void __run_timers(struct tvec_base *base)
{
struct timer_list *timer;
spin_lock_irq(&base->lock); // 1
while (time_after_eq(jiffies, base->timer_jiffies)) { // 2如果已经到期
struct list_head work_list;
struct list_head *head = &work_list;
int index = base->timer_jiffies & TVR_MASK; // 3
/*
* Cascade timers:
*/
if (!index && // index应该为0 在tv1[0]中
(!cascade(base, &base->tv2, INDEX(0))) &&
(!cascade(base, &base->tv3, INDEX(1))) &&
!cascade(base, &base->tv4, INDEX(2)))
cascade(base, &base->tv5, INDEX(3)); //这个操作消耗巨大,所有有新的机制
/*
这部分代码表明:
如果 index 有0再到0时( index 是对 timer_jiffies 取模),
说明时间已经过了256个 tick ,这时要把 tv2 中软件时钟转移到 tv1 中。
如果 index 和第一个 cascade 函数的返回值都从0再到到0时,
说明时间已经过了256*64个 tick ,这时要把 tv3 中软件时钟转移到 tv1 或者 tv2 中。
之后的调整过程依次类推。
*/
++base->timer_jiffies; //timer_jiffier加1 关键代码
list_replace_init(base->tv1.vec + index, &work_list);
while (!list_empty(head)) { //遍历链表
void (*fn)(unsigned long);
unsigned long data;
timer = list_first_entry(head, struct timer_list,entry);
fn = timer->function;
data = timer->data;
timer_stats_account_timer(timer);
set_running_timer(base, timer);
detach_timer(timer, 1); //卸载软件时钟
spin_unlock_irq(&base->lock);
{
int preempt_count = preempt_count();
#ifdef CONFIG_LOCKDEP
/*
* It is permissible to free the timer from
* inside the function that is called from
* it, this we need to take into account for
* lockdep too. To avoid bogus "held lock
* freed" warnings as well as problems when
* looking into timer->lockdep_map, make a
* copy and use that here.
*/
struct lockdep_map lockdep_map =
timer->lockdep_map;
#endif
/*
* Couple the lock chain with the lock chain at
* del_timer_sync() by acquiring the lock_map
* around the fn() call here and in
* del_timer_sync().
*/
lock_map_acquire(&lockdep_map);
trace_timer_expire_entry(timer);
fn(data); //执行软件时钟处理程序
trace_timer_expire_exit(timer);
lock_map_release(&lockdep_map);
if (preempt_count != preempt_count()) {
printk(KERN_ERR "huh, entered %p "
"with preempt_count %08x, exited"
" with %08x?\n",
fn, preempt_count,
preempt_count());
BUG();
}
}
spin_lock_irq(&base->lock);
}
}
set_running_timer(base, NULL);
spin_unlock_irq(&base->lock);
}
#define INDEX(N) ((base->timer_jiffies >> (TVR_BITS + (N) * TVN_BITS)) & TVN_MASK)
/*
函数 cascade 用于调整软件时钟(这个调整过程是指:
将马上就要到期的软件时钟从其所在的链表中删除,
重新计算到期时间的相对值(到期时间 - timer_jiffies ),
然后根据该值重新插入到 base 中)。
注意到在软件时钟处理过程中,每次都是从 tv1 中取出一个链表进行处理,
而不是从 tv2~tv5 中取,所以对软件时钟就要进行必要的调整?
?
在讲解 cascade 函数之前,再从直观上理解下为什么需要进行调整。
所有软件时钟都是按照其到期时间的相对值(相对于 timer_jiffies )被调加到 base 中的。
但是 timer_jiffies 的数值都会在处理中增加1(如3.3.2节所示),
也就是说这个相对值会随着处理发生变化,当这个相对值小于等于256时,
就要将软件时钟从 tv2~tv5 中转移到 tv1 中( tv1 中保存着下256个 tick 内到期的所有软件时钟)。
该函数根据索引,取出相应的 tv ( tv2~tv5 )中的链表,
然后遍历链表每一个元素。按照其到期时间重新将软件时钟加入到软件时钟的 base 中。
该函数返回 tv 中被调整的链表索引值
*/
static int cascade(struct tvec_base *base, struct tvec *tv, int index)
{
/* cascade all the timers from tv up one level */
struct timer_list *timer, *tmp;
struct list_head tv_list;
list_replace_init(tv->vec + index, &tv_list);
/*
* We are removing _all_ timers from the list, so we
* don't have to detach them individually.
*/
list_for_each_entry_safe(timer, tmp, &tv_list, entry) {
BUG_ON(tbase_get_base(timer->base) != base);
internal_add_timer(base, timer);
}
return index;
}
- linux2.6.32_软件时钟架构
- 正版 Microsoft 计划_下载时钟软件
- 系统架构_软件架构风格概述
- 系统架构_软件架构风格概述
- 基于Linux2.6.32内核对DMA架构分析
- 『常识』系统架构_软件架构风格概述
- 移植linux2.6.19成功^_^
- 网页时钟显示^_^
- Linux 时钟_定时器
- Linux 时钟_定时器
- 单片机_时钟定时
- Linux cpuidle framework(1)_概述和软件架构
- Linux PM QoS framework(1)_概述和软件架构
- Linux PM QoS framework(1)_概述和软件架构
- Linux cpuidle framework(1)_概述和软件架构
- 读书笔记_软件架构设计 程序员向架构师转型必备(第二版)温昱
- UE4_代理示例_时钟
- STM32F103C8T6学习笔记_时钟
- 动态链接器(三) 动态链接的步骤和实现
- STRUTS2的常用配置
- 程序内存结构及返回
- windows xp和ubuntu9.10双系统引导程序的修复
- linux2.6.32_complete(完成量)
- linux2.6.32_软件时钟架构
- Android的Contact数据库contacts2.db里的名字问题
- Magento 新闻模块开发教程 (五) 创建辅助类
- error LNK2019: 无法解析的外部符号 _GetAdaptersInfo@8,该符号在函数 _wmain 中被引用
- STRUTS中ActionMessage、ActionMessages、saveMessages()、saveErrors()、之间的关系及用法
- linux下新建oracle数据库实例
- Grasp WPF with key points
- 敏捷开发思想与实践
- VS2005 無法調試