📅  最后修改于: 2021-01-18 06:43:24             🧑  作者: Mango
我想到的第一个问题是,为什么我们需要信号灯?一个简单的答案,就是保护多个流程之间共享的关键/公共区域。
让我们假设,多个进程正在使用相同的代码区域,并且如果所有进程都希望并行访问,那么结果是重叠的。例如,假设有多个用户仅使用一台打印机(公共/关键部分),例如3个用户,同时给定3个作业,如果所有作业并行开始,则一个用户输出与另一个用户输出重叠。因此,我们需要使用信号量来保护它,即在一个进程运行时锁定关键部分,并在完成时解锁。将对每个用户/进程重复此操作,以使一个作业不会与另一作业重叠。
基本上,信号量分为两种类型-
二进制信号量-只有两个状态0和1,即锁定/解锁或可用/不可用,互斥量实现。
计数信号量-允许任意资源计数的信号量称为计数信号量。
假设我们有5台打印机(要理解为假设1台打印机仅接受1份作业),并且要打印3份作业。现在将为3台打印机(每台1台)分配3个作业。在进行此操作时,又有4个工作出现。现在,在2台可用打印机中,已计划2份作业,而我们剩下2份作业,只有在其中一台资源/打印机可用后才能完成。这种根据资源可用性进行的调度可以看作是对信号量进行计数。
要使用信号量执行同步,请按照以下步骤操作:
步骤1-创建一个信号量或连接到一个已经存在的信号量(semget())
步骤2-对信号量执行操作,即分配或释放或等待资源(semop())
步骤3-对消息队列执行控制操作(semctl())
现在,让我们用我们拥有的系统调用来检查这一点。
#include
#include
#include
int semget(key_t key, int nsems, int semflg)
该系统调用创建或分配系统V信号量集。需要传递以下参数-
第一个参数key是识别消息队列。键可以是任意值,也可以是从库函数ftok()派生的值。
第二个参数nsems指定信号量的数量。如果为二进制,则为1,表示需要1个信号集,否则按所需的信号集数量计数。
第三个参数semflg指定所需的信号灯标志,例如IPC_CREAT(如果不存在,则创建信号灯)或IPC_EXCL(与IPC_CREAT一起使用以创建信号灯,如果信号灯已经存在,则调用失败)。还需要传递权限。
注意-有关权限的详细信息,请参阅前面的部分。
此调用将在成功时返回有效的信号量标识符(用于信号量的进一步调用),在失败的情况下返回-1。要了解失败的原因,请使用errno变量或perror()函数。
关于此调用的各种错误包括EACCESS(拒绝权限),EEXIST(无法创建已经存在的队列),ENOENT(队列不存在),ENOMEM(没有足够的内存来创建队列),ENOSPC(最大设置限制)超过),等等。
#include
#include
#include
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_op为零,则调用过程将等待或休眠,直到信号量值达到0。
如果sem_op为+ ve,请释放资源。
例如-
struct sembuf sem_lock = {0,-1,SEM_UNDO};
struct sembuf sem_unlock = {0,1,SEM_UNDO};
第三个参数nsemops是该数组中的操作数。
#include
#include
#include
int semctl(int semid, int semnum, int cmd, …)
该系统调用对System V信号量执行控制操作。需要传递以下参数-
第一个参数Semid是信号量的标识符。此id是信号量标识符,它是semget()系统调用的返回值。
第二个参数semnum是信号量的数量。信号量从0开始编号。
第三个参数cmd是用于对信号量执行所需控制操作的命令。
第四个参数,union semun类型,取决于cmd。在少数情况下,第四个参数不适用。
让我们检查工会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 */
};
注意-有关其他数据结构,请参考手册页。
工会联盟cmd的有效值为-
IPC_STAT-将struct semid_ds的每个成员的当前值的信息复制到arg.buf指向的传递结构。此命令需要对信号量具有读取权限。
IPC_SET-设置结构ID所指向的用户ID,所有者的组ID,权限等。
IPC_RMID-删除信号量集。
IPC_INFO-返回有关arg .__ buf指向的结构semid_ds中的信号量限制和参数的信息。
SEM_INFO-返回seminfo结构,其中包含有关信号量消耗的系统资源的信息。
该调用将返回值(非负值),具体取决于传递的命令。成功后,IPC_INFO和SEM_INFO或SEM_STAT根据信号量或GETNCNT的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中实现。整个过程完成后,请删除信号灯(从其他程序读取后)
由于我们有单独的文件来读取共享内存中的counter值,并且对写入没有任何影响,因此读取程序保持不变(shm_read_cntr.c)
最好在一个终端中执行写入程序,然后从另一终端中读取程序。由于该程序仅在读写过程完成后才完成执行,因此可以在完全执行写入程序后运行该程序。写程序将一直等到读程序运行后才完成。
没有信号量的程序。
/* Filename: shm_write_cntr.c */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
#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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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