linux系统编程-3、进程间通信

前言:Linux系统编程的基础系列文章,随着不断学习会将一些知识点进行更新,前期主要是简单了解和学习,本期主要是进程间的通信。

进程间通信

每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核。

  1. 在内核中开辟一块缓冲区;
  2. 进程1把数据从用户空间拷到内核缓冲区;
  3. 进程2再从内核缓冲区把数据读走。

内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。

5、进程间通信

pipe管道

#include <unistd.h>
int pipe(int filedes[2]);
6、pipe管道
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
    int fd[2];///< fd[0]读端; fd[1]写端
    char str[40] = "hello pipe.\n";
    char buf[40];
    pid_t pid;

    if (pipe(fd) < 0) {
        perror("pipe");
        exit(1);
    }

    pid = fork();
    ///< 父写子读
    if (pid > 0) {
        sleep(2);
        ///< 父进程里,关闭父读
        close(fd[0]);
        write(fd[1], str, strlen(str));        
        close(fd[1]);
        wait(NULL);
    }
    else if (pid==0) {
        ///< 子进程里,关闭子写
        close(fd[1]);
        int len = read(fd[0], buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);
        close(fd[0]);
    }
    else {
        perror("fork");
        exit(1);
    }
    return 0;
}

管道只能作用于有血缘关系的进程之间,通过fork来传递,即子进程将会继承父进程的文件描述符

pipe建立过程

7、pipe管道建立过程
  1. 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
  2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
  3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。

先创建管道(pipe),再创建(fork)。

注意事项

  1. 写关闭,读端读完管道里内容时,再次读,返回0,相当于读到 EOF
  2. 写端未关闭,写端暂时无数据,读端读完管道里数据时,再次读,阻塞
  3. 读端关闭,写端写管道,会产生SIGPIPE信号,写进程默认会终止进程
  4. 读端未读,当写端写满管道后,再次写,阻塞

管道大小

fpathconf(int fd, int name)测试管道缓冲区大小,_PC_PIPE_BUF

#include <stdio.h>
#include <unistd.h>

int main(void) {
    int fd[2];

    pipe(fd);
    printf("pipe buf %ld.\n", fpathconf(fd[0], _PC_PIPE_BUF));

    return 0;
}
pipe buf 4096.

非阻塞管道

非阻塞管道, fcntl函数设置O_NONBLOCK标志

flags = fcntl(fd[0], F_GETFL);
flags |= O_NONBLOCK;
fcntl(fd[0], F_GETFL, flags);

fifo有名管道

解决无血缘关系的进程通信。

8、fifo有名管道
#include <sys/types.h> 
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);

fifo_w-test.c

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

int main(int argc, char *argv[]) {
    int fd;
    char buf[40] = "hello fifo.\n";

    if (argc < 2) {
        printf("./fifo_w-test.\n");
        exit(1);
    }

    fd = open(argv[1], O_WRONLY);
    if (fd < 0) {
        perror("open");
        exit(1);
    }
    write(fd, buf, strlen(buf));
    close(fd);

    return 0;
}

fifo_r-test.c

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

int main(int argc, char *argv[]) {
    int fd;
    char buf[40];

    if (argc < 2) {
        printf("./fifo_w-test.\n");
        exit(1);
    }

    fd = open(argv[1], O_RDONLY);
    if (fd < 0) {
        perror("open");
        exit(1);
    }
    int len = read(fd, buf, sizeof(buf));
    write(STDOUT_FILENO, buf, len);
    close(fd);

    return 0;
}
./fifo_w-test fifo-test
./fifo_r-test fifo-test
hello fifo.
prw-rw-r-- 1 kudio kudio     0 Mar 22 19:59 fifo-test

并且可以发现,管道并没有实际存储,只是进行了对内核里缓冲区的索引

  • 当只写打开FIFO管道时,如果没有FIFO没有读端打开,则open写打开会阻塞。
  • FIFO内核实现时可以支持双向通信。(pipe单向通信,因为父子进程共享同一个file结构体)
  • FIFO可以一个读端,多个写端;也可以一个写端,多个读端。(请测试

内存共享映射

9、mmap内存共享映射

mmap可以把磁盘文件的一部分直接映射到内存,这样文件中的位置直接就有对应的内存地址,对文件的读写可以直接用指针来做而不需要read/write函数。

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 
int munmap(void *addr, size_t length);

参数如下

  1. void *addr
    • 如果addr参数为NULL,内核会自己在进程地址空间中选择合适的地址建立映射。
    • 如果addr不是NULL,则给内核一个提示,应该从什么地址开始映射,内核会选择addr之上的某个合适的地址开始映射。
  2. size_t length
    • 需要映射的那一部分文件的长度。、
  3. int prot
    1. PROT_EXEC 表示映射的这一段可执行,例如映射共享库
    2. ROT_READ 表示映射的这一段可读
    3. PROT_WRITE 表示映射的这一段可写
    4. PROT_NONE 表示映射的这一段不可访问
  4. int flags
    1. MAP_SHARED 多个进程对同一个文件的映射是共享的,一个进程对映射的内存做了修改,另一个进程也会看到这种变化。
    2. MAP_PRIVATE 多个进程对同一个文件的映射不是共享的,一个进程对映射的内存做了修改,另一个进程并不会看到这种变化,也不会真的写到文件中去。
  5. int fd
    • 文件描述符
  6. off_t offset
    • 偏移量(4096的整数倍)
  • 如果mmap成功则返回映射首地址,如果出错则返回常数MAP_FAILED。
  • 当进程终止时,该进程的映射内存会自动解除,也可以调用munmap解除映射。munmap成功返回0,出错返回-1。
    • 进程未终止时,且未使用munmap,则会造成内存泄漏。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>

int main(void) {
    int fd;
    fd = open("mmap_str", O_RDWR);
    if (fd < 0) {
        perror("open");
        exit(1);
    }
    int len = lseek(fd, 0, SEEK_END);
    int  *mmap_addr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if (mmap_addr == MAP_FAILED) {
        perror("mmap");
        exit(1);
    }
    mmap_addr[0] = 0x30313233;

    munmap(mmap_addr, len);
    return 0;
}
od -tx1 -tc mmap_str
0000000  48  65  6c  6c  6f  20  6d  6d  61  70  2e  0a
          H   e   l   l   o       m   m   a   p   .  \n
0000014

od -tx1 -tc mmap_str
0000000  33  32  31  30  6f  20  6d  6d  61  70  2e  0a
          3   2   1   0   o       m   m   a   p   .  \n
0000014

通过上述实验,可以发现mmap是shared机制,修改内存的时候也会修改磁盘文件,修改磁盘文件也会修改内存。

10、mmap示例

mmap_w.c

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

#define MAPLEN  0x1000

void sys_err(char *str, int exit_num) {
    perror(str);
    exit(exit_num);
}

int main(int argc, char *argv[]) {
    char *mmap_res = NULL;
    int i = 0;
    int fd = 0;

    if (argc < 2) {
        printf("./mmap-A filename.\n");
        exit(1);
    }

    fd = open(argv[1], O_RDWR | O_CREAT, 0777);
    if (fd < 0)
        sys_err("open", 1);

    if ( lseek(fd, MAPLEN-1, SEEK_SET) < 0 )
        sys_err("lseek", 3);

    if ( write(fd, "\0", 1) < 0 )
        sys_err("write", 4);

    mmap_res = mmap(NULL, MAPLEN, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if (mmap_res == MAP_FAILED)
        sys_err("mmap", 2);

    close(fd);

    while (1) {
        sprintf(mmap_res, "hello:%d", i++);
        sleep(1);
    }
    munmap(mmap_res, MAPLEN);

    return 0;
}

mmap_r.c

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

#define MAPLEN  0x1000

void sys_err(char *str, int exit_num) {
    perror(str);
    exit(exit_num);
}

int main(int argc, char *argv[]) {
    char *mmap_res = NULL;
    int fd = 0;

    if (argc < 2) {
        printf("./mmap-A filename.\n");
        exit(1);
    }

    fd = open(argv[1], O_RDWR);
    if (fd < 0)
        sys_err("open", 1);

    mmap_res = mmap(NULL, MAPLEN, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    if (mmap_res == MAP_FAILED)
        sys_err("mmap", 2);

    close(fd);

    while (1) {
        printf("%s\n", mmap_res);
        sleep(1);
    }
    munmap(mmap_res, MAPLEN);

    return 0;
}
./mmap_w mmap_wr
./mmap_r mmap_wr

hello:1
hello:2
hello:3
hello:4
hello:5
hello:6
hello:7

查看mmap_wr,可发现磁盘内容也被更改。

  • 进程间通信的时候,如果一直创建共享内存空间,最终会导致磁盘空间浪费,所以应该设计成临时文件,在读完时候,close(fd)后unlink(argv[1])

  • 用于进程间通信时,一般设计成结构体,来传输通信的数据

  • 进程间通信的文件,应该设计成临时文件

  • 当报总线错误时,优先查看共享文件是否有存储空间

Socket

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值