Inter Process Communication - Semaforlar
Akla gelen ilk soru, neden semaforlara ihtiyacımız var? Birden çok işlem arasında paylaşılan kritik / ortak bölgeyi korumak için basit bir cevap.
Diyelim ki, birden çok işlem aynı kod bölgesini kullanıyor ve hepsi paralel olarak erişmek istiyorsa, sonuç örtüşüyor. Örneğin, birden fazla kullanıcının yalnızca bir yazıcı (ortak / kritik bölüm) kullandığını varsayalım, örneğin 3 kullanıcıya aynı anda 3 iş verildiğinde, tüm işler paralel olarak başlarsa, bir kullanıcı çıktısı diğeriyle çakışır. Yani, semafor kullanarak bunu korumalıyız, yani, bir işlem çalışırken kritik bölümü kilitlemeli ve bittiğinde kilidi açmalıyız. Bu, her kullanıcı / işlem için tekrarlanarak bir işin başka bir işle çakışmaması sağlanır.
Temel olarak semaforlar iki türe ayrılır -
Binary Semaphores - Yalnızca iki durum 0 ve 1, yani kilitli / kilitsiz veya kullanılabilir / kullanılamaz, Mutex uygulaması.
Counting Semaphores - Keyfi kaynak sayımına izin veren semaforlara sayma semaforları denir.
5 yazıcımız olduğunu varsayalım (1 yazıcının yalnızca 1 işi kabul ettiğini varsayalım) ve yazdırılacak 3 işimiz var. Şimdi 3 yazıcıya 3 iş verilecek (her biri 1). Bu devam ederken yine 4 iş geldi. Şimdi, mevcut 2 yazıcıdan 2 iş planlandı ve bize 2 iş daha kaldı ve bu iş yalnızca kaynak / yazıcıdan biri kullanılabilir olduktan sonra tamamlanacak. Kaynak kullanılabilirliğine göre bu tür bir planlama semaforları sayma olarak görülebilir.
Semafor kullanarak senkronizasyon gerçekleştirmek için aşağıdaki adımlar verilmiştir -
Step 1 - Bir semafor oluşturun veya mevcut bir semafora bağlanın (semget ())
Step 2 - Semafor üzerinde işlemler gerçekleştirin, yani kaynakları ayırın veya serbest bırakın veya bekleyin (semop ())
Step 3 - Mesaj kuyruğunda (semctl ()) kontrol işlemlerini gerçekleştirin
Şimdi bunu sahip olduğumuz sistem çağrıları ile kontrol edelim.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
Bu sistem çağrısı, bir System V semafor kümesi oluşturur veya tahsis eder. Aşağıdaki bağımsız değişkenlerin aktarılması gerekiyor -
İlk argüman olan key, mesaj kuyruğunu tanır. Anahtar, keyfi bir değer veya ftok () kitaplık işlevinden türetilebilen bir değer olabilir.
İkinci bağımsız değişken olan nsems semaforların sayısını belirtir. İkili ise 1 ise, 1 semafor kümesine ihtiyaç olduğunu gösterir, aksi halde semafor kümelerinin gerekli sayısına göre.
Üçüncü bağımsız değişken olan semflg, IPC_CREAT (yoksa semafor oluşturma) veya IPC_EXCL (semafor oluşturmak için IPC_CREAT ile kullanılır ve bir semafor zaten mevcutsa çağrı başarısız olur) gibi gerekli semafor bayraklarını / larını belirtir. İzinleri de geçmeniz gerekiyor.
Note - İzinlerle ilgili ayrıntılar için önceki bölümlere bakın.
Bu çağrı, başarılı olduğunda geçerli semafor tanımlayıcı (semafor çağrıları için kullanılır) ve başarısızlık durumunda -1 döndürür. Başarısızlığın nedenini öğrenmek için, errno değişkeni veya perror () işlevi ile kontrol edin.
Bu çağrı ile ilgili çeşitli hatalar şunlardır: EACCESS (izin reddedildi), EEXIST (kuyruk zaten oluşturulamıyor), ENOENT (kuyruk mevcut değil), ENOMEM (kuyruğu oluşturmak için yeterli bellek yok), ENOSPC (maksimum set sınırı aşıldı) vb.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *semops, size_t nsemops)
Bu sistem çağrısı, System V semafor kümelerindeki işlemleri gerçekleştirir, yani kaynakları tahsis eder, kaynakları bekler veya kaynakları serbest bırakır. Aşağıdaki argümanların aktarılması gerekiyor -
İlk argüman olan semid, semget () tarafından oluşturulan semafor kümesi tanımlayıcısını gösterir.
İkinci argüman olan semops, semafor kümesinde gerçekleştirilecek bir dizi işlemin göstericisidir. Yapı aşağıdaki gibidir -
struct sembuf {
unsigned short sem_num; /* Semaphore set num */
short sem_op; /* Semaphore operation */
short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};
Yukarıdaki yapıdaki sem_op öğesi, yapılması gereken işlemi gösterir -
Sem_op –ve ise, kaynakları ayırın veya edinin. Diğer işlemler tarafından yeterli kaynak serbest bırakılıncaya kadar çağırma işlemini engeller, böylece bu işlem tahsis edebilir.
Sem_op sıfır ise, çağıran işlem semafor değeri 0'a ulaşana kadar bekler veya uyur.
Sem_op + ve ise, kaynakları serbest bırakın.
Örneğin -
struct sembuf sem_lock = {0, -1, SEM_UNDO};
struct sembuf sem_unlock = {0, 1, SEM_UNDO};
Üçüncü argüman nsemops, o dizideki işlemlerin sayısıdır.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …)
Bu sistem çağrısı, bir System V semaforu için kontrol işlemi gerçekleştirir. Aşağıdaki bağımsız değişkenlerin aktarılması gerekiyor -
İlk argüman olan semid, semaforun tanımlayıcısıdır. Bu id, semget () sistem çağrısının dönüş değeri olan semafor tanımlayıcısıdır.
İkinci argüman semnum, semafor sayısıdır. Semaforlar 0'dan numaralandırılır.
Üçüncü argüman, cmd, semafor üzerinde gerekli kontrol işlemini gerçekleştirme komutudur.
Union semun türündeki dördüncü argüman cmd'ye bağlıdır. Birkaç durum için dördüncü argüman geçerli değildir.
Union semun'u kontrol edelim -
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 dosyasında tanımlanan semid_ds veri yapısı aşağıdaki gibidir -
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 */
};
Note - Diğer veri yapıları için lütfen kılavuz sayfalarına bakın.
union semun arg; Cmd için geçerli değerler -
IPC_STAT- struct semid_ds öğesinin her bir üyesinin geçerli değerlerinin bilgilerini arg.buf ile gösterilen iletilen yapıya kopyalar. Bu komut, semafora okuma izni gerektirir.
IPC_SET - semid_ds yapısıyla gösterilen kullanıcı kimliğini, sahibin grup kimliğini, izinleri vb. Ayarlar.
IPC_RMID - Semafor kümesini kaldırır.
IPC_INFO - arg .__ buf ile gösterilen semid_ds yapısındaki semafor limitleri ve parametreleri hakkında bilgi verir.
SEM_INFO - Semafor tarafından tüketilen sistem kaynakları hakkında bilgi içeren bir seminfo yapısı döndürür.
Bu çağrı, geçirilen komuta bağlı olarak değer (negatif olmayan değer) döndürür. Başarı durumunda, IPC_INFO ve SEM_INFO veya SEM_STAT, Semafor'a göre kullanılan en yüksek girişin dizinini veya tanımlayıcısını veya GETNCNT için semncnt değerini veya GETPID için sempid değerini veya başarı ile ilgili diğer işlemler için GETVAL 0 için semval değerini döndürür ve - Arıza durumunda 1. Başarısızlığın nedenini öğrenmek için, errno değişkeni veya perror () işlevi ile kontrol edin.
Koda bakmadan önce, uygulamasını anlayalım -
Çocuk ve ebeveyn diyelim ki iki süreç oluşturun.
Temel olarak sayacı ve okuma / yazma işleminin sonunu paylaşılan belleğe belirtmek için diğer bayrakları saklamak için gereken paylaşılan belleği oluşturun.
Sayaç, hem üst hem de alt süreçler tarafından sayılarak artırılır. Sayı, bir komut satırı bağımsız değişkeni olarak iletilir veya varsayılan olarak alınır (komut satırı bağımsız değişkeni olarak iletilmezse veya değer 10000'den azsa). Hem ebeveynin hem de çocuğun aynı anda yani paralel olarak paylaşılan belleğe erişmesini sağlamak için belirli bir uyku süresi ile çağrılır.
Sayaç, hem ebeveyn hem de alt öğe tarafından 1'lik adımlarla artırıldığından, son değer, sayacın iki katı olmalıdır. İşlemleri aynı anda gerçekleştiren hem üst hem de alt süreçler olduğundan, sayaç gerektiği kadar artırılmaz. Bu nedenle, bir sürecin tamamlanmasını ve ardından başka bir sürecin tamamlanmasını sağlamalıyız.
Yukarıdaki uygulamaların tümü shm_write_cntr.c dosyasında gerçekleştirilir.
Sayaç değerinin shm_read_cntr.c dosyasında uygulanıp uygulanmadığını kontrol edin
Tamamlanmasını sağlamak için, semafor programı shm_write_cntr_with_sem.c dosyasında uygulanmaktadır. Tüm işlem tamamlandıktan sonra semaforu kaldırın (diğer programdan okuma yapıldıktan sonra)
Paylaşılan bellekte sayaç değerini okumak için ayrı dosyalarımız olduğundan ve yazma işleminin herhangi bir etkisi olmadığından, okuma programı aynı kalır (shm_read_cntr.c)
Yazma programını bir terminalde çalıştırmak ve programı başka bir terminalden okumak her zaman daha iyidir. Program, yalnızca yazma ve okuma işlemi tamamlandıktan sonra yürütmeyi tamamladığından, yazma programını tamamen çalıştırdıktan sonra programın çalıştırılması uygundur. Yazma programı, okuma programı çalıştırılana kadar bekler ve ancak tamamlandıktan sonra biter.
Semaforsuz programlar.
/* 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;
}
Derleme ve Yürütme Adımları
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
Şimdi paylaşılan hafıza okuma programına bakalım.
/* 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;
}
Derleme ve Yürütme Adımları
Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
Yukarıdaki çıktıyı gözlemlerseniz, sayaç 20000 olmalıdır, ancak bir işlem görevinin tamamlanmasından önce diğer süreç de paralel olarak işlediğinden, sayaç değeri beklendiği gibi değildir. Çıktı sistemden sisteme değişir ve ayrıca her yürütmede de değişir. İki işlemin bir görev tamamlandıktan sonra görevi gerçekleştirmesini sağlamak için, senkronizasyon mekanizmaları kullanılarak uygulanmalıdır.
Şimdi semafor kullanarak aynı uygulamayı kontrol edelim.
Note - Okuma programı aynı kalır.
/* 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;
}
Derleme ve Yürütme Adımları
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
Şimdi okuma işlemi ile sayaç değerini kontrol edeceğiz.
Yürütme Adımları
Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete