📄 11.html
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><HTML><HEAD> <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=gb2312"> <META NAME="GENERATOR" CONTENT="《良友》v2.1, 作者:安富国,http://winking.126.com"> <TITLE>进程和线程编程</TITLE></HEAD><BODY style="font-family: 宋体; font-size: 9pt"> <CENTER><TABLE CELLSPACING=10 CELLPADDING=10 WIDTH="60%" BGCOLOR="#FFB693" ><TR><TD ALIGN=CENTER><FONT SIZE=+2><!--标题由此开始-->进程和线程编程</TD></TR></TABLE></CENTER><p><h3>目 录</h3><!--目录由此开始--><A NAME="Content" ID="Content"></A><OL><LI><A HREF="#I236">进程和线程编程</A></LI><OL><LI><A HREF="#I237">原始管道</A></LI><OL><LI><A HREF="#I238">pipe()</A></LI><LI><A HREF="#I239">dup()</A></LI><LI><A HREF="#I240">dup2()</A></LI><LI><A HREF="#I241">popen()和pclose()</A></LI></OL><LI><A HREF="#I242">命名管道</A></LI><OL><LI><A HREF="#I243">创建FIFO</A></LI><LI><A HREF="#I244">操作FIFO</A></LI><LI><A HREF="#I245">阻塞FIFO</A></LI></OL><LI><A HREF="#I246">消息队列</A></LI><OL><LI><A HREF="#I247">msgget()</A></LI><LI><A HREF="#I248">msgsnd()</A></LI><LI><A HREF="#I249">msgrcv()</A></LI><LI><A HREF="#I250">msgctl()</A></LI></OL><LI><A HREF="#I251">信号量</A></LI><OL><LI><A HREF="#I252">semget()</A></LI><LI><A HREF="#I253">semop()</A></LI><LI><A HREF="#I254">semctl()</A></LI></OL><LI><A HREF="#I255">共享内存</A></LI><OL><LI><A HREF="#I256">shmget()</A></LI><LI><A HREF="#I257">shmat()</A></LI><LI><A HREF="#I258">shmctl()</A></LI><LI><A HREF="#I259">shmdt()</A></LI></OL><LI><A HREF="#I260">线程</A></LI><OL><LI><A HREF="#I261">线程同步</A></LI><LI><A HREF="#I262">使用信号量协调程序</A></LI><LI><A HREF="#I263">代码例子</A></LI><OL><LI><A HREF="#I264">newthread</A></LI><LI><A HREF="#I265">exitthead</A></LI><LI><A HREF="#I266">getchannel</A></LI><LI><A HREF="#I267">def</A></LI><LI><A HREF="#I268">release</A></LI><LI><A HREF="#I269">redezvous</A></LI><LI><A HREF="#I270">unbouded</A></LI></OL></OL></OL></OL><hr><br><A NAME="I236" ID="I236"></A><center><b><font size=+2>进程和线程编程</font></b></center><br> 看一下UNIX系统中的进程和Mach的任务和线程之间的关系。在UNIX系统中,一个进程包括一个可执行的程序和一系列的资源,例如文件描述符表和地址空间。在Mach中,一个任务仅包括一系列的资源;线程处理所有的可执行代码。一个Mach的任务可以有任意数目的线程和它相关,同时每个线程必须和某个任务相关。和某一个给定的任务相关的所有线程都共享任务的资源。这样,一个线程就是一个程序计数器、一个堆栈和一系列的寄存器。所有需要使用的数据结构都属于任务。一个UNIX系统中的进程在Mach中对应于一个任务和一个单独的线程。<br><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I237" ID="I237"></A><center><b><font size=+2>原始管道</font></b></center><br> 使用C语言创建管道要比在shell下使用管道复杂一些。如果要使用C语言创建一个简单的管道,可以使用系统调用pipe()。它接受一个参数,也就是一个包括两个整数的数组。如果系统调用成功,此数组将包括管道使用的两个文件描述符。创建一个管道之后,一般情况下进程将产生一个新的进程。<p> 可以通过打开两个管道来创建一个双向的管道。但需要在子进程中正确地设置文件描述必须在系统调用fork()中调用pipe(),否则子进程将不会继承文件描述符。当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。因为管道存在于系统内核之中,所以任何不在创建管道的进程的祖先进程之中的进程都将无法寻址它。而在命名管道中却不是这样。<br><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I238" ID="I238"></A><center><b><font size=+2>pipe()</font></b></center><br>系统调用:pipe();<br>原型:intpipe(intfd[2]);<br>返回值:如果系统调用成功,返回0<br>如果系统调用失败返回-1:errno=EMFILE(没有空闲的文件描述符)<br>EMFILE(系统文件表已满)<br>EFAULT(fd数组无效)<br>注意fd[0]用于读取管道,fd[1]用于写入管道。<br>#include<stdio.h><br>#include<unistd.h><br>#include<sys/types.h><br>main()<br>{<br>intfd[2];<br>pipe(fd);<br>..<br>}<br>一旦创建了管道,我们就可以创建一个新的子进程:<br>#include<stdio.h><br>#include<unistd.h><br>#include<sys/types.h><br>main()<br>{<br>intfd[2];<br>pid_t childpid;<br>pipe(fd);<br>if((childpid=fork())==-1)<br>{<br>perror("fork");<br>exit(1);<br>}..<br>}<p> 如果父进程希望从子进程中读取数据,那么它应该关闭fd1,同时子进程关闭fd0。反之,如果父进程希望向子进程中发送数据,那么它应该关闭fd0,同时子进程关闭fd1。因为文件描述符是在父进程和子进程之间共享,所以我们要及时地关闭不需要的管道的那一端。单从技术的角度来说,如果管道的一端没有正确地关闭的话,你将无法得到一个EOF。<p>#include<stdio.h><br>#include<unistd.h><br>#include<sys/types.h><br>main()<br>{<br>intfd[2];<br>pid_t childpid;<br>pipe(fd);<br>if((childpid=fork())==-1)<br>{<br>perror("fork");<br>exit(1);<br>}<br>if(childpid==0)<br>{<br>/*Child process closes up in put side of pipe*/<br>close(fd[0]);<br>}<br>else<br>{<br>/*Parent process closes up out put side of pipe*/<br>close(fd[1]);<br>}..<br>}<p> 正如前面提到的,一但创建了管道之后,管道所使用的文件描述符就和正常文件的文件描述符一样了。<p>#include<stdio.h><br>#include<unistd.h><br>#include<sys/types.h><br>intmain(void)<br>{<br>intfd[2],nbytes;<br>pid_tchildpid;<br>charstring[]="Hello,world!\n";<br>charreadbuffer[80];<br>pipe(fd);<br>if((childpid=fork())==-1)<br>{<br>perror("fork");<br>exit(1);<br>}<br>if(childpid==0)<br>{<br>/*Child process closes up in put side of pipe*/<br>close(fd[0]);<br>/*Send"string"through the out put side of pipe*/<br>write(fd[1],string,strlen(string));<br>exit(0);<br>}<br>else<br>{<br>/*Parent process closes up out put side of pipe*/<br>close(fd[1]);<br>/*Readinastringfromthepipe*/<br>nbytes=read(fd[0],readbuffer,sizeof(readbuffer));<br>printf("Receivedstring:%s",readbuffer);<br>}<br>return(0);<br>}<p> 一般情况下,子进程中的文件描述符将会复制到标准的输入和输出中。这样子进程可以使用exec()执行另一个程序,此程序继承了标准的数据流。<p><p><p><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I239" ID="I239"></A><center><b><font size=+2>dup()</font></b></center><br>系统调用:dup();<br>原型:intdup(intoldfd);<br>返回:如果系统调用成功,返回新的文件描述符<br>如果系统调用失败,返回-1:errno=EBADF(oldfd不是有效的文件描述符)<br>EBADF(newfd超出范围)<br>EMFILE(进程的文件描述符太多)<p> 注意旧文件描述符oldfd没有关闭。虽然旧文件描述符和新创建的文件描述符可以交换使用,但一般情况下需要首先关闭一个。系统调用dup()使用的是号码最小的空闲的文件描述符。<p>再看下面的程序:<br>..<br>childpid=fork();<br>if(childpid==0)<br>{<br>/*Close up standard input of the child*/<br>close(0);<br>/*Dup licate the input side of pipe to stdin*/<br>dup(fd[0]);<br>execlp("sort","sort",NULL);<br>.<br>}<br> 因为文件描述符0(stdin)被关闭,所以dup()把管道的输入描述符复制到它的标准输入中。这样我们可以调用execlp(),使用sort程序覆盖子进程的正文段。因为新创建的程序从它的父进程中继承了标准输入/输出流,所以它实际上继承了管道的输入端作为它的标准输入端。现在,最初的父进程送往管道的任何数据都将会直接送往sort函数。<p><p><p><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I240" ID="I240"></A><center><b><font size=+2>dup2()</font></b></center><br>系统调用:dup2();<br>原型:intdup2(intoldfd,intnewfd);<br>返回值:如果调用成功,返回新的文件描述符<br>如果调用失败,返回-1:errno=EBADF(oldfd不是有效的文件描述符)<br>EBADF(newfd超出范围)<br>EMFILE(进程的文件描述符太多)<br>注意dup2()将关闭旧文件描述符。<p> 使用此系统调用,可以将close操作和文件描述符复制操作集成到一个系统调用中。另外,此系统调用保证了操作的自动进行,也就是说操作不能被其他的信号中断。这个操作将会在返回系统内核之前完成。如果使用前一个系统调用dup(),程序员不得不在此之前执行一个close()操作。请看下面的程序:<br>..<p>childpid=fork();<br>if(childpid==0)<br>{<br>/*Close stdin,dup licate the input side of pipe to stdin*/<br>dup2(0,fd[0]);<br>execlp("sort","sort",NULL);<br>..<br>}<p><p><p><center><A HREF="#Content">[目录]</A></center><hr><br><A NAME="I241" ID="I241"></A><center><b><font size=+2>popen()和pclose()</font></b></center><br>如果你认为上面创建和使用管道的方法过于繁琐的话,你也可以使用下面的简单的方法:<p>库函数:popen()和pclose();<br>原型:FILE*popen(char*command,char*type);<br>返回值:如果成功,返回一个新的文件流。<br>如果无法创建进程或者管道,返回NULL。<br> 此标准的库函数通过在系统内部调用pipe()来创建一个半双工的管道,然后它创建一个子进程,启动shell,最后在shell上执行command参数中的命令。管道中数据流的方向是由第二个参数type控制的。此参数可以是r或者w,分别代表读或写。但不能同时为读和写。在Linux系统下,管道将会以参数type中第一个字符代表的方式打开。所以,如果你在参数type中写入rw,管道将会以读的方式打开。<p> 虽然此库函数的用法很简单,但也有一些不利的地方。例如它失去了使用系统调用pipe()时可以有的对系统的控制。尽管这样,因为可以直接地使用shell命令,所以shell中的一些通配符和其他的一些扩展符号都可以在command参数中使用。<br>使用popen()创建的管道必须使用pclose()关闭。其实,popen/pclose和标准文件输入/输出流中的fopen()/fclose()十分相似。<p><br>库函数:pclose();<br>原型:intpclose(FILE*stream);<br>返回值:返回系统调用wait4()的状态。<br>如果stream无效,或者系统调用wait4()失败,则返回-1。<br> 注意此库函数等待管道进程运行结束,然后关闭文件流。库函数pclose()在使用popen()创建的进程上执行wait4()函数。当它返回时,它将破坏管道和文件系统。<br> 在下面的例子中,用sort命令打开了一个管道,然后对一个字符数组排序:<p>#include<stdio.h><br>#defineMAXSTRS5<br>intmain(void)<br>{<br>intcntr;<br>FILE*pipe_fp;<br>char*strings[MAXSTRS]={"echo","bravo","alpha",<br>"charlie","delta"};<br>/*Createonewaypipelinewithcalltopopen()*/<br>if((pipe_fp=popen("sort","w"))==NULL)<br>{<br>perror("popen");<br>exit(1);<br>}<br>/*Processingloop*/<br>for(cntr=0;cntr<MAXSTRS;cntr++){<br>fputs(strings[cntr],pipe_fp);<br>fputc('\n',pipe_fp);<br>}<br>/*Closethepipe*/<br>pclose(pipe_fp);<br>return(0);<br>}<br>因为popen()使用shell执行命令,所以所有的shell扩展符和通配符都可以使用。此外,它还可以和popen()一起使用重定向和输出管道函数。再看下面的例子:<br>popen("ls~scottb","r");<br>popen("sort>/tmp/foo","w");<br>popen("sort|uniq|more","w");<br>下面的程序是另一个使用popen()的例子,它打开两个管道(一个用于ls命令,另一个用于<br>sort命令):<br>#include<stdio.h><br>intmain(void)<br>{<br>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -