Linux 进程间通信

Linux 管道

管道是允许单向通信的通信设备。数据从管道的一端写入并从管道的另一端读出。管道是串行设备;数据总是以写入时的顺序被读取出来。通常,管道用于同一进程的两个不同线程或在父子进程间通行。

shell中,使用|符号创建管道。例如,下面的shell命令将导致shell创建两个子进程,分别用于lsless命令:

% ls | less

shell同时也创建了一个管道用于连接ls子进程的标准输出和less进程的标准输入。通过ls列出的文件名传输到less中,并且其顺序与它直接在终端显示一致。

管道的数据容量是有限的。如果写进程速度快于读进程读取数据,并且管道不能存储过多的数据,那么写进程将会阻塞直到有更多的容量可以使用。因此,管道将自动同步两个进程。

创建管道

要创建一个管道,调用pipe命令。指定一个大小为2的整数数组。Pipe调用将读文件描述符存放在数组第0号位置,写文件描述符存放在数组第1号位置。例如,考虑下面的代码:

int pipe_fds[2];
int read_fd;
int write_fd;

pipe(pipe_fds);
read_fd = pipe_fds[0];
write_fd = pipe_fds[1];

通过write_fd文件描述符写入的数据可以通过read_fd文件描述符读取回来。

父子进程间通信

通过pipe创建的文件描述符只在当前进程和其子进程中有效。进程文件描述符不能传递给不相关进程;然而,当进程调用fork时,文件描述符被复制到子进程中。因此,管道只能连接相关进程。

如下列出的程序中,fork创建一个子进程。子进程继承父进程的管道文件描述符。父进程写入字符串在管道中,子进程把它读取出来。这个程序使用fdopen将文件描述符转换为FILE*的文件流。因为使用流而不是文件描述符,可以让我们使用高层的标准CI/O库函数,例如printffgets

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

/* Write COUNT copies of MESSAGE to STREAM, pausing for a second
 * between each. */
void writer(const char *message, int count, FILE *stream)
{
    for (; count > 0; --count) {
        /* Write the message to the stream, and send it off immediately. */
        fprintf(stream, "%s\n", message);
        fflush(stream);
        /* Snooze a while. */
        sleep(1);
    }
}

/* Read random strings from the stream as long as possible. */
void reader(FILE *stream)
{
    char buffer[1024];
    /* Read until we hit the end of the stream. fgets reads until
     * either a newline or the end-of-file. */
    while (!feof(stream) && !ferror(stream) &&
            fgets(buffer, sizeof(buffer), stream) != NULL)
        fputs(buffer, stdout);
}<pre name="code" class="cpp">int main()
{
    int fds[2];
    pid_t pid;

    /* Create a pipe. File descriptors for the two ends of the
     * pipe are placed in fds. */
    pipe(fds);
    /* Fork a child process. */
    if ((pid = fork()) == 0) {
        FILE *stream;
        /* This is the child process. Close our copy the write end of
         * the file descriptor. */
        close(fds[1]);
        stream = fdopen(fds[0], "r");
        reader(stream);
        close(fds[0]);
    } else if (pid == -1) {

    } else {
        /* This is the parent process. */
        FILE *stream;
        /* Close our copy of the read end of the file descriptor. */
        close(fds[0]);
        stream = fdopen(fds[0], "r");
        reader(stream);
        close(fds[1]);
    }

    return 0;
}

main函数的开始,声明大小为2的整数数组。通过pipe创建管道,并将读写文件描述符放在数组中。程序接着创建子进程。在关闭了读管道后,父进程开始向管道中写入字符串。子进程在关闭写管道后,从读管道中读出字符串。

注意,在writer函数每次写入数据后,父进程通过调用fflush刷新管道。否则,字符串可能不会字符通过管道发送出去。

当调用ls | less命令时,出现两个进程:一个ls子进程和一个less子进程。这两个进程都继承管道文件描述符,因此,它们可以通过管道通信。为了使不相关的进程通信,可以使用FIFO代替。

重定向标准输入、输出和错误输出

通常,你需要创建一个子进程,并设置它管道的一端为标准输入或输出。使用dup2函数,你可以将一个文件描述符等同于另一个文件描述符。例如,为了将fd文件描述符的输入重定向到标准输入,可以使用以下方式:

dup2 (fd,STDI_FILENO);

常量STDIN_FILENO代表标准输入的文件描述符,其值为0。该函数关闭标准输入,随后作为fd的副本重新打开标准输入,这样,这两个文件描述符就可交换使用。Equated file descriptors share the same file position and the sameset of file status flags.Thus, characters read from fd are not reread fromstandard input.

popenpclose

管道通常用于向程序的子进程发送或接收数据。popenpclose函数简化了调用pipeforkdup2execfdopen

#include <stdio.h>
#include <unistd.h>

int main()
{
    FILE *stream = popen("sort", "w");
    fprintf(stream, "This is a test.\n");
    fprintf(stream, "Hello, world.\n");
    fprintf(stream, "My dog has fleas.\n");
    fprintf(stream, "This program is great.\n");
    fprintf(stream, "One fish, two fish.\n");
    return pclose(stream);
}

popen函数创建子进程执行sort命令。第二个参数“w”表明该进程想要向子进程中写入数据。popen函数的返回值是管道的一端;另一端用于连接子进程的标准输入。在写入数据完成后,调用pclose函数关闭子进程的流,等待进程终止,并返回状态值。

函数popen的第一个参数的执行类似于执行shell命令。shell按照通常的方式搜寻PATH环境变量找到程序执行。如果第二个参数是“r”,函数返回子进程的标准输出流,这样,父进程便可读取输出。如果第二个参数是“w”,函数返回子进程的标准输入流,父进程便可写入数据。如果出现错误,函数popen返回NULL指针。

调用pclose关闭由popen返回的流。在关闭指定的流后,pclose等待子进程终止。

FIFOs

先进先出(FIFO)文件是在文件系统中有名称的管道。任何进程都可打开或关闭FIFO;管道两端的进程不需要彼此相关。FIFOs通常也被称为命名管道。

要创建命名管道,可以使用mkfifo命令。在命令行上指定命名管道的路径。例如,创建一个/tmp/fifo命名管道:

% mkfifo /tmp/fifo
% ls –l /tmp/fifo
prw-rw-rw-   1  samuel  users 0 Jan 16 14:04 /tmp/fifo

ls命令输出的第一个字符是p,表示这个文件实际上是FIFO(命名管道)文件。在一个窗口中,通过下面的命令读取FIFO

% cat < /tmp/fifo

在另一个窗口中,通过下面的命令向FIFO写入数据:

% cat > /tmp/fifo

接着,输入一些文本。每次按下Enter键时,一行的文本内容通过FIFO发送并在第一个窗口中显示。通过在第二个窗口中按下Ctrl+D关闭FIFO。通过以下命令删除FIFO

% rm /tmp/fifo

1 创建FIFO

通过mkfifo以编程的方式创建FIFO。第一个参数是FIFO创建的路径;第二个参数指定管道的拥有者,组和权限。因为,关闭必定有读和写,因此权限必须包含读和写的权限。如果管道不能创建(例如,如果该文件已经存在),mkfifo返回-1。如果调用mkfifo,需要包含<sys/types.h><sys/stat.h>

2 访问FIFO

FIFO的访问就像访问普通文件一样。通过FIFO通信,一个程序必须以写的方式打开,另一个程序则以读的方式打开。低级I/O函数或者标准CI/O函数都可使用。

例如,使用低级I/OFIFO中写入数据,可使用下述代码:

int fd = open(fifo_path, O_WRONLY);
write(fd, data, data_length);
close(fd);

使用标准CI/O函数读取FIFO中的数据,可使用下述代码:

FILE *fifo = fopen(fifo_path, “r”);
fscanf(fifo, “%s”, buffer);
fclose(fifo);

一个FIFO可以有多个读进程或写进程。Bytesfrom each writer are written atomically up to a maximum size of PIPE_BUF (4KBon Linux). Chunks from simultaneous writers can be interleaved. Similar rulesapply to simultaneous reads.

3 Windows命名管道的不同

Win32操作系统中的管道类似与Linux中的管道。主要的区别集中在命名管道上,对于Win32来说,命名管道的功能更像是套接字。Win32命名管道可以通过网络连接分离的计算机的进程。同样,Win32允许多个读写连接到同一命名管道上,并不会存在交叉数据,并且支持双向通信。




郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。