https://blog.csdn.net/maopig/article/details/77800124
每个进程有独立的地址空间,任何一个进程的全局变量在另一个进程中都是看不见的。因此进程间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷贝到内存缓冲区,进程2再从内核缓冲区把数据读走。内核提供的这种机制称为进程间通信(IPC, inter process communication)。
对于32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。逻辑地址。
管道是最基本的IPC机制。在内核中开辟一块缓冲区(称为管道 pipe),提供一个读端文件描述符和一个写端文件描述符,供用户通信。
管道是一种最基本的IPC机制,作用于有亲缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道,有如下特质:
1、其本质是一个伪文件(实为内核缓冲区);[伪文件类型有:-s 套接字、-b 块设备、-c 字符设备、 -p 管道; 占磁盘空间的文件类型有: - 普通文件、 -d 目录、 -l 软连接]
2、由两个文件描述符引用,一个表示读端,一个表示写端;
3、规定数据从管道的写端流入管道,从读端流出,有FIFO的特性。
管道的原理:管道实为内核使用环形队列机制,借助内核缓冲区(4K = 8*512bytes; 512是磁盘一个扇区的大小)实现的。
管道局限性:
1> 数据不能自己读 自己写
2> 数据一旦被读走,便不再管道中存在,即不可反复读取
3> 由于管道采用半双工通信方式,因此数据只能在一个方向上流动
4》只能在有公共祖先的进程间使用管道
常见的通信方式有:单工通信、半双工通信、全双工通信
双工:有两个工作模式----接收和发送
单工:只有一个工作模式---接收或发送
半双工:用的时候,只能选择接收或发送一个模式
全双工:用的时候,同时可以接收和发送。
在linux中,管道的实现并没有专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个file结构指向同一个临时的VFS索引节点,而这个索引节点又指向一个物理页面而实现的。
所上图所示,两个file数据结构定义文件操作例程地址(f_op)是不同的,其中一个是向管道写入数据的例程地址,而另一个是从管道读取数据的例程地址。就这样,用户程序就可以对管道的两个文件描述符分别进行read、write系统调用(表现的依旧是文件操作),而内核却利用这种抽象机制实现了管道这一特殊操作。普通管道仅供具有共同祖先的两个进程之间共享,并且这个祖先已经为他们建立了供他们使用的管道。因为文件描述符是针对进程的。
内核给我们创建的管道的buffer有多大,是由pipe buf 和缓冲条目的数量共同决定的。pipe buf *条数 == pipe capactiy;
pipe buf定义了内核管道缓存区的容量,这个值由内核设定,可以通过ulimit-a命令查看
从查看到的资源看pipe size 512bytes * 8 = 4096bytes。即一次原子写入为4096字节。
当然另一个与之对应的缓冲条目的个数与linux的内核版本是有关联的,16条;4096*16 = 65536字节。
当管道满时,
对于O_NONBLOCK disable的:write调用阻塞,知道有程序读走数据;
对于O_NONBLOCK enable的:write调用返回-1,errno值为EAGAIN。
把管道写满需要多少字符。经验是65536个。
头文件:#include
原型: int pipe(int fd[2]);
函数说明: pipe()会建立管道,并将文件描述符由参数fl数组返回。fd[0]为管道的读取端,fd[1]是管道的写入端。
返回值: 成功 0 失败 -1, 错误原因存于errno中
错误代码:
EMFILE-----进程已用完文件描述符最大量;
ENFILE-----系统已无文件描述符可用;
EFAULT-----参数fd数组地址非法
开辟管道之后,父子进程是如何实现通信的,如下图所示步骤通信
1、父进程调用pipe开辟管道,得到连个文件描述符指向管道两端;
2、父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一个管道;
3、父进程关闭管道读端,子进程关闭写端,这样就可以实现父进程写,子进程读的进程间通信了。
即读的进程,要关闭fd[1]; 写的进程要关闭fd[0];
1>当管道中有数据,则read返回实际读到的字节数;
2>当管道中没有数据时
2.1》写端全关闭,read返回0
2.2》仍有写段打开,则阻塞等待 这就是为啥例程中,父进程早于子进程结束。
1>当读端全关闭,则进程异常终止(SIGPIPE信号);
2>当仍有读端打开
2.1》当管道未满,写入数据,返回写入的字节数
2.2》当管道满时,阻塞
限制:
1、匿名管道只能单向通信,要实现双向通信,要再创建一个管道;
2、管道通信依赖于文件系统,即管道的生命周期随进程。
3、匿名管脚只能进行在有亲缘关系的进程间通信,通常用于父子进程、兄弟进程;
4、管道容量有限
优点:
某些限制,从其他角度看也是优点。
1、自带同步机制,保证读写顺序一致------FIFO先进先出。
2、管道的通信被称为面向字节流,与通信格式无关。
3、单向通信
在应用匿名管道进行编程时,基本的流程是一样的:先创建一管道,然后并发一个进程,父子进程间通过管道进行数据交换。
头文件:
函数原型: FILE *popen(const char *command, const char *type);
参数: command----要执行的命令串;
type---指定要返回的FILE 类型指针是用于读,还是写的。
返回值: NULL---执行失败,可用errno查错误原因。
其他:就是一个标准I/O流。
通过fgets()来获取执行输出。
下面举个栗子
#include
#include
#include
#include
#define BUFFER_SIZE (1024)
int execute_shell_command(const char *cmd, char * result)
{
if((cmd == NULL) || (result == NULL))
return -1;
FILE * stream = NULL;
if((stream = popen(cmd, "r")) == NULL)
{
perror("popen");
}
while(fget(result, BUFFER_SIZE, stream) != NULL)
{
pclose(stream);
stream = NULL;
return 0;
}
}
void main(void)
{
char * command = "pwd 2>/dev/null; echo $?";
char result[BUFFER_SIZE] = {0};
if(execute_shell_command(command, result)!=-1)
{
printf("reslut = %d\n", result);
}
else
{
printf("execute shell %s failed\n", command);
}
}
有名管道可以在不具有亲缘关系的进程间实现数据通信。与匿名管道不同,有名管道可通过路径名指出,并且在文件系统中是可见的。匿名管道文件描述符指向的文件(缓冲区)是在内存上的。
有名管道FIFO(First in first out)是特殊的文件,它是严格地遵循先进先出规则,即对管道和FIFO的读总是从开始出返回数据,对它们的写则是把数据添加到末尾。注意不支持lseek等文件定位操作。
有名管道之所以可以实现进程间的通信在于通过同一路径名,可以看到同一份资源,这份资源以FIFO的文件形式存在于文件系统中。
在创建管道成功之后,就可以使用open、read、write这些函数了。
创建有名管道----在文件系统中创建一个专用文件,该文件用于提供FIFO功能。
头文件:#include
#include
函数原型: int mkfifo(const char * filename, mode_t mode);
参数: filename-----指定了文件名
mode-----指定了新建文件的访问权限,与文件的访问权限相同,如0644.
O_RDONLY 读管道
O_WRONLY 写管道
O_RDWR 读写管道
O_NONBLOCK 非阻塞
O_CREAT 如文件不存在,则创建一个新的文件,并用第三的参数为其设置权限。
O_EXCL 如使用O_CREAT时文件存在,那么可返回错误信息。这一参数可测试文件是否存在。
返回值: 0 函数执行成功; -1:执行失败,可查询errno获取错误详细信息。
创建一个名为pathname的文件系统节点(文件、设备文件、有名管道),其属性有mode和dev指定。
头文件:#include
#include
#include
#include
函数原型: int mknod(const char * pathname, mode_t mode, dev_t dev);
pathname:
mode: 指定要用到的权限和创建的节点的类型。
权限:(mode&(~umask))
类型: S_IFREG, S_IFCHR, S_IFBLK, S_FIFO 或S_IFSOCK,以分别指定普通文件(创建后为空),字符特殊文件、块特殊文件、FIFO(有名管道)、UNIX域套接字。
dev:
mknod是比较老的函数,而mkfifo函数更加简单和规范。尽量使用mkfifo而不是mknod.
需要注意的是:有名管道FIFO这一专用文件,读写打开方式有所不同,有两种方式:阻塞方式与非阻塞方式。在缺省状态下,open打开的是以阻塞方式打开。
在阻塞方式下:当打开一个管道用于读时,如果没有打开管道用于写的进程,则该打开操作将被阻塞,直到有一个进程用写的方式打开该管道为止。同样,先打开写,也要等有进程打开读。
具体使用如下所示:
下面以阻塞的方式打开匿名管道,看看读写的阻塞情况。
#include
#include
#include
#include
#include
#include
#include
#include
#define FIFO "/tmp/myfifo"
int main(void)
{
char* buf = "I am jimmy.";
int fd;
if((mkfifo(FIFO, O_CREAT|O_EXCL) < 0)&&(errno != EEXIST)) /*创建有名管道,访问权限为644*/
{
printf("cannot crate fifoserver\n"); /*错误处理*/
}
printf("Preparing for reading bytes...\n");
if(fd = open("p1", O_WRONLY, 0) < 0)
{
perror("open");
exit(-1);
}
write(fd, buf, strlen(buf));
close(fd);
sleep(5);
unlink(FIFO);
return 0;
}
int main(void)
{
char buf[1024] = {0};
int fd;
if(fd = open("p1", O_RDONLY, 0) < 0)
{
perror("open");
exit(-1);
}
if( read(fd, buf, 1024) > 0)
{
printf("read %s\n",buf);
}
close(fd);
return 0;
}