Linux进程间通信方式

2023-05-16

  • 进程与进程通信的概念
  • 进程通信的应用场景
  • 进程通信的几种方式
    • 管道
      • 管道简介
      • 管道原理
        • 管道如何通信
        • 管道如何创建
        • 管道读写实现
      • 管道api与用法
        • 普通管道
        • 流管道
        • 命名管道
          • 实现原理
          • api与应用
      • 匿名管道和有名管道总结
    • 信号
      • 信号来源
      • 信号生命周期和处理流程
      • api使用
    • 消息队列
      • 实现原理
      • api与应用以Posix为例
    • 共享内存
      • 实现原理
      • api和应用
        • system V共享内存
        • 共享文件映射
        • posix共享内存
    • 信号量
    • 原始套接字
      • 套接字特性
      • api实例

进程与进程通信的概念

进程是操作系统的概念,每当我们执行一个程序时,对于操作系统来讲就创建了一个进程,在这个过程中,伴随着资源的分配和释放。可以认为进程是一个程序的一次执行过程。
进程用户空间是相互独立的,一般而言是不能相互访问的。但很多情况下进程间需要互相通信,来完成系统的某项功能。进程通过与内核及其它进程之间的互相通信来协调它们的行为。

进程通信的应用场景

  • 数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几兆字节之间。

  • 共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。

  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

  • 资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。

  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。

进程通信的几种方式

管道

管道简介

管道包括三种:
- 普通管道pipe: 通常有两种限制,一是单工,只能单向传输;二是只能在父子或者兄弟进程间使用.
- 流管道s_pipe: 去除了第一种限制,为半双工,只能在父子或兄弟进程间使用,可以双向传输.
- 命名管道:name_pipe:去除了第二种限制,可以在许多并不相关的进程之间进行通讯.

管道原理

管道如何通信

管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。
这里写图片描述

管道如何创建

从原理上,管道利用fork机制建立,从而让两个进程可以连接到同一个PIPE上。最开始的时候,上面的两个箭头都连接在同一个进程Process 1上(连接在Process 1上的两个箭头)。当fork复制进程的时候,会将这两个连接也复制到新的进程(Process 2)。随后,每个进程关闭自己不需要的一个连接 (两个黑色的箭头被关闭; Process 1关闭从PIPE来的输入连接,Process 2关闭输出到PIPE的连接),这样,剩下的红色连接就构成了如上图的PIPE。
这里写图片描述
那么管道创建后其对应的数据结构是什么呢?
在 Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。如下图:
管道数据结构
有两个 file 数据结构,但它们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,而另一个是从管道中读出数据的例程地址。这样,用户程序的系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊操作。

管道读写实现

管道实现的源代码在fs/pipe.c中,在pipe.c中有很多函数,其中有两个函数比较重要,即管道读函数pipe_read()和管道写函数pipe_wrtie()。管道写函数通过将字节复制到 VFS 索引节点指向的物理内存而写入数据,而管道读函数则通过复制物理内存中的字节而读出数据。当然,内核必须利用一定的机制同步对管道的访问,为此,内核使用了锁、等待队列和信号。

当写进程向管道中写入时,它利用标准的库函数write(),系统根据库函数传递的文件描述符,可找到该文件的 file 结构。file 结构中指定了用来进行写操作的函数(即写入函数)地址,于是,内核调用该函数完成写操作。写入函数在向内存中写入数据之前,必须首先检查 VFS 索引节点中的信息,同时满足如下条件时,才能进行实际的内存复制工作:

内存中有足够的空间可容纳所有要写入的数据;
内存没有被读程序锁定。
如果同时满足上述条件,写入函数首先锁定内存,然后从写进程的地址空间中复制数据到内存。否则,写入进程就休眠在 VFS 索引节点的等待队列中,接下来,内核将调用调度程序,而调度程序会选择其他进程运行。写入进程实际处于可中断的等待状态,当内存中有足够的空间可以容纳写入数据,或内存被解锁时,读取进程会唤醒写入进程,这时,写入进程将接收到信号。当数据写入内存之后,内存被解锁,而所有休眠在索引节点的读取进程会被唤醒。

管道的读取过程和写入过程类似。但是,进程可以在没有数据或内存被锁定时立即返回错误信息,而不是阻塞该进程,这依赖于文件或管道的打开模式。反之,进程可以休眠在索引节点的等待队列中等待写入进程写入数据。当所有的进程完成了管道操作之后,管道的索引节点被丢弃,而共享数据页也被释放。

管道api与用法

普通管道

#include <unistd.h>
int pipe(int filedes[2]);
filedes[0]用于读出数据,读取时必须关闭写入端,即close(filedes[1]);
filedes[1]用于写入数据,写入时必须关闭读取端,即close(filedes[0])。
int main(void)
{
    int n;
    int fd[2];
    pid_t pid;
    char line[MAXLINE];

    if(pipe(fd)!0){                 /* 先建立管道得到一对文件描述符 */
        exit(0);
    }

    if((pid = fork())!=0)            /* 父进程把文件描述符复制给子进程 */
        exit(1);
    else if(pid > 0){                /* 父进程写 */
        close(fd[0]);                /* 关闭读描述符 */
        write(fd[1], "\nhello world\n", 14);
    }
    else{                            /* 子进程读 */
        close(fd[1]);                /* 关闭写端 */
        n = read(fd[0], line, MAXLINE);
        write(STDOUT_FILENO, line, n);
    }

    exit(0);
}

流管道

命名管道

由于基于fork机制,所以管道只能用于父进程和子进程之间,或者拥有相同祖先的两个子进程之间 (有亲缘关系的进程之间)。为了解决这一问题,Linux提供了FIFO方式连接进程。FIFO又叫做命名管道(named PIPE)。

实现原理

FIFO (First in, First out)为一种特殊的文件类型,它在文件系统中有对应的路径。当一个进程以读(r)的方式打开该文件,而另一个进程以写(w)的方式打开该文件,那么内核就会在这两个进程之间建立管道,所以FIFO实际上也由内核管理,不与硬盘打交道。之所以叫FIFO,是因为管道本质上是一个先进先出的队列数据结构,最早放入的数据被最先读出来,从而保证信息交流的顺序。FIFO只是借用了文件系统(file system,命名管道是一种特殊类型的文件,因为Linux中所有事物都是文件,它在文件系统中以文件名的形式存在。)来为管道命名。写模式的进程向FIFO文件中写入,而读模式的进程从FIFO文件中读出。当删除FIFO文件时,管道连接也随之消失。FIFO的好处在于我们可以通过文件的路径来识别管道,从而让没有亲缘关系的进程之间建立连接。

api与应用
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *filename, mode_t mode);
该函数的第一个参数是一个普通的路径名,也就是创建 后FIFO的名字。第二个参数与打开普通文件的open()函数中的mode 参数相同。 如果mkfifo的第一个参数是一个已经存在的路径名时,会返回EEXIST错误,所以一般典型的调用代码首先会检查是否返回该错误,如果确实返回该错 误,那么只要调用打开FIFO的函数就可以了。一般文件的I/O函数都可以用于FIFO,如close、read、write等等。
int mknode(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0 );
其中filename是被创建的文件名称,mode表示将在该文件上设置的权限位和将被创建的文件类型(在此情况下为S_IFIFO),dev是当创建设备特殊文件时使用的一个值。因此,对于先进先出文件它的值为0

程序实例:

#include<sys/types.h>  
#include<sys/stat.h>  
#include<fcntl.h>  
char *FIFO = "/tmp/my_fifo";  
main()  
{  
char buffer[80];  
int fd;  
unlink(FIFO);  
mkfifo(FIFO,0666);  
if(fork()>0){  
char s[ ] = “hello!\n”;  
fd = open (FIFO,O_WRONLY);  
write(fd,s,sizeof(s));  
close(fd);  
}  
else{  
fd= open(FIFO,O_RDONLY);  
read(fd,buffer,80);  
printf(“%s”,buffer);  
close(fd);  
}  
}  

匿名管道和有名管道总结

(1)管道是特殊类型的文件,在满足先入先出的原则条件下可以进行读写,但不能进行定位读写。
(2)匿名管道是单向的,只能在有亲缘关系的进程间通信;有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
(3)无名管道阻塞问题:无名管道无需显示打开,创建时直接返回文件描述符,在读写时需要确定对方的存在,否则将退出。如果当前进程向无名管道的一端写数据,必须确定另一端有某一进程。如果写入无名管道的数据超过其最大值,写操作将阻塞,如果管道中没有数据,读操作将阻塞,如果管道发现另一端断开,将自动退出。
(4)有名管道阻塞问题:有名管道在打开时需要确实对方的存在,否则将阻塞。即以读方式打开某管道,在此之前必须一个进程以写方式打开管道,否则阻塞。此外,可以以读写(O_RDWR)模式打开有名管道,即当前进程读,当前进程写,不会阻塞。

信号

信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。

  • 信号是Linux系统中用于进程间互相通信或者操作的一种机制,信号可以在任何时候发给某一进程,而无需知道该进程的状态。
  • 如果该进程当前并未处于执行状态,则该信号就有内核保存起来,直到该进程恢复执行并传递给它为止。
  • 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时信号才被传递给进程。

    Linux系统中常用信号:
    (1)SIGHUP:用户从终端注销,所有已启动进程都将收到该进程。系统缺省状态下对该信号的处理是终止进程。
    (2)SIGINT:程序终止信号。程序运行过程中,按Ctrl+C键将产生该信号。
    (3)SIGQUIT:程序退出信号。程序运行过程中,按Ctrl+\\键将产生该信号。
    (4)SIGBUS和SIGSEGV:进程访问非法地址。
    (5)SIGFPE:运算中出现致命错误,如除零操作、数据溢出等。
    (6)SIGKILL:用户终止进程执行信号。shell下执行kill -9发送该信号。
    (7)SIGTERM:结束进程信号。shell下执行kill 进程pid发送该信号。
    (8)SIGALRM:定时器信号。
    (9)SIGCLD:子进程退出信号。如果其父进程没有忽略该信号也没有处理该信号,则子进程退出后将形成僵尸进程。

信号来源

信号是软件层次上对中断机制的一种模拟,是一种异步通信方式,信号可以在用户空间进程和内核之间直接交互,内核可以利用信号来通知用户空间的进程发生了哪些系统事件,信号事件主要有两个来源:
- 硬件来源:用户按键输入Ctrl+C退出、硬件异常如无效的存储访问等。
- 软件来源:终止进程信号、其他进程调用kill函数、软件异常产生信号。

信号生命周期和处理流程

(1)信号被某个进程产生,并设置此信号传递的对象(一般为对应进程的pid),然后传递给操作系统;
(2)操作系统根据接收进程的设置(是否阻塞)而选择性的发送给接收者,如果接收者阻塞该信号(且该信号是可以阻塞的),操作系统将暂时保留该信号,而不传递,直到该进程解除了对此信号的阻塞(如果对应进程已经退出,则丢弃此信号),如果对应进程没有阻塞,操作系统将传递此信号。
(3)目的进程接收到此信号后,将根据当前进程对此信号设置的预处理方式,暂时终止当前代码的执行,保护上下文(主要包括临时寄存器数据,当前程序位置以及当前CPU的状态)、转而执行中断服务程序,执行完成后再恢复到中断的位置。当然,对于抢占式内核,在中断返回时还将引发新的调度。

api使用

消息队列

消息队列是Linux IPC中很常用的一种通信方式,它通常用来在不同进程间发送特定格式的消息数据。
消息队列和之前讨论过的管道和FIFO有很大的区别,主要有以下两点:
一个进程向消息队列写入消息之前,并不需要某个进程在该队列上等待该消息的到达,而管道和FIFO是相反的,进程向其中写消息时,管道和FIFO必需已经打开来读,那么内核会产生SIGPIPE信号。
IPC的持续性不同。管道和FIFO是随进程的持续性,当管道和FIFO最后一次关闭发生时,仍在管道和FIFO中的数据会被丢弃。消息队列是随内核的持续性,即一个进程向消息队列写入消息后,然后终止,另外一个进程可以在以后某个时刻打开该队列读取消息。只要内核没有重新自举,消息队列没有被删除。
消息队列中的每条消息通常具有以下属性:
- 一个表示优先级的整数;
- 消息的数据部分的长度;
- 消息数据本身

实现原理

api与应用(以Posix为例)

  • POSIX消息队列的创建和关闭
    POSIX消息队列的创建,关闭和删除用到以下三个函数接口:
#include <mqueue.h>  
mqd_t mq_open(const char *name, int oflag, /* mode_t mode, struct mq_attr *attr */);  
                       //成功返回消息队列描述符,失败返回-1  
mqd_t mq_close(mqd_t mqdes);  
mqd_t mq_unlink(const char *name);  
                           //成功返回0,失败返回-1  
mq_open用于打开或创建一个消息队列。
name:表示消息队列的名字,它符合POSIX IPC的名字规则。
oflag:表示打开的方式,和open函数的类似。有必须的选项:O_RDONLY,O_WRONLY,O_RDWR,还有可选的选项:O_NONBLOCK,O_CREAT,O_EXCL。
mode:是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时,才需要提供该参数。表示默认访问权限。可以参考open。
attr:也是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时才需要。该参数用于给新队列设定某些属性,如果是空指针,那么就采用默认属性。
mq_open返回值是mqd_t类型的值,被称为消息队列描述符。在Linux 2.6.18中该类型的定义为整型:
#include <bits/mqueue.h>  
typedef int mqd_t;  
mq_close用于关闭一个消息队列,和文件的close类型,关闭后,消息队列并不从系统中删除。一个进程结束,会自动调用关闭打开着的消息队列。
mq_unlink用于删除一个消息队列。消息队列创建后只有通过调用该函数或者是内核自举才能进行删除。每个消息队列都有一个保存当前打开着描述符数的引用计数器,和文件一样,因此本函数能够实现类似于unlink函数删除一个文件的机制。
  • POSIX消息队列的属性
    POSIX标准规定消息队列属性mq_attr必须要含有以下四个内容:
long    mq_flags //消息队列的标志:0或O_NONBLOCK,用来表示是否阻塞   
long    mq_maxmsg  //消息队列的最大消息数  
long    mq_msgsize  //消息队列中每个消息的最大字节数  
long    mq_curmsgs  //消息队列中当前的消息数目  
在Linux 2.6.18中mq_attr结构的定义如下:
#include <bits/mqueue.h>  
struct mq_attr  
{  
  long int mq_flags;      /* Message queue flags.  */  
  long int mq_maxmsg;   /* Maximum number of messages.  */  
  long int mq_msgsize;   /* Maximum message size.  */  
  long int mq_curmsgs;   /* Number of messages currently queued.  */  
  long int __pad[4];  
};  
POSIX消息队列的属性设置和获取可以通过下面两个函数实现:
#include <mqueue.h>  
mqd_t mq_getattr(mqd_t mqdes, struct mq_attr *attr);  
mqd_t mq_setattr(mqd_t mqdes, struct mq_attr *newattr, struct mq_attr *oldattr);  
                               //成功返回0,失败返回-1  
mq_getattr用于获取当前消息队列的属性,mq_setattr用于设置当前消息队列的属性。其中mq_setattr中的oldattr用于保存修改前的消息队列的属性,可以为空。
mq_setattr可以设置的属性只有mq_flags,用来设置或清除消息队列的非阻塞标志。newattr结构的其他属性被忽略。mq_maxmsg和mq_msgsize属性只能在创建消息队列时通过mq_open来设置。mq_open只会设置该两个属性,忽略另外两个属性。mq_curmsgs属性只能被获取而不能被设置。

下面是测试代码:

#include <iostream>  
#include <cstring>  

#include <errno.h>  
#include <unistd.h>  
#include <fcntl.h>  
#include <mqueue.h>  

using namespace std;  

int main()  
{  
    mqd_t mqID;  
    mqID = mq_open("/anonymQueue", O_RDWR | O_CREAT, 0666, NULL);  

    if (mqID < 0)  
    {  
        cout<<"open message queue error..."<<strerror(errno)<<endl;  
        return -1;  
    }  

    mq_attr mqAttr;  
    if (mq_getattr(mqID, &mqAttr) < 0)  
    {  
        cout<<"get the message queue attribute error"<<endl;  
        return -1;  
    }  

    cout<<"mq_flags:"<<mqAttr.mq_flags<<endl;  
    cout<<"mq_maxmsg:"<<mqAttr.mq_maxmsg<<endl;  
    cout<<"mq_msgsize:"<<mqAttr.mq_msgsize<<endl;  
    cout<<"mq_curmsgs:"<<mqAttr.mq_curmsgs<<endl;  
}  
在Linux 2.6.18中执行结果是:
mq_flags:0  
mq_maxmsg:10  
mq_msgsize:8192  
mq_curmsgs:0  
  • POSIX消息队列的使用

POSIX消息队列可以通过以下两个函数来进行发送和接收消息:

#include <mqueue.h>  
mqd_t mq_send(mqd_t mqdes, const char *msg_ptr,  
                      size_t msg_len, unsigned msg_prio);  
                     //成功返回0,出错返回-1  

mqd_t mq_receive(mqd_t mqdes, char *msg_ptr,  
                      size_t msg_len, unsigned *msg_prio);  
                     //成功返回接收到消息的字节数,出错返回-1  

#ifdef __USE_XOPEN2K  
mqd_t mq_timedsend(mqd_t mqdes, const char *msg_ptr,  
                      size_t msg_len, unsigned msg_prio,  
                      const struct timespec *abs_timeout);  

mqd_t mq_timedreceive(mqd_t mqdes, char *msg_ptr,  
                      size_t msg_len, unsigned *msg_prio,  
                      const struct timespec *abs_timeout);  
#endif  
mq_send向消息队列中写入一条消息,mq_receive从消息队列中读取一条消息。
mqdes:消息队列描述符;
msg_ptr:指向消息体缓冲区的指针;
msg_len:消息体的长度,其中mq_receive的该参数不能小于能写入队列中消息的最大大小,即一定要大于等于该队列的mq_attr结构中mq_msgsize的大小。如果mq_receive中的msg_len小于该值,就会返回EMSGSIZE错误。POXIS消息队列发送的消息长度可以为0。
msg_prio:消息的优先级;它是一个小于MQ_PRIO_MAX的数,数值越大,优先级越高。POSIX消息队列在调用mq_receive时总是返回队列中最高优先级的最早消息。如果消息不需要设定优先级,那么可以在mq_send是置msg_prio为0,mq_receive的msg_prio置为NULL。
还有两个XSI定义的扩展接口限时发送和接收消息的函数:mq_timedsend和mq_timedreceive函数。默认情况下mq_send和mq_receive是阻塞进行调用,可以通过mq_setattr来设置为O_NONBLOCK。

下面是消息队列使用的测试代码:

#include <iostream>  
#include <cstring>  
#include <errno.h>  

#include <unistd.h>  
#include <fcntl.h>  
#include <mqueue.h>  

using namespace std;  

int main()  
{  
    mqd_t mqID;  
    mqID = mq_open("/anonymQueue", O_RDWR | O_CREAT | O_EXCL, 0666, NULL);  

    if (mqID < 0)  
    {  
        if (errno == EEXIST)  
        {  
            mq_unlink("/anonymQueue");  
            mqID = mq_open("/anonymQueue", O_RDWR | O_CREAT, 0666, NULL);  
        }  
        else  
        {  
            cout<<"open message queue error..."<<strerror(errno)<<endl;  
            return -1;  
        }  
    }  

    if (fork() == 0)  
    {  
        mq_attr mqAttr;  
        mq_getattr(mqID, &mqAttr);  

        char *buf = new char[mqAttr.mq_msgsize];  

        for (int i = 1; i <= 5; ++i)  
        {  
            if (mq_receive(mqID, buf, mqAttr.mq_msgsize, NULL) < 0)  
            {  
                cout<<"receive message  failed. ";  
                cout<<"error info:"<<strerror(errno)<<endl;  
                continue;  
            }  

            cout<<"receive message "<<i<<": "<<buf<<endl;     
        }  
        exit(0);  
    }  

    char msg[] = "yuki";  
    for (int i = 1; i <= 5; ++i)  
    {  
        if (mq_send(mqID, msg, sizeof(msg), i) < 0)  
        {  
            cout<<"send message "<<i<<" failed. ";  
            cout<<"error info:"<<strerror(errno)<<endl;  
        }  
        cout<<"send message "<<i<<" success. "<<endl;     

        sleep(1);  
    }  
}                       
在Linux 2.6.18下的执行结构如下:
send message 1 success.   
receive message 1: yuki  
send message 2 success.   
receive message 2: yuki  
send message 3 success.   
receive message 3: yuki  
send message 4 success.   
receive message 4: yuki  
send message 5 success.   
receive message 5: yuki  
  • POSIX消息队列的限制
    POSIX消息队列本身的限制就是mq_attr中的mq_maxmsg和mq_msgsize,分别用于限定消息队列中的最大消息数和每个消息的最大字节数。在前面已经说过了,这两个参数可以在调用mq_open创建一个消息队列的时候设定。当这个设定是受到系统内核限制的。
    下面是在Linux 2.6.18下shell对启动进程的POSIX消息队列大小的限制:
# ulimit -a |grep message  
POSIX message queues     (bytes, -q) 819200 

限制大小为800KB,该大小是整个消息队列的大小,不仅仅是最大消息数*消息的最大大小;还包括消息队列的额外开销。前面我们知道Linux 2.6.18下POSIX消息队列默认的最大消息数和消息的最大大小分别为:

mq_maxmsg = 10  
mq_msgsize = 8192

为了说明上面的限制大小包括消息队列的额外开销,下面是测试代码:

#include <iostream>  
#include <cstring>  
#include <errno.h>  

#include <unistd.h>  
#include <fcntl.h>  
#include <mqueue.h>  

using namespace std;  

int main(int argc, char **argv)  
{  
    mqd_t mqID;  
    mq_attr attr;  
    attr.mq_maxmsg = atoi(argv[1]);  
    attr.mq_msgsize = atoi(argv[2]);  

    mqID = mq_open("/anonymQueue", O_RDWR | O_CREAT | O_EXCL, 0666, &attr);  

    if (mqID < 0)  
    {  
        if (errno == EEXIST)  
        {  
            mq_unlink("/anonymQueue");  
            mqID = mq_open("/anonymQueue", O_RDWR | O_CREAT, 0666, &attr);  

            if(mqID < 0)  
            {  
                cout<<"open message queue error..."<<strerror(errno)<<endl;  
                return -1;  
            }  
        }  
        else  
        {  
            cout<<"open message queue error..."<<strerror(errno)<<endl;  
            return -1;  
        }  
    }  

    mq_attr mqAttr;  
    if (mq_getattr(mqID, &mqAttr) < 0)  
    {  
        cout<<"get the message queue attribute error"<<endl;  
        return -1;  
    }  

    cout<<"mq_flags:"<<mqAttr.mq_flags<<endl;  
    cout<<"mq_maxmsg:"<<mqAttr.mq_maxmsg<<endl;  
    cout<<"mq_msgsize:"<<mqAttr.mq_msgsize<<endl;  
    cout<<"mq_curmsgs:"<<mqAttr.mq_curmsgs<<endl;   
}  

下面进行创建消息队列时设置最大消息数和消息的最大大小进行测试:

[root@idcserver program]# g++ -g test.cpp -lrt  
[root@idcserver program]# ./a.out 10 81920  
open message queue error...Cannot allocate memory  
[root@idcserver program]# ./a.out 10 80000  
open message queue error...Cannot allocate memory  
[root@idcserver program]# ./a.out 10 70000  
open message queue error...Cannot allocate memory  
[root@idcserver program]# ./a.out 10 60000  
mq_flags:0  
mq_maxmsg:10  
mq_msgsize:60000  
mq_curmsgs:0  

从上面可以看出消息队列真正存放消息数据的大小是没有819200B的。可以通过修改该限制参数,来改变消息队列的所能容纳消息的数量。可以通过下面方式来修改限制,但这会在shell启动进程结束后失效,可以将设置写入开机启动的脚本中执行,例如.bashrc,rc.local。

[root@idcserver ~]# ulimit -q 1024000000  
[root@idcserver ~]# ulimit -a |grep message  
POSIX message queues     (bytes, -q) 1024000000  

下面再次测试可以设置的消息队列的属性。

[root@idcserver program]# ./a.out 10 81920  
mq_flags:0  
mq_maxmsg:10  
mq_msgsize:81920  
mq_curmsgs:0  
[root@idcserver program]# ./a.out 10 819200  
mq_flags:0  
mq_maxmsg:10  
mq_msgsize:819200  
mq_curmsgs:0  
[root@idcserver program]# ./a.out 1000 8192    
mq_flags:0  
mq_maxmsg:1000  
mq_msgsize:8192  
mq_curmsgs:0  

POSIX消息队列在实现上还有另外两个限制:
MQ_OPEN_MAX:一个进程能同时打开的消息队列的最大数目,POSIX要求至少为8;
MQ_PRIO_MAX:消息的最大优先级,POSIX要求至少为32;

共享内存

实现原理

管道,FIFO,消息队列,他们的共同特点就是通过内核来进行通信(假设POSIX消息队列也是在内核中实现的,因为POSIX标准并没有限定它的实现方式)。向管道,FIFO,消息队列写入数据需要把数据从进程复制到内核,从这些IPC读取数据的时候又需要把数据从内核复制到进程。所以这种IPC方式往往需要2次在进程和内核之间进行数据的复制,即进程间的通信必须借助内核来传递。如下图所示:
内核IPC通信
共享内存也是一种IPC,它是目前可用IPC中最快的,它是使用方式是将同一个内存区映射到共享它的不同进程的地址空间中,这样这些进程间的通信就不再需要通过内核,只需对该共享的内存区域进程操作就可以了,和其他IPC不同的是,共享内存的使用需要用户自己进行同步操作。下图是共享内存区IPC的通信:
共享内存IPC通信

api和应用

Linux下有三种共享内存的IPC技术:System V共享内存、共享文件映射(mmap)、POSIX共享内存。

system V共享内存

共享文件映射

mmap函数主要的功能就是将文件或设备映射到调用进程的地址空间中,当使用mmap映射文件到进程后,就可以直接操作这段虚拟地址进行文件的读写等操作,不必再调用read,write等系统调用。在很大程度上提高了系统的效率和代码的简洁性。
使用mmap函数的主要目的是:
- 对普通文件提供内存映射I/O,可以提供无亲缘进程间的通信;
- 提供匿名内存映射,以供亲缘进程间进行通信。
- 对shm_open创建的POSIX共享内存区对象进程内存映射,以供无亲缘进程间进行通信。
下面是mmap函数的接口以及说明:

#include <sys/mman.h>  
void *mmap(void *start, size_t len, int prot, int flags, int fd, off_t offset);  
               //成功返回映射到进程地址空间的起始地址,失败返回MAP_FAILED  
start:指定描述符fd应被映射到的进程地址空间内的起始地址,它通常被设置为空指针NULL,这告诉内核自动选择起始地址,该函数的返回值即为fd映射到内存区的起始地址。
len:映射到进程地址空间的字节数,它从被映射文件开头的第offset个字节处开始,offset通常被设置为0。

prot:内存映射区的保护由该参数来设定,通常由以下几个值组合而成:
PROT_READ:数据可读;
 PROT_WRITE:数据可写;
 PROT_EXEC:数据可执行;
 PROT_NONE:数据不可访问;
flags:设置内存映射区的类型标志,POSIX标志定义了以下三个标志:
MAP_SHARED:该标志表示,调用进程对被映射内存区的数据所做的修改对于共享该内存区的所有进程都可见,而且确实改变其底层的支撑对象(一个文件对象或是一个共享内存区对象)。
 MAP_PRIVATE:调用进程对被映射内存区的数据所做的修改只对该进程可见,而不改变其底层支撑对象。
 MAP_FIXED:该标志表示准确的解释start参数,一般不建议使用该标志,对于可移植的代码,应该把start参数置为NULL,且不指定MAP_FIXED标志。
上面三个标志是在POSIX.1-2001标准中定义的,其中MAP_SHARED和MAP_PRIVATE必须选择一个。在Linux中也定义了一些非标准的标志,例如MAP_ANONYMOUS(MAP_ANON),MAP_LOCKED等,具体参考Linux手册。
fd:有效的文件描述符。如果设定了MAP_ANONYMOUS(MAP_ANON)标志,在Linux下面会忽略fd参数,而有的系统实现如BSD需要置fd为-1offset:相对文件的起始偏移。

mmap成功后,可以关闭fd,一般也是这么做的,这对该内存映射没有任何影响。
从进程的地址空间中删除一个映射关系,需要用到下面的函数:

#include <sys/mman.h>  
int munmap(void *start, size_t len);  
                           //成功返回0,出错返回-1  
start:被映射到的进程地址空间的内存区的起始地址,即mmap返回的地址。
len:映射区的大小。

对于一个MAP_SHARED的内存映射区,内核的虚拟内存算法会保持内存映射文件和内存映射区的同步,也就是说,对于内存映射文件所对应内存映射区的修改,内核会在稍后的某个时刻更新该内存映射文件。如果我们希望硬盘上的文件内容和内存映射区中的内容实时一致,那么我们就可以调用msync开执行这种同步:

#include <sys/mman.h>  
int msync(void *start, size_t len, int flags);  //成功返回0,出错返回-1  
start:被映射到的进程地址空间的内存区的起始地址,即mmap返回的地址。
len:映射区的大小。
flags:同步标志,有一下三个标志:
MS_ASYNC:异步写,一旦写操作由内核排入队列,就立刻返回;
MS_SYNC:同步写,要等到写操作完成后才返回。
MS_INVALIDATE:使该文件的其他内存映射的副本全部失效。
  • mmap内存映射区的大小
    Linux下的内存是采用页式管理机制。通过mmap进行内存映射,内核生成的映射区的大小都是以页面大小PAGESIZE为单位,即为PAGESIZE的整数倍。如果mmap映射的长度不是页面大小的整数倍,那么多余空间也会被闲置浪费。
  • mmap实现进程间通信
    mmap本身提供的进程间通信的两种方式,分别用于无亲缘和亲缘进程间的通信。
    (1)通过匿名内存映射提供亲缘进程间的通信
    我们可以通过在父进程fork之前指定MAP_SHARED调用mmap,通过映射一个文件来实现父子进程间的通信,POSIX保证了父进程的内存映射关系保留到子进程中,父子进程对内存映射区的修改双方都可以看到。
    在Linux 2.4以后,mmap提供匿名内存映射机制,即将mmap的flags参数指定为:MAP_SHARED | MAP_ANON。这样就彻底避免了内存映射文件的创建和打开,简化了对文件的操作。匿名内存映射机制的目的就是为了提供一个穿越父子进程间的内存映射区,很方便的提供了亲缘进程间的通信,下面是测试代码:
#include <iostream>  
#include <cstring>  
#include <cerrno>  

#include <unistd.h>  
#include <fcntl.h>  
#include <sys/mman.h>  

using namespace std;  

int main(int argc, char **argv)  
{  
    int *memPtr;  

    memPtr = (int *) mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON, 0, 0);  
    if (memPtr == MAP_FAILED)  
    {  
        cout<<"mmap failed..."<<strerror(errno)<<endl;  
        return -1;  
    }  

    *memPtr = 0;  

    if (fork() == 0)  
    {  
        *memPtr = 1;  
        cout<<"child:set memory "<<*memPtr<<endl;  

        exit(0);  
    }  

    sleep(1);  
    cout<<"parent:memory value "<<*memPtr<<endl;  

    return 0;  
}  
执行结果如下:
child:set memory 1  
parent:memory value 1  

(2)通过内存映射文件提供无亲缘进程间的通信
通过在不同进程间对同一内存映射文件进行映射,来进行无亲缘进程间的通信,如下测试代码:

//process 1  
#include <iostream>  
#include <cstring>  
#include <errno.h>  

#include <unistd.h>  
#include <fcntl.h>  
#include <sys/mman.h>  

using namespace std;  

#define  PATH_NAME "/tmp/memmap"  

int main()  
{  
    int *memPtr;  
    int fd;  

    fd = open(PATH_NAME, O_RDWR | O_CREAT, 0666);  
    if (fd < 0)  
    {  
        cout<<"open file "<<PATH_NAME<<" failed...";  
        cout<<strerror(errno)<<endl;  
        return -1;  
    }  

    ftruncate(fd, sizeof(int));  

    memPtr = (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);  
    close(fd);  

    if (memPtr == MAP_FAILED)  
    {  
        cout<<"mmap failed..."<<strerror(errno)<<endl;  
        return -1;  
    }  

    *memPtr = 111;  
    cout<<"process:"<<getpid()<<" send:"<<*memPtr<<endl;  

    return 0;  
}  

//process 2  
#include <iostream>  
#include <cstring>  
#include <errno.h>  

#include <unistd.h>  
#include <fcntl.h>  
#include <sys/mman.h>  

using namespace std;  

#define  PATH_NAME "/tmp/memmap"  

int main()  
{  
    int *memPtr;  
    int fd;  

    fd = open(PATH_NAME, O_RDWR | O_CREAT, 0666);  
    if (fd < 0)  
    {  
        cout<<"open file "<<PATH_NAME<<" failed...";  
        cout<<strerror(errno)<<endl;  
        return -1;  
    }  

    memPtr = (int *)mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);  
    close(fd);  

    if (memPtr == MAP_FAILED)  
    {  
        cout<<"mmap failed..."<<strerror(errno)<<endl;  
        return -1;  
    }  

    cout<<"process:"<<getpid()<<" receive:"<<*memPtr<<endl;  

    return 0;  
}  
执行结果如下:
# ./send   
process:12711 send:111  
# ./recv   
process:12712 receive:111  

上面的代码都没进行同步操作,在实际的使用过程要考虑到进程间的同步,通常会用信号量来进行共享内存的同步。

posix共享内存

上面介绍了通过内存映射文件进行进程间的通信的方式,现在要介绍的是通过POSIX共享内存区对象进行进程间的通信。POSIX共享内存使用方法有以下两个步骤:
1)通过shm_open创建或打开一个POSIX共享内存对象;
2)然后调用mmap将它映射到当前进程的地址空间;
和通过内存映射文件进行通信的使用上差别在于mmap描述符参数获取方式不一样,前者通过open而后者通过shm_open。
POSIX共享内存区对象的特殊操作函数就只有创建(打开)和删除两个函数,其他对共享内存区对象的操作都是通过已有的函数进行的。

#include <sys/mman.h>  
int shm_open(const char *name, int oflag, mode_t mode);  
                              //成功返回非负的描述符,失败返回-1  
int shm_unlink(const char *name);  
                              //成功返回0,失败返回-1  
shm_open用于创建一个新的共享内存区对象或打开一个已经存在的共享内存区对象。
name:POSIX IPC的名字,前面关于POSIX进程间通信都已讲过关于POSIX IPC的规则,这里不再赘述。
oflag:操作标志,包含:O_RDONLY,O_RDWR,O_CREAT,O_EXCL,O_TRUNC。其中O_RDONLY和O_RDWR标志必须且仅能存在一项。
mode:用于设置创建的共享内存区对象的权限属性。和open以及其他POSIX IPC的xxx_open函数不同的是,该参数必须一直存在,如果oflag参数中没有O_CREAT标志,该位可以置0;
shm_unlink用于删除一个共享内存区对象,跟其他文件的unlink以及其他POSIX IPC的删除操作一样,对象的析构会到对该对象的所有引用全部关闭才会发生。

POSIX共享内存和POSIX消息队列,有名信号量一样都是具有随内核持续性[^1]的特点。
下面是通过POSIX共享内存进行通信的测试代码,代码中通过POSIX信号量来进行进程间的同步操作。

//process 1  
#include <iostream>  
#include <cstring>  
#include <errno.h>  

#include <unistd.h>  
#include <fcntl.h>  
#include <semaphore.h>  
#include <sys/mman.h>  

using namespace std;  

#define SHM_NAME "/memmap"  
#define SHM_NAME_SEM "/memmap_sem"   

char sharedMem[10];  

int main()  
{  
    int fd;  
    sem_t *sem;  

    fd = shm_open(SHM_NAME, O_RDWR | O_CREAT, 0666);  
    sem = sem_open(SHM_NAME_SEM, O_CREAT, 0666, 0);  

    if (fd < 0 || sem == SEM_FAILED)  
    {  
        cout<<"shm_open or sem_open failed...";  
        cout<<strerror(errno)<<endl;  
        return -1;  
    }  

    ftruncate(fd, sizeof(sharedMem));  

    char *memPtr;  
    memPtr = (char *)mmap(NULL, sizeof(sharedMem), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);  
    close(fd);  

    char msg[] = "yuki...";  

    memmove(memPtr, msg, sizeof(msg));  
    cout<<"process:"<<getpid()<<" send:"<<memPtr<<endl;  

    sem_post(sem);  
    sem_close(sem);  

    return 0;  
}  

//process 2  
#include <iostream>  
#include <cstring>  
#include <errno.h>  

#include <unistd.h>  
#include <fcntl.h>  
#include <semaphore.h>  
#include <sys/mman.h>  

using namespace std;  

#define SHM_NAME "/memmap"  
#define SHM_NAME_SEM "/memmap_sem"   

int main()  
{  
    int fd;  
    sem_t *sem;  

    fd = shm_open(SHM_NAME, O_RDWR, 0);  
    sem = sem_open(SHM_NAME_SEM, 0);  

    if (fd < 0 || sem == SEM_FAILED)  
    {  
        cout<<"shm_open or sem_open failed...";  
        cout<<strerror(errno)<<endl;  
        return -1;  
    }  

    struct stat fileStat;  
    fstat(fd, &fileStat);  

    char *memPtr;  
    memPtr = (char *)mmap(NULL, fileStat.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);  
    close(fd);  

    sem_wait(sem);  

    cout<<"process:"<<getpid()<<" recv:"<<memPtr<<endl;  

    sem_close(sem);  

    return 0;  
}  
程序的执行结果如下:
# ./send   
process:13719 send:yuki...  
# ./recv   
process:13720 recv:yuki...  

在Linux 2.6.18中,对于POSIX信号量和共享内存的名字会在/dev/shm下建立对应的路径名,例如上面的测试代码,会生成如下的路径名:

# ll /dev/shm/  
total 8  
-rw-r--r-- 1 root root 10 Aug 13 00:28 memmap  
-rw-r--r-- 1 root root 32 Aug 13 00:28 sem.memmap_sem  

信号量

原始套接字

套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。
这里写图片描述
socket是应用层和传输层之间的桥梁

套接字特性

套接字的特性由3个属性确定,它们分别是:域、端口号、协议类型。
(1)套接字的域
它指定套接字通信中使用的网络介质,最常见的套接字域有两种:
一是AF_INET,它指的是Internet网络。当客户使用套接字进行跨网络的连接时,它就需要用到服务器计算机的IP地址和端口来指定一台联网机器上的某个特定服务,所以在使用socket作为通信的终点,服务器应用程序必须在开始通信之前绑定一个端口,服务器在指定的端口等待客户的连接。
另一个域AF_UNIX(AF_LOCAL),表示UNIX文件系统,它就是文件输入/输出,而它的地址就是文件名。
(2)套接字的端口号
每一个基于TCP/IP网络通讯的程序(进程)都被赋予了唯一的端口和端口号,端口是一个信息缓冲区,用于保留Socket中的输入/输出信息,端口号是一个16位无符号整数,范围是0-65535,以区别主机上的每一个程序(端口号就像房屋中的房间号),低于256的端口号保留给标准应用程序,比如pop3的端口号就是110,每一个套接字都组合进了IP地址、端口,这样形成的整体就可以区别每一个套接字。
(3)套接字协议类型
因特网提供三种通信机制,

  1. 流套接字(SOCK_STREAM)
    流套接字在域中通过TCP/IP连接实现,同时也是AF_UNIX中常用的套接字类型。流套接字提供的是一个有序、可靠、双向字节流的连接,因此发送的数据可以确保不会丢失、重复或乱序到达,而且它还有一定的出错后重新发送的机制。
  2. 数据报套接字(SOCK_DGRAM)
    它不需要建立连接和维持一个连接,它们在域中通常是通过UDP/IP协议实现的。它对可以发送的数据的长度有限制,数据报作为一个单独的网络消息被传输,它可能会丢失、复制或错乱到达,UDP不是一个可靠的协议,但是它的速度比较高,因为它并一需要总是要建立和维持一个连接。
  3. 原始套接字(SOCK_RAW)
    原始套接字允许对较低层次的协议直接访问,比如IP、 ICMP协议,它常用于检验新的协议实现,或者访问现有服务中配置的新设备,因为RAW SOCKET可以自如地控制Windows下的多种协议,能够对网络底层的传输机制进行控制,所以可以应用原始套接字来操纵网络层和传输层应用。比如,我们可以通过RAW SOCKET来接收发向本机的ICMP、IGMP协议包,或者接收TCP/IP栈不能够处理的IP包,也可以用来发送一些自定包头或自定协议的IP包。网络监听技术很大程度上依赖于SOCKET_RAW。
    原始套接字与标准套接字的区别在于:
    原始套接字可以读写内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送数据必须使用原始套接字。

api实例

服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define UNIX_DOMAIN "/tmp/UNIX.domain"

int main(void)
{
  int lsn_fd, apt_fd;
  struct sockaddr_un srv_addr;
  struct sockaddr_un clt_addr;
  socklen_t clt_len;
  int ret;
  int i;
  char recv_buf[1024];
  char send_buf[1024];

  //create socket to bind local IP and PORT
  lsn_fd = socket(PF_UNIX, SOCK_STREAM, 0);
  if(lsn_fd < 0)
  {
    perror("can't create communication socket!");
    return 1;
  }

  //create local IP and PORT
  srv_addr.sun_family = AF_UNIX;
  strncpy(srv_addr.sun_path, UNIX_DOMAIN, sizeof(srv_addr.sun_path) - 1);
  unlink(UNIX_DOMAIN);

  //bind sockfd and sockaddr
  ret = bind(lsn_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
  if(ret == -1)
  {
    perror("can't bind local sockaddr!");
    close(lsn_fd);
    unlink(UNIX_DOMAIN);
    return 1;
  }

  //listen lsn_fd, try listen 1
  ret = listen(lsn_fd, 1);
  if(ret == -1)
  {
    perror("can't listen client connect request");
    close(lsn_fd);
    unlink(UNIX_DOMAIN);
    return 1;
  }

  clt_len = sizeof(clt_addr);
  while(1)
  {
    apt_fd = accept(lsn_fd, (struct sockaddr*)&clt_addr, &clt_len);
    if(apt_fd < 0)
    {
      perror("can't listen client connect request");
      close(lsn_fd);
      unlink(UNIX_DOMAIN);
      return 1;
    }

    printf("received a connection\n");
    printf("send message to client\n");
    memset(send_buf, 0, 1024);
    strcpy(send_buf, "Hello, you have connected to server succeed");

    int snd_num = write(apt_fd, send_buf, 1024);
    if(snd_num != 1024)
    {
      perror("send messge to client failed\n");
      close(apt_fd);
      close(lsn_fd);
      unlink(UNIX_DOMAIN);
      return 1;
    }
    //read and printf client info
    printf("============info=================\n");
    for(i = 0; i < 4; i++)
    {
      memset(recv_buf, 0, 1024);
      int rcv_num = read(apt_fd, recv_buf, sizeof(recv_buf));
      printf("Message from client (%d) :%s\n", rcv_num, recv_buf);
    }
  }
  close(apt_fd);
  close(lsn_fd);
  unlink(UNIX_DOMAIN);
  return 0;
}

客户端

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#define UNIX_DOMAIN "/tmp/UNIX.domain"

int main(void)
{
  int connect_fd;
  struct sockaddr_un srv_addr;
  char snd_buf[1024];
  char rcv_buf[1024];
  int ret;
  int i;

  //create client socket
  connect_fd = socket(PF_UNIX, SOCK_STREAM, 0);
  if(connect_fd < 0)
  {
    perror("client create socket failed");
    return 1;
  }

  //set server sockaddr_un
  srv_addr.sun_family = AF_UNIX;
  strcpy(srv_addr.sun_path, UNIX_DOMAIN);

  //connect to server
  ret = connect(connect_fd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
  if(ret == -1)
  {
    perror("connect to server failed!");
    close(connect_fd);
    unlink(UNIX_DOMAIN);
    return 1;
  }

  //receive message from server
  memset(rcv_buf, 0, 1024);
  int rcv_num = read(connect_fd, rcv_buf, sizeof(rcv_buf));
  printf("receive message from server (%d) :%s\n", rcv_num, rcv_buf);
  //printf("\n");

  //send message to server
  memset(snd_buf, 0, 1024);
  strcpy(snd_buf, "message from client");
  printf("sizeof(snd_buf): %d\n", sizeof(snd_buf));
  sleep(2000);

  //send message to server
  for(i = 0; i < 4; i++)
  {
    write(connect_fd, snd_buf, sizeof(snd_buf));
  }
  close(connect_fd);
  return 0;
}
进程通信方式原理特点
管道管道及有名管道及有名管道则是典型的随进程持续IPC[^1],并且只能传送无格式的字节流无疑会给应用程序开发带来不便,另外,它的缓冲区大小也受到限制。不需要额外同步机制
信号待补充
消息队列消息队列就是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向中按照一定的规则添加新消息;对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列是随内核持续的不需要额外同步机制,消息队列与管道以及有名管道相比,具有更大的灵活性,首先,它提供有格式字节流,有利于减少开发人员的工作量;其次,消息具有类型,在实际应用中,可作为优先级使用。这两点是管道以及有名管道所不能比的。同样,消息队列可以在几个进程间复用,而不管这几个进程是否具有亲缘关系,这一点与有名管道很相似;但消息队列是随内核持续的,与有名管道(随进程持续)相比,生命力更强,应用空间更大
共享内存待补充需要额外同步机制
信号量待补充
原始套接字待补充

[^1]
随进程持续:IPC一直存在到打开IPC对象的最后一个进程关闭该对象为止。如管道和有名管道;
随内核持续:IPC一直持续到内核重新自举或者显示删除该对象为止。如消息队列、信号灯以及共享内存等;
随文件系统持续:IPC一直持续到显示删除该对象为止。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux进程间通信方式 的相关文章

  • Linux 中什么处理 ping?

    我想覆盖 更改 linux 处理 ping icmp echo 请求数据包的方式 这意味着我想运行自己的服务器来回复传入的 icmp 回显请求或其他 数据包 但为了使其正常工作 我想我需要禁用 Linux 的默认 ping icmp 数据包
  • SSE:跨页边界的未对齐加载和存储

    我在页面边界旁边执行未对齐加载或存储之前读过某处 例如使用 mm loadu si128 mm storeu si128内在函数 代码应首先检查整个向量 在本例中为 16 个字节 是否属于同一页 如果不属于同一页 则切换到非向量指令 我知道
  • docker容器大小远大于实际大小

    我正在尝试从中构建图像debian latest 构建后 报告的图像虚拟大小来自docker images命令为 1 917 GB 我登录查看尺寸 du sh 大小为 573 MB 我很确定这么大的尺寸通常是不可能的 这里发生了什么 如何获
  • 查找哪些页面不再与写入时复制共享

    假设我在 Linux 中有一个进程 我从中fork 另一个相同的过程 后forking 因为原始进程将开始写入内存 Linux写时复制机制将为进程提供与分叉进程使用的不同的唯一物理内存页 在执行的某个时刻 我如何知道原始进程的哪些页面已被写
  • ftrace:仅打印trace_printk()的输出

    是否可以只转储trace printk 输出于trace文件 我的意思是过滤掉函数跟踪器 或任何其他跟踪器 中的所有函数 一般来说 您可以在选项目录中关闭选项 sys kernel debug tracing options Use ls显
  • 如何通过ssh检查ubuntu服务器上是否存在php和apache

    如何通过ssh检查Ubuntu服务器上apache是 否安装了php和mysql 另外如果安装的话在哪个目录 如果安装了其他软件包 例如 lighttpd 那么它在哪里 确定程序是否已安装的另一种方法是使用which命令 它将显示您正在搜索
  • 如何确保应用程序在 Linux 上持续运行

    我试图确保脚本在开发服务器上保持运行 它会整理统计数据并提供网络服务 因此它应该会持续存在 但一天中有几次 它会因未知原因而消失 当我们注意到时 我们只需再次启动它 但这很麻烦 并且某些用户没有权限 或专有技术 来启动它 作为一名程序员 我
  • Linux 上有关 getBounds() 和 setBounds() 的 bug_id=4806603 的解决方法?

    在 Linux 平台上 Frame getBounds 和 Frame setBounds 的工作方式不一致 这在 2003 年就已经有报道了 请参见此处 http bugs java com bugdatabase view bug do
  • 如何更改 Apache 服务器的根目录? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 如何更改 Apache 服务器的文档根目录 我基本上想要localhost从 来 users spencer projects目录而不是
  • CoAP数据包的大小是多少?

    我是这项技术的新手 有人可以帮助我了解一些疑问吗 Q 1 CoAP数据包的大小是多少 我知道有 4 字节固定标头 但是包括标头 选项和负载在内的最大大小限制是多少 Q 2 有像MQTT那样的Keep Alive的概念吗 它在UDP上工作 它
  • 我不明白 execlp() 在 Linux 中如何工作

    过去两天我一直在试图理解execlp 系统调用 但我还在这里 让我直奔主题 The man pageexeclp 将系统调用声明为int execlp const char file const char arg 与描述 execl exe
  • Linux - 从第二个选项卡获取文本

    假设我们有这样的文件 一些文本11 一些文本12 一些文本13 一些文本21 一些文本22 一些文本23 文本由制表符分隔 我们知道第 1 列中的一些文本 但希望从第 2 列中获取文本 我知道我可以通过以下方式获取线路 grep somet
  • 我如何知道 C 程序的可执行文件是在前台还是后台运行?

    在我的 C 程序中 我想知道我的可执行文件是否像这样在前台运行 a out 或者像这样 a out 如果你是前台工作 getpgrp tcgetpgrp STDOUT FILENO or STDIN FILENO or STDERR FIL
  • 如果在等待“read -s”时中断,在子进程中运行 bash 会破坏 tty 的标准输出吗?

    正如 Bakuriu 在评论中指出的那样 这基本上与BASH 输入期间按 Ctrl C 会中断当前终端 https stackoverflow com questions 31808863 bash ctrlc during input b
  • C++ Boost ASIO 简单的周期性定时器?

    我想要一个非常简单的周期性计时器每 50 毫秒调用我的代码 我可以创建一个始终休眠 50 毫秒的线程 但这很痛苦 我可以开始研究用于制作计时器的 Linux API 但它不可移植 I d like使用升压 我只是不确定这是否可能 boost
  • 高效的内存屏障

    我有一个多线程应用程序 其中每个线程都有一个整数类型的变量 这些变量在程序执行期间递增 在代码中的某些点 线程将其计数变量与其他线程的计数变量进行比较 现在 我们知道在多核上运行的线程可能会无序执行 一个线程可能无法读取其他线程的预期计数器
  • git 错误:无法处理 https

    当我尝试使用 git clone 时https xxx https xxx我收到以下错误我不处理协议 https 有人可以帮我吗 完整消息 dementrock dementrock A8Se git 克隆https git innosta
  • 警告:请求的映像平台 (linux/amd64) 与检测到的主机平台 (linux/arm64/v8) 不匹配

    警告 请求的映像平台 linux amd64 与检测到的主机平台 linux arm64 v8 不匹配 并且未请求特定平台 docker 来自守护程序的错误响应 无法选择具有功能的设备驱动程序 gpu 我在 mac 上尝试运行此命令时遇到此
  • 如何在 GNU/Linux 上设置 Subversion (SVN) 服务器 - Ubuntu [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我有一台运行 Ubuntu 的笔记本电脑 我想将其用作 Subversion 服务器 既让我自己在本地承诺 也让其他人远程承诺 要使其
  • vagrant ssh -c 并在连接关闭后保持后台进程运行

    我正在编写一个脚本来启动和后台流浪机器内的进程 似乎每次脚本结束和 ssh 会话结束时 后台进程也会结束 这是我正在运行的命令 vagrant ssh c cd vagrant src nohup python hello py gt he

随机推荐

  • 基址寻址和变址寻址区别(白话版)

    在寻址方式里面 xff0c 基址寻址和变址寻址是比较常用的两种寻址方式 但因为两种太像了 xff0c 总是搞不清楚 上网查到的描述太过专业看起来特别吃力 写这篇 xff0c 希望能用一种通俗易懂的方式对二者做个区分 为什么总容易搞混呢 xf
  • ROS修改pkg名和node名教程

    修改pkg名 有的时候最开始起了一个功能包package名 xff0c 但后来要进行修改 修改package名 xff0c 需要改两步然后重新catkin make即可 操作如下 xff1a 再回到工作空间执行catkin make即可 参
  • ROS-TCP-Connector and ROS-TCP-Endpoint

    Unity官方提供了和ROS交互的接口 xff1a ROS TCP Connector and ROS TCP Endpoint 有了这两个Unity就能够更好的和真实机器人做交互 两个接口的实现基于ROS ros bridge xff0c
  • Python 使用can模块(记录稿)

    直接安装 xff1a pip install python can 如果报这个错 更新一下pip pip3 install upgrade pip 或者是 pip install upgrade pip 再安装wrapt pip insta
  • Android Studio报错:W/System.err: java.net.SocketException: socket failed: EPERM (Operation not permitt

    解决方案 xff1a 在AndroidManifest xml中增加 xff1a span class token operator lt span uses span class token operator span permissio
  • C++简单实现http服务端客户端传输实例

    使用本代码有两个注意事项 xff1a 代码使用到了httplib库 xff0c 需要下载然后把 h文件放到自己的目录下 httplib下载地址 xff1a https gitcode net mirrors yhirose cpp http
  • VSCODE连接vmware虚拟机

    有时候想用VSOCDE中的ssh连接虚拟机 桥接模式 的终端进行操作 xff0c 但是执行ssh命令后总是报错 xff1a Could not connect to span class token number 192 168 span
  • 字符串加密工具

    可用于密码加密 xff0c 代码上阵 xff1a package com util import java security MessageDigest import java security NoSuchAlgorithmExcepti
  • 【RPLIDAR】ubuntu18.04安装cartographer源码并使用RPLIDAR A2M8 - R4建图

    1 创建工作空间 mkdir cartographer ws cd cartographer ws wstool init src 2 下载cartographer源码包 wstool merge t src https raw githu
  • 《C++ Primer Plus》学习笔记——第一章 介绍C++

    C 43 43 在C语言的基础上添加了面向对象编程和泛型编程 C 43 43 继承了C语言高效 简洁 快速和可移植性的传统 C 43 43 比C多了两样编程方法 xff0c 这使得它功能强大 xff0c 同样也意味着使用者需要学习更多的内容
  • 【转载】理解SIP的认证

    理解SIP的认证 From http blog sina com cn s blog 4b839a1b01000bqq html 1 认证和加密 认证 xff08 Authorization xff09 的作用在于表明自己是谁 xff0c
  • 尝试使用绕线法制作数字电路

    最近在学习电路制作的过程中 xff0c 发现使用洞洞板 xff0c 很难处理好具有很多复杂引脚的集成电路 用 锡接走线法 的话 xff0c 集成电路不能太多太复杂 xff0c 否则板子上很难排开 也可以用比较细的导线 xff0c 飞线焊接
  • 用C语言实现一个简单的HTTP客户端(HTTP Client)

    用C语言实现一个简单的HTTP Client xff08 HTTP客户端 xff09 作者 xff1a gobitan xff08 雨水 xff09 日期 xff1a 2007 04 03 转载请注明出处 http blog csdn ne
  • C语言常见的自定义数据类型(1)—— 结构体

    目录 1 结构体 1 1 结构体的定义 1 2 结构体的自引用 1 3 结构体类型的重命名 1 4 结构体的嵌套 2 结构体大小的计算 2 1 结构体内存对齐 2 2 嵌套结构体大小的计算 2 3 offsetof函数 2 4 修改默认对齐
  • 一篇解决!小白迷惑:Go mod本地包导入

    最近在学习go xff0c 在导入本都包遇到一个问题 xff0c 根据网上许多教程来都走不通 xff0c 最后在官网得到了最正确的答案 官网教程 xff1a Call your code from another module The Go
  • Linux nf_conntrack连接跟踪的实现

    连接跟踪 xff0c 顾名思义 xff0c 就是识别一个连接上双方向的数据包 xff0c 同时记录状态 下面看一下它的数据结构 xff1a struct nf conn Usage count in here is 1 for hash t
  • 组播MAC地址和各类IP地址

    MAC地址是以太网二层使用的一个48bit xff08 6字节十六进制数 xff09 的地址 xff0c 用来标识设备位置 MAC地址分成两部分 xff0c 前24位是组织唯一标识符 xff08 OUI Organizationally u
  • Linux内核中的软中断、tasklet和工作队列详解

    TOC 本文基于Linux2 6 32内核版本 引言 软中断 tasklet和工作队列并不是Linux内核中一直存在的机制 xff0c 而是由更早版本的内核中的 下半部 xff08 bottom half xff09 演变而来 下半部的机制
  • Linux内核中的各种锁

    Linux内核中的各种锁 在现代操作系统里 xff0c 同一时间可能有多个内核执行流在执行 xff0c 因此内核其实象多进程多线程编程一样也需要一些同步机制来同步各执行单元对共享数据的访问 尤其是在多处理器系统上 xff0c 更需要一些同步
  • Linux进程间通信方式

    进程与进程通信的概念进程通信的应用场景进程通信的几种方式 管道 管道简介管道原理 管道如何通信管道如何创建管道读写实现 管道api与用法 普通管道流管道命名管道 实现原理api与应用 匿名管道和有名管道总结 信号 信号来源信号生命周期和处理