歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux綜合 >> Linux內核

《Linux內核設計與實現》筆記——內核同步簡介

相關概念

競爭條件

多個執行線程(進程/線程/中斷處理程序)並發(並行)訪問共享資源,因為執行順序不一樣造成結果不一樣的情況,稱為競爭條件(race condition)

舉例說明

#include

using namespace std;

int i = 0;

void thread1(){

//for(int x=0;x<100000;x++)

i++;

}

void thread2(){

//for(int x=0;x<100000;x++)

i++;

}

int main(){

std::thread t1(thread1);

std::thread t2(thread1);

t1.join();

t2.join();

printf(" i = %d\n",i);

}

t1,t2兩個線程並發執行時有可能發生競爭條件(當然這個程序發生競爭條件概率很低,但如果你把注釋去掉,競爭條件就非常容易發生了)

i++;這條語句一般需要3條機器指令

MOV EAX,i

INC EAX

MOV EAX,i

如果按照如下順序執行,是沒有有問題的,打印出的i是2

thread 1thread 2

MOV EAX,i(i=0)

INC EAX

MOV EAX,i(i=1)

MOV EAX,i(i=1)

INC EAX

MOV EAX,i(i=2)

但是兩個線程很可能按照如下順序執行,此時就會出現競爭條件,打印出的i是1

thread 1thread 2

MOV EAX,i (i=0)

INC EAX

MOV EAX,i (i=0)

MOV EAX,i (i=1)

INC EAX

MOV EAX,i (i=1)

臨界區(critical section)是指 訪問和操作共享資源的代碼段,例子裡就是兩個線程的i++語句

為避免競爭條件,臨界區必須原子地執行,執行結束前不能被打斷。一般會對臨界區加鎖,使用信號量,條件變量等,例子中的簡單操作也可以使用原子變量。

原子性的理解

一般書上對原子性的解釋為執行結束前不能被打斷。這句話如何理解呢,我個人的理解是:

原子變量這種,使用鎖總線的方式來實現,相關的若干條指令是連續執行的;使用鎖(自旋鎖/睡眠鎖),信號量,條件變量/完成變量這些,並不是說臨界區的所有指令連續執行,可能在臨界區執行到了一半,因為調度搶占或者中斷,CPU不繼續在臨界區內執行,也就是執行被打斷了。

但是這種情況依舊能保證避免競爭條件,原因在於,如果被打斷的進程之外的進程被調度,該進程要想訪問臨界區的時候,被鎖或者信號量等“攔住”,是進入不了臨界區的,直到原被打斷的臨界區執行完畢,釋放鎖/信號量等,其他進程才有可能進入臨界區。

造成並發的原因

中斷:中斷幾乎可以在任何時刻異步發生,也就可能隨時打斷當前正在執行的代碼軟中斷和tasklet:內核能在任何時候喚醒或調度軟中斷和tasklet,打斷當前正在執行的代碼內核搶占:Linux內核具有搶占性,所以內核的任務可能被另一任務搶占睡眠及用戶空間的同步:在內核執行的進程可能會睡眠,會喚醒調度程

序,從而調度一個新的用戶進程執行 SMP,兩個或多個處理器可以同時執行代碼

內核中的同步手段

加鎖

為了避免競爭條件,內核提供了幾個機制用於同步,基本思路就是加鎖,臨界區互斥訪問,信號量也可以支持多於一個線程並發訪問。

鎖的爭用

指所被占用時,有其他線程試圖獲得該鎖。鎖的爭用程度越高,系統性能也就會越低(加鎖會降低系統性能,比如睡眠鎖,自旋鎖),但是為保證正確性有必須使用。要降低鎖的爭用,加鎖粒度要盡可能小,也就是臨界區要盡量小。

幾種同步方法簡介

同步方法主要接口備注

原子整數(32bit)操作atomic_t v;/*定義 v*/

atomic_t u = ATOMIC_INIT(0);/*定義u,初始化為0*/

atomic_set(&v, 4);/* v=4 */

atomic_add(2, &v);/* v=v+2 */

atomic_inc(&v);/* v=v+1 */

原子整數(64bit)操作atomic64_t v;

原子位操作unsigned long word = 0;對普通的指針進行操作

set_bit(0,&word); //第0位被設置__set_bit形式為非原子操作

set_bit(1,&word); //第1位被設置如果不需要原子操作,非原子操作更快一些

printk("%ul\n",word);//打印3《linux內核設計與實現》P147

clear_bit(1,&word);//清空第1位

change_bit(0,&word);//反轉第0位

自旋鎖DEFINE_SPINLOCK(mr_lock);自旋鎖在同一時刻只能由一個線程位於臨界區

spin_lock(&mr_lock);可為多處理器提供並發訪問所需的保護機制

/*臨界區*/單處理機,當作一個內核搶占是否開啟的開關,如果禁止內核搶占,編譯時自旋鎖會被完全剔除內核

spin_unlock(&mr_lock);自旋鎖不可遞歸

自旋鎖和下半部下半部和進程上下文共享數據/下半部和中斷處理程序共享數據,需要加鎖下半部可以搶占進程上下文,中斷處理程序可以搶占下半部

讀寫自旋鎖DEFINE_RWLOCK(mr_rwlock);

read_lock(&mr_rwlock);所有讀者共享

/*臨界區(只讀)*/

read_unlock(&mr_rwlock);

write_lock(&mr_rwlock);寫者相互排斥,寫者和讀者互斥

/*臨界區(讀寫)*/

write_unlock(&mr_rwlock);

信號量struct semophore name;爭用的線程會睡眠,因此適合鎖會被長時間占有的情況

sema_init(&name, count);在進程上下文中使用

static DECLARE_MUTEX(name);

init_MUTEX(sem)動態初始化互斥信號量

static DECLARE_MUTEX(mr_sem);

if(down_interruptible(&mr_sem)){ /*信號被接收,信號量還未獲取*/ }

/*臨界區*/

up(&mr_sem);//釋放給定的信號量

讀寫信號量static DECLARE_RWSEM(mr_rwsem);靜態定義

init_rwsem(struct rw_semaphore *sem);動態初始化

down_read(&mr_rwsem);

/*臨界區(只讀)*/

up_read(&mr_rwsem);

down_write(&mr_rwsem);

/*臨界區(讀寫)*/

up_write(&mr_rwsem);

互斥體DEFINE_MUTEX(name);用於處理互斥的睡眠鎖

mutex_init(&mutex);動態初始化

mutex_lock(&mutex)不能遞歸上鎖和解鎖

/*臨界區*/不能再中斷或下半部執行

mutex_unlock(&mutex)

完成變量DECLARE_COMPLETION(mr_comp)等待,知道任務完成,發出信號

init_completion();動態創建

等待任務調用wait_for_completion();

產生事件的任務調用complete();//發送信號喚醒特定事件

關於大內核鎖BLK,順序鎖的內容見《Linux內核設計與實現》P160

禁止搶占

內核搶占代碼使用自旋鎖作為非搶占區域的標記

可以禁用搶占

 

這裡寫圖片描述

 

更簡潔的解決每個處理器上的數據訪問問題,get_cpu()獲取處理器編號,返回處理器編號前會先關閉內核搶占

int cpu;

cpu = get_cpu;//禁止內核搶占,將CPU設置為當前cpu

/*對每一個cpu的數據進行操作*

/*在給予內核搶占性,“cpu”可改變,所以不再有效*/

put_up();

順序和屏障

保證順序的原因

多處理器之間,硬件設備的同步問題時,需要在程序代碼中按照指定的順序讀取內存和寫入內存。和硬件交互時,常需要保證給定讀操作在其他讀/寫操作之前多處理器上,可能需要按照寫數據的順序讀數據。

為了效率,編譯器(編譯優化)和處理器(亂序執行)可能對讀和寫重新排序。

所有可能重新排序讀寫的cpu提供機器指令,確保按順序執行。也可以指示編譯器不進行給定點周圍的指令序列進行重新排序。

e.g.

//可能重新排序

a=1;

b=2;

//不可能重新排序

a=1;

b=a;

舉例解釋保證順序的原因

a=1,b=2

線程1線程2

a=3;–

mb();–

b=4;c=b;

–rmb();

–d=a;

如果沒有內存屏障,某些處理器上,可能c接受了b的新值4,d接受了a原來的值1

幾個方法

 

這裡寫圖片描述

 

完整接口列表

截圖自《Linux內核設計與實現》第10章

 

這裡寫圖片描述

 

 

這裡寫圖片描述

 

 

這裡寫圖片描述

 

 

這裡寫圖片描述

 

 

這裡寫圖片描述

 

 

這裡寫圖片描述

 

 

這裡寫圖片描述

 

 

這裡寫圖片描述

 

 

這裡寫圖片描述
Copyright © Linux教程網 All Rights Reserved