首先想到的問題是,為什么我們需要信號量? 一個簡單的答案,以保護多個進程共享的關鍵/共同區(qū)域。
假設多個進程正在使用相同的代碼區(qū)域,如果所有人都想并行訪問,那么結果是重疊的。 例如,多個用戶僅使用一臺打印機(通用/關鍵部分),例如3
個用戶,同時給予3
個作業(yè),如果所有作業(yè)并行啟動,則一個用戶輸出與另一個用戶輸出重疊。 因此,我們需要使用信號量來保護這個信號,即當一個進程正在運行時鎖定關鍵部分,并在完成時解鎖。 這將為每個用戶/進程重復,以便一個作業(yè)不與另一個作業(yè)重疊。
基本上信號量分為兩類 -
0
和1
,即鎖定/解鎖或可用/不可用,互斥實現(xiàn)。假設有5臺打印機(要了解1
臺打印機只接受1
一項工作),我們有3
個打印作業(yè)。 現(xiàn)在有三個打印機(每個打印機1
個)提供3
個工作。 這項工作還在進行中,共有4
項工作。 現(xiàn)在,在可用的兩臺打印機中,已經安排了兩個作業(yè),剩下兩個作業(yè),只有在其中一個資源/打印機可用時才能完成。 根據資源可用性的這種調度可以被看作計數(shù)信號量。
要使用信號量執(zhí)行同步,請執(zhí)行以下步驟 -
第1步 - 創(chuàng)建一個信號量或連接到一個已經存在的信號量(semget()
)
第2步 - 對信號量執(zhí)行操作,即分配或釋放或等待資源(semop()
)
第3步 - 在消息隊列(semctl()
)上執(zhí)行控制操作
現(xiàn)在,讓我們查看一下系統(tǒng)調用。
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
這個系統(tǒng)調用創(chuàng)建或分配一個System V信號集。 需要傳遞以下參數(shù) -
key
用于識別消息隊列。key
可以是任意值,也可以是來自庫函數(shù)ftok()
的值。nsems
指定了信號的數(shù)量。 如果二進制那么它是1
,意味著需要1
個信號集,否則按照所需的信號量集計數(shù)。semflg
指定所需的信號量標志,如IPC_CREAT(如果不存在則創(chuàng)建信號量)或IPC_EXCL(與IPC_CREAT一起用于創(chuàng)建信號量,如果信號量已經存在,則調用失敗)。 還需要傳遞權限。注 - 有關權限的詳細信息,請參閱前面幾節(jié)。
這個調用會在成功時返回有效的信號量標識符(用于進一步調用信號量),在失敗的情況下返回-1
。 要知道失敗的原因,請檢查errno變量或perror()函數(shù)。
關于這個調用的各種錯誤是EACCESS(權限被拒絕),EEXIST(隊列已經存在不能創(chuàng)建),ENOENT(隊列不存在),ENOMEM(沒有足夠的內存來創(chuàng)建隊列),ENOSPC(最大限制 超過)等
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *semops, size_t nsemops)
這個系統(tǒng)調用在System V信號集上執(zhí)行操作,即分配資源,等待資源或釋放資源。 以下參數(shù)需要傳遞 -
semid
指示由semget()
創(chuàng)建的信號集標識符。semops
是指向要在信號集上執(zhí)行的操作數(shù)組的指針。 結構如下 -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
指示需要執(zhí)行的操作 -sem_op
是-ve
,則分配或獲取資源。 阻塞調用進程,直到其他進程釋放了足夠的資源,以便此進程可以分配。sem_op
為0
,則調用進程等待或休眠,直到信號量值達到0
。sem_op
是+ve
,則釋放資源。nsemops
是該數(shù)組中的操作數(shù)。#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …)
此系統(tǒng)調用執(zhí)行System V信號量的控制操作。 以下參數(shù)需要傳遞 -
semid
是信號量的標識符。 這個id
是信號量標識符,它是semget()
系統(tǒng)調用的返回值。semnum
是信號量的數(shù)量。 信號量從0
開始編號。cmd
是在信號量上執(zhí)行所需控制操作的命令。union semun
,取決于cmd
。 少數(shù)情況下,第四個參數(shù)是不適用的。讓我們來看看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
數(shù)據結構如下所示 -
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 */
};
注 - 請參閱手冊頁以獲取其他數(shù)據結構。
union semun arg
的有效值是 -
IPC_STAT
- 將struct semid_ds
的每個成員的當前值的信息復制到arg.buf
指向的傳遞結構。 該命令需要信號量的讀取權限。IPC_SET
- 設置結構semid_ds
指向的用戶ID,所有者的組ID,權限等。IPC_RMID
- 刪除信號集。IPC_INFO
- 返回有關arg.__ buf
指向的semid_ds
結構中的信號限制和參數(shù)的信息。SEM_INFO
- 返回一個包含有關信號量消耗的系統(tǒng)資源信息的seminfo
結構。這個調用將根據傳遞的命令返回值(非負值)。 一旦成功,IPC_INFO和SEM_INFO或SEM_STAT返回根據Semaphore的最高使用條目的索引或標識符,或GETPID的semncnt值或GETPID的sempid值或GETVAL 0的semval值, 1在失敗的情況下。 要知道失敗的原因,請檢查errno變量或perror()函數(shù)。
在看代碼之前,讓我們了解它的實現(xiàn) -
shm_write_cntr.c
文件中執(zhí)行shm_read_cntr.c
中實現(xiàn)shm_write_cntr_with_sem.c
中實現(xiàn)。 在完成整個過程(從其他程序完成讀取之后)中刪除信號量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;
}
執(zhí)行上面程序代碼,得到以下結果 -
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
現(xiàn)在讓我們來看看共享內存讀取程序的實現(xiàn) -
/* 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;
}
執(zhí)行上面程序代碼,得到以下結果 -
Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
如果觀察到上面的輸出,計數(shù)器應該是20000,但是,因為在一個進程任務完成之前其他進程也是并行處理的,所以計數(shù)器值不是預期的。 每個系統(tǒng)的輸出會有所不同,而且每次執(zhí)行都會有所不同。 為了確保兩個進程在完成一個任務后執(zhí)行任務,應該使用同步機制來實現(xiàn)。
現(xiàn)在,讓我們使用信號機制,看看下面相同的應用程序。
注 - 讀取程序的實現(xiàn)保持不變。
/* 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;
}
執(zhí)行上面示例代碼,得到以下結果 -
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
現(xiàn)在,我們將通過讀取進程來檢查計數(shù)器值。執(zhí)行結果如下 -
Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete