Linux:进程间通信

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

1 、管道

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

a. 匿名管道:只能用于具有亲缘关系的进程间的通信,如父进程调用pipe创建一个管道,获取读文件描述符fd[0] 和写文件描述符fd[1],其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_text[BUFSIZ];}; 且进程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 地址和端口,而是绑定一个本地文件,这也就是它们之间的最大区别。

相关推荐
芥子沫1 小时前
Memos捷径(Shortcuts)用法介绍
linux·服务器·windows
会编程的土豆2 小时前
c语言时间戳从入门到精通
linux·c语言·算法
fygfh.2 小时前
Linux的系统架构浅析
linux·arm开发·系统架构
无敌海苔咪2 小时前
【解决方案】CentOS 7 网络显示线缆已拔出
linux·运维·centos
何中应2 小时前
ubuntu如何安装nvm
linux·运维·ubuntu·node.js
cuijiecheng20182 小时前
Linux下CPP-DateTime-library库的使用
linux·运维·服务器
繁华如雪亦如歌2 小时前
Linux:临界资源、同步与互斥、锁、信号量
linux
QC班长2 小时前
如何进行接口性能优化?
java·linux·性能优化·重构·系统架构
_OP_CHEN3 小时前
【Linux网络编程】(一)初识计算机网络:从独立主机到协议世界的入门之旅
linux·服务器·网络·网络协议·计算机网络·socket·c/c++