首先想到的問題是,為什麼我們需要信號量? 一個簡單的答案,以保護多個進程共用的關鍵/共同區域。
假設多個進程正在使用相同的代碼區域,如果所有人都想並行訪問,那麼結果是重疊的。 例如,多個用戶僅使用一臺印表機(通用/關鍵部分),例如3
個用戶,同時給予3
個作業,如果所有作業並行啟動,則一個用戶輸出與另一個用戶輸出重疊。 因此,我們需要使用信號量來保護這個信號,即當一個進程正在運行時鎖定關鍵部分,並在完成時解鎖。 這將為每個用戶/進程重複,以便一個作業不與另一個作業重疊。
基本上信號量分為兩類 -
- 二進位信號 - 只有兩個狀態
0
和1
,即鎖定/解鎖或可用/不可用,互斥實現。 - 計算信號量 - 允許任意資源計數的信號量稱為計數信號量。
假設有5臺印表機(要瞭解1
臺印表機只接受1
一項工作),我們有3
個列印作業。 現在有三個印表機(每個印表機1
個)提供3
個工作。 這項工作還在進行中,共有4
項工作。 現在,在可用的兩臺印表機中,已經安排了兩個作業,剩下兩個作業,只有在其中一個資源/印表機可用時才能完成。 根據資源可用性的這種調度可以被看作計數信號量。
要使用信號量執行同步,請執行以下步驟 -
第1步 - 創建一個信號量或連接到一個已經存在的信號量(semget()
)
第2步 - 對信號量執行操作,即分配或釋放或等待資源(semop()
)
第3步 - 在消息佇列(semctl()
)上執行控制操作
現在,讓我們查看一下系統調用。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
這個系統調用創建或分配一個System V信號集。 需要傳遞以下參數 -
- 第一個參數
key
用於識別消息佇列。key
可以是任意值,也可以是來自庫函數ftok()
的值。 - 第二個參數
nsems
指定了信號的數量。 如果二進位那麼它是1
,意味著需要1
個信號集,否則按照所需的信號量集計數。 - 第三個參數
semflg
指定所需的信號量標誌,如IPC_CREAT(如果不存在則創建信號量)或IPC_EXCL(與IPC_CREAT一起用於創建信號量,如果信號量已經存在,則調用失敗)。 還需要傳遞許可權。注 - 有關許可權的詳細資訊,請參閱前面幾節。
這個調用會在成功時返回有效的信號量識別字(用於進一步調用信號量),在失敗的情況下返回-1
。 要知道失敗的原因,請檢查errno變數或perror()函數。
關於這個調用的各種錯誤是EACCESS(許可權被拒絕),EEXIST(佇列已經存在不能創建),ENOENT(佇列不存在),ENOMEM(沒有足夠的記憶體來創建佇列),ENOSPC(最大限制 超過)等
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *semops, size_t nsemops)
這個系統調用在System V信號集上執行操作,即分配資源,等待資源或釋放資源。 以下參數需要傳遞 -
- 第一個參數
semid
指示由semget()
創建的信號集識別字。 - 第二個參數
semops
是指向要在信號集上執行的運算元組的指針。 結構如下 -
上述結構中的元素struct sembuf { unsigned short sem_num; /* Semaphore set num */ short sem_op; /* Semaphore operation */ short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */ };
sem_op
指示需要執行的操作 -- 如果
sem_op
是-ve
,則分配或獲取資源。 阻塞調用進程,直到其他進程釋放了足夠的資源,以便此進程可以分配。 - 如果
sem_op
為0
,則調用進程等待或休眠,直到信號量值達到0
。 - 如果
sem_op
是+ve
,則釋放資源。
- 如果
- 第三個參數
nsemops
是該數組中的運算元。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …)
此系統調用執行System V信號量的控制操作。 以下參數需要傳遞 -
- 第一個參數
semid
是信號量的識別字。 這個id
是信號量識別字,它是semget()
系統調用的返回值。 - 第二個參數
semnum
是信號量的數量。 信號量從0
開始編號。 - 第三個參數
cmd
是在信號量上執行所需控制操作的命令。 - 第四個參數是
union semun
,取決於cmd
。 少數情況下,第四個參數是不適用的。
讓我們來看看union semun
-
union semun {
int val; /* val for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT and IPC_SET */
unsigned short *array; /* Buffer for GETALL and SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO and SEM_INFO*/
};
在sys/sem.h
中定義的semid_ds
數據結構如下所示 -
struct semid_ds {
struct ipc_perm sem_perm; /* Permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* Number of semaphores in the set */
};
注 - 請參閱手冊頁以獲取其他數據結構。
union semun arg
的有效值是 -
IPC_STAT
- 將struct semid_ds
的每個成員的當前值的資訊複製到arg.buf
指向的傳遞結構。 該命令需要信號量的讀取許可權。IPC_SET
- 設置結構semid_ds
指向的用戶ID,所有者的組ID,許可權等。IPC_RMID
- 刪除信號集。IPC_INFO
- 返回有關arg.__ buf
指向的semid_ds
結構中的信號限制和參數的資訊。SEM_INFO
- 返回一個包含有關信號量消耗的系統資源資訊的seminfo
結構。
這個調用將根據傳遞的命令返回值(非負值)。 一旦成功,IPC_INFO和SEM_INFO或SEM_STAT返回根據Semaphore的最高使用條目的索引或識別字,或GETPID的semncnt值或GETPID的sempid值或GETVAL 0的semval值, 1在失敗的情況下。 要知道失敗的原因,請檢查errno變數或perror()函數。
在看代碼之前,讓我們瞭解它的實現 -
- 創建兩個進程 - 子進程和父進程。
- 創建共用記憶體主要需要存儲計數器和其他標誌,以指示讀/寫過程結束到共用記憶體中。
- 計數器由父進程和子進程的計數遞增。 計數可以作為命令行參數傳遞,也可以作為默認值(如果不作為命令行參數傳遞,或者值小於10000)。 被調用一定的睡眠時間,以確保父母和孩子同時訪問共用記憶體,即並行訪問。
- 由於父進程和子進程的計數器都是以1為單位遞增,所以最終的數值應該是計數器的兩倍。 因為父,子進程同時執行這些操作,所以計數器不會按需要遞增。 因此,我們需要確保一個進程完成之後的其他過程的完整性。
- 以上所有的實現都在
shm_write_cntr.c
檔中執行 - 檢查計數器值是否在檔
shm_read_cntr.c
中實現 - 為了確保完成,信號量程序在檔
shm_write_cntr_with_sem.c
中實現。 在完成整個過程(從其他程式完成讀取之後)中刪除信號量 - 由於有單獨的檔來讀取共用記憶體中的計數器的值,並且沒有任何影響,讀取程式保持不變(
shm_read_cntr.c
) - 在一個終端上執行寫入程式並從另一個終端上讀取程式總是比較好的。 因為程式只有在寫入和讀取過程完成之後才能完成執行,那麼在完全執行寫入程式之後運行程式就可以了。 寫程式將一直等到讀程式運行,並且只有在完成後才能完成。
沒有信號量的程式 -
/* Filename: shm_write_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SHM_KEY 0x12345
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count);
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
char *bufptr;
int total_count;
int sleep_time;
pid_t pid;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
printf("Total Count is %d\n", total_count);
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
shmp->cntr = 0;
pid = fork();
/* Parent Process - Writing Once */
if (pid > 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
} else if (pid == 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
return 0;
} else {
perror("Fork Failure\n");
return 1;
}
while (shmp->read_complete != 1)
sleep(1);
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
return 0;
}
/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
int cntr;
int numtimes;
int sleep_time;
cntr = shmp->cntr;
shmp->write_complete = 0;
if (pid == 0)
printf("SHM_WRITE: CHILD: Now writing\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Now writing\n");
//printf("SHM_CNTR is %d\n", shmp->cntr);
/* Increment the counter in shared memory by total_count in steps of 1 */
for (numtimes = 0; numtimes < total_count; numtimes++) {
cntr += 1;
shmp->cntr = cntr;
/* Sleeping for a second for every thousand */
sleep_time = cntr % 1000;
if (sleep_time == 0)
sleep(1);
}
shmp->write_complete = 1;
if (pid == 0)
printf("SHM_WRITE: CHILD: Writing Done\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Writing Done\n");
return;
}
執行上面程式代碼,得到以下結果 -
Total Count is 10000
SHM_WRITE: PARENT: Now writing
SHM_WRITE: CHILD: Now writing
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete
現在讓我們來看看共用記憶體讀取程式的實現 -
/* Filename: shm_read_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#define SHM_KEY 0x12345
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
int main(int argc, char *argv[]) {
int shmid, numtimes;
struct shmseg *shmp;
int total_count;
int cntr;
int sleep_time;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Read the shared memory cntr and print it on standard output */
while (shmp->write_complete != 1) {
if (shmp->cntr == -1) {
perror("read");
return 1;
}
sleep(3);
}
printf("Reading Process: Shared Memory: Counter is %d\n", shmp->cntr);
printf("Reading Process: Reading Done, Detaching Shared Memory\n");
shmp->read_complete = 1;
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
printf("Reading Process: Complete\n");
return 0;
}
執行上面程式代碼,得到以下結果 -
Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
如果觀察到上面的輸出,計數器應該是20000,但是,因為在一個進程任務完成之前其他進程也是並行處理的,所以計數器值不是預期的。 每個系統的輸出會有所不同,而且每次執行都會有所不同。 為了確保兩個進程在完成一個任務後執行任務,應該使用同步機制來實現。
現在,讓我們使用信號機制,看看下麵相同的應用程式。
注 - 讀取程式的實現保持不變。
/* Filename: shm_write_cntr_with_sem.c */
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SHM_KEY 0x12345
#define SEM_KEY 0x54321
#define MAX_TRIES 20
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
void shared_memory_cntr_increment(int, struct shmseg*, int);
void remove_semaphore();
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
char *bufptr;
int total_count;
int sleep_time;
pid_t pid;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
printf("Total Count is %d\n", total_count);
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach: ");
return 1;
}
shmp->cntr = 0;
pid = fork();
/* Parent Process - Writing Once */
if (pid > 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
} else if (pid == 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
return 0;
} else {
perror("Fork Failure\n");
return 1;
}
while (shmp->read_complete != 1)
sleep(1);
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
remove_semaphore();
return 0;
}
/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
int cntr;
int numtimes;
int sleep_time;
int semid;
struct sembuf sem_buf;
struct semid_ds buf;
int tries;
int retval;
semid = semget(SEM_KEY, 1, IPC_CREAT | IPC_EXCL | 0666);
//printf("errno is %d and semid is %d\n", errno, semid);
/* Got the semaphore */
if (semid >= 0) {
printf("First Process\n");
sem_buf.sem_op = 1;
sem_buf.sem_flg = 0;
sem_buf.sem_num = 0;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Operation: ");
return;
}
} else if (errno == EEXIST) { // Already other process got it
int ready = 0;
printf("Second Process\n");
semid = semget(SEM_KEY, 1, 0);
if (semid < 0) {
perror("Semaphore GET: ");
return;
}
/* Waiting for the resource */
sem_buf.sem_num = 0;
sem_buf.sem_op = 0;
sem_buf.sem_flg = SEM_UNDO;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked: ");
return;
}
}
sem_buf.sem_num = 0;
sem_buf.sem_op = -1; /* Allocating the resources */
sem_buf.sem_flg = SEM_UNDO;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked: ");
return;
}
cntr = shmp->cntr;
shmp->write_complete = 0;
if (pid == 0)
printf("SHM_WRITE: CHILD: Now writing\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Now writing\n");
//printf("SHM_CNTR is %d\n", shmp->cntr);
/* Increment the counter in shared memory by total_count in steps of 1 */
for (numtimes = 0; numtimes < total_count; numtimes++) {
cntr += 1;
shmp->cntr = cntr;
/* Sleeping for a second for every thousand */
sleep_time = cntr % 1000;
if (sleep_time == 0)
sleep(1);
}
shmp->write_complete = 1;
sem_buf.sem_op = 1; /* Releasing the resource */
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked\n");
return;
}
if (pid == 0)
printf("SHM_WRITE: CHILD: Writing Done\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Writing Done\n");
return;
}
void remove_semaphore() {
int semid;
int retval;
semid = semget(SEM_KEY, 1, 0);
if (semid < 0) {
perror("Remove Semaphore: Semaphore GET: ");
return;
}
retval = semctl(semid, 0, IPC_RMID);
if (retval == -1) {
perror("Remove Semaphore: Semaphore CTL: ");
return;
}
return;
}
執行上面示例代碼,得到以下結果 -
Total Count is 10000
First Process
SHM_WRITE: PARENT: Now writing
Second Process
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Now writing
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete
現在,我們將通過讀取進程來檢查計數器值。執行結果如下 -
Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete