进程间通信--信号量

来源:互联网 发布:阿克苏新站怎么做优化 编辑:程序博客网 时间:2024/06/05 19:02

from:http://hi.baidu.com/monalisa88188/item/3f04911f5ddfd35df0090e5d



    一、信号量的基本概念
    信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源访问的同步。临界资源可以简单地理解为在某一时刻只能由一个进程或线程进行操作的资源,这里的资源可以是一段代码、一个变量或某种硬件资源。信号量的值大于或等于0时表示可供并发进程使用的资源实体数;小于0时代表正在等待使用临界资源的进程数。
    注意:这里的信号量与前面讲过的信号是没有关系的。
    与消息队列类似,Linux内核也为每个信号集维护了一个semid_ds数据结构实例。该结构定义在头文件linux/sem.h中,各字段含义如下:
struct semid_ds{
struct ipc_perm    sem_perm;     /*对信号进行操作的许可权*/
__kernel_time_t     sem_otime;     /*对信号进行操作的最后时间*/
__kernel_time_t     em_ctime;       /*对信号进行修改的最后时间*/
struct sem            *sembase       /*指向第一个信号*/
struct sem_queue sem_pending;    /*等待处理的挂起操作*/
struct sem_queue **sem_pending_last;    /*最后一个正在挂起的操作*/
struct sem_undo   *undo;               /*撤销的请求*/
ushort                   sem_nsems;      /*数组中的信号数*/
};
    二、信号量的创建与使用
    1、信号集的创建或打开
    Linux下使用系统函数semget创建或打开信号集。这个函数定义在头文件sys/sem.h中,函数原型如下:
int semget(key_t key,int nsems,int semflg);
    该函数执行成功则返回一个信号集的标识符,失败返回-1。函数的第一个参数是由ftok()得到的键值;第二个参数nsems指明要创建的信号集包含的信号个数,如果只是打开信号集,把nsems设置为0即可;第三个参数semflg为操作标志,可以取如下值。
    IPC_CREAT:调用semget()时,它会将此值与系统中其他信号集的key进行对比,如果存在相同的key,说明信号集已存在,此时返回该信号集的标识符,否则新建一个信号集并返回其标识符。
    IPC_EXCL:该宏须和IPC_CREATE一起使用,否则没有意义。当semflg取IPC_CREAT|IPC_EXCL时,表示如果发现信号集已经存在,则返回错误,错误码为EEXIST。
    下面是一个创建信号集并对信号集中所有信号进行初始化的函数:
int createsem(const char *pathname,int proj_id,int members,int init_val)
{
key_t msgkey;
int index,sid;
union semun semopts; //后面会介绍该共用体

/*获取键值*/
if ((msgkey = ftok(pathname,proj_id)) == -1)
    {
       perror("ftok error!\n");
       return -1;
    }

if ((sid = semget(msgkey,members,IPC_CREATE0666)) == -1)
    {
       perror("semget call failed.\n");
       return -1;
    }

/*初始化操作,后面将介绍该函数的使用方法*/
semopts.val = init_val;
for(index = 0;index<members;index++)
    {
       semctl(sid,index,SETVAL,semopts);
    }
return (sid);
}

    2、信号量的操作
    信号量的值与相应资源的使用情况有关,当它的值大于0时,表示当前可用资源的数量,当它的值小于0时,其绝对值表示等待使用该资源的进程个数。信号量的值仅能由PV操作来改变。在Linux下,PV操作通过调用函数semop实现。该函数定义在头文件sys/sem.h,原型如下:
int semop(int semid,struct sembuf *sops,size_t nsops);
    函数的参数semid为信号集的标识符;参数sops指向进行操作的结构体数组首地址;参数nsops指出将要进行操作的信号的个数。semop函数调用成功返回0,否则返回-1。
    semop的第二个参数sops指向的结构体数组中,每个sembuf结构体对应一个特定信号的操作。因此对信号进行操作必须熟悉该数据结构,该结构体定义在linux/sem.h,如下所示:
struct sembuf{
ushort    sem_num;    /*信号在信号集中的索引*/
short      sem_op;       /*操作类型,见下表*/
short      sem_flg;       /*操作标志*/
};
                      sem_op的取值及意义
取值范围                                 操作意义
sem_op>0                          信号加上sem_op的值,表示进程释放控制的资源
sem_op=0                          如果没有设置IPC_NOWAIT,则调用进程进入睡眠状态,直到 
                                           信号值为0;否则进程不会睡眠,直接返回EAGAIN
sem_op<0                          信号加上sem_op的值,若没有设置IPC_NOWAIT,则调用进
                                           程阻塞,直到资源可用;否则进程直接返回EAGAIN
    下面是对一个信号集中的某个信号进行操作的P、V函数。
/*P操作函数*/
int sem_p(int semid,int index)
{
struct sembuf buf = {0,-1,IPC_NOWAIT};

if (index < 0)
    {
       perror("index of array cannot equals a minus value!");
       return -1;
    }

buf.sem_num = index;
if (semop(semid,&buf,1) == -1)
    {
       perror ("a wrong operation to semaphore occurred!");
       return -1;
    }

return 0;
}

/*V操作函数*/
int sem_v(int semid,int index)
{
struct sembuf buf = {0,1,IPC_NOWAIT};

if (index < 0)
{
     perror("index of arrary cannot equals a minus value!");
     return -1;
}

buf.sem_num = index;
if (semop(semid,&buf,1) == -1)
{
     perror("a wrong operation to semaphore occurred!");
     return -1;
}

return 0;
}

    3、信号集的控制
    使用信号量时,往往需要对信号集进行一些控制操作,比如删除信号集、对内核维护的信号集的数据结构semid_ds进行设置、获取信号集中信号值等。通过semctl控制函数可以完成这些操作,该函数定义在sys/sem.h,如下所示:
int semctl(int semid,int semnum,int cmd,...);
    函数中,参数semid为信号集的标识符;参数semnum标识一个特定的信号;cmd指明控制操作的类型;最后的“..."说明函数的参数是可选的,它依赖于第三个参数cmd,它通过共用体变量semun选择要操作的参数。semun定义在linux/sem.h,如下所示:
union semun{
int           val;
struct semid_ds *buf;
ushort                 *buf;
struct seminfo     *buf;
void                      *pad; 
};
    各字段的含义如下:
    val:仅用于SETVAL操作类型,设置某个信号的值等于val。
    buf:用于IPC_STAT和IPC_SET操作,存取semid_ds结构。
    array:用于SETALL和GETALL操作。
    buf:为控制IPC_INFO提供的缓存。
    第二个参数cm,通过宏来指示操作类型,可取的各个宏的含义如下:
    IPC_STAT:通过semun共用体的buf参数返回当前的semid_ds结构体。
    注意,调用者必须首先分配一个semid_ds结构,并把buf设置为指向这个结构体。
    IPC_SET:对信号集的属性进行设置。
    IPC_RMID:把semid指定的信号集从系统中删除。
    GETPID:返回最后一个执行semop操作的进程ID。
    GETVAL:返回信号集中semnum指定信号的值。
    GETALL:返回信号集中所有信号的值。
    GETNCNT:返回正在等待资源的进程的数量。
    GETZCNT:返回正在等待完全空闲资源的进程数量。
    SETVAL:设置信号集中semnum指定的信号的值。
    SETALL:设置信号集中所有信号的值。
    下面是一个获取和设置单个信号的函数,代码如下:
int semval_op(int semid,int index,int cmd)
{
if (index < 0)
    {
       printf("index cannot be minus!\n");
       return -1;
    }

if (cmd == GETVAL || cmd == SETVAL)
    {
       return semctl(semid,index,cmd,0);
    }
printf("function cannot surport cmd:%d\n",cmd);
return -1;
}
    三、信号量的应用实例
    信号量一般用于处理访问临界资源的同步问题。下面通过例10-15和例10-16来演示如何控制对资源的访问。10-15创建一个信号集,并对信号量循环减1,相当于分配资源。10-16执行时检查信号量,如果其值大于0代表有资源可用,继续执行,如果小于等于0代表资源已经分配完毕,进程10-16退出。
    例10-15
#include <sys/types.h>
#include <linux/sem.h>

#define MAX_RESOURCE    5

int main(void)
{
key_t key;
int semid;
struct sembuf sbuf = {0,-1,IPC_NOWAIT};
union semun semopts;

if ((key = ftok(".",'s')) == -1)
    {
      perror("ftok error!\n");
      exit(1);
    }

if ((semid = semget(key,1,IPC_CREAT|0666)) == -1)
    {
      perror("semget error!\n");
      exit(1);
    }

semopts.val = MAX_RESOURCE;
if (semctl(semid,0,SETVAL,semopts) == -1)
    {
      perror("semctl error!\n");
      exit(1);
    }

while (1)
    {
      if (semop(semid,&sbuf,1) == -1)
    {
    perror("semop error!\n");
    exit(1);
    }
      sleep(3);
    }

exit(0);
}
    例10-16
#include <sys/types.h>
#include <linux/sem.h>

int main(void)
{
key_t key;
int semid,semval;
union semun semopts;

if ((key = ftok(".",'s')) == -1)
    {
      perror("semget error!\n");
      exit(1);
    }

if ((semid = semget(key,1,IPC_CREAT|0666)) == -1)
    {
      perror("semget error!\n");
      exit(1);
    }

while(1)
    {
      if ((semval = semctl(semid,0,GETVAL,0)) == -1)
    {
    perror("semctl error!\n");
    exit(1);
    }
      if (semval > 0)
    {
    printf("Still %d resources can be used\n",semval);
    }
      else
    {
    printf("No more resources can be used!\n");
    break;
    }

      sleep(3);
    }
exit(0);
}
    程序说明:
    首先在一个终端上编译并运行10-15,再在另外一个终端上编译运行10-16。观察10-16运行结果如下:
$ ./10-16
Still 4 resources can be used
Still 3 resources can be used
Still 2 resources can be used
Still 1 resources can be used
No more resources can be used!
    由运行结果可以看出,信号量可以实现锁的功能。