Linux-信號量與P,V操作


Linux-信號量與P,V操作

內容

  • 使用信號量實現進程互斥
  • 使用信號量及PV實現子進程讀寫同步

機理

Linux信號量集

Linux信號量作為IPC機制的一種,與其他通信方式類似,Linux也是通過kern_ipc_perm結構中的key來唯一標志一個信號量集,並通過該結構設置並檢查訪問權限。針對信號量集,系統維護一個由信號量集組成的數組,數組中的每個單元指向一個信號量集。

PV原語

PV操作是典型的同步機制之一。用一個信號量與一個消息聯系起來,當信號量的值為0時,表示期望的消息尚未產生;當信號量的值非0時,表示期望的消息已經存在。用PV操作實現進程同步時,調用P操作測試消息是否到達,調用V操作發送消息。

調用函數說明

創建一個新的信號量集或獲取一個已存在的信號量集

int semget(key_t key ,int nsems ,int semflg)

參數說明:

key

  • 使用IPC_PRIVATE,由系統產生key值並返回標識符,或者返回key值已存在的信號集的標識符。
  • 用戶指定一個非0整數型值,對信號量集的打開或存取依賴於semflg參數的取值。

nsems指定打開或者新創建的信號量集將包含的信號量的數目。

semflg當key不為IPC_PRIVATE時使用

  • 若只設置semflg的IPC_CREAT位,則創建一個信號量集,如果該信號量集已經存在,則返回其標識符。
  • semflg的IPC_CREAT|IPC_EXCL位,則創建一個新的信號量集,如果該key值的信號量已經存在,則返回錯誤信息。
  • 只設置IPC_EXCL而不設置IPC_CREAT位沒有任何意義

返回值:正確返回信號量集的標識符,錯誤時返回-1。

如,創建一個只包含一個信號量的信號量集:

  • semid = semget(IPC_PRIVATE,1,IPC_CREAT|0666);

對信號量的PV操作

int semop(int semid ,struct sembuf * sops ,unsigned nsops);

參數說明:

  • semid是信號量集的標識符,由semget()得到
  • sops指向一個sembuf結構數組,該數組的每一個元素對以一次信號量操作。
struct sembuf
{
    unsigned short sem_num;		/*semaphore index in array*/
    short sem_op;				/*semaphore operation*/
    short sem_flg;				/*operation flags*/
};
/*
sem_num標明它是信號量集的第幾個元素,從0開始
sem_op指定信號量采取的操作
	<0相當於P操作,占有資源
	>0相當於V操作,釋放資源
	=0進程睡眠直到信號量的值為0
sem_flg指明操作的執行模式,兩個標志位。一個是IPC_NOWAIT,指明以非阻塞方式操作信號量。一個是SEM_UNDO,指明內核為信號量操作保留恢復值。
*/

返回值:正確返回0,錯誤時返回-1

信號量集的控制函數

int semctl(int semid ,int semnum ,int cmd ,union semun arg);

參數說明:

  • semid是信號量集的標識符,由semget()得到
  • semnum指定semid信號量集的第幾個信號量,在撤銷信號量集時,此參數可缺省。
  • cmd指定操作類型。
取值 含義
GETVAL 返回semnum指定的信號量的semval域值
SETVAL 指定semval域值為arg.val
GETPID 返回semnum指定信號量sempid
GETNCNT 返回semncnt
GETZCNT 返回semzcnt
GETALL 返回所有信號量的值,結果保存到arg.array中
SETALL 通過arg.array更新所有信號量的值
IPC_STAT 獲取信號量集的arg.array,存入arg.buf
IPC_SET 將arg.buf數據結構的sem_perm.uid,sem_perm.gid,sem_perm.mode賦給sem_array,此操作僅限root、sem_perm.cuid或sem_perm.uid
IPC_RMID 刪除指定信號量集。此操作僅限root、sem_perm.cuid或sem_perm.uid
IPC_INFO 獲取信號量集的相關信息存放於arg.buf中
  • arg為5中數據類型的共同體類型semnu,該類型在include/linux/sem.h中定義
union senum
{
	int val;					/*value for setval*/
	struct semi_ds *buf;		/*buffer for IPC_STAT & IPC_SET*/
	unsigned short *array;		/*array for GETALL & SETALL*/
	struct seminfo *_buf;		/*buffer for IPC_INFO*/
	void *_pad
};

返回值:正確時根據cmd的的不同返回值或0,錯誤時返回-1。

撤銷信號量集

semctl(semid ,IPC_RMID ,0)

思路

  • 定義信號量標志符:int semid;
  • 定義信號量數據結構
    • 定義PV操作所用的數據結構:struct sembuf P,V;
    • 定義給信號量賦初值的參數數據結構:union semun arg;
  • 申請一個信號量的信號量集
  • 對每一個信號量semid賦初值
  • 定義信號量的P操作
  • 定義信號量的V操作
  • 執行PV操作
  • 撤銷信號量

實例

  • 利用信號量實現進程互斥
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/sem.h>
/*
父子進程共享一個臨界資源(這里就是stdout),
每個進程循環進入臨界區3次
父進程進入后顯示parent in
父進程出去后顯示parent out
子進程進入后顯示chld in
子進程出去后顯示chld out
*/
union semun{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
};
int mutexid;									//互斥信號量

int main()
{
	int chld,i,j;
	
	struct sembuf P,V;
	union semun arg;
	/*****申請只有一個信號量的信號量集*****/
	mutexid=semget(IPC_PRIVATE,1,0666|IPC_CREAT);
	/*****分別對每個信號量賦初值*****/
	arg.val=1;
	if(semctl(mutexid,0,SETVAL,arg)==-1)perror("semctl setval error");
	/*****定義PV操作*****/
	P.sem_num=0;
	P.sem_op=-1;
	P.sem_flg=SEM_UNDO;
	V.sem_num=0;
	V.sem_op=1;
	V.sem_flg=SEM_UNDO;
	
	while((chld=fork())==-1);
	if(chld>0)
	{/*****父進程塊*****/
		i=1;
		while(i<=3)
		{
			sleep(1);
			semop(mutexid,&P,1);			/*占有臨界資源*/
			printf("parent in\n");
			sleep(1);
			printf("parent out\n");
			semop(mutexid,&V,1);			/*釋放臨界資源*/
			i++;
		}
		wait(0);
		/*****撤銷信號量集*****/
		semctl(mutexid,IPC_RMID,0);
		exit(0);
	}
	else
	{/*****子進程塊*****/
		j=1;
		while(j<=3)
		{
			sleep(1);
			semop(mutexid,&P,1);
			printf("chld in\n");
			sleep(1);
			printf("chld out\n");
			semop(mutexid,&V,1);
			j++;
		}
		exit(0);
	}
}

運行結果

  • 利用信號量實現進程同步
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<sys/shm.h>
/*
父子進程共用一個存儲區,
子進程寫入信息,父進程
讀取信息。
*/
union semun{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
};

int emptyid;
int fullid;									//同步信號量

main()
{
	int chld;
	
	struct sembuf P,V;
	union semun arg;
	
	int shmid;
	char *viraddr;
	char buffer[BUFSIZ];
	/*****申請只有一個信號量的信號量集*****/
	emptyid=semget(IPC_PRIVATE,1,IPC_CREAT|0666);
	fullid=semget(IPC_PRIVATE,1,IPC_CREAT|0666);
	/*****分別對每個信號量賦初值*****/
	arg.val=1;
	if(semctl(emptyid,0,SETVAL,arg)==-1)perror("semctl setval error");
	arg.val=0;
	if(semctl(fullid,0,SETVAL,arg)==-1)perror("semctl setval error");
	/*****定義PV操作*****/
	P.sem_num=0;
	P.sem_op=-1;
	P.sem_flg=SEM_UNDO;
	V.sem_num=0;
	V.sem_op=1;
	V.sem_flg=SEM_UNDO;
	/*****申請共享內存空間*****/
	shmid=shmget(IPC_PRIVATE,BUFSIZ,0666|IPC_CREAT);
	viraddr=(char*)shmat(shmid,0,0);
	
	while((chld=fork())==-1);
	if(chld>0)
	{
		while(1)
		{
			semop(fullid,&P,1);
			printf("Your message is:\n%s",viraddr);
			semop(emptyid,&V,1);
			if(strncmp(viraddr,"end",3)==0)break;
		}
		wait(0);
		shmdt(viraddr);
		/*****撤銷信號量集、釋放共享內存*****/
		shmctl(shmid,IPC_RMID,0);
		semctl(emptyid,IPC_RMID,0);
		semctl(fullid,IPC_RMID,0);
		printf("Parent exit!\n");
		exit(0);
	}
	else
	{
		while(1)
		{
			semop(emptyid,&P,1);
			puts("Enter your text:");
			fgets(buffer,BUFSIZ,stdin);
			strcpy(viraddr,buffer);
			semop(fullid,&V,1);
			if(strncmp(viraddr,"end",3)==0)
			{
				sleep(1);
				break;
			}
		}
		printf("Child exit!\n");
		exit(0);
	}
}

運行結果


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM