1. 程式人生 > >嵌入式Linux併發程式設計,程序間通訊方式,System V IPC,訊號燈集,建立/開啟semget(),初始化semctl(),P/V操作semop(),sembuf結構體定義

嵌入式Linux併發程式設計,程序間通訊方式,System V IPC,訊號燈集,建立/開啟semget(),初始化semctl(),P/V操作semop(),sembuf結構體定義

文章目錄

1,System V IPC - 訊號燈

  1. 訊號燈也叫訊號量,用於程序/執行緒同步或互斥的機制
  2. 訊號燈的型別
    ·Posix 無名訊號燈
    ·Posix有名訊號燈
    ·System V 訊號燈
  3. 訊號燈的含義
    ·計數訊號燈(訊號燈的值就是他代表的資源的數量,Posix 無名訊號燈/Posix有名訊號燈,都是計數訊號燈)

2,System V IPC - 訊號燈特點

  1. System V 訊號燈是一個或多個計數訊號燈的集合
  2. 可同時操作集合中的多個訊號燈
  3. 申請多個資源時避免死鎖(訊號燈在一個集合裡,需要的資源同時申請,都滿足時資源才能申請到)

3,System V訊號燈使用步驟

  1. 開啟/建立訊號燈 semget
  2. 訊號燈初始化 semctl
  3. P/V操作 semop
  4. 刪除訊號燈 semctl

3.1,訊號燈建立/開啟 semget()

#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);

  1. 成功時返回訊號燈的id,失敗時返回-1
  2. key 和訊號燈關聯的key IPC_PRIVATE 或 ftok
  3. nsems 集合中包含的計數訊號燈個數
  4. semflg 標誌位 IPC_CREAT|0666 IPC_EXCL(通常和IPC_CREAT一起使用,加上IPC_EXCL,如果訊號燈存在,則報錯)

訊號燈在使用之前必須初始化,且只能初始化一次,通常第一個程序初始化,後面的程序就不能初始化了。

if((semid = semget(key,3,IPC_CREAT|0666|IPC_EXCL)) < 0)
{
	if(errno == EEXIST)
	{
		semid = semget(key,3,IPC_CREAT|0666)
	}
}
else
{
	初始化訊號燈集;
}

3.2,訊號燈初始化 semctl()

#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …);

  1. 成功時返回0,失敗時返回EOF
  2. semid 要操作的訊號燈集id
  3. semnum 要操作的集合中的訊號燈編號
  4. cmd 執行的操作 SETVAL(設定訊號燈值) IPC_RMID(刪除整個訊號燈集)
  5. union semun 取決於cmd(SETVAL需要用到第四個引數,IPC_RMID不需要第四個引數),該共用體需要使用者自己定義

系統中union semun 共用體參考

union semun {union semun {
   	int              val;    /* Value for SETVAL *///訊號量初始值
   	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
   	unsigned short  *array;  /* Array for GETALL, SETALL */
   	struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                          (Linux-specific) */
          };

3.2.1,訊號燈初始化—示例

要求:假設訊號燈集合中包含兩個訊號燈;第一個初始化為2,第二個初始化為0

union  semun  myun;

myun.val = 2;
if (semctl(semid, 0, SETVAL, myun) < 0) 
{
	perror(“semctl”);     exit(-1);
}
myun.val = 0;
if (semctl(semid, 1, SETVAL, myun) < 0) 
{
	perror(“semctl”);     exit(-1);
}

3.3,訊號燈P/V操作 semop()

#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);

  1. 成功時返回0,失敗時返回-1
  2. semid 要操作的訊號燈集id
  3. sops 描述對訊號燈操作的結構體(陣列)
  4. nsops 要操作的訊號燈的個數(一次操作sops數組裡的前nsops個元素)

3.3.1,訊號燈操作 sembuf結構體定義

struct  sembuf 
 {
     short  sem_num;
     short  sem_op;
     short  sem_flg;
 };
  1. semnum 訊號燈編號
  2. sem_op -1:P操作 1:V操作 (也可以讓訊號數量增加或減少多個,將“1”換成“n”)
  3. sem_flg 0(阻塞方式) / IPC_NOWAIT
假設當前訊號燈集合中有3個訊號燈(意味著最多可以同時操作3個訊號燈),編號:0,1,2
現在同時對編號為0和2的訊號燈程序P操作
struct  sembuf buf[3];//結構體陣列的大小應該和訊號燈集中訊號燈的數目保持一致
buf[0].sem_num = 0;
buf[0].sem_op = -1;
buf[0].sem_flg = 0;
buf[1].sem_num = 2;
buf[1].sem_op = -1;
buf[1].sem_flg = 0;
semop(semid,&buf,2);//2表示依次取出buf中的前兩個元素進行操作

4,訊號燈集/共享記憶體—示例

要求:父子程序通過System V訊號燈同步對共享記憶體的讀寫
·父程序從鍵盤輸入字串到共享記憶體
·子程序刪除字串中的空格並列印
·父程序輸入quit後刪除共享記憶體和訊號燈集,程式結束

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>

#define N 64  	//共享記憶體大小
#define READ 0	//可讀緩衝區(個數)訊號燈在訊號燈集合中的編號為0
#define WRITE 1	//可寫緩衝區(個數)訊號燈在訊號燈集合中的編號為1

union semun{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
	struct seminfo * __buf;
};
void init_sem(int semid,int s[],int n);
void sem_pv(int semid,int num,int op);

int main(int argc, const char *argv[])
{
	int shmid,semid,s[]={0,1};
	pid_t pid;
	key_t key;
	char *shmaddr;

	if((key = ftok(".",'s')) == -1)//生成KEY值
	{
		perror("ftok");
		exit(-1);
	}

	if((shmid = shmget(key,N,IPC_CREAT|0666)) < 0)//申請共享記憶體
	{
		perror("shmget");
		exit(-1);
	}

	if((semid = semget(key,2,IPC_CREAT|0666)) < 0)//申請訊號燈集
	{
		perror("semget");
		goto _error1;//申請失敗,釋放申請的共享記憶體,程式退出
	}

	init_sem(semid,s,2);//訊號燈集初始化

	if((shmaddr = shmat(shmid,NULL,0)) == (char *)-1)//對映共享記憶體地址
	{
		perror("shmat");
		goto _error2;//對映失敗,釋放申請的訊號燈集,釋放申請的共享記憶體,程式退出
	}
	
	if((pid = fork()) < 0)//建立子程序
	{
		perror("fork");
		goto _error2;
	}
	else if(pid == 0)
	{
		char *p,*q;
		while(1)
		{
			sem_pv(semid,READ,-1);//對讀訊號燈進行P操作
			p = q = shmaddr;
			while(*q)
			{
				if(*q != ' ')
				{
					*p++ = *q;
				}
				q++;
			}
			*p = '\0';
			printf("%s",shmaddr);
			sem_pv(semid,WRITE,1);//對寫訊號燈進行V操作
		}
	}
	else
	{
		while(1)
		{
			sem_pv(semid,WRITE,-1);//對寫訊號燈進行P操作
			printf("input > ");
			fgets(shmaddr,N,stdin);
			if(strcmp(shmaddr,"quite\n") == 0)break;
			sem_pv(semid,READ,1);//對讀訊號燈進行V操作
		}
		kill(pid,SIGUSR1);//給子程序發訊號,讓其結束
	}


_error2:
	semctl(semid,0,IPC_RMID);//釋放申請的訊號燈集
_error1:
	shmctl(shmid,IPC_RMID,NULL);//釋放申請的共享記憶體

	return 0;
}

void init_sem(int semid,int s[],int n)//訊號燈集初始化函式
{
	int i;
	union semun myyun;
	
	for(i=0;i<n;i++)
	{
		myyun.val = s[i];//讀訊號燈值初始化為0,寫訊號燈值初始化為1
		semctl(semid,i,SETVAL,myyun);
	}
}

void sem_pv(int semid,int num,int op)//訊號燈集P/V操作函式
{
	struct sembuf buf;

	buf.sem_num = num;//訊號燈編號
	buf.sem_op = op;
	buf.sem_flg = 0;
	semop(semid,&buf,1);
}