linux进程和线程教程,Linux 进程和线程编程

使用C语言创建管道要比在shell下使用管道复杂一些。如果要使用C语言创建一个简单的管道,可以使用系统调用pipe()。它接受一个参数,也就是一

个包括两个整数的数组。如果系统调用成功,此数组将包括管道使用的两个文件描述符。创建一个管道之后,一般情况下进程将产生一个新的进程。

可以通过打开两个管道来创建一个双向的管道。但需要在子进程中正确地设置文件描述必须在系统调用fork()中调用pipe(),否则子进程将不会继承文

件描述符。当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。因为管道存在于系统内核之中,所以任何不在创建管道的进程的祖先进程之中的

进程都将无法寻址它。而在命名管道中却不是这样。

[目录]

pipe()

系统调用:pipe();

原型:intpipe(intfd[2]);

返回值:如果系统调用成功,返回0

如果系统调用失败返回-1:errno=EMFILE(没有空闲的文件描述符)

EMFILE(系统文件表已满)

EFAULT(fd数组无效)

注意fd[0]用于读取管道,fd[1]用于写入管道。

#include

#include

#include

main()

{

intfd[2];

pipe(fd);

..

}

一旦创建了管道,我们就可以创建一个新的子进程:

#include

#include

#include

main()

{

intfd[2];

pid_t childpid;

pipe(fd);

if((childpid=fork())==-1)

{

perror("fork");

exit(1);

}..

}

如果父进程希望从子进程中读取数据,那么它应该关闭fd1,同时子进程关闭fd0。反之,如果父进程希望向子进程中发送数据,那么它应该关闭fd0,同时

子进程关闭fd1。因为文件描述符是在父进程和子进程之间共享,所以我们要及时地关闭不需要的管道的那一端。单从技术的角度来说,如果管道的一端没有正确

地关闭的话,你将无法得到一个EOF。

#include

#include

#include

main()

{

intfd[2];

pid_t childpid;

pipe(fd);

if((childpid=fork())==-1)

{

perror("fork");

exit(1);

}

if(childpid==0)

{

close(fd[0]);

}

else

{

close(fd[1]);

}..

}

正如前面提到的,一但创建了管道之后,管道所使用的文件描述符就和正常文件的文件描述符一样了。

#include

#include

#include

intmain(void)

{

intfd[2],nbytes;

pid_tchildpid;

charstring[]="Hello,world!\n";

charreadbuffer[80];

pipe(fd);

if((childpid=fork())==-1)

{

perror("fork");

exit(1);

}

if(childpid==0)

{

close(fd[0]);

write(fd[1],string,strlen(string));

exit(0);

}

else

{

close(fd[1]);

nbytes=read(fd[0],readbuffer,sizeof(readbuffer));

printf("Receivedstring:%s",readbuffer);

}

return(0);

}

一般情况下,子进程中的文件描述符将会复制到标准的输入和输出中。这样子进程可以使用exec()执行另一个程序,此程序继承了标准的数据流。

[目录]

dup()

系统调用:dup();

原型:intdup(intoldfd);

返回:如果系统调用成功,返回新的文件描述符

如果系统调用失败,返回-1:errno=EBADF(oldfd不是有效的文件描述符)

EBADF(newfd超出范围)

EMFILE(进程的文件描述符太多)

注意旧文件描述符oldfd没有关闭。虽然旧文件描述符和新创建的文件描述符可以交换使用,但一般情况下需要首先关闭一个。系统调用dup()使用的是号码最小的空闲的文件描述符。

再看下面的程序:

..

childpid=fork();

if(childpid==0)

{

close(0);

dup(fd[0]);

execlp("sort","sort",NULL);

.

}

因为文件描述符0(stdin)被关闭,所以dup()把管道的输入描述符复制到它的标准输入中。这样我们可以调用execlp(),使用sort程序覆

盖子进程的正文段。因为新创建的程序从它的父进程中继承了标准输入/输出流,所以它实际上继承了管道的输入端作为它的标准输入端。现在,最初的父进程送往

管道的任何数据都将会直接送往sort函数。

[目录]

dup2()

系统调用:dup2();

原型:intdup2(intoldfd,intnewfd);

返回值:如果调用成功,返回新的文件描述符

如果调用失败,返回-1:errno=EBADF(oldfd不是有效的文件描述符)

EBADF(newfd超出范围)

EMFILE(进程的文件描述符太多)

注意dup2()将关闭旧文件描述符。

使用此系统调用,可以将close操作和文件描述符复制操作集成到一个系统调用中。另外,此系统调用保证了操作的自动进行,也就是说操作不能被其他的信号

中断。这个操作将会在返回系统内核之前完成。如果使用前一个系统调用dup(),程序员不得不在此之前执行一个close()操作。请看下面的程序:

..

childpid=fork();

if(childpid==0)

{

dup2(0,fd[0]);

execlp("sort","sort",NULL);

..

}

[目录]

popen()和pclose()

如果你认为上面创建和使用管道的方法过于繁琐的话,你也可以使用下面的简单的方法:

库函数:popen()和pclose();

原型:FILE*popen(char*command,char*type);

返回值:如果成功,返回一个新的文件流。

如果无法创建进程或者管道,返回NULL。

此标准的库函数通过在系统内部调用pipe()来创建一个半双工的管道,然后它创建一个子进程,启动shell,最后在shell上执行command参

数中的命令。管道中数据流的方向是由第二个参数type控制的。此参数可以是r或者w,分别代表读或写。但不能同时为读和写。在Linux系统下,管道将

会以参数type中第一个字符代表的方式打开。所以,如果你在参数type中写入rw,管道将会以读的方式打开。

虽然此库函数的用法很简单,但也有一些不利的地方。例如它失去了使用系统调用pipe()时可以有的对系统的控制。尽管这样,因为可以直接地使用

shell命令,所以shell中的一些通配符和其他的一些扩展符号都可以在command参数中使用。

使用popen()创建的管道必须使用pclose()关闭。其实,popen/pclose和标准文件输入/输出流中的fopen()/fclose()十分相似。

库函数:pclose();

原型:intpclose(FILE*stream);

返回值:返回系统调用wait4()的状态。

如果stream无效,或者系统调用wait4()失败,则返回-1。

注意此库函数等待管道进程运行结束,然后关闭文件流。库函数pclose()在使用popen()创建的进程上执行wait4()函数。当它返回时,它将破坏管道和文件系统。

在下面的例子中,用sort命令打开了一个管道,然后对一个字符数组排序:

#include

#defineMAXSTRS5

intmain(void)

{

intcntr;

FILE*pipe_fp;

char*strings[MAXSTRS]={"echo","bravo","alpha",

"charlie","delta"};

if((pipe_fp=popen("sort","w"))==NULL)

{

perror("popen");

exit(1);

}

for(cntr=0;cntr

fputs(strings[cntr],pipe_fp);

fputc('\n',pipe_fp);

}

pclose(pipe_fp);

return(0);

}

因为popen()使用shell执行命令,所以所有的shell扩展符和通配符都可以使用。此外,它还可以和popen()一起使用重定向和输出管道函数。再看下面的例子:

popen("ls~scottb","r");

popen("sort>/tmp/foo","w");

popen("sort|uniq|more","w");

下面的程序是另一个使用popen()的例子,它打开两个管道(一个用于ls命令,另一个用于

sort命令):

#include

intmain(void)

{

FILE*pipein_fp,*pipeout_fp;

charreadbuf[80];

if((pipein_fp=popen("ls","r"))==NULL)

{

perror("popen");

exit(1);

}

if((pipeout_fp=popen("sort","w"))==NULL)

{

perror("popen");

exit(1);

}

while(fgets(readbuf,80,pipein_fp))

fputs(readbuf,pipeout_fp);

pclose(pipein_fp);

pclose(pipeout_fp);

return(0);

}

最后,我们再看一个使用popen()的例子。此程序用于创建一个命令和文件之间的管道:

#include

intmain(intargc,char*argv[])

{

FILE*pipe_fp,*infile;

charreadbuf[80];

if(argc!=3){

fprintf(stderr,"USAGE:popen3[command][filename]\n");

exit(1);

}

if((infile=fopen(argv[2],"rt"))==NULL)

{

perror("fopen");

exit(1);

}

if((pipe_fp=popen(argv[1],"w"))==NULL)

{

perror("popen");

exit(1);

}

do{

fgets(readbuf,80,infile);

if(feof(infile))break;

fputs(readbuf,pipe_fp);

}while(!feof(infile));

fclose(infile);

pclose(pipe_fp);

return(0);

}

下面是使用此程序的例子:

popen3sortpopen3.c

popen3catpopen3.c

popen3morepopen3.c

popen3catpopen3.c|grepmain

[目录]

命名管道

命名管道和一般的管道基本相同,但也有一些显著的不同:

*命名管道是在文件系统中作为一个特殊的设备文件而存在的。

*不同祖先的进程之间可以通过管道共享数据。

*当共享管道的进程执行完所有的I/O操作以后,命名管道将继续保存在文件系统中以便以后使用。

一个管道必须既有读取进程,也要有写入进程。如果一个进程试图写入到一个没有读取进程的管道中,那么系统内核将会产生SIGPIPE信号。当两个以上的进程同时使用管道时,这一点尤其重要。

[目录]

创建FIFO

可以有几种方法创建一个命名管道。头两种方法可以使用shell。

mknodMYFIFOp

mkfifoa=rwMYFIFO

上面的两个命名执行同样的操作,但其中有一点不同。命令mkfifo提供一个在创建之后直接改变FIFO文件存取权限的途径,而命令mknod需要调用命令chmod。

一个物理文件系统可以通过p指示器十分容易地分辨出一个FIFO文件。

$ls-lMYFIFO

prw-r--r--1rootroot0Dec1422:15MYFIFO|

请注意在文件名后面的管道符号“|”。

我们可以使用系统调用mknod()来创建一个FIFO管道:

库函数:mknod();

原型:intmknod(char*pathname,mode_tmode,dev_tdev);

返回值:如果成功,返回0

如果失败,返回-1:errno=EFAULT(无效路径名)

EACCES(无存取权限)

ENAMETOOLONG(路径名太长)

ENOENT(无效路径名)

ENOTDIR(无效路径名)

下面看一个使用C语言创建FIFO管道的例子:

mknod("/tmp/MYFIFO",S_IFIFO|0666,0);

在这个例子中,文件/tmp/MYFIFO是要创建的FIFO文件。它的存取权限是0666。存取权限

也可以使用umask修改:

final_umask=requested_permissions&~original_umask

一个常用的使用系统调用umask()的方法就是临时地清除umask的值:

umask(0);

mknod("/tmp/MYFIFO",S_IFIFO|0666,0);

另外,mknod()中的第三个参数只有在创建一个设备文件时才能用到。它包括设备文件的

主设备号和从设备号。

}

}

[目录]

操作FIFO

FIFO上的I/O操作和正常管道上的I/O操作基本一样,只有一个主要的不同。系统调用open用来在物理上打开一个管道。在半双工的管道中,这是不必

要的。因为管道在系统内核中,而不是在一个物理的文件系统中。在我们的例子中,我们将像使用一个文件流一样使用管道,也就是使用fopen()打开管道,

使用fclose()关闭它。

请看下面的简单的服务程序进程:

#include

#include

#include

#include

#include

#defineFIFO_FILE"MYFIFO"

intmain(void)

{

FILE*fp;

charreadbuf[80];

umask(0);

mknod(FIFO_FILE,S_IFIFO|0666,0);

while(1)

{

fp=fopen(FIFO_FILE,"r");

fgets(readbuf,80,fp);

printf("Receivedstring:%s\n",readbuf);

fclose(fp);

return(0);

因为FIFO管道缺省时有阻塞的函数,所以你可以在后台运行此程序:

$fifoserver&

再来看一下下面的简单的客户端程序:

#include

#include

#defineFIFO_FILE"MYFIFO"

intmain(int argc,char* argv[])

{

FILE*fp;

if(argc!=2){

printf("USAGE:fifoclient[string]\n");

exit(1);

}

if((fp=fopen(FIFO_FILE,"w"))==NULL){

perror("fopen");

exit(1);

}

fputs(argv[1],fp);

fclose(fp);

return(0);

}[目录]

阻塞FIFO

一般情况下,FIFO管道上将会有阻塞的情况发生。也就是说,如果一个FIFO管道打开供读取的话,它将一直阻塞,直到其他的进程打开管道写入信息。这种

过程反过来也一样。如果你不需要阻塞函数的话,你可以在系统调用open()中设置O_NONBLOCK标志,这样可以取消缺省的阻塞函数。

[目录]

消息队列

在UNIX的SystemV版本,AT&T引进了三种新形式的IPC功能(消息队列、信号量、以及共享内存)。但BSD版本的UNIX使用套接口作为主要的IPC形式。Linux系统同时支持这两个版本。

[目录]

msgget()

系统调用msgget()

如果希望创建一个新的消息队列,或者希望存取一个已经存在的消息队列,你可以使用系统调用msgget()。

系统调用:msgget();

原型:intmsgget(key_t key,int msgflg);

返回值:如果成功,返回消息队列标识符

如果失败,则返回-1:errno=EACCESS(权限不允许)

EEXIST(队列已经存在,无法创建)

EIDRM(队列标志为删除)

ENOENT(队列不存在)

ENOMEM(创建队列时内存不够)

ENOSPC(超出最大队列限制)

系统调用msgget()中的第一个参数是关键字值(通常是由ftok()返回的)。然后此关键字值将会和其他已经存在于系统内核中的关键字值比较。这时,打开和存取操作是和参数msgflg中的内容相关的。

IPC_CREAT如果内核中没有此队列,则创建它。

IPC_EXCL当和IPC_CREAT一起使用时,如果队列已经存在,则失败。

如果单独使用IPC_CREAT,则msgget()要么返回一个新创建的消息队列的标识符,要么返回具有相同关键字值的队列的标识符。如果

IPC_EXCL和IPC_CREAT一起使用,则msgget()要么创建一个新的消息队列,要么如果队列已经存在则返回一个失败值-1。

IPC_EXCL单独使用是没有用处的。

下面看一个打开和创建一个消息队列的例子:

intopen_queue(key_t keyval)

{

intqid;

if((qid=msgget(keyval,IPC_CREAT|0660))==-1)

{

return(-1);

}

return(qid);

}

[目录]

msgsnd()

系统调用msgsnd()

一旦我们得到了队列标识符,我们就可以在队列上执行我们希望的操作了。如果想要往队列中发送一条消息,你可以使用系统调用msgsnd():

系统调用:msgsnd();

原型:intmsgsnd(int msqid,struct msgbuf*msgp,int msgsz,int

msgflg);

返回值:如果成功,0。

如果失败,-1:errno=EAGAIN(队列已满,并且使用了IPC_NOWAIT)

EACCES(没有写的权限)

EFAULT(msgp地址无效)

EIDRM(消息队列已经删除)

EINTR(当等待写操作时,收到一个信号)

EINVAL(无效的消息队列标识符,非正数的消息类型,或

者无效的消息长度)

ENOMEM(没有足够的内存复制消息缓冲区)

系统调用msgsnd()的第一个参数是消息队列标识符,它是由系统调用msgget返回的。第二个参数是msgp,是指向消息缓冲区的指针。参数msgsz中包含的是消息的字节大小,但不包括消息类型的长度(4个字节)。

参数msgflg可以设置为0(此时为忽略此参数),或者使用IPC_NOWAIT。

如果消息队列已满,那么此消息则不会写入到消息队列中,控制将返回到调用进程中。如果没有指明,调用进程将会挂起,直到消息可以写入到队列中。

下面是一个发送消息的程序:

intsend_message(int qid,struct mymsgbuf *qbuf)

{

intresult,length;

length=sizeof(structmymsgbuf)-sizeof(long);

if((result=msgsnd(qid,qbuf,length,0))==-1)

{

return(-1);

}

return(result);

}

这个小程序试图将存储在缓冲区qbuf中的消息发送到消息队列qid中。下面的程序是结合了上面两个程序的一个完整程序:

#include

#include

#include

#include

main()

{

intqid;

key_t msgkey;

struct mymsgbuf{

longmtype;

intrequest;

doublesalary;

}msg;

msgkey=ftok(".",'m');

if((qid=open_queue(msgkey))==-1){

perror("open_queue");

exit(1);

}

msg.mtype=1;

msg.request=1;

msg.salary=1000.00;

if((send_message(qid,&msg))==-1){

perror("send_message");

exit(1);

}

}

在创建和打开消息队列以后,我们将测试数据装入到消息缓冲区中。最后调用send_messag把消息发送到消息队列中。现在在消息队列中有了一条消息,

我们可以使用ipcs命令来查看队列的状态。下面讨论如何从队列中获取消息。可以使用系统调用msgrcv():

[目录]

msgrcv()

系统调用:msgrcv();

原型:intmsgrcv(intmsqid,structmsgbuf*msgp,intmsgsz,longmtype,intmsgflg);

返回值:如果成功,则返回复制到消息缓冲区的字节数。

如果失败,则返回-1:errno=E2BIG(消息的长度大于msgsz,没有MSG_NOERROR)

EACCES(没有读的权限)

EFAULT(msgp指向的地址是无效的)

EIDRM(队列已经被删除)

EINTR(被信号中断)

EINVAL(msgqid无效,或者msgsz小于0)

ENOMSG(使用IPC_NOWAIT,同时队列中的消息无法满足要求)

很明显,第一个参数用来指定将要读取消息的队列。第二个参数代表要存储消息的消息缓冲区的地址。第三个参数是消息缓冲区的长度,不包括mtype的长度,它可以按照如下的方法计算:

msgsz=sizeof(structmymsgbuf)-sizeof(long);

第四个参数是要从消息队列中读取的消息的类型。如果此参数的值为0,那么队列中最长时间的一条消息将返回,而不论其类型是什么。

如果调用中使用了IPC_NOWAIT作为标志,那么当没有数据可以使用时,调用将把ENOMSG返回到调用进程中。否则,调用进程将会挂起,直到队列中

的一条消息满足msgrcv()的参数要求。如果当客户端等待一条消息的时候队列为空,将会返回EIDRM。如果进程在等待消息的过程中捕捉到一个信号,

则返回EINTR。

下面就是一个从队列中读取消息的程序:

intread_message(int qid,long type,struct mymsgbuf*qbuf)

{

intresult,length;

length=sizeof(structmymsgbuf)-sizeof(long);

if((result=msgrcv(qid,qbuf,length,type,0))==-1)

{

return(-1);

}

return(result);

}

在成功地读取了一条消息以后,队列中的这条消息的入口将被删除。

参数msgflg中的MSG_NOERROR位提供一种额外的用途。如果消息的实际长度大于msgsz,同时使用了MSG_NOERROR,那么消息将会

被截断,只有与msgsz长度相等的消息返回。一般情况下,系统调用msgrcv()会返回-1,而这条消息将会继续保存在队列中。我们可以利用这个特点

编制一个程序,利用这个程序可以查看消息队列的情况,看看符合我们条件的消息是否已经到来:

intpeek_message(int qid,long type)

{

intresult,length;

if((result=msgrcv(qid,NULL,0,type,IPC_NOWAIT))==-1)

{

if(errno==E2BIG)

return(TRUE);

}

return(FALSE);

}

在上面的程序中,我们忽略了缓冲区的地址和长度。这样,系统调用将会失败。尽管如此,我们可以检查返回的E2BIG值,它说明符合条件的消息确实存在。

[目录]

msgctl()

系统调用msgctl()

下面我们继续讨论如何使用一个给定的消息队列的内部数据结构。我们可以使用系统调用msgctl

( )来控制对消息队列的操作。

系统调用: msgctl( ) ;

调用原型: int msgctl ( int msgqid, int cmd, struct msqid_ds *buf

);

返回值: 0 ,如果成功。

- 1,如果失败:errno = EACCES (没有读的权限同时cmd 是IPC_STAT )

EFAULT (buf 指向的地址无效)

EIDRM (在读取中队列被删除)

EINVAL (msgqid无效, 或者msgsz 小于0 )

EPERM (IPC_SET或者IPC_RMID 命令被使用,但调用程序没有写的权限)

下面我们看一下可以使用的几个命令:

IPC_STAT

读取消息队列的数据结构msqid_ds,并将其存储在b u f指定的地址中。

IPC_SET

设置消息队列的数据结构msqid_ds中的ipc_perm元素的值。这个值取自buf参数。

IPC_RMID

从系统内核中移走消息队列。

我们在前面讨论过了消息队列的数据结构(msqid_ds)。系统内核中为系统中的每一个消息队列保存一个此数据结构的实例。通过使用IPC_STAT命令,我们可以得到一个此数据结构的副本。下面的程序就是实现此函数的过程:

int get_queue_ds( int qid, struct msgqid_ds *qbuf )

{

if( msgctl( qid, IPC_STAT, qbuf) == -1)

{

return(-1);

}

return(0);

}

如果不能复制内部缓冲区,调用进程将返回-1。如果调用成功,则返回0。缓冲区中应该包括消息队列中的数据结构。

消息队列中的数据结构中唯一可以改动的元素就是ipc_perm。它包括队列的存取权限和关于队列创建者和拥有者的信息。你可以改变用户的id、用户的组id以及消息队列的存取权限。

下面是一个修改队列存取模式的程序:

int change_queue_mode(int qid, char *mode )

{

struct msqid_ds tmpbuf;

get_queue_ds( qid, &tmpbuf);

sscanf(mode, "%ho", &tmpbuf.msg_perm.mode);

if( msgctl( qid, IPC_SET, &tmpbuf) == -1)

{

return(-1);

}

return(

}

我们通过调用get_queue_ds来读取队列的内部数据结构。然后,我们调用sscanf(

)修改数据结构msg_perm中的mode

成员的值。但直到调用msgctl()时,权限的改变才真正完成。在这里msgctl()使用的是IPC_SET命令。

最后,我们使用系统调用msgctl ( )中的IPC_RMID命令删除消息队列:

int remove_queue(int qid )

{

if( msgctl( qid, IPC_RMID, 0) == -1)

{

return(-1);

}

return(0);

}

};

[目录]

信号量

信号量是一个可以用来控制多个进程存取共享资源的计数器。它经常作为一种锁定机制来防止当一个进程正在存取共享资源时,另一个进程也存取同一资源。下面先简要地介绍一下信号量中涉及到的数据结构。

1.内核中的数据结构semid_ds

和消息队列一样,系统内核为内核地址空间中的每一个信号量集都保存了一个内部的数据结构。数据结构的原型是semid_ds。它是在linux/sem.h中做如下定义的:

structsemid_ds{

structipc_permsem_perm;

time_tsem_otime;

time_tsem_ctime;

structsem*sem_base;

structwait_queue*eventn;

structwait_queue*eventz;

structsem_undo*undo;

ushortsem_nsems;

};

sem_perm是在linux/ipc.h定义的数据结构ipc_perm的一个实例。它保存有信号量集的存取权限的信息,以及信号量集创建者的有关信息。

sem_otime最后一次semop()操作的时间。

sem_ctime最后一次改动此数据结构的时间。

sem_base指向数组中第一个信号量的指针。

sem_undo数组中没有完成的请求的个数。

sem_nsems信号量集(数组)中的信号量的个数。

2.内核中的数据结构sem

在数据结构semid_ds中包含一个指向信号量数组的指针。此数组中的每一个元素都是一个

数据结构sem。它也是在linux/sem.h中定义的:

structsem{

shortsempid;

ushortsemval;

ushortsemncnt;

ushortsemzcnt;

};

sem_pid最后一个操作的PID(进程ID)。

sem_semval信号量的当前值。

sem_semncnt等待资源的进程数目。

sem_semzcnt等待资源完全空闲的进程数目。

[目录]

semget()

我们可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集:

系统调用:semget();

原型:intsemget(key_t key,int nsems,int semflg);

返回值:如果成功,则返回信号量集的IPC标识符。如果失败,则返回-1:errno=EACCESS(没有权限)

EEXIST(信号量集已经存在,无法创建)

EIDRM(信号量集已经删除)

ENOENT(信号量集不存在,同时没有使用IPC_CREAT)

ENOMEM(没有足够的内存创建新的信号量集)

ENOSPC(超出限制)

系统调用semget()的第一个参数是关键字值(一般是由系统调用ftok()返回的)。系统内核将此值和系统中存在的其他的信号量集的关键字值进行比

较。打开和存取操作与参数semflg中的内容相关。IPC_CREAT如果信号量集在系统内核中不存在,则创建信号量集。IPC_EXCL当和

IPC_CREAT一同使用时,如果信号量集已经存在,则调用失败。如果单独使用IPC_CREAT,则semget()要么返回新创建的信号量集的标识

符,要么返回系统中已经存在的同样的关键字值的信号量的标识符。如果IPC_EXCL和IPC_CREAT一同使用,则要么返回新创建的信号量集的标识

符,要么返回-1。IPC_EXCL单独使用没有意义。参数nsems指出了一个新的信号量集中应该创建的信号量的个数。信号量集中最多的信号量的个数是

在linux/sem.h中定义的:

#defineSEMMSL32

下面是一个打开和创建信号量集的程序:

intopen_semaphore_set(key_t keyval,int numsems)

{

intsid;

if(!numsems)

return(-1);

if((sid=semget(mykey,numsems,IPC_CREAT|0660))==-1)

{

return(-1);

}

return(sid);

}

};

[目录]

你可能感兴趣的:(linux进程和线程教程)