Inter Process Communication - Semaphores
Câu hỏi đầu tiên xuất hiện trong đầu là, tại sao chúng ta cần semaphores? Một câu trả lời đơn giản, để bảo vệ khu vực quan trọng / chung được chia sẻ giữa nhiều quy trình.
Chúng ta hãy giả sử rằng, nhiều quy trình đang sử dụng cùng một vùng mã và nếu tất cả đều muốn truy cập song song thì kết quả sẽ bị chồng chéo. Ví dụ: giả sử nhiều người dùng chỉ sử dụng một máy in (phần phổ biến / quan trọng), giả sử 3 người dùng, được giao 3 công việc cùng một lúc, nếu tất cả các công việc bắt đầu song song, thì đầu ra của một người dùng sẽ bị chồng lên nhau. Vì vậy, chúng ta cần bảo vệ điều đó bằng cách sử dụng semaphores, tức là khóa phần quan trọng khi một tiến trình đang chạy và mở khóa khi nó hoàn tất. Điều này sẽ được lặp lại cho mỗi người dùng / quy trình để một công việc không bị chồng chéo với công việc khác.
Về cơ bản semaphores được phân thành hai loại -
Binary Semaphores - Chỉ có hai trạng thái 0 & 1, tức là đã khóa / mở khóa hoặc có sẵn / không khả dụng, triển khai Mutex.
Counting Semaphores - Semaphores cho phép đếm tài nguyên tùy ý được gọi là semaphores đếm.
Giả sử rằng chúng ta có 5 máy in (có thể hiểu rằng 1 máy in chỉ chấp nhận 1 lệnh in) và chúng ta có 3 lệnh in. Bây giờ 3 công việc sẽ được đưa ra cho 3 máy in (mỗi máy 1). Một lần nữa 4 công việc đến trong khi điều này đang được tiến hành. Bây giờ, trong số 2 máy in có sẵn, 2 công việc đã được lên lịch và chúng tôi còn lại 2 công việc nữa, sẽ chỉ hoàn thành sau khi một trong các tài nguyên / máy in có sẵn. Loại lập lịch này tùy theo khả năng cung cấp tài nguyên có thể được xem như đếm các bán nguyệt san.
Để thực hiện đồng bộ hóa bằng semaphores, sau đây là các bước:
Step 1 - Tạo một semaphore hoặc kết nối với một semaphore đã tồn tại (semget ())
Step 2 - Thực hiện các hoạt động trên semaphore, tức là cấp phát hoặc giải phóng hoặc đợi tài nguyên (semop ())
Step 3 - Thực hiện các thao tác điều khiển trên hàng đợi tin nhắn (semctl ())
Bây giờ, chúng ta hãy kiểm tra điều này với các lệnh gọi hệ thống mà chúng ta có.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
Lệnh gọi hệ thống này tạo hoặc cấp phát tập hợp semaphore System V. Các đối số sau cần được truyền:
Đối số đầu tiên, khóa, nhận dạng hàng đợi tin nhắn. Khóa có thể là một giá trị tùy ý hoặc một giá trị có thể được dẫn xuất từ hàm thư viện ftok ().
Đối số thứ hai, nsems, chỉ định số lượng semaphores. Nếu nhị phân thì nó là 1, có nghĩa là cần 1 bộ semaphore, nếu không thì theo số lượng yêu cầu của bộ semaphore.
Đối số thứ ba, semflg, chỉ định cờ / s semaphore bắt buộc như IPC_CREAT (tạo semaphore nếu nó không tồn tại) hoặc IPC_EXCL (được sử dụng với IPC_CREAT để tạo semaphore và cuộc gọi không thành công, nếu một semaphore đã tồn tại). Cần phải vượt qua các quyền.
Note - Tham khảo các phần trước để biết chi tiết về các quyền.
Lệnh gọi này sẽ trả về định danh semaphore hợp lệ (được sử dụng cho các lệnh gọi tiếp theo của semaphores) khi thành công và -1 trong trường hợp thất bại. Để biết nguyên nhân của lỗi, hãy kiểm tra với biến errno hoặc hàm perror ().
Các lỗi khác nhau liên quan đến lệnh gọi này là EACCESS (quyền bị từ chối), EEXIST (hàng đợi đã tồn tại không thể tạo), ENOENT (hàng đợi không tồn tại), ENOMEM (không đủ bộ nhớ để tạo hàng đợi), ENOSPC (bộ giới hạn tối đa vượt quá), v.v.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *semops, size_t nsemops)
Lệnh gọi hệ thống này thực hiện các hoạt động trên bộ semaphore System V, phân bổ tài nguyên, chờ đợi tài nguyên hoặc giải phóng tài nguyên. Các đối số sau cần được truyền:
Đối số đầu tiên, semid, chỉ ra mã định danh tập hợp semaphore được tạo bởi semget ().
Đối số thứ hai, semops, là con trỏ đến một mảng các hoạt động sẽ được thực hiện trên tập hợp semaphore. Cấu trúc như sau:
struct sembuf {
unsigned short sem_num; /* Semaphore set num */
short sem_op; /* Semaphore operation */
short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};
Phần tử, sem_op, trong cấu trúc trên, cho biết thao tác cần được thực hiện -
Nếu sem_op là –ve, phân bổ hoặc lấy tài nguyên. Chặn quá trình gọi cho đến khi các quá trình khác giải phóng đủ tài nguyên, để quá trình này có thể cấp phát.
Nếu sem_op bằng 0, quá trình gọi sẽ đợi hoặc ngủ cho đến khi giá trị semaphore bằng 0.
Nếu sem_op là + ve, hãy giải phóng tài nguyên.
Ví dụ -
struct sembuf sem_lock = {0, -1, SEM_UNDO};
struct sembuf sem_unlock = {0, 1, SEM_UNDO};
Đối số thứ ba, nsemops, là số lượng các phép toán trong mảng đó.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …)
Lệnh gọi hệ thống này thực hiện hoạt động điều khiển cho một semaphore Hệ thống V. Các đối số sau cần được truyền:
Đối số đầu tiên, semid, là định danh của semaphore. Id này là định danh semaphore, là giá trị trả về của lệnh gọi hệ thống semget ().
Đối số thứ hai, semnum, là số lượng semaphore. Các semaphores được đánh số từ 0.
Đối số thứ ba, cmd, là lệnh để thực hiện thao tác điều khiển cần thiết trên semaphore.
Đối số thứ tư, kiểu, semun union, phụ thuộc vào cmd. Đối với một số trường hợp, đối số thứ tư không được áp dụng.
Hãy để chúng tôi kiểm tra hiệp hội -
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*/
};
Cấu trúc dữ liệu semid_ds được định nghĩa trong sys / sem.h như sau:
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 - Vui lòng tham khảo các trang hướng dẫn sử dụng cho các cấu trúc dữ liệu khác.
công đoàn semun arg; Giá trị hợp lệ cho cmd là -
IPC_STAT- Sao chép thông tin về các giá trị hiện tại của từng thành viên trong struct semid_ds vào cấu trúc được trỏ bởi arg.buf. Lệnh này yêu cầu quyền đọc đối với semaphore.
IPC_SET - Đặt ID người dùng, ID nhóm của chủ sở hữu, quyền, v.v. được trỏ đến bởi cấu trúc semid_ds.
IPC_RMID - Loại bỏ tập hợp semaphores.
IPC_INFO - Trả về thông tin về các giới hạn semaphore và các tham số trong cấu trúc semid_ds được trỏ bởi arg .__ buf.
SEM_INFO - Trả về một cấu trúc seminfo chứa thông tin về tài nguyên hệ thống được semaphore tiêu thụ.
Lệnh gọi này sẽ trả về giá trị (giá trị không âm) tùy thuộc vào lệnh được truyền. Khi thành công, IPC_INFO và SEM_INFO hoặc SEM_STAT trả về chỉ mục hoặc mã định danh của mục nhập được sử dụng cao nhất theo Semaphore hoặc giá trị của semncnt cho GETNCNT hoặc giá trị của sempid cho GETPID hoặc giá trị của semval cho GETVAL 0 đối với các hoạt động khác khi thành công và - 1 trong trường hợp thất bại. Để biết nguyên nhân của lỗi, hãy kiểm tra với biến errno hoặc hàm perror ().
Trước khi xem mã, hãy để chúng tôi hiểu cách triển khai của nó -
Tạo hai quy trình nói, con và mẹ.
Tạo bộ nhớ chia sẻ chủ yếu cần thiết để lưu bộ đếm và các cờ khác để chỉ báo kết thúc quá trình đọc / ghi vào bộ nhớ dùng chung.
Bộ đếm được tăng lên theo số đếm bởi cả quá trình mẹ và con. Số đếm được chuyển dưới dạng đối số dòng lệnh hoặc được lấy làm mặc định (nếu không được chuyển dưới dạng đối số dòng lệnh hoặc giá trị nhỏ hơn 10000). Được gọi với thời gian ngủ nhất định để đảm bảo cả cha và con đều truy cập vào bộ nhớ dùng chung cùng một lúc, tức là song song.
Vì bộ đếm được tăng lên theo từng bước 1 bởi cả cha và con, giá trị cuối cùng phải gấp đôi bộ đếm. Vì cả tiến trình cha và con thực hiện các hoạt động cùng một lúc, bộ đếm không được tăng lên theo yêu cầu. Do đó, chúng ta cần đảm bảo tính hoàn chỉnh của một quá trình hoàn thành sau đó là quá trình khác.
Tất cả các triển khai trên được thực hiện trong tệp shm_write_cntr.c
Kiểm tra xem giá trị bộ đếm có được triển khai trong tệp shm_read_cntr.c không
Để đảm bảo hoàn thành, chương trình semaphore được thực hiện trong tệp shm_write_cntr_with_sem.c. Loại bỏ semaphore sau khi hoàn thành toàn bộ quá trình (sau khi đọc xong từ chương trình khác)
Vì chúng tôi có các tệp riêng biệt để đọc giá trị của bộ đếm trong bộ nhớ dùng chung và không có bất kỳ ảnh hưởng nào từ việc ghi, chương trình đọc vẫn giữ nguyên (shm_read_cntr.c)
Luôn luôn tốt hơn nếu thực hiện chương trình viết trong một thiết bị đầu cuối và đọc chương trình từ một thiết bị đầu cuối khác. Vì chương trình chỉ hoàn thành việc thực thi sau khi quá trình ghi và đọc hoàn tất, nên có thể chạy chương trình sau khi thực hiện hoàn toàn chương trình ghi. Chương trình ghi sẽ đợi cho đến khi chương trình đọc được chạy và chỉ kết thúc sau khi hoàn tất.
Chương trình không có semaphores.
/* 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;
}
Các bước biên dịch và thực hiện
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
Bây giờ, chúng ta hãy kiểm tra chương trình đọc bộ nhớ được chia sẻ.
/* 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;
}
Các bước biên dịch và thực hiện
Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
Nếu bạn quan sát kết quả đầu ra ở trên, bộ đếm phải là 20000, tuy nhiên, vì trước khi hoàn thành một tác vụ quá trình, quá trình khác cũng đang xử lý song song, giá trị bộ đếm không như mong đợi. Đầu ra sẽ khác nhau giữa các hệ thống và nó cũng sẽ thay đổi theo mỗi lần thực thi. Để đảm bảo hai quá trình thực hiện nhiệm vụ sau khi hoàn thành một nhiệm vụ, nó nên được thực hiện bằng cách sử dụng cơ chế đồng bộ hóa.
Bây giờ, chúng ta hãy kiểm tra cùng một ứng dụng bằng cách sử dụng semaphores.
Note - Chương trình đọc được giữ nguyên.
/* 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;
}
Các bước biên dịch và thực hiện
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
Bây giờ, chúng ta sẽ kiểm tra giá trị bộ đếm bằng quá trình đọc.
Các bước thực hiện
Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete