嵌入式 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;
}


相关文章
|
8月前
|
并行计算 Linux
Linux内核中的线程和进程实现详解
了解进程和线程如何工作,可以帮助我们更好地编写程序,充分利用多核CPU,实现并行计算,提高系统的响应速度和计算效能。记住,适当平衡进程和线程的使用,既要拥有独立空间的'兄弟',也需要在'家庭'中分享和并行的成员。对于这个世界,现在,你应该有一个全新的认识。
305 67
|
7月前
|
Web App开发 Linux 程序员
获取和理解Linux进程以及其PID的基础知识。
总的来说,理解Linux进程及其PID需要我们明白,进程就如同汽车,负责执行任务,而PID则是独特的车牌号,为我们提供了管理的便利。知道这个,我们就可以更好地理解和操作Linux系统,甚至通过对进程的有效管理,让系统运行得更加顺畅。
223 16
|
7月前
|
Unix Linux
对于Linux的进程概念以及进程状态的理解和解析
现在,我们已经了解了Linux进程的基础知识和进程状态的理解了。这就像我们理解了城市中行人的行走和行为模式!希望这个形象的例子能帮助我们更好地理解这个重要的概念,并在实际应用中发挥作用。
143 20
|
6月前
|
监控 Shell Linux
Linux进程控制(详细讲解)
进程等待是系统通过调用特定的接口(如waitwaitpid)来实现的。来进行对子进程状态检测与回收的功能。
131 0
|
6月前
|
存储 负载均衡 算法
Linux2.6内核进程调度队列
本篇文章是Linux进程系列中的最后一篇文章,本来是想放在上一篇文章的结尾的,但是想了想还是单独写一篇文章吧,虽然说这部分内容是比较难的,所有一般来说是简单的提及带过的,但是为了让大家对进程有更深的理解与认识,还是看了一些别人的文章,然后学习了学习,然后对此做了总结,尽可能详细的介绍明白。最后推荐一篇文章Linux的进程优先级 NI 和 PR - 简书。
203 0
|
6月前
|
存储 Linux Shell
Linux进程概念-详细版(二)
在Linux进程概念-详细版(一)中我们解释了什么是进程,以及进程的各种状态,已经对进程有了一定的认识,那么这篇文章将会继续补全上篇文章剩余没有说到的,进程优先级,环境变量,程序地址空间,进程地址空间,以及调度队列。
133 0
|
6月前
|
Linux 调度 C语言
Linux进程概念-详细版(一)
子进程与父进程代码共享,其子进程直接用父进程的代码,其自己本身无代码,所以子进程无法改动代码,平时所说的修改是修改的数据。为什么要创建子进程:为了让其父子进程执行不同的代码块。子进程的数据相对于父进程是会进行写时拷贝(COW)。
184 0
|
9月前
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
9月前
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
705 5
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能