进程间通信——信号量

本文介绍了Linux环境下信号量机制的基本概念,包括信号量的作用、特点及其三种主要系统调用:semget、semop和semctl的功能与用法。此外,还提供了一个使用IPC_PRIVATE创建信号量并进行父子进程间通信的示例。

1、信号量介绍

    多个进程同时访问系统上的同一资源时,就需要考虑同步问题,以确保在任一时刻只有一个进程对资源独占式访问。通常,我们称访问共享资源的代码为临界区,进程同步,也就是确保同一时刻,只有一个进程进入临界区。

    信号量是实现进程同步的一种方式,信号量只能取自然数并且只支持两种操作,即等待和信号,在Linux中称为P、V操作。比如有SV信号量,则P、V操作的含义如下:

  • P(SV),如果SV的值大于0,就将其减1;如果SV的值等于0,则挂起该进程的执行
  • V(SV),如果有其他进程因为等待信号量SV而挂起,则唤醒;如果没有,则将SV加1

    信号量可以取任意自然数,但是最常用的信号量为二进制信号量,它只能取0和1,这里只讨论二进制信号量。关于信号量的系统调用主要有3个:semget、semop、semctl。此三个函数都是被设计用来操作信号量集,而不是单个信号量。

2、semget系统调用

#include<sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags)

    函数功能为用来创建一个信号量集或获取一个已经存在的信号量集,参数信息如下:

  • key参数是一个键值,用来标识一个全局的唯一的信号量集。通过信号量通信的进程使用相同的键值来创建\获取该信号量。当key为特殊键值IPC_PRIVATE(值为0)时,无论该信号量是否已经存在,都将会创建一个新的信号量集。信号量键值一般只有semget函数使用,其他的有关信号量函数,使用semget返回的信号量集标识符来访问该信号量。
  • num_sems参数指定要创建/获取的信号量集中的信号量数目。如果是创建信号量,该值必须被设置,如果是获取,则可以置为0。
  • sem_flags参数指定一组标志。低9位指示了信号量的权限。它可以和IPC_CREAT标志做按位或操作,用来创建信号量,此时即使信号量已经存在,semget也不会产生错误。还可以联合使用IPC_CREAT和IPC_EXCL标志来确保创建一组新的、唯一的信号量集,这种情况下,如果信号量集已经存在,则semget返回错误并置errno为EEXIST。
  • semget成功时返回信号量集标识符(正整数值),失败时返回-1,并设置errno。

3、semop系统调用

#include<sys/sem.h>
int semop(int sem_id, struct sembuf* sem_ops, size_t num_sem_ops)

    semop函数用来改变信号量的值,即执行P、V操作,参数信息如下:

  • sem_id参数是由semget调用返回的信号量集标识符,指定要改变的信号量集。
  • sem_ops参数是一个指向sembuf结构数组的指针。sembuf结构体定义如下:
struct sembuf
{
    unsigned short int sem_num;
    short int sem_op;
    short int sem_flg;
}

    sem_num成员指定信号量集中的信号量编号,从0开始。sem_op成员指定操作类型,其值可为正整数、0和负整数,信号量集操作类型较为复杂(可参见APUE),这里就只以二进制信号量为例来说明,sem_op为-1时即为P操作,为1时即为V操作。sem_flg成员可选值为IPC_NOWAIT和SEM_UNDO,IPC_NOWAIT表示系统调用无论是否操作成功,都将立即返回,类似于非阻塞;而SEM_UNDO则表示使内核跟踪信号量,如果进程没有释放信号量而异常退出,则由内核来释放该信号量值。

  • num_sem_ops指定sem_ops数组中的元素个数,即要操作的个数。
  • semop成功时返回0,失败时返回-1并设置errno。失败时,sem_ops数组中指定的所有操作都不被执行。

4、semctl系统调用

#include<sys/sem.h>
int semctl(int sem_id, int sem_num, int command, ...)

    semctl函数用于对信号量进行控制,参数信息如下:

  • sem_id参数是由semget调用返回的信号量集标识符。
  • sem_num参数指定被操作的信号量的编号,从0开始计数。
  • command参数指定要执行的命令,有的命令需要调用者传递第四个参数,第四个参数的类型由用户自定义,推荐格式如下:
union semun
{
    int val;
    struct semid_ds* buf;
    unsigned short* array;
    struct seminfo* _buf;
}

    command常用的操作有两种,其一,SETVAL,用来把信号量初始化为一个已知值,这个值由union semun中的val成员指定,其作用是在信号量初次使用前进行设置;其二,IPC_RMID,用于删除一个不需要再使用的信号量集,并唤醒所有等待该信号量集的进程。

5、使用IPC_PRIVATE信号量进行进程间通信示例

#include<sys/sem.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>

#define SEM_FLAGS 0666

union semun
{
	int val;
	struct semid_ds* buf;
	unsigned short int* array;
	struct seminfo* _buf;
};

void pv(int sem_id, int option)   //pv操作,option为-1时为p操作,option为1时为v操作
{
	struct sembuf sem_buf;
	sem_buf.sem_num = 0;
	sem_buf.sem_op = option;
	sem_buf.sem_flg = SEM_UNDO;
	semop(sem_id, &sem_buf, 1);
}

int main(int argc, char* argv[])
{
	int sem_id = semget(IPC_PRIVATE, 1, SEM_FLAGS);  //创建信号量
	union semun sem_un;
	sem_un.val = 1;
	semctl(sem_id, 0, SETVAL, sem_un);   //初始化信号量的值为1

	pid_t pid = fork();
	if(0 == id)
	{
		pv(sem_id, -1);
		printf("child process\n");
		sleep(5);
		pv(sem_id, 1);
		exit(0);
	}
	else if(id > 0)
	{
		pv(sem_id, -1);
		printf("parent process\n");
		sleep(5);
		pv(sem_id, 1);
	}
	else
	{
		printf("fork error\n");
		return 1;
	}

	wait(NULL);
	semctl(sem_id, 0, IPC_RMID, sem_un);   //删除信号量
	return 0;
}
信号量是一种用于进程间通信和同步的机制。它是一个计数器,用于保证在共享资源上的互斥访问。在Linux系统中,可以使用信号量来实现进程间的同步和互斥。以下是信号量的基本概念: - 计数器:信号量的值是一个计数器,它可以被多个进程共享。 - P操作:当一个进程需要访问共享资源时,它必须执行P操作,该操作会将信号量的值减1。如果信号量的值为0,则进程将被阻塞,直到信号量的值大于0。 - V操作:当一个进程使用完共享资源后,它必须执行V操作,该操作会将信号量的值加1。如果有进程正在等待该信号量,则唤醒其中一个进程继续执行。 在ZUCC中,可以使用信号量来实现进程的同步和互斥。首先,需要使用semget函数创建一个信号量集合,并使用semctl函数对信号量进行初始化。然后,可以使用semop函数执行P和V操作。例如,下面是一个简单的示例程序,用于演示信号量的使用: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/sem.h> #define SEM_KEY 1234 union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main() { int semid, pid; union semun arg; struct sembuf sb; // 创建信号量集合 semid = semget(SEM_KEY, 1, IPC_CREAT | 0666); if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); } // 初始化信号量 arg.val = 1; if (semctl(semid, 0, SETVAL, arg) == -1) { perror("semctl"); exit(EXIT_FAILURE); } // 创建子进程 pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程执行P操作 sb.sem_num = 0; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop P"); exit(EXIT_FAILURE); } printf("Child process\n"); // 子进程执行V操作 sb.sem_num = 0; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop V"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } else { // 父进程执行P操作 sb.sem_num = 0; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop P"); exit(EXIT_FAILURE); } printf("Parent process\n"); // 父进程执行V操作 sb.sem_num = 0; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop V"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } return 0; } ``` 在上述代码中,创建了一个信号量集合,并将其初始化为1。然后,创建了一个子进程和一个父进程,它们分别执行P和V操作。由于信号量的初始值为1,因此父进程和子进程都可以顺利地执行。如果将信号量的初始值改为0,那么父进程和子进程都将被阻塞,直到有一个进程执行V操作为止。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值