瞭解Linux的鎖與同步

上週看了Linux的進程與線程,對操做系統的底層有了更進一步的一些瞭解。我同時用Linux內核設計與實現Solaris內核結構兩本書對比着看,這樣更容易產生對比和引起思考。現代操做系統不少思路都是相同的,好比搶佔式的多線程及內核、虛擬內存管理等方面。但另一方面仍是有不少差別。在瞭解鎖和同步以前,原子操做是全部一切底層實現的基礎。html

原子操做Atomic

一般操做系統和硬件都提供特性,能夠對一個字節進行原子操做的的讀寫,而且一般在此基礎上來實現更高級的鎖特性。java

  • atomic_t結構

原子操做一般針對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));
}
鎖的類型
  • Spinlocks自旋鎖

若是鎖被佔用,嘗試獲取鎖的線程進入busy-wait狀態,即CPU不停的循環檢查鎖是否可用。自旋鎖適合佔用鎖很是短的場合,避免等待鎖的線程sleep而帶來的CPU兩個context switch的開銷。學習

  • Semaphores信號量

若是鎖被佔用,嘗試獲取鎖的線程進入sleep狀態,CPU切換到別的線程。當鎖釋放以後,系統會自動喚醒sleep的線程。信號量適合對鎖佔用較長時間的場合。ui

  • Adaptive locks自適應鎖

顧名思義,自適應鎖就是上面兩種的結合。當線程嘗試申請鎖,會自動根據擁有鎖的線程繁忙或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

相關文章
相關標籤/搜索