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