Linux:进程间通信

Linux 中,不同进程的用户地址空间独立,一般不能互相访问,需借助内核空间进行通信。

1 、管道

管道本质是内核空间的一段环形缓冲区,分为匿名管道和有名管道两种

a. 匿名管道:只能用于具有亲缘关系的进程间的通信,如父进程调用pipe创建一个管道,获取读文件描述符fd0 和写文件描述符fd1,其fork的子进程复制了文件描述符资源,同样有读、写的文件描述符。

b. 有名管道:通过int mkfifo(const char *pathname, mode_t mode) 创建一个类型为管道的设备文件,可以实现无亲缘关系的进程间通信,通信双方通过open("my_named_pipe", O_RDONLY或O_WRONLY);配合read、write进行通信

管道特点:流式,即通信的数据时无格式的;先进先出,因此为了防止同一个进程读到自己发送的数据,一个管道只用于单向通信(匿名管道通过close读或写文件描述符,有名管道通过设置open的权限),若要双向通信应当再创建一个管道

管道效率较低,不适合进程间频繁通信

2、消息队列

消息队列本质和内核维护的一个消息链表,相较于管道,消息队列可以传输自定义的有格式的数据 struct message{long msg_type; char msg_textBUFSIZ;}; 且进程A发送消息后无需等待进程B接收,可继续往下执行

消息队列的使用:

复制代码
/* 包含头文件*/
#include <sys/ipc.h>
#include <sys/msg.h>

/*定义消息结构 */
struct message
{
    long mtype; /* 消息类型*/
    char mtext[BUFSIZE]; /* 消息内容*/
}

/*ftok()创建或者getpid()获取一个唯一的键*/
key_t key = ftok("some_file", 'a');

/* 创建消息队列*/
int msgid = msgget(key, 0666 | IPC_CREAT);

if (msgid == -1)
{
    perror("msgget");
    exit(1);
}

/* 发送消息*/
struct message msg;
msg.mtype = 1; // 消息类型
strcpy(msg.mtext, "Hello, Message Queue!");
if (msgsnd(msgid, &msg, strlen(msg.mtext), 0) == -1)
{
    perror("msgsnd");
    exit(1);
}

/* 接收消息*/
struct message rcv_msg;
if (msgrcv(msgid, &rcv_msg, sizeof(rcv_msg.mtext), rcv_msg.mtype, 0) == -1)
{
    perror("msgrcv");
    exit(1);
}
printf("Received: %s\n", rcv_msg.mtext);

/* 移除消息队列 */
if (msgctl(msgid, IPC_RMID, NULL) == -1)
{
    perror("msgctl");
    exit(1);
}

消息队列优点:可以自定义格式;

消息队列缺点:通信不及时;消息大小有限制(不适合传输大数据),在 Linux 内核中,有两个宏定义 MSGMAXMSGMNB,它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度;存在内核态与用户态的拷贝开销

3 、共享内存

共享内存的机制:不同进程的虚拟地址空间映射到同一块物理内存,不需要拷贝,是最快的IPC方式

复制代码
/* 创建者 */
int main() {
    key_t key = ftok("shmkeyfile", 'k');
    int shmid = shmget(key, 1024, 0666 | IPC_CREAT);
    if (shmid == -1)
    {
        perror("shmget");
        return 1;
    }

    char *memory = (char *)shmat(shmid, NULL, 0);
    if (memory == (char *)(-1))
    {
        perror("shmat");
        return 1;
    }

    /* 使用共享内存 */
    strcpy(memory, "Hello, Shared Memory!");

    shmdt(memory); /* 脱离共享内存 */
    return 0;
}

/* 使用者 */
int main() {
    key_t key = ftok("shmkeyfile", 'k');
    int shmid = shmget(key, 0, 0666);
    if (shmid == -1)
    {
        perror("shmget");
        return 1;
    }

    char *memory = (char *)shmat(shmid, NULL, 0);
    if (memory == (char *)(-1))
    {
        perror("shmat");
        return 1;
    }

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

    shmdt(memory); /* 脱离共享内存 */
    shmctl(shmid, IPC_RMID, NULL); /* 删除共享内存段 */
    return 0;
}

4 、信号量

对于上述的共享内存,如果多个进程同时操作同一内存,存在数据竞态的问题,信号量可避免数据竞态,实现互斥、同步

信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。

信号量表示资源的数量,控制信号量的方式有两种原子操作:

  • 一个是 P 操作,这个操作会把信号量减去 1,相减后如果信号量 < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使用,进程可正常继续执行。
  • 另一个是 V 操作,这个操作会把信号量加上 1,相加后如果信号量 <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量 > 0,则表明当前没有阻塞中的进程;

P 操作是用在进入共享资源之前,V 操作是用在离开共享资源之后,这两个操作是必须成对出现的。

信号量初始化为1,代表互斥信号量;信号量初始化为0,代表同步信号量(生产者消费者模型);信号量初始化为CNT,表示有CNT个资源(如线程池,连接池等等的数量)

5 、信号

上面的进程间通信,都是常规状态下的工作模式。对于异常情况下的工作模式,就需要用「信号」的方式来通知进程。

可以通过 kill 命令的方式给进程发送信号,但前提需要知道运行中的进程 PID 号,例如:

  • kill -9 1050 ,表示给 PID 为 1050 的进程发送 SIGKILL 信号,用来立即结束该进程;

信号事件的来源主要有硬件来源(如键盘 Ctrl+C )和软件来源(如 kill 命令)。

信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程,一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式。

1.执行默认操作。Linux 对每种信号都规定了默认操作,例如,上面列表中的 SIGTERM 信号,就是终止进程的意思。

2.捕捉信号。我们可以为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。

3.忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILLSIGSTOP,它们用于在任何时候中断或结束某一进程。

6 、socket

管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,要实现跨网络与不同主机上的进程之间通信,需要 Socket 通信。

针对 TCP 协议通信的 socket 编程模型

针对 UDP 协议通信的 socket 编程模型

针对本地进程间通信的 socket 编程模型

本地 socket 被用于在同一台主机上进程间通信的场景:

  • 本地 socket 的编程接口和 IPv4 、IPv6 套接字编程接口是一致的,可以支持「字节流」和「数据报」两种协议;
  • 本地 socket 的实现效率大大高于 IPv4 和 IPv6 的字节流、数据报 socket 实现;

对于本地字节流 socket,其 socket 类型是 AF_LOCAL 和 SOCK_STREAM。

对于本地数据报 socket,其 socket 类型是 AF_LOCAL 和 SOCK_DGRAM。

本地字节流 socket 和 本地数据报 socket 在 bind 的时候,不像 TCP 和 UDP 要绑定 IP 地址和端口,而是绑定一个本地文件,这也就是它们之间的最大区别。

相关推荐
FoldWinCard9 小时前
D5 Linux 网络及端口命令
linux·运维·服务器
零涂毕业设计10 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
tedcloud12310 小时前
OmniRoute怎么部署?开源AI模型路由平台Linux部署教程
linux·服务器·人工智能·开源·音视频
霸道流氓气质11 小时前
KMS 密钥管理服务(Key Management Service)原理与实践
linux·服务器·数据库
SakitamaX11 小时前
LVS(Linux Virtual Server)概念详解
linux·运维·lvs
小张同学a.11 小时前
Linux系统管理
linux·运维
阿龙的工作记录11 小时前
深入理解Linux设备驱动模型
linux·嵌入式硬件·工作记录
代码村新手12 小时前
Linux的基本指令
linux·运维·服务器
hkj880812 小时前
Ubuntu 切换java版本
java·linux·ubuntu
撩得Android一次心动14 小时前
Linux编程笔记3【个人用】
linux·笔记