Linux信号量通信高文宇gwy@gdcc.edu.cnContents•纪录锁•IPC(Inter-ProcessCommunication)•信号量(Semaphore)纪录锁•进程间进行协作的手段•对文件进行锁定•fcntl高级IPC机制•UNIX提供了一系列高级的进程间通信机制,这些IPC机制的存在使得UNIX在进程通信领域手段相当丰富,也使得程序员在开发一个由多个进程协作的任务组成的系统时,可以采用多种多样的方法。我们将讨论的这些高级IPC机制可分为以下三类:•1.消息传递•2.信号量•3.共享存储IPC机制关键值•所有三种IPC机制的编程界面设计得尽可能的相似,表明了它们在内核中实现的相似性。其中最重要的共性就是IPC机制关键值。这里的关键值是一些数字,用来在UNIX系统中标识一个IPC对象,使其能在多个进程间共享,这与用文件名来标识文件是一样的。被标识的对象可以是一个消息队列、一个信号量集合或者一个共享的内存段。关键值的实际数据结构类型是由与实现相关的类型key_t决定的,它在系统头文件sys/types.h中定义。IPCget操作•程序使用关键值来创建一个IPC对象或取得对一个已有对象的访问权,这两种操作都是通过IPCget操作来实现的,返回值是一个名为IPC标识符的整数。这个标识符在调用其他IPC函数时使用。get操作与文件的open操作相似,IPC机制标识符相当于一个文件描述符。与文件描述符不同的是,IPC机制标识符是独一无二的。不同进程对同一个IPC对象使用相同的标识符。•下面的语句使用msgget调用创建了一个新的消息队列。•mqid=msgget((key_t)0100,0644|IPC_CREAT|IPC_EXCL);•其中msgget的第一个参数是消息队列关键值。如果调用成功,函数将返回一个非负值给mqid,该返回值就是消息队列标识符。类似的,与信号量和共享存储相应的调用分别是semget与shmget调用。其他IPC操作•对于IPC机制还有其他两种操作。首先是控制操作,它用于获得状态信息或者设置控制值。实际执行这些功能的函数是msgctl,semctl,和shmctl。其次是一些用于完成特殊任务的操作,统称为IPC操作。对于每种IPC机制,都有一系列这样的操作。例如,对于消息队列有两种操作:msgsnd将一条消息放入消息队列,而msgrcv从队列中取出一条消息。状态数据结构•一个IPC对象被创建后,系统就会建立一个IPC机制状态数据结构,用于存放与该队向有关的管理信息。三种IPC机制消息队列、信号量和共享存储各有一种状态结构,每种结构都包含只与特定IPC机制有关的信息,而三种状态结构又都包含一个共同的权限结构。这个权限结构即ipc_perm,它包含如下成员:•uid_tcuid;/*user-idofcreatorofIPCcbject*/•gid_tcgid;/*group-idofcreator*/•uid_tuid;/*effectiveuser-id*/•gid_tgid/*effectivegroup-ic*/•mode_tumode;/*permissions*/•该结构决定了用户能否读(即取得与该对象有关的信息)或写(即操作该对象)一个IPC对象。这里的权限结构与文件的权限完全一样。当umode为0644时表示所有者可以读写相应的对象,而其他用户只能对它进行读取。注意,这里有效的用户id和组id(包含在成员uid与gid中)与umode一起决定了存储权限。执行权限在这里没有意义。超级用户在这里拥有全部的权限。与其他UNIX结构不同的是,用户的umask值对一个IPC机制创建无任何影响。信号量semget•#includesys/sem..h•intsemget(key_tkey,intnsems,intpermflags)•semget调用中的参数nsems给出了信号量集合中的信号量个数,这说明了一个很重要的问题,即在UNIX中信号量的操作是针对一个信号量集合,而不是单个信号量的,如图显示了一个信号量集合,按照C语言的用法,信号量集合中元素的索引标识从0到nsems-1。这个特性也使得其它信号量函数的接口变得很复杂。•semget调用与open或creat执行相似的操作。参数key实质上只是一个数,用来在系统中标识一个信号量。如果调用成功,即一个新的信号量被创建或者取得对一个已有信号量的访问权,则semget将返回一个非负整数,称为信号量标识符。semid索引1索引2索引3索引0Semval=2Semval=2Semval=2Semval=2nsems=4Semget的参数—key_tkey•IPC机制关键值•所有三种IPC机制的编程界面设计得尽可能的相似,表明了它们在内核中实现的相似性。其中最重要的共性就是IPC机制关键值。这里的关键值是一些数字,用来在UNIX系统中标识一个IPC对象,使其能在多个进程间共享,这与用文件名来标识文件是一样的。被标识的对象可以是一个消息队列、一个信号量集合或者一个共享的内存段。关键值的实际数据结构类型是由与实现相关的类型key_t决定的,它在系统头文件sys/types.h中定义。Semget的参数—intnsems•semget调用中的参数nsems给出了信号量集合中的信号量个数,这说明了一个很重要的问题,即在UNIX中信号量的操作是针对一个信号量集合,而不是单个信号量的。如图显示了一个信号量集合。我们将看到,这个特性也使得其它信号量函数的接口变得很复杂。Semget的参数—intpermflags•Permflags参数决定了msgget将实现的操作,有两个常值可供选择,它们都在头文件sys/ipc.h中定义,这两个值可以单独使用,也可以用“或”位运算符连接起来使用:•IPC_CREAT它告诉msgget当key所代表的消息列队不存在时创建一个新的队列。如果继续上面的文件操作类比,这个标志使得msgget实现的功能与creat相似,当然在队列已存在时不会出现覆盖的情况。如果IPC_CREAT标志没有设置,则当key所对应的队列已存在时,msgget将返回这个消息队列的标识符。•IPC_EXCL如果它和IPC_CREAT都设置了,则调用只试图创建一个新的队列,即当key所对应的队列已存在时,msgget调用将失败并返回-1,同时错误指示变量errno将包含值EEXIT.•当创建一个新的消息队列时,permflags的低9位将用于为新队列设置权限值,这也与文件模式一样。它们将存储在与队列一起被创建的ipc_perm结构中。•Mqid=msgget((key_t)0100,0644|IPC_CREAT|IPC_EXCL);•这个调用试图为关键值(key_t)100创建(并且仅用于创建)一个消息队列。如果调用成功,则新队将具有权限值0644。它与文件权限值的含义一样,表示队列的创建者可以向队列发送或读取消息,而同组成员和其他人只能从队列中读取消息。必要的时候msgctl还可用来改变队列的权限值和所有者。semctl系统调用•#includewey/sem.h•intsemctl(intsemid,intsem_num,intcommand,unionsemunctl_arg);•从申明可以看出,semctl函数要比msgctl复杂。semid参数标识一个有效的信号量标识符,即semget的返回值。command给出要完成何种功能。这些信号量集合的功能。所有的可用功能都在表8.1中列出。•Sem_num参数与semctl中完成第二类的功能的选项一起使用,以确定集合中一个特定的信号量。ctl_arg参数是一个联合体,定义如下:•Unionsemun{•intval;•structsemid_ds*buf;•unsignedshort*array;};•联合体每个成员都有各自不同的类型,分别对应三种不同的semctl功能。例如,如果semval是SETVAL.则使用的将是ctl_arg.val.•semctl中的一个重要用途是为信号量赋初值,因为进程无法直接对信号量的值进行修改。Semctl功能表标准的IPC函数(注意在头文件sys/sem.h中包含semid_ds结构的定义)IPC_STAT把状态信息放入ctl_arg.stat中IPC_SET用ctl_arg.stat中的值设置所有权/许可权IPC_RMID从系统中删除信号量集合单信号量操作(下面这些宏与sem_num指定的信号量合semctl返回值相关)GETVAL返回信号量的值(也就是semval)SETVAL把信号量的值写入ctl_arg.val中GETPID返回sempid值GETNCNT返回semncnt(参考上面内容)GETZCNT返回semzcnt(参考上面内容)全信号量操作GETALL把所有信号量的semvals值写入ctl_arg.arraySETALL用ctl_arg.array中的值设置所有信号量的semvals执行信号量操作—semop调用•#includesys/sem.h•intsemop(intsemid,structsembuf*op_array,size_tnum_ops);•semid是信号量集合标识符它可能是从前一次的semgrt调用中获得的。Op_array参数是一个sembuf结构的数组,在sys/sem.h中定义。Num_ops参数是sembuf结构数组中的一员。每个sembuf结构描述了一个对信号量的操作。•在这里,关键之处还在于操作是针对一个信号量集合的,semop函数使得一组操作自动执行。这意味着如果当中的某个操作不能完成,则整个操作无法完成。如果没有专门设置,进程将阻塞,直至它能一次完成所有操作。•sembuf结构包含下列成员;•unsignedshortsen_num;//存放集合中某一信号量的索引•shorsem_op;//取值为有符号整数,给定了semop函数的功能•shortsem_flg;Sem_op三种类型的操作•情况1;sem_op取负值•其基本思想为,semop函数首先测试与信号量sem_num有关的semval值。如果semval值足够大,则立即执行相减操作否则进程等待直至semval值变为足够大。不过,如果IPC_NOWAIT选项在sem_flg中设置了,sem_op将立即返回—1,并将errno置为EAGAIN.•情况2:sem_op取正值•这种情况与前面的v()操作相应。Sem_op的值只是简单地加到当前地semval上,而正在等待这个新值地进程将被唤醒。•情况3:sem_op值为零•在这种情况下,sem_op将等待直至信号量地变为0,但是不会改变semval地值。如果在sem_flg中设置了IPC_NOWAIT选项,则当semval值不为零时,semop将出错,并立即返回。信号量初始化•P/V操作的实现//pv.h头文件#includesys/types.h#includesys/ipc.h#includesys/sem.h#includeerrno.h#defineSEMPERM0600#defineTRUE1#defineFALSE0typedefunion_semun{intval;structsemid_ds*buf;ushort*array;}semun;//initsem.c对信号量赋初值,初值固定为1#includepv.hintinitsem(key_tsemkey){intstatus=0,semid;if((semid=semget(semkey,1,SEMPERM|IPC_CREAT|IPC_EXCL))==-1){if(errno==EEXIST)semid=semget(semkey,1,0);}else{semunarg;arg.val=1;//信号量的初值status=semctl(semid,0,SETVAL,arg);}if(se