进程与进程间的通信,进程间通讯机制
到目前为止,我们只能在两个相关的程序之间传输数据,即由一个共同的祖先进程启动的程序。这通常不太方便,因为我们希望不相关的进程也能交换数据。
我们可以用FIFO来实现这个操作,通常称为命名管道。命名管道是一种特殊的文件类型,在文件系统中作为名称存在(记住,在Linux中一切都是文件),但它的行为类似于我们已经知道的未知管道。
我们可以通过命令行或在程序中创建命名管道。出于历史原因,用于创建命名管道的命令是mknod:
$ mknod文件名p
但是,mknod命令不在X/Open命令列表中,因此它在所有类似Unix的系统上都不可用。更好的命令行方法是使用
$ mkfifo文件名
在程序内部,我们可以使用两个不同的命令:
#包含sys/types.h
#包含系统/统计信息
int mkfifo(const char *filename,mode _ t mode);
int mknod(const char *filename,mode_t mode S_IFIFO,(dev _ t)0);
类似于mknod命令,我们可以使用mknod函数创建许多特殊的文件类型。使用dev_t类型的0值和使用S _ IFIFO的或文件访问模型是创建命名管道函数的唯一可移植方法。在我们的例子中,我们将使用简化的mkfifo。
测试-创建一个著名的管道。
作为fifo1.c,我们只需要输入以下代码:
#包含stdio.h
#包含stdlib.h
#包含字符串. h
#包含sys/types.h
#包含系统/统计信息
int main()
{
int res=mkfifo(/tmp/my_fifo ,0777);
if (res==0)
printf(FIFO已创建/n );
退出(EXIT _ SUCCESS);
}
我们可以使用以下命令查看该管道:
$ ls -lF /tmp/my_fifo
prw xr-xr-x 1 Rick users 0 7月10日14:55 /tmp/my_fifo
注意,输出的第一个字符是p,表示它是一个管道。下面的是由ls命令的-F选项添加的,这也表明它是一个管道。
使用程序的mkfifo功能创建一个特殊文件。虽然我们请求了0777模式,但是这个模式被用户掩码设置修改了,就像在普通文件创建中一样,所以最终的文件模式是755。如果我们的umask设置不同,比如0002,我们会看到不同的权限。
我们可以通过使用rm命令或者在程序中使用unlink系统调用来删除FIFO,就像删除一个普通文件一样。
访问FIFO
众所周知的管道的一个非常有用的特性是,因为它们出现在文件系统中,所以我们可以像通常使用文件名一样使用它们。在使用我们为更多编程而创建的FIFO之前,我们可以使用常见的文件命令来探索FIFO文件的行为。
实验-访问FIFO文件
1.首先,让我们试着读取(空的)FIFO文件:
$ cat /tmp/my_fifo
现在尝试将数据写入FIFO。我们需要使用不同的终端,因为第一个终端现在被挂起,等待数据出现在FIFO中。
$ echo sdsdfasdf /tmp/my_fifo
我们可以看到cat命令输出的结果。如果我们不向FIFO发送任何数据,cat命令将挂起,直到我们中断它,通常使用Ctrl C。
3通过将第一个命令放在后端,我们可以同时执行两个操作:
$ cat /tmp/my_fifo
[1] 1316
$ echo "sdsdfasdf" /tmp/my_fifo
sdsdfasdf
[1]完成cat /tmp/my_fifo
$
因为FIFO中没有数据,所以cat和echo程序会阻塞,分别等待数据到达和一个进程读取数据。
看第三步。cat进程最初在后台被阻塞。当echo命令使一些数据可用时,cat命令读取数据并将其输出到标准输出。请注意,cat命令将立即退出,而不会等待更多数据。他没有阻塞,因为第二个命令把数据完全放入FIFO后管道就会关闭,所以cat程序中的read调用会返回0,表示文件结束。
现在我们已经看到了命令行程序FIFO是如何工作的,让我们学习更多关于程序接口的知识,它允许我们在访问FIFO时对读和写执行更多的控制。
注意:与管道调用创建的管道不同,FIFO是作为一个命名文件存在的,而不是一个打开的文件描述符,它必须在被读写后才能打开。当我们操作文件时,我们可以通过打开和关闭函数来打开和关闭FIFO。Open调用需要传递FIFO的路径名,而不是常规文件。
带Open的Open FIFO
打开FIFO的主要限制是使用O_RDWR模型的程序可能会也可能不会打开一个FIFO进行读写。如果一个程序这样做,结果是不确定的。这是一个合理的限制,因为通常我们只使用FIFO单向传输数据,所以不需要O _ RDWR模型。该进程将读取自己的输出。
如果我们想在程序之间进行双向数据传输,最好使用一对FIFO或管道,每个管道用于一个方向,或者通过关闭和重新打开FIFO来改变数据流的方向。我们将在本章后面讨论双向数据交换。
打开FIFO和打开常规文件的另一个区别是带有O_NONBLOCK选项的open标志(open的第二个参数)。使用这种open模式不仅会改变open调用的处理方式,还会改变对返回的文件描述符的读写请求的处理方式。
O_RDONLY、O_WRONLY和O_NONBLOCK标记有四种合法的组合。我们将依次考虑每一个问题。
open(const char *path,O _ rd only);
在这种情况下,open调用将阻塞;直到一个进程打开同一个FIFO进行写操作,这个调用才会返回。这种情况类似于前面的cat示例。
open(const char *path,O _ rd only O _ non block);
open调用将成功并立即返回,即使FIFO没有被任何进程打开以写入数据。
open(const char *path,O _ wr only);
在这种情况下,open调用将阻塞,直到一个进程打开同一个FIFO进行读取。
open(const char *path,O _ WRONLY O _ non block);
这个调用会立即返回,但是如果没有进程打开FIFO进行读取,open会返回error -1,FIFO不会被打开。如果一个进程打开FIFO进行读取,那么返回的文件描述符可以用来写FIFO。
注意:O_NONBLOCK与使用O_RDONLY和O_WRONLY的区别在于,在非阻塞情况下,如果没有进程打开管道进行读操作,那么写操作的open调用会失败,但非阻塞读操作调用不会失败。O_NONBLOCK标志不影响关闭调用的行为。
测试-打开FIFO文件
让我们来看看如何使用open with O_NONBLOCK标签同步两个进程。我们将编写一个测试程序fifo2.c,而不是使用多个示例程序,它将让我们通过传递不同的参数来讨论fifo的行为。
1我们从头文件和#define开始,检查所提供的命令行参数的正确数量:
#包含stdio.h
#包含stdlib.h
#包含字符串. h
#包括unistd.h
#包含fcntl.h
#包含sys/types.h
#包含系统/统计信息
#define FIFO_NAME /tmp/my_fifo
int main(int argc,char **argv)
{
int res
int open _ mode=0;
if (argc 2)
{
fprintf(stderr,用法:% s O _ RDONLY O _ WRONLY O _ non block/n ,* argv);
退出(EXIT _ FAILURE);
}
2假设程序传递了正确的参数,我们现在通过这些参数设置open_mode的值:
argv
if (strncmp(*argv, O_RDONLY ,8)==0)open _ mode =O _ rd only;
if (strncmp(*argv, O_WRONLY ,8)==0)open _ mode =O _ wr only;
if (strncmp(*argv, O_NONBLOCK ,10)==0)open _ mode =O _ non block;
argv
if(*argv)
{
if(strncmp(*argv, O_RDONLY ,8)==0)open _ mode =O _ rd only;
if(strncmp(*argv, O_WRONLY ,8)==0)open _ mode =O _ wr only;
if(strncmp(*argv, O_NONBLOCK ,10)==0)open _ mode =O _ non block;
}
我们现在检查FIFO是否存在,如果需要,我们将创建它。然后,FIFO将被打开,并产生一个输出。FIFO终于要关闭了。
if (access(FIFO_NAME,F_OK)==-1)
{
res=mkfifo(FIFO_NAME,0777);
if(res!=0)
{
fprintf(stderr,无法创建fifo %s/n ,FIFO _ NAME);
退出(EXIT _ FAILURE);
}
}
printf(Process %d open FIFO/n ,getpid());
res=open(FIFO_NAME,open _ mode);
printf(进程%d结果%d/n ,getpid(),RES);
睡眠(5);
if(res!=-1)(void)close(RES);
printf(Process %d finished/n ,getpid());
退出(EXIT _ SUCCESS);
}
这个程序允许我们在命令行中指定O_RDONLY、O_WRONLY和O_NONBLOCK的组合。通过比较已知字符串和命令行参数,如果字符串匹配,将设置相应的标志。这个程序使用access函数来检查FIFO是否已经存在,如果需要,它会创建这个文件。
我们没有破坏FIFO,因为没有办法判断其他程序是否在使用FIFO。
O _ RDONLY和O_WRONLY与O_NONBLOCK
现在我们有了测试程序,让我们尝试一些组合;请注意,我们将文件程序放在后台:
$ ./FIFO O _ r仅
[1] 152
进程152打开FIFO
$ ./FIFO O _ wr only
过程153打开FIFO
过程152结果3
流程153结果3
过程152结束
过程153完成
这是众所周知的管道的最常见用法。他将使用read进程在open调用中启动并等待,然后当第二个程序打开FIFO时,两个程序都将继续。请注意,当调用open时,读取过程和写入过程是同步的。
注意:当一个Linux进程被阻塞时,它不会消耗CPU资源,所以这种进程同步的方法非常有效。
O _ RDONLY和O_WRONLY与O_NONBLOCK
这一次,读取过程将执行open调用并立即继续,尽管没有写入过程。写过程也将在open调用后立即继续,因为FIFO已经打开用于读取。
$ ./FIFO O _ r only O _ non block
[1] 160
过程160打开FIFO
$ ./FIFO O _ wr only
进程161打开FIFO
过程160结果3
过程161结果3
过程160结束
过程161完成
[1]Done FIFO O _ r only O _ non block
这两个例子是开放模式最常见的组合。我们可以用一些其他的组合来试验这个程序。