﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-C++ Coder-随笔分类-Linux_Unix编程</title><link>http://www.cppblog.com/jackdongy/category/20078.html</link><description>HCP高性能计算架构，实现，编译器指令优化，算法优化，
  LLVM   CLANG   OpenCL   CUDA   OpenACC    C++AMP   OpenMP   MPI</description><language>zh-cn</language><lastBuildDate>Thu, 10 Jan 2013 23:05:00 GMT</lastBuildDate><pubDate>Thu, 10 Jan 2013 23:05:00 GMT</pubDate><ttl>60</ttl><item><title>Linux进程间通信：管道及有名管道 </title><link>http://www.cppblog.com/jackdongy/archive/2013/01/07/197062.html</link><dc:creator>jackdong</dc:creator><author>jackdong</author><pubDate>Mon, 07 Jan 2013 03:33:00 GMT</pubDate><guid>http://www.cppblog.com/jackdongy/archive/2013/01/07/197062.html</guid><wfw:comment>http://www.cppblog.com/jackdongy/comments/197062.html</wfw:comment><comments>http://www.cppblog.com/jackdongy/archive/2013/01/07/197062.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jackdongy/comments/commentRss/197062.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jackdongy/services/trackbacks/197062.html</trackback:ping><description><![CDATA[<p style="text-indent: 2em"><strong><a href="http://linux.ccidnet.com/art/302/20061117/953467_1.html">http://linux.ccidnet.com/art/302/20061117/953467_1.html</a><br />管道及有名管道 </strong>
<p style="text-indent: 2em">在本系列序中作者概述了 linux 进程间通信的几种主要手段。其中管道和有名管道是最早的进程间通信机制之一，管道可用于具有亲缘关系进程间的通信，有名管道克服了管道没有名字的限制，因此，除具有管道所具有的功能外，它还允许无亲缘关系进程间的通信。 认清管道和有名管道的读写规则是在程序中应用它们的关键，本文在详细讨论了管道和有名管道的通信机制的基础上，用实例对其读写规则进行了程序验证，这样做有利于增强读者对读写规则的感性认识，同时也提供了应用范例。 
<p style="text-indent: 2em"><strong>1、 管道概述及相关API应用</strong> 
<p style="text-indent: 2em"><strong>1.1 管道相关的关键概念</strong> 
<p style="text-indent: 2em">管道是Linux支持的最初Unix IPC形式之一，具有以下特点： 
<p style="text-indent: 2em">管道是半双工的，数据只能向一个方向流动；需要双方通信时，需要建立起两个管道； 
<p style="text-indent: 2em">只能用于父子进程或者兄弟进程之间（具有亲缘关系的进程）； 
<p style="text-indent: 2em">单独构成一种独立的文件系统：管道对于管道两端的进程而言，就是一个文件，但它不是普通的文件，它不属于某种文件系统，而是自立门户，单独构成一种文件系统，并且只存在与内存中。 
<p style="text-indent: 2em">数据的读出和写入：一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾，并且每次都是从缓冲区的头部读出数据。 
<p style="text-indent: 2em"><strong>1.2管道的创建：</strong> 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>#include 
int pipe(int fd[2])</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">该函数创建的管道的两端处于一个进程中间，在实际应用中没有太大意义，因此，一个进程在由pipe()创建管道后，一般再fork一个子进程，然后通过管道实现父子进程间的通信（因此也不难推出，只要两个进程中存在亲缘关系，这里的亲缘关系指的是具有共同的祖先，都可以采用管道方式来进行通信）。 <br /></p>
<p style="text-indent: 2em"><strong>1.3管道的读写规则：</strong> 
<p style="text-indent: 2em">管道两端可分别用描述字fd[0]以及fd[1]来描述，需要注意的是，管道的两端是固定了任务的。即一端只能用于读，由描述字fd[0]表示，称其为管道读端；另一端则只能用于写，由描述字fd[1]来表示，称其为管道写端。如果试图从管道写端读取数据，或者向管道读端写入数据都将导致错误发生。一般文件的I/O函数都可以用于管道，如close、read、write等等。 
<p style="text-indent: 2em">从管道中读取数据： 
<p style="text-indent: 2em">如果管道的写端不存在，则认为已经读到了数据的末尾，读函数返回的读出字节数为0；当管道的写端存在时，如果请求的字节数目大于PIPE_BUF，则返回管道中现有的数据字节数，如果请求的字节数目不大于PIPE_BUF，则返回管道中现有数据字节数（此时，管道中数据量小于请求的数据量）；或者返回请求的字节数（此时，管道中数据量不小于请求的数据量）。注：（PIPE_BUF在include/linux/limits.h中定义，不同的内核版本可能会有所不同。Posix.1要求PIPE_BUF至少为512字节，red hat 7.2中为4096）。 
<p style="text-indent: 2em">关于管道的读规则验证： 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>* readtest.c *
#include 
#include 
#include 
main()
{
int pipe_fd[2];
pid_t pid;
char r_buf[100];
char w_buf[4];
char* p_wbuf;
int r_num;
int cmd;

memset(r_buf,0,sizeof(r_buf));
memset(w_buf,0,sizeof(r_buf));
p_wbuf=w_buf;
if(pipe(pipe_fd)&lt;0)
{
printf("pipe create error\n");
return -1;
}

if((pid=fork())==0)
{
printf("\n");
close(pipe_fd[1]);
sleep(3);//确保父进程关闭写端
r_num=read(pipe_fd[0],r_buf,100);
printf( "read num is %d the data read from the pipe is %d\n",r_num,atoi(r_buf));

close(pipe_fd[0]);
exit();
}
else if(pid&gt;0)
{
close(pipe_fd[0]);//read
strcpy(w_buf,"111");
if(write(pipe_fd[1],w_buf,4)!=-1)
printf("parent write over\n");
close(pipe_fd[1]);//write
printf("parent close fd[1] over\n");
sleep(10);
} 
}</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">程序输出结果： 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>* parent write over
* parent close fd[1] over
* read num is 4 the data read from the pipe is 111</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">附加结论：管道写端关闭后，写入的数据将一直存在，直到读出为止。 </p>
<p style="text-indent: 2em"></p>
<p style="text-indent: 2em">向管道中写入数据： 
<p style="text-indent: 2em">向管道中写入数据时，linux将不保证写入的原子性，管道缓冲区一有空闲区域，写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据，那么写操作将一直阻塞。 
<p style="text-indent: 2em">注：只有在管道的读端存在时，向管道中写入数据才有意义。否则，向管道中写入数据的进程将收到内核传来的SIFPIPE信号，应用程序可以处理该信号，也可以忽略（默认动作则是应用程序终止）。对管道的写规则的验证1：写端对读端存在的依赖性 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>#include 
#include 
main()
{
int pipe_fd[2];
pid_t pid;
char r_buf[4];
char* w_buf;
int writenum;
int cmd;

memset(r_buf,0,sizeof(r_buf));
if(pipe(pipe_fd)&lt;0)
{
printf("pipe create error\n");
return -1;
}

if((pid=fork())==0)
{
close(pipe_fd[0]);
close(pipe_fd[1]);
sleep(10); 
exit();
}
else if(pid&gt;0)
{
sleep(1); //等待子进程完成关闭读端的操作
close(pipe_fd[0]);//write
w_buf="111";
if((writenum=write(pipe_fd[1],w_buf,4))==-1)
printf("write to pipe error\n");
else 
printf("the bytes write to pipe is %d \n", writenum);

close(pipe_fd[1]);
} 
}</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">则输出结果为： Broken pipe,原因就是该管道以及它的所有fork()产物的读端都已经被关闭。如果在父进程中保留读端，即在写完pipe后，再关闭父进程的读端，也会正常写入pipe，读者可自己验证一下该结论。因此，在向管道写入数据时，至少应该存在某一个进程，其中管道读端没有被关闭，否则就会出现上述错误（管道断裂,进程收到了SIGPIPE信号，默认动作是进程终止）对管道的写规则的验证2：linux不保证写管道的原子性验证 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>#include 
#include 
#include 
main(int argc,char**argv)
{
int pipe_fd[2];
pid_t pid;
char r_buf[4096];
char w_buf[4096*2];
int writenum;
int rnum;
memset(r_buf,0,sizeof(r_buf)); 
if(pipe(pipe_fd)&lt;0)
{
printf("pipe create error\n");
return -1;
}

if((pid=fork())==0)
{
close(pipe_fd[1]);
while(1)
{
sleep(1); 
rnum=read(pipe_fd[0],r_buf,1000);
printf("child: readnum is %d\n",rnum);
}
close(pipe_fd[0]);

exit();
}
else if(pid&gt;0)
{
close(pipe_fd[0]);//write
memset(r_buf,0,sizeof(r_buf)); 
if((writenum=write(pipe_fd[1],w_buf,1024))==-1)
printf("write to pipe error\n");
else 
printf("the bytes write to pipe is %d \n", writenum);
writenum=write(pipe_fd[1],w_buf,4096);
close(pipe_fd[1]);
} 
}</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">输出结果： 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>the bytes write to pipe 1000
the bytes write to pipe 1000 //注意，此行输出说明了写入的非原子性
the bytes write to pipe 1000
the bytes write to pipe 1000
the bytes write to pipe 1000
the bytes write to pipe 120 //注意，此行输出说明了写入的非原子性
the bytes write to pipe 0
the bytes write to pipe 0
......</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">结论： 
<p style="text-indent: 2em">写入数目小于4096时写入是非原子的！ 
<p style="text-indent: 2em">如果把父进程中的两次写入字节数都改为5000，则很容易得出下面结论： 
<p style="text-indent: 2em">写入管道的数据量大于4096字节时，缓冲区的空闲空间将被写入数据（补齐），直到写完所有数据为止，如果没有进程读数据，则一直阻塞。 </p><br />
<p style="text-indent: 2em"><strong>1.4管道应用实例：</strong> 
<p style="text-indent: 2em">实例一：用于shell 
<p style="text-indent: 2em">管道可用于输入输出重定向，它将一个命令的输出直接定向到另一个命令的输入。比如，当在某个shell程序（Bourne shell或C shell等）键入who&#9474;wc -l后，相应shell程序将创建who以及wc两个进程和这两个进程间的管道。考虑下面的命令行： 
<p style="text-indent: 2em">$kill -l 运行结果见附一。 
<p style="text-indent: 2em">$kill -l | grep SIGRTMIN 运行结果如下： 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1
34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5
38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9
42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13
46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">实例二：用于具有亲缘关系的进程间通信 
<p style="text-indent: 2em">下面例子给出了管道的具体应用，父进程通过管道发送一些命令给子进程，子进程解析命令，并根据命令作相应处理。 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>#include 
#include 
main()
{
int pipe_fd[2];
pid_t pid;
char r_buf[4];
char** w_buf[256];
int childexit=0;
int i;
int cmd;

memset(r_buf,0,sizeof(r_buf));

if(pipe(pipe_fd)&lt;0)
{
printf("pipe create error\n");
return -1;
}
if((pid=fork())==0)
//子进程：解析从管道中获取的命令，并作相应的处理
{
printf("\n");
close(pipe_fd[1]);
sleep(2);

while(!childexit)
{ 
read(pipe_fd[0],r_buf,4);
cmd=atoi(r_buf);
if(cmd==0)
{
printf("child: receive command from parent over\n now child process exit\n");
childexit=1;
}

else if(handle_cmd(cmd)!=0)
return;
sleep(1);
}
close(pipe_fd[0]);
exit();
}
else if(pid&gt;0)
//parent: send commands to child
{
close(pipe_fd[0]);

w_buf[0]="003";
w_buf[1]="005";
w_buf[2]="777";
w_buf[3]="000";
for(i=0;i&lt;4;i++)
write(pipe_fd[1],w_buf,4);
close(pipe_fd[1]);
} 
}
//下面是子进程的命令处理函数（特定于应用）：
int handle_cmd(int cmd)
{
if((cmd&lt;0)||(cmd&gt;256))
//suppose child only support 256 commands
{
printf("child: invalid command \n");
return -1;
}
printf("child: the cmd from parent is %d\n", cmd);
return 0;
}</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em"><strong>1.5管道的局限性</strong> 
<p style="text-indent: 2em">管道的主要局限性正体现在它的特点上： 
<p style="text-indent: 2em">只支持单向数据流； 
<p style="text-indent: 2em">只能用于具有亲缘关系的进程之间； 
<p style="text-indent: 2em">没有名字； 
<p style="text-indent: 2em">管道的缓冲区是有限的（管道制存在于内存中，在管道创建时，为缓冲区分配一个页面大小）； 
<p style="text-indent: 2em">管道所传送的是无格式字节流，这就要求管道的读出方和写入方必须事先约定好数据的格式，比如多少字节算作一个消息（或命令、或记录）等等。 
<p style="text-indent: 2em"><strong>2、 有名管道概述及相关API应用</strong> 
<p style="text-indent: 2em"><strong>2.1 有名管道相关的关键概念</strong> 
<p style="text-indent: 2em">管道应用的一个重大限制是它没有名字，因此，只能用于具有亲缘关系的进程间通信，在有名管道（named pipe或FIFO）提出后，该限制得到了克服。FIFO不同于管道之处在于它提供一个路径名与之关联，以FIFO的文件形式存在于文件系统中。这样，即使与FIFO的创建进程不存在亲缘关系的进程，只要可以访问该路径，就能够彼此通过FIFO相互通信（能够访问该路径的进程以及FIFO的创建进程之间），因此，通过FIFO不相关的进程也能交换数据。值得注意的是，FIFO严格遵循先进先出（first in first out），对管道及FIFO的读总是从开始处返回数据，对它们的写则把数据添加到末尾。它们不支持诸如lseek()等文件定位操作。 
<p style="text-indent: 2em"><strong>2.2有名管道的创建</strong> 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>#include 
#include 
int mkfifo(const char * pathname, mode_t mode)</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">该函数的第一个参数是一个普通的路径名，也就是创建后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode 参数相同。如果mkfifo的第一个参数是一个已经存在的路径名时，会返回EEXIST错误，所以一般典型的调用代码首先会检查是否返回该错误，如果确实返回该错误，那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用于FIFO，如close、read、write等等。 
<p style="text-indent: 2em"><strong>2.3有名管道的打开规则</strong> 
<p style="text-indent: 2em">有名管道比管道多了一个打开操作：open。 
<p style="text-indent: 2em">FIFO的打开规则： 
<p style="text-indent: 2em">如果当前打开操作是为读而打开FIFO时，若已经有相应进程为写而打开该FIFO，则当前打开操作将成功返回；否则，可能阻塞直到有相应进程为写而打开该FIFO（当前打开操作设置了阻塞标志）；或者，成功返回（当前打开操作没有设置阻塞标志）。 
<p style="text-indent: 2em">如果当前打开操作是为写而打开FIFO时，如果已经有相应进程为读而打开该FIFO，则当前打开操作将成功返回；否则，可能阻塞直到有相应进程为读而打开该FIFO（当前打开操作设置了阻塞标志）；或者，返回ENXIO错误（当前打开操作没有设置阻塞标志）。对打开规则的验证参见附2。 </p><br />
<p style="text-indent: 2em"><strong>2.4有名管道的读写规则</strong> 
<p style="text-indent: 2em">从FIFO中读取数据： 
<p style="text-indent: 2em">约定：如果一个进程为了从FIFO中读取数据而阻塞打开FIFO，那么称该进程内的读操作为设置了阻塞标志的读操作。 
<p style="text-indent: 2em">如果有进程写打开FIFO，且当前FIFO内没有数据，则对于设置了阻塞标志的读操作来说，将一直阻塞。对于没有设置阻塞标志读操作来说则返回-1，当前errno值为EAGAIN，提醒以后再试。 
<p style="text-indent: 2em">对于设置了阻塞标志的读操作说，造成阻塞的原因有两种：当前FIFO内有数据，但有其它进程在读这些数据；另外就是FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入，不论信写入数据量的大小，也不论读操作请求多少数据量。 
<p style="text-indent: 2em">读打开的阻塞标志只对本进程第一个读操作施加作用，如果本进程内有多个读操作序列，则在第一个读操作被唤醒并完成读操作后，其它将要执行的读操作将不再阻塞，即使在执行读操作时，FIFO中没有数据也一样（此时，读操作返回0）。 
<p style="text-indent: 2em">如果没有进程写打开FIFO，则设置了阻塞标志的读操作会阻塞。 
<p style="text-indent: 2em">注：如果FIFO中有数据，则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞，此时，读操作会返回FIFO中现有的数据量。 
<p style="text-indent: 2em">向FIFO中写入数据： 
<p style="text-indent: 2em">约定：如果一个进程为了向FIFO中写入数据而阻塞打开FIFO，那么称该进程内的写操作为设置了阻塞标志的写操作。 
<p style="text-indent: 2em">对于设置了阻塞标志的写操作： 
<p style="text-indent: 2em">当要写入的数据量不大于PIPE_BUF时，linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数，则进入睡眠，直到当缓冲区中能够容纳要写入的字节数时，才开始进行一次性写操作。 
<p style="text-indent: 2em">当要写入的数据量大于PIPE_BUF时，linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域，写进程就会试图向管道写入数据，写操作在写完所有请求写的数据后返回。 
<p style="text-indent: 2em">对于没有设置阻塞标志的写操作： 
<p style="text-indent: 2em">当要写入的数据量大于PIPE_BUF时，linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后，写操作返回。 
<p style="text-indent: 2em">当要写入的数据量不大于PIPE_BUF时，linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数，写完后成功返回；如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数，则返回EAGAIN错误，提醒以后再写； 
<p style="text-indent: 2em">对FIFO读写规则的验证： 
<p style="text-indent: 2em">下面提供了两个对FIFO的读写程序，适当调节程序中的很少地方或者程序的命令行参数就可以对各种FIFO读写规则进行验证。 
<p style="text-indent: 2em">程序1：写FIFO的程序 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>#include 
#include 
#include 
#include 
#define FIFO_SERVER "/tmp/fifoserver"

main(int argc,char** argv)
//参数为即将写入的字节数
{
int fd;
char w_buf[4096*2];
int real_wnum;
memset(w_buf,0,4096*2);
if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)&lt;0)&amp;&amp;(errno!=EEXIST))
printf("cannot create fifoserver\n");

if(fd==-1)
if(errno==ENXIO)
printf("open error; no reading process\n");

fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
//设置非阻塞标志
//fd=open(FIFO_SERVER,O_WRONLY,0);
//设置阻塞标志
real_wnum=write(fd,w_buf,2048);
if(real_wnum==-1)
{
if(errno==EAGAIN)
printf("write to fifo error; try later\n");
}
else 
printf("real write num is %d\n",real_wnum);
real_wnum=write(fd,w_buf,5000);
//5000用于测试写入字节大于4096时的非原子性
//real_wnum=write(fd,w_buf,4096);
//4096用于测试写入字节不大于4096时的原子性

if(real_wnum==-1)
if(errno==EAGAIN)
printf("try later\n");
}</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">程序2：与程序1一起测试写FIFO的规则，第一个命令行参数是请求从FIFO读出的字节数 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>#include 
#include 
#include 
#include 
#define FIFO_SERVER "/tmp/fifoserver"

main(int argc,char** argv)
{
char r_buf[4096*2];
int fd;
int r_size;
int ret_size;
r_size=atoi(argv[1]);
printf("requred real read bytes %d\n",r_size);
memset(r_buf,0,sizeof(r_buf));
fd=open(FIFO_SERVER,O_RDONLY|O_NONBLOCK,0);
//fd=open(FIFO_SERVER,O_RDONLY,0);
//在此处可以把读程序编译成两个不同版本：阻塞版本及非阻塞版本
if(fd==-1)
{
printf("open %s for read error\n");
exit(); 
}
while(1)
{

memset(r_buf,0,sizeof(r_buf));
ret_size=read(fd,r_buf,r_size);
if(ret_size==-1)
if(errno==EAGAIN)
printf("no data avlaible\n");
printf("real read bytes %d\n",ret_size);
sleep(1);
} 
pause();
unlink(FIFO_SERVER);
}</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center><br />
<p style="text-indent: 2em">程序应用说明： 
<p style="text-indent: 2em">把读程序编译成两个不同版本： 
<p style="text-indent: 2em">阻塞读版本:br 
<p style="text-indent: 2em">以及非阻塞读版本nbr 
<p style="text-indent: 2em">把写程序编译成两个四个版本： 
<p style="text-indent: 2em">非阻塞且请求写的字节数大于PIPE_BUF版本：nbwg 
<p style="text-indent: 2em">非阻塞且请求写的字节数不大于PIPE_BUF版本：版本nbw 
<p style="text-indent: 2em">阻塞且请求写的字节数大于PIPE_BUF版本：bwg 
<p style="text-indent: 2em">阻塞且请求写的字节数不大于PIPE_BUF版本：版本bw 
<p style="text-indent: 2em">下面将使用br、nbr、w代替相应程序中的阻塞读、非阻塞读 
<p style="text-indent: 2em">验证阻塞写操作： 
<p style="text-indent: 2em">当请求写入的数据量大于PIPE_BUF时的非原子性： 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>nbr 1000 
bwg</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">当请求写入的数据量不大于PIPE_BUF时的原子性： 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>nbr 1000 
bw</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">验证非阻塞写操作： 
<p style="text-indent: 2em">当请求写入的数据量大于PIPE_BUF时的非原子性： 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>nbr 1000 
nbwg</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">请求写入的数据量不大于PIPE_BUF时的原子性： 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>nbr 1000 
nbw</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">不管写打开的阻塞标志是否设置，在请求写入的字节数大于4096时，都不保证写入的原子性。但二者有本质区别： 
<p style="text-indent: 2em">对于阻塞写来说，写操作在写满FIFO的空闲区域后，会一直等待，直到写完所有数据为止，请求写入的数据最终都会写入FIFO； 
<p style="text-indent: 2em">而非阻塞写则在写满FIFO的空闲区域后，就返回(实际写入的字节数)，所以有些数据最终不能够写入。 
<p style="text-indent: 2em">对于读操作的验证则比较简单，不再讨论。 
<p style="text-indent: 2em"><strong>2.5有名管道应用实例</strong> 
<p style="text-indent: 2em">在验证了相应的读写规则后，应用实例似乎就没有必要了。 
<p style="text-indent: 2em"><strong>小结：</strong> 
<p style="text-indent: 2em">管道常用于两个方面：（1）在shell中时常会用到管道（作为输入输入的重定向），在这种应用方式下，管道的创建对于用户来说是透明的；（2）用于具有亲缘关系的进程间通信，用户自己创建管道，并完成读写操作。 
<p style="text-indent: 2em">FIFO可以说是管道的推广，克服了管道无名字的限制，使得无亲缘关系的进程同样可以采用先进先出的通信机制进行通信。 
<p style="text-indent: 2em">管道和FIFO的数据是字节流，应用程序之间必须事先确定特定的传输"协议"，采用传播具有特定意义的消息。 
<p style="text-indent: 2em">要灵活应用管道及FIFO，理解它们的读写规则是关键。 
<p style="text-indent: 2em">附1：kill -l 的运行结果，显示了当前系统支持的所有信号： 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL
5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE
9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2
13) SIGPIPE 14) SIGALRM 15) SIGTERM 17) SIGCHLD
18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN
22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO
30) SIGPWR 31) SIGSYS 32) SIGRTMIN 33) SIGRTMIN+1
34) SIGRTMIN+2 35) SIGRTMIN+3 36) SIGRTMIN+4 37) SIGRTMIN+5
38) SIGRTMIN+6 39) SIGRTMIN+7 40) SIGRTMIN+8 41) SIGRTMIN+9
42) SIGRTMIN+10 43) SIGRTMIN+11 44) SIGRTMIN+12 45) SIGRTMIN+13
46) SIGRTMIN+14 47) SIGRTMIN+15 48) SIGRTMAX-15 49) SIGRTMAX-14
50) SIGRTMAX-13 51) SIGRTMAX-12 52) SIGRTMAX-11 53) SIGRTMAX-10
54) SIGRTMAX-9 55) SIGRTMAX-8 56) SIGRTMAX-7 57) SIGRTMAX-6
58) SIGRTMAX-5 59) SIGRTMAX-4 60) SIGRTMAX-3 61) SIGRTMAX-2
62) SIGRTMAX-1 63) SIGRTMAX</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em">除了在此处用来说明管道应用外，接下来的专题还要对这些信号分类讨论。 
<p style="text-indent: 2em">附2：对FIFO打开规则的验证（主要验证写打开对读打开的依赖性） 
<p style="text-indent: 2em">
<center><ccid_nobr>
<table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1">
<tbody>
<tr>
<td class="code" style="font-size: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>#include 
#include 
#include 
#include 
#define FIFO_SERVER "/tmp/fifoserver"

int handle_client(char*);
main(int argc,char** argv)
{
int r_rd;
int w_fd;
pid_t pid;

if((mkfifo(FIFO_SERVER,O_CREAT|O_EXCL)&lt;0)&amp;&amp;(errno!=EEXIST))
printf("cannot create fifoserver\n");
handle_client(FIFO_SERVER);

}

int handle_client(char* arg)
{
int ret;
ret=w_open(arg);
switch(ret)
{
case 0:
{ 
printf("open %s error\n",arg);
printf("no process has the fifo open for reading\n");
return -1;
}
case -1:
{
printf("something wrong with open the fifo except for ENXIO");
return -1;
}
case 1:
{
printf("open server ok\n");
return 1;
}
default:
{
printf("w_no_r return ????\n");
return 0;
}
} 
unlink(FIFO_SERVER);
}

int w_open(char*arg)
//0 open error for no reading
//-1 open error for other reasons
//1 open ok
{
if(open(arg,O_WRONLY|O_NONBLOCK,0)==-1)
{ if(errno==ENXIO)
{
return 0;
}
else
return -1;
}
return 1;

}</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center>
<p style="text-indent: 2em"><strong>参考文献：</strong> 
<p style="text-indent: 2em">UNIX网络编程第二卷：进程间通信，作者：W.Richard Stevens，译者：杨继张，清华大学出版社。丰富的UNIX进程间通信实例及分析，对Linux环境下的程序开发有极大的启发意义。 
<p style="text-indent: 2em">linux内核源代码情景分析（上、下），毛德操、胡希明著，浙江大学出版社，当要验证某个结论、想法时，最好的参考资料； 
<p style="text-indent: 2em">UNIX环境高级编程，作者：W.Richard Stevens，译者：尤晋元等，机械工业出版社。具有丰富的编程实例，以及关键函数伴随Unix的发展历程。 
<p style="text-indent: 2em">http://www.linux.org.tw/CLDP/gb/Secure-Programs-HOWTO/x346.html 点明linux下sigaction的实现基础，linux源码../kernel/signal.c更说明了问题； 
<p style="text-indent: 2em">pipe手册，最直接而可靠的参考资料 
<p style="text-indent: 2em">fifo手册，最直接而可靠的参考资料 
<p style="text-indent: 2em"><strong>关于作者</strong> 
<p style="text-indent: 2em">郑彦兴，男，现攻读国防科大计算机学院网络方向博士学位。您可以通过电子邮件 mlinux@163.com和他联系。 </p><img src ="http://www.cppblog.com/jackdongy/aggbug/197062.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jackdongy/" target="_blank">jackdong</a> 2013-01-07 11:33 <a href="http://www.cppblog.com/jackdongy/archive/2013/01/07/197062.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux下的C语言多线程编程</title><link>http://www.cppblog.com/jackdongy/archive/2013/01/07/197061.html</link><dc:creator>jackdong</dc:creator><author>jackdong</author><pubDate>Mon, 07 Jan 2013 03:24:00 GMT</pubDate><guid>http://www.cppblog.com/jackdongy/archive/2013/01/07/197061.html</guid><wfw:comment>http://www.cppblog.com/jackdongy/comments/197061.html</wfw:comment><comments>http://www.cppblog.com/jackdongy/archive/2013/01/07/197061.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jackdongy/comments/commentRss/197061.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jackdongy/services/trackbacks/197061.html</trackback:ping><description><![CDATA[<strong><a href="http://www.pcdog.com/edu/linux/13/11/y237287.html">http://www.pcdog.com/edu/linux/13/11/y237287.html</a><br />引言</strong> 
<p>&nbsp;&nbsp;&nbsp; 线程（thread）技术早在60年代就被提出，但真正应用<a href="http://www.pcdog.com/net/1243/index.html" target="_blank">多线程</a>到<a href="http://www.pcdog.com/special/1108/index.html" target="_blank">操作系统</a>中去，是在80年代中期，<a href="http://www.pcdog.com/special/1188/index.html" target="_blank">solaris</a>是这方面的佼佼者。传统的Unix也支持线程的概念，但是在一个进程（process）中只允许有一个线程，这样多线程就意味着多进程。现在，多</p>
<p>&nbsp;&nbsp;&nbsp; 为什么有了进程的概念后，还要再引入线程呢？使用多线程到底有哪些好处？什么的系统应该选用多线程？我们首先必须回答这些问题。 </p>
<p>&nbsp;&nbsp;&nbsp; 使用多线程的理由之一是和进程相比，它是一种非常"节俭"的多任务操作方式。我们知道，在<a href="http://www.pcdog.com/special/1355/index.html" target="_blank">Linux系统</a>下，启动一个新的进程必须分配给它独立的地址空间，建立众多的数据表来维护它的代码段、堆栈段和数据段，这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个线程，它们彼此之间使用相同的地址空间，共享大部分数据，启动一个线程所花费的空间远远小于启动一个进程所花费的空间，而且，线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。</p>
<p>&nbsp;&nbsp;&nbsp; 使用多线程的理由之二是线程间方便的通信机制。对不同进程来说，它们具有独立的数据空间，要进行数据的传递只能通过通信的方式进行，这种方式不仅费时，而且很不方便。线程则不然，由于同一进程下的线程之间共享数据空间，所以一个线程的数据可以直接为其它线程所用，这不仅快捷，而且方便。当然，数据的共享也带来其他一些问题，有的变量不能同时被两个线程所修改，有的子程序中声明为static的数据更有可能给多线程程序带来灾难性的打击，这些正是编写多线程程序时最需要注意的地方。</p>
<p>&nbsp;&nbsp;&nbsp; 除了以上所说的优点外，不和进程比较，多线程程序作为一种多任务、并发的工作方式，当然有以下的优点： </p>
<p>&nbsp;&nbsp;&nbsp; 1) 提高应用程序响应。这对图形界面的程序尤其有意义，当一个操作耗时很长时，整个系统都会等待这个操作，此时程序不会响应键盘、鼠标、菜单的操作，而使用多线程技术，将耗时长的操作（time consuming）置于一个新的线程，可以避免这种尴尬的情况。 <br />&nbsp;&nbsp;&nbsp; 2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时，不同的线程运行于不同的CPU上。 <br />&nbsp;&nbsp;&nbsp; 3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程，成为几个独立或半独立的运行部分，这样的程序会利于理解和修改。 </p>
<p>&nbsp;&nbsp;&nbsp; 下面我们先来尝试编写一个简单的多线程程序。<br /><br /><strong>简单的</strong><a href="http://www.pcdog.com/net/1243/index.html" target="_blank"><strong>多线程</strong></a><strong>编程 </strong></p>
<p><strong>&nbsp;&nbsp;&nbsp; </strong>Linux系统下的多线程遵循POSIX线程接口，称为pthread。编写Linux下的多线程程序，需要使用头文件pthread.h，连接时需要使用库libpthread.a。顺便说一下，Linux下pthread的实现是通过系统调用clone（）来实现的。clone（）是Linux所特有的系统调用，它的使用方式类似fork，关于clone（）的详细情况，有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序example1.c。 </p>
<p>&nbsp;</p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>/* example.c*/
#include &lt;stdio.h&gt;
#include &lt;pthread.h&gt;
void thread(void)
{
int i;
for(i=0;i&lt;3;i++)
printf("This is a pthread.\n");
}
int main(void)
{
pthread_t id;
int i,ret;
ret=pthread_create(&amp;id,NULL,(void *) thread,NULL);
if(ret!=0){
printf ("Create pthread error!\n");
exit (1);
}
for(i=0;i&lt;3;i++)
printf("This is the main process.\n");
pthread_join(id,NULL);
return (0);
} </div>
</pre></div>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 我们编译此程序： <br />&nbsp;&nbsp;&nbsp; gcc example1.c -lpthread -o example1 <br />&nbsp;&nbsp;&nbsp; 运行example1，我们得到如下结果： <br />&nbsp;&nbsp;&nbsp; This is the main process. <br />&nbsp;&nbsp;&nbsp; This is a pthread. <br />&nbsp;&nbsp;&nbsp; This is the main process. <br />&nbsp;&nbsp;&nbsp; This is the main process. <br />&nbsp;&nbsp;&nbsp; This is a pthread. <br />&nbsp;&nbsp;&nbsp; This is a pthread. <br />&nbsp;&nbsp;&nbsp; 再次运行，我们可能得到如下结果： <br />&nbsp;&nbsp;&nbsp; This is a pthread. <br />&nbsp;&nbsp;&nbsp; This is the main process. <br />&nbsp;&nbsp;&nbsp; This is a pthread. <br />&nbsp;&nbsp;&nbsp; This is the main process. <br />&nbsp;&nbsp;&nbsp; This is a pthread. <br />&nbsp;&nbsp;&nbsp; This is the main process. <br /><br />&nbsp;&nbsp;&nbsp; 前后两次结果不一样，这是两个线程争夺CPU资源的结果。上面的示例中，我们使用到了两个函数，　　pthread_create和pthread_join，并声明了一个pthread_t型的变量。 <br />&nbsp;&nbsp;&nbsp; pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义： <br />&nbsp;&nbsp;&nbsp; typedef unsigned long int pthread_t; <br />&nbsp;&nbsp;&nbsp; 它是一个线程的标识符。函数pthread_create用来创建一个线程，它的原型为： <br />&nbsp;&nbsp;&nbsp; extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr, <br />&nbsp;&nbsp;&nbsp; void *(*__start_routine) (void *), void *__arg)); <br />&nbsp;&nbsp;&nbsp; 第一个参数为指向线程标识符的指针，第二个参数用来设置线程属性，第三个参数是线程运行函数的起始地址，最后一个参数是运行函数的参数。这里，我们的函数thread不需要参数，所以最后一个参数设为空指针。第二个参数我们也设为空指针，这样将生成默认属性的线程。对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时，函数返回0，若不为0则说明创建线程失败，常见的错误返回代码为EAGAIN和EINVAL。前者表示系统限制创建新的线程，例如线程数目过多了；后者表示第二个参数代表的线程属性值非法。创建线程成功后，新创建的线程则运行参数三和参数四确定的函数，原来的线程则继续运行下一行代码。</p>
<p>&nbsp;&nbsp;&nbsp; 函数pthread_join用来等待一个线程的结束。函数原型为： <br />&nbsp;&nbsp;&nbsp; extern int pthread_join __P ((pthread_t __th, void **__thread_return)); </p>
<p>&nbsp;&nbsp;&nbsp; 第一个参数为被等待的线程标识符，第二个参数为一个用户定义的指针，它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞的函数，调用它的函数将一直等待到被等待的线程结束为止，当函数返回时，被等待线程的资源被收回。一个线程的结束有两种途径，一种是象我们上面的例子一样，函数结束了，调用它的线程也就结束了；另一种方式是通过函数pthread_exit来实现。它的函数原型为： <br />　　extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__)); </p>
<p>&nbsp;&nbsp;&nbsp; 唯一的参数是函数的返回代码，只要pthread_join中的第二个参数thread_return不是NULL，这个值将被传递给thread_return。最后要说明的是，一个线程不能被多个线程等待，否则第一个接收到信号的线程成功返回，其余调用pthread_join的线程则返回错误代码ESRCH。 </p>
<p>&nbsp;&nbsp;&nbsp; 在这一节里，我们编写了一个最简单的线程，并掌握了最常用的三个函数pthread_create，pthread_join和pthread_exit。下面，我们来了解线程的一些常用属性以及如何设置这些属性。</p>
<p>&nbsp;</p><strong>修改线程的属性 </strong>
<p>&nbsp;&nbsp;&nbsp; 在上一节的例子里，我们用pthread_create函数创建了一个线程，在这个线程中，我们使用了默认参数，即将该函数的第二个参数设为NULL。的确，对大多数程序来说，使用默认属性就够了，但我们还是有必要来了解一下线程的有关属性。 </p>
<p>&nbsp;&nbsp;&nbsp; 属性结构为pthread_attr_t，它同样在头文件/usr/include/pthread.h中定义，喜欢追根问底的人可以自己去查看。属性值不能直接设置，须使用相关函数进行操作，初始化的函数为pthread_attr_init，这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。 </p>
<p>&nbsp;&nbsp;&nbsp; 关于线程的绑定，牵涉到另外一个概念：轻进程（LWP：Light Weight Process）。轻进程可以理解为内核线程，它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的，一个轻进程可以控制一个或多个线程。默认状况下，启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的，这种状况即称为非绑定的。绑定状况下，则顾名思义，即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度，这是因为CPU时间片的调度是面向轻进程的，绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实时反应之类的要求。 </p>
<p>&nbsp;&nbsp;&nbsp; 设置线程绑定状态的函数为pthread_attr_setscope，它有两个参数，第一个是指向属性结构的指针，第二个是绑定类型，它有两个取值：PTHREAD_SCOPE_SYSTEM（绑定的）和PTHREAD_SCOPE_PROCESS（非绑定的）。下面的代码即创建了一个绑定的线程。 </p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>#include &lt;pthread.h&gt;
pthread_attr_t attr;
pthread_t tid;
/*初始化属性值，均设为默认值*/
pthread_attr_init(&amp;attr);
pthread_attr_setscope(&amp;attr, PTHREAD_SCOPE_SYSTEM);
pthread_create(&amp;tid, &amp;attr, (void *) my_function, NULL); </div>
</pre></div>
<p>&nbsp;&nbsp;&nbsp; 线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中，我们采用了线程的默认属性，即为非分离状态，这种情况下，原有的线程等待创建的线程结束。只有当pthread_join（）函数返回时，创建的线程才算终止，才能释放自己占用的系统资源。而分离线程不是这样子的，它没有被其他的线程所等待，自己运行结束了，线程也就终止了，马上释放系统资源。程序员应该根据自己的需要，选择适当的分离状态。设置线程分离状态的函数为pthread_attr_setdetachstate（pthread_attr_t *attr, int detachstate）。第二个参数可选为PTHREAD_CREATE_DETACHED（分离线程）和 PTHREAD _CREATE_JOINABLE（非分离线程）。这里要注意的一点是，如果设置一个线程为分离线程，而这个线程运行又非常快，它很可能在pthread_create函数返回之前就终止了，它终止以后就可能将线程号和系统资源移交给其他的线程使用，这样调用pthread_create的线程就得到了错误的线程号。要避免这种情况可以采取一定的同步措施，最简单的方法之一是可以在被创建的线程里调用pthread_cond_timewait函数，让这个线程等待一会儿，留出足够的时间让函数pthread_create返回。设置一段等待时间，是在<a href="http://www.pcdog.com/net/1243/index.html" target="_blank">多线程</a>编程里常用的方法。但是注意不要使用诸如wait（）之类的函数，它们是使整个进程睡眠，并不能解决线程同步的问题。 </p>
<p>&nbsp;&nbsp;&nbsp; 另外一个可能常用的属性是线程的优先级，它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数pthread_attr_setschedparam进行存放，一般说来，我们总是先取优先级，对取得的值修改后再存放回去。下面即是一段简单的例子。 </p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>#include &lt;pthread.h&gt;
#include &lt;sched.h&gt;
pthread_attr_t attr;
pthread_t tid;
sched_param param;
int newprio=20;
pthread_attr_init(&amp;attr);
pthread_attr_getschedparam(&amp;attr, &amp;param);
param.sched_priority=newprio;
pthread_attr_setschedparam(&amp;attr, &amp;param);
pthread_create(&amp;tid, &amp;attr, (void *)myfunction, myarg); </div>
</pre></div><br /><strong>线程的数据处理</strong> 
<p>&nbsp;&nbsp;&nbsp; 和进程相比，线程的最大优点之一是数据的共享性，各个进程共享父进程处沿袭的数据段，可以方便的获得、修改数据。但这也给<a href="http://www.pcdog.com/net/1243/index.html" target="_blank">多线程</a>编程带来了许多问题。我们必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的，即同时不能运行一个函数的多个拷贝（除非使用不同的数据段）。在函数中声明的静态变量常常带来问题，函数的返回值也会有问题。因为如果返回的是函数内部静态声明的空间的地址，则在一个线程调用该函数得到地址后使用该地址指向的数据时，别的线程可能调用此函数并修改了这一段数据。在进程中共享的变量必须用关键字volatile来定义，这是为了防止编译器在优化时（如gcc中使用-OX参数）改变它们的使用方式。为了保护变量，我们必须使用信号量、互斥等方法来保证我们对变量的正确使用。下面，我们就逐步介绍处理线程数据时的有关知识。 <br /><br />&nbsp;&nbsp;&nbsp; <strong>4.1 线程数据</strong></p>
<p><strong>&nbsp;&nbsp;&nbsp; </strong>在单线程的程序里，有两种基本的数据：全局变量和局部变量。但在多线程程序里，还有第三种数据类型：线程数据（TSD: Thread-Specific Data）。它和全局变量很象，在线程内部，各个函数可以象使用全局变量一样调用它，但它对线程外部的其它线程是不可见的。这种数据的必要性是显而易见的。例如我们常见的变量errno，它返回标准的出错信息。它显然不能是一个局部变量，几乎每个函数都应该可以调用它；但它又不能是一个全局变量，否则在A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量，我们就必须使用线程数据。我们为每个线程数据创建一个键，它和这个键相关联，在各个线程里，都使用这个键来指代线程数据，但在不同的线程里，这个键代表的数据是不同的，在同一个线程里，它代表同样的数据内容。 </p>
<p>&nbsp;&nbsp;&nbsp; 和线程数据相关的函数主要有4个：创建一个键；为一个键指定线程数据；从一个键读取线程数据；删除键。 <br />&nbsp;&nbsp;&nbsp; 创建键的函数原型为： <br />&nbsp;&nbsp;&nbsp; extern int pthread_key_create __P ((pthread_key_t *__key, <br />&nbsp;&nbsp;&nbsp; void (*__destr_function) (void *))); </p>
<p>&nbsp;&nbsp;&nbsp; 第一个参数为指向一个键值的指针，第二个参数指明了一个destructor函数，如果这个参数不为空，那么当每个线程结束时，系统将调用这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once ((pthread_once_t*once_control, void (*initroutine) (void)))一起使用，为了让这个键只被创建一次。函数pthread_once声明一个初始化函数，第一次调用pthread_once时它执行这个函数，以后的调用将被它忽略。 <br /><br />&nbsp;&nbsp;&nbsp; 在下面的例子中，我们创建一个键，并将它和某个数据相关联。我们要定义一个函数createWindow，这个函数定义一个图形窗口（数据类型为Fl_Window *，这是图形界面开发工具FLTK中的数据类型）。由于各个线程都会调用这个函数，所以我们使用线程数据。 </p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>/* 声明一个键*/
pthread_key_t myWinKey;
/* 函数 createWindow */
void createWindow ( void ) {
Fl_Window * win;
static pthread_once_t once= PTHREAD_ONCE_INIT;
/* 调用函数createMyKey，创建键*/
pthread_once ( &amp; once, createMyKey) ;
/*win指向一个新建立的窗口*/
win=new Fl_Window( 0, 0, 100, 100, "MyWindow");
/* 对此窗口作一些可能的设置工作，如大小、位置、名称等*/
setWindow(win);
/* 将窗口指针值绑定在键myWinKey上*/
pthread_setpecific ( myWinKey, win);
}
/* 函数 createMyKey，创建一个键，并指定了destructor */
void createMyKey ( void ) {
pthread_keycreate(&amp;myWinKey, freeWinKey);
}
/* 函数 freeWinKey，释放空间*/
void freeWinKey ( Fl_Window * win){
delete win;
} </div>
</pre></div>
<p>&nbsp;&nbsp;&nbsp; 这样，在不同的线程中调用函数createMyWin，都可以得到在线程内部均可见的窗口变量，这个变量通过函数pthread_getspecific得到。在上面的例子中，我们已经使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原型如下： <br />&nbsp;&nbsp;&nbsp; extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer)); <br />&nbsp;&nbsp;&nbsp; extern void *pthread_getspecific __P ((pthread_key_t __key)); </p>
<p>&nbsp;&nbsp;&nbsp; 这两个函数的参数意义和使用方法是显而易见的。要注意的是，用pthread_setspecific为一个键指定新的线程数据时，必须自己释放原有的线程数据以回收空间。这个过程函数pthread_key_delete用来删除一个键，这个键占用的内存将被释放，但同样要注意的是，它只释放键占用的内存，并不释放该键关联的线程数据所占用的内存资源，而且它也不会触发函数pthread_key_create中定义的destructor函数。线程数据的释放必须在释放键之前完成。</p><br />&nbsp; <strong>4.2 互斥锁</strong> 
<p>&nbsp;&nbsp;&nbsp; 互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见：假设各个线程向同一个文件顺序写入数据，最后得到的结果一定是灾难性的。 </p>
<p>&nbsp;&nbsp;&nbsp; 我们先看下面一段代码。这是一个读/写程序，它们公用一个缓冲区，并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态：有信息或没有信息。 </p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>void reader_function ( void );
void writer_function ( void );
char buffer;
int buffer_has_item=0;
pthread_mutex_t mutex;
struct timespec delay;
void main ( void ){
pthread_t reader;
/* 定义延迟时间*/
delay.tv_sec = 2;
delay.tv_nec = 0;
/* 用默认属性初始化一个互斥锁对象*/
pthread_mutex_init (&amp;mutex,NULL);
pthread_create(&amp;reader, pthread_attr_default, (void *)&amp;reader_function), NULL);
writer_function( );
}
void writer_function (void){
while(1){
/* 锁定互斥锁*/
pthread_mutex_lock (&amp;mutex);
if (buffer_has_item==0){
buffer=make_new_item( );
buffer_has_item=1;
}
/* 打开互斥锁*/
pthread_mutex_unlock(&amp;mutex);
pthread_delay_np(&amp;delay);
}
}
void reader_function(void){
while(1){
pthread_mutex_lock(&amp;mutex);
if(buffer_has_item==1){
consume_item(buffer);
buffer_has_item=0;
}
pthread_mutex_unlock(&amp;mutex);
pthread_delay_np(&amp;delay);
}
} </div>
</pre></div>
<p>&nbsp;&nbsp;&nbsp; 这里声明了互斥锁变量mutex，结构pthread_mutex_t为不公开的数据类型，其中包含一个系统分配的属性对象。函数pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁，须调用函数pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared，它有两个取值，PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步，后者用于同步本进程的不同线程。在上面的例子中，我们使用的是默认属性PTHREAD_PROCESS_ PRIVATE。后者用来设置互斥锁类型，可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制，一般情况下，选用最后一个默认属性。 </p>
<p>&nbsp;&nbsp;&nbsp; pthread_mutex_lock声明开始用互斥锁上锁，此后的代码直至调用pthread_mutex_unlock为止，均被上锁，即同一时间只能被一个线程调用执行。当一个线程执行到pthread_mutex_lock处时，如果该锁此时被另一个线程使用，那此线程被阻塞，即程序将等待到另一个线程释放此互斥锁。在上面的例子中，我们使用了pthread_delay_np函数，让线程睡眠一段时间，就是为了防止一个线程始终占据此函数。 </p>
<p>&nbsp;&nbsp;&nbsp; 上面的例子非常简单，就不再介绍了，需要提出的是在使用互斥锁的过程中很有可能会出现死锁：两个线程试图同时占用两个资源，并按不同的次序锁定相应的互斥锁，例如两个线程都需要锁定互斥锁1和互斥锁2，a线程先锁定互斥锁1，b线程先锁定互斥锁2，这时就出现了死锁。此时我们可以使用函数pthread_mutex_trylock，它是函数pthread_mutex_lock的非阻塞版本，当它发现死锁不可避免时，它会返回相应的信息，程序员可以针对死锁做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样，但最主要的还是要程序员自己在程序设计注意这一点。</p><br /><strong>4.3 条件变量 </strong>
<p><strong>&nbsp;&nbsp;&nbsp; </strong>前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信，互斥锁一个明显的缺点是它只有两种状态：锁定和非锁定。而条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足，它常和互斥锁一起使用。使用时，条件变量被用来阻塞一个线程，当条件不满足时，线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量，它将通知相应的条件变量唤醒一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来，条件变量被用来进行线承间的同步。 </p>
<p>&nbsp;&nbsp;&nbsp; 条件变量的结构为pthread_cond_t，函数pthread_cond_init（）被用来初始化一个条件变量。它的原型为： <br />&nbsp;&nbsp;&nbsp; extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr)); </p>
<p>&nbsp;&nbsp;&nbsp; 其中cond是一个指向结构pthread_cond_t的指针，cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件变量的属性结构，和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用，默认值是PTHREAD_ PROCESS_PRIVATE，即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件变量的函数为pthread_cond_ destroy（pthread_cond_t cond）。　 </p>
<p>&nbsp;&nbsp;&nbsp; 函数pthread_cond_wait（）使线程阻塞在一个条件变量上。它的函数原型为： <br />&nbsp;&nbsp;&nbsp; extern int pthread_cond_wait __P ((pthread_cond_t *__cond, <br />&nbsp;&nbsp;&nbsp; pthread_mutex_t *__mutex)); </p>
<p>&nbsp;&nbsp;&nbsp; 线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数pthread_cond_broadcast唤醒，但是要注意的是，条件变量只是起阻塞和唤醒线程的作用，具体的判断条件还需用户给出，例如一个变量是否为0等等，这一点我们从后面的例子中可以看到。线程被唤醒后，它将重新检查判断条件是否满足，如果还不满足，一般说来线程应该仍阻塞在这里，被等待被下一次唤醒。这个过程一般用while语句实现。 </p>
<p>&nbsp;&nbsp;&nbsp; 另一个用来阻塞线程的函数是pthread_cond_timedwait（），它的原型为： <br />&nbsp;&nbsp;&nbsp; extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond, <br />&nbsp;&nbsp;&nbsp; pthread_mutex_t *__mutex, __const struct timespec *__abstime)); </p>
<p>&nbsp;&nbsp;&nbsp; 它比函数pthread_cond_wait（）多了一个时间参数，经历abstime段时间后，即使条件变量不满足，阻塞也被解除。 <br />&nbsp;&nbsp;&nbsp; 函数pthread_cond_signal（）的原型为： <br />&nbsp;&nbsp;&nbsp; extern int pthread_cond_signal __P ((pthread_cond_t *__cond)); </p>
<p>&nbsp;&nbsp;&nbsp; 它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时，哪一个线程被唤醒是由线程的调度策略所决定的。要注意的是，必须用保护条件变量的互斥锁来保护这个函数，否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出，从而造成无限制的等待。下面是使用函数pthread_cond_wait（）和函数pthread_cond_signal（）的一个简单的例子。 </p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count　() {
pthread_mutex_lock (&amp;count_lock);
while(count==0)
pthread_cond_wait( &amp;count_nonzero, &amp;count_lock);
count=count -1;
pthread_mutex_unlock (&amp;count_lock);
}
increment_count(){
pthread_mutex_lock(&amp;count_lock);
if(count==0)
pthread_cond_signal(&amp;count_nonzero);
count=count+1;
pthread_mutex_unlock(&amp;count_lock);
} 　　</div>
</pre></div>
<p>&nbsp;&nbsp;&nbsp; count值为0时，decrement函数在pthread_cond_wait处被阻塞，并打开互斥锁count_lock。此时，当调用到函数increment_count时，pthread_cond_signal（）函数改变条件变量，告知decrement_count（）停止阻塞。读者可以试着让两个线程分别运行这两个函数，看看会出现什么样的结果。 </p>
<p>&nbsp;&nbsp;&nbsp; 函数pthread_cond_broadcast（pthread_cond_t *cond）用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁，所以必须小心使用这个函数。</p><br /><br />&nbsp;<strong>4.4 信号量 </strong>
<p><strong>&nbsp;&nbsp;&nbsp; </strong>信号量本质上是一个非负的整数计数器，它被用来控制对公共资源的访问。当公共资源增加时，调用函数sem_post（）增加信号量。只有当信号量值大于０时，才能使用公共资源，使用后，函数sem_wait（）减少信号量。函数sem_trywait（）和函数pthread_ mutex_trylock（）起同样的作用，它是函数sem_wait（）的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数，它们都在头文件/usr/include/semaphore.h中定义。 </p>
<p>&nbsp;&nbsp;&nbsp; 信号量的数据类型为结构sem_t，它本质上是一个长整型的数。函数sem_init（）用来初始化一个信号量。它的原型为： <br />&nbsp;&nbsp;&nbsp; extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value)); </p>
<p>&nbsp;&nbsp;&nbsp; sem为指向信号量结构的一个指针；pshared不为０时此信号量在进程间共享，否则只能为当前进程的所<a href="http://www.pcdog.com/special/1150/index.html" target="_blank">有线</a>程共享；value给出了信号量的初始值。 </p>
<p>&nbsp;&nbsp;&nbsp; 函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时，调用这个函数会使其中的一个线程不在阻塞，选择机制同样是由线程的调度策略决定的。 <br />&nbsp;&nbsp;&nbsp; 函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0，解除阻塞后将sem的值减一，表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem )是函数sem_wait（）的非阻塞版本，它直接将信号量sem的值减一。 <br />&nbsp;&nbsp;&nbsp; 函数sem_destroy(sem_t *sem)用来释放信号量sem。 </p>
<p>&nbsp;&nbsp;&nbsp; 下面我们来看一个使用信号量的例子。在这个例子中，一共有4个线程，其中两个线程负责从文件读取数据到公共的缓冲区，另两个线程从缓冲区读取数据作不同的处理（加和乘运算）。 </p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>/* File sem.c */
#include &lt;stdio.h&gt;
#include &lt;pthread.h&gt;
#include &lt;semaphore.h&gt;
#define MAXSTACK 100 
int stack[MAXSTACK][2];
int size=0;
sem_t sem;
/* 从文件1.dat读取数据，每读一次，信号量加一*/
void ReadData1(void){
FILE *fp=fopen("1.dat","r");
while(!feof(fp)){
fscanf(fp,"%d %d",&amp;stack[size][0],&amp;stack[size][1]);
sem_post(&amp;sem);
++size;
}
fclose(fp);
}
/*从文件2.dat读取数据*/
void ReadData2(void){
FILE *fp=fopen("2.dat","r");
while(!feof(fp)){
fscanf(fp,"%d %d",&amp;stack[size][0],&amp;stack[size][1]);
sem_post(&amp;sem);
++size;
}
fclose(fp);
}
/*阻塞等待缓冲区有数据，读取数据后，释放空间，继续等待*/
void HandleData1(void){
while(1){
sem_wait(&amp;sem);
printf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1],
stack[size][0]+stack[size][1]);
--size;
}
}
void HandleData2(void){
while(1){
sem_wait(&amp;sem);
printf("Multiply:%d*%d=%d\n",stack[size][0],stack[size][1],
stack[size][0]*stack[size][1]);
--size;
}
}
int main(void){
pthread_t t1,t2,t3,t4;
sem_init(&amp;sem,0,0);
pthread_create(&amp;t1,NULL,(void *)HandleData1,NULL);
pthread_create(&amp;t2,NULL,(void *)HandleData2,NULL);
pthread_create(&amp;t3,NULL,(void *)ReadData1,NULL);
pthread_create(&amp;t4,NULL,(void *)ReadData2,NULL);
/* 防止程序过早退出，让它在此无限期等待*/
pthread_join(t1,NULL);
} </div>
</pre></div>
<p>&nbsp;&nbsp;&nbsp; 在<a href="http://www.pcdog.com/special/1193/index.html" target="_blank">Linux</a>下，我们用命令gcc -lpthread sem.c -o sem生成可执行文件sem。 我们事先编辑好数据文件1.dat和2.dat，假设它们的内容分别为1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10 ，我们运行sem，得到如下的结果： <br />&nbsp;&nbsp;&nbsp; Multiply:-1*-2=2 <br />&nbsp;&nbsp;&nbsp; Plus:-1+-2=-3 <br />&nbsp;&nbsp;&nbsp; Multiply:9*10=90 <br />&nbsp;&nbsp;&nbsp; Plus:-9+-10=-19 <br />&nbsp;&nbsp;&nbsp; Multiply:-7*-8=56 <br />&nbsp;&nbsp;&nbsp; Plus:-5+-6=-11 <br />&nbsp;&nbsp;&nbsp; Multiply:-3*-4=12 <br />&nbsp;&nbsp;&nbsp; Plus:9+10=19 <br />&nbsp;&nbsp;&nbsp; Plus:7+8=15 <br />&nbsp;&nbsp;&nbsp; Plus:5+6=11 <br /><br />&nbsp;&nbsp;&nbsp; 从中我们可以看出各个线程间的竞争关系。而数值并未按我们原先的顺序显示出来这是由于size这个数值被各个线程任意修改的缘故。这也往往是<a href="http://www.pcdog.com/net/1243/index.html" target="_blank">多线程</a>编程要注意的问题。 <br /><br />&nbsp;&nbsp;<strong>&nbsp; 小结</strong>&nbsp; </p>
<p>&nbsp;&nbsp;&nbsp; 多线程编程是一个很有意思也很有用的技术，使用多线程技术的网络蚂蚁是目前最常用的下载工具之一，使用多线程技术的grep比单线程的grep要快上几倍，类似的例子还有很多。希望大家能用多线程技术写出高效实用的好程序来。 </p><img src ="http://www.cppblog.com/jackdongy/aggbug/197061.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jackdongy/" target="_blank">jackdong</a> 2013-01-07 11:24 <a href="http://www.cppblog.com/jackdongy/archive/2013/01/07/197061.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux下的管道编程技术</title><link>http://www.cppblog.com/jackdongy/archive/2013/01/07/197055.html</link><dc:creator>jackdong</dc:creator><author>jackdong</author><pubDate>Mon, 07 Jan 2013 02:05:00 GMT</pubDate><guid>http://www.cppblog.com/jackdongy/archive/2013/01/07/197055.html</guid><wfw:comment>http://www.cppblog.com/jackdongy/comments/197055.html</wfw:comment><comments>http://www.cppblog.com/jackdongy/archive/2013/01/07/197055.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jackdongy/comments/commentRss/197055.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jackdongy/services/trackbacks/197055.html</trackback:ping><description><![CDATA[<p align="left"><a href="http://www.pcdog.com/edu/linux/13/11/y237288.html">http://www.pcdog.com/edu/linux/13/11/y237288.html</a><br /><br />管道技术是<a href="http://www.pcdog.com/special/1193/index.html" target="_blank">Linux</a>的一种基本的进程间<a href="http://www.pcdog.com/special/1328/index.html" target="_blank">通信技术</a>。在本文中，我们将为读者介绍管道技术的模型，匿名管道和命名管道技术的定义和区别，以及这两种管道的创建方法。同时，阐述如何在应用程序和命令行中通过管道进行通信的详细方法。 <br /><br /><strong>&nbsp;&nbsp;&nbsp; 一、管道技术模型&nbsp;<br /></strong><br /><strong>&nbsp;&nbsp;&nbsp; </strong>管道技术是Linux操作系统中历来已久的一种进程间通信机制。所有的管道技术，无论是半双工的匿名管道，还是命名管道，它们都是利用FIFO排队模型来指挥进程间的通信。对于管道，我们可以形象地把它们当作是连接两个实体的一个单向连接器。例如，请看下面的命令：<br />&nbsp;&nbsp;</p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>    ls -1 | wc -l </div>
</pre></div>
<p align="left"><br />&nbsp;&nbsp;&nbsp; 该命令首先创建两个进程，一个对应于ls &#8211;1，另一个对应于wc &#8211;l。然后，把第一个进程的标准输出设为第二个进程的标准输入（如图1所示）。它的作用是计算当前目录下的文件数量。<br /></p>
<p align="center"><a href="http://www.pcdog.com/ArtImage/20071112/uv50_1.jpg" target="_blank"><img border="0" alt="Linux下的管道编程技术（图一）" src="http://www.pcdog.com/ArtImage/20071112/uv50_1.jpg" width="590" /> </a>
<center><a href="http://www.pcdog.com/ArtImage/20071112/uv50_1.jpg" target="_blank">点击查看大图</a></center>
<p>&nbsp;</p>
<p align="center">图1：管道示意图&nbsp;</p>
<p align="left"><br /><br />&nbsp;&nbsp;&nbsp; 如上图所示，前面的例子实际上就是在两个命令之间建立了一根管道（有时我们也将之称为命令的流水线操作）。第一个命令ls执行后产生的输出作为了第二个命令wc的输入。这是一个半双工通信，因为通信是单向的。两个命令之间的连接的具体工作，是由内核来完成的。下面我们将会看到，除了命令之外，应用程序也可以使用管道进行连接。<br /><br /><strong>二、信号和消息的区别</strong> <br /><br />&nbsp;&nbsp;&nbsp; 我们知道，进程间的信号通信机制在传递信息时是以信号为载体的，但管道通信机制的信息载体是消息。那么信号和消息之间的区别在哪里呢？&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>首先，在数据内容方面，信号只是一些预定义的代码，用于表示系统发生的某一状况；消息则为一组连续语句或符号，不过量也不会太大。在作用方面，信号担任进程间少量信息的传送，一般为内核程序用来通知用户进程一些异常情况的发生；消息则用于进程间交换彼此的数据。&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>在发送时机方面，信号可以在任何时候发送；信息则不可以在任何时刻发送。在发送者方面，信号不能确定发送者是谁；信息则知道发送者是谁。在发送对象方面，信号是发给某个进程；消息则是发给消息队列。在处理方式上，信号可以不予理会；消息则是必须处理的。在数据传输效率方面，信号不适合进大量的信息传输，因为它的效率不高；消息虽然不适合大量的数据传送，但它的效率比信号强，因此适于中等数量的数据传送。&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; 三、管道和命名管道的区别</strong>&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>我们知道，命名管道和管道都可以在进程间传送消息，但它们也是有区别的。&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>管道技术只能用于连接具有共同祖先的进程，例如父子进程间的通信，它无法实现不同用户的进程间的信息共享。再者，管道不能常设，当访问管道的进程终止时，管道也就撤销。这些限制给它的使用带来不少限制，但是命名管道却克服了这些限制。&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>命名管道也称为FIFO，是一种永久性的机构。FIFO文件也具有文件名、文件长度、访问许可权等属性，它也能像其它<a href="http://www.pcdog.com/special/1193/index.html" target="_blank">Linux</a>文件那样被打开、关闭和删除，所以任何进程都能找到它。换句话说，即使是不同祖先的进程，也可以利用命名管道进行通信。&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>如果想要全双工通信，那最好使用Sockets API。下面我们分别介绍这两种管道，然后详细说明用来进行管道编程的编程接口和系统级命令。 <br /><br /><strong>四、管道编程技术</strong>&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>在程序中利用管道进行通信时，根据通信主体大体可以分为两种情况：一种是具有共同祖先的进程间的通信，比较简单；另一种是任意进程间通信，相对较为复杂。下面我们先从较为简单的进程内通信开始介绍。&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>1. 具有共同祖先的进程间通信管道编程&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>为了了解管道编程技术，我们先举一个例子。在这个例中，我们将在进程中新建一个管道，然后向它写入一个消息，管道读取消息后将其发出。代码如下所示：&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>示例代码1：管道程序示例&nbsp;<br /></p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div><strong>    1: #include &lt;unistd.h&gt;</strong>
<strong>    2: #include &lt;stdio.h&gt;</strong>
<strong>    3: #include &lt;string.h&gt;</strong>
<strong>    4:
    5: #define MAX_LINE 80 </strong>
<strong>    6: #define PIPE_STDIN 0 </strong>
<strong>    7: #define PIPE_STDOUT 1 </strong>
<strong>    8:
    9: int main()
    10: ...{
    11: const char *string=...{"A sample message."};
    12: int ret, myPipe[2];
    13: char buffer[MAX_LINE+1];
    14:
    15: /**//* 建立管道 */</strong>
<strong>    16: ret = pipe( myPipe );
    17:
    18: if (ret == 0) ...{
19:
20: /**//* 将消息写入管道 */</strong>
<strong>21: write( myPipe[PIPE_STDOUT], string, strlen(string) );
22:
23: /**//* 从管道读取消息 */</strong>
<strong>24: ret = read( myPipe[PIPE_STDIN], buffer, MAX_LINE );
25:
26: /**//* 利用Null结束字符串 */</strong>
<strong>27: buffer[ ret ] = 0;
28:
29: printf("%s\n", buffer);
30:
31: }</strong>
<strong>32:
33: return 0;
34: }</strong>
<strong></strong></div>
</pre></div>
<p align="left"></p>
<p><strong>&nbsp;&nbsp;&nbsp; <br /></strong>&nbsp;&nbsp;&nbsp; 上面的示例代码中，我们利用pipe调用新建了一个管道，参见第16行代码。 我们还建立了一个由两个元素组成的数组，用来描述我们的管道。我们的管道被定义为两个单独的文件描述符，一个用来输入，一个用来输出。我们能从管道的一端输入，然后从另一端读出。如果调用成功，pipe函数返回值为0。返回后，数组myPipe中存放的是两个新的文件描述符，其中元素myPipe[1]包含的文件描述符用于管道的输入，元素myPipe[0] 包含的文件描述符用于管道的输出。 <br /><br />&nbsp;&nbsp;&nbsp; 在第21行代码，我们利用write函数把消息写入管道。站在应用程序的角度，它是在向stdout输出。现在，该管道存有我们的消息，我们可以利用第24行的read函数来读它。对于应用程序来说，我们是利用stdin描述符从管道读取消息的。read函数把从管道读取的数据存放到buffer变量中。然后在buffer变量的末尾添加一个NULL，这样就能利用printf函数正确的输出它了。在本例中的管道可以利用下图解释： <br /></p>
<p align="center"><a href="http://www.pcdog.com/ArtImage/20071112/uv50_2.jpg" target="_blank"><img border="0" alt="Linux下的管道编程技术（图二）" src="http://www.pcdog.com/ArtImage/20071112/uv50_2.jpg" width="590" /> </a>
<center><a href="http://www.pcdog.com/ArtImage/20071112/uv50_2.jpg" target="_blank">点击查看大图</a></center>
<p>&nbsp;</p>
<p align="center">图2：示例代码1中半双工管道的示意图 </p>
<p>&nbsp;&nbsp;&nbsp; 这个例子中，通信是在具有共同祖先的进程间发生的，即父进程和子进程通信。这样做局限性太大，但我们只是用它来给读者一个感性的认识。接下来，我们将介绍更为高级的进程间的管道通信。</p>
<p align="left"><strong>&nbsp;2.进程间通信管道编程</strong>&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>在利用管道技术进行编程时，处理要用到上面介绍的pipe函数外，还用到另外三个函数，如下所示。 <br /><br /> pipe函数：该函数用于创建一个新的匿名管道。 <br /><br /> dup函数：该函数用于拷贝文件描述符。 <br /><br /> mkfifo函数：该函数用于创建一个命名管道（fifo）。&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>当然，在管道通信过程中还用到其它函数，到时我们会加以介绍。需要注意的是，说到底，管道无非就是一对文件描述符，因此任何能够操作文件操作符的函数都可以使用管道。这包括但不限于这些函数：select、read、write、 fcntl、freopen，等等。&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; 2.1函数pipe</strong>&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>函数pipe用来建立一个新的管道，该管道用两个文件描述符进行描述。函数pipe的原型如下所示：<br /></p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>#include &lt;unistd.h&gt;
    int pipe( int fds[2] );
</div>
</pre></div>
<p align="left"></p>
<p><strong>&nbsp;&nbsp;&nbsp; </strong>当调用成功时，函数pipe返回值为0，否则返回值为-1。成功返回时，数组fds被填入两个有效的文件描述符。数组的第一个元素中的文件描述符供应用程序读取之用，数组的第二个元素中的文件描述符可以用来供应用程序写入。&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>下面我们考察在一个包含多个进程的应用程序中的管道示例。在该程序中（见示例代码2），第14行用于创建一个管道，然后进程在第16行分叉，变成一个父进程和一个子进程。在子进程中，我们尝试从（在第18行建立的）管道的输入描述符读取，这时该进程将被挂起，直到管道中有可以读取的内容为止。&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>读完后，我们用NULL作为读取的内容的结束符，这样的话，读的这些内容就能使用printf函数正确打印输出了。父进程先是利用存放在thePipe[1]中的&#8220;写文件标识符&#8221;向管道写入测试字符串，然后就使用wait函数来等待子进程退出。&nbsp;<br /><br /><strong>&nbsp;&nbsp;&nbsp; </strong>在我们的这个程序中需要加以注意的是，我们的子进程是如何继承父进程利用pipe函数建立的文件描述符的，以及如何利用该文件描述符进行通信的。函数fork一旦执行，子进程会继承父进程的功能和管道的文件描述符，但对于内核来说，父进程和子进程是平等的，它们是独立运行的。也就是说，两个进程分别具有单独的内存空间，它们正是通过pipe函数来互通有无的。<br /></p>
<p align="left"></p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>示例代码2：演示两个进程间的管道模型的代码

     1:       #include &lt;stdio.h&gt;
     2:       #include &lt;unistd.h&gt;
     3:       #include &lt;string.h&gt;
     4:       #include &lt;wait.h&gt;
     5:
     6:       #define MAX_LINE        80
     7:
     8:       int main()
     9:       ...{
     10:         int thePipe[2], ret;
     11:         char buf[MAX_LINE+1];
     12:         const char *testbuf=...{"a test string."};
     13:
     14:         if ( pipe( thePipe ) == 0 ) ...{
     15:
     16:           if (fork() == 0) ...{
     17:
     18:             ret = read( thePipe[0], buf, MAX_LINE );
     19:             buf[ret] = 0;
     20:             printf( "Child read %s\n", buf );
     21:
     22:           } else ...{
     23:
     24:             ret = write( thePipe[1], testbuf, strlen(testbuf) );
     25:             ret = wait( NULL );
     26:
     27:           }
     28:
     29:         }
     30:
     31:         return 0;
     32:       }
</div>
</pre></div>
<p align="left"></p>
<p><br /><br />&nbsp;&nbsp;&nbsp; 需要注意的是，在这个示例程序中我们没有说明如何关闭管道，因为一旦进程结束，与管道有关的资源将被自动释放。尽管如此，为了养成一种良好的编程习惯，最好利用close调用来关闭管道的描述符，如下所示：&nbsp;<br /></p>
<p align="left"></p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div> ret = pipe( myPipe );
    ...
    close( myPipe[0] );
close( myPipe[1] );
</div>
</pre></div>
<p align="left"><br />&nbsp;&nbsp;&nbsp; 如果管道的写入端关闭，但是还有进程尝试从管道读取的话，将被返回0，用来指出管道已不可用，并且应当关闭它。如果管道的读出端关闭，但是还有进程尝试向管道写入的话，试图写入的进程将收到一个SIGPIPE信号，至于信号的具体处理则要视其信号处理程序而定了。<br /><br /><strong>2.2 dup函数和dup2函数 <br /><br /></strong>&nbsp;&nbsp;&nbsp; dup和dup2也是两个非常有用的调用，它们的作用都是用来复制一个文件的描述符。它们经常用来重定向进程的stdin、stdout和stderr。这两个函数的原型如下所示： <br /></p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>#include &lt;unistd.h&gt;
    int dup( int oldfd );
    int dup2( int oldfd, int targetfd )
</div>
</pre></div>
<p align="left">&nbsp;&nbsp;&nbsp; 利用函数dup，我们可以复制一个描述符。传给该函数一个既有的描述符，它就会返回一个新的描述符，这个新的描述符是传给它的描述符的拷贝。这意味着，这两个描述符共享同一个<a href="http://www.pcdog.com/special/1002/index.html" target="_blank">数据结构</a>。例如，如果我们对一个文件描述符执行lseek操作，得到的第一个文件的位置和第二个是一样的。下面是用来说明dup函数使用方法的代码片段：<br /></p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>    int fd1, fd2;
    ...
fd2 = dup( fd1 );
</div>
</pre></div>
<p align="left">&nbsp;&nbsp;&nbsp; 需要注意的是，我们可以在调用fork之前建立一个描述符，这与调用dup建立描述符的效果是一样的，子进程也同样会收到一个复制出来的描述符。 <br /><br />&nbsp;&nbsp;&nbsp; dup2函数跟dup函数相似，但dup2函数允许调用者规定一个有效描述符和目标描述符的id。dup2函数成功返回时，目标描述符（dup2函数的第二个参数）将变成源描述符（dup2函数的第一个参数）的复制品，换句话说，两个文件描述符现在都指向同一个文件，并且是函数第一个参数指向的文件。下面我们用一段代码加以说明： </p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>     int oldfd;
    oldfd = open("app_log", (O_RDWR | O_CREATE), 0644 );
    dup2( oldfd, 1 );
    close( oldfd );
</div>
</pre></div>
<p align="left">&nbsp;&nbsp;&nbsp; 本例中，我们打开了一个新文件，称为&#8220;app_log&#8221;，并收到一个文件描述符，该描述符叫做fd1。我们调用dup2函数，参数为oldfd和1，这会导致用我们新打开的文件描述符替换掉由1代表的文件描述符（即stdout，因为标准输出文件的id为1）。任何写到stdout的东西，现在都将改为写入名为&#8220;app_log&#8221;的文件中。需要注意的是，dup2函数在复制了oldfd之后，会立即将其关闭，但不会关掉新近打开的文件描述符，因为文件描述符1现在也指向它。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 下面我们介绍一个更加深入的示例代码。回忆一下本文前面讲的命令行管道，在那里，我们将ls &#8211;1命令的标准输出作为标准输入连接到wc &#8211;l命令。接下来，我们就用一个C程序来加以说明这个过程的实现。代码如下面的示例代码3所示。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 在示例代码3中，首先在第9行代码中建立一个管道，然后将应用程序分成两个进程：一个子进程（第13&#8211;16行）和一个父进程（第20&#8211;23行）。接下来，在子进程中首先关闭stdout描述符（第13行），然后提供了ls &#8211;1命令功能，不过它不是写到stdout（第13行），而是写到我们建立的管道的输入端，这是通过dup函数来完成重定向的。在第14行，使用dup2函数把stdout重定向到管道（pfds[1]）。之后，马上关掉管道的输入端。然后，使用execlp函数把子进程的映像替换为命令ls &#8211;1的进程映像，一旦该命令执行，它的任何输出都将发给管道的输入端。&nbsp;<br /><br />&nbsp;&nbsp;&nbsp; 现在来研究一下管道的接收端。从代码中可以看出，管道的接收端是由父进程来担当的。首先关闭stdin描述符（第20行），因为我们不会从机器的键盘等标准设备文件来接收数据的输入，而是从其它程序的输出中接收数据。然后，再一次用到dup2函数（第21行），让stdin变成管道的输出端，这是通过让文件描述符0（即常规的stdin）等于pfds[0]来实现的。关闭管道的stdout端（pfds[1]），因为在这里用不到它。最后，使用execlp函数把父进程的映像替换为命令wc -1的进程映像，命令wc -1把管道的内容作为它的输入（第23行）。<br /></p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>示例代码3：利用C实现命令的流水线操作的代码

     1:       #include &lt;stdio.h&gt;
     2:       #include &lt;stdlib.h&gt;
     3:       #include &lt;unistd.h&gt;
     4:
     5:       int main()
     6:       ...{
     7:         int pfds[2];
     8:
     9:         if ( pipe(pfds) == 0 ) ...{
     10:
     11:           if ( fork() == 0 ) ...{
     12:
     13:             close(1);
     14:             dup2( pfds[1], 1 );
     15:             close( pfds[0] );
     16:             execlp( "ls", "ls", "-1", NULL );
     17:
     18:           } else ...{
     19:
     20:             close(0);
     21:             dup2( pfds[0], 0 );
     22:             close( pfds[1] );
     23:             execlp( "wc", "wc", "-l", NULL );
     24:
     25:           }
     26:
     27:         }
     28:
     29:         return 0;
     30:       }

</div>
</pre></div>
<p align="left"><br />&nbsp;&nbsp;&nbsp;&nbsp; 在该程序中，需要格外关注的是，我们的子进程把它的输出重定向的管道的输入，然后，父进程将它的输入重定向到管道的输出。这在实际的应用程序开发中是非常有用的一种技术。<br /><br /><strong>&nbsp; 2.3 mkfifo函数 <br /><br /></strong>&nbsp;&nbsp;&nbsp; mkfifo函数的作用是在文件系统中创建一个文件，该文件用于提供FIFO功能，即命名管道。前边讲的那些管道都没有名字，因此它们被称为匿名管道，或简称管道。对文件系统来说，匿名管道是不可见的，它的作用仅限于在父进程和子进程两个进程间进行通信。而命名管道是一个可见的文件，因此，它可以用于任何两个进程之间的通信，不管这两个进程是不是父子进程，也不管这两个进程之间有没有关系。Mkfifo函数的原型如下所示： </p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div> #include &lt;sys/types.h&gt;
    #include &lt;sys/stat.h&gt;
int mkfifo( const char *pathname, mode_t mode );
</div>
</pre></div>
<p align="left"></p>
<p>&nbsp;&nbsp;&nbsp; &nbsp;mkfifo函数需要两个参数，第一个参数（pathname）是将要在文件系统中创建的一个专用文件。第二个参数（mode）用来规定FIFO的读写权限。Mkfifo函数如果调用成功的话，返回值为0；如果调用失败返回值为-1。下面我们以一个实例来说明如何使用mkfifo函数建一个fifo，具体代码如下所示：</p>
<p align="left"></p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>    int ret;
    ...
    ret = mkfifo( "/tmp/cmd_pipe", S_IFIFO | 0666 );
    if (ret == 0) ...{
      // 成功建立命名管道
    } else ...{
      // 创建命名管道失败
}
</div>
</pre></div>
<p align="left"></p>
<p>&nbsp;&nbsp;&nbsp; 在这个例子中，利用/tmp目录中的cmd_pipe文件建立了一个命名管道（即fifo）。之后，就可以打开这个文件进行读写操作，并以此进行通信了。命名管道一旦打开，就可以利用典型的输入输出函数从中读取内容。举例来说，下面的代码段向我们展示了如何通过fgets函数来从管道中读取内容：</p>
<p align="left"></p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div> pfp = fopen( "/tmp/cmd_pipe", "r" );
    ...
ret = fgets( buffer, MAX_LINE, pfp );
</div>
</pre></div>
<p align="left"></p>
<p>&nbsp;&nbsp;&nbsp; 我们还能向管道中写入内容，下面的代码段向我们展示了利用fprintf函数向管道写入的具体方法：</p>
<p align="left"></p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>  pfp = fopen( "/tmp/cmd_pipe", "w+ );
    ...
ret = fprintf( pfp, "Here&#8217;s a test string!\n" );
</div>
</pre></div>
<p align="left"></p>
<p><br />&nbsp;&nbsp;&nbsp; 对命名管道来说，除非写入方主动打开管道的读取端，否则读取方是无法打开命名管道的。Open调用执行后，读取方将被锁住，直到写入方出现为止。尽管命名管道有这样的局限性，但它仍不失为一种有效的进程间通信工具。 <br /><br />&nbsp;&nbsp;&nbsp; 上面介绍的是与管道有关的一些系统调用，下面介绍管道命令相关的系统命令。<br /><strong>五、与管道相关的系统命令</strong> <br /><br />&nbsp;&nbsp;&nbsp; 现在开始，我们来研究与进程间通信密切相关的一些系统命令。首先介绍的是mkfifo命令，它的功能与mkfifo系统调用相似，只不过它是用来在命令行中建立一个命名管道。 <br /><br />&nbsp;&nbsp;&nbsp; 在命令行下建立fifo的专用文件，即命名管道的常用方法有两个，mkfifo命令便是其中之一。mkfifo命令的一般用法如下所示： </p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>mkfifo [options] name</div>
</pre></div>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 这里的options一般为-m，即模式，用以指出读写权限；name是要创建的管道的名称，必要时可以加上路径。如果我们没有规定权限，该命令会采取默认值0644。这里以一个具体实例来说明如何在/tmp目录下面建立一个称为cmd_pipe的命名管道：<br /></p>
<p>&nbsp;</p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>$ mkfifo /tmp/cmd_pipe</div>
</pre></div>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 下面用例子说明如何给命名管道指定读写权限。这里我们先将前面建立的管道删掉，然后重新建立管道，并指定管道的权限为0644，当然您也可以指定其他权限：</p>
<p>&nbsp;</p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>    $ rm cmd_pipe
$ mkfifo -m 0644 /tmp/cmd_pipe
</div>
</pre></div>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 上面的权限一经建立，就能够在命令行行下通过此管道进行通信了。比如，可以在一个终端上，利用cat命令来读取管道：</p>
<p>&nbsp;</p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>$ cat cmd_pipe</div>
</pre></div>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 当输入该命令后，我们的进程就会被挂起，等待写入程序打开此管道。现在，在另一个终端上利用echo命令向这个命名管道写入：<br /></p>
<p>&nbsp;</p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>    $ echo Hi &gt; cmd_pipe</div>
</pre></div>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 这个命令结束后，要读取该管道的程序（即cat）将被唤醒，然后结束。为醒目起见，这里列出完整的读取方（也就是读取管道的程序）输入的命令和得到的结果：</p>
<p>&nbsp;</p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>$ cat cmd_pipe
Hi
$
</div>
</pre></div>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp; 由此看来，命名管道不仅在C程序中非常有用，而且在脚本中作用也很大。当然，如果组合使用，效果也是很好的。<br /><br />&nbsp;&nbsp;&nbsp; 除了mkfifo命令外，mknod命令也可以用来创建命名管道，其用法如下所示：</p>
<p>&nbsp;</p>
<div style="overflow: auto; width: 500px"><pre style="border-top: black 1px solid; border-right: black 1px solid; border-bottom: black 1px solid; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: black 1px solid; padding-right: 4px; background-color: rgb(237,237,237)"><div>    $ mknod cmd_pipe p</div>
</pre></div>
<p>&nbsp;</p>
<p><br />&nbsp;&nbsp;&nbsp; 该命令执行后，将在当前目录下创建一个命名管道cmd_pipe，p用于指出建立的是命名管道。 <br /><br /><strong>&nbsp;&nbsp;&nbsp; 六、小结</strong> <br /><br />&nbsp;&nbsp;&nbsp; 在这篇文章中，我们介绍了管道和命名管道的概念，详细的说明了应用程序和命令行创建管道的方法，以及通过它们进行通信的I/O机制。然后，讨论了如何利用dup和 dup2命令来进行输入输出重定向。我们希望本文能够帮您更好的了解<a href="http://www.pcdog.com/special/1193/index.html" target="_blank">Linux</a>下的管道技术。</p>
<p>&nbsp;</p>
<p align="left"><br /></p><img src ="http://www.cppblog.com/jackdongy/aggbug/197055.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jackdongy/" target="_blank">jackdong</a> 2013-01-07 10:05 <a href="http://www.cppblog.com/jackdongy/archive/2013/01/07/197055.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ubuntu下ibus的设置 </title><link>http://www.cppblog.com/jackdongy/archive/2013/01/05/196959.html</link><dc:creator>jackdong</dc:creator><author>jackdong</author><pubDate>Sat, 05 Jan 2013 00:59:00 GMT</pubDate><guid>http://www.cppblog.com/jackdongy/archive/2013/01/05/196959.html</guid><wfw:comment>http://www.cppblog.com/jackdongy/comments/196959.html</wfw:comment><comments>http://www.cppblog.com/jackdongy/archive/2013/01/05/196959.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jackdongy/comments/commentRss/196959.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jackdongy/services/trackbacks/196959.html</trackback:ping><description><![CDATA[<a href="http://blog.csdn.net/lixiaoyuaini/article/details/5551852">http://blog.csdn.net/lixiaoyuaini/article/details/5551852</a><br /><br /><span style="color: rgb(0,0,255)"><span style="color: rgb(0,0,0)">ubuntu 装好后，默认的ibus输入法只能输入单个汉字，因此需要自己修改。首先在终端卸载ibus：<br /><span style="color: rgb(56,118,29)">sudo apt-get remove ibus</span> <br />完成后运行安装<br /><span style="color: rgb(56,118,29)">sudo apt-get install ibus<br />sudo apt-get install ibus-pinyin</span> <br /><br />一般系统会提示：</span> <br /></span>IBus 已经成功启动！如果你不能正常使用 IBus，请将下面代码加入到 $HOME/.bashrc中，并重新登录桌面。<br />export GTK_IM_MODULE=ibus<br />export XMODIFIERS=@im=ibus<br />export QT_IM_MODULE=ibus<br /><br />因此可以自己修改.bashrc文件。此文件原内容如下蓝色部分所示，红色部分为根据系统添加的内容。<br /><span style="color: rgb(0,0,255); background-color: rgb(147,196,125)"><br /># ~/.bashrc: executed by bash(1) for non-login shells.<br /># see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)<br /># for examples<br /><br /># If not running interactively, don't do anything<br />[ -z "$PS1" ] &amp;&amp; return<br /><br /># don't put duplicate lines in the history. See bash(1) for more options<br /># don't overwrite GNU Midnight Commander's setting of `ignorespace'.<br />HISTCONTROL=$HISTCONTROL${HISTCONTROL+,}ignoredups<br /># ... or force ignoredups and ignorespace<br />HISTCONTROL=ignoreboth<br /><br /># append to the history file, don't overwrite it<br />shopt -s histappend<br /><br /># for setting history length see HISTSIZE and HISTFILESIZE in bash(1)<br /><br /># check the window size after each command and, if necessary,<br /># update the values of LINES and COLUMNS.<br />shopt -s checkwinsize<br /><br /># make less more friendly for non-text input files, see lesspipe(1)<br />[ -x /usr/bin/lesspipe ] &amp;&amp; eval "$(SHELL=/bin/sh lesspipe)"<br /><br /># set variable identifying the chroot you work in (used in the prompt below)<br />if [ -z "$debian_chroot" ] &amp;&amp; [ -r /etc/debian_chroot ]; then<br />debian_chroot=$(cat /etc/debian_chroot)<br />fi<br /><br /># set a fancy prompt (non-color, unless we know we "want" color)<br />case "$TERM" in<br />xterm-color) color_prompt=yes;;<br />esac<br /><br /># uncomment for a colored prompt, if the terminal has the capability; turned<br /># off by default to not distract the user: the focus in a terminal window<br /># should be on the output of commands, not on the prompt<br />#force_color_prompt=yes<br /><br />if [ -n "$force_color_prompt" ]; then<br />if [ -x /usr/bin/tput ] &amp;&amp; tput setaf 1 &gt;&amp;/dev/null; then<br /># We have color support; assume it's compliant with Ecma-48<br /># (ISO/IEC-6429). (Lack of such support is extremely rare, and such<br /># a case would tend to support setf rather than setaf.)<br />color_prompt=yes<br />else<br />color_prompt=<br />fi<br />fi<br /><br />if [ "$color_prompt" = yes ]; then<br />PS1='${debian_chroot:+($debian_chroot)}/[/033[01;32m/]/u@/h/[/033[00m/]:/[/033[01;34m/]/w/[/033[00m/]/$ '<br />else<br />PS1='${debian_chroot:+($debian_chroot)}/u@/h:/w/$ '<br />fi<br />unset color_prompt force_color_prompt<br /><br /># If this is an xterm set the title to user@host:dir<br />case "$TERM" in<br />xterm*|rxvt*)<br />PS1="/[/e]0;${debian_chroot:+($debian_chroot)}/u@/h: /w/a/]$PS1"<br />;;<br />*)<br />;;<br />esac<br /><br /># enable color support of ls and also add handy aliases<br />if [ -x /usr/bin/dircolors ]; then<br />test -r ~/.dircolors &amp;&amp; eval "$(dircolors -b ~/.dircolors)" || eval "$(dircolors -b)"<br />alias ls='ls --color=auto'<br />#alias dir='dir --color=auto'<br />#alias vdir='vdir --color=auto'<br /><br />alias grep='grep --color=auto'<br />alias fgrep='fgrep --color=auto'<br />alias egrep='egrep --color=auto'<br />fi<br /><br /># some more ls aliases<br />#alias ll='ls -l'<br />#alias la='ls -A'<br />#alias l='ls -CF'<br /><br /># Alias definitions.<br /># You may want to put all your additions into a separate file like<br /># ~/.bash_aliases, instead of adding them here directly.<br /># See /usr/share/doc/bash-doc/examples in the bash-doc package.<br /><br />if [ -f ~/.bash_aliases ]; then<br />. ~/.bash_aliases<br />fi<br /><br /># enable programmable completion features (you don't need to enable<br /># this, if it's already enabled in /etc/bash.bashrc and /etc/profile<br /># sources /etc/bash.bashrc).<br />if [ -f /etc/bash_completion ] &amp;&amp; ! shopt -oq posix; then<br />. /etc/bash_completion<br />fi</span> <br /><br /><span style="color: rgb(255,0,0)">export GTK_IM_MODULE=ibus<br />export XMODIFIERS=@im=ibus<br />export QT_IM_MODULE=ibus</span> 
<p>&nbsp;</p>
<p><span style="color: rgb(255,0,0)">然后重启。<br /></span></p><img src ="http://www.cppblog.com/jackdongy/aggbug/196959.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jackdongy/" target="_blank">jackdong</a> 2013-01-05 08:59 <a href="http://www.cppblog.com/jackdongy/archive/2013/01/05/196959.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>2011-03-04 20:26 Linux ./configure --prefix命令</title><link>http://www.cppblog.com/jackdongy/archive/2013/01/04/196940.html</link><dc:creator>jackdong</dc:creator><author>jackdong</author><pubDate>Fri, 04 Jan 2013 06:47:00 GMT</pubDate><guid>http://www.cppblog.com/jackdongy/archive/2013/01/04/196940.html</guid><wfw:comment>http://www.cppblog.com/jackdongy/comments/196940.html</wfw:comment><comments>http://www.cppblog.com/jackdongy/archive/2013/01/04/196940.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jackdongy/comments/commentRss/196940.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jackdongy/services/trackbacks/196940.html</trackback:ping><description><![CDATA[<a href="http://hi.baidu.com/raybin_yang/item/52fb4315ed99de05d0d66da2">http://hi.baidu.com/raybin_yang/item/52fb4315ed99de05d0d66da2</a><br /><br />
<div class="content-head clearfix">
<div class="content-other-info"><span>2011-03-04 20:26</span> </div>
<h2 class="title content-title">Linux ./configure --prefix命令</h2></div>
<div id="content" class="content mod-cs-content text-content clearfix">
<p><a href="http://blog.csdn.net/xiyangfan/archive/2010/02/24/5321790.aspx">http://blog.csdn.net/xiyangfan/archive/2010/02/24/5321790.aspx</a></p>
<p><a href="http://blog.dormforce.net/rox/2008/09/26/configure-prefixxxx%E7%9A%84%E4%BD%9C%E7%94%A8/">http://blog.dormforce.net/rox/2008/09/26/configure-prefixxxx%E7%9A%84%E4%BD%9C%E7%94%A8/</a></p>
<p>源码的安装一般由3个步骤组成：配置（configure）、编译（make）、安装（make install），具体的安装方法一般作者都会给出文档，这里主要讨论配置（configure）。Configure是一个可执行脚本，它有很多选项，使用命令./configure &#8211;help输出详细的选项列表，如下：<br />-bash-3.00# ./configure --help<br />Usage: configure [options] [host]<br />Options: [defaults in brackets after descriptions]<br />Configuration:<br />--cache-file=FILE &nbsp; &nbsp; cache test results in FILE<br />--help &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print this message<br />--no-create &nbsp; &nbsp; &nbsp; &nbsp; do not create output files<br />--quiet, --silent &nbsp; &nbsp; do not print `checking...' messages<br />--version &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; print the version of autoconf that created configure<br />Directory and file names:<br />--prefix=PREFIX &nbsp; &nbsp; &nbsp; install architecture-independent files in PREFIX<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [/usr/local]<br />--exec-prefix=EPREFIX &nbsp; install architecture-dependent files in EPREFIX<br />&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; [same as prefix]<br />--bindir=DIR &nbsp; &nbsp; &nbsp; &nbsp; user executables in DIR [EPREFIX/bin]<br />&#8230;&#8230;&#8230;.(省略若干)<br />很多的选项，个人认为，你可以忽略其他的一切，但请把&#8212;prefix加上。这里以安装supersparrow-0.0.0为例，我们打算把它安装到目录 /usr/local/supersparrow,于是在supersparrow-0.0.0目录执行带选项的脚本./configure --prefix=/usr/local/supersparrow,执行成功后再编译、安装（make，make install）；安装完成将自动生成目录supersparrow,而且该软件所有的文件都被复制到这个目录。为什么要指定这个安装目录？是为了以后的维护方便，如果没有用这个选项，安装过程结束后，该软件所需的软件被复制到不同的系统目录下，很难弄清楚到底复制了那些文件、都复制到哪里去了&#8212;基本上是一塌糊涂。<br /><br />用了&#8212;prefix选项的另一个好处是卸载软件或移植软件。当某个安装的软件不再需要时，只须简单的删除该安装目录，就可以把软件卸载得干干净净；移植软件只需拷贝整个目录到另外一个机器即可（相同的操作系统）。<br /><br />一个小选项有这么方便的作用，建议在实际工作中多多使用</p>---不指定prefix，可执行文件默认放在/usr /local/bin，库文件默认放在/usr/local/lib，配置文件默认放在/usr/local/etc。其它的资源文件放在/usr /local/share。要卸载这个程序，要么在原来的make目录下用一次make uninstall（前提是make文件指定过uninstall）,要么去上述目录里面把相关的文件一个个手工删掉。指定prefix，直接删掉一个文件夹就够了。</div><img src ="http://www.cppblog.com/jackdongy/aggbug/196940.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jackdongy/" target="_blank">jackdong</a> 2013-01-04 14:47 <a href="http://www.cppblog.com/jackdongy/archive/2013/01/04/196940.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ubuntu--top命令详解  </title><link>http://www.cppblog.com/jackdongy/archive/2012/10/24/193815.html</link><dc:creator>jackdong</dc:creator><author>jackdong</author><pubDate>Wed, 24 Oct 2012 14:43:00 GMT</pubDate><guid>http://www.cppblog.com/jackdongy/archive/2012/10/24/193815.html</guid><wfw:comment>http://www.cppblog.com/jackdongy/comments/193815.html</wfw:comment><comments>http://www.cppblog.com/jackdongy/archive/2012/10/24/193815.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jackdongy/comments/commentRss/193815.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jackdongy/services/trackbacks/193815.html</trackback:ping><description><![CDATA[<div><h2>top 命令详解</h2>   <div id="content" mod-cs-content="" text-content=""  clearfix"=""> <p><strong>版权声明</strong>：可以任意转载，但转载时必须标明原作者charlee、原始链接<a href="http://tech.idv2.com/2006/08/16/top-command/">http://tech.idv2.com/2006/08/16/top-command/</a>以及本声明。</p><p>top命令是Linux下常用的性能分析工具，能够实时显示系统中各个进程的资源占用状况，类似于Windows的任务管理器。下面详细介绍它的使用方法。 </p><ul><li><a href="http://tech.idv2.com/2006/08/16/top-command/#content_1_0">认识top的显示结果</a>    <ul><li><a href="http://tech.idv2.com/2006/08/16/top-command/#content_1_1">统计信息区</a></li><li><a href="http://tech.idv2.com/2006/08/16/top-command/#content_1_2">进程信息区</a></li></ul>    </li><li><a href="http://tech.idv2.com/2006/08/16/top-command/#content_1_3">更改显示内容</a></li></ul><hr />认识top的显示结果<p>top命令的显示结果如下所示：</p>top - 01:06:48 up  1:22,  1 user,  load average: 0.06, 0.60, 0.48<br />Tasks:  29 total,   1 running,  28 sleeping,   0 stopped,   0 zombie<br />Cpu(s):  0.3% us,  1.0% sy,  0.0% ni, 98.7% id,  0.0% wa,  0.0% hi,  0.0% si<br />Mem:    191272k total,   173656k used,    17616k free,    22052k buffers<br />Swap:   192772k total,        0k used,   192772k free,   123988k cached<br />PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND<br />1379 root      16   0  7976 2456 1980 S  0.7  1.3   0:11.03 sshd<br />14704 root      16   0  2128  980  796 R  0.7  0.5   0:02.72 top<br />1 root      16   0  1992  632  544 S  0.0  0.3   0:00.90 init<br />2 root      34  19     0    0    0 S  0.0  0.0   0:00.00 ksoftirqd/0<br />3 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 watchdog/0统计信息区<p>前五行是系统整体的统计信息。第一行是任务队列信息，同 uptime 命令的执行结果。其内容如下：</p>                         01:06:48            当前时间                             up 1:22            系统运行时间，格式为时:分                            1 user             当前登录用户数                            load average: 0.06, 0.60,  0.48            系统负载，即任务队列的平均长度。<br />            三个数值分别为 1分钟、5分钟、15分钟前到现在的平均值。            <p>第二、三行为进程和CPU的信息。当有多个CPU时，这些内容可能会超过两行。内容如下：</p>                         Tasks: 29 total            进程总数                             1 running            正在运行的进程数                            28  sleeping            睡眠的进程数                            0 stopped             停止的进程数                            0 zombie            僵尸进程数                             Cpu(s): 0.3% us            用户空间占用CPU百分比                             1.0% sy            内核空间占用CPU百分比                             0.0% ni            用户进程空间内改变过优先级的进程占用CPU百分比                             98.7% id            空闲CPU百分比                            0.0% wa             等待输入输出的CPU时间百分比                            0.0% hi                                          0.0% si                         <p>最后两行为内存信息。内容如下：</p>                         Mem: 191272k total            物理内存总量                             173656k used            使用的物理内存总量                             17616k free            空闲内存总量                            22052k  buffers            用作内核缓存的内存量                            Swap: 192772k  total            交换区总量                            0k used             使用的交换区总量                            192772k free            空闲交换区总量                             123988k cached            缓冲的交换区总量。<br />            内存中的内容被换出到交换区，而后又被换入到内存，但使用过的交换区尚未被覆盖，<br />            该数值即为这些<strong>内容已存在于内存中</strong>的交换区的大小。<br />            相应的内存再次被换出时可不必再对交换区写入。            进程信息区<p>统计信息区域的下方显示了各个进程的详细信息。首先来认识一下各列的含义。</p>                         序号            列名            含义                                     a            PID            进程id                             b            PPID            父进程id                            c             RUSER            Real user name                            d             UID            进程所有者的用户id                            e             USER            进程所有者的用户名                            f             GROUP            进程所有者的组名                            g            TTY             启动进程的终端名。不是从终端启动的进程则显示为 ?                            h             PR            优先级                            i            NI             nice值。负值表示高优先级，正值表示低优先级                            j            P             最后使用的CPU，仅在多CPU环境下有意义                            k             %CPU            上次更新到现在的CPU时间占用百分比                            l             TIME            进程使用的CPU时间总计，单位秒                            m             TIME+            进程使用的CPU时间总计，单位1/100秒                            n             %MEM            进程使用的<strong>物理内存</strong>百分比                             o            VIRT            进程使用的虚拟内存总量，单位kb。VIRT=SWAP+RES                             p            SWAP             进程使用的虚拟内存中，被换出的大小，单位kb。                            q            RES             进程使用的、未被换出的物理内存大小，单位kb。RES=CODE+DATA                            r             CODE            可执行代码占用的<strong>物理</strong>内存大小，单位kb                            s            DATA            可执行代码以外的部分(数据段+栈)占用的<strong>物理</strong>内 存大小，单位kb                            t            SHR             共享内存大小，单位kb                            u            nFLT             页面错误次数                            v            nDRT             最后一次写入到现在，被修改过的页面数。                            w            S             进程状态。<br />            <strong>D</strong>=不可中断的睡眠状态<br />            <strong>R</strong>=运行<br />            <strong>S</strong>=睡眠<br />            <strong>T</strong>=跟踪/停止<br />            <strong>Z</strong>= 僵尸进程                            x            COMMAND            命令名/命令行                             y            WCHAN             若该进程在睡眠，则显示睡眠中的系统函数名                            z            Flags             任务标志，参考 sched.h            <p>默认情况下仅显示比较重要的 PID、USER、PR、NI、VIRT、RES、SHR、S、%CPU、%MEM、TIME+、COMMAND 列。可以通过下面的快捷键来更改显示内容。</p>更改显示内容<p>通过 <strong>f </strong>键可以选择显示的内容。按 f 键之后会显示列的列表，按 a-z 即可显示或隐藏对应的列，最后按回车键确定。</p><p>按 <strong>o</strong> 键可以改变列的显示顺序。按小写的 a-z 可以将相应的列向右移动，而大写的 A-Z 可以将相应的列向左移动。最后按回车键确定。</p><p>按大写的 <strong>F</strong> 或 <strong>O</strong> 键，然后按 a-z 可以将进程按照相应的列进行排序。而大写的 <strong>R</strong> 键可以将当前的排序倒转。</p> </div></div><img src ="http://www.cppblog.com/jackdongy/aggbug/193815.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jackdongy/" target="_blank">jackdong</a> 2012-10-24 22:43 <a href="http://www.cppblog.com/jackdongy/archive/2012/10/24/193815.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>使用编译器来使用宏变量 </title><link>http://www.cppblog.com/jackdongy/archive/2012/10/21/193599.html</link><dc:creator>jackdong</dc:creator><author>jackdong</author><pubDate>Sun, 21 Oct 2012 04:41:00 GMT</pubDate><guid>http://www.cppblog.com/jackdongy/archive/2012/10/21/193599.html</guid><wfw:comment>http://www.cppblog.com/jackdongy/comments/193599.html</wfw:comment><comments>http://www.cppblog.com/jackdongy/archive/2012/10/21/193599.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/jackdongy/comments/commentRss/193599.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/jackdongy/services/trackbacks/193599.html</trackback:ping><description><![CDATA[<a href="http://blog.csdn.net/bendanban/article/details/7753995">http://blog.csdn.net/bendanban/article/details/7753995</a><br />刚学了一招，可以使用编译器的-D选项来定义程序中使用的宏。 
<div style="font-size: 13px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; word-break: break-all; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: #cccccc 1px solid; padding-right: 5px; width: 98%; background-color: #eeeeee"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" /><span style="color: #000000">#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">stdio.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" /></span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;main(</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;argc,&nbsp;</span><span style="color: #0000ff">char</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">**</span><span style="color: #000000">argv)<br /><img onclick="this.style.display='none'; Codehighlighter1_51_177_Open_Text.style.display='none'; Codehighlighter1_51_177_Closed_Image.style.display='inline'; Codehighlighter1_51_177_Closed_Text.style.display='inline';" id="Codehighlighter1_51_177_Open_Image" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockStart.gif"><img onclick="this.style.display='none'; Codehighlighter1_51_177_Closed_Text.style.display='none'; Codehighlighter1_51_177_Open_Image.style.display='inline'; Codehighlighter1_51_177_Open_Text.style.display='inline';" id="Codehighlighter1_51_177_Closed_Image" style="display: none" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ContractedBlock.gif"></span><span id="Codehighlighter1_51_177_Closed_Text" style="border-top: #808080 1px solid; border-right: #808080 1px solid; border-bottom: #808080 1px solid; border-left: #808080 1px solid; display: none; background-color: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"  alt="" /></span><span id="Codehighlighter1_51_177_Open_Text"><span style="color: #000000">{<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif"  alt="" />&nbsp;&nbsp;&nbsp;&nbsp;#ifdef&nbsp;MY_MAC<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif"  alt="" />&nbsp;&nbsp;&nbsp;&nbsp;printf(</span><span style="color: #000000">"</span><span style="color: #000000">Hello&nbsp;-D.\n</span><span style="color: #000000">"</span><span style="color: #000000">);<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif"  alt="" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">#else</span><span style="color: #000000">&nbsp;</span><span style="color: #000000"><br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif"  alt="" />&nbsp;&nbsp;&nbsp;&nbsp;printf(</span><span style="color: #000000">"</span><span style="color: #000000">MY_MAC&nbsp;was&nbsp;not&nbsp;defined.\n</span><span style="color: #000000">"</span><span style="color: #000000">);<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif"  alt="" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">#endif</span><span style="color: #000000"><br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif"  alt="" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">0</span><span style="color: #000000">;<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockEnd.gif"  alt="" />}</span></span></div>上面的代码中使用了MY_MAC宏， 
<p>&nbsp;</p>
<p>【】$g++ -DMY_MAC -o dtest dtest.c</p>
<p>执行结果：</p>
<div style="font-size: 13px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; word-break: break-all; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: #cccccc 1px solid; padding-right: 5px; width: 98%; background-color: #eeeeee"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" /><span style="color: #000000">【】$&nbsp;.</span><span style="color: #000000">/</span><span style="color: #000000">dtest&nbsp;<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" />Hello&nbsp;</span><span style="color: #000000">-</span><span style="color: #000000">D.<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" />【】$</span></div>
<p>无掉-D选项，重新编译，执行结果： </p>
<div style="font-size: 13px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; word-break: break-all; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: #cccccc 1px solid; padding-right: 5px; width: 98%; background-color: #eeeeee"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" /><span style="color: #000000">【】$&nbsp;g</span><span style="color: #000000">++</span><span style="color: #000000">&nbsp;.</span><span style="color: #000000">/</span><span style="color: #000000">dtest.c&nbsp;</span><span style="color: #000000">-</span><span style="color: #000000">o&nbsp;dtest<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" />【】$&nbsp;.</span><span style="color: #000000">/</span><span style="color: #000000">dtest&nbsp;<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" />MY_MAC&nbsp;was&nbsp;not&nbsp;defined.<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" /></span></div>
<p>我们可以利用编译器这个选项来调试我们的程序奥。 </p>
<p>&nbsp;</p>
<p>如果我的宏代表一个常量怎么办呢？？</p>
<p>看看修改后的代码：</p>
<p>&nbsp;</p>
<div style="font-size: 13px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; word-break: break-all; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: #cccccc 1px solid; padding-right: 5px; width: 98%; background-color: #eeeeee"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" /><span style="color: #000000">#include&nbsp;</span><span style="color: #000000">&lt;</span><span style="color: #000000">stdio.h</span><span style="color: #000000">&gt;</span><span style="color: #000000"><br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" /></span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;main(</span><span style="color: #0000ff">int</span><span style="color: #000000">&nbsp;argc,&nbsp;</span><span style="color: #0000ff">char</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">**</span><span style="color: #000000">argv)<br /><img onclick="this.style.display='none'; Codehighlighter1_51_188_Open_Text.style.display='none'; Codehighlighter1_51_188_Closed_Image.style.display='inline'; Codehighlighter1_51_188_Closed_Text.style.display='inline';" id="Codehighlighter1_51_188_Open_Image" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockStart.gif"><img onclick="this.style.display='none'; Codehighlighter1_51_188_Closed_Text.style.display='none'; Codehighlighter1_51_188_Open_Image.style.display='inline'; Codehighlighter1_51_188_Open_Text.style.display='inline';" id="Codehighlighter1_51_188_Closed_Image" style="display: none" align="top" src="http://www.cppblog.com/images/OutliningIndicators/ContractedBlock.gif"></span><span id="Codehighlighter1_51_188_Closed_Text" style="border-top: #808080 1px solid; border-right: #808080 1px solid; border-bottom: #808080 1px solid; border-left: #808080 1px solid; display: none; background-color: #ffffff"><img src="http://www.cppblog.com/Images/dot.gif"  alt="" /></span><span id="Codehighlighter1_51_188_Open_Text"><span style="color: #000000">{<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif"  alt="" />&nbsp;&nbsp;&nbsp;&nbsp;#ifdef&nbsp;MY_MAC<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif"  alt="" />&nbsp;&nbsp;&nbsp;&nbsp;printf(</span><span style="color: #000000">"</span><span style="color: #000000">Hello&nbsp;-D.&nbsp;%d\n</span><span style="color: #000000">"</span><span style="color: #000000">,&nbsp;MY_MAC);<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif"  alt="" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">#else</span><span style="color: #000000">&nbsp;</span><span style="color: #000000"><br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif"  alt="" />&nbsp;&nbsp;&nbsp;&nbsp;printf(</span><span style="color: #000000">"</span><span style="color: #000000">MY_MAC&nbsp;was&nbsp;not&nbsp;defined.\n</span><span style="color: #000000">"</span><span style="color: #000000">);<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif"  alt="" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">#endif</span><span style="color: #000000"><br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/InBlock.gif"  alt="" />&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #0000ff">return</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">0</span><span style="color: #000000">;<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/ExpandedBlockEnd.gif"  alt="" />}</span></span></div>
<p><br /><br />我输出了宏代表的值。 </p>
<p>&nbsp;</p>
<p>在终端执行一下命令：</p>
<p>&nbsp;</p>
<div style="font-size: 13px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; word-break: break-all; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: #cccccc 1px solid; padding-right: 5px; width: 98%; background-color: #eeeeee"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" /><span style="color: #000000">【】$&nbsp;g</span><span style="color: #000000">++</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">-</span><span style="color: #000000">DMY_MAC</span><span style="color: #000000">=</span><span style="color: #000000">5</span><span style="color: #000000">&nbsp;.</span><span style="color: #000000">/</span><span style="color: #000000">dtest.c&nbsp;</span><span style="color: #000000">-</span><span style="color: #000000">o&nbsp;dtest<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" />【】$&nbsp;.</span><span style="color: #000000">/</span><span style="color: #000000">dtest&nbsp;<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" />Hello&nbsp;</span><span style="color: #000000">-</span><span style="color: #000000">D.&nbsp;</span><span style="color: #000000">5</span><span style="color: #000000"><br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" />【】$<br /><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" /></span></div>
<p>如果程序中有多个宏可以这样编译</p>
<div style="font-size: 13px; border-top: #cccccc 1px solid; border-right: #cccccc 1px solid; border-bottom: #cccccc 1px solid; word-break: break-all; padding-bottom: 4px; padding-top: 4px; padding-left: 4px; border-left: #cccccc 1px solid; padding-right: 5px; width: 98%; background-color: #eeeeee"><!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--><img align="top" src="http://www.cppblog.com/images/OutliningIndicators/None.gif"  alt="" /><span style="color: #000000">【】$g</span><span style="color: #000000">++</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">-</span><span style="color: #000000">DMAC1</span><span style="color: #000000">=</span><span style="color: #000000">5</span><span style="color: #000000">&nbsp;</span><span style="color: #000000">-</span><span style="color: #000000">DMAC2</span><span style="color: #000000">=</span><span style="color: #000000">6</span><span style="color: #000000">&nbsp;soucefile.c</span></div>
<p>^_^，，很兴奋是吧？？这样我们就不用在代码里修改宏变量了。。<br /><br /><br /></p><br /><br /><img src ="http://www.cppblog.com/jackdongy/aggbug/193599.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/jackdongy/" target="_blank">jackdong</a> 2012-10-21 12:41 <a href="http://www.cppblog.com/jackdongy/archive/2012/10/21/193599.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>