你可以使用mod_timer()修改my_timer的到期時間,使用del_timer()取消定時器,或使用timer_pending()以檢視my_timer目前是否處于pending狀态。檢視kernel/timer.c源代碼,你會發現schedule_timeout()内部就使用了這些API。
使用者空間的clock_settime()和clock_gettime()函數可用于獲得核心定時器服務。使用者應用程式可以使用setitimer()和getitimer()來控制一個alarm信号在特定的逾時後發生。
短延時
在核心中,小于jiffy的延時被認為是短延時。這種延時在程序或中斷上下文都可能發生。由于不可能使用基于jiffy的方法實作短延時,之前讨論的睡眠等待将不再能用于小的逾時。這種情況下,唯一的解決途徑就是忙等待。
實作短延時的核心API包括mdelay()、udelay()和ndelay(),分别支援毫秒、微妙和納秒級的延時。這些函數的實際實作依賴于體系結構,而且也并非在所有平台上都被完整實作。忙等待的實作方法是測量CPU執行一條指令的時間,為了延時,執行一定數量的指令。從前文可知,核心會在啟動過程中進行測量出一個loops_per_jiffy值。短延時API就使用了loops_per_jiffy值來決定它們需要進行循環的數量。為了實作握手程序中1微妙的延時,USB主機控制器驅動(drivers/usb/host/ehci-hcd.c)會調用udelay(),而udelay()的内部會調用loops_per_jiffy:
result = ehci_readl(ehci, ptr);
/* ... */
if (result == done) return 0;
udelay(1); /* Internally uses loops_per_jiffy */
usec--;
} while (usec > 0);
Pentium時間戳計數器
時間戳計數器(TSC)是Pentium相容處理器中的一個計數器,它記錄自啟動以來CPU消耗的時鐘周期數。由于TSC睡着處理器周期速率的比例正常,它提供了非常高的精确度。TSC通常被用于剖析和監測代碼。使用rdtsc指令可測量某段代碼的執行時間,其精度達到微妙級。TSC的節拍可以被轉化為秒,方法是将其除以CPU時鐘速率(包含在核心變量cpu_khz中)。
在如下的代碼片段中。low_tsc_ticks和high_tsc_ticks分别包含了TSC的低32位和高32位。低32位可能在數秒内溢出(具體時間依賴于處理器速度),但是這已經用于許多代碼的剖析了:
unsigned long low_tsc_ticks0, high_tsc_ticks0;
unsigned long low_tsc_ticks1, high_tsc_ticks1;
unsigned long exec_time;
rdtsc(low_tsc_ticks0, high_tsc_ticks0); /* Timestamp
before */
printk("Hello World\n"); /* Code to be
profiled */
rdtsc(low_tsc_ticks1, high_tsc_ticks1); /* Timestamp after */
exec_time = low_tsc_ticks1 - low_tsc_ticks0;
在1.8GHz Pentium 處理器上,exec_time的結果為871(或半微妙)。
在2.6.21核心中,針對高精度定時器的支援(CONFIG_HIGH_RES_TIMERS)已經被融入了核心。它使用了硬體特定的高速定時器來提供對nanosleep()等API高精度的支援。在基于Pentium的機器上,核心借助的是TSC。
實時鐘
RTC在非易失性存儲器上記錄絕對時間。在x86 PC上,RTC位于由電池供電[4]的互補金屬氧化物半導體(CMOS)存儲器的頂部。從第5章《字元裝置驅動》的圖5.1可以看出傳統PC體系結構中CMOS的位置。在嵌入式系統中,RTC可能被內建到處理器中,也可能通過I2C或SPI總線在外部連接配接,見第8章。
[4]RTC的電池能夠持續使用很多年,通過會超過電腦的使用壽命,是以,你從來都不需要替換它。
使用RTC,你可以完成如下工作:
(2)産生頻率從2HZ到8192HZ之間的周期性中斷;
(3)設定alarm
許多應用程式需要使用絕對時間或稱牆上時間(wall time)。jiffies是相對于系統啟動後的時間,它不包含牆上時間。核心将牆上時間記錄在xtime變量中,在啟動過程中,會根據從RTC讀取到的目前的牆上時間初始化xtime,在系統停機後,牆上時間會被寫回RTC。你可以使用do_gettimeofday()讀取牆上時間,其最高精度由硬體決定:
static struct timeval curr_time;
do_gettimeofday(&curr_time);
my_timestamp = cpu_to_le32(curr_time.tv_sec); /* Record timestamp */
使用者空間也包含一系列可以通路牆上時間的函數,包括:
(1) time(),該函數傳回月曆時間,或從Epoch(1970年1月1日00:00:00)以來經曆的秒數;
(2) localtime(),以分散的形式傳回月曆時間;
(3) mktime(), 進行localtime()的反向工作;
(4) gettimeofday(),如果你的平台支援的話,該函數将以微妙精度傳回月曆時間。
核心中的并發
随着多核筆記本電腦時代的到來,對稱多處理器(SMP)的使用不再被限于高科技使用者。SMP和核心搶占是多線程執行的2種場景。多個線程能夠同時操作共享的核心資料結構,是以,對這些資料結構的通路必須被串行化。
接下來,我們會讨論并發通路情況下保護共享核心資源的基本概念。我們以一個簡單的例子開始,并逐漸引入中斷、核心搶占和SMP等複雜概念。
自選鎖可以確定在同時隻有一個線程進入臨界區。其他想進入臨界區的線程必須不停地原地打轉,知道第1個線程釋放自選鎖。
注意:這裡所說的線程不僅限于核心線程,還包含使用者線程進入核心後的代表。
#include <linux/spinlock.h>
spinlock_t mylock = SPIN_LOCK_UNLOCKED; /* Initialize */
/* Acquire the spinlock. This is inexpensive if there
* is no one inside the critical section. In the face of
* contention, spinlock() has to busy-wait.
*/
spin_lock(&mylock);
/* ... Critical Section code ... */
spin_unlock(&mylock); /* Release the lock */
與自選鎖不同的是,互斥體在進入一個被占用的臨界區之前,不會原地打轉而是使目前線程進入睡眠狀态。如果要等待的時間較長,互斥體比自選鎖會更合适,因為自選鎖會消耗CPU資源。在使用互斥體的場合,多于2次程序切換時間都可被認為是長時間,是以一個互斥體會引起本線程睡眠,而當其被喚醒時,它需要被切換回來。
是以,在很多情況下,決定使用自選鎖還是互斥體相對來說很容易:
(2)由于互斥體會在面臨競争的情況下将目前線程置于睡眠狀态,是以,在中斷處理函數中,隻能使用自選鎖。(在第4章中,你将學習到更多的關于中斷上下文的限制。)
#include <linux/mutex.h>
/* Statically declare a mutex. To dynamically
create a mutex, use mutex_init() */
static DEFINE_MUTEX(mymutex);
/* Acquire the mutex. This is inexpensive if there
* contention, mutex_lock() puts the calling thread to sleep.
mutex_lock(&mymutex);
mutex_unlock(&mymutex); /* Release the mutex */
(1)非搶占核心,單CPU情況下存在于程序上下文的臨界區;
(2)非搶占核心,單CPU情況下存在于程序和中斷上下文的臨界區;
(3)可搶占核心,單CPU情況下存在于程序和中斷上下文的臨界區;
(4)可搶占核心,SMP情況下存在于程序和中斷上下文的臨界區。
互斥體接口代替了老的信号量接口(semaphore),它互斥體誕生于-rt樹,在2.6.16核心中被融入主線核心。
盡管如此,但是老的信号量仍然在核心和驅動中被廣泛使用。信号量接口的基本用法如下:
#include <asm/semaphore.h> /* Architecture dependent
header */
/* Statically declare a semaphore. To dynamically
create a semaphore, use init_MUTEX() */
static DECLARE_MUTEX(mysem);
down(&mysem); /* Acquire the semaphore */
up(&mysem); /* Release the semaphore */
案例1:程序上下文,單CPU,非搶占核心
這種情況最為簡單,不需要加鎖,是以不再贅述。
案例2:程序和上下文,單CPU,非搶占核心
在這種情況下,為了保護臨界區,僅僅需要禁止中斷。如圖2.4,假定程序上下文的執行單元A、B以及中斷上下文的執行單元C都企圖進入相同的臨界區。
Point A:
local_irq_disable(); /* Disable Interrupts in local CPU */
/* ... Critical Section ... */
local_irq_enable(); /* Enable Interrupts in local CPU */
但是,如果當執行到Point A的時候已經被禁止,local_irq_enable()将産生副作用,它會重新使能中斷,而不是恢複之前的中斷狀态。可以這樣修複它:
Point A:
local_irq_save(flags); /* Disable Interrupts */
/* ... Critical Section ... */
local_irq_restore(flags); /* Restore state to what
it was at Point A */
不論Point A的中斷處于什麼狀态,上述工作都将正确執行。
案例3:程序和中斷上下文,單CPU,搶占核心
如果核心使能了搶占,僅僅禁止中斷将不再能確定對臨界區的保護,因為另一個處于程序上下文的執行單元可能會進入臨界區。重新回到圖2.4,現在,除了C以外,執行單元A和B必須提防彼此。顯而易見,解決該問題的方法是在進入臨界區之前禁止核心搶占、中斷,并在退出臨界區的時候恢複核心搶占和中斷。是以,執行單元A和B使用了自選鎖API附帶irq的變體:
unsigned long flags;
/* Save interrupt state.
* Disable interrupts - this implicitly disables preemption */
spin_lock_irqsave(&mylock, flags);
/* Restore interrupt state to what it was at Point A */
spin_unlock_irqrestore(&mylock, flags);
我們不需要在最後顯示地恢複Point A的搶占狀态,因為核心自身會通過一個名叫搶占計數器的變量維護它。在搶占被禁止時(通過調用preempt_disable()),計數器會增加;在搶占被使能時(通過調用preempt_enable()),計數器被減少。隻有在計數器為0的時候,搶占才發揮作用。
案例4:程序和中斷上下文,SMP機器,搶占核心
/*
- Save interrupt state on the local CPU
- Disable interrupts on the local CPU. This implicitly disables
preemption.
- Lock the section to regulate access by other CPUs
*/
- Restore interrupt state and preemption to what it
was at Point A for the local CPU
- Release the lock
在SMP系統上,擷取自旋鎖時,僅僅本CPU上的中斷被禁止。是以,一個程序上下文的執行單元(圖2.4中的執行單元A)在一個CPU上運作的同時,一個中斷處理函數(圖2.4中的執行單元C)可能運作在另一個CPU上。非本CPU上的中斷處理函數必須自旋等待本CPU上的程序上下文代碼退出臨界區。中斷上下文需要調用spin_lock()/spin_unlock():
/* ... Critical Section ... */
spin_unlock(&mylock);
除了有irq變體以外,自旋鎖也有底半部(BH)變體。在鎖被擷取的時候,spin_lock_bh()會禁止底半部,而spin_unlock_bh()則會在鎖被釋放時重新使能底半部。我們将在第4章讨論底半部。
-rt樹
原子操作
if (!skb->cloned ||
/* Atomically decrement and check if the returned value is zero */
!atomic_sub_return(skb->nohdr ? (1 << SKB_DATAREF_SHIFT) + 1 :
1,&skb_shinfo(skb)->dataref)) {
kfree(skb->head);
}
當skb_release_data()執行的時候,另一個調用skbuff_clone()(也定義于net/core/skbuff.c)的執行單元也許在同步地增加資料引用計數:
/* ... */
/* Atomically bump up the data reference count */
atomic_inc(&(skb_shinfo(skb)->dataref));
核心也支援set_bit()、clear_bit()和test_and_set_bit()操作,它們可用于原子地進行位修改。檢視include/asm-your-arch/atomic.h檔案可以看出你所在體系結構所支援的原子操作。
另一個特定的并發保護機制是自旋鎖的變體讀者—寫者鎖。如果每個執行單元在通路臨界區的時候要麼是讀,要麼是寫共享的資料結構,但是它們都不會同時進行讀和寫操作,這種鎖是最好的選擇。多則讀執行單元被允許同時進入臨界區。讀者自旋鎖可以這樣定義:
read_lock(&myrwlock); /* Acquire reader lock */
/* ... Critical Region ... */
read_unlock(&myrwlock); /* Release lock */
但是,如果一個寫執行單元進入了臨界區,其他的讀和寫都不被允許進入。寫者鎖的用法如下:
rwlock_t myrwlock = RW_LOCK_UNLOCKED;
write_lock(&myrwlock); /* Acquire writer lock */
write_unlock(&myrwlock); /* Release lock */
net/ipx/ipx_route.c中的IPX路由代碼是使用讀者—寫者鎖的真執行個體子。一個稱作ipx_routes_lock的讀者—寫者鎖将保護IPX路由表的并發通路。要通過查找路由表實作包轉發的執行單元需要請求讀者鎖。需要添加和删除路由表中入口的執行單元必須擷取寫者鎖。由于通過讀路由表的情況比更新路由表的情況多地多,使用讀者—寫者鎖重大地提供了性能。
u64 get_jiffies_64(void) /* Defined in kernel/time.c */
{
unsigned long seq;
u64 ret;
do {
seq = read_seqbegin(&xtime_lock);
ret = jiffies_64;
} while (read_seqretry(&xtime_lock, seq));
return ret;
fs/dcache.c檔案中包含一個RCU的使用例子。在Linux中,每個檔案都與一個目錄入口資訊(dentry結構體)、中繼資料資訊(存放在inode中)和實際的資料(存放在資料塊中)關聯。每次你操作一個檔案的時候,檔案路徑中的元件會被解析,相應的dentry會被擷取。為了加速未來的操作,dentry結構體被緩存在稱為dcache的資料結構中。任何時候,對dcache進行查找的數量都遠多于dcache的更新操作,是以,對dcache的通路适宜用RCU原語進行保護。
spin_lock(&mylock); /* Acquire lock */
if (error) { /* This error condition occurs rarely */
return -EIO; /* Forgot to release the lock! */
spin_unlock(&mylock); /* Release lock */
proc檔案系統
proc檔案系統(procfs)是一種虛拟的檔案系統,它建立核心内部的視窗。你浏覽procfs看到的資料是在核心運作過程中産生的。procfs中檔案可被用于配置核心參數、檢視核心結構體、從裝置驅動中收集統計資訊或者擷取通用的系統資訊。
為了取得procfs能力的第一感覺,請檢視/proc/cpuinfo、/proc/meminfo、 /proc/interrupts、/proc/tty/driver/serial、/proc/bus/usb/devices和/proc/stat的内容。通過寫/proc/sys/目錄中的檔案可以在運作時修改某些核心參數。例如,通過向/proc/sys/kernel/printk檔案echo一個新的值,你可以改變核心printk 日志的級别。許多實用程式(如ps)和系統性能監視工具(如sysstat)就是通過駐留于/proc中的檔案來擷取資訊的。
2.6核心引入的seq檔案簡化了大的procfs操作。附錄C《Seq檔案》對此進行了描述。
記憶體配置設定
核心會以分頁形式組織實體記憶體,而頁大小則依賴于具體的體系結構。在基于x86的機器上,其大小為4096位元組。實體記憶體中的每一頁都有一個與之對應的page結構體(定義在include/linux/mm_types.h檔案中):
unsigned long flags; /* Page status */
atomic_t _count; /* Reference count */
void * virtual; /* Explained later on */
};
在32位x86系統上,預設的核心配置會将4GB的位址空間分成給使用者空間的3GB的虛拟記憶體空間和給核心空間的1GB的空間(如圖2.5)。這導緻核心能處理的處理記憶體有1GB的限制。現實情況是,限制為896MB,因為位址空間的128MB已經被核心資料結構占據。通過改變3GB/1GB的分割線你可以增加這個限制,但是由于減少了使用者程序虛拟位址空間的大小,對于記憶體密集型的應用程式,可能會導緻一些問題。
是以,存在如下的記憶體zone:
(1)ZONE_DMA (<16MB),該zone用于直接記憶體通路(DMA)。由于傳統的ISA裝置有24條位址線,隻能通路開始的16MB,是以,核心将該區域獻給了這些裝置;
kmalloc()是一個用于從ZONE_NORMAL區域傳回連續記憶體的記憶體配置設定函數,其原型如下:
void *kmalloc(int count, int flags);
count是要配置設定的位元組數,flags是一個模式說明符。支援的所有标志列在include/linux./gfp.h檔案中(gfp的意思是“get free page”),如下的标志最常用:
(2)GFP_ATOMIC,用于在中斷上下問擷取記憶體。在這種模式下,kmalloc()不允許進行睡眠等待以獲得空閑頁,是以GFP_ATOMIC配置設定成功的可能性比用GFP_KERNEL低。
由于kmalloc()傳回的記憶體保留了“前世”的内容,是以,如果将它暴露給使用者空間,可到會導緻安全問題,是以我們可以kzalloc()獲得被填充為0的記憶體。
如果你需要配置設定大的記憶體緩沖區,而且也不要求記憶體在實體上聯系,可以用vmalloc()代替kmalloc():
count是要請求配置設定的記憶體大小,該函數傳回核心虛拟位址。
Vmalloc()允許比kmalloc()更大的配置設定尺寸,但是它更慢,而且不能從中斷上下文調用。另外,你不能用vmalloc()傳回的實體上不連續的記憶體執行DMA。在裝置打開時,高性能的網絡驅動通常會使用vmalloc()來配置設定較大的描述符環行緩沖區。
核心還提供了一些更複雜的記憶體配置設定技術,包括後備緩沖區(look aside buffer)、slab和mempool,它們超出了本章的範圍。
記憶體啟動始于執行arch/x86/boot/目錄中的實模式彙編代碼。檢視arch/x86/kernel/setup_32.c檔案可以看出保護模式的核心怎樣擷取實模式核心收集的資訊。
第一條資訊來自于init/main.c中的代碼,深入挖掘init/calibrate.c可以對BogoMIPS校準了解地更清楚,而include/asm-your-arch/bugs.h則包含體系結構相關的檢查。
記憶體管理源代碼存放在頂級mm/目錄中。
表2.1給出了本章中主要的資料結構以及其在源代碼樹中定義的位置。表2.2則列出了本章中主要核心程式設計接口及其定義的位置。
表2.1 資料結構總結
資料結構
位置
描述
HZ
include/asm-your-arch/param.h
Number of times the system timer ticks in 1 second
loops_per_jiffy
init/main.c
timer_list
include/linux/timer.h
timeval
include/linux/time.h
Timestamp
spinlock_t
include/linux/spinlock_types.h
A busy-locking mechanism to ensure that only a single thread enters a critical section
semaphore
include/asm-your-arch/semaphore.h
mutex
include/linux/mutex.h
rwlock_t
Reader-writer spinlock
page
include/linux/mm_types.h
表2.2 核心程式設計接口總結
time_after()
time_after_eq()
time_before()
ime_before_eq()
include/linux/jiffies.h
Compares the current value of jiffies with a specified future value
schedule_timeout()
kernel/timer.c
wait_event_timeout()
include/linux/wait.h
DEFINE_TIMER()
Statically defines a timer
init_timer()
Dynamically defines a timer
add_timer()
Schedules the timer for execution after the timeout has elapsed
mod_timer()
Changes timer expiration
timer_pending()
Checks if a timer is pending at the moment
udelay()
include/asm-your-arch/delay.h arch/your-arch/lib/delay.c
Busy-waits for the specified number of microseconds
rdtsc()
include/asm-x86/msr.h
Gets the value of the TSC on Pentium-compatible processors
do_gettimeofday()
kernel/time.c
Obtains wall time
local_irq_disable()
include/asm-your-arch/system.h
Disables interrupts on the local CPU
local_irq_enable()
Enables interrupts on the local CPU
local_irq_save()
Saves interrupt state and disables interrupts
local_irq_restore()
spin_lock()
include/linux/spinlock.h kernel/spinlock.c
Acquires a spinlock.
spin_unlock()
include/linux/spinlock.h
Releases a spinlock
spin_lock_irqsave()
spin_unlock_irqrestore()
Restores interrupt state and preemption and releases the lock
DEFINE_MUTEX()
Statically declares a mutex
Dynamically declares a mutex
mutex_lock()
kernel/mutex.c
Acquires a mutex
mutex_unlock()
Releases a mutex
DECLARE_MUTEX()
Statically declares a semaphore
init_MUTEX()
Dynamically declares a semaphore
up()
arch/your-arch/kernel/semaphore.c
Acquires a semaphore
down()
Releases a semaphore
atomic_inc()
atomic_inc_and_test()
atomic_dec()
atomic_dec_and_test()
clear_bit()
set_bit()
test_bit()
test_and_set_bit()
include/asm-your-arch/atomic.h
Atomic operators to perform lightweight operations
read_lock()
read_unlock()
read_lock_irqsave()
read_lock_irqrestore()
write_lock()
write_unlock()
write_lock_irqsave()
write_lock_irqrestore()
Reader-writer variant of spinlocks
down_read()
up_read()
down_write()
up_write()
kernel/rwsem.c
Reader-writer variant of semaphores
read_seqbegin()
read_seqretry()
write_seqlock()
write_sequnlock()
include/linux/seqlock.h
Seqlock operations
kmalloc()
include/linux/slab.h mm/slab.c
Allocates physically contiguous memory from ZONE_NORMAL
kzalloc()
include/linux/slab.h mm/util.c
Obtains zeroed kmalloced memory
kfree()
mm/slab.c
Releases kmalloced memory
vmalloc()
mm/vmalloc.c
本文轉自 21cnbao 51CTO部落格,原文連結:http://blog.51cto.com/21cnbao/120804,如需轉載請自行聯系原作者