alin的学习之路(Linux系统编程:七)(命名管道、共享存储映射)

alin的学习之路(Linux系统编程:七)

1. 有名管道

命名管道(FIFO)不同于无名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,这样,即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。

命名管道(FIFO)和无名管道(pipe)有一些特点是相同的,不一样的地方在于:

  1. FIFO 在文件系统中作为一个特殊的文件而存在,但 FIFO 中的内容却存放在内存中。

  2. 当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。

  3. FIFO 有名字,不相关的进程可以通过打开命名管道进行通信。

1. 通过命令创建有名管道

mkfifo 管道文件名

itcast@ubuntu:~/classcode/7day$ mkfifo fifo
itcast@ubuntu:~/classcode/7day$ ll
total 8
drwxrwxr-x 2 itcast itcast 4096 Jul 21 03:23 ./
drwxrwxr-x 7 itcast itcast 4096 Jul 21 03:15 ../
prw-rw-r-- 1 itcast itcast    0 Jul 21 03:23 fifo|

2. 通过函数创建有名管道

判断文件是否存在

#include <unistd.h>

int access(const char *pathname, int mode);
功能:
    判断文件是否存在
参数:
    pathname 文件路径
    mode 判断的属性
返回值:
    成功 0
    失败 -1 设置errno

mkfifo 创建有名管道

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);
功能:
    命名管道的创建。
参数:
    pathname : 普通的路径名,也就是创建后 FIFO 的名字。
    mode : 文件的权限,与打开普通文件的 open() 函数中的 mode 参数相同。(0666)
返回值:
    成功:0   状态码
    失败:如果文件已经存在,则会出错且返回 -1

示例代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
    //判断管道是否存在,不存在则创建
    int ret = access("fifo",F_OK);
    if(-1 == ret)
    {
        ret = mkfifo("fifo",0664);
        if(-1 == ret)
        {
            perror("mkfifo");
            return 1;
        }
        printf("有名管道创建成功\n");
    }
    return 0;
}

3. 设置非阻塞

//获取原来的flags
int flags = fcntl(fd[0], F_GETFL);
// 设置新的flags
flag |= O_NONBLOCK;
// flags = flags | O_NONBLOCK;
fcntl(fd[0], F_SETFL, flags);

如果设置成为非阻塞读,那么例如读管道,如果没有数据就会直接返回,不会阻塞等待

4. 有名管道的特性

一个进程以只读方式打开有名管道会阻塞,直到另一个进程以只写方式打开有名管道

一个进程以只写方式打开有名管道会阻塞,直到另一个进程以只读方式打开有名管道

一个进程以读写方式打开有名管道,不会阻塞

读管道:

  • 管道中有数据,read返回实际读到的字节数。

  • 管道中无数据:

    • 管道写端被全部关闭,read返回0 (相当于读到文件结尾)
    • 写端没有全部被关闭,read阻塞等待

写管道:

  • 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程终止)

  • 管道读端没有全部关闭:

    • 管道已满,write阻塞。
    • 管道未满,write将数据写入,并返回实际写入的字节数。

5.使用存储映射实现原始的聊天

talkA.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#define SIZE 128

int main()
{
    int fdr,fdw;
    char buf[SIZE];
    fdr = open("fifo1",O_RDONLY);
    if(-1 == fdr)
    {
        perror("open");
        return 1;
    }
    printf("以只读方式打开管道1\n");
    fdw = open("fifo2",O_WRONLY);
    if(-1 == fdw)
    {
        perror("write");
        return 1;
    }
    printf("以只写方式打开管道2\n");

    while(1)
    {
        memset(buf,0,SIZE);
        int ret = read(fdr,buf,SIZE);
        if(ret <= 0)
        {
            perror("read");
            break;
        }
        printf("read : %s\n",buf);

        memset(buf,0,SIZE);
        fgets(buf,SIZE,stdin);
        if(buf[strlen(buf)-1] == '\n')
            buf[strlen(buf)-1] = '\0';
        ret = write(fdw,buf,SIZE);
        if(ret <= 0)
        {
            perror("write");
            break;
        }
        printf("write: %s\n",buf);
    }
    close(fdr);
    close(fdw);
    return 0;
}

talkB.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#define SIZE 128

int main()
{
    int fdr,fdw;
    char buf[SIZE];
    fdw = open("fifo1",O_WRONLY);
    if(-1 == fdw)
    {
        perror("write");
        return 1;
    }
    printf("以只写方式打开管道1\n");
    fdr = open("fifo2",O_RDONLY);
    if(-1 == fdr)
    {
        perror("open");
        return 1;
    }
    printf("以只读方式打开管道2\n");

    while(1)
    {
        memset(buf,0,SIZE);
        fgets(buf,SIZE,stdin);
        if(buf[strlen(buf)-1] == '\n')
            buf[strlen(buf)-1] = '\0';
        int ret = write(fdw,buf,SIZE);
        if(ret <= 0)
        {
            perror("write");
            break;
        }
        printf("write: %s\n",buf);

        memset(buf,0,SIZE);
        ret = read(fdr,buf,SIZE);
        if(ret <= 0)
        {
            perror("read");
            break;
        }
        printf("read : %s\n",buf);

    }
    close(fdr);
    close(fdw);
    return 0;
}

缺点:只能一次一端发送一句话

改进:使用多进程,父进程和子进程分别控制读和写,公用一个终端,从而实现了读和写可以在一端多次进行

talkA.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define SIZE 128
int main()
{
    pid_t pid;
    char buf[SIZE];
    int fdr,fdw;
    int ret;

    pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return 1;
    }
    else if(0 == pid)
    {
        //子进程读管道1
        fdr = open("fifo1",O_RDONLY);
        if(-1 == fdr)
        {
            perror("open");
            exit(1);
        }
        while(1){
            memset(buf,0,SIZE);
            ret = read(fdr,buf,SIZE);
            if(ret <= 0)
            {
                perror("read");
                exit(1);
            }
            printf("read : %s\n",buf);
        }
        close(fdr);
    }
    else
    {
        //父进程写管道2
        fdw = open("fifo2",O_WRONLY);
        if(-1 == fdw)
        {
            perror("open");
            exit(1);
        }
        while(1)
        {
            memset(buf,0,SIZE);
            fgets(buf,SIZE,stdin);
            if(buf[strlen(buf)-1 == '\n' ])
                buf[strlen(buf)-1] = '\0';
            ret = write(fdw,buf,SIZE);
            if(-1 == ret)
            {
                perror("write");
                exit(1);
            }
        }
        close(fdw);
    }
    return 0;
}

talkB.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define SIZE 128
int main()
{
    pid_t pid;
    char buf[SIZE];
    int fdr,fdw;
    int ret;

    pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return 1;
    }
    else if(0 == pid)
    {
        //子进程写管道1
        fdw = open("fifo1",O_WRONLY);
        if(-1 == fdw)
        {
            perror("open");
            exit(1);
        }
        while(1)
        {
            memset(buf,0,SIZE);
            fgets(buf,SIZE,stdin);
            if(buf[strlen(buf)-1 == '\n' ])
                buf[strlen(buf)-1] = '\0';
            ret = write(fdw,buf,SIZE);
            if(-1 == ret)
            {
                perror("write");
                exit(1);
            }
        }
        close(fdw);
    }
    else
    {
        //父进程读管道2
        fdr  = open("fifo2",O_RDONLY);
        if(-1 == fdr)
        {
            perror("open");
            exit(1);
        }
        while(1)
        {
            memset(buf,0,SIZE);
            ret = read(fdr,buf,SIZE);
            if(ret <= 0)
            {
                perror("read");
                exit(1);
            }
            printf("read : %s\n",buf);
        }
        close(fdr);
    }
    return 0;
}

2. 共享存储映射

在这里插入图片描述

1. 存储映射函数

  1. mmap函数

    #include <sys/mman.h>
    
    void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
    功能:
        一个文件或者其它对象映射进内存
    参数:
        addr :  指定映射的起始地址, 通常设为NULL, 由系统指定
        length:映射到内存的文件长度
        prot:  映射区的保护方式, 最常用的 :
            a) 读:PROT_READ
            b) 写:PROT_WRITE
            c) 读写:PROT_READ | PROT_WRITE
        flags:  映射区的特性, 可以是
            a) MAP_SHARED : 写入映射区的数据会复制回文件, 且允许其他映射该文件的进程共享。
            b) MAP_PRIVATE : 对映射区的写入操作会产生一个映射区的复制(copy - on - write), 对此区域所做的修改不会写回原文件。
        fd:由open返回的文件描述符, 代表要映射的文件。
        offset:以文件开始处的偏移量, 必须是4k的整数倍, 通常为0, 表示从文件头开始映射
    返回值:
        成功:返回创建的映射区首地址
        失败:MAP_FAILED宏
    

    关于mmap函数的使用总结:

    1. 第一个参数写成NULL

    2. 第二个参数要映射的文件大小 > 0

    3. 第三个参数:PROT_READ 、PROT_WRITE

    4. 第四个参数:MAP_SHARED 或者 MAP_PRIVATE

    5. 第五个参数:打开的文件对应的文件描述符

    6. 第六个参数:4k的整数倍,通常为0

  2. munmap函数

    #include <sys/mman.h>
    
    int munmap(void *addr, size_t length);
    功能:
        释放内存映射区
    参数:
        addr:使用mmap函数创建的映射区的首地址
        length:映射区的大小
    返回值:
        成功:0
        失败:-1
    

2. 注意事项

  1. 创建映射区的过程中,隐含着一次对映射文件的读操作。

  2. 当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制。

  3. 映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭。

  4. 特别注意,当映射文件大小为0时,不能创建映射区。所以,用于映射的文件必须要有实际大小。mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。

  5. munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作。

  6. 如果文件偏移量必须为4K的整数倍。

  7. mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

3. 通过存储映射进行进程通信

read.c

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>

#define SIZE 128

int main()
{
    int fd;
    char buf[SIZE];
    void *addr = NULL;

    fd = open("txt",O_RDWR);
    if(-1 == fd)
    {
        perror("open");
        return 1;
    }

    addr = mmap(addr,1024,PROT_READ,MAP_SHARED,fd,0);
    if(MAP_FAILED == addr)
    {
        perror("mmap");
        return 1;
    }

    close(fd);

    while(1)
    {
        memset(buf,0,SIZE);
        memcpy(buf,addr,SIZE);
        printf("read:%s\n",buf);
        sleep(1);
    }
    munmap(addr,1024);
    return 0;
}

write.c

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>

#define SIZE 128

int main()
{
    int fd;
    char buf[SIZE];
    int i = 0;
    void *addr = NULL;

    fd = open("txt",O_RDWR);
    if(-1 == fd)
    {
        perror("open");
        return 1;
    }

    addr = mmap(addr,1024,PROT_WRITE,MAP_SHARED,fd,0);
    if(MAP_FAILED == addr)
    {
        perror("mmap");
        return 1;
    }

    close(fd);

    while(1)
    {
        memset(buf,0,SIZE);
        sprintf(buf,"hello itcast %d",i++);
        memcpy(addr,buf,SIZE);
        printf("write %d\n",i);
        sleep(1);
    }
    munmap(addr,1024);
    return 0;
}

4. 共享存储映射与多进程

父进程读,子进程写,注意addr是void*,使用需要做强制类型转换

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>

int main()
{
    pid_t pid;
    int fd;
    void* addr = NULL;

    fd = open("txt",O_RDWR);
    if(-1 == fd)
    {
        perror("open");
        return 1;
    }

    addr = mmap(addr,1024,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
    if(MAP_FAILED == addr)
    {
        perror("mmap");
        return 1;
    }

    close(fd);

    pid = fork();
    if(-1 == pid)
    {
        perror("fork");
        return 1;
    }
    else if(0 == pid)
    {
        //子进程写
        memcpy(addr,"hello itcast",sizeof("hello itcast")+1);

        munmap(addr,1024);
    }
    else
    {
        wait(NULL);
        printf("addr = %s\n",(char*) addr);
    }
    return 0;
}

3. 一些小的point

1. fpathconf 用于输出文件配置信息

可使用fpathconf来获取管道的大小

#include <stdio.h>
#include <unistd.h>
int main()
{
    int fd[2];
    int ret = pipe(fd);
    if(-1 == ret)
    {
        perror("pipi");
        return 1;
    }
    printf("fd[0] = %d,fd[1] = %d, pipeSize = %ld\n",fd[0],fd[1],fpathconf(fd[0],_PC_PIPE_BUF));

    return 0;
}

2. ulimit -a

ulimit -a 可以获取一些系统设定内存的大小

itcast@ubuntu:~/classcode/7day$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 3472
max locked memory       (kbytes, -l) 65536
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 3472
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

3. 内存四区模型

在这里插入图片描述

以32位为例,注意:

  1. 堆的大小为8M
  2. const int a = 10;放在栈区

猜你喜欢

转载自blog.csdn.net/qq_41775886/article/details/107496817