System IPC 与Posix IPC(semaphore信号灯)

 

POSIX下IPC主要包括三种:

posix message queue

posix semaphores

posix shared memory

sysytem v IPC包括:

system v message queue

system v semaphores

system v shared memory

 

 

1.Semaphores

在POSIX IPC中,每个IPC对象是有名称的,而且名称是一个很重要的概念,posix ipc使用ipc的名称作为ipc的标识。mq_open  sem_open  shm_open三个函数的第一个参数就是这个名称,这个名称不一定是在文件系统中存在的名称。 要使用IPC对象,需要创建或者打开,这与文件操作类似,主要是使用mq_open、sem_open、shm_open 函数操作。在创建或者打开ipc对象时需要指定操作的mode,例如O_RONLY、O_WRONLY、O_RDWR、O_CREAT、O_EXCL 等,IPC对象是有一定权限的,与文件的权限类似.

 

System v ipc中有一个重要的类型是key_t,在msget、semget、shmget函数操作中都需要利用这个类型是参数。系统中对每个ipc对象都会有一个结构体来标识:

 

 

信号灯分为两种:
一种是简单信号量(Posix ),另一种是用于进程间通讯的信号量集(System);

一、简单信号量:
属于POSIX标准的信号量;
从信号量的命名来看,信号量又可分为命名信号量和匿名(未命名)信号量;
从信号量的值来看,信号量可分为二进制信号量和计数信号量;

1、匿名信号量和命名信号量:
  匿名信号量是在内存中分配内存、进行初始化并由系统API进行管理的,它可以在多个线程之间进行资源同步,也可以在多个进程之间进行资源同步,这主要是看在初始化的时候给pshared传递的参数值,为0,则在线程之间同步,非0,则在进程之间同步;
  命名信号量与匿名信号量不同,它一般只用于在进程之间进行资源同步,而且是使用文件全路径来对信号量进行命名的;命名信号量具有属主用户ID、组ID和保护模式等参数;命名信号量的名称是在文件系统的命名空间中定义的;
  匿名信号量的操作函数有:sem_init、sem_destroy、sem_wait(P操作(-1))、sem_trywait、sem_post(V操作(+1))、sem_getvalue;

   sem_init创建一个信号灯,并初始化其值为value.pshared决定了信号量能否在几个进程 间共享.由于目前Linux还没有实现进程间共享信号灯,所以这个值只能够取0.

   sem_destroy是用来删除信号灯的.sem_wait调用将阻塞进程,直到信号灯的值大于0.这个函数返回的时候自动的将信号灯的值的件一.

   sem_post和sem_wait相反,是将信号灯的内容加一同时发出信号唤醒等待的进程..sem_trywait和sem_wait相同,不过不阻塞的,当信号灯的值为0的时候返回EAGAIN,

   表示以后重试.sem_getvalue得到信号灯的值.

 

#include <semaphore.h>
#include <stdio.h>
#include < string.h>
#include <stdlib.h>


#if 0
int main( int argc, char *argv[]){
    sem_t sem;
     if(- 1==sem_init(&sem, 0, 1)){
        perror( " sem_init error\n ");
    }
     int value= 0;

     while( 1){
        sem_wait(&sem);
        sleep( 2);
        sem_post(&sem);
        sem_getvalue(&sem,&value);
        printf( " %d\n ",value);
        sem_destroy(&sem);
    }

     return  0;
}
#endif


#if 1
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include < string.h>
#include <pthread.h>
#include <semaphore.h>

sem_t bin_sem;
void *thread_function1( void *arg)
{
    printf( " thread_function1--------------sem_wait\n ");
    sem_wait(&bin_sem);
    printf( " sem_wait\n ");
     while ( 1)
    {
    }
}

void *thread_function2( void *arg)
{
    printf( " thread_function2--------------sem_post\n ");
    sem_post(&bin_sem);
    printf( " sem_post\n ");
     while ( 1)
    {
    }
}



int main()
{
     int res;
    pthread_t a_thread;
     void *thread_result;

    res = sem_init(&bin_sem,  00);
     if (res !=  0)
    {
        perror( " Semaphore initialization failed ");
    }
    printf( " sem_init\n ");
    res = pthread_create(&a_thread, NULL, thread_function1, NULL);
     if (res !=  0)
    {
        perror( " Thread creation failure ");
    }
    printf( " thread_function1\n ");
    sleep ( 5);
    printf( " sleep\n ");
    res = pthread_create(&a_thread, NULL, thread_function2, NULL);
     if (res !=  0)
    {
        perror( " Thread creation failure ");
    }
     while ( 1)
    {
    }
}

#endif

 

  命名信号量的操作函数有:sem_open、sem_close、sem_wait(阻塞P操作(-1)、sem_trywait、sem_post(唤醒V操作(+1))、sem_getvalue、sem_unlink(删除系统中的信号灯);

 

        1 #include <fcntl.h>
        2 #include <sys/stat.h>
        3 #include <semaphore.h>
        4 #include <errno.h>
        5  #define ABORT() {printf("File:%s,Line:%d",__FILE__,__LINE__);abort();}
        6  #define PATH "txt"
-       7  #if 1 
|-      8  int main(){
||      9     sem_t *sem;
||     10     sem = sem_open(PATH,O_CREAT|O_EXCL, 00666, 2);
||-    11      switch(errno){
|||    12          case EINVAL:
|||    13             ABORT();            
|||    14              break;
|||    15          case EEXIST: 
|||    16             perror( " EXIST ");
|||    17              break;
|||    18          default:
|||    19              break;
|||    20     }
||     21      int value;
||     23     sem_wait(sem);
||     24     sem_getvalue(sem,&value);
||     25     printf( " current value is %d\n ",value);
||     26     sem_close(sem);
||     27 
||     28     pause();                                                                                                                           
||     29     exit( 0);
||     30 
||     31      return  0;
||     32 }
|      33  #endif

 

意: 一般发行版的linux操作系统内核并不支持有名信号量的操作,需要在编译内核时将 其打开

 

 

2、二进制信号量和计数信号量:
  二进制信号量的值只有两个:0和1;这个时候信号量就相当于一个互斥锁;它表示可用资源数量只有1个,当一个调用者拥有并访问它的时候(值为0),其它调用者就必须阻塞并等待.知道拥有者释放这个信号量(值为1).
  计数信号量,从概念上来讲,它的值是一个非负整数,表示可用资源的数量;用于协调对共享资源的访问:在线程中操作共享资源的时候,当一个访问者申请使用一 个单位的资源时,信号量的值减1,表示剩余的可用资源的数量减少1;当这个访问者使用完这个共享资源并退还这个共享资源给系统的时候,信号量的值增1,表 示可用的共享资源数量多1;而申请资源和释放资源的操作是原子操作,即:信号量的值减1和增1操作是原子操作;也就是说,有一个资源申请动作,就必须对应 有一个资源释放动作,有一个信号量减1操作,就必须对应有一个信号量增1的动作;如果信号量的值为0,就表示没有资源可用,那么这个时候,如果有后续的调 用者来申请可用的共享资源,那么这个调用者就会被阻塞在这个信号量上,直到有一个调用者释放一个共享资源为止;
 
3、当信号量作为互斥锁使用时:
  信号量:相当于是代码周围的卫兵,当卫兵发现共享代码段正在被执行,则卫兵不让后续调用者前去执行;当共享代码段为空闲时,卫兵允许后续调用者去执行;
  互斥量:相当于一把多人共用的钥匙,谁拥有这把钥匙,谁就可以访问受保护的代码段;
  

 

 

 
二、信号量集:
它属于System V信号量,是一个集合;
常用于进程间的通讯;System V的IPC要求用于进程间通讯的信号量必须是一个集合;它是系统内核定义的一个数据结构;
信号量集的初始化操作由shmget函数完成;

 信号灯与其他进程间通信方式不大相同,它主要提供对进程间共享资源访问控制机制。相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志。除了用于访问控制外,还可用于进程同步。信号灯有以下两种类型:

  • 二值信号灯:最简单的信号灯形式,信号灯的值只能取0或1,类似于互斥锁。
    注:二值信号灯能够实现互斥锁的功能,但两者的关注内容不同。信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须由进程本身来解锁。
  • 计算信号灯:信号灯的值可以取任意非负值(当然受内核本身的约束)。

二、Linux信号灯

linux对信号灯的支持状况与消息队列一样,在red had 8.0发行版本中支持的是系统V的信号灯。因此,本文将主要介绍系统V信号灯及其相应API。在没有声明的情况下,以下讨论中指的都是系统V信号灯。

注意,通常所说的系统V信号灯指的是计数信号灯集。

三、信号灯与内核

1、系统V信号灯是随内核持续的,只有在内核重起或者显示删除一个信号灯集时,该信号灯集才会真正被删除。因此系统中记录信号灯的数据结构(struct ipc_ids sem_ids)位于内核中,系统中的所有信号灯都可以在结构sem_ids中找到访问入口。

2、下图说明了内核与信号灯是怎样建立起联系的:

其中:struct ipc_ids sem_ids是内核中记录信号灯的全局数据结构;描述一个具体的信号灯及其相关信息。


System IPC 与Posix IPC(semaphore信号灯)

其中,struct sem结构如下:

struct sem{ int semval;		// current value int sempid		// pid of last operation } 

从上图可以看出,全局数据结构struct ipc_ids sem_ids可以访问到struct kern_ipc_perm的第一个成员:struct kern_ipc_perm;而每个struct kern_ipc_perm能够与具体的信号灯对应起来是因为在该结构中,有一个key_t类型成员key,而key则唯一确定一个信号灯集;同时,结构 struct kern_ipc_perm的最后一个成员sem_nsems确定了该信号灯在信号灯集中的顺序,这样内核就能够记录每个信号灯的信息了。 kern_ipc_perm结构参见《Linux环境进程间通信(三):消息队列》。struct sem_array见附录1。

四、操作信号灯

对消息队列的操作无非有下面三种类型:

1、 打开或创建信号灯
与消息队列的创建及打开基本相同,不再详述。

2、 信号灯值操作
linux可以增加或减小信号灯的值,相应于对共享资源的释放和占有。具体参见后面的semop系统调用。

3、 获得或设置信号灯属性:
系统中的每一个信号灯集都对应一个struct sem_array结构,该结构记录了信号灯集的各种信息,存在于系统空间。为了设置、获得该信号灯集的各种信息及属性,在用户空间有一个重要的联合结构与之对应,即union semun。


System IPC 与Posix IPC(semaphore信号灯)

联合semun数据结构各成员意义参见附录2

信号灯API

1、文件名到键值

#include <sys/types.h> #include <sys/ipc.h> key_t ftok (char*pathname, char proj); 

它返回与路径pathname相对应的一个键值,具体用法请参考《Linux环境进程间通信(三):消息队列》。

2、 linux特有的ipc()调用:

int ipc(unsigned int call, int first, int second, int third, void *ptr, long fifth);

参数call取不同值时,对应信号灯的三个系统调用:
当call为SEMOP时,对应int semop(int semid, struct sembuf *sops, unsigned nsops)调用;
当call为SEMGET时,对应int semget(key_t key, int nsems, int semflg)调用;
当call为SEMCTL时,对应int semctl(int semid,int semnum,int cmd,union semun arg)调用;
这些调用将在后面阐述。

注:本人不主张采用系统调用ipc(),而更倾向于采用系统V或者POSIX进程间通信API。原因已在Linux环境进程间通信(三):消息队列中给出。

3、系统V信号灯API

系统V消息队列API只有三个,使用时需要包括几个头文件:

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

1)int semget(key_t key, int nsems, int semflg)
参数key是一个键值,由ftok获得,唯一标识一个信号灯集,用法与msgget()中的key相同;参数nsems指定打开或者新创建的信号灯集中将 包含信号灯的数目;semflg参数是一些标志位。参数key和semflg的取值,以及何时打开已有信号灯集或者创建一个新的信号灯集与 msgget()中的对应部分相同,不再祥述。
该调用返回与健值key相对应的信号灯集描述字。
调用返回:成功返回信号灯集描述字,否则返回-1。
注:如果key所代表的信号灯已经存在,且semget指定了IPC_CREAT|IPC_EXCL标志,那么即使参数nsems与原来信号灯的数目不 等,返回的也是EEXIST错误;如果semget只指定了IPC_CREAT标志,那么参数nsems必须与原来的值一致,在后面程序实例中还要进一步 说明。

2)int semop(int semid, struct sembuf *sops, unsigned nsops);
semid是信号灯集ID,sops指向数组的每一个sembuf结构都刻画一个在特定信号灯上的操作。nsops为sops指向数组的大小。
sembuf结构如下:

struct sembuf { 	unsigned short  	sem_num;		/* semaphore index in array */ 	short			sem_op;		/* semaphore operation */ 	short			sem_flg;		/* operation flags */ }; 

sem_num对应信号集中的信号灯,0对应第一个信号灯。sem_flg可取IPC_NOWAIT以及SEM_UNDO两个标志。如 果设置了SEM_UNDO标志,那么在进程结束时,相应的操作将被取消,这是比较重要的一个标志位。如果设置了该标志位,那么在进程没有释放共享资源就退 出时,内核将代为释放。如果为一个信号灯设置了该标志,内核都要分配一个sem_undo结构来记录它,为的是确保以后资源能够安全释放。事实上,如果进 程退出了,那么它所占用就释放了,但信号灯值却没有改变,此时,信号灯值反映的已经不是资源占有的实际情况,在这种情况下,问题的解决就靠内核来完成。这 有点像僵尸进程,进程虽然退出了,资源也都释放了,但内核进程表中仍然有它的记录,此时就需要父进程调用waitpid来解决问题了。
sem_op的值大于0,等于0以及小于0确定了对sem_num指定的信号灯进行的三种操作。具体请参考linux相应手册页。
这里需要强调的是semop同时操作多个信号灯,在实际应用中,对应多种资源的申请或释放。semop保证操作的原子性,这一点尤为重要。尤其对于多种资 源的申请来说,要么一次性获得所有资源,要么放弃申请,要么在不占有任何资源情况下继续等待,这样,一方面避免了资源的浪费;另一方面,避免了进程之间由 于申请共享资源造成死锁。
也许从实际含义上更好理解这些操作:信号灯的当前值记录相应资源目前可用数目;sem_op>0对应相应进程要释放sem_op数目的共享资 源;sem_op=0可以用于对共享资源是否已用完的测试;sem_op<0相当于进程要申请-sem_op个共享资源。再联想操作的原子性,更不 难理解该系统调用何时正常返回,何时睡眠等待。
调用返回:成功返回0,否则返回-1。

3) int semctl(int semid,int semnum,int cmd,union semun arg)
该系统调用实现对信号灯的各种控制操作,参数semid指定信号灯集,参数cmd指定具体的操作类型;参数semnum指定对哪个信号灯操作,只对几个特殊的cmd操作有意义;arg用于设置或返回信号灯信息。
该系统调用详细信息请参见其手册页,这里只给出参数cmd所能指定的操作。

IPC_STAT 获取信号灯信息,信息由arg.buf返回;
IPC_SET 设置信号灯信息,待设置信息保存在arg.buf中(在manpage中给出了可以设置哪些信息);
GETALL 返回所有信号灯的值,结果保存在arg.array中,参数sennum被忽略;
GETNCNT 返回等待semnum所代表信号灯的值增加的进程数,相当于目前有多少进程在等待semnum代表的信号灯所代表的共享资源;
GETPID 返回最后一个对semnum所代表信号灯执行semop操作的进程ID;
GETVAL 返回semnum所代表信号灯的值;
GETZCNT 返回等待semnum所代表信号灯的值变成0的进程数;
SETALL 通过arg.array更新所有信号灯的值;同时,更新与本信号集相关的semid_ds结构的sem_ctime成员;
SETVAL 设置semnum所代表信号灯的值为arg.val;

调用返回:调用失败返回-1,成功返回与cmd相关:

Cmd return value
GETNCNT Semncnt
GETPID Sempid
GETVAL Semval
GETZCNT Semzcnt

五、信号灯的限制

1、 一次系统调用semop可同时操作的信号灯数目SEMOPM,semop中的参数nsops如果超过了这个数目,将返回E2BIG错误。SEMOPM的大小特定与系统,redhat 8.0为32。

2、 信号灯的最大数目:SEMVMX,当设置信号灯值超过这个限制时,会返回ERANGE错误。在redhat 8.0中该值为32767。

3、 系统范围内信号灯集的最大数目SEMMNI以及系统范围内信号灯的最大数目SEMMNS。超过这两个限制将返回ENOSPC错误。redhat 8.0中该值为32000。

4、 每个信号灯集中的最大信号灯数目SEMMSL,redhat 8.0中为250。 SEMOPM以及SEMVMX是使用semop调用时应该注意的;SEMMNI以及SEMMNS是调用semget时应该注意的。SEMVMX同时也是semctl调用应该注意的。

六、竞争问题

第一个创建信号灯的进程同时也初始化信号灯,这样,系统调用semget包含了两个步骤:创建信号灯;初始化信号灯。由此可能导致一种 竞争状态:第一个创建信号灯的进程在初始化信号灯时,第二个进程又调用semget,并且发现信号灯已经存在,此时,第二个进程必须具有判断是否有进程正 在对信号灯进行初始化的能力。在参考文献[1]中,给出了绕过这种竞争状态的方法:当semget创建一个新的信号灯时,信号灯结构semid_ds的 sem_otime成员初始化后的值为0。因此,第二个进程在成功调用semget后,可再次以IPC_STAT命令调用semctl,等待 sem_otime变为非0值,此时可判断该信号灯已经初始化完毕。下图描述了竞争状态产生及解决方法:


System IPC 与Posix IPC(semaphore信号灯)

实际上,这种解决方法也是基于这样一个假定:第一个创建信号灯的进程必须调用semop,这样sem_otime才能变为非零值。另外,因为第一个进程可能不调用semop,或者semop操作需要很长时间,第二个进程可能无限期等待下去,或者等待很长时间。

七、信号灯应用实例

本实例有两个目的:1、获取各种信号灯信息;2、利用信号灯实现共享资源的申请和释放。并在程序中给出了详细注释。


#include <linux/sem.h>
#include <stdio.h>
#include <errno.h>
#define SEM_PATH "/unix/my_sem"
#define max_tries 3 
int semid;
main()
{
int flag1,flag2,key,i,init_ok,tmperrno;
struct semid_ds sem_info;
struct seminfo sem_info2;
union semun arg;        // union semun: 请参考附录2
struct sembuf askfor_res, free_res;
flag1=IPC_CREAT|IPC_EXCL| 00666;
flag2=IPC_CREAT| 00666;
key=ftok(SEM_PATH, ' a ');
// error handling for ftok here;
init_ok= 0;
semid=semget(key, 1,flag1);
// create a semaphore set that only includes one semphore.
if(semid< 0)
{
  tmperrno=errno;
  perror( " semget ");
if(tmperrno==EEXIST)
// errno is undefined after a successful library call( including perror call) 
// so it is saved  in tmperrno.
    {
    semid=semget(key, 1,flag2);
// flag2 只包含了IPC_CREAT标志, 参数nsems(这里为1)必须与原来的信号灯数目一致
    arg.buf=&sem_info;
     for(i= 0; i<max_tries; i++)
    {
       if(semctl(semid,  0, IPC_STAT, arg)==- 1)
      {  perror( " semctl error "); i=max_tries;}
       else
      { 
         if(arg.buf->sem_otime!= 0){ i=max_tries;  init_ok= 1;}
         else   sleep( 1);  
      }
    }
     if(!init_ok)
   //  do some initializing, here we assume that the first process that creates the sem
  
//   will finish initialize the sem and run semop in max_tries*1 seconds. else it will  
  
//  not run semop any more.
    {
      arg.val= 1;
       if(semctl(semid, 0,SETVAL,arg)==- 1) perror( " semctl setval error ");
    } 
  }
   else
  {perror( " semget error, process exit ");  exit();  }
}
else  // semid>=0; do some initializing   
{
  arg.val= 1;
   if(semctl(semid, 0,SETVAL,arg)==- 1)
    perror( " semctl setval error ");
}
// get some information about the semaphore and the limit of semaphore in redhat8.0
  arg.buf=&sem_info;
   if(semctl(semid,  0, IPC_STAT, arg)==- 1)
    perror( " semctl IPC STAT ");    
  printf( " owner's uid is %d\n ",   arg.buf->sem_perm.uid);
  printf( " owner's gid is %d\n ",   arg.buf->sem_perm.gid);
  printf( " creater's uid is %d\n ",   arg.buf->sem_perm.cuid);
  printf( " creater's gid is %d\n ",   arg.buf->sem_perm.cgid);
  arg.__buf=&sem_info2;
   if(semctl(semid, 0,IPC_INFO,arg)==- 1)
    perror( " semctl IPC_INFO ");
  printf( " the number of entries in semaphore map is %d \n ",  arg.__buf->semmap);
  printf( " max number of semaphore identifiers is %d \n ",    arg.__buf->semmni);
  printf( " mas number of semaphores in system is %d \n ",   arg.__buf->semmns);
  printf( " the number of undo structures system wide is %d \n ",  arg.__buf->semmnu);
  printf( " max number of semaphores per semid is %d \n ",   arg.__buf->semmsl);
  printf( " max number of ops per semop call is %d \n ",  arg.__buf->semopm);
  printf( " max number of undo entries per process is %d \n ",  arg.__buf->semume);
  printf( " the sizeof of struct sem_undo is %d \n ",  arg.__buf->semusz);
  printf( " the maximum semaphore value is %d \n ",  arg.__buf->semvmx);
  
// now ask for available resource:  
  askfor_res.sem_num= 0;
  askfor_res.sem_op=- 1;
  askfor_res.sem_flg=SEM_UNDO;    
    
     if(semop(semid,&askfor_res, 1)==- 1) // ask for resource
      perror( " semop error ");
  
  sleep( 3); 
   // do some handling on the sharing resource here, just sleep on it 3 seconds
  printf( " now free the resource\n ");  
  
// now free resource  
  free_res.sem_num= 0;
  free_res.sem_op= 1;
  free_res.sem_flg=SEM_UNDO;
   if(semop(semid,&free_res, 1)==- 1) // free the resource.
     if(errno==EIDRM)
      printf( " the semaphore set was removed\n ");
// you can comment out the codes below to compile a different version:      
   if(semctl(semid,  0, IPC_RMID)==- 1)
    perror( " semctl IPC_RMID ");
   else printf( " remove sem ok\n ");
}

注:读者可以尝试一下注释掉初始化步骤,进程在运行时会出现何种情况(进程在申请资源时会睡眠),同时可以像程序结尾给出的注释那样,把该程序编译成两个不同版本。下面是本程序的运行结果(操作系统redhat8.0):

owner ' s uid is 0
owner ' s gid is 0
creater ' s uid is 0
creater ' s gid is 0
the number of entries  in semaphore map  is  32000 
max number of semaphore identifiers  is  128 
mas number of semaphores  in system  is  32000 
the number of undo structures system wide  is  32000 
max number of semaphores per semid  is  250 
max number of ops per semop call  is  32 
max number of undo entries per process  is  32 
the  sizeof of  struct sem_undo  is  20 
the maximum semaphore value  is  32767 
now free the resource
remove sem ok

Summary:信号灯与其它进程间通信方式有所不同,它主要用于进程间同步。通常所说的系统V信号灯实际上是一个信号灯的集合,可用 于多种共享资源的进程间同步。每个信号灯都有一个值,可以用来表示当前该信号灯代表的共享资源可用(available)数量,如果一个进程要申请共享资 源,那么就从信号灯值中减去要申请的数目,如果当前没有足够的可用资源,进程可以睡眠等待,也可以立即返回。当进程要申请多种共享资源时,linux可以 保证操作的原子性,即要么申请到所有的共享资源,要么放弃所有资源,这样能够保证多个进程不会造成互锁。Linux对信号灯有各种各样的限制,程序中给出 了输出结果。另外,如果读者想对信号灯作进一步的理解,建议阅读sem.h源代码,该文件不长,但给出了信号灯相关的重要数据结构。

附录1: struct sem_array如下:

/*系统中的每个信号灯集对应一个sem_array 结构 */ struct sem_array {   struct kern_ipc_perm  sem_perm;    /* permissions .. see ipc.h */   time_t      sem_otime;      /* last semop time */   time_t      sem_ctime;      /* last change time */   struct sem    *sem_base;      /* ptr to first semaphore in array */   struct sem_queue  *sem_pending;    /* pending operations to be processed */   struct sem_queue  **sem_pending_last;   /* last pending operation */   struct sem_undo    *undo;      /* undo requests on this array */   unsigned long    sem_nsems;    /* no. of semaphores in array */ }; 

其中,sem_queue结构如下:

/* 系统中每个因为信号灯而睡眠的进程,都对应一个sem_queue结构*/  struct sem_queue {   struct sem_queue *  next;     /* next entry in the queue */   struct sem_queue **  prev;    /* previous entry in the queue, *(q->prev) == q */   struct task_struct*  sleeper;   /* this process */   struct sem_undo *  undo;     /* undo structure */   int   pid;             /* process id of requesting process */   int   status;           /* completion status of operation */   struct sem_array *  sma;       /* semaphore array for operations */   int  id;               /* internal sem id */   struct sembuf *  sops;       /* array of pending operations */   int  nsops;             /* number of operations */   int  alter;             /* operation will alter semaphore */ }; 

附录2:union semun是系统调用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 */   //test!! 	void *__pad; }; struct  seminfo { 	int semmap; 	int semmni; 	int semmns; 	int semmnu; 	int semmsl; 	int semopm; 	int semume; 	int semusz; 	int semvmx; 	int semaem; }; 
 
/*
 * SemServer.c
 *
 *  Created on: Sep 11, 2013
 *      Author: zsf
 
*/

#include<sys/types.h>
#include<linux/sem.h>
#include<stdlib.h>

#define MAX_RESOURCE 5
int main( void)

{
    key_t key;
     int semid;
     struct sembuf sbuf = {  0, - 1, IPC_NOWAIT };
    union semun semopts;
     // 创建SEM
     if ((key = ftok( " . "' s ')) == - 1) {
        perror( " ftok error!\n ");
        exit(- 1);
    }
     // 根据key_t 来获取信号集,信号集中有1个信号灯
     if ((semid = semget(key,  1, IPC_CREAT |  0666)) == - 1) {
        perror( " semget error!\n ");
        exit( 1);
    }
     // 从信号集中取得操作第0个信号灯或是根据信号集(simid)中取得信号灯
    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);
}  
/*
 * SemClient.c
 *
 *  Created on: Sep 11, 2013
 *      Author: zsf
 
*/

#include<sys/types.h>
#include<linux/sem.h>
#include<stdlib.h>
#include<stdio.h>
int main( void) {
    key_t key;
     int semid, semval;
    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);
    }
     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);
}

 System IPC 与Posix IPC(semaphore信号灯)

 

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

#define PATH "./"


#define ABORT() {printf("File:%s,Line:%d",__FILE__,__LINE__);abort();}



int main(int argc,char *argv[]){
    key_t key;
    int proj_id;

    key = ftok(PATH,'a');
    if(key ==-1){
        ABORT();
        perror("ftok error");
    }

    int sem_id;
    sem_id = semget(key,1,IPC_CREAT|00666);
    if(sem_id ==-1){
        ABORT();
        perror("semget error");
    }
    union semun va;
    va.val = 5;

    struct sembuf semout,semin;
    semout.sem_flg = SEM_UNDO;
    semout.sem_num = 0;
    semout.sem_op = -1;

    semin.sem_op = 1;
    semin.sem_flg = SEM_UNDO;
    semin.sem_num = 0;

    semctl(sem_id,0,SETVAL,va);
    while(1){
        semop(sem_id,&semout,1);
        
        int value = semctl(sem_id,0,GETVAL);
        printf("current value is %d\n",value);
        if(!value){
            break;
        }
    }
}

 

对于POSIX信号量,你可以有命名的信号量,例如,信号量有一个文件关联它们, 对于最后三个函数,被用来创建,关闭和删除这样一个命名的信号量。
而sem_init()和sem_destroy()仅仅供非命名信号量使用。
他们是有关信号量的两组程序设计接口函数。POSIX信号量来源于POSIX技术规范的实时 扩展方案(POSIX Realtime Extension),常用于线程;system v信号量,常用于进程的同步。
这两者非常相近,但它们使用的函数调用各不相同。前一种的头文件为semaphore.h,函数调用为sem_init(),sem_wait(),sem_post(),sem_destory()等等。后一种头文件为<sys/sem.h>,函数调用为semctl(),semget(),semop()等函数。 更详细地请看 man sem_overview
总结:
System V的信号量一般用于进程同步, 且是内核持续的, api为
semget
semctl
semop
Posix的有名信号量一般用于进程同步, 有名信号量是内核持续的. 有名信号量的api为
sem_open
sem_close
sem_unlink

Posix的无名信号量一般用于线程同步, 无名信号量是进程持续的, 无名信号量的api为

sem_init

sem_destroy

 

 

 

 注意:

基于内存的信号灯是由sem_init初始化的。sem参数指向必须由应用程序分配的sem_t变量。如果shared为0,那么待初始化的信号灯是在同 一进程的各个线程共享的,否则该信号灯是在进程间共享的。当shared为零时,该信号灯必须存放在即将使用它的所有进程都能访问的某种类型的共享内存 中。跟sem_open一样,value参数是该信号灯的初始值。
   使用完一个基于内存的信号灯后,我们调用sem_destroy关闭它。
除了sem_open和sem_close外,其它的poisx有名信号灯函数都可以用于基于内存的信号灯。

注意:posix基于内存的信号灯和posix有名信号灯有一些区别,我们必须注意到这些。
1.sem_open不需要类型与shared的参数,有名信号灯总是可以在不同进程间共享的。
2.sem_init不使用任何类似于O_CREAT标志的东西,也就是说,sem_init总是初始化信号灯的值。因此,对于一个给定的信号灯,我们必须小心保证只调用一次sem_init。
3.sem_open返回一个指向某个sem_t变量的指针,该变量由函数本身分配并初始化。但sem_init的第一个参数是一个指向某个sem_t变量的指针,该变量由调用者分配,然后由sem_init函数初始化。
4.posix有名信号灯是通过内核持续的,一个进程创建一个信号灯,另外的进程可以通过该信号灯的外部名(创建信号灯使用的文件名)来访问它。 posix基于内存的信号灯的持续性却是不定的,如果基于内存的信号灯是由单个进程内的各个线程共享的,那么该信号灯就是随进程持续的,当该进程终止时它 也会消失。如果某个基于内存的信号灯是在不同进程间同步的,该信号灯必须存放在共享内存区中,这要只要该共享内存区存在,该信号灯就存在。
5.基于内存的信号灯应用于线程很麻烦(待会你会知道为什么),而有名信号灯却很方便,基于内存的信号灯比较适合应用于一个进程的多个线程。

 

void print(pid_t);
sem_t *sem;  /* 定义Posix有名信号灯 */
int val;  /* 定义信号灯当前值 */

int main( int argc, char *argv[])
{
int n= 0;

sem=sem_open(argv[ 1],O_CREAT, 0644, 3);  /* 打开一个信号灯 */
sem_getvalue(sem,&val);  /* 查看信号灯的值 */
printf(“The value have %d\n”,val);

while(n++循环创建5个子进程,使它们同步运行*/
{
if(fork()== 0
{
       sem_wait(sem);  /* 申请信号灯 */
       print(getpid());  /* 调用共享代码段 */
       sleep( 1); 
       sem_post(sem);  /* 释放信号灯 */
       printf(“I’m finished,my pid  is %d\n”,getpid());
        return  0
}
wait();  /* 等待所有子进程结束 */
return  0;
}

void print(pid_t pid)
{
printf(“I  get it,my pid  is %d\n”,pid);
sem_getvalue(sem,&val);
printf(“Now the value have %d\n”,val);
}

 

问题在于sem信号灯不在共享内存区中。fork出来的子进程通常不共享父进程的内存空间。子进程是在父进程内存空间的拷贝上启动的,它跟共享内存不是一回事。

 

你可能感兴趣的:(Semaphore)