Linux-进程间通信(IPC)

进程间通信(IPC)介绍

进程间通信(IPC,InterProcess Communication)是指在不同的进程之间传播或交换信息。IPC 的方式包括管道(无名管道和命名管道)、消息队列、信号量、共享内存、Socket、Streams 等。其中 Socket 和 Streams 支持不同主机上的两个进程间通信。

1. 管道

1.1 无名管道

1.1.1 特点
  • 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
  • 它只能用于父子进程之间的通信。
  • 管道是创建在内存中,进程结束空间释放,管道不复存在。对于它的读写可以使用普通的 readwrite 等函数。
1.1.2 函数原型
cpp 复制代码
#include <unistd.h>
int pipe(int pipefd[2]);
1.1.3 返回值

成功返回 0,失败返回 -1。当一个管道建立时,它会创建两个文件描述符:fd[0] 为读而打开,fd[1] 为写而打开。

1.1.4 无名管道代码示例
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main() {
    int fd[2];
    int pid;
    char buf[128];

    if (pipe(fd) == -1) {
        printf("create pipe fail\n");
    }

    pid = fork();

    if (pid < 0) {
        printf("create child fail\n");
    } else if (pid > 0) {
        sleep(3);
        printf("this is father\n");
        close(fd[0]);
        write(fd[1], "hello from father", strlen("hello from father"));
        wait(NULL);
    } else {
        printf("this is child\n");
        close(fd[1]);
        read(fd[0], buf, 128);
        printf("read = %s \n", buf);
        exit(0);
    }
    return 0;
}

注意:管道的特性:管道里没有数据时会阻塞。

1.2 命名管道

1.2.1 特点
  • 命名管道可以在无关的进程之间交换数据,与无名管道不同。
  • 命名管道有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
1.2.2 函数原型
cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
1.2.3 命名管道代码示例

read.c

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd;
    char buf[128] = {0};

    if (mkfifo("./file", 0600) == -1 && errno != EEXIST) {
        printf("mkfifo fail\n");
        perror("why");
    }

    fd = open("./file", O_RDONLY);
    printf("read open success\n");

    while (1) {
        int n_read = read(fd, buf, 128);
        printf("read %d byte, context = %s \n", n_read, buf);
    }

    close(fd);
    return 0;
}

write.c

cpp 复制代码
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main() {
    int fd;
    char *str = "message from fifo";

    fd = open("./file", O_WRONLY);
    printf("write open success\n");

    while (1) {
        write(fd, str, strlen(str));
        sleep(1);
    }
    close(fd);
    return 0;
}

2. 消息队列

消息队列是信息的链接表,存放在内核中。一个消息队列由一个标识符(即队列 ID)来标识。

2.1 特点

  • 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
  • 消息队列独立于发送与接收进程。进程终止时,消息队列及内容并不会删除。
  • 消息队列可以实现信息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

2.2 函数原型

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

2.3 消息队列代码示例

send.c

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

struct msgbuf {
    long mtype;
    char mtext[128];
};

int main() {
    struct msgbuf sendBuf = {888, "this is msg from queue"};
    struct msgbuf readBuf;
    key_t key = ftok(".", 1);
    printf("key=%d\n", key);

    int msgId = msgget(key, IPC_CREAT | 0777);
    if (msgId == -1) {
        perror("why:");
    }

    while (1) {
        msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
        sleep(1);
        printf("write success\n");

        msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 988, 0);
        sleep(1);
        printf("read from queue: %s\n", readBuf.mtext);
    }

    msgctl(msgId, IPC_RMID, NULL);
    return 0;
}

read.c

cpp 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

struct msgbuf {
    long mtype;
    char mtext[128];
};

int main() {
    struct msgbuf readBuf;
    struct msgbuf sendBuf = {988, "this is msg from father"};
    key_t key = ftok(".", 1);
    printf("key=%d\n", key);

    int msgId = msgget(key, IPC_CREAT | 0777);
    if (msgId == -1) {
        perror("why:");
    }

    while (1) {
        msgrcv(msgId, &readBuf, sizeof(readBuf.mtext), 888, 0);
        sleep(1);
        printf("read from queue: %s\n", readBuf.mtext);

        msgsnd(msgId, &sendBuf, strlen(sendBuf.mtext), 0);
        sleep(1);
        printf("write success\n");
    }

    msgctl(msgId, IPC_RMID, NULL);
    return 0;
}

3. 信号

3.1 信号的处理

信号的处理有三种方法:忽略、捕捉和默认动作。具体的信号默认动作可以使用 man 7 signal 来查看系统的具体定义。

3.2 信号处理函数的注册

信号处理函数的注册可以分为入门版和高级版:

  • 入门版:signal 函数
  • 高级版:sigaction 函数

3.3 信号处理发送函数

信号发送函数也分为入门版和高级版:

  • 入门版:kill
  • 高级版:sigqueue

3.4 signal 函数原型及示例

cpp 复制代码
#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

示例

cpp 复制代码
#include <signal.h>
#include <stdio.h>

void handler(int signum) {
    printf("get signum = %d\n", signum);
    switch (signum) {
        case SIGINT:
            printf("SIGINT\n");
            break;
        case SIGKILL:
            printf("SIGKILL\n");
            break;
        case SIGUSR1:
            printf("SIGUSR1\n");
            break;
    }
}

int main() {
    signal(SIGINT, handler);
    signal(SIGKILL, handler);
    signal(SIGUSR1, handler);
    while (1);
    return 0;
}

3.5 kill 函数原型及示例

cpp 复制代码
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

示例

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc, char **argv) {
    int signum = atoi(argv[1]);
    int pid = atoi(argv[2]);
    printf("signum = %d , pid = %d \n", signum, pid);
    kill(pid, signum);
    printf("send signal ok\n");
    return 0;
}

4. 信号量

信号量(semaphore)是一个计数器,用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

4.1 特点

  • 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
  • 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
  • 支持信号量组。

4.2 函数原型

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, unsigned nsops);
int semctl(int semid, int semnum, int cmd, ...);

4.3 信号量代码示例

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>

//联合体,用于 semctl 初始化
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

void pGetKey(int id) {
    struct sembuf set;
    set.sem_num = 0;
    set.sem_op = -1;
    set.sem_flg = SEM_UNDO;
    semop(id, &set, 1);
    printf("get key\n");
}

void pPutBackKey(int id) {
    struct sembuf set;
    set.sem_num = 0;
    set.sem_op = 1;
    set.sem_flg = SEM_UNDO;
    semop(id, &set, 1);
    printf("put back the key\n");
}

int main() {
    int semid;
    key_t key = ftok(".", 2);

    //1. 获取或创建信号量
    semid = semget(key, 1, IPC_CREAT | 0666);

    union semun initsem;
    initsem.val = 0;

    //2. 初始化信号量
    semctl(semid, 0, SETVAL, initsem);

    int pid = fork();
    if (pid > 0) {
        //4. 拿锁
        pGetKey(semid);
        printf("this is father\n");

        //5. 还锁
        pPutBackKey(semid);

        //6. 销毁锁
        semctl(semid, 0, IPC_RMID);
    } else if (pid == 0) {
        printf("this is child\n");
        //3. 放锁
        pPutBackKey(semid);
    } else {
        printf("fork error\n");
    }

    return 0;
}

5. 共享内存

共享内存(shared memory)指两个或多个进程共享一个给定的存储区。

5.1 特点

  • 共享内存是最快的一种 IPC,因为进程是直接对内存进行存储,而不需要任何数据的拷贝。
  • 只能单独一个进程写或读,如果 A 和 B 进程同时写,会造成数据的混乱(需要搭配信号量来使用)。

5.2 函数原型

cpp 复制代码
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);

5.3 共享内存代码示例

shm_write.c

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main() {
    int shmid;
    char *shmaddr;
    key_t key = ftok(".", 1);

    // 创建共享内存
    shmid = shmget(key, 1024 * 4, IPC_CREAT | 0600);
    if (shmid == -1) {
        printf("create shm fail\n");
        exit(-1);
    }

    // 连接映射共享内存
    shmaddr = shmat(shmid, 0, 0);
    printf("shmat OK\n");

    // 将数据拷贝到共享内存
    strcpy(shmaddr, "hello world\n");

    sleep(5); // 等待 5 秒,避免一下子断开连接。等待另外一个进程读完。

    // 断开共享内存连接
    shmdt(shmaddr);

    // 删除共享内存
    shmctl(shmid, IPC_RMID, 0);

    printf("quit\n");
    return 0;
}

shm_get.c

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main() {
    int shmid;
    char *shmaddr;
    key_t key = ftok(".", 1);

    // 打开共享内存
    shmid = shmget(key, 1024 * 4, 0);
    if (shmid == -1) {
        printf("create shm fail\n");
        exit(-1);
    }

    // 连接并映射共享内存
    shmaddr = shmat(shmid, 0, 0);
    printf("get from shm_write message is: %s", shmaddr);

    // 断开共享内存连接
    shmdt(shmaddr);

    printf("quit\n");
    return 0;
}

6. 结合消息队列、共享内存、信号量的示例

6.1 代码示例

get.c

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include <string.h>

// 消息队列结构
struct msg_form {
    long mtype;
    char mtext;
};

// 联合体,用于 semctl 初始化
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// 初始化信号量
int init_sem(int sem_id, int value) {
    union semun tmp;
    tmp.val = value;
    if (semctl(sem_id, 0, SETVAL, tmp) == -1) {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

// P 操作
int sem_p(int sem_id) {
    struct sembuf sbuf;
    sbuf.sem_num = 0;
    sbuf.sem_op = -1;
    sbuf.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sbuf, 1) == -1) {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V 操作
int sem_v(int sem_id) {
    struct sembuf sbuf;
    sbuf.sem_num = 0;
    sbuf.sem_op = 1;
    sbuf.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sbuf, 1) == -1) {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

// 删除信号量集
int del_sem(int sem_id) {
    union semun tmp;
    if (semctl(sem_id, 0, IPC_RMID, tmp) == -1) {
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}

// 创建一个信号量集
int create_sem(key_t key) {
    int sem_id;
    if ((sem_id = semget(key, 1, IPC_CREAT | 0666)) == -1) {
        perror("semget error");
        exit(-1);
    }
    init_sem(sem_id, 1);  // 初值设为 1 资源未占用
    return sem_id;
}

int main() {
    key_t key;
    int shmid, semid, msqid;
    char *shm;
    struct shmid_ds buf1;  // 用于删除共享内存
    struct msqid_ds buf2;  // 用于删除消息队列
    struct msg_form msg;   // 消息队列用于通知对方更新了共享内存

    // 获取 key 值
    if ((key = ftok(".", 'z')) < 0) {
        perror("ftok error");
        exit(1);
    }

    // 创建共享内存
    if ((shmid = shmget(key, 1024, IPC_CREAT | 0666)) == -1) {
        perror("Create Shared Memory Error");
        exit(1);
    }

    // 连接共享内存
    shm = (char *)shmat(shmid, 0, 0);
    if ((int)shm == -1) {
        perror("Attach Shared Memory Error");
        exit(1);
    }

    // 创建消息队列
    if ((msqid = msgget(key, IPC_CREAT | 0777)) == -1) {
        perror("msgget error");
        exit(1);
    }

    // 创建信号量
    semid = create_sem(key);

    // 读数据
    while (1) {
        msgrcv(msqid, &msg, 1, 888, 0);  // 读取类型为 888 的消息
        if (msg.mtext == 'q')  // quit - 跳出循环
            break;
        if (msg.mtext == 'r')  // read - 读共享内存
        {
            sem_p(semid);
            printf("%s\n", shm);
            sem_v(semid);
        }
    }

    // 断开连接
    shmdt(shm);

    // 删除共享内存、消息队列、信号量
    shmctl(shmid, IPC_RMID, &buf1);
    msgctl(msqid, IPC_RMID, &buf2);
    del_sem(semid);

    return 0;
}

send.c

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <sys/msg.h>
#include <string.h>

// 消息队列结构
struct msg_form {
    long mtype;
    char mtext;
};

// 联合体,用于 semctl 初始化
union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};

// P 操作
int sem_p(int sem_id) {
    struct sembuf sbuf;
    sbuf.sem_num = 0;
    sbuf.sem_op = -1;
    sbuf.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sbuf, 1) == -1) {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V 操作
int sem_v(int sem_id) {
    struct sembuf sbuf;
    sbuf.sem_num = 0;
    sbuf.sem_op = 1;
    sbuf.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sbuf, 1) == -1) {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

int main() {
    key_t key;
    int shmid, semid, msqid;
    char *shm;
    struct msg_form msg;
    int flag = 1;  // while 循环条件

    // 获取 key 值
    if ((key = ftok(".", 'z')) < 0) {
        perror("ftok error");
        exit(1);
    }

    // 获取共享内存
    if ((shmid = shmget(key, 1024, 0)) == -1) {
        perror("shmget error");
        exit(1);
    }

    // 连接共享内存
    shm = (char *)shmat(shmid, 0, 0);
    if ((int)shm == -1) {
        perror("Attach Shared Memory Error");
        exit(1);
    }

    // 创建消息队列
    if ((msqid = msgget(key, 0)) == -1) {
        perror("msgget error");
        exit(1);
    }

    // 获取信号量
    if ((semid = semget(key, 0, 0)) == -1) {
        perror("semget error");
        exit(1);
    }

    // 写数据
    printf("***************************************\n");
    printf("*                 IPC                 *\n");
    printf("*    Input r to send data to server.  *\n");
    printf("*    Input q to quit.                 *\n");
    printf("***************************************\n");

    while (flag) {
        char c;
        printf("Please input command: ");
        scanf("%c", &c);
        switch (c) {
            case 'r':
                printf("Data to send: ");
                sem_p(semid);  // 访问资源
                scanf("%s", shm);
                sem_v(semid);  // 释放资源
                // 清空标准输入缓冲区
                while ((c = getchar()) != '\n' && c != EOF);
                msg.mtype = 888;
                msg.mtext = 'r';  // 发送消息通知服务器读数据
                msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                break;
            case 'q':
                msg.mtype = 888;
                msg.mtext = 'q';
                msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
                flag = 0;
                break;
            default:
                printf("Wrong input!\n");
                // 清空标准输入缓冲区
                while ((c = getchar()) != '\n' && c != EOF);
        }
    }

    // 断开连接
    shmdt(shm);

    return 0;
}

7. 对比总结

通过上述对比可以看出,各种 IPC 方式各有优劣,选择合适的方式进行进程间通信可以提高程序的效率和可靠性。

相关推荐
人生苦短,菜的抠脚20 分钟前
Linux 内核IIO sensor驱动
linux·驱动开发
jz_ddk32 分钟前
[LVGL] 从0开始,学LVGL:进阶应用与项目实战(上)
linux·信息可视化·嵌入式·gui·lvgl·界面设计
望获linux1 小时前
【实时Linux实战系列】Linux 内核的实时组调度(Real-Time Group Scheduling)
java·linux·服务器·前端·数据库·人工智能·深度学习
MC丶科1 小时前
【SpringBoot常见报错与解决方案】端口被占用?Spring Boot 修改端口号的 3 种方法,第 3 种 90% 的人不知道!
java·linux·spring boot
江公望1 小时前
ubuntu kylin(优麒麟)和标准ubuntu的区别浅谈
linux·服务器·ubuntu·kylin
Lynnxiaowen1 小时前
今天我们开始学习python语句和模块
linux·运维·开发语言·python·学习
生态笔记2 小时前
PPT宏代码
linux·服务器·powerpoint
mucheni2 小时前
迅为RK3588开发板Ubuntu 系统开发ubuntu终端密码登录
linux·运维·ubuntu
skywoodsky2 小时前
Ubuntu 24.04环境下的挂起转休眠
linux
小云数据库服务专线2 小时前
GaussDB 应用侧报Read timed out解决方法
linux·服务器·gaussdb