上週看了Linux的進程與線程,對操做系統的底層有了更進一步的一些瞭解。我同時用Linux內核設計與實現和Solaris內核結構兩本書對比着看,這樣更容易產生對比和引起思考。現代操做系統不少思路都是相同的,好比搶佔式的多線程及內核、虛擬內存管理等方面。但另一方面仍是有不少差別。在瞭解鎖和同步以前,原子操做是全部一切底層實現的基礎。html
原子操做Atomic一般操做系統和硬件都提供特性,能夠對一個字節進行原子操做的的讀寫,而且一般在此基礎上來實現更高級的鎖特性。java
原子操做一般針對int或bit類型的數據,可是Linux並不能直接對int進行原子操做,而只能經過atomic_t的數據結構來進行。目前瞭解到的緣由有兩個。數據結構
一是在老的Linux版本,atomic_t實際只有24位長,低8位用來作鎖,以下圖所示。這是因爲Linux是一個跨平臺的實現,能夠運行在多種 CPU上,有些類型的CPU好比SPARC並無原生的atomic指令支持,因此只能在32位int使用8位來作同步鎖,避免多個線程同時訪問。(最新版SPARC實現已經突破此限制)多線程
另一個緣由是避免atomic_t傳遞到程序其餘地方進行操做修改等。強制使用atomic_t,則避免被不恰當的誤用。curl
atomic_t my_counter = ATOMIC_INIT(0); val = atomic_read( &my_counter ); atomic_add( 1, &my_counter ); atomic_inc( &my_counter ); atomic_sub( 1, &my_counter ); atomic_dec( &my_counter );
Solaris的實現是基於test-and-set的指令,而且該指令爲原子操做。好比Solaris的實如今SPARC上是基於ldstub和cas指令,在x86上用的是cmpxchg指令。可是Linux彷佛直接用的add指令。jvm
OpenSolaris i386的實現ide
movl 4(%esp), %edx / %edx = target address movl (%edx), %eax / %eax = old value 1: leal 1(%eax), %ecx / %ecx = new value lock cmpxchgl %ecx, (%edx) / try to stick it in jne 1b movl %ecx, %eax / return new value ret
在Linux源代碼asm_i386/atomic.h中性能
/** * atomic_add - add integer to atomic variable * @i: integer value to add * @v: pointer of type atomic_t * * Atomically adds @i to @v. Note that the guaranteed useful range * of an atomic_t is only 24 bits. */ static __inline__ void atomic_add(int i, atomic_t *v) { __asm__ __volatile__( LOCK "addl %1,%0" :"=m" (v->counter) :"ir" (i), "m" (v->counter)); }鎖的類型
若是鎖被佔用,嘗試獲取鎖的線程進入busy-wait狀態,即CPU不停的循環檢查鎖是否可用。自旋鎖適合佔用鎖很是短的場合,避免等待鎖的線程sleep而帶來的CPU兩個context switch的開銷。學習
若是鎖被佔用,嘗試獲取鎖的線程進入sleep狀態,CPU切換到別的線程。當鎖釋放以後,系統會自動喚醒sleep的線程。信號量適合對鎖佔用較長時間的場合。ui
顧名思義,自適應鎖就是上面兩種的結合。當線程嘗試申請鎖,會自動根據擁有鎖的線程繁忙或sleep來選擇是busy wait仍是sleep。這種鎖只有Solaris內核提供,Linux上未見有相關描述。
幾種鎖的性能比較(Windows操做系統下,第一種相似atomic_inc, 2,3相似spinlock, 4,5相似semaphore)
(圖片來源:Intel Software Network)
Java synchronization再理論聯繫實際一下,看Java中的鎖底層如何實現的。這篇關於JVM的Thin Lock, Fat Lock, SPIN Lock與Tasuki Lock中講到Java synchronization實際上也是一種自適應鎖。
因而,JVM早期版本的作法是,若是T1, T2,T3,T4…產生線程競爭,則T1經過CAS得到鎖(此時是Thin Lock方式),若是T1在CAS期間得到鎖,則T2,T3進入SPIN狀態直到T1釋放鎖;而第二個得到鎖的線程,好比T2,會將鎖升級(Inflation)爲Fat Lock,因而,之後嘗試得到鎖的線程都使用Mutex方式得到鎖。
Java AtomicInteger的實現,彷佛和Solaris的實現很是相似,也是一個busy wait的方式
/** * Atomically increments by one the current value. * * @return the updated value */ public final int incrementAndGet() { for (;;) { int current = get(); int next = current + 1; if (compareAndSet(current, next)) return next; } } /** * Atomically sets the value to the given updated value * if the current value {@code ==} the expected value. * * @param expect the expected value * @param update the new value * @return true if successful. False return indicates that * the actual value was not equal to the expected value. */ public final boolean compareAndSet(int expect, int update) { // unsafe.compareAndSwapInt是用本地代碼實現 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); }Solaris
感受OpenSolaris在不少地方要比Linux優秀,Solaris在理論設計和實踐上都很是優雅,而Linux內核不少地方彷佛更偏工程實踐方向一些。另外Solaris用來作學習操做系統更合適,它的mdb幾乎無所不能。
我在VirtualBox虛擬機上安裝了OpenSolaris,很是容易安裝,使用這個Minimal OpenSolaris Appliance OVF p_w_picpath for VirtualBox 2.2 簡易方法,安裝一個沒有gui的版本,大約3分鐘之內就能夠裝好。OpenSolaris安裝軟件和Ubuntu同樣方便,使用 pkg install SUNWxxx 的命令,好比 pkg install SUNWcurl