Linux 核心依據 HZ 的值設定 PIT 裝置(Intel 8254)發出中斷的頻率,並且初始化 IRQ 0 為 Timer Interrupt,其中 1193182 是 8254 晶片內部的頻率。
每當時間中斷產生時,就更新變數 jiffies_64 及 xtime 之值。以下列出核心的相關程序的原始碼:
• setup_pit_timer() 設定 PIT
• time_init_hook() 初始化 IRQ 0
• do_timer_interrupt() 時間中斷的處理函式
定義於 include/asm-i386/timex.h
#ifdef CONFIG_X86_ELAN
# define CLOCK_TICK_RATE 1189200 /* AMD Elan has different frequency! */
#else
# define CLOCK_TICK_RATE 1193182 /* Underlying HZ */
#endif
定義於 include/linux/jiffies.h
/* LATCH is used in the interval timer and ftape setup. */
#define LATCH ((CLOCK_TICK_RATE + HZ/2) / HZ) /* For divider */
定義於 arch/i386/kernel/timers/timer_pit.c
void setup_pit_timer(void)
{
extern spinlock_t i8253_lock;
unsigned long flags;
spin_lock_irqsave(&i8253_lock, flags);
outb_p(0x34,PIT_MODE); /* binary, mode 2, LSB/MSB, ch 0 */
udelay(10);
outb_p(LATCH & 0xff , PIT_CH0); /* LSB */
udelay(10);
outb(LATCH >> 8 , PIT_CH0); /* MSB */
spin_unlock_irqrestore(&i8253_lock, flags);
}
定義於 arch/i386/mach-default/setup.c
static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, CPU_MASK_NONE, "timer", NULL, NULL};
/**
* time_init_hook - do any specific initialisations for the system timer.
*
* Description:
* Must plug the system timer interrupt source at HZ into the IRQ listed
* in irq_vectors.h:TIMER_IRQ
**/
void __init time_init_hook(void)
{
setup_irq(0, &irq0);
}
定義於 include/asm-i386/mach-default/do_timer.h
/**
* do_timer_interrupt_hook - hook into timer tick
* @regs: standard registers from interrupt
*
* Description:
* This hook is called immediately after the timer interrupt is ack'd.
* It's primary purpose is to allow architectures that don't possess
* individual per CPU clocks (like the CPU APICs supply) to broadcast the
* timer interrupt as a means of triggering reschedules etc.
**/
static inline void do_timer_interrupt_hook(struct pt_regs *regs)
{
do_timer(regs);
#ifndef CONFIG_SMP
update_process_times(user_mode(regs));
#endif
/*
* In the SMP case we use the local APIC timer interrupt to do the
* profiling, except when we simulate SMP mode on a uniprocessor
* system, in that case we have to call the local interrupt handler.
*/
#ifndef CONFIG_X86_LOCAL_APIC
profile_tick(CPU_PROFILING, regs);
#else
if (!using_apic_timer)
smp_local_timer_interrupt(regs);
#endif
}
定義於 arch/i386/kernel/time.c
/*
* Called by the timer interrupt. xtime_lock must already be taken
* by the timer IRQ!
*/
static inline void update_times(void)
{
unsigned long ticks;
ticks = jiffies - wall_jiffies;
if (ticks) {
wall_jiffies += ticks;
update_wall_time(ticks);
}
calc_load(ticks);
}
/*
* The 64-bit jiffies value is not atomic - you MUST NOT read it
* without sampling the sequence number in xtime_lock.
* jiffies is defined in the linker script...
*/
void do_timer(struct pt_regs *regs)
{
jiffies_64++;
update_times();
}
/*
* timer_interrupt() needs to keep up the real-time clock,
* as well as call the "do_timer()" routine every clocktick
*/
static inline void do_timer_interrupt(int irq, void *dev_id,
struct pt_regs *regs)
{
#ifdef CONFIG_X86_IO_APIC
if (timer_ack) {
/*
* Subtle, when I/O APICs are used we have to ack timer IRQ
* manually to reset the IRR bit for do_slow_gettimeoffset().
* This will also deassert NMI lines for the watchdog if run
* on an 82489DX-based system.
*/
spin_lock(&i8259A_lock);
outb(0x0c, PIC_MASTER_OCW3);
/* Ack the IRQ; AEOI will end it automatically. */
inb(PIC_MASTER_POLL);
spin_unlock(&i8259A_lock);
}
#endif
do_timer_interrupt_hook(regs);
/*
* If we have an externally synchronized Linux clock, then update
* CMOS clock accordingly every ~11 minutes. Set_rtc_mmss() has to be
* called as close as possible to 500 ms before the new second starts.
*/
if ((time_status & STA_UNSYNC) == 0 &&
xtime.tv_sec > last_rtc_update + 660 &&
(xtime.tv_nsec / 1000)
>= USEC_AFTER - ((unsigned) TICK_SIZE) / 2 &&
(xtime.tv_nsec / 1000)
<= USEC_BEFORE + ((unsigned) TICK_SIZE) / 2) {
/* horrible...FIXME */
if (efi_enabled) {
if (efi_set_rtc_mmss(xtime.tv_sec) == 0)
last_rtc_update = xtime.tv_sec;
else
last_rtc_update = xtime.tv_sec - 600;
} else if (set_rtc_mmss(xtime.tv_sec) == 0)
last_rtc_update = xtime.tv_sec;
else
last_rtc_update = xtime.tv_sec - 600; /* do it again in 60 s */
}
if (MCA_bus) {
/* The PS/2 uses level-triggered interrupts. You can't
turn them off, nor would you want to (any attempt to
enable edge-triggered interrupts usually gets intercepted by a
special hardware circuit). Hence we have to acknowledge
the timer interrupt. Through some incredibly stupid
design idea, the reset for IRQ 0 is done by setting the
high bit of the PPI port B (0x61). Note that some PS/2s,
notably the 55SX, work fine if this is removed. */
irq = inb_p( 0x61 ); /* read the current state */
outb_p( irq0x80, 0x61 ); /* reset the IRQ */
}
}
/*
* This is the same as the above, except we _also_ save the current
* Time Stamp Counter value at the time of the timer interrupt, so that
* we later on can estimate the time of day more exactly.
*/
irqreturn_t timer_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
/*
* Here we are in the timer irq handler. We just have irqs locally
* disabled but we don't know if the timer_bh is running on the other
* CPU. We need to avoid to SMP race with it. NOTE: we don' t need
* the irq version of write_lock because as just said we have irq
* locally disabled. -arca
*/
write_seqlock(&xtime_lock);
cur_timer->mark_offset();
do_timer_interrupt(irq, NULL, regs);
write_sequnlock(&xtime_lock);
return IRQ_HANDLED;
}
2010年8月15日 星期日
2010年8月14日 星期六
Timing Measurements - 1
System Time and Date
Linux 的系統時間與日期是靠一顆 Real Time Clock (RTC) 的晶片所維護的,應用程式可經由 /dev/rtc 來控制晶片,
核心則是透過 I/O port 0x70 和 0x71 來控制。
Time Measurement
x86 處理器有一名為 Time Stamp Counter (TSC) 的暫存器,每當 CPU 的 clock 腳位有訊號時,TSC 就會遞增。
若 clock 的頻率是 1 GHz,則 TSC 會每 1 nanosecond 做加 1 的動作。Linux 核心使用 TSC 的值便可讓做出精準的時間量測。
Timer Interrupt
PC 硬體上會有至少一個的 Programmable Interval Timer (PIT) 裝置,如 Intel 的 8254 晶片,提供定時的功能,
並且於 timeout 時對 CPU 發出中斷通知,也就是 Timer Interrupt - IRQ 0。
PIT 可以透過 I/O port 0x40 至 0x43 的 4 個埠口來控制。
tick, jiffies and xtime
Linux 核心設定 PIT 產生頻率為 1000 Hz 的 Timer Interrupt,即每 1 millisecond 會產生一次中斷,
此 1 millisecond 的時間稱為一個 "tick",該值存於變數 tick_nsec 之中,而頻率值以巨集 HZ 表示。
愈短的 tick 值會得到快速的 I/O 多工的反應時間,但亦會拖慢應用程式的執行速度。
通常較慢的機器會設定 10 millisecond 的 tick 值,而較快的機器會設定 1 millisecond 的 tick 值。
定義於 kernel/timer.c
/*
* Timekeeping variables
*/
unsigned long tick_usec = TICK_USEC; /* USER_HZ period (usec) */
unsigned long tick_nsec = TICK_NSEC; /* ACTHZ period (nsec) */
/*
* The current time
* wall_to_monotonic is what we need to add to xtime (or xtime corrected
* for sub jiffie times) to get to monotonic time. Monotonic is pegged
* at zero at system boot time, so wall_to_monotonic will be negative,
* however, we will ALWAYS keep the tv_nsec part positive so we can use
* the usual normalization.
*/
struct timespec xtime __attribute__ ((aligned (16)));
struct timespec wall_to_monotonic __attribute__ ((aligned (16)));
EXPORT_SYMBOL(xtime);
定義於 include/asm-i386/param.h
#ifdef __KERNEL__
# define HZ 1000 /* Internal kernel timer frequency */
# define USER_HZ 100 /* .. some user interfaces are in "ticks" */
# define CLOCKS_PER_SEC (USER_HZ) /* like times() */
#endif
#ifndef HZ
#define HZ 100
#endif
核心變數 jiffies 用來儲存自系統啟動以來,共經過了多少次 tick 的值。故每次的 tick 就會使 jiffies 遞增。
由於 jiffies 的長度只有 32-bit,對於 tick = 1 millisecond 機器,經過約 50 天就會讓 jiffies 溢位。
因此真正用來儲存 jiffies 值的是長度為 64-bit 的變數 jiffies_64。
定義於 include/linux/jiffies.h
/*
* The 64-bit value is not volatile - you MUST NOT read it
* without sampling the sequence number in xtime_lock.
* get_jiffies_64() will do this for you as appropriate.
*/
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
核心變數 xtime 用來記錄自系目前的時間與日期,其結構包含兩個欄位:
• tv_sec 儲存自 1970.01.01 以來,共經過多少秒 (second)
• tv_nsec 儲存自上一秒的時間以來,共經過多少的奈秒 (nanosecond)
定義於 include/linux/time.h
#ifndef _STRUCT_TIMESPEC
#define _STRUCT_TIMESPEC
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
#endif /* _STRUCT_TIMESPEC */
Linux 的系統時間與日期是靠一顆 Real Time Clock (RTC) 的晶片所維護的,應用程式可經由 /dev/rtc 來控制晶片,
核心則是透過 I/O port 0x70 和 0x71 來控制。
Time Measurement
x86 處理器有一名為 Time Stamp Counter (TSC) 的暫存器,每當 CPU 的 clock 腳位有訊號時,TSC 就會遞增。
若 clock 的頻率是 1 GHz,則 TSC 會每 1 nanosecond 做加 1 的動作。Linux 核心使用 TSC 的值便可讓做出精準的時間量測。
Timer Interrupt
PC 硬體上會有至少一個的 Programmable Interval Timer (PIT) 裝置,如 Intel 的 8254 晶片,提供定時的功能,
並且於 timeout 時對 CPU 發出中斷通知,也就是 Timer Interrupt - IRQ 0。
PIT 可以透過 I/O port 0x40 至 0x43 的 4 個埠口來控制。
tick, jiffies and xtime
Linux 核心設定 PIT 產生頻率為 1000 Hz 的 Timer Interrupt,即每 1 millisecond 會產生一次中斷,
此 1 millisecond 的時間稱為一個 "tick",該值存於變數 tick_nsec 之中,而頻率值以巨集 HZ 表示。
愈短的 tick 值會得到快速的 I/O 多工的反應時間,但亦會拖慢應用程式的執行速度。
通常較慢的機器會設定 10 millisecond 的 tick 值,而較快的機器會設定 1 millisecond 的 tick 值。
定義於 kernel/timer.c
/*
* Timekeeping variables
*/
unsigned long tick_usec = TICK_USEC; /* USER_HZ period (usec) */
unsigned long tick_nsec = TICK_NSEC; /* ACTHZ period (nsec) */
/*
* The current time
* wall_to_monotonic is what we need to add to xtime (or xtime corrected
* for sub jiffie times) to get to monotonic time. Monotonic is pegged
* at zero at system boot time, so wall_to_monotonic will be negative,
* however, we will ALWAYS keep the tv_nsec part positive so we can use
* the usual normalization.
*/
struct timespec xtime __attribute__ ((aligned (16)));
struct timespec wall_to_monotonic __attribute__ ((aligned (16)));
EXPORT_SYMBOL(xtime);
定義於 include/asm-i386/param.h
#ifdef __KERNEL__
# define HZ 1000 /* Internal kernel timer frequency */
# define USER_HZ 100 /* .. some user interfaces are in "ticks" */
# define CLOCKS_PER_SEC (USER_HZ) /* like times() */
#endif
#ifndef HZ
#define HZ 100
#endif
核心變數 jiffies 用來儲存自系統啟動以來,共經過了多少次 tick 的值。故每次的 tick 就會使 jiffies 遞增。
由於 jiffies 的長度只有 32-bit,對於 tick = 1 millisecond 機器,經過約 50 天就會讓 jiffies 溢位。
因此真正用來儲存 jiffies 值的是長度為 64-bit 的變數 jiffies_64。
定義於 include/linux/jiffies.h
/*
* The 64-bit value is not volatile - you MUST NOT read it
* without sampling the sequence number in xtime_lock.
* get_jiffies_64() will do this for you as appropriate.
*/
extern u64 __jiffy_data jiffies_64;
extern unsigned long volatile __jiffy_data jiffies;
核心變數 xtime 用來記錄自系目前的時間與日期,其結構包含兩個欄位:
• tv_sec 儲存自 1970.01.01 以來,共經過多少秒 (second)
• tv_nsec 儲存自上一秒的時間以來,共經過多少的奈秒 (nanosecond)
定義於 include/linux/time.h
#ifndef _STRUCT_TIMESPEC
#define _STRUCT_TIMESPEC
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
#endif /* _STRUCT_TIMESPEC */
2010年3月1日 星期一
Linux Synchronization - 4
"scheduling while atomic: x/y/z"
其中 x 為行程的名稱 (current->comm)
y 為16進制的 preempt_count
z 為 PID
出現此訊息時,表示目前的 kernel task 是一個不可被中斷的行程,但卻被排程進入休眠。
以下為核心列印該錯誤訊息的程式片段:
/*
* schedule() is the main scheduler function.
*/
asmlinkage void __sched schedule(void)
{
long *switch_count;
task_t *prev, *next;
runqueue_t *rq;
prio_array_t *array;
struct list_head *queue;
unsigned long long now;
unsigned long run_time;
int cpu, idx;
/*
* Test if we are atomic. Since do_exit() needs to call into
* schedule() atomically, we ignore that path for now.
* Otherwise, whine if we are scheduling when we should not be.
*/
if (likely(!current->exit_state)) {
if (unlikely(in_atomic())) {
printk(KERN_ERR "scheduling while atomic: "
"%s/0x%08x/%d\n",
current->comm, preempt_count(), current->pid);
dump_stack();
}
}
profile_hit(SCHED_PROFILING, __builtin_return_address(0));
... /* 以下略 */
}
定義於 kernel/sched.c
#if defined(CONFIG_PREEMPT) && !defined(CONFIG_PREEMPT_BKL)
# define in_atomic() ((preempt_count() & ~PREEMPT_ACTIVE) != kernel_locked())
#else
# define in_atomic() ((preempt_count() & ~PREEMPT_ACTIVE) != 0)
#endif
定義於 include/linux/hardirq.h
#define preempt_count() (current_thread_info()->preempt_count)
定義於 include/linux/preempt.h
當核心要進行 schedule 時,會先檢查目前是否為不可中斷的行程(in_atomic)
若為真,則印出 "scheduling while atomic" 的訊息,表示此時不應該做排程
而 in_atomic 則是判斷 kernel preemption 是否為關閉的狀態
也就是關閉 preemption 是為了讓行程能夠不被打斷的執行,直到完成其工作
換言之,開啟 preemption 就允許其他行程取代目前行程,故可進行排程
以下的程式片段在定義 CONFIG_PREEMPT 的核心會造成 "scheduling while atomic" 錯誤:
(若未定義 CONFIG_PREEMPT 就不是 in_atomic 的程序)
struct semaphore _sem;
spinlock_t _lock;
spin_lock_init( &_lock );
sema_init(&_sem, 0);
spin_lock( &_lock );
down_interruptible( &_sem ); /* 在 Spin lock 中休眠 */
spin_unlock( &_lock );
其中 x 為行程的名稱 (current->comm)
y 為16進制的 preempt_count
z 為 PID
出現此訊息時,表示目前的 kernel task 是一個不可被中斷的行程,但卻被排程進入休眠。
以下為核心列印該錯誤訊息的程式片段:
/*
* schedule() is the main scheduler function.
*/
asmlinkage void __sched schedule(void)
{
long *switch_count;
task_t *prev, *next;
runqueue_t *rq;
prio_array_t *array;
struct list_head *queue;
unsigned long long now;
unsigned long run_time;
int cpu, idx;
/*
* Test if we are atomic. Since do_exit() needs to call into
* schedule() atomically, we ignore that path for now.
* Otherwise, whine if we are scheduling when we should not be.
*/
if (likely(!current->exit_state)) {
if (unlikely(in_atomic())) {
printk(KERN_ERR "scheduling while atomic: "
"%s/0x%08x/%d\n",
current->comm, preempt_count(), current->pid);
dump_stack();
}
}
profile_hit(SCHED_PROFILING, __builtin_return_address(0));
... /* 以下略 */
}
定義於 kernel/sched.c
#if defined(CONFIG_PREEMPT) && !defined(CONFIG_PREEMPT_BKL)
# define in_atomic() ((preempt_count() & ~PREEMPT_ACTIVE) != kernel_locked())
#else
# define in_atomic() ((preempt_count() & ~PREEMPT_ACTIVE) != 0)
#endif
定義於 include/linux/hardirq.h
#define preempt_count() (current_thread_info()->preempt_count)
定義於 include/linux/preempt.h
當核心要進行 schedule 時,會先檢查目前是否為不可中斷的行程(in_atomic)
若為真,則印出 "scheduling while atomic" 的訊息,表示此時不應該做排程
而 in_atomic 則是判斷 kernel preemption 是否為關閉的狀態
也就是關閉 preemption 是為了讓行程能夠不被打斷的執行,直到完成其工作
換言之,開啟 preemption 就允許其他行程取代目前行程,故可進行排程
以下的程式片段在定義 CONFIG_PREEMPT 的核心會造成 "scheduling while atomic" 錯誤:
(若未定義 CONFIG_PREEMPT 就不是 in_atomic 的程序)
struct semaphore _sem;
spinlock_t _lock;
spin_lock_init( &_lock );
sema_init(&_sem, 0);
spin_lock( &_lock );
down_interruptible( &_sem ); /* 在 Spin lock 中休眠 */
spin_unlock( &_lock );
2010年2月25日 星期四
Linux Synchronization - 3
Spin lock 的變化型:
‧ spin_lock(spinlock_t *lock) & spin_unlock(spinlock_t *lock)
最基本的 Spin lock 函式,用來取得或釋放 lock。適用於多處理器系統,且共用資源會被不同的 CPU 執行的行程同時存取的情況。
‧ spin_lock_bh(spinlock_t *lock) & spin_unlock_bh(spinlock_t *lock)
加上 Local softirq disabling 的 Spin lock 函式,關閉 Bottom half 的程序並取得 lock,或是釋放 lock 並開啟 Bottom half 的程序。
適用於多處理器系統,且共用資源會被 Bottom half 的程序所存取的情況。
spin_lock_bh(l) = local_bh_disable() + spin_lock(l)
spin_unlock_bh(l) = spin_unlock(l) + local_bh_enable()
‧ spin_lock_irq(spinlock_t *lock) & spin_unlock_irq(spinlock_t *lock)
加上 Local interrupt disabling 的 Spin lock 函式,關閉 CPU 的中斷並取得 lock,或是釋放 lock 並開啟 CPU 的中斷。
適用於多處理器系統,且共用資源會被中斷處理函式所存取的情況。
spin_lock_irq(l) = local_irq_disable() + spin_lock(l)
spin_unlock_irq(l) = spin_unlock(l) + local_irq_enable()
‧ spin_lock_irqsave(spinlock_t *lock, unsigned long flags) & spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
加上 Local interrupt disabling 的 Spin lock 函式,關閉 CPU 的中斷並取得 lock,或是釋放 lock 並開啟 CPU 的中斷。
此外,在關閉中斷前會儲存中斷的狀態,以及使用該儲存的狀態來開啟中斷。
適用於多處理器系統,且共用資源會被中斷處理函式所存取,同時中斷會在呼叫 Spin lock 之前就己經關閉的場合。
這一組 spin lock 會有當釋放 lock 時,不會再啟動中斷的效果(例如巢狀的中斷)。
spin_lock_irqsave(l, f) = local_irq_save(f) + spin_lock(l)
spin_unlock_irqrestore(l, f) = spin_unlock(l) + local_irq_restore(f)
使用 Spin lock 的注意事項:
握有 lock 的行程不能進入休眠狀態或是被另一行程搶走 CPU 執行權,否則可能會讓其他想要取得同一個 lock 的行程
進入無窮的 "忙碌迴圈" 之中 ,CPU資源就會完全地被該行程給吃掉(因為握有 lock 的行程不知何時才會釋放 lock)。
‧ spin_lock(spinlock_t *lock) & spin_unlock(spinlock_t *lock)
最基本的 Spin lock 函式,用來取得或釋放 lock。適用於多處理器系統,且共用資源會被不同的 CPU 執行的行程同時存取的情況。
‧ spin_lock_bh(spinlock_t *lock) & spin_unlock_bh(spinlock_t *lock)
加上 Local softirq disabling 的 Spin lock 函式,關閉 Bottom half 的程序並取得 lock,或是釋放 lock 並開啟 Bottom half 的程序。
適用於多處理器系統,且共用資源會被 Bottom half 的程序所存取的情況。
spin_lock_bh(l) = local_bh_disable() + spin_lock(l)
spin_unlock_bh(l) = spin_unlock(l) + local_bh_enable()
‧ spin_lock_irq(spinlock_t *lock) & spin_unlock_irq(spinlock_t *lock)
加上 Local interrupt disabling 的 Spin lock 函式,關閉 CPU 的中斷並取得 lock,或是釋放 lock 並開啟 CPU 的中斷。
適用於多處理器系統,且共用資源會被中斷處理函式所存取的情況。
spin_lock_irq(l) = local_irq_disable() + spin_lock(l)
spin_unlock_irq(l) = spin_unlock(l) + local_irq_enable()
‧ spin_lock_irqsave(spinlock_t *lock, unsigned long flags) & spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags)
加上 Local interrupt disabling 的 Spin lock 函式,關閉 CPU 的中斷並取得 lock,或是釋放 lock 並開啟 CPU 的中斷。
此外,在關閉中斷前會儲存中斷的狀態,以及使用該儲存的狀態來開啟中斷。
適用於多處理器系統,且共用資源會被中斷處理函式所存取,同時中斷會在呼叫 Spin lock 之前就己經關閉的場合。
這一組 spin lock 會有當釋放 lock 時,不會再啟動中斷的效果(例如巢狀的中斷)。
spin_lock_irqsave(l, f) = local_irq_save(f) + spin_lock(l)
spin_unlock_irqrestore(l, f) = spin_unlock(l) + local_irq_restore(f)
使用 Spin lock 的注意事項:
握有 lock 的行程不能進入休眠狀態或是被另一行程搶走 CPU 執行權,否則可能會讓其他想要取得同一個 lock 的行程
進入無窮的 "忙碌迴圈" 之中 ,CPU資源就會完全地被該行程給吃掉(因為握有 lock 的行程不知何時才會釋放 lock)。
2010年2月21日 星期日
Linux Synchronization - 2
以下為 Spin lock 不同的實作方式:
1. Uni-processor 且為 Non-preemptive 的系統 (CONFIG_SMP 及 CONFIG_PREEMPT 均未定義)
Spin lock 完全無作用——因為在這種系統中,共用資源在同一時間不可能會被兩個以上的行程所存取,故不需要實作出 lock 的保護功能。
typedef struct { } spinlock_t; /* 內容為空的結構 */
#define spin_lock(lock) _spin_lock(lock)
#define spin_unlock(lock) _spin_unlock(lock)
#define _spin_lock(lock) \
do { \
preempt_disable(); \
_raw_spin_lock(lock); \
__acquire(lock); \
} while(0)
#define _spin_unlock(lock) \
do { \
_raw_spin_unlock(lock); \
preempt_enable(); \
__release(lock); \
} while (0)
#define _raw_spin_lock(lock) do { (void)(lock); } while(0)
#define _raw_spin_unlock(lock) do { (void)(lock); } while(0)
定義於 include/linux/spinlock.h
#define preempt_disable() do { } while (0)
#define preempt_enable() do { } while (0)
定義於 include/linux/preempt.h
#define __acquire(x) (void)0
#define __release(x) (void)0
定義於 include/linux/compiler.h
2. Uni-processor 且為 Preemptive 的系統 (定義 CONFIG_PREEMPT 且 CONFIG_SMP 未定義)
Spin lock 實作成開、關 Kernel preemption 的功能,目的也是讓共用資源在同一時間不會被兩個以上的行程所存取。
typedef struct { } spinlock_t; /* 內容為空的結構 */
#define spin_lock(lock) _spin_lock(lock)
#define spin_unlock(lock) _spin_unlock(lock)
#define _spin_lock(lock) \
do { \
preempt_disable(); \
_raw_spin_lock(lock); \
__acquire(lock); \
} while(0)
#define _spin_unlock(lock) \
do { \
_raw_spin_unlock(lock); \
preempt_enable(); \
__release(lock); \
} while (0)
#define _raw_spin_lock(lock) do { (void)(lock); } while(0)
#define _raw_spin_unlock(lock) do { (void)(lock); } while(0)
定義於 include/linux/spinlock.h
#define preempt_disable() \
do { \
inc_preempt_count(); \
barrier(); \
} while (0)
#define preempt_enable() \
do { \
preempt_enable_no_resched(); \
preempt_check_resched(); \
} while (0)
定義於 include/linux/preempt.h
#define __acquire(x) (void)0
#define __release(x) (void)0
定義於 include/linux/compiler.h
3. Multi-processor 且為 Non-preemptive 的系統 (定義 CONFIG_SMP 且 CONFIG_PREEMPT 未定義)
Spin lock 有實作出 "忙碌迴圈" 的功能,用來鎖住想要使用共用資源卻又無法取得 lock 的行程。
typedef struct {
volatile unsigned int slock;
} spinlock_t;
static inline void _raw_spin_lock(spinlock_t *lock)
{
__asm__ __volatile__(
spin_lock_string
:"=m" (lock->slock) : : "memory"
);
}
static inline void _raw_spin_unlock(spinlock_t *lock)
{
char oldval = 1;
__asm__ __volatile__(
spin_unlock_string
);
}
#define spin_lock_string \
"\n1:\t" \
"lock ; decb %0\n\t" \
"jns 3f\n" \
"2:\t" \
"rep;nop\n\t" \
"cmpb $0,%0\n\t" \
"jle 2b\n\t" \
"jmp 1b\n" \
"3:\n\t"
#define spin_unlock_string \
"xchgb %b0, %1" \
:"=q" (oldval), "=m" (lock->slock) \
:"0" (oldval) : "memory"
定義於 include/asm-i386/spinlock.h
#define spin_lock(lock) _spin_lock(lock)
#define spin_unlock(lock) _spin_unlock(lock)
定義於 include/linux/spinlock.h
void __lockfunc _spin_lock(spinlock_t *lock)
{
preempt_disable();
_raw_spin_lock(lock);
}
EXPORT_SYMBOL(_spin_lock);
void __lockfunc _spin_unlock(spinlock_t *lock)
{
_raw_spin_unlock(lock);
preempt_enable();
}
EXPORT_SYMBOL(_spin_unlock);
定義於 kernel/spinlock.c
#define preempt_disable() do { } while (0)
#define preempt_enable() do { } while (0)
定義於 include/linux/preempt.h
4. Multi-processor 且為 Preemptive 的系統 (定義 CONFIG_SMP 及 CONFIG_PREEMPT)
Spin lock 有實作出 "忙碌迴圈" 的功能,而且被鎖住的行程,可以讓其他行程搶走 CPU 的執行權。
typedef struct {
volatile unsigned int slock;
unsigned int break_lock; /* #ifdef CONFIG_PREEMPT */
} spinlock_t;
static inline int _raw_spin_trylock(spinlock_t *lock)
{
char oldval;
__asm__ __volatile__(
"xchgb %b0,%1"
:"=q" (oldval), "=m" (lock->slock)
:"0" (0) : "memory"
);
return oldval > 0;
}
static inline void _raw_spin_unlock(spinlock_t *lock)
{
char oldval = 1;
__asm__ __volatile__(
spin_unlock_string
);
}
#define spin_unlock_string \
"xchgb %b0, %1" \
:"=q" (oldval), "=m" (lock->slock) \
:"0" (oldval) : "memory"
#define spin_is_locked(x) (*(volatile signed char *)(&(x)->slock) <= 0)
定義於 include/asm-i386/spinlock.h
#define spin_lock(lock) _spin_lock(lock)
#define spin_unlock(lock) _spin_unlock(lock)
#define spin_can_lock(lock) (!spin_is_locked(lock))
定義於 include/linux/spinlock.h
#define BUILD_LOCK_OPS(op, locktype) \
void __lockfunc _##op##_lock(locktype##_t *lock) \
{ \
preempt_disable(); \
for (;;) { \
if (likely(_raw_##op##_trylock(lock))) \
break; \
preempt_enable(); \
if (!(lock)->break_lock) \
(lock)->break_lock = 1; \
while (!op##_can_lock(lock) && (lock)->break_lock) \
cpu_relax(); \
preempt_disable(); \
} \
} \
\
EXPORT_SYMBOL(_##op##_lock);
BUILD_LOCK_OPS(spin, spinlock); /* #ifdef CONFIG_PREEMPT */
void __lockfunc _spin_unlock(spinlock_t *lock)
{
_raw_spin_unlock(lock);
preempt_enable();
}
EXPORT_SYMBOL(_spin_unlock);
定義於 kernel/spinlock.c
#define preempt_disable() \
do { \
inc_preempt_count(); \
barrier(); \
} while (0)
#define preempt_enable() \
do { \
preempt_enable_no_resched(); \
preempt_check_resched(); \
} while (0)
定義於 include/linux/preempt.h
1. Uni-processor 且為 Non-preemptive 的系統 (CONFIG_SMP 及 CONFIG_PREEMPT 均未定義)
Spin lock 完全無作用——因為在這種系統中,共用資源在同一時間不可能會被兩個以上的行程所存取,故不需要實作出 lock 的保護功能。
typedef struct { } spinlock_t; /* 內容為空的結構 */
#define spin_lock(lock) _spin_lock(lock)
#define spin_unlock(lock) _spin_unlock(lock)
#define _spin_lock(lock) \
do { \
preempt_disable(); \
_raw_spin_lock(lock); \
__acquire(lock); \
} while(0)
#define _spin_unlock(lock) \
do { \
_raw_spin_unlock(lock); \
preempt_enable(); \
__release(lock); \
} while (0)
#define _raw_spin_lock(lock) do { (void)(lock); } while(0)
#define _raw_spin_unlock(lock) do { (void)(lock); } while(0)
定義於 include/linux/spinlock.h
#define preempt_disable() do { } while (0)
#define preempt_enable() do { } while (0)
定義於 include/linux/preempt.h
#define __acquire(x) (void)0
#define __release(x) (void)0
定義於 include/linux/compiler.h
2. Uni-processor 且為 Preemptive 的系統 (定義 CONFIG_PREEMPT 且 CONFIG_SMP 未定義)
Spin lock 實作成開、關 Kernel preemption 的功能,目的也是讓共用資源在同一時間不會被兩個以上的行程所存取。
typedef struct { } spinlock_t; /* 內容為空的結構 */
#define spin_lock(lock) _spin_lock(lock)
#define spin_unlock(lock) _spin_unlock(lock)
#define _spin_lock(lock) \
do { \
preempt_disable(); \
_raw_spin_lock(lock); \
__acquire(lock); \
} while(0)
#define _spin_unlock(lock) \
do { \
_raw_spin_unlock(lock); \
preempt_enable(); \
__release(lock); \
} while (0)
#define _raw_spin_lock(lock) do { (void)(lock); } while(0)
#define _raw_spin_unlock(lock) do { (void)(lock); } while(0)
定義於 include/linux/spinlock.h
#define preempt_disable() \
do { \
inc_preempt_count(); \
barrier(); \
} while (0)
#define preempt_enable() \
do { \
preempt_enable_no_resched(); \
preempt_check_resched(); \
} while (0)
定義於 include/linux/preempt.h
#define __acquire(x) (void)0
#define __release(x) (void)0
定義於 include/linux/compiler.h
3. Multi-processor 且為 Non-preemptive 的系統 (定義 CONFIG_SMP 且 CONFIG_PREEMPT 未定義)
Spin lock 有實作出 "忙碌迴圈" 的功能,用來鎖住想要使用共用資源卻又無法取得 lock 的行程。
typedef struct {
volatile unsigned int slock;
} spinlock_t;
static inline void _raw_spin_lock(spinlock_t *lock)
{
__asm__ __volatile__(
spin_lock_string
:"=m" (lock->slock) : : "memory"
);
}
static inline void _raw_spin_unlock(spinlock_t *lock)
{
char oldval = 1;
__asm__ __volatile__(
spin_unlock_string
);
}
#define spin_lock_string \
"\n1:\t" \
"lock ; decb %0\n\t" \
"jns 3f\n" \
"2:\t" \
"rep;nop\n\t" \
"cmpb $0,%0\n\t" \
"jle 2b\n\t" \
"jmp 1b\n" \
"3:\n\t"
#define spin_unlock_string \
"xchgb %b0, %1" \
:"=q" (oldval), "=m" (lock->slock) \
:"0" (oldval) : "memory"
定義於 include/asm-i386/spinlock.h
#define spin_lock(lock) _spin_lock(lock)
#define spin_unlock(lock) _spin_unlock(lock)
定義於 include/linux/spinlock.h
void __lockfunc _spin_lock(spinlock_t *lock)
{
preempt_disable();
_raw_spin_lock(lock);
}
EXPORT_SYMBOL(_spin_lock);
void __lockfunc _spin_unlock(spinlock_t *lock)
{
_raw_spin_unlock(lock);
preempt_enable();
}
EXPORT_SYMBOL(_spin_unlock);
定義於 kernel/spinlock.c
#define preempt_disable() do { } while (0)
#define preempt_enable() do { } while (0)
定義於 include/linux/preempt.h
4. Multi-processor 且為 Preemptive 的系統 (定義 CONFIG_SMP 及 CONFIG_PREEMPT)
Spin lock 有實作出 "忙碌迴圈" 的功能,而且被鎖住的行程,可以讓其他行程搶走 CPU 的執行權。
typedef struct {
volatile unsigned int slock;
unsigned int break_lock; /* #ifdef CONFIG_PREEMPT */
} spinlock_t;
static inline int _raw_spin_trylock(spinlock_t *lock)
{
char oldval;
__asm__ __volatile__(
"xchgb %b0,%1"
:"=q" (oldval), "=m" (lock->slock)
:"0" (0) : "memory"
);
return oldval > 0;
}
static inline void _raw_spin_unlock(spinlock_t *lock)
{
char oldval = 1;
__asm__ __volatile__(
spin_unlock_string
);
}
#define spin_unlock_string \
"xchgb %b0, %1" \
:"=q" (oldval), "=m" (lock->slock) \
:"0" (oldval) : "memory"
#define spin_is_locked(x) (*(volatile signed char *)(&(x)->slock) <= 0)
定義於 include/asm-i386/spinlock.h
#define spin_lock(lock) _spin_lock(lock)
#define spin_unlock(lock) _spin_unlock(lock)
#define spin_can_lock(lock) (!spin_is_locked(lock))
定義於 include/linux/spinlock.h
#define BUILD_LOCK_OPS(op, locktype) \
void __lockfunc _##op##_lock(locktype##_t *lock) \
{ \
preempt_disable(); \
for (;;) { \
if (likely(_raw_##op##_trylock(lock))) \
break; \
preempt_enable(); \
if (!(lock)->break_lock) \
(lock)->break_lock = 1; \
while (!op##_can_lock(lock) && (lock)->break_lock) \
cpu_relax(); \
preempt_disable(); \
} \
} \
\
EXPORT_SYMBOL(_##op##_lock);
BUILD_LOCK_OPS(spin, spinlock); /* #ifdef CONFIG_PREEMPT */
void __lockfunc _spin_unlock(spinlock_t *lock)
{
_raw_spin_unlock(lock);
preempt_enable();
}
EXPORT_SYMBOL(_spin_unlock);
定義於 kernel/spinlock.c
#define preempt_disable() \
do { \
inc_preempt_count(); \
barrier(); \
} while (0)
#define preempt_enable() \
do { \
preempt_enable_no_resched(); \
preempt_check_resched(); \
} while (0)
定義於 include/linux/preempt.h
2010年2月1日 星期一
Linux Synchronization - 1
以下列出 Kernel 的同步機制:
1. Per-CPU variables
將 Kernel 中的變數宣告成 per-CPU 的陣列型式,每一個 CPU 都有屬於自己的變數空間,不會和其他的CPU共用。
2. Atomic operations
讓存取記憶體的指令能夠完整的執行完成,不會被中斷或其他的 CPU 指令所打斷。
3. Memory barriers
利用一些 Macro 安插於某段程式中,確保程式能如預期的順序來執行。
4. Spin lock
以 lock 的方法來保護特定資源只能被一個程序使用,其他欲使用該資源的程序會進入 "忙碌迴圈" 等待資源被釋放為止。
5. Semaphore
類似 Spin lock 的保護方法,但待等特定資源的行程會進入休眠狀態,也就是要讓出 CPU 使用權,因此不可用於中斷處理函式。
6. Seqlock
類似 Spin lock 的原理,將 lock 分為 reader 及 writer 兩種,分別用來保護資源被謮與寫的動作。而 writer 有高於 reader 的優先權,亦即同時發生讀、寫同一資源時,寫的動作會優先執行。
7. Local interrupt disabling
利用關閉目前 CPU 的中斷處理函式來保護程序不會被其他中斷所打斷,但是在多 CPU 的機器上不能使其他 CPU 關閉中斷。
8. Local softirq disabling
利用停止延遲執行的中斷處理函式(例如 Bottom Half),來保護特定資源不會被其他的延遲執行的中斷處理函式所存取,而且此種方式不會關閉中斷。
9. Read-Copy Update (RCU)
利用指標的資料結構,當某指標發生被寫的動作時,將該指標複製一份(例如拷貝指標所指向的記憶體),然後對複本進行存取。完成寫的動作後,再更新該指標成為修改過的複本(例如將原來的指標改成複製後的記憶體位址)。
下表列出如何使用同步機制來保護 Kernel data structure:
1. Per-CPU variables
將 Kernel 中的變數宣告成 per-CPU 的陣列型式,每一個 CPU 都有屬於自己的變數空間,不會和其他的CPU共用。
2. Atomic operations
讓存取記憶體的指令能夠完整的執行完成,不會被中斷或其他的 CPU 指令所打斷。
3. Memory barriers
利用一些 Macro 安插於某段程式中,確保程式能如預期的順序來執行。
4. Spin lock
以 lock 的方法來保護特定資源只能被一個程序使用,其他欲使用該資源的程序會進入 "忙碌迴圈" 等待資源被釋放為止。
5. Semaphore
類似 Spin lock 的保護方法,但待等特定資源的行程會進入休眠狀態,也就是要讓出 CPU 使用權,因此不可用於中斷處理函式。
6. Seqlock
類似 Spin lock 的原理,將 lock 分為 reader 及 writer 兩種,分別用來保護資源被謮與寫的動作。而 writer 有高於 reader 的優先權,亦即同時發生讀、寫同一資源時,寫的動作會優先執行。
7. Local interrupt disabling
利用關閉目前 CPU 的中斷處理函式來保護程序不會被其他中斷所打斷,但是在多 CPU 的機器上不能使其他 CPU 關閉中斷。
8. Local softirq disabling
利用停止延遲執行的中斷處理函式(例如 Bottom Half),來保護特定資源不會被其他的延遲執行的中斷處理函式所存取,而且此種方式不會關閉中斷。
9. Read-Copy Update (RCU)
利用指標的資料結構,當某指標發生被寫的動作時,將該指標複製一份(例如拷貝指標所指向的記憶體),然後對複本進行存取。完成寫的動作後,再更新該指標成為修改過的複本(例如將原來的指標改成複製後的記憶體位址)。
下表列出如何使用同步機制來保護 Kernel data structure:
2010年1月31日 星期日
Linux Interrupts and Exceptions - 5
Kernel 使用 CPU 資源的策略如下:
1. 若 CPU 在執行 User mode 程式時有中斷發生,則切換 CPU 執行權去處理中斷要求。
2. 若 CPU 在執行 Kernel mode 程式時有中斷發生,則切換 CPU 執行權去處理中斷要求。
3. 若 CPU 在處理中斷服務時有另一個中斷發生,則切換 CPU 執行權去處理新的中斷要求,接著再繼續原來的中斷服務。
4. 中斷請求結束後,CPU 可能會去處理其他的 Kernel mode 程式,而不是原來在中斷發生前正在處理的 Kernel mode 程式。
以上規則 1~3 就是核心對巢狀 Exception 及 Interrupt 的處理方式,而規則 4 則是 Kernel preemption 的特性—在 Kernel mode 中,
一個正在執行的 process 可以被另一個 process (有較高的執行優先權) 所取代,也就是行程可搶奪 CPU 的使用權。
具有此特性的核心適用於硬體動作頻繁的場合,可使得正在等待 I/O 動作的行程讓出 CPU。
1. 若 CPU 在執行 User mode 程式時有中斷發生,則切換 CPU 執行權去處理中斷要求。
2. 若 CPU 在執行 Kernel mode 程式時有中斷發生,則切換 CPU 執行權去處理中斷要求。
3. 若 CPU 在處理中斷服務時有另一個中斷發生,則切換 CPU 執行權去處理新的中斷要求,接著再繼續原來的中斷服務。
4. 中斷請求結束後,CPU 可能會去處理其他的 Kernel mode 程式,而不是原來在中斷發生前正在處理的 Kernel mode 程式。
以上規則 1~3 就是核心對巢狀 Exception 及 Interrupt 的處理方式,而規則 4 則是 Kernel preemption 的特性—在 Kernel mode 中,
一個正在執行的 process 可以被另一個 process (有較高的執行優先權) 所取代,也就是行程可搶奪 CPU 的使用權。
具有此特性的核心適用於硬體動作頻繁的場合,可使得正在等待 I/O 動作的行程讓出 CPU。
訂閱:
文章 (Atom)