一、进程间通信
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。如下图所示。
二、管道是一种最基本的IPC机制:
1.必须在系统调用fork()中调用pipe(),否则子进程将不会继承文件描述符;
2.当使用半双工管道时,任何关联的进程都必须共享一个相关的祖先进程。
#include <unistd.h>
int pipe(int filedes[2]);
1.pipe()会建立管道,并将文件描述词由参数filedes数组返回。
2. filedes[0]为管道里的读取端
filedes[1]则为管道的写入端。
3.若成功则返回零,否则返回-1,错误原因存于errno中。
错误代码:
EMFILE 进程已用完文件描述词最大量
ENFILE 系统已无文件描述词可用。
EFAULT 参数 filedes 数组地址不合法。
开辟了管道之后如何实现两个进程间的通信呢?比如可以按下面的步骤通信。
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
int main(int argc, char *argv[])
{
int pipefd[2];
if (pipe(pipefd) == -1)
perror("pipe error");
pid_t pid;
pid = fork();
if (pid == -1)
perror("fork error");
if (pid == 0)
{
close(pipefd[0]);
write(pipefd[1], "hello", 5);
close(pipefd[1]);
exit(EXIT_SUCCESS);
}
close(pipefd[1]);
char buf[10] = {0};
read(pipefd[0], buf, 10);
printf("buf=%s\n", buf);
return 0;
}
1. 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道写端,子进程关闭管道读端。子进程可以往管道里写,父进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
使用管道有一些限制:
两个进程通过一个管道只能实现单向通信,比如最上面的例子,父进程读子进程写,如果有时候也需要子进程读父进程写,就必须另开一个管道。
管道的读写端通过打开的文件描述符来传递,因此要通信的两个进程必须从它们的公共祖先那里继承管道文件描述符。上面的例子是父进程把文件描述符传给子进程之后父子进程之间通信,也可以父进程fork两次,把文件描述符传给两个子进程,然后两个子进程之间通信,总之需要通过fork传递文件描述符使两个进程都能访问同一管道,它们才能通信。
进程间通信必须通过内核提供的通道,而且必须有一种办法在进程中标识内核提供的某个通道,前面讲过的匿名管道是用打开的文件描述符来标识的。如果要互相通信的几个进程没有从公共祖先那里继承文件描述符,它们怎么通信呢?内核提供一条通道不成问题,问题是如何标识这条通道才能使各进程都可以访问它?文件系统中的路径名是全局的,各进程都可以访问,因此可以用文件系统中的路径名来标识一个IPC通道。
FIFO和UNIX Domain Socket这两种IPC机制都是利用文件系统中的特殊文件来标识的。
FIFO文件在磁盘上没有数据块,仅用来标识内核中的一条通道.
2.命名管道
$ mkfifo filename或者
如果当前打开操作是为读而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO时
O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
需要注意的是打开的文件描述符默认是阻塞的.
注意:
系统加于管道和FIFO的唯一限制是:
1、OPEN_MAX 一个进程在任意时刻打开的最大描述符数。可以通过调用sysconf函数查询。
2、PIPE_BUF 可原子地写往一个管道或FIFO的最大数据量。Posix任务它是一个路径名变量,它的值可以随指定的路径名而变化,因为不同的路径名可以落在不同文件系统上,而这些文件系统可能有不同的特征。所以PIPE_BUF可通过pathconf函数取得。
pipeconf.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main(int argc, char **argv)
{
if(argc != 2)
printf("usage:pipeconf <pathname>");
printf("PIPE_BUF = %ld, OPEN_MAX = %ld\n",
pathconf(argv[1], _PC_PIPE_BUF), sysconf(_SC_OPEN_MAX));
exit(0);
}
对管道或FIFO能以两种方式设置成非阻塞:
1、调用open时可指定O_NONBLOCK标志。
writefd = open(FIFO1, O_WRONLY | O_NONBLOCK, 0);
2、如果一个描述符已经打开,可调用fcntl启用O_NONBLOCK标志。对于管道来说,必须使用这种技术,因为管道没哟open调用,在pipe调用中也无法指定O_NONBLOCK标志。使用fcntl时,先使用F_GETFL命令取得当前文件状态标志,将它与O_NONBLOCK标志按位或,再使用F_SETFL命令存储这些文件状态标志。
int flags;
if((flags = fcntl(fd, F_GETFL, 0) < 0))
err_sys("F_GETEFL error");
flags |= O_NONBLOCK;
if((flags = fcntl(fd, F_SETFL, 0) < 0))
err_sys("F_SETEFL error");
下面用一个简单程序,演示FIFO IPC的用法。
该程序分为2端:
server程序创建一个FIFO,并从FIFO读取字符,转换成大写后输出到屏幕。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FIFO_PATH "/tmp/myfifo"
int main()
{
int ret;
int fd;
char buffer;
int nread;
int i;
/*建立FIFO*/
ret = mkfifo(FIFO_PATH, 0777);
/*打开FIFO*/
fd = open(FIFO_PATH, O_RDONLY);
if(-1 == fd)
{
printf("error/n");
return -1;
}
while(1)
{
nread = read(fd, &buffer, 1);
if(nread > 0)
{
buffer = toupper(buffer); //将字符c转换为大写英文字母.如果c为小写英文字母.
//则返回对应的大写字母;否则返回原来的值。
printf("%c", buffer);
}
}
}
运行server后,可看到创建了文件/tmp/myfifo,这是mkfifo函数指定的命名管道的路径(名字)。
当然,系统不会真的在磁盘上创建这个文件。
client程序读取用户输入并写入FIFO。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define FIFO_PATH "/tmp/myfifo"
int main()
{
int fd;
int ret;
char c;
fd = open(FIFO_PATH, O_WRONLY);
if(-1 == fd)
{
printf("error/n");
return -1;
}
while(c = getchar())
{
write(fd, &c, 1);
}
}
先启动server程序,再运行client,随便输入些字符。
server端将在屏幕上显示转换为大写后的输入字符。