1.管道ubuntu
对于具备公共祖先的进程,其管道是创建在3-4G的内核空间中的。每一个进程各自有不一样的用户地址空间,任何一个进程的全局变量在另外一个进程中都看不到,因此进程之间要交换数据必须经过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通讯(IPC,InterProcess Communication)。
socket
调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通讯,它有一个读端一个写端,而后经过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出同样)。因此管道在用户程序看起来就像一个打开的文件,经过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据实际上是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。函数
#include <sys/wait.h> #include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<stdlib.h> #include<string.h> int main(void) { pid_t pid; int fd[2]; if(pipe(fd)<0) { perror("pipe"); exit(1); } printf("fd[0]=%d, fd[1]= %d\n",fd[0],fd[1]); if((pid=fork())<0) { perror("fork"); exit(1); } else if(pid==0)//child { char c_str[1024]; int n; close(fd[1]);//关闭写端口 n=read(fd[0],c_str,sizeof(c_str)/sizeof(c_str[0]));//因为不知道读多少,因此读取最大长度 close(fd[0]); write(STDOUT_FILENO,c_str,n); } else//parents { char str[]="hello pipe!\n"; sleep(2); close(fd[0]);//关闭读端口 write(fd[1],str,strlen(str)); close(fd[1]); wait(NULL);//等待回收子进程资源 } return 0; }
在父进程没有传输数据在管道中时,子进程中的read函数会阻塞等待。咱们能够使用fcntl函数改变一个已经打开文件的属性,如从新设置读、写、追加、非阻塞等标志。大数据
#include<stdio.h> #include<sys/types.h> #include<sys/wait.h> #include<unistd.h> #include<stdlib.h> #include<string.h> #include<fcntl.h> #include<errno.h> int main(void) { pid_t pid; int fd[2]; if(pipe(fd)<0) { perror("pipe"); exit(1); } printf("fd[0]=%d, fd[1]= %d\n",fd[0],fd[1]); if((pid=fork())<0) { perror("fork"); exit(1); } else if(pid==0)//child { char c_str[1024]; int n,flags; flags=fcntl(fd[0],F_GETFL); flags |=O_NONBLOCK; if(fcntl(fd[0],F_SETFL,flags)==-1) { perror("fcntl"); exit(1); } close(fd[1]);//关闭写端口 tryagain: n=read(fd[0],c_str,sizeof(c_str)/sizeof(c_str[0]));//因为不知道读多少,因此读取最大长度 if(n<0) { if(errno==EAGAIN) { write(STDOUT_FILENO,"try again...\n",13); sleep(1); goto tryagain; } perror("read"); exit(1); } close(fd[0]); write(STDOUT_FILENO,c_str,n); } else//parents { char str[]="hello pipe!\n"; sleep(2); close(fd[0]);//关闭读端口 write(fd[1],str,strlen(str)); close(fd[1]); wait(NULL);//等待回收子进程资源 } return 0; }
此时,read已经再也不是阻塞了。须要注意的是,使用管道技术,应该在fork以前建立管道。spa
2.FIFO3d
FIOF也被称为命名管道。未命名的管道pipe只能在两个有共同祖先的进程之间使用。可是经过FIFO,彻底不相关的进程也能交换数据。code
分别建立只读和只写文件fifo_r.c和fifo_r.c:blog
/*只读:fifo_r.c*/ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <fcntl.h> #include <sys/stat.h> #include <string.h> void sys_err(const char *str, int exitno) { perror(str); exit(exitno); } int main(int argc, char *argv[]) { int fd, len; char buf[1024]; if (argc < 2) { printf("usage:%s fifoname\n",argv[0]); exit(1); } if(access(argv[1],F_OK)==-1) { if(mkfifo(argv[1],0775)==-1) { sys_err("mkfifo",1); } } printf("1\n"); fd = open(argv[1], O_RDONLY); if (fd < 0) sys_err("open", 1); printf("2\n"); len = read(fd, buf, sizeof(buf)); write(STDOUT_FILENO, buf, len); close(fd); return 0; }
/*只写:fifo_w.c*/ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <fcntl.h> #include <sys/stat.h> #include <string.h> void sys_err(const char *str, int exitno) { perror(str); exit(exitno); } int main(int argc, char *argv[]) { int fd; char buf[1024] = "hello nanmed pipe!\n"; if (argc < 2) { printf("usage:%s fifoname\n",argv[0]); exit(1); } if(access(argv[1],F_OK)==-1) { if(mkfifo(argv[1],0775)==-1) { sys_err("mkfifo",1); } } printf("1\n"); fd = open(argv[1], O_WRONLY); if (fd < 0) sys_err("open", 1); printf("2\n"); write(fd, buf, strlen(buf)); close(fd); return 0; }
先运行写进程,此时程序阻塞在只写打开的open函数,再ctrl+shift+n,打开新的终端,运行只读进程:进程
此时读进程正确读取了写进程的数据。反之,先执行读取进程,读进程也会阻塞在只读open函数处,直到写入数据。前提是没有指定O_NONBLOCK标志。ip
FIFO和PIPE的最大数据量能够经过fpathconf函数获得:
在建立了FIFO或者PIPE以后使用:printf("FIFO_PIPE_BUF_SIZE = %ld\n",fpathconf(fd, _PC_PIPE_BUF));
能够发现,下ubuntu 16.04中,FIFO和PIPE的缓冲区大小为4096个字节。不一样的系统版本,可能存在差别。
3.内存共享映射
sysconf(_SC_PAGESIZE)的返回值,在本文的ubuntu16.04中为4096字节。故off的值应该是4096的整数倍,一般该值设置为0。
如今,使用mmap实现一个复制指令:
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <signal.h> 4 #include <stdlib.h> 5 #include <unistd.h> 6 #include <sys/time.h> 7 #include <sys/resource.h> 8 #include <sys/types.h> 9 #include <sys/stat.h> 10 #include <fcntl.h> 11 #include <syslog.h> 12 #include <string.h> 13 #include <sys/mman.h> 14 15 16 #define COPYINCR (1024*1024*1024) /* 1 GB */ 17 int main(int argc, char *argv[]) 18 { 19 int fdin, fdout; 20 void *src, *dst; 21 size_t copysz; 22 struct stat sbuf; 23 off_t fsz = 0; 24 if (argc != 3) 25 printf("usage: %s <fromfile> <tofile>", argv[0]); 26 if ((fdin = open(argv[1], O_RDONLY)) < 0) 27 printf("can’t open %s for reading", argv[1]); 28 if ((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0666)) < 0) 29 printf("can’t creat %s for writing", argv[2]); 30 if (fstat(fdin, &sbuf) < 0) /* need size of input file */ 31 printf("fstat error"); 32 if (ftruncate(fdout, sbuf.st_size) < 0) /* 文件字节数:sbuf.st_size ,set output file size */ 33 printf("ftruncate error"); 34 35 if ((sbuf.st_size - fsz) > COPYINCR) 36 copysz = COPYINCR; 37 else 38 copysz = sbuf.st_size - fsz; 39 if ((src = mmap(0, copysz, PROT_READ, MAP_SHARED, fdin, fsz)) == MAP_FAILED) 40 printf("mmap error for input"); 41 if ((dst = mmap(0, copysz, PROT_READ | PROT_WRITE,MAP_SHARED, fdout, fsz)) == MAP_FAILED) 42 printf("mmap error for output"); 43 44 memcpy(dst, src, copysz); /* does the file copy */ 45 46 munmap(src, copysz);//释放内存 47 munmap(dst, copysz);//释放内存 48 49 50 exit(0); 51 }
使用Vim打开对比,内容天然也是彻底一致的:
这个例子至关于在磁盘的main.c映射一个地址空间到src(只读),而后建立另外一个文件,可读可写,经过前面映射的只读地址空间,将其内容拷贝到此时建立的main.c.copy中。
这个思想能够应用在多进程的通讯中。本文目前描述的状况,都是最简单的场景,不存在进程间的竞争关系,如多个进程同时写一个文件,此时则须要执行相应的处理方法,如信号量,互斥锁等,这个在后面的随笔中再介绍。
消息邮箱和socket的进程间通讯方法,也将在后续随笔中介绍。