Linux之进程间通信③——信号量(Semaphore)

一、信号量

1.1 基本介绍

信号量是用来处理进程间同步和互斥问题的一种进程间通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作(PV操作),其中,信号量对应于某一种资源,取一个非负的整形值。信号量值(通常用sem_id表示)指的是当前可用的该资源的数量,若等于0则意味着目前没有可用的资源。

1.1.1 同步与互斥

同步关系:指在某些时刻为完成某种任务而使得多个进程协调工作而产生的制约关系,也称为直接制约关系(如管道通信中,必须先写后读);
互斥关系:指当一个进程访问某临界资源时,另一个想要访问该临界资源的进程必须等待,直到当前访问临界资源的进程访问结束,释放该资源后,另一个进程才能去访问临界资源,也称为间接制约关系;(通俗地说就是在只有一个洗手间多人等待时,当此时洗手间里的人使用完出来后,才能轮到下一个人)
同步是一种更为复杂的互斥,而互斥是一种特殊的同步
进程间的同步与互斥关系存在的根源在于临界资源

1.1.2 临界资源

临界资源:在同一时刻只允许有限个(通常只有一个)进程可以访问(读)或修改(写)的资源,通常包括硬件资源(处理器、内存、存储器及其他外围设备等)和软件资源(共享代码段、共享结构和变量等)
临界区:访问临界资源的代码(临界区本身也叫临界资源)

1.1.3 PV操作

P操作:如果有可用的资源(信号量值>0),则此操作所在的进程占用一个资源(此时信号量值减1,进入临界区代码);如果没有可用的资源(信号量值=0,)则此操作所在的进程被阻塞直到系统将资源分配给该进程(进入等待队列,一直等到资源轮到该进程);
V操作:如果在该信号量值的等待队列中有进程在等待资源,则唤醒一个阻塞进程;如果没有进程等待它,则释放一个资源(即信号量值加1);

1.1.4 使用信号量的基本步骤

①创建信号量或获得在系统中已存在的信号量,调用semget()函数(不同进程通过使用同一个信号量键值来获得同一个信号量)
②初始化信号量,调用semctl()函数的SETVAL操作(当使用互斥信号量时,通常将信号量初始化为1)
③进行信号量的PV操作,调用semop()函数(是实现进程间的同步和互斥的核心工作部分)
④在系统中删除不需要的信号量,调用semctl()函数的IPC_RMID操作

1.2 函数介绍

1.2.1 semget()

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h> 
int semget( key_t key, int nsems, int semflg);

①函数功能:创建或获取信号量
②函数参数
第一个参数→key是所创建或打开的信号量集的键值,即fork()的返回值;
第二个参数→nsems是指定需要的信号量数目,几乎总是1;
第三个参数→semflg是一组标志,当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作
③函数返回值:成功,返回信号量集的标识符;失败,返回-1;

1.2.2 semctl()

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h>
int semctl(int semid, int semun, int cmd, ...);

①函数功能:初始化信号集或删除信号集
②函数参数
第一个参数→semid为semget()返回的信号量键值;
第二个参数→semun为操作信号在信号集中的编号,第一个信号为0;
第三个参数→cmd是在semid指定的信号量集合上执行此命令,命令如下:

SETVAL:设置信号量集中的一个单独的信号量的值,此时需要传入第四个参数;
IPC_RMID:从系统中删除该信号量集合;
IPC_SEAT:对此集合取semid_ds结构,并存放在由arg.buf指向的结构中;

第四个参数→可选,如使用该参数,其类型为semun,是多个特定命令参数的联合,该联合不在任何系统头文件中定义,需要自行在代码中定义

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

③函数返回值:成功,返回一个正数;失败,返回-1;

1.2.3 semop()

#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid,struct sembuf *sops,unsigned nsops);

①函数功能:操作一个或一组信号,或叫PV操作
②函数参数
第一个参数→semid为semget()返回的信号量键值;
第二个参数→sops是一个指针,指向一个信号量操作数组,信号量操作由结构体sembuf结构表示如下:

struct sembuf 
{
    
    
	unsigned short sem_um;//操作信号在信号集中的编号
 	short	sem_op;//操作为负(P操作),其绝对值大于信号的现有值,操作将会阻塞,直到信号值≥ |sem_op|。通常用于获取资源的使用权;
 			      //操作为正(V操作),其值会加到现有的信号内值上。通常用于释放所控制资源的使用权;					   									  
		  	     //操作为0:如sem_flg没有设置IPC_NOWAIT,则调用该操作的进程或线程将暂时睡眠,直到信号量的值为0;否则进程或线程会返回错误EAGAIN;
	short	sem_flg;//信号操作标识符,有如下两种选择:
				   //IPC_NOWAIT:对信号的操作不能满足时,semop()不会阻塞,并立即返回,同时设置错误消息;
				  //SEM_UNDO:程序结束时(正常退出或异常终止),保证信号会被重设为semop()调用前的值。避免程序在异常情况下结束时未解锁锁定的资源,造成资源被永远锁定,造成死锁。
 };

第三个参数→nsops为信号操作结构的数量,恒大于或等于1;
③函数返回值:成功,返回0;失败,返回-1;

1.3 信号量编程

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

#define  FTOK_PATH  "/dev/zero"
#define  FTOK_PROJID    0x22

union semun
{
    
    
    int val;
    struct semid_ds  *buf;
    unsigned  short  *arry;
};

union semun sem_union;
int semid;

int semaphore_init(void);			//信号量初始化
int semaphore_p(int semid);			//父进程进行P操作
int semaphore_v(int semid);			//子进程进行V操作(优先子进程先运行)
void semaphore_term(int semid);

int main(int argc,char **argv)
{
    
    
    pid_t pid;
    int i;
    int semid;

    if((semid = semaphore_init()) < 0)
    {
    
    
		printf("semaphore initial failure:%s\n",strerror(errno));
		return -1;
    }

    if((pid = fork()) < 0)
    {
    
    
		printf("create child process failure:%s\n",strerror(errno));
		return -2;
    }
    else if(0 == pid)
    {
    
    
		printf("child process[%d] start running and do something now...\n",getpid());
		sleep(3);
		printf("child process done.\n");
		semaphore_v(semid);		//子进程进行V操作
		sleep(1);
		printf("child process exit now\n");
		exit(0);
    }
    printf("parent process P operator wait child process over.\n");
    semaphore_p(semid);		//子进程没进行v操作前,父进程在阻塞在此
    printf("parent process[%d] start running and do something now...\n",getppid());
    sleep(2);
    printf("parent process destroy semaphore and exit\n");
    semaphore_term(semid);
    return 0;
}
int semaphore_init(void)
{
    
    
    key_t   key;
    int  semid;
    union semun sem_union;
    sem_union.val = 0;		//将信号量的值设为0
    key = ftok(FTOK_PATH,FTOK_PROJID);		//获取IPC关键字key
    if(key < 0)
    {
    
    
        printf("ftok() get key failure:%s\n",strerror(errno));
        return -1;
    }
    printf("ftok() get key successfully!\n");

    semid = semget(key,1,IPC_CREAT|0644);		//创建或获取信号量,信号量不存在则创建
    if(semid < 0)
    {
    
    
		printf("semget() get semid  failure:%s\n",strerror(errno));
		return -2;
    }

    if(semctl(semid,0,SETVAL,sem_union) < 0)	//初始化信号集,设置信号量集中的一个单独的信号量的值,并使用该参数
    {
    
    
		printf("semctl() set initial value failure: %s\n", strerror(errno));		//设置初始化值失败
		return -3;
    }
    printf("semaphore get key_t[0x%x] and semid[%d]\n", key, semid);	//key数据类型为16进制整数
    return semid;	
}

void semaphore_term(int semid)
{
    
    
     if(semctl(semid,0,IPC_RMID,sem_union) < 0)
     {
    
    
		printf("semctl() delete semaphore ID failure:%s\n",strerror(errno));
     }
     return ;
}

int semaphore_p(int semid)
{
    
    
     struct sembuf  _sembuf;
     _sembuf.sem_num = 0;		//第一个信号编号为0,最后一个为nsems-1
     _sembuf.sem_op  =  -1;		//操作为负操作(P操作),semid ≥ |sem_op| 才能继续运行,否则阻塞
     _sembuf.sem_flg  =  SEM_UNDO;	//信号操作标识,程序结束时(正常退出或异常终止),保证信号值会被重设为semop()调用前的值。避免程序在异常情况下结束时未解锁锁定的资源,造成资源被永远锁定。造成死锁。
     
    if(semop(semid,&_sembuf,1) < 0)
    {
    
    
		printf("semop  P  operator   failure:%s\n",strerror(errno));
		return -1;
    }
    return 0;
}

int semaphore_v(int semid)
{
    
    
     struct sembuf  _sembuf;
     _sembuf.sem_num = 0;		
     _sembuf.sem_op  =  1;		
     _sembuf.sem_flg  =  SEM_UNDO;	
    if(semop(semid,&_sembuf,1) < 0)
    {
    
    
		printf("semop V  operator   failure:%s\n",strerror(errno));
		return -1;
    }
    return 0;
}

运行结果:

ftok() get key successfully!
semaphore get key_t[0x22050005] and semid[1]
parent process P operator wait child process over.
child process[24213] start running and do something now...
child process do something over...
parent process[24161] start running and do something now...
child process exit now
parent process destroy semaphore and exit

猜你喜欢

转载自blog.csdn.net/xll102500/article/details/129465264