號誌


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

假設多個進程正在使用相同的程式碼區域,如果所有人都想並行存取,那麼結果是重疊的。 例如,多個使用者僅使用一台列印機(通用/關鍵部分),例如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