首先想到的問題是,為什麼我們需要號誌? 一個簡單的答案,以保護多個進程共用的關鍵/共同區域。
假設多個進程正在使用相同的程式碼區域,如果所有人都想並行存取,那麼結果是重疊的。 例如,多個使用者僅使用一台列印機(通用/關鍵部分),例如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()函式。
在看程式碼之前,讓我們了解它的實現 -
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