进程间通信--信号量

本文深入解析信号量机制,包括其核心概念如临界区、互斥、原子性等,并详细阐述信号量如何通过P/V操作解决多进程共享资源的问题。此外,还提供了具体的信号量操作函数实现案例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言


在这我们需要提出一些概念: 
临界区:指的是访问临界资源的程序代码片段。临界区只能允许一个进程进入。 
临界资源:临界资源说的是一次只能提供一个进程使用的资源。 
互斥:互斥是指某一个资源同时只允许一个访问者对其进行访问。 
原子性:一个事务包含多个操作,这些操作要么全部执行,要么全都不执行。 
加锁:这里的加锁就是给临界区加锁,也叫做互斥锁,用来保护临界区。因为两个进程同时访问临界区会可能出现问题,所以需要互斥锁来保护。


使用信号量的原因:为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,它可以通过生成并使用令牌来授权,在任意时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来协调进程对共享资源的访问的。

缺点:创建和初始化分离。



信号量只能进行两种操作等待和发送信号,即P和V: 
P(sv):如果sv的值大于0,就给它减1,如果它的值为0,就挂起该进程的执行。 
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1。


信号量有关的函数

1、semget函数创建一个信号量集对象(得到一个信号量集标识符)

    函数原型:int semget(key_t key,int nsems,int semflg)

    key:由ftok()函数的得到

    nsems:创建信号量集中信号的个数

    semflg:

        IPC_CREAT:若内核中不存在键值与key相等的信号量集,则创建,否则,返回此信号量集的标识符

        IPC_EXCL:单独使用无意义

        IPC_CREAT | IPC_EXCL :创建一个新的信号量集并返回信号量集的标识符,否则,返回-1.

    返回值:成功返回信号量集的标识符。失败返回-1.

 

2、semctl函数:在指定的信号集或信号集内的某个信号上执行操作控制

    函数原型:int semctl(int semid,int semnum,int cmd,union semun arg)

      semid: 信号量集标识符

      semnum:信号量集数组上的下标,表示某一个信号量

      arg:

                union semun {

                   short val;          /*SETVAL用的值*/

                   struct semid_ds* buf; /*IPC_STATIPC_SET用的semid_ds结构*/

                   unsigned short* array; /*SETALLGETALL用的数组值*/

                   struct seminfo *buf;   /*为控制IPC_INFO提供的缓存*/

                  } arg;

3、semop函数:对信号量进行P,V操作(本文的重点) 

P操作负责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;

V操作负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。

semop函数原型如下:

int semop(int semid, struct sembuf  *sops, unsigned nsops);

semop操作中:sembuf结构的sem_flg成员可以为0IPC_NOWAITSEM_UNDO 。为SEM_UNDO时,它将使操作系统跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的。

sembuf结构的sem_flg成员为SEM_UNDO时,它将使操作系统跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量,从而使另外一个进程可以继续工作,防止其他进程因为得不到信号量而发生【死锁现象】。个标志在某些情况下是很有用的,比如某进程做了P操作得到资源,但还没来得及做V操作时就异常退出了,此时,其他进程就只能都阻塞在P操作上,于是造成了死锁。若采取SEM_UNDO标志,就可以避免因为进程异常退出而造成的死锁。为此一般建议使用SEM_UNDO。


实现代码:


#ifndef _SEM_H_
#define _SEM_H_
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>

#define PATHNAME "."
#define PROJ_ID 0x666


int creat_sem(int nsems);  //创建几个信号量。

int init_sem(int semid); //对信号量初始化。

int get_sem();  //得到信号量。

int destroy_sem(int semid);  //销毁信号量。

int p_sem(int semid,int which); //对信号量中某一个信号操作。

int v_sem(int semid,int which);//对信号量中某一个信号操作。






#endif
#include"sem.h"


static int comm_sem(int nsems, int flags)//函数只在这个文件中使用,
{

	key_t K = ftok(PATHNAME,PROJ_ID);//获取键值。
	if (K < 0)
	{
		perror("ftok");
		return -1;
	}
	int semid = semget(K,nsems,flags);//得到信号量集id.nsems表示信号量集中的个数,创建新集合的时候,必须指定nsems,引用现有的,则设定nsem为0.
	if (semid < 0)
	{
		perror("semget");
		return -2;
	}
	return semid;
}


int creat_sem(int nsems)  //创建几个信号量。
{
	return comm_sem(nsems,IPC_CREAT|IPC_EXCL|0x666); //创建一个信号量。信号量的权限为666。
}


int init_sem(int semid) //对信号量初始化。
{
	union semun  //semctl的第四个参数是可以选择的,是共用体semun.
	{
		int val;
		struct semid_ds *buf;
		unsigned short *array;
		struct seminfo *_buf;
	};
	union semun semun;
	semun.val = 1;
	int ret = semctl(semid,0,SETVAL,semun);//第二个参数表示信号量集数组上的下标,表示某一个信号量。第三个参数表示用联合体中val成员的值设置信号量集合中单个信号量的值。函数成功返回0.
	if (ret < 0)
	{
		perror("semctl");
		return -1;
	}
	return ret;

}


int get_sem()  //得到信号量。
{
	return comm_sem(0,IPC_CREAT); //获取现有的,nsems为0.
}


int destroy_sem(int semid) //销毁信号量。
{
	int ret = semctl(semid,0,IPC_RMID); //cmd: IPC_RMID表示从内核中删除信号量集合。
	if (ret < 0)
	{
		perror("destroy_sem");
		return -1;
	}
	return ret;
}

static int comm_op(int semid,int which,int op) //对信号量中的那个信号量进行哪一种操作。
{

	struct sembuf sembuf;  //这个结构不需要自己定义
	sembuf.sem_num = which;//sem_num表示某一个信号量下标。
	sembuf.sem_op = op;//sem_op表示操作方式。
	sembuf.sem_flg = 0;//sem_flag 可以为0,IPC_NOWAIT ,SEM_UNDO,一般用SEM_UNDO 可以防止死锁。
	int ret = semop(semid,&sembuf,1);//semop第三个参数为nsops:表示进行操作信号量的个数,即sops结构变量的个数,需大于或等于1,最常见的此值设置为1,只完成对一个信号两个的操作。
	if (ret < 0)
	{
		perror("semop");
		return -1;
	}
	return ret;
}

int p_sem(int semid,int which)//对信号量中某一个信号操作
{
	return(comm_op(semid,which,-1));  //p进行减1操作。
}

int v_sem(int semid,int which)//对信号量中某一个信号操作。
{
	return comm_op(semid,which,1);//进行加1操作。
}
#include"sem.h"
#include<unistd.h>

int main()
{
	int semid = creat_sem(1);
	int ret = init_sem(semid);
	if (ret < 0)
	{
		return -1;
	}
	printf("semid:%d\n",semid);
	pid_t id = fork();
	if (id == 0)
	{/child
		while (1)
		{
			p_sem(semid,0);
			usleep(10002);
			printf("A");
			fflush(stdout);
			usleep(1080);
			printf("A");
			fflush(stdout);
			v_sem(semid,0);
		}
	}
	else
	{

		while (1)
		{
			p_sem(semid,0);
			usleep(1002);
			printf("B");
			fflush(stdout);
			usleep(10080);
			printf("B");
			fflush(stdout);
			v_sem(semid,0);
		}
		wait(NULL);
	}
	destory_sem(semid);

	return 0;
}







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值