Komunikasi Proses Antar - Semaphores

Pertanyaan pertama yang muncul di benak kita adalah, mengapa kita membutuhkan semaphore? Jawaban sederhana, untuk melindungi wilayah kritis / umum yang digunakan bersama di antara berbagai proses.

Mari kita asumsikan, beberapa proses menggunakan wilayah kode yang sama dan jika semua ingin mengakses secara paralel maka hasilnya akan tumpang tindih. Katakanlah, misalnya, beberapa pengguna hanya menggunakan satu printer (bagian umum / kritis), katakanlah 3 pengguna, diberi 3 pekerjaan pada saat yang sama, jika semua pekerjaan dimulai secara paralel, maka satu keluaran pengguna tumpang tindih dengan yang lain. Jadi, kita perlu melindungi itu menggunakan semaphores yaitu, mengunci bagian kritis ketika satu proses sedang berjalan dan membuka kunci ketika itu selesai. Ini akan diulangi untuk setiap pengguna / proses sehingga satu pekerjaan tidak tumpang tindih dengan pekerjaan lain.

Pada dasarnya semaphore diklasifikasikan menjadi dua jenis -

Binary Semaphores - Hanya dua status 0 & 1, yaitu, terkunci / tidak terkunci atau tersedia / tidak tersedia, implementasi Mutex.

Counting Semaphores - Semaphore yang memungkinkan penghitungan sumber daya sewenang-wenang disebut menghitung semaphore.

Asumsikan bahwa kita memiliki 5 printer (untuk dipahami asumsikan bahwa 1 printer hanya menerima 1 pekerjaan) dan kita mendapat 3 pekerjaan untuk dicetak. Sekarang 3 pekerjaan akan diberikan untuk 3 printer (masing-masing 1). Sekali lagi 4 pekerjaan datang saat ini sedang berlangsung. Sekarang, dari 2 printer yang tersedia, 2 pekerjaan telah dijadwalkan dan kami memiliki 2 pekerjaan lagi, yang akan diselesaikan hanya setelah salah satu sumber daya / printer tersedia. Jenis penjadwalan sesuai ketersediaan sumber daya dapat dilihat sebagai penghitungan semaphore.

Untuk melakukan sinkronisasi menggunakan semaphores, berikut langkah-langkahnya -

Step 1 - Buat semaphore atau hubungkan ke semaphore yang sudah ada (semget ())

Step 2 - Lakukan operasi pada semaphore yaitu mengalokasikan atau melepaskan atau menunggu sumber daya (semop ())

Step 3 - Lakukan operasi kontrol pada antrian pesan (semctl ())

Sekarang, mari kita periksa ini dengan panggilan sistem yang kita miliki.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg)

Panggilan sistem ini membuat atau mengalokasikan set semafor Sistem V. Argumen berikut harus diteruskan -

  • Argumen pertama, key, mengenali antrian pesan. Kuncinya bisa berupa nilai arbitrer atau yang bisa diturunkan dari fungsi library ftok ().

  • Argumen kedua, nsems, menentukan jumlah semaphore. Jika biner maka itu adalah 1, berarti membutuhkan 1 set semaphore, jika tidak sesuai jumlah jumlah set semaphore yang diperlukan.

  • Argumen ketiga, semflg, menentukan flag semaphore yang diperlukan seperti IPC_CREAT (membuat semaphore jika tidak ada) atau IPC_EXCL (digunakan dengan IPC_CREAT untuk membuat semaphore dan panggilan gagal, jika semaphore sudah ada). Harus melewati izin juga.

Note - Lihat bagian sebelumnya untuk detail tentang izin.

Panggilan ini akan mengembalikan pengenal semaphore yang valid (digunakan untuk panggilan semaphore lebih lanjut) saat berhasil dan -1 jika terjadi kegagalan. Untuk mengetahui penyebab kegagalan, periksa dengan variabel errno atau fungsi perror ().

Berbagai kesalahan sehubungan dengan panggilan ini adalah EACCESS (izin ditolak), EEXIST (antrian sudah ada tidak dapat dibuat), ENOENT (antrian tidak ada), ENOMEM (tidak cukup memori untuk membuat antrian), ENOSPC (batas set maksimum terlampaui), dll.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *semops, size_t nsemops)

Panggilan sistem ini melakukan operasi pada set semafor Sistem V yaitu, mengalokasikan sumber daya, menunggu sumber daya atau membebaskan sumber daya. Argumen berikut harus diteruskan -

  • Argumen pertama, semid, menunjukkan pengenal set semaphore yang dibuat oleh semget ().

  • Argumen kedua, semops, adalah penunjuk ke larik operasi yang akan dilakukan pada set semafor. Strukturnya adalah sebagai berikut -

struct sembuf {
   unsigned short sem_num; /* Semaphore set num */
   short sem_op; /* Semaphore operation */
   short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};

Elemen, sem_op, dalam struktur di atas, menunjukkan operasi yang perlu dilakukan -

  • Jika sem_op adalah –ve, alokasikan atau dapatkan sumber daya. Memblokir proses panggilan hingga sumber daya yang cukup telah dibebaskan oleh proses lain, sehingga proses ini dapat dialokasikan.

  • Jika sem_op nol, proses pemanggilan menunggu atau tidur sampai nilai semaphore mencapai 0.

  • Jika sem_op + ve, lepaskan resource.

Misalnya -

struct sembuf sem_lock = {0, -1, SEM_UNDO};

struct sembuf sem_unlock = {0, 1, SEM_UNDO};

  • Argumen ketiga, nsemops, adalah jumlah operasi dalam larik itu.

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, …)

Panggilan sistem ini melakukan operasi kontrol untuk semafor Sistem V. Argumen berikut harus diteruskan -

  • Argumen pertama, semid, adalah pengidentifikasi semafor. Id ini adalah pengenal semaphore, yang merupakan nilai kembalian dari panggilan sistem semget ().

  • Argumen kedua, semnum, adalah jumlah semafor. Semaphore diberi nomor dari 0.

  • Argumen ketiga, cmd, adalah perintah untuk melakukan operasi kontrol yang diperlukan pada semafor.

  • Argumen keempat, tipe, union semun, bergantung pada cmd. Untuk beberapa kasus, argumen keempat tidak dapat diterapkan.

Mari kita periksa serikat 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*/
};

Struktur data semid_ds yang didefinisikan dalam sys / sem.h adalah sebagai berikut -

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 - Silakan merujuk halaman manual untuk struktur data lainnya.

serikat semun arg; Nilai yang valid untuk cmd adalah -

  • IPC_STAT- Menyalin informasi dari nilai saat ini dari setiap anggota struct semid_ds ke struktur yang dilewati yang ditunjukkan oleh arg.buf. Perintah ini membutuhkan izin baca ke semafor.

  • IPC_SET - Menetapkan ID pengguna, ID grup pemilik, izin, dll. Yang ditunjukkan oleh struktur semid_ds.

  • IPC_RMID - Menghapus set semaphores.

  • IPC_INFO - Mengembalikan informasi tentang batas dan parameter semaphore dalam struktur semid_ds yang ditunjukkan oleh arg .__ buf.

  • SEM_INFO - Mengembalikan struktur seminfo yang berisi informasi tentang sumber daya sistem yang dikonsumsi oleh semafor.

Panggilan ini akan mengembalikan nilai (nilai non-negatif) tergantung pada perintah yang diteruskan. Setelah berhasil, IPC_INFO dan SEM_INFO atau SEM_STAT mengembalikan indeks atau pengenal entri tertinggi yang digunakan sesuai Semaphore atau nilai semncnt untuk GETNCNT atau nilai sempid untuk GETPID atau nilai semval untuk GETVAL 0 untuk operasi lain pada saat berhasil dan - 1 jika terjadi kegagalan. Untuk mengetahui penyebab kegagalan, periksa dengan variabel errno atau fungsi perror ().

Sebelum melihat kodenya, mari kita pahami implementasinya -

  • Buat dua proses katakanlah, anak dan orang tua.

  • Buat memori bersama terutama yang diperlukan untuk menyimpan penghitung dan tanda lainnya untuk menunjukkan akhir proses baca / tulis ke dalam memori bersama.

  • Penghitung bertambah menurut hitungan oleh proses induk dan anak. Hitungan diteruskan sebagai argumen baris perintah atau diambil sebagai default (jika tidak diteruskan sebagai argumen baris perintah atau nilainya kurang dari 10000). Dipanggil dengan waktu tidur tertentu untuk memastikan kedua orang tua dan anak mengakses memori bersama pada saat yang sama, yaitu secara paralel.

  • Karena, penghitung bertambah dalam langkah 1 oleh orang tua dan anak, nilai akhir harus menggandakan penghitung. Karena, kedua proses induk dan anak melakukan operasi pada waktu yang sama, penghitung tidak bertambah seperti yang diperlukan. Oleh karena itu, kita perlu memastikan kelengkapan satu proses penyelesaian diikuti dengan proses lainnya.

  • Semua implementasi di atas dilakukan di file shm_write_cntr.c

  • Periksa apakah nilai penghitung diimplementasikan dalam file shm_read_cntr.c

  • Untuk memastikan penyelesaian, program semaphore diimplementasikan dalam file shm_write_cntr_with_sem.c. Hapus semaphore setelah menyelesaikan seluruh proses (setelah membaca dilakukan dari program lain)

  • Karena, kami memiliki file terpisah untuk membaca nilai penghitung di memori bersama dan tidak memiliki efek apa pun dari penulisan, program membaca tetap sama (shm_read_cntr.c)

  • Itu selalu lebih baik untuk menjalankan program penulisan di satu terminal dan membaca program dari terminal lain. Karena, program menyelesaikan eksekusi hanya setelah proses menulis dan membaca selesai, maka tidak masalah untuk menjalankan program setelah menjalankan program tulis sepenuhnya. Program tulis akan menunggu hingga program baca dijalankan dan hanya selesai setelah selesai.

Program tanpa semaphore.

/* 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;
}

Langkah Kompilasi dan Eksekusi

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

Sekarang, mari kita periksa program membaca memori bersama.

/* 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;
}

Langkah Kompilasi dan Eksekusi

Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete

Jika Anda mengamati keluaran di atas, penghitung harus 20000, namun, karena sebelum menyelesaikan satu tugas proses, proses lain juga diproses secara paralel, nilai penghitung tidak seperti yang diharapkan. Output akan bervariasi dari sistem ke sistem dan juga akan bervariasi dengan setiap eksekusi. Untuk memastikan kedua proses menjalankan tugas setelah menyelesaikan satu tugas, itu harus dilaksanakan menggunakan mekanisme sinkronisasi.

Sekarang, mari kita periksa aplikasi yang sama menggunakan semaphores.

Note - Program membaca tetap sama.

/* 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;
}

Langkah Kompilasi dan Eksekusi

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

Sekarang, kita akan memeriksa nilai penghitung dengan proses membaca.

Langkah Eksekusi

Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete