天天看點

Linux下程序通信

Linux下程序通信

2011年05月07日

  Linux下程序通信的八種方法[連載-記1]:所有方法登場 本連載是我對《Linux Programming by Example》《Linux Aplication Development》《Linux應用開發技術詳解》等書中介紹的Linux下程序通信的方法的相關章節作的筆記,具體細節的還請參照以上書籍。本連載是我對《Linux應用開發技術詳解》一書中介紹的Linux下程序通信的方法的相關章節作的筆記。

  Linux下程序通信的八種方法:管道(pipe),命名管道(FIFO),記憶體映射(mapped memeory),消息隊列(message queue),共享記憶體(shared memory),信号量(semaphore),信号(signal),套接字(Socket).

  (1) 管道(pipe):管道允許一個程序和另一個與它有共同祖先的程序之間進行通信;

  (2) 命名管道(FIFO):類似于管道,但是它可以用于任何兩個程序之間的通信,命名管道在檔案系統中有對應的檔案名。命名管道通過指令mkfifo或系統調用mkfifo來建立;

  (3) 信号(signal):信号是比較複雜的通信方式,用于通知接收程序有某種事情發生,除了用于程序間通信外,程序還可以發送信号給程序本身;Linux除了支援UNIX早期信号語義函數signal外,還支援語義符合POSIX.1标準的信号函數sigaction(實際上,該函數是基于BSD的,BSD即能實作可靠信号機制,又能夠統一對外接口,用sigaction函數重新實作了signal函數的功能);

  (4) 記憶體映射(mapped memory):記憶體映射允許任何多個程序間通信,每一個使用該機制的程序通過把一個共享的檔案映射到自己的程序位址空間來實作它;

  (5) 消息隊列(message queue):消息隊列是消息的連接配接表,包括POSIX消息對和System V消息隊列。有足夠權限的程序可以向隊列中添加消息,被賦予讀權限的程序則可以讀走隊列中的消息。消息隊列克服了信号承載資訊量少,管道隻能成該無格式位元組流以及緩沖區大小受限等缺點;

  (6) 信号量(semaphore):信号量主要作為程序間以及同程序不同線程之間的同步手段;

  (7) 共享記憶體 (shared memory):它使得多個程序可以通路同一塊記憶體空間,是最快的可用IPC形式。這是針對其他通信機制運作效率較低而設計的。它往往與其他通信機制,如信号量結合使用,以達到程序間的同步及互斥;

  (8) 套接字(Socket):它是更為通用的程序間通信機制,可用于不同機器之間的程序間通信。起初是由UNIX系統的BSD分支開發出來的,但現在一般可以移植到其他類UNIX系統上:Linux和System V的變種都支援套接字;

  linux程序間的同步方法

  http://buaadallas.blog.51cto.com/399160/171061

  程序間通訊(IPC)方法主要有以下幾種:

  管道/FIFO/共享記憶體/消息隊列/信号

  1.管道中還有命名管道和非命名管道(即匿名管道)之分,非命名管道(即匿名管道)隻能用于父子程序通訊,命名管道可用于非父子程序,命名管道就是FIFO,管道是先進先出的通訊方式

  2.消息隊列是用于兩個程序之間的通訊,首先在一個程序中建立一個消息隊列,然後再往消息隊列中寫資料,而另一個程序則從那個消息隊列中取資料。需要注意的是,消息隊列是用建立檔案的方式建立的,如果一個程序向某個消息隊列中寫入了資料之後,另一個程序并沒有取出資料,即使向消息隊列中寫資料的程序已經結束,儲存在消息隊列中的資料并沒有消失,也就是說下次再從這個消息隊列讀資料的時候,就是上次的資料!!!!

  3.信号量,它與WINDOWS下的信号量是一樣的,是以就不用多說了

  4.共享記憶體,類似于WINDOWS下的DLL中的共享變量,但LINUX下的共享記憶體區不需要像DLL這樣的東西,隻要首先建立一個共享記憶體區,其它程序按照一定的步驟就能通路到這個共享記憶體區中的資料,當然可讀可寫

  以上幾種方式的比較:

  1.管道:速度慢,容量有限,隻有父子程序能通訊

  2.FIFO:任何程序間都能通訊,但速度慢

  3.消息隊列:容量受到系統限制,且要注意第一次讀的時候,要考慮上一次沒有讀完資料的問題

  4.信号量:不能傳遞複雜消息,隻能用來同步

  5.共享記憶體區:能夠很容易控制容量,速度快,但要保持同步,比如一個程序在寫的時候,另一個程序要注意讀寫的問題,相當于線程中的線程安全,當然,共享記憶體區同樣可以用作線程間通訊,不過沒這個必要,線程間本來就已經共享了同一程序内的一塊記憶體

  程序通信---FIFO

  http://blogold.chinaunix.net/u3/102839/showart_204 1236.html

  管道沒有名字,是以隻能在具有血緣關系的程序間使用,而在無名管道發展出來的有名管道FIFO,則有路徑名與之相關聯,以一種特殊裝置檔案形式存在于檔案系統中,進而允許無親緣關系的程序通路FIFO,下面看FIFO的詳細操作

  1.FIFO的建立

  FIFO是存在于檔案系統的檔案節點,是以我們可以建立檔案節點的mknod系統用來建立它,也可以mkfifo系統調用

  mkfifo說明:

  #include

  #include

  int mkfifo(char *path,mode_t mode);

  說明:path:路徑名,mode:指定檔案存取權标志,mkfifo()會依參數pathname建立特殊的FIFO檔案,該檔案必須不存在,系統調用已經指定O_CREATE|O_EXCL

  傳回:若成功則傳回0,否則傳回-1,錯誤原因存于errno中。

  錯誤代碼

  EACCESS 參數pathname所指定的目錄路徑無可執行的權限

  EEXIST 參數pathname所指定的檔案已存在。

  ENAMETOOLONG 參數pathname的路徑名稱太長。

  ENOENT 參數pathname包含的目錄不存在

  ENOSPC 檔案系統的剩餘空間不足

  ENOTDIR 參數pathname路徑中的目錄存在但卻非真正的目錄。

  EROFS 參數pathname指定的檔案存在于隻讀檔案系統内。

  2.FIFO使用

  建立後,在讀寫前,要先打開它,用open系統調用

  當使用open()來打開 FIFO檔案時,O_NONBLOCK旗标會有影響

  1、當使用O_NONBLOCK 旗标時,打開FIFO 檔案來讀取的操作會立刻傳回,但是若還沒有其他程序打開FIFO 檔案來讀取,則寫入的操作會傳回ENXIO 錯誤代碼。

  2、沒有使用O_NONBLOCK 旗标時,打開FIFO 來讀取的操作會等到其他程序打開FIFO檔案來寫入才正常傳回。同樣地,打開FIFO檔案來寫入的操作會等到其他程序打開FIFO 檔案來讀取後才正常傳回。

  下面練習,分别寫兩個程式,一個是伺服器程式,不斷從管道讀取客戶發送的資訊;另一個是客戶程式,在指令行輸入資訊并從管道發送:

  客戶程式(寫管道)

  

  #include

  #include

  #include

  #include

  #include

  #include

  #include

  

  #define FIFO_SERVER "/tmp/myfifo"

  main(int argc,char** argv)

  {

  int fd = 0;

  char w_buf[100];

  int nwrite;

  

  fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);

  if(fd==-1)

  if(errno==ENXIO)

  printf("open error; no reading process\n");

  

  if(argc==1)

  printf("Please send something\n");

  

  strcpy(w_buf,argv[1]);

  

  if((nwrite=write(fd,w_buf,100))==-1)

  {

  if(errno==EAGAIN)

  printf("The FIFO has not been read yet.Please try later\n");

  }

  else

  

  printf("write %s to the FIFO\n",w_buf);

  }

  服務程式(讀管道)

  

  #include

  #include

  #include

  #include

  #include

  #include

  #include

  

  #define FIFO "/tmp/myfifo"

  main(int argc,char** argv)

  {

  char buf_r[100];

  int fd;

  int nread;

  

  if((mkfifo(FIFO,O_CREAT|O_EXCL))&&(errno!=EEXIST))

  printf("cannot create fifoserver\n");

  printf("Preparing for reading bytes...\n");

  memset(buf_r,0,sizeof(buf_r));

  

  fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);

  if(fd==-1)

  {

  perror("open");

  exit(1);

  }

  while(1)

  {

  memset(buf_r,0,sizeof(buf_r));

  

  if((nread=read(fd,buf_r,100))==-1){

  if(errno==EAGAIN)

  printf("no data yet\n");

  }

  printf("read %s from FIFO\n",buf_r);

  sleep(1);

  }

  pause();

  unlink(FIFO);

  }

  接下來進行編譯,編譯好後,在Linux中運作兩個終端,分别運作以上兩個程式,可以看到,運作fifo_read時,程式一直在每隔一秒讀,然後我們在另一個終端輸入:

  $ ./fifo_write helloworld

  可以看出fifo_read顯示出"helloworld",說明接受成功

  FIFO的一些注意問題:

  (1)管道資料的FIFO處理方式

  首先放入管道的資料,在端口首先被讀出

  (2)管道資料的不可再現性

  已讀取的資料在管道裡消失,不能再讀

  (3)管道長度的有限性

  (4)SIGPIPE信号 如果一個程序試圖寫入到一個沒有讀取到程序的管道中哦你,系統核心産生SIGPIPE信号

  【轉帖】信号量與自旋鎖 |Linux,信号量,自旋鎖,睡眠鎖,spinlock,semaphore

  2008-11-18 15:14核心同步措施

  為了避免并發,防止競争。核心提供了一組同步方法來提供對共享資料的保護。 我們的重點不是介紹這些方法的詳細用法,而是強調為什麼使用這些方法和它們之間的差别。

  Linux 使用的同步機制可以說從2.0到2.6以來不斷發展完善。從最初的原子操作,到後來的信号量,從大核心鎖到今天的自旋鎖。這些同步機制的發展伴随 Linux從單處理器到對稱多處理器的過度;伴随着從非搶占核心到搶占核心的過度。鎖機制越來越有效,也越來越複雜。

  目前來說核心中原子操作多用來做計數使用,其它情況最常用的是兩種鎖以及它們的變種:一個是自旋鎖,另一個是信号量。我們下面就來着重介紹一下這兩種鎖機制。

  自旋鎖

  -------------------------------------------------- ----

  自旋鎖是專為防止多處理器并發而引入的一種鎖,它在核心中大量應用于中斷處理等部分(對于單處理器來說,防止中斷進行中的并發可簡單采用關閉中斷的方式,不需要自旋鎖)。

  自旋鎖最多隻能被一個核心任務持有,如果一個核心任務試圖請求一個已被争用(已經被持有)的自旋鎖,那麼這個任務就會一直進行忙循環--旋轉--等待鎖重新可用。要是鎖未被争用,請求它的核心任務便能立刻得到它并且繼續進行。自旋鎖可以在任何時刻防止多于一個的核心任務同時進入臨界區,是以這種鎖可有效地避免多處理器上并發運作的核心任務競争共享資源。

  事實上,自旋鎖的初衷就是:在短期間内進行輕量級的鎖定。一個被争用的自旋鎖使得請求它的線程在等待鎖重新可用的期間進行自旋(特别浪費處理器時間),是以自旋鎖不應該被持有時間過長。如果需要長時間鎖定的話, 最好使用信号量。

  自旋鎖的基本形式如下:

  spin_lock(&mr_lock);

  //臨界區

  spin_unlock(&mr_lock);

  因為自旋鎖在同一時刻隻能被最多一個核心任務持有,是以一個時刻隻有一個線程允許存在于臨界區中。這點很好地滿足了對稱多處理機器需要的鎖定服務。在單處 理器上,自旋鎖僅僅當作一個設定核心搶占的開關。如果核心搶占也不存在,那麼自旋鎖會在編譯時被完全剔除出核心。

  簡單的說,自旋鎖在核心中主要用來防止多處理器中并發通路臨界區,防止核心搶占造成的競争。另外自旋鎖不允許任務睡眠(持有自旋鎖的任務睡眠會造成自死鎖--因為睡眠有可能造成持有鎖的核心任務被重新排程,而再次申請自己已持有的鎖),它能夠在中斷上下文中使用。

  死鎖:假設有一個或多個核心任務和一個或多個資源,每個核心都在等待其中的一個資源,但所有的資源都已經被占用了。這便會發生所有核心任務都在互相等待, 但它們永遠不會釋放已經占有的資源,于是任何核心任務都無法獲得所需要的資源,無法繼續運作,這便意味着死鎖發生了。自死瑣是說自己占有了某個資源,然後 自己又申請自己已占有的資源,顯然不可能再獲得該資源,是以就自縛手腳了。

  信号量

  -------------------------------------------------- ----

  Linux中的信号量是一種睡眠鎖。如果有一個任務試圖獲得一個已被持有的信号量時,信号量會将其推入等待隊列,然後讓其睡眠。這時處理器獲得自由去執行 其它代碼。當持有信号量的程序将信号量釋放後,在等待隊列中的一個任務将被喚醒,進而便可以獲得這個信号量。

  信号量的睡眠特性,使得信号量适用于鎖會被長時間持有的情況;隻能在程序上下文中使用,因為中斷上下文中是不能被排程的;另外當代碼持有信号量時,不可以再持有自旋鎖。

  信号量基本使用形式為:

  static DECLARE_MUTEX(mr_sem);//聲明互斥信号量

  if(down_interruptible(&mr_sem))

  //可被中斷的睡眠,當信号來到,睡眠的任務被喚醒

  //臨界區

  up(&mr_sem);

  信号量和自旋鎖差別

  -------------------------------------------------- ----

  雖然聽起來兩者之間的使用條件複雜,其實在實際使用中信号量和自旋鎖并不易混淆。注意以下原則:

  如果代碼需要睡眠--這往往是發生在和使用者空間同步時--使用信号量是唯一的選擇。由于不受睡眠的限制,使用信号量通常來說更加簡單一些。如果需要在自旋 鎖和信号量中作選擇,應該取決于鎖被持有的時間長短。理想情況是所有的鎖都應該盡可能短的被持有,但是如果鎖的持有時間較長的話,使用信号量是更好的選 擇。另外,信号量不同于自旋鎖,它不會關閉核心搶占,是以持有信号量的代碼可以被搶占。這意味者信号量不會對影響排程反應時間帶來負面影響。

  自旋鎖對信号量

  -------------------------------------------------- ----

  需求 建議的加鎖方法

  低開銷加鎖 優先使用自旋鎖

  短期鎖定 優先使用自旋鎖

  長期加鎖 優先使用信号量

  中斷上下文中加鎖 使用自旋鎖

  持有鎖是需要睡眠、排程 使用信号量

  以下部分的來源:kcrazy的紙¨

  自旋鎖我的了解就好比

  小A,小B,小C,小D 同住一個屋子,可屋子隻有一間茅房和一個馬桶。他們誰想"便"的時候誰就要把茅房的門鎖上,然後占據馬桶,比如小A正在占有,聚精會神,非常惬意。碰巧小 C此時甚急,但沒辦法,因為小A已經把門上了鎖。于是小B在門口急得打轉,即為"自旋"。注意這個"自旋"兩個字用的好,小B不是一看門被上鎖就回屋睡覺 去了,而是在門口"自旋"。... 最終的結果是小A開鎖,小B占用。而且在開鎖閉鎖過程中動作幹淨利落,不容他人搶在前面。

  如此周而複始......

  這裡的 小A,B,C,D 即為處理器,茅房的鎖即為自旋鎖。當其他處理器想通路這個公共的資源的時候就要先擷取這個鎖。如果鎖被占用,則自旋(循環)等待。

  小A的聚精會神代表了IRQL為2,開關鎖動作快表示為原子操作。

  -------------------------------------------------- --------

  不知道我了解的對還是不對,可能這樣舉例有些不恰當。有了解不對之處希望指點一二,以免誤入歧途,悔之晚矣。

  -------------------------------------------------- --------

  寫了個測試程式測試了一下:

  KSPIN_LOCK spinlock;

  NTSTATUS DriverEntry(

  IN PDRIVER_OBJECT DriverObject,

  IN PUNICODE_STRING RegistryPath

  )

  {

  NTSTATUS Status;

  UNICODE_STRING DeviceName;

  PDEVICE_OBJECT DeviceObject;

  HANDLE ThreadHandle;

  KIRQL oldirql;

  KIRQL irql;

  ULONG Processor;

  ULONG i;

  DeviceObject = NULL;

  RtlInitUnicodeString( &DeviceName, deviceNameBuffer );

  Status = IoCreateDevice( DriverObject,

  0,

  &DeviceName,

  FILE_DEVICE_UNKNOWN,

  0,

  FALSE,

  &DeviceObject );

  if ( !NT_SUCCESS(Status) )

  {

  return Status;

  }

  DriverObject->DriverUnload = DriverUnload;

  KeInitializeSpinLock( &spinlock ); // (2)

  PsCreateSystemThread( &ThreadHandle, THREAD_ALL_ACCESS, NULL, NULL, NULL, ThreadRoutine, NULL );

  i = 10000;

  KeAcquireSpinLock( &spinlock, &oldirql );

  while (i--)

  {

  __asm nop

  irql = KeGetCurrentIrql();

  Processor = KeGetCurrentProcessorNumber();

  KdPrint(( " [%d] CurrentIrql:\t%d", Processor, irql ));

  }

  KeReleaseSpinLock( &spinlock, oldirql ); return Status;

  } VOID

  ThreadRoutine( IN PVOID StartContext )

  {

  KIRQL oldirql;

  KIRQL irql;

  ULONG Processor;

  ULONG i; i = 10000; KeAcquireSpinLock( &spinlock, &oldirql ); // (1) while (i--)

  {

  __asm nop

  irql = KeGetCurrentIrql();

  Processor = KeGetCurrentProcessorNumber(); KdPrint(( "**[%d] CurrentIrql:\t%d", Processor, irql ));

  } KeReleaseSpinLock( &spinlock, oldirql ); // (1)

  } -------------------------------------------------- ------------------------------- 首先說明一下我是雙核系統,如果是單核的話我想進入自旋鎖之後IRQL已經提高到 DPC 級别,第二個線程就跑不起來了。如果他神奇的跑了起來,那一定會發生死鎖。

  分幾種情況測試:

  1、就是上邊的代碼測試

  先抓到鎖的先跑,後抓到鎖的後跑。并且被鎖的期間的IRQL 為 DPC 級别。

  2、去掉 标記 (1) 的兩行

  結果是兩個線程同時跑,一個占處理器 [0] 一個占處理器 [1]

  上鎖的那個 IRQL 級别是 DPC 級。

  沒上鎖的IRQL為 0

  即 自旋鎖 并不影響其他處理器的正常運作。除非其他處理器也想獲得這個鎖。

  3、去掉 标記 (2) 的一行(spinlock 是全局變量)

  和 1 的結果相同,因為全局變量預設是初始化為0的。

繼續閱讀