Linux进程间通信

37 篇文章 2 订阅
11 篇文章 1 订阅

概述

Inter Process Communication(IPC)是指在作为程序执行单位的进程之间进行的数据交换。进程的依赖由操作系统管理,以便尽可能松散耦合。因此,IPC必须通过Linux操作系统的功能完成。操作系统提供给进程的数据交换方法不止一种。提供了各具特色的丰富多彩的方法。
这里介绍的有以下5个。

  • 共享内存
  • 信号量
  • Map内存
  • 管道(Pipe)
  • socket 通信

共享内存

优点

进程之间共享相同的内存。共享内存的最大优势在于其访问速度。一旦生成共享内存,就可以在不使用内核功能的情况下进行访问,因此可以以与进程中的普通内存相同的速度进行访问。这种方法经常用于需要处理性能的软件,如需要这种访问速度的好处。

缺点

另一方面,如果从两个进程同时写入,则会发生冲突。共享内存不包含防止这种冲突的互斥机制。
需要自己准备。

例子

在process_a中确保共享内存字符串“Hello World!”写入共享内存。写入共享内存的数据由process_b读取并打印到控制台上。

  • process_a.cpp
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string>
#include <sys/shm.h>
#include <sys/stat.h>
#include <sys/ipc.h>

int main ()
{
    /* 段ID的生成 */
    const std::string file_path("./key_data.dat");
    const int id = 42;
    const auto key = ftok(file_path.c_str(), id);
    if(-1 == key) {
        std::cerr << "Failed to acquire key" << std::endl;
        return EXIT_FAILURE;
    }

    /* 段的分配 */
    const int shared_segment_size = 0x6400;
    const auto segment_id = shmget(key, shared_segment_size,
                                   IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);
    if(-1==segment_id) {
        std::cerr << segment_id << " :Failed to acquire segment" << std::endl;
        return EXIT_FAILURE;
    }

    /* 显示密钥和片段ID */
    std::cout << "キー:" << key << std::endl;
    std::cout << "セグメントID:" << segment_id << std::endl;

    /* 附加到共享内存 */
    char* const shared_memory = reinterpret_cast<char*>(shmat(segment_id, 0, 0));
    printf ("shared memory attached at address %p\n", shared_memory);

    /* 写入共享存储器 */
    sprintf (shared_memory, "Hello, world.");


    std::cout << "Hit any key when ready to close shared memory" << std::endl;
    std::cin.get();

    /* 分离共享内存 */
    shmdt(shared_memory);
    /* 释放共享内存 */
    shmctl (segment_id, IPC_RMID, 0);

    return EXIT_SUCCESS;
}
  • process_b.cpp
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <string>
#include <sys/shm.h>
#include <sys/stat.h>

int main ()
{
    /* 段ID的生成 */
    const std::string file_path("./key_data.dat");
    const int id = 42;
    const auto key = ftok(file_path.c_str(), id);
    if(-1 == key) {
        std::cerr << "Failed to acquire key" << std::endl;
        return EXIT_FAILURE;
    }


    /* 附加到共享内存 */
    const auto segment_id = shmget(key, 0, 0);
    const char* const shared_memory = reinterpret_cast<char*>(shmat(segment_id, 0, 0));
    printf ("shared memory attached at address %p\n", shared_memory);

    /* 读取共享内存 */
    printf ("%s\n", shared_memory);

    /* 分离共享内存 */
    shmdt(shared_memory);

    return EXIT_SUCCESS;
}

涉及进程间通信的Linux命令

介绍创建进程间通信程序所需的命令。

ipcs命令

显示进程间通信的状态。

kei@Glou-Glou:~/src/ipc/shared_memory$ ipcs
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages

------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status
0x0052e2c1 0          postgres   600        56         6

------ Semaphore Arrays --------
key        semid      owner      perms      nsems

ipcrm命令

释放确保的共享资源。

kei@Glou-Glou:~/src/ipc/shared_memory$ ipcrm shm <shmid>

信号量

信号量在没有父子关系的进程之间共享整数类型的数据。它有一种机制来控制多个进程的同时访问,但它的缺点是只能处理整数类型的数据。主要被使用在共享内存的排他处理上。

例子

  • process_a.cpp
#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/types.h>
#include<cstdlib>
#include<iostream>

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short int *array;
    struct seminfo *__buf;
};

enum SEMAPHORE_OPERATION
{
    UNLOCK = -1,
    WAIT = 0,
    LOCK = 1,
};

int main()
{
    /* 确保信号量的安全 */
    const key_t key = 112;
    int sem_flags = 0666;
    int sem_id = semget(key, 1, sem_flags | IPC_CREAT);
    if(-1 == sem_id)
    {
        std::cerr << "Failed to acquire semapore" << std::endl;
        return EXIT_FAILURE;
    }

    /* 信号量的初始化 */
    union semun argument;
    unsigned short values[1];
    values[0] = 1;
    argument.array = values;
    semctl(sem_id, 0, SETALL, argument);

    /* 等待进程B的执行 */
    std::cout << "Waiting for post operation..." << std::endl;
    sembuf operations[1];
    operations[0].sem_num = 0;
    operations[0].sem_op = WAIT;
    operations[0].sem_flg = SEM_UNDO;
    semop(sem_id, operations, 1);

    /* 信号量的解放 */
    auto result = semctl(sem_id, 1, IPC_RMID, NULL);
    if(-1 == result)
    {
        std::cerr << "Failed to close semaphore" << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}
  • process_b.cpp
#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/types.h>
#include<cstdlib>
#include<iostream>

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short int *array;
    struct seminfo *__buf;
};

enum SEMAPHORE_OPERATION
{
    UNLOCK = -1,
    WAIT = 0,
    LOCK = 1,
};

int main()
{
    /* 确保信号量的安全 */
    const key_t key = 112;
    int sem_flags = 0666;
    int sem_id = semget(key, 1, sem_flags);
    if(-1 == sem_id)
    {
        std::cerr << "Failed to acquire semapore" << std::endl;
        return EXIT_FAILURE;
    }

    std::cout << "Unlock semaphore" << std::endl;

    /* POST信号 */
    sembuf operations[1];
    operations[0].sem_num = 0;
    operations[0].sem_op = UNLOCK;
    operations[0].sem_flg = SEM_UNDO;
    semop(sem_id, operations, 1);

    return EXIT_SUCCESS;
}

Map内存

多个进程通过文件进行通信。在访问文件时执行内存映射以加快处理速度。内存映射是指将文件、设备等映射到虚拟地址空间,使其可以像内存一样访问。这允许您将数据放置在文件中,而无需序列化。

例子

  • process_a.cpp
#include<cstdlib>
#include<cstdio>
#include<string>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<unistd.h>

const unsigned int FILE_LENGTH = 0x100;
const std::string FILE_NAME("./data.dat");

int main()
{
    /* Prepare a file large enough to hold an unsigned integer. */
    auto fd = open(FILE_NAME.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
    lseek(fd, FILE_LENGTH+1, SEEK_SET);
    write(fd, "", 1);
    lseek(fd, 0, SEEK_SET);
    /* Create the memory mapping. */
    char* const file_memory = reinterpret_cast<char*>(mmap(0, FILE_LENGTH, PROT_WRITE, MAP_SHARED, fd, 0));
    close(fd);
    /* Write a random integer to memory-mapped area. */
    sprintf(file_memory, "%s", "Hello World!");
    /* Release the memory (unnecessary because the program exits). */
    munmap (file_memory, FILE_LENGTH);

    return EXIT_SUCCESS;
}
  • process_b.cpp
#include<cstdlib>
#include<cstdio>
#include<string>
#include<fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<unistd.h>

const unsigned int FILE_LENGTH = 0x100;
const std::string FILE_NAME("./data.dat");

int main()
{
    /* Open the file. */
    auto fd = open(FILE_NAME.c_str(), O_RDWR, S_IRUSR | S_IWUSR);
    /* Create the memory mapping. */
    char* const file_memory = reinterpret_cast<char*>(mmap(0, FILE_LENGTH, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
    close(fd);
    /* Read the integer, print it out, and double it. */
    printf("%s\n", file_memory);

    /* Release the memory (unnecessary because the program exits). */
    munmap(file_memory, FILE_LENGTH);

    return EXIT_SUCCESS;
}

管道

两个进程之间的管道是父进程中创建的一对文件。管道连接父进程拨叉时的结果进程。管道被称为“匿名”,因为它们不存在于文件名空间中。管道通常只连接两个进程,但您也可以将任何数的子进程相互连接,或者仅通过父进程和一个管道连接。

管道是在父进程中使用pipe(2)调用创建的。Pipe(2)将两个文件描述符返回到参数数组。fork后,两个进程使用p[0]读取,并使用p[1]写入。该过程实际上是由它们管理的循环缓冲区之间的读取和写入。

使用fork(2),每个进程打开的文件表被复制,所以每个进程都有两个读取器和两个写入器。为了使管道正常工作,您必须关闭额外的读取器和写入器。

例如,如果另一个读取器的结尾也被打开以通过相同的进程写入,则文件结束的指示不会返回。以下代码显示了管道创建、fork和重复管道结束的清除。

#include <stdio.h>
#include <unistd.h>
 ...
 	int p[2];
 ...
 	if (pipe(p) == -1) exit(1);
 	switch( fork() )
 	{
 		case 0:						/* in child */
 			close( p[0] );
 			dup2( p[1], 1);
 			close P[1] );
 			exec( ... );
 			exit(1);
 		default:						/* in parent */
 			close( p[1] );
 			dup2( P[0], 0 );
 			close( p[0] );
 			break;
 	}
 	...

显示了在一定条件下从管道读取和写入管道的结果。
管道读取和写入的结果

项目条件结果
读入空管道,写入器连接读取将被阻塞
写入完整的管道,写入器连接写入将被阻塞
读入空管道,无连接写入器EOF被送回
写入没有读入SIGPIPE

如果将fcntl(2)调用到描述符并设置FNDELAY,则可以阻止阻塞。如果在此状态下调用输入/输出函数,则errno将设置EWOULDBLOCK,并返回错误(-1)。

FIFO

又名named piped。在文件系统上具有名称的管道。没有父子关系,所有进程都可以创建、访问和删除FIFO。这个FIFO也可以通过mkfifo命令从控制台创建。

$ mkfifo ./fifo.tmp

创建的FIFO可以像普通文件一样访问。

  • FIFO写入
$ cat > fifo.tmp
  • FIFO读入
$ cat fifo.tmp

以下是在FIFO中交换数据的示例代码。

  • process_a.cpp
#include <cstdio>
#include <cstdlib>
#include <string>
#include <iostream>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

const size_t BUFFER_SIZE(80);

int main()
{
    // 文件描述符
    int fd;

    // FIFO作成
    // mkfifo(<pathname>, <permission>)
    mkfifo("/tmp/myfifo", 0666);

    std::string message("Hello World!");
    // 在只写打开FIFO
    fd = open("/tmp/myfifo", O_WRONLY);

    // 消息的写入
    write(fd, message.c_str(), message.size() + 1);
    close(fd);

    return EXIT_SUCCESS;
}
  • process_b.cpp
#include <cstdio>
#include <cstdlib>
#include <string>
#include <iostream>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

const size_t BUFFER_SIZE(80);

int main()
{
    // 文件描述符
    int fd;

    // FIFO作成
    // mkfifo(<pathname>, <permission>)
    mkfifo("/tmp/myfifo", 0666);

    char str[BUFFER_SIZE];
    // 在只读打开FIFO
    fd = open("/tmp/myfifo", O_RDONLY);
    read(fd, str, BUFFER_SIZE);
    const std::string message(str);

    // 读取内容的显示
    std::cout << message << std::endl;
    close(fd);

    return EXIT_SUCCESS;
}

FIFO可以从多个进程读取和写入。多个同时访问将自动排他处理。

socket通讯

socket实现不依赖进程之间的通信。此外,还有其他方法没有的好处。它可以与其他机器上的进程进行通信。以下是使用socket的示例代码:

  • process_a.cpp
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cassert>
#include<sys/socket.h>
#include<sys/un.h>
#include<unistd.h>

int server (const int& client_socket)
{
    while (1) {
        const size_t MAX_SIZE = 128;
        char buffer[MAX_SIZE];
        read(client_socket, buffer, MAX_SIZE);
        const std::string message(buffer);
        if(message.size() == 0) {
            return 0;
        } else {
            std::cout << message << std::endl;
            return 1;
        }
    }

    assert(!"This line must not be executed.");
}

int main()
{
    const std::string socket_name("my_socket");
    int socket_fd;
    sockaddr_un name;
    int client_sent_quit_message;
    /* socket作成 */
    socket_fd = socket(PF_LOCAL, SOCK_STREAM, 0);
    /* 设置为服务器 */
    name.sun_family = AF_LOCAL;
    strcpy(name.sun_path, socket_name.c_str());
    bind(socket_fd, reinterpret_cast<sockaddr*>(&name), SUN_LEN (&name));
    /* 打开socket */
    listen(socket_fd, 5);
    /* 等待,直到连接到消息 */
    do {
        sockaddr_un client_name;
        socklen_t client_name_len;
        int client_socket_fd;
        /* 等待直到有连接 */
        client_socket_fd = accept(socket_fd, reinterpret_cast<sockaddr*>(&client_name), &client_name_len);
        /* 收到信息 */
        client_sent_quit_message = server(client_socket_fd);
        /* 切断*/
        close(client_socket_fd);
    } while (!client_sent_quit_message);
    /* 关闭socket */
    close(socket_fd);
    unlink(socket_name.c_str());

    return EXIT_SUCCESS;
}
  • process_b.cpp
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<sys/socket.h>
#include<sys/un.h>
#include<unistd.h>

/* Write TEXT to the socket given by file descriptor SOCKET_FD. */
int write_text(const int& socket_fd, const std::string& message)
{
    /* Write the string. */
    write(socket_fd, message.c_str(), message.size() + 1);
    return 0;
}

int main ()
{
    const std::string socket_name("my_socket");
    const std::string message = "Hello World!!";
    int socket_fd;
    sockaddr_un name;
    /* socket作成 */
    socket_fd = socket(PF_LOCAL, SOCK_STREAM, 0);
    /* 设置socket名 */
    name.sun_family = AF_LOCAL;
    strcpy(name.sun_path, socket_name.c_str());
    /* 连接socket */
    connect(socket_fd, reinterpret_cast<sockaddr*>(&name), SUN_LEN (&name));
    /* 发送消息 */
    write_text(socket_fd, message);
    close(socket_fd);
    return EXIT_SUCCESS;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值