Linux系统编程_进程间通信(IPC)

  • 进程间通信即完成两个进程间数据的传递。需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。比如其中的文件,之所以能通过文件进行IPC,是因为每个文件的文件描述符指向的文件结构体在内核中。注意,用不同进程打开同一文件得到的文件描述符不同。
  • 现今常用的进程间通信方式有:
    ① 管道 (使用最简单)
    ② 信号 (开销最小)
    ③ 共享映射区 (无血缘关系)
    ④ 本地套接字 (最稳定)

文件

利用文件进行进程间通信的程序示例:

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

int main() {
        pid_t pid;
        int fd1, fd2;
        char buf[1024];
        char* str = "------test for shared fd in parent child process---\n";

        pid = fork();
        if(pid == -1) {
                perror("fork error");
                exit(1);
        }
        if(pid == 0) {
                fd1 = open("test.txt", O_RDWR | O_CREAT, 0644);
                if(fd1 < 0) {
                        perror("open error");
                        exit(1);
                }
                write(fd1, str, strlen(str));
                printf("child wrote over\n");
        }
        if(pid > 0) {
                fd2 = open("test.txt", O_RDWR | O_CREAT, 0644);
                if(fd2 < 0) {
                        perror("open error");
                        exit(1);
                }
                sleep(1);

                int ret = read(fd2, buf, sizeof(buf));
                write(STDOUT_FILENO, buf, ret);

                wait(NULL);
        }

        return 0;
}

管道(pipe)

  • pipe函数
  • 管道一般读写行为
    程序示例:利用管道实现ls | wc -l命令
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

void sys_err(char* str) {
        perror(str);
        exit(-1);
}

int main() {
        int fd[2];
        pid_t pid;

        int ret = pipe(fd);
        if(ret == -1) 
                sys_err("pipe error");
        pid = fork();
        if(pid == -1) 
                sys_err("fork error");
        if(pid == 0) {
                close(fd[0]);
                dup2(fd[1], STDOUT_FILENO);
                execlp("ls", "ls", NULL);
                sys_err("execlp ls error");
        } else if(pid > 0) {
                close(fd[1]);
                dup2(fd[0], STDIN_FILENO);
                execlp("wc", "wc", "-l", NULL);
                sys_err("execlp wc -l error");
        }

        return 0;
}

FIFO(命名管道)

  • 用于非血缘关系IPC
//fifo_w.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

void sys_err(char *str)
{
    perror(str);
    exit(-1);
}

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

    if (argc < 2) {
        printf("Enter like this: ./a.out fifoname\n");
        return -1; 
    }   

    int ret = access(argv[1], F_OK);
    if (ret != 0) {
        mkfifo(argv[1], 0664);
    }

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

    while (1) {
        fgets(buf, sizeof(buf), stdin);
        write(fd, buf, strlen(buf));
    }
    close(fd);

    return 0;
}
//fifo_r.c
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>

void sys_err(char *str)
{
    perror(str);
    exit(1);
}
int main(int argc, char *argv[])
{
    int fd, len;
    char buf[4096];

    if (argc < 2) {
        printf("./a.out fifoname\n");
        return -1; 
    }   
    fd = open(argv[1], O_RDONLY);
    if (fd < 0)  
        sys_err("open");
    while (1) {
        len =read(fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, len);
    }
    close(fd);

    return 0;
}

共享存储映射

  • mmap函数
    1)参数
    2)返回值

  • 借助共享内存存放磁盘文件

  • 父子进程、血缘关系进程间的通信

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

int var = 0;

int main() {
        int fd;
        int* p;
        pid_t pid;

        fd = open("temp.txt", O_RDWR | O_CREAT | O_TRUNC, 0644);
        if(fd < 0) {
                perror("open error");
                exit(1);
        }
        unlink("temp.txt");
        ftruncate(fd, 4);
        p = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        //p = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
        if(p == MAP_FAILED) {
                perror("mmap error");
                exit(1);
        }
        close(fd);

        pid = fork();
        if(pid == 0) {
                *p = 100;
                var = 100;
                printf("child: *p = %d, var = %d\n", *p, var);
        } else if(pid > 0) {
                sleep(1);
                printf("parent: *p = %d, var = %d\n", *p, var);
                wait(NULL);

                int ret = munmap(p, 4);
                if(ret == -1) {
                        perror("munmap error");
                        exit(1);
                }
        }

        return 0;
}
  • 匿名映射区
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

int var = 0;

int main() {
        int* p;
        pid_t pid;
        //int fd = open("/dev/zero", O_RDWR);//类unix操作系统下的通行匿名映射方法
        //p = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); 
        p = mmap(NULL, 4, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0); 
        if(p == MAP_FAILED) {
                perror("mmap error");
                exit(1);
        }

        pid = fork();
        if(pid == 0) {
                *p = 100;
                var = 100;
                printf("child: *p = %d, var = %d\n", *p, var);
        } else if(pid > 0) {
                sleep(1);
                printf("parent: *p = %d, var = %d\n", *p, var);
                wait(NULL);

                int ret = munmap(p, 4);
                if(ret == -1) {
                        perror("munmap error");
                        exit(1);
                }
        }

        return 0;
}
  • 非血缘关系进程间通信:
    mmap借助一个文件在内核区创建了一个映射区,多个进程之间利用该映射区完成数据传递。由于内核空间多进程共享,因此无血缘关系的进程间也可以使用mmap来完成通信。只要设置相应的标志位参数flags即可(MAP_SHARED)。关键点是各个进程在创建映射区时必须打开的是同一个文件。
//mmap_nonpc_write.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>

struct STD {
        char name[20];
        int age;
        char sex;
};

void sys_err(const char* str) {
        perror(str);
        exit(-1);
}

int main(int argc, char** argv) {
        int fd;
        struct STD m_std = {"xl", 10, 'm'};

        struct STD* mm;

        if(argc < 2) {
                printf("input error\n");
                exit(-1);
        }

        fd = open(argv[1], O_RDWR | O_CREAT, 0644);
        if(fd < 0) {
                sys_err("open error");
        }
        int ret = ftruncate(fd, sizeof(struct STD));
        if(ret < 0)
                sys_err("ftruncate error");
        mm = mmap(NULL, sizeof(struct STD), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if(mm == MAP_FAILED)
                sys_err("mmap error");

        close(fd);

        while(1) {
                memcpy(mm, &m_std, sizeof(struct STD));
                m_std.age++;
                sleep(1);
        }

        munmap(mm, sizeof(struct STD));

        return 0;
}
//mmap_nonpc_read.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <string.h>
#include <stdlib.h>

struct STD {
        char name[20];
        int age;
        char sex;
};

void sys_err(const char* str) {
        perror(str);
        exit(-1);
}

int main(int argc, char** argv) {
        int fd;
        struct STD m_std;
        struct STD* mm;

        if(argc < 2) {
                printf("a.out file_shared\n");
                exit(-1);
        }

        fd = open(argv[1], O_RDONLY);
        if(fd < 0) {
                sys_err("open error");
        }
        mm = mmap(NULL, sizeof(struct STD), PROT_READ, MAP_SHARED, fd, 0);
        if(mm == MAP_FAILED)
                sys_err("mmap error");

        close(fd);

        while(1) {
                printf("name = %s, age = %d, sex = %c\n", mm->name, mm->age, mm->sex);
                sleep(1);
        }

        munmap(mm, sizeof(struct STD));

        return 0;
}

并且,由于open、write、read函数的底层实现是借助mmap函数,因此非血缘关系进程间通信也可以借助文件的方式。

//file_mmap_w.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

void sys_err(char *str) {
        perror(str);
        exit(-1);
}

#define N 5

int main() {
        int ret;
        char buf[1024];
        char* str = "-------write into file:file_mmap.txt\n";
        int fd = open("file_mmap.txt", O_RDWR |O_TRUNC | O_CREAT, 0644);
        if(fd < 0)  
                sys_err("open error");

        write(fd, str, strlen(str));
        printf("write into file:file_mmap.txt finished\n");

        sleep(N);

        lseek(fd, 0, SEEK_SET); //把读写指针位置移至起始

        ret = read(fd, buf, sizeof(buf));
        ret = write(STDOUT_FILENO, buf, ret);
        if(ret < 0) {
                perror("write second error");
                exit(1);
        }

        close(fd);

        return 0;
}
//file_mmap_r.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

void sys_err(char *str) {
        perror(str);
        exit(-1);
}

int main() {
        char buf[1024];
        char* str = "read and write into file: file_mmap.txt\n";

        int fd = open("file_mmap.txt", O_RDWR);
        if(fd < 0)  
                sys_err("open error");

        int ret = read(fd, buf, sizeof(buf));
        write(STDOUT_FILENO, buf, ret);

        write(fd, str, strlen(str));
        printf("read and write finished\n");

        close(fd);

        return 0;
}

本地套接字(domain socket)

  • 虽然网络socket也可用于同一台主机的进程间通讯(通过 loopback地址127.0.0.1),但是UNIX Domain Socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。
  • UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制。
  • UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值