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的進程間通訊方法,也將在後續隨筆中介紹。