Linux 进程间通信

来源:互联网 发布:人物绘画教程软件下载 编辑:程序博客网 时间:2024/05/15 20:50

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/fifoprw-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允许多个读写连接到同一命名管道上,并不会存在交叉数据,并且支持双向通信。




0 0
原创粉丝点击