Linux中的System V訊號量

Ephemerally發表於2020-12-28

在程式同步,併發執行時,保證按序地訪問共享資源是十分重要的。因此引入了臨界區的概念,一次只能有一個執行緒進入臨界區完成他的指令。而訊號量(semaphore)的作用,類似於一個交通訊號燈,它負責程式協作,因此訊號量又稱為訊號燈。

在Linux系統中,它提供兩種訊號量:

  • 核心訊號量,由核心控制路徑使用

  • 使用者態程式使用的訊號量,這種訊號量有兩種介面,POSIX訊號量和SYSTEM V訊號量。

    訊號量的本質是一個計數器。一個較為常見的用法,是為每個資源都會分配一個訊號量。記訊號量為S,除了初始化之外,有兩個標準原子操作:wait()signal()

System V訊號量介面

  • semget

    建立一個新訊號量或取得一個已有訊號量

    int semget(key_t key, int num_sems, int sem_flags);
    

    key是一個整數值(唯一非零),可以理解成是訊號量的識別符號。

    num_sems指定了需要的訊號量數目,通常為1。

    sem_flags是一組標誌,當建立一個新的訊號量時,設定許可權與值IPC_CREAT做按位或操作。設定了IPC_CREAT標誌後,即使給出的鍵是一個已有訊號量的鍵,也不會產生錯誤。而IPC_CREAT | IPC_EXCL則可以建立一個新的,唯一的訊號量,如果訊號量已存在,返回一個錯誤。

    函式成功返回一個相應訊號識別符號(非零),失敗返回-1

  • semctl

    直接控制訊號量資訊

    int semctl(int sem_id, int sem_num, int command, ...);
    

    第二個引數是操作訊號在訊號集中的編號,第一個訊號的編號是0

    第三個引數command通常是下面兩個值中的其中一個:

    SETVAL:用來把訊號量初始化為一個已知的值。

    IPC_RMID:用於刪除一個已經無需繼續使用的訊號量識別符號。

    如果有第四個引數,它通常是一個union semum結構,定義如下:

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

    改變訊號量的值

    int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
    

    sem_id是由semget返回的訊號量識別符號,sembuf結構的定義如下:

    struct sembuf{
        short sem_num;//除非使用一組訊號量,否則為0
        short sem_op;//訊號量在一次操作中需要改變的資料,-1即P(等待)操作,+1即V(傳送訊號)操作。
        short sem_flg;//通常為SEM_UNDO,使作業系統跟蹤訊號,並在程式沒有釋放該訊號量而終止時,作業系統釋放訊號量
    };
    

程式同步例項

無訊號量例項
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
 
int main()
{
	pid_t pid;
	pid = fork();
	srand(pid); 
	if(pid > 0) // parent process
	{
		char a = 'A'; // char to print
		for(int i = 0; i < 10; ++i)
		{
			printf("%c", a);
			fflush(stdout); // flush stdout buffer
			sleep(1);

			printf("%c", a);
			fflush(stdout); 
			sleep(1);
		}
	}
	else // child process
	{
		char b = 'B'; 
		for(int i = 0; i < 10; ++i)
		{
			printf("%c", b);
			fflush(stdout); 
			sleep(1);

			printf("%c", b);
			fflush(stdout); 
			sleep(1);
		}
	}
	printf("\n%d - finished\n", getpid());
	sleep(3);
	return 0;
}

執行結果

有訊號量例項
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

#define SEMKEY 0x00002222 // set a key for semaphore

union semun // union for semaphore
{
	int val;
	struct semid_ds *buf;
	unsigned short  *array;
};

struct sembuf p = { 0, -1, SEM_UNDO};
struct sembuf v = { 0, +1, SEM_UNDO};

int main()
{
	int sem_id = semget(SEMKEY, 1, 0666 | IPC_CREAT); // get semaphore  
	
	union semun sem_union;
	sem_union.val = 1;
	if(semctl(sem_id, 0, SETVAL, sem_union) < 0)
	{
		perror("semctl error");
		return -1;
	}
	int pid;
	pid = fork();
	srand(pid); 
	if(pid > 0) // parent process
	{
		char a = 'A'; // char to print
		for(int i = 0; i < 10; ++i)
		{
			if(semop(sem_id, &p, 1) < 0) // P operation
			{
				perror("semop p error");
				return -1;
			}
			printf("%c", a);
			fflush(stdout); // flush stdout buffer
			sleep(1);

			printf("%c", a);
			fflush(stdout); 
			if(semop(sem_id, &v, 1) < 0) // V operation
			{
				perror("semop v error");
				return -1;
			}
			sleep(1);
		}
	}
	else // child process
	{
		char b = 'B'; // char to print
		for(int i = 0; i < 10; ++i)
		{
			if(semop(sem_id, &p, 1) < 0) // P operation
			{
				perror("semop p error");
				return -1;
			}
			printf("%c", b);
			fflush(stdout); // flush stdout buffer
			sleep(1);

			printf("%c", b);
			fflush(stdout); 
			if(semop(sem_id, &v, 1) < 0) // V operation
			{
				perror("semop v error");
				return -1;
			}
			sleep(1);
		}
	}
	printf("\n%d - finished\n", getpid());
	sleep(3);
	if (pid > 0)
	{
		system("ipcrm -S 0x00002222");
	}
	return 0;
}

執行結果

因為設定訊號量的關係,一個執行緒在臨界區內一定會執行兩次print()操作,所以A或B一定成對出現。

相關文章