barrier()函數


轉自:http://blog.chinaunix.net/uid-21961753-id-1810628.html

 

今天看內核發現disable_preempt這個函數,覺得挺有意思就看了下網上資料,以下我將之整理成了兩個函數來加以理解。

一、barrier函數

內存屏障出現因為編譯器或現在的處理器常會自作聰明地對指令序列進行一些處理,比如數據緩存,讀寫指令亂序執行等等。如果優化對象是普通內存,那么一般會提升性能而且不會產生邏輯錯誤。但如果對 I/O操作進行類似優化很可能造成致命錯誤。所以要使用內存屏障,以強制該語句前后的指令以正確的次序完成。其實在指令序列中放一個wmb的效果是使得指令執行到該處時,把所有緩存的數據寫到該寫的地方,同時使得wmb前面的寫指令一定會在wmb的寫指令之前執行。rmb(讀內存屏障)保證了屏障之前的讀操作一定會在后來的讀操作執行之前完成。wmb 保證寫操作不會亂序,mb 指令保證了兩者都不會。這些函數都是 barrier函數的超集。

這些函數在已編譯的指令流中插入硬件內存屏障;具體的插入方法是平台相關的。

關於barrier()宏實際上也是優化屏障:

#define barrier() __asm__ __volatile__("": : :"memory")

CPU越過內存屏障后,將刷新自己對存儲器的緩沖狀態。這條語句實際上不生成任何代碼,但可使gccbarrier()之后刷新寄存器對變量的分配。

1set_mb(),mb(),barrier()函數追蹤到底,就是__asm__ __volatile__("":::"memory"),而這行代碼就是內存屏障。
2__asm__用於指示編譯器在此插入匯編語句
3__volatile__用於告訴編譯器,嚴禁將此處的匯編語句與其它的語句重組合優化。即:原原本本按原來的樣子處理這這里的匯編。
4memory強制gcc編譯器假設RAM所有內存單元均被匯編指令修改,這樣cpu中的registerscache中已緩存的內存單元中的數據將作廢。cpu將不得不在需要的時候重新讀取內存中的數據。這就阻止了cpu又將registerscache中的數據用於去優化指令,而避免去訪問內存。
5"":::表示這是個空指令。barrier()不用在此插入一條串行化匯編指令。

6__asm__,__volatile__,memory在前面已經解釋     

            

1

                1        int a = 5, b = 6;

                2        barrier();

                3        a = b;

      

    line 3GCC不會用存放b的寄存器給a賦值,而是重新讀內存中的b值,賦值給a

     

2

它在進程上下文中將一個元素插入一個單向鏈表:

new->next=i->next;

wmb();

i->next=new;

同時,如果不加鎖地遍歷這個單向鏈表。或者在遍歷鏈表時已經可以看到new,或者new還不在該鏈表中。兩個內存寫

事件的順序必須按照程序順序進行。否則可能newnext指針將指向一個無效地址,就很可能出現 OOPS!

 

不論是gcc編譯器的優化還是處理器本身采用的大量優化,如Write buffer, Lock-up free, Non- blocking reading, Register allocation, Dynamic scheduling, Multiple issues 等,都可能使得實際執行可能違反程序順序,因此,引入內存屏障來保證事件的執行次序嚴格按程序順序來執行。

 

使用內存屏障強加的嚴格的CPU內存事件次序,保證程序的執行看上去象是遵循順序一致性模型。在當前的實現中,wmb() 實際上是一個空操作,這是因為目前IntelCPU系列都遵循“處理機一致性”,所有的寫操作是遵循程序順序的,不會越過前面的讀寫操作。但是,由於 Intel CPU系列可能會在將來采用更弱的內存一致性模型並且其他體系結構可能采用其他放松的一致性模型,仍然在內核里必須適當地插入wmb()保證內存事件的正確次序。

二、disable_preempt函數

先講下linux的調度機制,linux下有兩種調度方式:

1)顯式調度,進程自己因為缺少相應的所申請的資源,顯式調用調度器,讓出處理器,比如:內核申請的信號阻塞了,自旋鎖鎖住了。
2)隱式調度,整個linux系統在運行過程中的非顯示的調用調度器,這又分兩種情況:
A)用戶態搶占調度  比如:在系統調用,中斷處理,異常處理返回用戶態時,該進程的時間片已經用完。
B)內核態搶占調度  比如:當前內核態執行過程中事先沒有禁止內核態搶占,有中斷產生時,中斷處理 又產生了更高級優先進程,那么就會直接搶占前面的內核態執行體。
           
 
常見的調度點
1)進程被阻塞時 比如申請資源時被阻塞 
2)
調整參數時   比如通過sched_setscheduler() ,nice()等函數調整進程的調度策略,靜態優先級時

3)睡眠進程被喚醒時  比如wake_up喚醒等待隊列中的進程時,如果該進程具有更高優先級則會設置當前
               進程TIF_NEED_RESCHED,如果允許內核態搶占,則會調度一次,
               ( 
這是由等待隊列中的默認的喚醒函數控制的,默認的喚醒函數為

               int default_wake_function(wait_queue_t*,unisgned int  mode,int sync,void* key)
               EXPORT_SYMBOL(default_wake_function)
               
因為EXPORT_SYMBOLdefault_wake_function,所以我們可以制作我們自己的喚醒函數
.
4)
中斷處理完時  如果中斷處理過程中設置了TIF_NEED_SCHED標志,中斷返回時,不論是要返回內核態還是用戶態,都會發生一次搶占.當然,在這也會檢查有沒有軟中斷需要處理
.
5)
執行了preempt_enable()函數  (見前面說明)

 

而我們在搶占式內核中,有三處地方需要顯示的禁用搶占:
1. 操作Per-CPU變量的時候,比如smp_processor_id()就是這一類問題,但一個進程被搶占后重新調度,有可能調度到其他的CPU上去,這時定義的Per-CPU變量就會有問題。下面是一個例子:
        struct this_needs_locking tux[NR_CPUS];
        tux[smp_processor_id()] = some_value;
        /* task is preempted here... */
        something = tux[smp_processor_id()];
這里如果沒有搶占保護的話some_valuesomething可能返回不同的值。當處理CPU ID時,可以考慮使用get_pcu()/put_cpu()接口,該函數對實現了禁用搶占,取得CPU ID,使能搶占的序列。算是kernel推薦的使用方法。

2. 必須保護CPU的狀態。這類問題是體系結構依賴的。例如,在x86上,進入和退出FPU就是一種臨界區,必須在禁搶占的情況下使用。

3. 獲得和釋放鎖必須在一個進程中實現。也就是說一個鎖被一個進程持有,也必須在這個進程中釋放。

禁用/使能搶占的函數主要有:
spin_lock()/spin_unlock()
disable_preempt()/enable_preempt()
(禁止或使能內核搶占)調用下面的inc_preempt_count()/dec_preempt_count(),並且加入了memory barrier
inc_preempt_count()/dec_preempt_count()
get_cpu()/put_cpu()


相關數據結構及函數如下:
struct thread_info

{
unisgned int preempt_count;-----(PREEMPT 0-7
位表示內核態禁止搶占計數器,SOFTIRQ 8-15表示軟中斷禁止計數器,HARDIRQ 16-27表示中斷嵌套的深度)
}
只要PREEMPT0時才允許內核態搶占
.

preempt_disable()--------------
主要執行inc_preempt_count()(增加PREEMPT,從而禁止內核態搶占
)
preempt_enable()--------------
主要執行preempt_enable_no_resched()
preempt_check_resched()
                          preempt_enable_no_resched()
主要執行
dec_preempt_count()
                          preempt_check_resched()
主要執行
test_thread_flag(TIF_NEED_RESCHED)
                                (
是否設置了需要調度的標志)preempt_schedule()(進行內核態搶占調度
)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM