📄 linux环境进程间通信(四).htm
字号:
<TD class=code-outline><PRE class=displaycode>#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok (char*pathname, char proj);
</PRE></TD></TR></TBODY></TABLE><BR>
<P>它返回与路径pathname相对应的一个键值,具体用法请参考《Linux环境进程间通信(三):消息队列》。</P>
<P>2、 linux特有的ipc()调用:</P>
<P>int ipc(unsigned int call, int first, int second, int third, void
*ptr, long fifth);</P>
<P>参数call取不同值时,对应信号灯的三个系统调用: <BR>当call为SEMOP时,对应int semop(int semid,
struct sembuf *sops, unsigned nsops)调用; <BR>当call为SEMGET时,对应int
semget(key_t key, int nsems, int semflg)调用; <BR>当call为SEMCTL时,对应int
semctl(int semid,int semnum,int cmd,union semun arg)调用;
<BR>这些调用将在后面阐述。 </P>
<P>注:本人不主张采用系统调用ipc(),而更倾向于采用系统V或者POSIX进程间通信API。原因已在Linux环境进程间通信(三):消息队列中给出。</P>
<P>3、系统V信号灯API</P>
<P>系统V消息队列API只有三个,使用时需要包括几个头文件:</P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD class=code-outline><PRE class=displaycode>#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
</PRE></TD></TR></TBODY></TABLE><BR>
<P>1)int semget(key_t key, int nsems, int semflg)
<BR>参数key是一个键值,由ftok获得,唯一标识一个信号灯集,用法与msgget()中的key相同;参数nsems指定打开或者新创建的信号灯集中将包含信号灯的数目;semflg参数是一些标志位。参数key和semflg的取值,以及何时打开已有信号灯集或者创建一个新的信号灯集与msgget()中的对应部分相同,不再祥述。
<BR>该调用返回与健值key相对应的信号灯集描述字。 <BR>调用返回:成功返回信号灯集描述字,否则返回-1。
<BR>注:如果key所代表的信号灯已经存在,且semget指定了IPC_CREAT|IPC_EXCL标志,那么即使参数nsems与原来信号灯的数目不等,返回的也是EEXIST错误;如果semget只指定了IPC_CREAT标志,那么参数nsems必须与原来的值一致,在后面程序实例中还要进一步说明。
</P>
<P>2)int semop(int semid, struct sembuf *sops, unsigned nsops);
<BR>semid是信号灯集ID,sops指向数组的每一个sembuf结构都刻画一个在特定信号灯上的操作。nsops为sops指向数组的大小。
<BR>sembuf结构如下: </P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD class=code-outline><PRE class=displaycode>struct sembuf {
unsigned short sem_num; /* semaphore index in array */
short sem_op; /* semaphore operation */
short sem_flg; /* operation flags */
};
</PRE></TD></TR></TBODY></TABLE><BR>
<P>sem_num对应信号集中的信号灯,0对应第一个信号灯。sem_flg可取IPC_NOWAIT以及SEM_UNDO两个标志。如果设置了SEM_UNDO标志,那么在进程结束时,相应的操作将被取消,这是比较重要的一个标志位。如果设置了该标志位,那么在进程没有释放共享资源就退出时,内核将代为释放。如果为一个信号灯设置了该标志,内核都要分配一个sem_undo结构来记录它,为的是确保以后资源能够安全释放。事实上,如果进程退出了,那么它所占用就释放了,但信号灯值却没有改变,此时,信号灯值反映的已经不是资源占有的实际情况,在这种情况下,问题的解决就靠内核来完成。这有点像僵尸进程,进程虽然退出了,资源也都释放了,但内核进程表中仍然有它的记录,此时就需要父进程调用waitpid来解决问题了。
<BR>sem_op的值大于0,等于0以及小于0确定了对sem_num指定的信号灯进行的三种操作。具体请参考linux相应手册页。
<BR>这里需要强调的是semop同时操作多个信号灯,在实际应用中,对应多种资源的申请或释放。semop保证操作的原子性,这一点尤为重要。尤其对于多种资源的申请来说,要么一次性获得所有资源,要么放弃申请,要么在不占有任何资源情况下继续等待,这样,一方面避免了资源的浪费;另一方面,避免了进程之间由于申请共享资源造成死锁。
<BR>也许从实际含义上更好理解这些操作:信号灯的当前值记录相应资源目前可用数目;sem_op>0对应相应进程要释放sem_op数目的共享资源;sem_op=0可以用于对共享资源是否已用完的测试;sem_op<0相当于进程要申请-sem_op个共享资源。再联想操作的原子性,更不难理解该系统调用何时正常返回,何时睡眠等待。
<BR>调用返回:成功返回0,否则返回-1。 </P>
<P>3) int semctl(int semid,int semnum,int cmd,union semun arg)
<BR>该系统调用实现对信号灯的各种控制操作,参数semid指定信号灯集,参数cmd指定具体的操作类型;参数semnum指定对哪个信号灯操作,只对几个特殊的cmd操作有意义;arg用于设置或返回信号灯信息。
<BR>该系统调用详细信息请参见其手册页,这里只给出参数cmd所能指定的操作。 </P>
<TABLE width="100%" border=0>
<TBODY>
<TR>
<TD>IPC_STAT</TD>
<TD>获取信号灯信息,信息由arg.buf返回;</TD></TR>
<TR>
<TD>IPC_SET</TD>
<TD>设置信号灯信息,待设置信息保存在arg.buf中(在manpage中给出了可以设置哪些信息);</TD></TR>
<TR>
<TD>GETALL</TD>
<TD>返回所有信号灯的值,结果保存在arg.array中,参数sennum被忽略;</TD></TR>
<TR>
<TD>GETNCNT</TD>
<TD>返回等待semnum所代表信号灯的值增加的进程数,相当于目前有多少进程在等待semnum代表的信号灯所代表的共享资源;</TD></TR>
<TR>
<TD>GETPID</TD>
<TD>返回最后一个对semnum所代表信号灯执行semop操作的进程ID;</TD></TR>
<TR>
<TD>GETVAL</TD>
<TD>返回semnum所代表信号灯的值;</TD></TR>
<TR>
<TD>GETZCNT</TD>
<TD>返回等待semnum所代表信号灯的值变成0的进程数;</TD></TR>
<TR>
<TD>SETALL</TD>
<TD>通过arg.array更新所有信号灯的值;同时,更新与本信号集相关的semid_ds结构的sem_ctime成员;</TD></TR>
<TR>
<TD>SETVAL</TD>
<TD>设置semnum所代表信号灯的值为arg.val;</TD></TR></TBODY></TABLE>
<P>调用返回:调用失败返回-1,成功返回与cmd相关:</P>
<TABLE width="60%" border=1>
<TBODY>
<TR>
<TD>Cmd</TD>
<TD>return value</TD></TR>
<TR>
<TD>GETNCNT</TD>
<TD>Semncnt</TD></TR>
<TR>
<TD>GETPID</TD>
<TD>Sempid</TD></TR>
<TR>
<TD>GETVAL</TD>
<TD>Semval</TD></TR>
<TR>
<TD>GETZCNT</TD>
<TD>Semzcnt</TD></TR></TBODY></TABLE><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt=""
src="Linux环境进程间通信(四).files/blue_rule.gif"
width="100%"><BR><IMG height=6 alt=""
src="Linux环境进程间通信(四).files/c.gif" width=8
border=0></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD><IMG height=4 alt="" src="Linux环境进程间通信(四).files/c.gif"
width="100%"><BR>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt=""
src="Linux环境进程间通信(四).files/u_bold.gif" width=16
border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox
href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part4/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=N1019E><SPAN class=atitle>五、信号灯的限制</SPAN></A></P>
<P>1、
一次系统调用semop可同时操作的信号灯数目SEMOPM,semop中的参数nsops如果超过了这个数目,将返回E2BIG错误。SEMOPM的大小特定与系统,redhat
8.0为32。</P>
<P>2、 信号灯的最大数目:SEMVMX,当设置信号灯值超过这个限制时,会返回ERANGE错误。在redhat
8.0中该值为32767。</P>
<P>3、
系统范围内信号灯集的最大数目SEMMNI以及系统范围内信号灯的最大数目SEMMNS。超过这两个限制将返回ENOSPC错误。redhat
8.0中该值为32000。</P>
<P>4、 每个信号灯集中的最大信号灯数目SEMMSL,redhat 8.0中为250。
SEMOPM以及SEMVMX是使用semop调用时应该注意的;SEMMNI以及SEMMNS是调用semget时应该注意的。SEMVMX同时也是semctl调用应该注意的。</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt=""
src="Linux环境进程间通信(四).files/blue_rule.gif"
width="100%"><BR><IMG height=6 alt=""
src="Linux环境进程间通信(四).files/c.gif" width=8
border=0></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD><IMG height=4 alt="" src="Linux环境进程间通信(四).files/c.gif"
width="100%"><BR>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt=""
src="Linux环境进程间通信(四).files/u_bold.gif" width=16
border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox
href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part4/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=N101B0><SPAN class=atitle>六、竞争问题</SPAN></A></P>
<P>第一个创建信号灯的进程同时也初始化信号灯,这样,系统调用semget包含了两个步骤:创建信号灯;初始化信号灯。由此可能导致一种竞争状态:第一个创建信号灯的进程在初始化信号灯时,第二个进程又调用semget,并且发现信号灯已经存在,此时,第二个进程必须具有判断是否有进程正在对信号灯进行初始化的能力。在参考文献[1]中,给出了绕过这种竞争状态的方法:当semget创建一个新的信号灯时,信号灯结构semid_ds的sem_otime成员初始化后的值为0。因此,第二个进程在成功调用semget后,可再次以IPC_STAT命令调用semctl,等待sem_otime变为非0值,此时可判断该信号灯已经初始化完毕。下图描述了竞争状态产生及解决方法:</P><BR><IMG
height=193 alt="" src="Linux环境进程间通信(四).files/3.gif" width=613
border=0> <BR>
<P>实际上,这种解决方法也是基于这样一个假定:第一个创建信号灯的进程必须调用semop,这样sem_otime才能变为非零值。另外,因为第一个进程可能不调用semop,或者semop操作需要很长时间,第二个进程可能无限期等待下去,或者等待很长时间。</P><BR>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD><IMG height=1 alt=""
src="Linux环境进程间通信(四).files/blue_rule.gif"
width="100%"><BR><IMG height=6 alt=""
src="Linux环境进程间通信(四).files/c.gif" width=8
border=0></TD></TR></TBODY></TABLE>
<TABLE class=no-print cellSpacing=0 cellPadding=0 align=right>
<TBODY>
<TR align=right>
<TD><IMG height=4 alt="" src="Linux环境进程间通信(四).files/c.gif"
width="100%"><BR>
<TABLE cellSpacing=0 cellPadding=0 border=0>
<TBODY>
<TR>
<TD vAlign=center><IMG height=16 alt=""
src="Linux环境进程间通信(四).files/u_bold.gif" width=16
border=0><BR></TD>
<TD vAlign=top align=right><A class=fbox
href="http://www.ibm.com/developerworks/cn/linux/l-ipc/part4/index.html#main"><B>回页首</B></A></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR><BR>
<P><A name=N101CD><SPAN class=atitle>七、信号灯应用实例</SPAN></A></P>
<P>本实例有两个目的:1、获取各种信号灯信息;2、利用信号灯实现共享资源的申请和释放。并在程序中给出了详细注释。</P>
<TABLE cellSpacing=0 cellPadding=0 width="100%" border=0>
<TBODY>
<TR>
<TD class=code-outline><PRE class=displaycode>#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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -