《进程和线程编程 .pdf》由会员分享,可在线阅读,更多相关《进程和线程编程 .pdf(40页珍藏版)》请在taowenge.com淘文阁网|工程机械CAD图纸|机械工程制图|CAD装配图下载|SolidWorks_CaTia_CAD_UG_PROE_设计图分享下载上搜索。
1、进程和线程编程目 录1.进程和线程编程1.原始管道1.pipe()2.dup()3.dup2()4.popen()和 pclose()2.命名管道1.创建 FIFO 2.操作 FIFO 3.阻塞 FIFO 3.消息队列1.msgget()2.msgsnd()3.msgrcv()4.msgctl()4.信号量1.semget()2.semop()3.semctl()5.共享内存1.shmget()2.shmat()3.shmctl()4.shmdt()6.线程1.线程同步2.使用信号量协调程序3.代码例子1.newthread 2.exitthead 3.getchannel 4.def 5.r
2、elease 6.redezvous 7.unbouded 进程和线程编程看一下 UNIX系统中的进程和Mach 的任务和线程之间的关系。在UNIX 系统中,一个进程包括一个可执行的程序和一系列的资源,例如文件描述符表和地址空间。在Mach 中,一个任务仅包括一系列的资源;线程处理所有的可执行代码。一个 Mach 的任务可以有任意数目的线程和它相关,同时每个线程必须和某个任务相关。和某一个给定的任务相关的所有名师资料总结-精品资料欢迎下载-名师精心整理-第 1 页,共 40 页 -线程都共享任务的资源。这样,一个线程就是一个程序计数器、一个堆栈和一系列的寄存器。所有需要使用的数据结构都属于任务
3、。一个 UNIX 系统中的进程在Mach 中对应于一个任务和一个单独的线程。原始管道使用 C 语言创建管道要比在shell 下使用管道复杂一些。如果要使用C 语言创建一个简单的管道,可以使用系统调用pipe()。它接受一个参数,也就是一个包括两个整数的数组。如果系统调用成功,此数组将包括管道使用的两个文件描述符。创建一个管道之后,一般情况下进程将产生一个新的进程。可以通过打开两个管道来创建一个双向的管道。但需要在子进程中正确地设置文件描述必须在系统调用fork()中调用pipe(),否则子进程将不会继承文件描述符。当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。因为管道存在于系
4、统内核之中,所以任何不在创建管道的进程的祖先进程之中的进程都将无法寻址它。而在命名管道中却不是这样。pipe()系统调用:pipe();原型:intpipe(intfd2);返回值:如果系统调用成功,返回0 如果系统调用失败返回-1:errno=EMFILE(没有空闲的文件描述符)EMFILE(系统文件表已满)EFAULT(fd 数组无效)注意 fd0 用于读取管道,fd1 用于写入管道。#include#include#include main()intfd2;pipe(fd);.一旦创建了管道,我们就可以创建一个新的子进程:#include#include#include main()in
5、tfd2;pid_t childpid;pipe(fd);if(childpid=fork()=-1)perror(fork);名师资料总结-精品资料欢迎下载-名师精心整理-第 2 页,共 40 页 -exit(1);.如果父进程希望从子进程中读取数据,那么它应该关闭fd1,同时子进程关闭fd0。反之,如果父进程希望向子进程中发送数据,那么它应该关闭fd0,同时子进程关闭fd1。因为文件描述符是在父进程和子进程之间共享,所以我们要及时地关闭不需要的管道的那一端。单从技术的角度来说,如果管道的一端没有正确地关闭的话,你将无法得到一个EOF。#include#include#include mai
6、n()intfd2;pid_t childpid;pipe(fd);if(childpid=fork()=-1)perror(fork);exit(1);if(childpid=0)/*Child process closes up in put side of pipe*/close(fd0);else /*Parent process closes up out put side of pipe*/close(fd1);.正如前面提到的,一但创建了管道之后,管道所使用的文件描述符就和正常文件的文件描述符一样了。#include#include#include intmain(void)in
7、tfd2,nbytes;pid_tchildpid;charstring=Hello,world!n;charreadbuffer80;pipe(fd);if(childpid=fork()=-1)名师资料总结-精品资料欢迎下载-名师精心整理-第 3 页,共 40 页 -perror(fork);exit(1);if(childpid=0)/*Child process closes up in put side of pipe*/close(fd0);/*Sendstringthrough the out put side of pipe*/write(fd1,string,strlen(s
8、tring);exit(0);else /*Parent process closes up out put side of pipe*/close(fd1);/*Readinastringfromthepipe*/nbytes=read(fd0,readbuffer,sizeof(readbuffer);printf(Receivedstring:%s,readbuffer);return(0);一般情况下,子进程中的文件描述符将会复制到标准的输入和输出中。这样子进程可以使用 exec()执行另一个程序,此程序继承了标准的数据流。dup()系统调用:dup();原型:intdup(intol
9、dfd);返回:如果系统调用成功,返回新的文件描述符如果系统调用失败,返回-1:errno=EBADF(oldfd不是有效的文件描述符)EBADF(newfd超出范围)EMFILE(进程的文件描述符太多)注意旧文件描述符oldfd 没有关闭。虽然旧文件描述符和新创建的文件描述符可以交换使用,但一般情况下需要首先关闭一个。系统调用dup()使用的是号码最小的空闲的文件描述符。再看下面的程序:.childpid=fork();if(childpid=0)/*Close up standard input of the child*/close(0);/*Dup licate the input s
10、ide of pipe to stdin*/dup(fd0);名师资料总结-精品资料欢迎下载-名师精心整理-第 4 页,共 40 页 -execlp(sort,sort,NULL);.因为文件描述符0(stdin)被关闭,所以dup()把管道的输入描述符复制到它的标准输入中。这样我们可以调用execlp(),使用 sort 程序覆盖子进程的正文段。因为新创建的程序从它的父进程中继承了标准输入/输出流,所以它实际上继承了管道的输入端作为它的标准输入端。现在,最初的父进程送往管道的任何数据都将会直接送往sort 函数。dup2()系统调用:dup2();原型:intdup2(intoldfd,in
11、tnewfd);返回值:如果调用成功,返回新的文件描述符如果调用失败,返回-1:errno=EBADF(oldfd不是有效的文件描述符)EBADF(newfd超出范围)EMFILE(进程的文件描述符太多)注意 dup2()将关闭旧文件描述符。使用此系统调用,可以将close 操作和文件描述符复制操作集成到一个系统调用中。另外,此系统调用保证了操作的自动进行,也就是说操作不能被其他的信号中断。这个操作将会在返回系统内核之前完成。如果使用前一个系统调用dup(),程序员不得不在此之前执行一个 close()操作。请看下面的程序:.childpid=fork();if(childpid=0)/*Cl
12、ose stdin,dup licate the input side of pipe to stdin*/dup2(0,fd0);execlp(sort,sort,NULL);.popen()和 pclose()如果你认为上面创建和使用管道的方法过于繁琐的话,你也可以使用下面的简单的方法:库函数:popen()和 pclose();原型:FILE*popen(char*command,char*type);返回值:如果成功,返回一个新的文件流。如果无法创建进程或者管道,返回NULL。此标准的库函数通过在系统内部调用pipe()来创建一个半双工的管道,然后它创建一个子进程,启动 shell,最
13、后在 shell 上执行 command 参数中的命令。管道中数据流的方向是由第二个参数type 控制的。此参数可以是r 或者 w,分别代表读或写。但不能同时为读和写。在 Linux 系统下,管道将会以参数type 中第一个字符代表的方式打开。所以,如果你在参数 type 中写入 rw,管道将会以读的方式打开。虽然此库函数的用法很简单,但也有一些不利的地方。例如它失去了使用系统调用pipe()名师资料总结-精品资料欢迎下载-名师精心整理-第 5 页,共 40 页 -时可以有的对系统的控制。尽管这样,因为可以直接地使用shell 命令,所以shell 中的一些通配符和其他的一些扩展符号都可以在c
14、ommand 参数中使用。使用 popen()创建的管道必须使用pclose()关闭。其实,popen/pclose 和标准文件输入/输出流中的 fopen()/fclose()十分相似。库函数:pclose();原型:intpclose(FILE*stream);返回值:返回系统调用wait4()的状态。如果 stream 无效,或者系统调用wait4()失败,则返回-1。注意此库函数等待管道进程运行结束,然后关闭文件流。库函数 pclose()在使用 popen()创建的进程上执行wait4()函数。当它返回时,它将破坏管道和文件系统。在下面的例子中,用sort 命令打开了一个管道,然后对
15、一个字符数组排序:#include#defineMAXSTRS5 intmain(void)intcntr;FILE*pipe_fp;char*stringsMAXSTRS=echo,bravo,alpha,charlie,delta;/*Createonewaypipelinewithcalltopopen()*/if(pipe_fp=popen(sort,w)=NULL)perror(popen);exit(1);/*Processingloop*/for(cntr=0;cntr/tmp/foo,w);popen(sort|uniq|more,w);下面的程序是另一个使用popen()的例
16、子,它打开两个管道(一个用于ls 命令,另一个用于sort 命令):#include intmain(void)名师资料总结-精品资料欢迎下载-名师精心整理-第 6 页,共 40 页 -FILE*pipein_fp,*pipeout_fp;charreadbuf80;/*Createonewaypipelinewithcalltopopen()*/if(pipein_fp=popen(ls,r)=NULL)perror(popen);exit(1);/*Createonewaypipelinewithcalltopopen()*/if(pipeout_fp=popen(sort,w)=NULL
17、)perror(popen);exit(1);/*Processingloop*/while(fgets(readbuf,80,pipein_fp)fputs(readbuf,pipeout_fp);/*Closethepipes*/pclose(pipein_fp);pclose(pipeout_fp);return(0);最后,我们再看一个使用popen()的例子。此程序用于创建一个命令和文件之间的管道:#include intmain(intargc,char*argv)FILE*pipe_fp,*infile;charreadbuf80;if(argc!=3)fprintf(stder
18、r,USAGE:popen3commandfilenamen);exit(1);/*Open up input file*/if(infile=fopen(argv2,rt)=NULL)perror(fopen);exit(1);/*Create one way pipe line with call topopen()*/if(pipe_fp=popen(argv1,w)=NULL)perror(popen);exit(1);名师资料总结-精品资料欢迎下载-名师精心整理-第 7 页,共 40 页 -/*Processingloop*/do fgets(readbuf,80,infile);i
19、f(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 操作以
20、后,命名管道将继续保存在文件系统中以便以后使用。一个管道必须既有读取进程,也要有写入进程。如果一个进程试图写入到一个没有读取进程的管道中,那么系统内核将会产生SIGPIPE 信号。当两个以上的进程同时使用管道时,这一点尤其重要。创建 FIFO 可以有几种方法创建一个命名管道。头两种方法可以使用shell。mknodMYFIFOp mkfifoa=rwMYFIFO 上面的两个命名执行同样的操作,但其中有一点不同。命令mkfifo提供一个在创建之后直接改变FIFO 文件存取权限的途径,而命令mknod 需要调用命令chmod。一个物理文件系统可以通过p 指示器十分容易地分辨出一个FIFO 文件。$
21、ls-lMYFIFO prw-r-r-1rootroot0Dec1422:15MYFIFO|请注意在文件名后面的管道符号“|”。我们可以使用系统调用mknod()来创建一个FIFO 管道:库函数:mknod();原型:intmknod(char*pathname,mode_tmode,dev_tdev);返回值:如果成功,返回 0 如果失败,返回-1:errno=EFAULT(无效路径名)名师资料总结-精品资料欢迎下载-名师精心整理-第 8 页,共 40 页 -EACCES(无存取权限)ENAMETOOLONG(路径名太长)ENOENT(无效路径名)ENOTDIR(无效路径名)下面看一个使用C
22、 语言创建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()中的第三个参数只有在创建一个设备文件时才能用到。它包括设备文件的主设备号和从设备号。操作 FIF
23、O FIFO 上的 I/O 操作和正常管道上的I/O 操作基本一样,只有一个主要的不同。系统调用 open 用来在物理上打开一个管道。在半双工的管道中,这是不必要的。因为管道在系统内核中,而不是在一个物理的文件系统中。在我们的例子中,我们将像使用一个文件流一样使用管道,也就是使用fopen()打开管道,使用fclose()关闭它。请看下面的简单的服务程序进程:#include#include#include#include#include#defineFIFO_FILEMYFIFO intmain(void)FILE*fp;charreadbuf80;/*CreatetheFIFOifitdo
24、esnotexist*/umask(0);mknod(FIFO_FILE,S_IFIFO|0666,0);while(1)fp=fopen(FIFO_FILE,r);fgets(readbuf,80,fp);名师资料总结-精品资料欢迎下载-名师精心整理-第 9 页,共 40 页 -printf(Receivedstring:%sn,readbuf);fclose(fp);return(0);因为 FIFO 管道缺省时有阻塞的函数,所以你可以在后台运行此程序:$fifoserver&再来看一下下面的简单的客户端程序:#include#include#defineFIFO_FILEMYFIFO i
25、ntmain(int argc,char*argv)FILE*fp;if(argc!=2)printf(USAGE:fifoclientstringn);exit(1);if(fp=fopen(FIFO_FILE,w)=NULL)perror(fopen);exit(1);fputs(argv1,fp);fclose(fp);return(0);阻塞 FIFO 一般情况下,FIFO 管道上将会有阻塞的情况发生。也就是说,如果一个FIFO 管道打开供读取的话,它将一直阻塞,直到其他的进程打开管道写入信息。这种过程反过来也一样。如果你不需要阻塞函数的话,你可以在系统调用open()中设置O_NON
26、BLOCK标志,这样可以取消缺省的阻塞函数。名师资料总结-精品资料欢迎下载-名师精心整理-第 10 页,共 40 页 -消息队列在 UNIX 的 SystemV 版本,AT&T 引进了三种新形式的IPC 功能(消息队列、信号量、以及共享内存)。但 BSD 版本的 UNIX 使用套接口作为主要的IPC 形式。Linux 系统同时支持这两个版本。msgget()系统调用msgget()如果希望创建一个新的消息队列,或者希望存取一个已经存在的消息队列,你可以使用系统调用 msgget()。系统调用:msgget();原型:int msgget(key_t key,int msgflg);返回值:如果
27、成功,返回消息队列标识符如果失败,则返回-1:errno=EACCESS(权限不允许)EEXIST(队列已经存在,无法创建)EIDRM(队列标志为删除)ENOENT(队列不存在)ENOMEM(创建队列时内存不够)ENOSPC(超出最大队列限制)系统调用 msgget()中的第一个参数是关键字值(通常是由ftok()返回的)。然后此关键字值将会和其他已经存在于系统内核中的关键字值比较。这时,打开和存取操作是和参数msgflg 中的内容相关的。IPC_CREAT如果内核中没有此队列,则创建它。IPC_EXCL当和 IPC_CREAT 一起使用时,如果队列已经存在,则失败。如果单独使用IPC_CRE
28、AT,则 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);名师资料总结-精品资料欢迎下载-名师精心整理-第 11 页,共 40 页 -
29、msgsnd()系统调用msgsnd()一旦我们得到了队列标识符,我们就可以在队列上执行我们希望的操作了。如果想要往队列中发送一条消息,你可以使用系统调用msgsnd():系统调用:msgsnd();原型:int msgsnd(int msqid,struct msgbuf*msgp,int msgsz,int msgflg);返回值:如果成功,0。如果失败,-1:errno=EAGAIN(队列已满,并且使用了IPC_NOW AIT)EACCES(没有写的权限)EFAULT(msgp 地址无效)EIDRM(消息队列已经删除)EINTR(当等待写操作时,收到一个信号)EINVAL(无效的消息队列
30、标识符,非正数的消息类型,或者无效的消息长度)ENOMEM(没有足够的内存复制消息缓冲区)系统调用msgsnd()的第一个参数是消息队列标识符,它是由系统调用msgget 返回的。第二个参数是msgp,是指向消息缓冲区的指针。参数 msgsz中包含的是消息的字节大小,但不包括消息类型的长度(4 个字节)。参数 msgflg可以设置为0(此时为忽略此参数),或者使用IPC_NOW AIT。如果消息队列已满,那么此消息则不会写入到消息队列中,控制将返回到调用进程中。如果没有指明,调用进程将会挂起,直到消息可以写入到队列中。下面是一个发送消息的程序:intsend_message(int qid,s
31、truct mymsgbuf*qbuf)int result,length;/*The length is essentially the size of the structure minus sizeof(mtype)*/length=sizeof(struct mymsgbuf)-sizeof(long);if(result=msgsnd(qid,qbuf,length,0)=-1)return(-1);return(result);这个小程序试图将存储在缓冲区qbuf 中的消息发送到消息队列qid 中。下面的程序是结合了上面两个程序的一个完整程序:#include#include#in
32、clude#include main()intqid;名师资料总结-精品资料欢迎下载-名师精心整理-第 12 页,共 40 页 -key_t msgkey;struct mymsgbuf long mtype;/*Message type*/int request;/*Work request number*/double salary;/*Employees salary*/msg;/*Generateour IPC key value*/msgkey=ftok(.,m);/*Open/createthequeue*/if(qid=open_queue(msgkey)=-1)perror(o
33、pen_queue);exit(1);/*Load up the message with a r bitrary test data*/msg.mtype=1;/*Message type must be a positive number!*/msg.request=1;/*Data element#1*/msg.salary=1000.00;/*Data element#2(my yearly salary!)*/*Bombsaway!*/if(send_message(qid,&msg)=-1)perror(send_message);exit(1);在创建和打开消息队列以后,我们将测
34、试数据装入到消息缓冲区中。最后调用send_messag把消息发送到消息队列中。现在在消息队列中有了一条消息,我们可以使用ipcs命令来查看队列的状态。下面讨论如何从队列中获取消息。可以使用系统调用msgrcv():msgrcv()系统调用:msgrcv();原型:int msgrcv(int msqid,struct msgbuf*msgp,int msgsz,long mtype,int msgflg);返回值:如果成功,则返回复制到消息缓冲区的字节数。如果失败,则返回-1:errno=E2BIG(消息的长度大于msgsz,没有 MSG_NOERROR)EACCES(没有读的权限)EFAU
35、LT(msgp 指向的地址是无效的)EIDRM(队列已经被删除)EINTR(被信号中断)EINVAL(msgqid 无效,或者 msgsz小于 0)ENOMSG(使用 IPC_NOW AIT,同时队列中的消息无法满足要求)很明显,第一个参数用来指定将要读取消息的队列。第二个参数代表要存储消息的消息缓冲区的地址。第三个参数是消息缓冲区的长度,不包括mtype 的长度,它可以按照如下的方法计算:msgsz=sizeof(struct mymsgbuf)-sizeof(long);第四个参数是要从消息队列中读取的消息的类型。如果此参数的值为0,那么队列中最名师资料总结-精品资料欢迎下载-名师精心整理
36、-第 13 页,共 40 页 -长时间的一条消息将返回,而不论其类型是什么。如果调用中使用了IPC_NOW AIT作为标志,那么当没有数据可以使用时,调用将把ENOMSG 返回到调用进程中。否则,调用进程将会挂起,直到队列中的一条消息满足msgrcv()的参数要求。如果当客户端等待一条消息的时候队列为空,将会返回EIDRM。如果进程在等待消息的过程中捕捉到一个信号,则返回EINTR。下面就是一个从队列中读取消息的程序:Int read_message(int qid,long type,struct mymsgbuf*qbuf)intresult,length;/*The length is
37、essentially the size of the structure minus sizeof(mtype)*/length=sizeof(struct mymsgbuf)-sizeof(long);if(result=msgrcv(qid,qbuf,length,type,0)=-1)return(-1);return(result);在成功地读取了一条消息以后,队列中的这条消息的入口将被删除。参数 msgflg中的 MSG_NOERROR位提供一种额外的用途。如果消息的实际长度大于msgsz,同时使用了MSG_NOERROR,那么消息将会被截断,只有与msgsz长度相等的消息返回。一
38、般情况下,系统调用msgrcv()会返回-1,而这条消息将会继续保存在队列中。我们可以利用这个特点编制一个程序,利用这个程序可以查看消息队列的情况,看看符合我们条件的消息是否已经到来:Int peek_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 值,它说明符合条件的消
39、息确实存在。msgctl()系统调用msgctl()下面我们继续讨论如何使用一个给定的消息队列的内部数据结构。我们可以使用系统调用 msgctl()来控制对消息队列的操作。系统调用:msgctl();调用原型:int msgctl(int msgqid,int cmd,struct msqid_ds*buf);返回值:0,如果成功。名师资料总结-精品资料欢迎下载-名师精心整理-第 14 页,共 40 页 -1,如果失败:errno=EACCES(没有读的权限同时cmd 是 IPC_STAT)EFAULT(buf 指向的地址无效)EIDRM(在读取中队列被删除)EINVAL(msgqid 无效,
40、或者 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 命令,我们可以得到一个此数据结构的副本。下面的程序就
41、是实现此函数的过程:int get_queue_ds(int qid,struct msgqid_ds*qbuf)if(msgctl(qid,IPC_STA T,qbuf)=-1)return(-1);return(0);如果不能复制内部缓冲区,调用进程将返回-1。如果调用成功,则返回0。缓冲区中应该包括消息队列中的数据结构。消息队列中的数据结构中唯一可以改动的元素就是ipc_perm。它包括队列的存取权限和关于队列创建者和拥有者的信息。你可以改变用户的id、用户的组id 以及消息队列的存取权限。下面是一个修改队列存取模式的程序:int change_queue_mode(int qid,ch
42、ar*mode)struct msqid_ds tmpbuf;/*Retrieve a current copy of the internal data structure*/get_queue_ds(qid,&tmpbuf);/*Change the permissions using an old trick*/sscanf(mode,%ho,&tmpbuf.msg_perm.mode);/*Update the internal data structure*/if(msgctl(qid,IPC_SET,&tmpbuf)=-1)return(-1);return(我们通过调用get_q
43、ueue_ds来读取队列的内部数据结构。然后,我们调用sscanf()修改名师资料总结-精品资料欢迎下载-名师精心整理-第 15 页,共 40 页 -数据结构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);信号量信号量是一个可以用来控制多个进程存取共享资源的计数器。它经常
44、作为一种锁定机制来防止当一个进程正在存取共享资源时,另一个进程也存取同一资源。下面先简要地介绍一下信号量中涉及到的数据结构。1.内核中的数据结构semid_ds 和消息队列一样,系统内核为内核地址空间中的每一个信号量集都保存了一个内部的数据结构。数据结构的原型是semid_ds。它是在linux/sem.h 中做如下定义的:/*One semid data structure for each set of semaphores in the system.*/structsemid_ds structipc_permsem_perm;/*permissions.seeipc.h*/time_
45、tsem_otime;/*last semop time*/time_tsem_ctime;/*last change time*/structsem*sem_base;/*ptr to first semaphore in array*/structwait_queue*eventn;structwait_queue*eventz;structsem_undo*undo;/*undo requestson this array*/ushortsem_nsems;/*no.of semaphores in array*/;sem_perm 是在 linux/ipc.h 定义的数据结构ipc_p
46、erm 的一个实例。它保存有信号量集的存取权限的信息,以及信号量集创建者的有关信息。sem_otime 最后一次semop()操作的时间。sem_ctime 最后一次改动此数据结构的时间。sem_base指向数组中第一个信号量的指针。sem_undo数组中没有完成的请求的个数。sem_nsems信号量集(数组)中的信号量的个数。2.内核中的数据结构sem 在数据结构semid_ds 中包含一个指向信号量数组的指针。此数组中的每一个元素都是一个数据结构sem。它也是在linux/sem.h 中定义的:/*One semaphore structure for each semaphore in
47、the system.*/structsem 名师资料总结-精品资料欢迎下载-名师精心整理-第 16 页,共 40 页 -shortsempid;/*pid of las toperation*/ushortsemval;/*current value*/ushortsemncnt;/*num procs awaiting increase in semval*/ushortsemzcnt;/*num procs awaiting semval=0*/;sem_pid 最后一个操作的PID(进程 ID)。sem_semval信号量的当前值。sem_semncnt等待资源的进程数目。sem_se
48、mzcnt等待资源完全空闲的进程数目。semget()我们可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集:系统调用:semget();原型:intsemget(key_t key,int nsems,int semflg);返回值:如果成功,则返回信号量集的IPC 标识符。如果失败,则返回-1:errno=EACCESS(没有权限)EEXIST(信号量集已经存在,无法创建)EIDRM(信号量集已经删除)ENOENT(信号量集不存在,同时没有使用IPC_CREAT)ENOMEM(没有足够的内存创建新的信号量集)ENOSPC(超出限制)系统调用 semget(
49、)的第一个参数是关键字值(一般是由系统调用ftok()返回的)。系统内核将此值和系统中存在的其他的信号量集的关键字值进行比较。打开和存取操作与参数semflg 中的内容相关。IPC_CREAT 如果信号量集在系统内核中不存在,则创建信号量集。IPC_EXCL 当和 IPC_CREAT 一同使用时,如果信号量集已经存在,则调用失败。如果单独使用 IPC_CREAT,则 semget()要么返回新创建的信号量集的标识符,要么返回系统中已经存在的同样的关键字值的信号量的标识符。如果 IPC_EXCL 和 IPC_CREAT 一同使用,则要么返回新创建的信号量集的标识符,要么返回-1。IPC_EXCL
50、 单独使用没有意义。参数 nsems指出了一个新的信号量集中应该创建的信号量的个数。信号量集中最多的信号量的个数是在linux/sem.h 中定义的:#defineSEMMSL32/*=512maxnumofsemaphoresperid*/下面是一个打开和创建信号量集的程序: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);名师资料总结-精品资料欢迎下