1. 程式人生 > >Linux下程序間通訊方式之管道、訊號、共享記憶體、訊息佇列、訊號量、套接字

Linux下程序間通訊方式之管道、訊號、共享記憶體、訊息佇列、訊號量、套接字

/*
1,程序間通訊 (IPC ) Inter-Process Communication
  比較好理解概念的就是程序間通訊就是在不同程序之間傳播或交換資訊。
  2,linux下IPC機制的分類:管道、訊號、共享記憶體、訊息佇列、訊號量、套接字

  3,這篇主要說說管道:本質是檔案,其他理論什麼的網上已經有一大堆了,我就只寫一點用法吧。
  3.1 特點

  1)管道是最古老的IPC,但目前很少使用
  2)以檔案做互動的媒介,管道分為有名管道和無名管道
  3)歷史上的管道通常是指半雙工管道


  3.2 管道:有兩種形式,命令列和非命令列
  (1)命令列:

  mkfifo testfifo
  echo "testfifo" >fifo
  cat fifo

  (2)非命令列:這裡又分有名管道和無名管道

  程式設計模型:程序A建立管道(mkfifo) -> 程序A寫開啟管道(open) -> 程序B讀開啟管道(open) -> 程序A開始往管道里寫資料(write) ->

         程序B從管道中讀資料(read) -> 程序A關閉管道(close) -> 程序B關閉管道(close) -> 刪除管道(unlink)
*/
//有名管道(例項):
//程序A:

#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>

#define PIPENAME "pipetest"

int main()
{
	// 建立管道
	if(mkfifo(PIPENAME, 0666) < 0)
	{
		perror("mkfifo");
		return -1;
	}

	// 寫開啟管道 
	int fd = open(PIPENAME, O_WRONLY);
	if(-1 == fd)
	{
		perror("open");
		return -1;
	}

	unlink(PIPENAME);

	int i = 0;
	for(i = 0; i < 10; i++)
	{
		write(fd, &i, sizeof(i));
		printf("%d\n", i);
		sleep(1); // 這個是以秒為單位掛起
	}

	// 關閉管道
	close(fd);

	return 0;

}

//程序B:

#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>

#define PIPENAME "pipetest"

int main()
{
	// 讀開啟管道
	int fd = open(PIPENAME, O_RDONLY);
	if(-1 == fd)
	{
		perror("open");
		return -1;
	}

	int num = 0;
	int i = 0;
	for(i = 0; i < 10; i++)
	{
		read(fd, &num, sizeof(int));
		printf("%d\n", num);
		fflush(stdout); // 強制重新整理輸出緩衝區
	}

	printf("\n");
	close(fd);

	return 0;

}

/*
一,訊息佇列

1,概念:“訊息佇列”是在訊息的傳輸過程中儲存訊息的容器

2,訊息佇列就是一個訊息的連結串列。可以把訊息看作一個記錄,具有特定的格式以及特定的優先順序。
  對訊息佇列有寫許可權的程序可以向訊息佇列中按照一定的規則新增新訊息;
   對訊息佇列有讀許可權的程序則可以從訊息佇列中讀走訊息。
   訊息佇列是隨核心持續的。

3,程式設計注意事項:使用時先把資料封裝成訊息,把訊息存入佇列
	  程式設計步驟: 具體函式的用法可以用man手冊檢視(強力推薦)
	  (1)ftok()生產key
	  (2)使用msgget( ) 建立/獲取訊息佇列,返回值是佇列識別符號
	  (3)使用msgsnd( ) 傳送訊息
	     使用msgrcv( ) 接收訊息
	  (4)使用msgctl( ) 刪除訊息佇列
4,例項:

sendmsg.c   用來發送訊息的
*/
// sendmsg.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>

struct my_msg
{
	int mtype; // 訊息型別
	char buf[256];
}msg1, msg2;

int main()
{
	key_t key = ftok("./", 88);

	int msgid = msgget(key, 0666|IPC_CREAT);
	if(-1 == msgid)
	{
		perror("msgget failed!!!");
		exit(1);
	}

	msg1.mtype = 2;
	strcpy(msg1.buf, "hello, msg2");
	msgsnd(msgid, &msg1, sizeof(msg1), 0); // 阻塞
	//    msgsnd(msgid, &msg1, sizeof(msg1), IPC_NOWAIT); // 非阻塞

	msg2.mtype = 1;
	strcpy(msg2.buf, "hello, msg1");
	msgsnd(msgid, &msg2, sizeof(msg2), 0); // 阻塞

	printf("訊息傳送完成,按回車銷燬訊息佇列\n");
	getchar();

	if(-1 == shmctl(msgid, IPC_RMID, NULL))
	{
		perror("shmctl failed");
		exit(2);
	}
	return 0;
}

//recvmsg.c  用來接收訊息的

// recvmsg.c

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<string.h>

struct my_msg
{
	int mtype; // 訊息型別
	char buf[256];
}msg;

int main()
{
	key_t key = ftok("./", 88);

	// 獲取訊息佇列
	int msgid = msgget(key, 0);
	if(-1 == msgid)
	{
		perror("msgget failed!!!");
		exit(1);
	}

	int res = msgrcv(msgid, &msg, sizeof(msg),
		2, // 取訊息型別為2的訊息
		0);
	printf("型別:%d, 內容:%s\n", msg.mtype, msg.buf);

	printf("訊息接收完成,按回車銷燬訊息佇列\n");
	getchar();

	if(-1 == shmctl(msgid, IPC_RMID, NULL))
	{
		perror("shmctl failed");
		exit(2);
	}
	return 0;
}

/*
一,共享記憶體
核心管理一片實體記憶體,允許不同的程序同時對映,多個程序可以對映同一塊記憶體,被多個程序同時對映的實體記憶體,即共享記憶體。
對映實體記憶體叫掛接,用完以後解除對映叫脫接。

1,共享記憶體的特點:
    優點:是最快的IPC。
    缺點:要程式設計者自己實現對共享記憶體互斥訪問。如何實現?

2,程式設計模型:具體函式的用法可以用man手冊檢視(強力推薦)

	程序A: writeshm.c
	1) 獲得key, ftok()
	2) 使用key來建立一個共享記憶體 shmget()
	3) 對映共享記憶體(得到虛擬地址), shmat()
	4) 使用共享記憶體, 往共享記憶體中寫入資料
	5) 解除對映 shmdt()
	6) 如果共享記憶體不再使用,可以使用shmctl()銷燬共享記憶體

	程序B: readshm.c     
  1) 獲得key, ftok()     
   2) 使用key來獲得一個共享記憶體 shmget()     
	3) 對映共享記憶體(得到虛擬地址), shmat()     
	4) 使用共享記憶體, 讀取共享記憶體中的資料     
	5) 解除對映 shmdt()     
3,例項

*/

//程序A:
// writeshm.c

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>

int main()
{
  // 生成一個key
  key_t key = ftok("./", 66);

  // 建立共享記憶體,返回一個id
  int shmid = shmget(key, 8, IPC_CREAT|0666|IPC_EXCL);
  if(-1 == shmid)
  {
	  perror("shmget failed");
	  exit(1);
  }

  // 對映共享記憶體,得到虛擬地址
  void *p = shmat(shmid, 0, 0);
  if((void*)-1 == p)
  {
	  perror("shmat failed");
	  exit(2);
  }

  // 寫共享記憶體
  int *pp = p;
  *pp = 0x12345678;
  *(pp + 1) = 0xffffffff;

  // 解除對映
  if(-1 == shmdt(p))
  {
	  perror("shmdt failed");
	  exit(3);
  }
  printf("解除對映成功,點選回車銷燬共享記憶體\n");
  getchar();

  // 銷燬共享記憶體
  if(-1 == shmctl(shmid, IPC_RMID, NULL))
  {
	  perror("shmctl failed");
	  exit(4);
  }

  return 0;
}

//程序B:

// readshm.c

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>

 int main()
{
  // 生成一個key
  key_t key = ftok("./", 66);

  // 獲取共享記憶體,返回一個id
  int shmid = shmget(key, 0, 0);
  if(-1 == shmid)
  {
	  perror("shmget failed");
	  exit(1);
  }

  // 對映共享記憶體,得到虛擬地址
  void *p = shmat(shmid, 0, 0);
  if((void*)-1 == p)
  {
	  perror("shmat failed");
	  exit(2);
  }

  // 讀共享記憶體
  int x = *(int *)p;
  int y = *((int *)p + 1);
  printf("從共享記憶體中都取了:0x%x 和 0x%x \n", x, y);

  // 解除對映
  if(-1 == shmdt(p))
  {
	  perror("shmdt failed");
	  exit(3);
  }

  return 0;
}

/*
一、訊號量

1,訊號量本質是一個計數器,控制訪問共享資源的最大並行程序總數。(和訊號有很大的區別)

2,訊號量的使用主要是用來保護共享資源,使得資源在一個時刻只有一個程序(執行緒)所擁有。
訊號量的值為正的時候,說明它空閒。所測試的執行緒可以鎖定而使用它。若為0,說明它被佔用,測試的執行緒要進入睡眠佇列中,等待被喚醒。

3,訊號量分類:Linux提供兩種訊號量:
(1) 核心訊號量,由核心控制路徑使用
(2) 使用者態程序使用的訊號量,這種訊號量又分為POSIX訊號量和SYSTEMV訊號量。
POSIX訊號量又分為有名訊號量和無名訊號量。
有名訊號量,其值儲存在檔案中, 所以它可以用於執行緒也可以用於程序間的同步。無名訊號量,其值儲存在記憶體中。

乾貨來源:  http://blog.csdn.net/qinxiongxu/article/details/7830537

4,最簡單的訊號量是隻能取0和1的變數,這也是訊號量最常見的一種形式,叫做二進位制訊號量。

而可以取多個正整數的訊號量被稱為通用訊號量。這裡主要討論二進位制訊號量。

5,使用方法

使用時給其一個初始值,假如該資源允許同時兩個程序使用,初始值就設定為2,有一個程序使用該資源計數-1(原子操作),有一個程序放棄使用該資源計數+1(原子操作)。如果計數為0,不允許新的程序來訪問資源,新的程序阻塞等待,直到計數重新大於0解除阻塞。
如果有多個資源需要控制訪問,就需要多個訊號量,把多個訊號量存入陣列中,這個陣列就叫訊號量集。

二,程式設計實現

參考: http://blog.csdn.net/ljianhui/article/details/10243617    其實就是用這篇部落格的。

這裡用的是二進位制訊號量,初始值是1,最多允許1個程序獲取訊號量。 

這個例子採用兩個相同的程式往終端輸出字元,根據命令列引數加以區分。

*/

#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/sem.h>

union semun
{
  int val;
  struct semid_ds *buf;
  unsigned short *arry;
};

static int sem_id = 0;
static int set_semvalue();
static void del_semvalue();
static int semaphore_p();
static int semaphore_v();

int main(int argc, char **argv)
{
  char message = 'x';
  int i = 0;
  // 建立訊號量
  sem_id = semget((key_t)1234, 1, 0666|IPC_CREAT);

  if(argc > 1)
  {
	  // 程式第一次呼叫,初始化訊號量
	  if(!set_semvalue())
	  {
		  fprintf(stderr, "Failed Init semaphore\n");
		  exit(EXIT_FAILURE);
	  }

	  // 設定輸出到螢幕中的資訊
	  message = argv[1][0];
	  sleep(2);
  }

  for(i = 0; i < 10; i++)
  {
	  if(!semaphore_p()) // 進入臨界區
	  {
		  exit(EXIT_FAILURE);
	  }

	  printf("%c", message);
	  fflush(stdout); // 清理緩衝區
	  sleep(rand() % 3); // 休眠隨機時間
	  printf("%c", message);
	  fflush(stdout); 

	  if(!semaphore_v()) // 離開臨界區
	  {
		  exit(EXIT_FAILURE);
	  }
	  sleep(rand() % 2); // 休眠隨機時間
  }

  sleep(10);
  printf("\n %d - finished\n", getpid());

  if(argc > 1)
  {
	  sleep(3);
	  del_semvalue();
  }

  exit(EXIT_SUCCESS);
}

// 初始化訊號量
static int set_semvalue()
{
  union semun sem_union;
  sem_union.val = 1;
  if(-1 == semctl(sem_id, 0, SETVAL, sem_union))
  {
	  return 0;
  }
  return 1;
}

// 刪除訊號量 
static void del_semvalue()
{
  union semun sem_union;
  if(-1 == semctl(sem_id, 0, IPC_RMID, sem_union))
  {
	  fprintf(stderr, "Failed delete semphore\n");
  }
}

// 對訊號量-1操作,即等待P(sv)
static int semaphore_p()
{
  struct sembuf sem_b;
  sem_b.sem_num = 0;
  sem_b.sem_op = -1; // P()
  sem_b.sem_flg = SEM_UNDO;

  if(-1 == semop(sem_id, &sem_b, 1))
  {
	  fprintf(stderr, "Failed semaphore_p()\n");
	  return 0;
  }

  return 1;
}

// 釋放操作, +1, 傳送訊號V(sv)
static int semaphore_v()
{
  struct sembuf sem_b;
  sem_b.sem_num = 0;
  sem_b.sem_op = 1; // P()
  sem_b.sem_flg = SEM_UNDO;

  if(-1 == semop(sem_id, &sem_b, 1))
  {
	  fprintf(stderr, "Failed semaphore_v()\n");
	  return 0;
  }

  return 1;
}
/*
因為每個程式都在其進入臨界區後和離開臨界區前列印一個字元,所以每個字元都應該成對出現。
一個程序在列印時,會先執行P操作,若沒有列印完,也就是沒有執行V操作。另一個程序要執行列印,也要進行P操作,這時候由於訊號量的值為0,獲取訊號量失敗,程序只能掛起自己。等另一個程式釋放(V操作)才能列印。
任何時刻只有一個程序得到了訊號量,只有一個程序在執行列印
總結:
訊號量是一個特殊的變數,程式對其訪問都是原子操作,且只允許對它進行等待(即P(訊號變數))和傳送(即V(訊號變數))資訊操作。
我們通常通過訊號來解決多個程序對同一資源的訪問競爭的問題,使在任一時刻只能有一個執行執行緒訪問程式碼的臨界區域,
也可以說它是協調程序間的對同一資源的訪問權,也就是用於同步程序的。
*/