信號量

首先想到的問題是,爲什麼我們需要信號量? 一個簡單的答案,以保護多個進程共享的關鍵/共同區域。

假設多個進程正在使用相同的代碼區域,如果所有人都想並行訪問,那麼結果是重疊的。 例如,多個用戶僅使用一臺打印機(通用/關鍵部分),例如3個用戶,同時給予3個作業,如果所有作業並行啓動,則一個用戶輸出與另一個用戶輸出重疊。 因此,我們需要使用信號量來保護這個信號,即當一個進程正在運行時鎖定關鍵部分,並在完成時解鎖。 這將爲每個用戶/進程重複,以便一個作業不與另一個作業重疊。

基本上信號量分爲兩類 -

  • 二進制信號 - 只有兩個狀態01,即鎖定/解鎖或可用/不可用,互斥實現。
  • 計算信號量 - 允許任意資源計數的信號量稱爲計數信號量。

假設有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_op0,則調用進程等待或休眠,直到信號量值達到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()函數。

在看代碼之前,讓我們瞭解它的實現 -

  • 創建兩個進程 - 子進程和父進程。
  • 創建共享內存主要需要存儲計數器和其他標誌,以指示讀/寫過程結束到共享內存中。
  • 計數器由父進程和子進程的計數遞增。 計數可以作爲命令行參數傳遞,也可以作爲默認值(如果不作爲命令行參數傳遞,或者值小於10000)。 被調用一定的睡眠時間,以確保父母和孩子同時訪問共享內存,即並行訪問。
  • 由於父進程和子進程的計數器都是以1爲單位遞增,所以最終的數值應該是計數器的兩倍。 因爲父,子進程同時執行這些操作,所以計數器不會按需要遞增。 因此,我們需要確保一個進程完成之後的其他過程的完整性。
  • 以上所有的實現都在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