• 进程间通信


    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
    ",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!
    ";
            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
    ",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...
    ",13);
                    sleep(1);
                    goto tryagain;
                }
                perror("read");
                exit(1);
            }
            close(fd[0]);
            write(STDOUT_FILENO,c_str,n);
        }
        else//parents
        {
            char str[]="hello pipe!
    ";
            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
    ",argv[0]);
            exit(1);
        }
        if(access(argv[1],F_OK)==-1)
        {
            if(mkfifo(argv[1],0775)==-1)
            {
                sys_err("mkfifo",1);
            }
        }
         printf("1
    ");
        fd = open(argv[1], O_RDONLY);
        if (fd < 0) 
            sys_err("open", 1);
         printf("2
    ");
        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!
    ";
        if (argc < 2) {
            printf("usage:%s fifoname
    ",argv[0]);
            exit(1);
        }
        if(access(argv[1],F_OK)==-1)
        {
            if(mkfifo(argv[1],0775)==-1)
            {
                sys_err("mkfifo",1);
            }
        }
        printf("1
    ");
        fd = open(argv[1], O_WRONLY);
        if (fd < 0) 
            sys_err("open", 1);
         printf("2
    ");
        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 ",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的进程间通信方法,也将在后续随笔中介绍。

  • 相关阅读:
    day4
    cache用法
    Excel批量生成SQL语句,处理大量数据(增,改)
    IDEA中Maven依赖下载失败解决方案
    IDEA 自动生成类图 UML
    springboot报错说 Failed to parse multipart servlet request; nested exception is java.io.IOException
    controller层的引用service层一直报空指针问题
    CONCATENATE函数
    AQS
    String 类和常量池
  • 原文地址:https://www.cnblogs.com/yangguang-it/p/10799409.html
Copyright © 2020-2023  润新知