从上文我们了解,进程的本质是在内存中的一段独立的地址空间,所以一个进程是不能直接访问另一个进程的,但现实中进程是必须协作的,**进程通信IPC(Inter-Process Communication)**就出现了。

    进程通信总体分为6大类:匿名管道,有名管道,信号量,共享内存,socket通信,消息队列,我们将以此进行介绍。

1 管道通信

1.1 匿名管道(pipe)

匿名管道pipe:匿名管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用,进程的亲缘关系通常是指父子进程关系/兄弟进程。

什么是管道?

如图所示,管道的实质就是**内核缓冲区,**管道通信就是我们借用内核缓冲区的空间进行通信;

创建管道pipe;

**int pipe(fd[2]):**pipe用于创建两个文件描述符,fd[0] 和 fd[1] 分别构成管道的两端,往 fd[1] 写入的数据可以从 fd[0] 读出。并且 fd[1] 一端只能进行写操作,fd[0] 一端只能进行读操作,不能反过来使用,所以管道只能单向操作。

返回值:成功返回0,失败返回-1;

    由于我们需要通过创建的文件描述符fd\[0\],fd\[1\]来进行读写数据,所以只有父子进程间才能通过管道进行通信,因为子进程继承父进程的文件描述符表,可以找到这两个文件描述符,其他进程找不到。

1.2 有名管道(fifo)

匿名管道与有名管道的本质完全一样,都是利用内核缓冲区来传递数据;

区别:由上述介绍也可以知道,匿名管道是没有"文件名"的,只有两个创造出来的文件描述符,而有名管道是有路径名的,是一个在系统中可见的特殊文件,所以没有亲缘关系的两个进程也可以进行通信。

关键函数 mkfifo;

int mkfifo(const char *filename, mode_t mode):创建有名管道;

参数:

const char* filename:创建的特殊文件的路径;

mode_t mode:文件的权限;

返回值:成功返回0,失败返回-1;

使用完最好释放管道:unlink(pipe_path);

管道通信的劣势:管道通信是在内存的内核空间缓冲区上开辟一段空间,占用内核空间总归是不好的。

2 共享内存

  • 共享内存:多个进程访问同一内存区域,直接读写,无需内核中转

  • 管道:数据需要从用户空间复制到内核,再从内核复制到目标进程

正是因为跳过了内核中转和多次数据复制,共享内存成为最快的IPC方式之一。

代码层面的区别:管道我们在代码中是通过操作文件描述符fd来进行数据的跨进程传输,而共享内存我们则是直接操作地址(指针)来进行数据传输。

使用共享内存进行进程通信的步骤:

1.创建共享内存对象并打开;

2.设置共享内存的大小;

3.内存映射;

这三步操作之后,我们得到地址share,后续我们直接操作这个share即可;

例:

总结:由于不用涉及内核空间,所以通信的速度更快,更大。

3 消息队列

消息队列最大的特点就是通过这种方式发送与接收数据的方式是一条一条的,且发送者与接收者在时间上是异步的,即发送者不需要等待接收者接收,它可以一直发,接收者则会按顺序读取接收。

建立消息队列:

char* mq_name = “/mymq”:消息队列命名,消息队列的名称开头必须是/,且不能出现其他/;

mqd_t mq_open(const char *name, int oflag, mode_t mode, struct mq_attr *attr ):打开/创建一个消息队列;

参数:

const char *name:上述设置的消息队列名称;

int oflag:表示打开的方式,和open函数的类似;

mode_t mode:是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时,才需要提供该参数。表示默认访问权限;

struct mq_attr *attr:也是一个可选参数,在oflag中含有O_CREAT标志且消息队列不存在时才需要。该参数用于给新队列设定某些属性,如果是空指针,那么就采用默认属性。

attr的属性:

mq_flags:消息队列的标志:0或O_NONBLOCK,用来表示是否阻塞 ;
mq_maxmsg :消息队列的最大消息数;
mq_msgsize :消息队列中每个消息的最大字节数;
mq_curmsgs :消息队列中当前的消息数目,第一次建立设置0即可;

消息队列建立的文件在 /dev/mqueue/ 中;

例:父进程发送数据子进程接收并打印到标准输入

int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio, const struct timespec *abs_timeout):将消息发送到消息队列

参数:

**mqd_t mqdes:**上述建立并打开的消息队列描述符;

**const char *msg_ptr:**要发送的内容;

**size_t msg_len:**发送内容的长度;

**unsigned msg_prio:**消息优先级,没要求设0即可;

**const struct timespec *abs_timeout:**绝对超时时间,即经过多久如果消息还没发出就报错,必须是绝对时间;

获取绝对时间:

定义参数:struct timespec timeout;

获取当前时间:clock_gettime(0,&timeout);

+5s:timeout.yv_sec +=5;

子进程接收数据,与上述发送过程类似:

最后,socket进程间通信是一种相对特殊的进程间通信方式,我们下一篇单独讲解。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐