6.信号量

本文深入讲解了信号量机制的基本概念,包括其特点、函数原型、关键API如semget、semop及semctl的使用方法,并提供了两个代码示例,帮助读者理解如何在Linux环境下运用信号量实现进程间的同步。
摘要由CSDN通过智能技术生成

目录

1.特点:

2.函数原型:

3.semget

4.semop

5.semctl

6.信号量代码demo

7.代码demo优化


信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

1.特点:

  1. 信号量用于进程间同步,若要在进程间传递胡数据需要结合共享内存。
  2. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。(P操作:拿锁。V操作:放回锁)
  3. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或 减1 ,而且可以加加减任意正整数。
  4. 支持信号量组。

2.函数原型:

最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。

Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

//创建或获取一个信号量组,成功会返回信号量集 ID ,失败返回 -1
int semget(key_t key, int nsems, int semflg);
//对信号量组进行操作,改变信号量的值,成功返回 0,失败返回 -1 (用于 PV 操作)
int semop(int semid, struct sembuf *sops, unsigned nsops);    
//控制信号量的相关信息 (用于给信号量初始化)
int semctl(int semid, int semnum, int cmd, ...);

3.semget

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

参数:

 key:一个整型值对应内核中一个信号量对象,可以自己指定,不同信号量的key值不一样。不相关的进程可以通过它访问同一个信号量。程序对所有信号量的访问都是间接的,它先提供一个键,再由系统生成一个响应的信号标识符。

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

semflg: 由九个权限标志构成,它们的用法和创建文件时使⽤的mode模式标志是一样的

返回值:成功返回⼀一个⾮非负整数,即该信号集的标识码;失败返回-1

4.semop

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

参数  :

semid:是该信号量的标识码,也就是semget函数的返回值   

sops:是个指向一个结构数值的指针 

指向一个结构数组的指针,每个数组元素至少包含以下几个成员:

struct sembuf{
   short sem_num; //信号量编号,除非使用一组信号量,否则它的取值为0
   short sem_op;  //信号量在一次操作中需要改变的数值。通常用到两个值,-1,也就是p操作,表示拿锁;+1,也就是V操作,表示放回锁。
   short sem_flg; //通过被设置为SEM_UNDO。表示操作系统会跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量,防止其他进程一直处于等待状态。 
};  

nsops:信号量的个数

返回值:成功返回0;失败返回-1

5.semctl

系统调用semctl用来执行在信号量集上的控制操作。这和在消息队列中的系统调用msgctl是十分相似的。但这两个系统调用的参数略有不同。

semid: 信号量的标志码(ID),也就是semget()函数的返回值;

semnum: 操作信号在信号集中的编号。从0开始。

cmd: 命令,表示将要进行的操作。

参数cmd中可以使用的命令如下:

·IPC_STAT   读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
·IPC_SET    设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
·IPC_RMID  将信号量集从内存中删除。
·GETALL      用于读取信号量集中的所有信号量的值。
·GETNCNT  返回正在等待资源的进程数目。
·GETPID      返回最后一个执行semop操作的进程的PID。
·GETVAL      返回信号量集中的一个单个的信号量的值。
·GETZCNT   返回正在等待完全空闲的资源的进程数目。
·SETALL       设置信号量集中的所有的信号量的值。
·SETVAL      设置信号量集中的一个单独的信号量的值。【一般用这个】

此函数具有三个或四个参数,具体取决于cmd。当
有四个时,第四个具有union semun类型。

第四个参数:可选。是否使用取决于所请求的命令。如果使用该参数,则其类型是semun。
union semun{
  int val;                   SETVAL的值
  struct semid_ds *buf;       IPC_STAT,IPC_SET的缓冲区 
  unsigned short *array;      GETALL,SETALL的数组
    struct seminfo __buf;       IPC_INFO的缓冲区(特定于Linux)
};
一般只使用val这个成员,来为信号量赋初值。当信号量值为0时,进程会阻塞运行。

6.信号量代码demo

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>

//int semget(key_t key, int nsems, int semflg);
//int semctl(int semid, int semnum, int cmd, ...);
//int semop(int semid, struct sembuf *sops, unsigned nsops);

//联合体,用于semctl初始化
union semun {
        int              val;    /* Value for SETVAL */
        struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
        unsigned short  *array;  /* Array for GETALL, SETALL */
        struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                    (Linux-specific) */
};

void pGetKey(int id)
{
        struct sembuf set;
        set.sem_num = 0;            //一般默认是 0
        set.sem_op = -1;            //因为我信号量的初始值设置为0,所以这里-1就是取走锁
        set.sem_flg = SEM_UNDO;     //一般是 SEM_UNDO ,大概是表示阻塞等待

        semop(id,&set,1);           //参数2:是个指向⼀一个结构数值的指针
                                    //参数3:信号量的个数

        printf("get key\n");
}

void pPutBackKey(int id)
{
        struct sembuf set;
        set.sem_num = 0;           //一般默认是 0
        set.sem_op = 1;            //因为我信号量的初始值设置为0,所以这里+1就是有锁
        set.sem_flg = SEM_UNDO;    //一般是 SEM_UNDO ,大概是表示阻塞等待

        semop(id,&set,1);

        printf("put back the key\n");
}

int main()
{
        int semid;

        key_t key;
        key = ftok(".",2);

        //1.获取或创建信号量
        semid =  semget(key,1,IPC_CREAT|0666);  //参数2:信号量集合中有 1 个信号量 
                                                //参数3:0666 是信号量的权限

        union semun initsem;
        initsem.val = 0;           //一般只使用val这个成员,来为信号量赋初值。为0时,没有钥匙

        //2.初始化信号量    
        semctl(semid,0,SETVAL,initsem);    //参数2:操作第0个信号量    
                                           //参数3:SETVAL 设置信号量的初值,此时决定有第四个参数,设置为initsem.
                                           //参数4:是一个联合体。初始化信号量

        int pid = fork();
        if(pid > 0 ){
                //4.去拿锁
                pGetKey(semid);
                printf("this is father\n");
               
                //5.锁返回去
                pPutBackKey(semid);
               
                //6.销毁锁
                semctl(semid,0,IPC_RMID);
        }
        else if(pid == 0){
                printf("this is child\n");
                //3.放锁
                pPutBackKey(semid);
        }
        else{
                printf("fork error\n");
        }

        return 0;
}

7.代码demo优化

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>

//联合体,用于semctl初始化
union semun
{
	int              val;
	struct semid_ds *buf;
	unsigned short *array;
};	

//初始化信号量
int init_sem(int sem_id,int value)
{
	union semun tmp;
	tmp.val = value;

	if(semctl(sem_id,0,SETVAL,tmp) == -1){
		perror("init semaphore error");
		return -1;
	}
	return 0;
}

// P 操作
// 若信号量值为 1,获取资源并将信号量值 -1
// 若信号量值为 0,进程挂起等待
int sem_p(int sem_id)
{
        struct sembuf sbuf;
        sbuf.sem_num = 0;
        sbuf.sem_op = -1;
        sbuf.sem_flg = SEM_UNDO;

        if(semop(sem_id,&sbuf,1) == -1){
                perror("P operation error");
                return -1;
        }
        
	return 0;
}

// V 操作
// 释放资源并将信号量值+1
// 如果有进程正在挂起等待,则唤醒他们
int sem_v(int sem_id)
{
	struct sembuf sbuf;
	sbuf.sem_num = 0;	//序号
	sbuf.sem_op = 1;	// V 操作
	sbuf.sem_flg = SEM_UNDO;

	if(semop(sem_id,&sbuf,1) == -1){
		perror("V operation error");
		return -1;
	}
	
	return 0;
}

//删除信号量集
int del_sem(int sem_id)
{
	union semun tmp;

	if(semctl(sem_id,0,IPC_RMID,tmp) == -1){
		perror("delete semaphore erroe");
		return -1;
	}
	return 0;
}

int main()
{
	int   sem_id; //信号量集 ID
	key_t key;
	pid_t pid;

	//获取key值
	if((key = ftok(".",'z')) < 0){
		perror("ftok error");
		exit(1);
	}

	//创建信号量集,其中只有一个信号量
	if(sem_id = semget(key,1,IPC_CREAT|0666) == -1){
		perror("semget error");
		exit(1);
	}

	//初始化:初值设为 0 资源被占用
	init_sem(sem_id,0);

	if(pid = fork() == -1){
		perror("Fork error");		
	}
	else if(pid == 0){	//子进程
		sleep(2);
		printf("process child: pid = %d\n",getpid());
		sem_v(sem_id);	//释放资源
	}
	else{ //父进程
		sem_p(sem_id);	//等待资源
		printf("process father: pid = %d\n",getpid());
		sem_v(sem_id);	//释放资源
		del_sem(sem_id);//删除信号量集
	}

	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

枕上

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值