進程間通信


1.管道

 

對於具有公共祖先的進程,其管道是建立在3-4G的內核空間中的。每個進程各自有不同的用戶地址空間,任何一個進程的全局變量在另一個進程中都看不到,所以進程之間要交換數據必須通過內核,在內核中開辟一塊緩沖區,進程1把數據從用戶空間拷到內核緩沖區,進程2再從內核緩沖區把數據讀走,內核提供的這種機制稱為進程間通信(IPC,InterProcess Communication)。

 

調用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之前創建管道。

 

2.FIFO

 FIOF也被稱為命名管道。未命名的管道pipe只能在兩個有共同祖先的進程之間使用。但是通過FIFO,完全不相關的進程也能交換數據。

 

 

 

分別創建只讀和只寫文件fifo_r.c和fifo_r.c:

/*只讀: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標志。

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM