进程通信——信号量

浅谈什么是信号量

在讲信号量之前,先了解两个概念同步和互斥:一条食品生产线上,假设A、B共同完成一个食品的包装任务,A负责将食品放到盒子里,B和C负责将盒子打包。必须得是A先装食品B再打包吧,要是B不按规则先打包,那A还装啥,所以就需要一种机制方法保证A先进行B再进行,“信号量”就是这种机制方法,AB之间的关系就是同步关系;再假设打包要用到刀子,而车间就有一把刀子,这时候B和C就构成了互斥关系。 在多任务操作系统环境下,多个进程会同时运行,并且一些进程间可能会存在一定的关联。多个进程可能为了完成同一个任务相互协作,这就形成了进程间的同步关系。而且在不同进程间,为了争夺有限的系统资源(硬件或软件资源)会进入竞争状态,这就是进程间的互斥关系。

进程间的互斥关系与同步关系存在的根源在于临界资源。临界资源是在同一时刻只允许有限个(通常只有一个)进程可以访问(读)或修改(写)的资源,通常包括硬件资源(处理器、内存、存储器及其它外围设备等)和软件资源(共享代码段、共享结构和变量等)。访问临界资源的代码叫做临界区,临界区本身也会称为临界资源。信号量是用来解决进程间的同步与互斥问题的一种进程间通信机制,包括一个称为信号量的变量和在该信号量下等待资源的进程等待队列,以及对信号量进行的两个原子操作(P/V操作)。其中,信号量对应于某一种资源,取一个非负的整形值。信号量值(常用sem_id表示)指的是当前可用的该资源的数量,若等于0则意味着目前没有可用的资源。

信号量
信号量本质上是一个计数器(不设置全局变量是因为进程间是相互独立的,而这不一定能看到,看到也不能保证++引用计数为原子操作),用于多进程对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。

信号量的工作原理
pv原子操作:

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

ftok()函数获取IPC关键字

共享内存,消息队列,信号量三种进程间通信方式都需要ket_t类型的关键字ID值,就像我们需要一个唯一的身份证号来区分一样,有时我们可以直接指定一个固定的整数值作为该ID的值,但通常情况下会通过调用ftok()函数获取该值。在一般的UNIX实现中,该函数是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。如指定文件的索引节点号为65538,换算成十六进程为0x10002,而你指定的ID值为38,换成16进程为0x26,则最后的key_t返回值为0x26010002.查询文件索引节点号的方法是: ls -i filename

ftok()
函数原型

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

key_t  ftok(const  char *pathname, int proj_id);

函数说明:该函数根据pathname指定的文件或目录的索引节点号和proj_id计算并返回一个key_t类型的ID值,如果失败则返回-1.
参数说明:第一个参数pathname是一个系统中必须存在的文件或文件夹的路径,会使用该文件的索引节点;
第二个参数proj_id是用户指定的一个子序号,这个数字有的称之为proje ID。它是一个8bit的整数,取值范围是1~255.

需要注意的是:如果要确保key值不变,要么确保ftok()的文件不被删除,要么不用ftok()指定一个固定的key值

信号量的相关函数

1、semget()
函数原型

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

int semget( key_t key, int nsems, int semflg);

函数说明:该函数用来创建一个信号集,或者获取已存在的信号集。成功返回信号集的标识符,失败返回-1。

参数说明:第一个参数:key是所创建或打开信号量集的键值(ftok成功执行的返回值),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget()函数并提供一个键值,再由系统生成一个相应的信号标识符(semget()函数的返回值),只有semget()函数才直接使用信号量键,所有其他的信号量函数使用由semget()函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作;
第二个参数:nsems指定需要的信号量数目,它的值几乎总是1;
第三个参数:semflg是一组标志,当想要信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作,设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已经存在,返回一个错误。

2、semctl()
函数原型

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

int semctl(int semid, int semun, int cmd,....);

函数说明:该函数用来初始化信号集,或者删除信号集。成功返回一个正数,失败返回-1。
参数说明:第一个参数:semid是前面讲的semget()函数返回的信号量键值;
第二个参数:semun是操作信号在信号集中的编号,第一个信号是0;
第三个参数:cmd是在semid指定的信号量集合上执行此命令,可以是:
SETVAL:设置信号量集中的一个单独的信号量的值,此时需要传入第四个参数;
IPC_RMID:从系统中删除该信号量集合;
IPC_SEAT:对此集合取semid_ds结构,并存放在由arg.buf指向的结构中;
第四个参数:是可选的,如果使用该参数,则其类型是semun,它是多个特定命令行参数的联合(union),该联合不在任何系统头文件中定义,需要我们在自己的代码中定义:

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

3、semop()
函数原型

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

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

函数说明:操作一个或一组信号,也可以叫pv操作。成功执行时,都会返回0,失败时返回-1,并设置errno错误信息。
参数说明:第一个参数:semid是前面讲的semget()函数返回的信号量键值;
第二个参数sops是一个指针,指向一个信号量操作数组。信号量操作由结构体sembuf结构表示如下:

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

第三个参数:nsops信号操作结构的数量,恒大于或等于1.

信号量的使用代码示例

在linux系统中,信号量通常分为以下四个步骤:

  1. 创建信号量或获得在系统中已存在的信号量,此时需要调用semget()函数。不同进程通过使用同一个信号量键值来获得同一个信号量。
  2. 初始化信号量,此时使用semctl()函数的SETVAL操作。当使用互斥信号量时,通常将信号量初始化为1.
  3. 进行信号量的PV操作,此时调用semop()函数。这一步时实现进程间的同步和互斥的核心工作部分。
  4. 如果不需要信号量,则从系统中删除它,此时使用semctl()函数的IPC_RMID操作。需要注意的是,在程序中不应该出现对已经被删除的信号量的操作。

下面我们将通过P、V操作来实现父子进程同步运行的例子。在初始化信号量时将信号量初始值设为0,如果父进程先运行的话讲会调用semaphore_p(semid),这时因为资源为0所以父进程会阻塞,而之后子进程运行时会执行semaphore_v(semid)将资源+1,父进程之后就开始运行了。

semaphore的代码

#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;

};
int semaphore_init(void);
int semaphore_p(int semid);
int semaphore_v(int semid);
void semaphore_term(int semid);

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

    if( (semid=semaphore_init()) < 0)
    {
        printf("semaphore initial failure:%s\n",strerror(errno));
        return -1;
    }
    if( (pid=fork()) <0)
    {
        printf("fork()failure:%s\n", strerror(errno));
        return -2;
    }
    else if (0 == pid)
    {
        printf("child process start running and do something\n");
        sleep(3);
        printf("child process do something over..\n");
        semaphore_v(semid);

        sleep(1);
        printf("child process exit now\n");
        exit(0);

    }

    printf("parent process p operator wait child process over\n");
    semaphore_p(semid);

    printf("perent process destroy semaphore and exit\n");
    sleep(2);
    printf("child process exit and");
    semaphore_term(semid);

    return 0;
} 
int semaphore_init(void)
{
    key_t   key;
    int     semid;
    union   semun   sem_union;

    if( (key=ftok(FTOK_PATH,FTOK_PROJID)) <0)
    {
        printf("ftok get IPC token failure:%s\n",strerror(errno));
        return -1;
    }
    semid = semget(key,1,IPC_CREAT|0644);
    if(semid< 0)
    {
        printf("semget get semid failure %s\n",strerror(errno));
        return -2;
    }
    sem_union.val =0;
    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);

    return semid;
}   
void semaphore_term(int semid)
{
    union semun sem_union;

    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;
    _sembuf.sem_op = -1;
    _sembuf.sem_flg = SEM_UNDO;

    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;
}

代码运行结果:
在这里插入图片描述

发布了28 篇原创文章 · 获赞 44 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/makunIT/article/details/105024978
今日推荐