嵌入式 Linux进程之间的通信

简介: 嵌入式 Linux进程之间的通信

1、Linux进程间的通信继承

 Linux 下的进程通信手段基本上是从 UNIX 平台上的进程通信手段继承而来的。 而对 UNIX 发展做出重大贡献的两大主力 AT&T 贝尔实验室及 BSD(加州大学伯克利分校的伯 克利软件发布中心)在进程间的通信方面的侧重点有所不同。 前者是对 UNIX 早期的进程间通信手段进行了系统的改进和扩充,形成了“system V IPC”, 其通信进程主要局限在单个计算机内。 后者则跳过了该限制,形成了基于套接口(socket)的进程间通信机制。

而 Linux 则把两者的优势都继承了下来,如下图所示,

2、Linux进程之间的通信种类

linux 进程之间的通信主要有下面几种:

通信方式 描述
管道 pipe, 命名管道 named pipe 管道允许亲缘关系进程间的通信。 命名管道还允许无亲缘关系进程间通信。
信号 signal 在软件层模拟中断机制,通知进程某事发生。 它是比较复杂的通信方式,用于通知进程有某事件发生,一个进程 收到一个信号与处理器收到一个中断请求效果上可以说是一样的。
消息队列 message queue 是消息的链接表,包括 posix 消息队列和 SystemV 消息队列,它克服了前两种通信方式中信息量有限的缺点。 具有写权限的进程可以按照一定的规则向消息队列中添加新消息。 对消息队列有读权限的进程则可以从消息队列中读取消息。
共享内存 Shared memory 可以说是最有用的进程间通信方式,是最快的可用 ipc 形式。 是针对其他通信机制运行效率较低而设计。 它使得多个进程可以访问同一块内存空间,不同进程可以及时看到 对方进程中对共享内存中数据的更新。 这种通信方式需要依靠某种同步机制,如互斥锁和信号量等。
信号量 Semaphore 进程间同步。 主要作为进程之间以及同一进程的不同线程之间的同步和互斥手 段。
套接字 socket 用于网络中不同机器之间的进程通信

显示详细信息

3、管道

3.1 管道概述

管道好比一条水管,有两个端口,一端进水,另一端出水。 管道是 Linux 进程间通信的一种方式,如命令 ps -ef | grep ntp。

3.2 管道文件

我们软件的管道文件也有两个端口,分别是读端和写端。进水可看成数据从写端被写入,出水 可看数据从读端被读出。 在 linux 下文件类型为 p 的文件就是管道文件。

3.3 管道特点

(1)管道通信是单向的,有固定的读端和写端;

(2)数据被进程在管道读出后,管道中的数据就不存在了;

(3)当进程去读取空管道的时候,进程会阻塞;

(4)当进程往满管道写入数据时,进程会阻塞;

(5)管道容量为 64KB (#define PIPE_BUFFERS 16 include/linux/pipe);

3.4 通信框架

3.5 对管道文件进行操作

我们分别用 read、 write 函数来对管道的读端和写端进行读写,所以必须要知道读写两端 分别对应的文件描述符。

这两个文件描述符我们通常保存在一个有两个整型元素的数组中,如 int fds[2]; 然后调用函数 pipe(fds),这个函数会创建一个管道,并且数组 fds 中的两个元素会成为管 道读端和写端对应的两个文件描述符。

即 fds[0]和读端相对应, fds[1]和写端相对应。 fds[0]有可读属性, fds[1]有可写的属性。

4、标准流管道

像文件操作有标准 io 流一样,管道也支持文件流模式。 用来创建连接到另一进程的管道 popen 函数和关闭管道函数 pclose。

如果 open_mode 是“r”,被调用程序的输出就可以被调用程序使用,调用程序利用 popen 函数返回的 FILE*文件流指针,就可以通过常用的 stdio 库函数(如 fread)来读取被调用程 序的输出。 如果 open_mode 是“w”,调用程序就可以用 fwrite 向被调用程序发送数据,而被调用程 序可以在自己的标准输入上读取这些数据。

这两个函数应用于 Linux 执行 shell 命令的场景。

(1) popen(comm, type)函数会创建一个管道,再 fork 一个子进程,在子进程中执行 execX 函数来执行 comm 命令(因为 execX 执行新程序后新程序的进程空间会覆盖原进程的进程空间,所 以开一个子进程来执行 execX 家族函数),然后想要返回 stdout 或者 stdin 的文件指针(取决于 type); (2) 因为 comm 命令是通过子进程的执行的,那么 stdin 或者 stdout 文件指针也是子进程的 进程片空间的,要将其返回给父进程,这就需要管道了;

(3) stdin 是供程序写数据的,stdout 是供程序读数据的。这里设计的巧妙之处在于,管道 的读端跟 stdout 绑定,管道的写端跟 stdin 绑定;

(4) 读写管道操作的无非就是管道(文件)的 fd(文件描述符),这里将 fd 封装到文件流指针 fp 中; (5) popen 返回的是 stdout,那么 type 为”r”,表示创建一个管道且该管道文件的读端赋 给 fpr; (6) popen 返回的是 stdin,那么 type 为”w”,表示创建一个管道且该管道文件的写端赋 给 fpw; (7) 这样子,读 fpr(管道的读端)等于读子进程的 stdout,写 fpw(管道的写端)等于写子进 程的 stdin。

实例:从标准管道流中读取打印//etc/profile的内容;

#include <stdio.h>
int main()
{
char buf[512] = {0};
FILE* fp = popen("cat /etc/profile", "r"); //执行完这行代码,标准输出就装
满,这里这个标准输出标记为 out1,管道指向 out1,fp 指向管道的读端
//while ((ret = fread(buf, 1, sizeof(buf), fp)) > 0)
//从 out1 中读取 512 个字节数据,存放在 buf
while(fgets(buf, sizeof(buf), fp)){
puts(buf); //输出到终端
}
pclose(fp);
return 0;
}

5、无名管道 PIPE

5.1 无名管道特点

1)只能在亲缘关系进程间通信(父子或兄弟)。

2)半双工(固定的读端和固定的写端)。

3)它是特殊的文件,可以用 read、write 等函数操作,这种文件只能在内存中。

5.2 创建管道函数

管道函数原型:

#include <unistd.h>
int pipe(int fds[2]);

函数 pipe 用于创建一个无名管道,如果成功, fds[0]存放可读的文件描述符,fds[1]存 放可写文件描述符,并且成功返回 0,否则返回-1。 通过调用 pipe 函数获取这对打开的文件描述符后,一个进程就可以从 fds[0]中读数据,而 另一个进程就可以往 fds[1]中写数据。 当然两进程间必须有继承关系,才能继承这对打开的文件描述符。

管道不象真正的物理文件,不是持久的,即两进程终止后,管道也自动消失了。

示例:创建父子进程,创建无名管道,父写子读。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
int main()
{
int fds[2] = {0};
pipe(fds);
char buf[32];
if(fork() == 0){
sleep(2); //保证父进程有机会把数据写入
read(fds[0], buf, sizeof(buf)); //子进程从读端读数据
puts(buf);
close(fds[0]);
close(fds[1]);
}
else{
write(fds[1], "hello", 6); //父进程向写端写数据
waitpid(-1, NULL, 0); //等子退出
close(fds[0]);
close(fds[1]);
}
return 0;
}

注意: 管道两端的关闭是有先后顺序的。 如果先关闭写端则从另一端读数据时,read 函数将返回 0,表示管道已经关闭; 但是如果先关闭读端,则从另一端写数据时,将会使写数据的进程接收到 SIGPIPE 信号,如 果写进程不对该信号进行处理,将导致写进程终止,如果写进程处理了该信号,则写数据的 write 函数返回一个负值,表示管道已经关闭。

6、有名管道(FIFO

 无名管道只能在亲缘关系的进程间通信,这大大限制了管道的使用,有名管道突破了这个限制, 通过指定路径名的形式实现不相关进程间的通信。

6.1 创建、删除FIFO文件

创建 FIFO 文件与创建普通文件很类似,只是创建后的文件用于 FIFO。

1)用函数创建和删除 FIFO 文件 创建 FIFO 文件的函数原型如下:

#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
形参:
pathname 要创建的 FIFO 文件的全路径名;
mode 为文件访问权限,比如 0666。
返回值:
如果创建成功,则返回 0,否则-1。

删除 FIFO 文件的函数原型如下:

#include int unlink(const char *pathname);

跟 mkfifo 的第一个形参一样。

示例:用函数创建FIFO文件

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[]) //演示通过命令行传递参数
{
if(argc != 2){
puts("arg cnt err:");
return -1;
}
if(mkfifo(argv[1], 0666) == -1){
perror("mkfifo fail");
return -2;
}
//unlink(argv[1]);//加上这句会将创建的 FIFO 文件删除。
return 0;
}
/*
说明 :创建名字为 2 的 FIFO 文件
[root@localhost test]# gcc -o main main.c
[root@localhost test]# ./main 2 */

2)用命令创建和删除 FIFO 文件 用命令 mkfifo 创建 fifo 文件,不能重复创建。 用命令 unlink 删除 fifo 文件。 创建完毕之后,就可以访问 FIFO 文件了:

一个终端:cat < myfifo //输出 另一个终端:echo “hello” > myfifo //输入

6.2  打开、关闭FIFO文件

对 FIFO 类型的文件的打开/关闭跟普通文件一样,都是使用 open 和 close 函数。

1)如果打开时使用 O_WRONLY 选项,则打开 FIFO 的写入端,

2)如果使用 O_RDONLY 选项,则打开 FIFO 的读取端,

3)写入端和读取端都可以被几个进程同时打开。

该管道可以通过路径名来指出,并且在文件系统中是可见的。 在建立了管道之后,两个进程就可以把它当作普通文件一样进行读写操作,使用非常方便。

注意:

1)FIFO 是严格地遵循先进先出规则。

2)对管道及 FIFO 的读总是从该文件开始处返回数据。

3)对它们的写则把数据添加到该文件末尾。

4)它们不支持如 lseek()等文件定位操作。

6.3 读写FIFO文件

可以采用与普通文件相同的读写方式读写 FIFO。

示例: 先执行#mkfifo f.fifo 命令,创建一个 FIFO 文件。 编写 write.c,如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
int main()
{
int fd = open("f.fifo", O_WRONLY); //1. 打开(判断是否成功打开略)
write(fd, "hello", 6); //2. 写
close(fd); //3. 关闭
return 0;
}


相关文章
|
27天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
62 1
|
1天前
|
Ubuntu Linux 开发者
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
29 15
|
15天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
79 13
|
22天前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
30天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
2月前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
8月前
|
Linux Shell 调度
【Linux】7. 进程概念
【Linux】7. 进程概念
73 3
|
8月前
|
存储 缓存 Linux
【Linux】进程概念(冯诺依曼体系结构、操作系统、进程)-- 详解
【Linux】进程概念(冯诺依曼体系结构、操作系统、进程)-- 详解
|
5月前
|
Linux Shell 调度
【在Linux世界中追寻伟大的One Piece】Linux进程概念
【在Linux世界中追寻伟大的One Piece】Linux进程概念
49 1
|
7月前
|
存储 Linux Shell
Linux进程概念(上)
冯·诺依曼体系结构概述,包括存储程序概念,程序控制及五大组件(运算器、控制器、存储器、输入设备、输出设备)。程序和数据混合存储,通过内存执行指令。现代计算机以此为基础,但面临速度瓶颈问题,如缓存层次结构解决内存访问速度问题。操作系统作为核心管理软件,负责资源分配,包括进程、内存、文件和驱动管理。进程是程序执行实例,拥有进程控制块(PCB),如Linux中的task_struct。创建和管理进程涉及系统调用,如fork()用于创建新进程。
70 3
Linux进程概念(上)