2014年7月26日 核心搶占和核心控制路徑的設計
核心搶占的一種定義:如果程序正在核心态執行核心函數時,允許發生核心切換(就是被替換的程序是核心函數所在程序),這個核心就是搶占的。
linux核心提供了核心搶占的開啟和關閉功能,在current_thread_info的preempt_count字段大于0時,核心就是不能搶占的。可以通過preempt_disable和preempt_enable來增加這個字段來控制這個開關。
中斷和軟中斷和搶占的控制主要使用到current_thread_info->preempt_count字段,這個字段被分成了4部分:0-7位為搶占計數器,8-15位為軟中斷計數器,16-27位為硬中斷計數器,28位為PREEMPT_ACTIVE标志。
核心設計上的選擇子某種程度上簡化了核心控制路徑的同步:
1.所有的中斷處理程式相應來自于pic的中斷并且禁用IRQ線。此外在中斷處理程式結束前,不允許産生相同的中斷事件。
2.中斷處理程式、軟中斷和tasklet既不可以被搶占也不能被阻塞,是以它們不可能長時間處于挂起狀态。在最壞的情況下,它們的執行将會有輕微的延遲,因為在執行過程中可能發生其他的中斷(核心控制路徑的嵌套執行)。
3.執行中斷處理的核心控制路徑不能被執行可延遲函數或系統調用服務例程的核心控制路徑中斷。
4.軟中斷和tasklet不能一個給定的cpu上交錯執行。
5.同一個tasklet不能同時在幾個cpu上執行,但是不同類型的tasklet可以在幾個cpu上同時執行。
2014年7月26日 同步原語
核心使用的各種同步技術:
技術 | 說明 | 适用範圍 |
每cpu變量 | 在多個cpu之間複制資料 | 所有cpu |
原子操作 | 對一個計數器原子地“讀-修改-寫”操作 | 所有cpu |
記憶體屏障 | 避免指令重新排序 | 本地cpu或者所有cpu |
自旋鎖 | 加鎖時忙等 | 所有cpu |
信号量 | 加鎖時阻塞等待(睡眠) | 所有cpu |
順序鎖 | 基于通路計數器的鎖 | 所有cpu |
本地中斷的禁止 | 禁止單個cpu上的中斷處理 | 本地cpu |
本地軟中斷的禁止 | 禁止單個cpu上的可延遲函數的執行 | 本地cpu |
讀-拷貝-寫 | 通過指針而不是鎖來通路共享資料結構 | 所有cpu |
每cpu變量:主要做法是聲明一個nr_cpus大小的變量數組,每個cpu都使用其自己的變量。要注意不要通路屬于其他cpu的變量,可以使用get_cpu_var()(禁止了搶占)宏來獲得這個變量,用put_cpu_var()(啟動搶占)宏來結束使用這個變量。
原子操作:通過lock,inc,dec等等原子性彙編指令來實作整型值的原子操作。
記憶體屏障:避免編譯器、cpu對指令進行重新排序,當然這樣可能會失去指令重排帶來的優化。
自旋鎖:在進入臨界區之前嘗試去獲得鎖,如果不能獲得就一直在那空等。其實在底層還是通過lock,inc,dec等等原子性的彙編指令來實作。由于自旋鎖浪費cpu周期,隻适合臨界區比較小的同步場景。
信号量:在進入臨界區之前嘗試去獲得信号量 ,如果不能獲得就把目前程序放到信号量的等待隊列裡面睡眠。底層是通過自旋鎖來實作的。信号量不能在中斷處理程式和可延遲函數(tasklets)中使用,因為它們不允許睡眠。
順序鎖:類似于讀寫鎖,但是不同于讀寫鎖的讀寫操作擁有相同優先級,順序鎖中寫操作擁有更高的優先級。
本地中斷的禁止:通過cli和sti來關閉和打開本地中斷。核心提供了local_irq_disable和local_irq_enable來實作這些功能。核心還提供了local_irq_save和local_irq_restore在前面兩個宏的功能基礎上實作eflags寄存器的儲存和恢複功能,這對中斷嵌套執行非常重要。
本地軟中斷的禁止:通過local_bh_disable和local_bh_enable來實作本地軟中斷的關閉和開啟功能。宏展開其實也就是對current_thread_info->preempt_count的軟中斷部分進行加減操作。
讀-拷貝-寫:為了提高核心的并發度而引入的一種技術,主要場景适合于指針資料,讀操作不需要同步,寫操作時,先複制一份副本,再在副本上修改,最後在合适的時候(因為其他讀操作可能正在進行中 )将原資料的指針指向副本的位址。