Linux编程——多任务间通信和同步

在前面的文章中(Linux编程基础——多线程),简单对Linux中的多线程进行了介绍,包括pthread、信号量与互斥锁,本文将对Linux编程中的多任务间通信与同步技术进行相对完整的补充。

在Linux中有两种多任务实现手段:进程和线程

  • 由于进程是工作在独立的内存空间中,不同的进程间不能直接访问到对方的内存空间,因此需要通过某种方式来通信。
  • 而同一进程内的线程共享内存空间,很容易实现数据共享,但需要严格控制多线程对同一内存地址的访问,因此需要采用一定的方式来进行同步。

Linux中主要提供了以下的一些方式来实现多任务通信和同步:

  • 信号。信号是在软件层次上对中断机制的一种模拟,用于通知接受进程(或线程)某事件的发生。
  • 管道及有名管道。管道勇于具有亲缘关系的进程间的通信。有名管道,除具有管道的全部功能外,还允许无亲缘关系的进程间的通信。
  • 消息队列。消息队列是消息的链接表,克服了前两种通信方式中信息量有限的缺点,具有写权限的进程可以向消息队列中按照一定的规则添加新消息;消息队列有读权限的进程则可以从消息队列中读取消息。
  • 共享内存。主要用于进程间通信。使得多个进程可以访问同一块内存空间,不同进程可以及时看到共享内存中数据的更新。在一些多核的系统应用中,也基于共享内存实现共享。
  • 套接字。更一般的通信机制,可用于不同机器之间的进程间通信。在相关网络通信介绍中均会有所涉及。
  • 信号量。是一个可以用来控制多个进程存取共享资源的计数器。其经常作为一种锁定机制来防止当一个进程正在存取共享资源时,另一个进程也存取同一资源。
  • 互斥锁。主要用于线程同步,可以对共享资源加锁,任何其他试图在此对互斥量加锁的线程将会被阻塞直至当前线程释放该互斥锁,保证每次只有一个线程可以对共享资源访问。

其中,前5种主要用于多任务间通信,后2种主要用于多任务间同步。

1. 信号

信号(Signal)是Linux操作系统中用于进程间通信和进程内部通信的一种机制。信号是由内核向进程发送的一种异步通知(类似中断),表示某个事件已经发生或者某个条件已经满足。每个信号都对应一个唯一的整数信号值,例如SIGINT表示中断信号。
Linux中的型号包括两种类型:同步信号和异步信号。

  • 同步信号,是由某个进程自己发出的,例如在程序中调用kill函数发送信号给自己或其他进程;
  • 异步信号,由内核发出给进程的,例如由硬件中断或其他进程的型号发送给本进程。

信号涉及头文件<signal.h>,将信号都定义为整数。

信号名称信号定义
SIGINT终端中断,终止进程的中断信号,通常由CTRL+C触发
SIGTERM终止进程的请求信号,通常由kill命令发送
SIGKILL停止进程,强制终止进程的信号,无法被忽略、阻塞或处理,通常用于终止僵尸进程和崩溃的进程
SIGSTOP停止执行,暂停进程的信号,无法被忽略、阻塞或处理,通常由CTRL+Z触发
SIGTSTP终端停止信号,暂停进程的信号,可以被忽略、阻塞或处理,通常由CTRL+Z触发
SIGCONT如果被停止则继续执行,恢复进程的信号,通常用于从暂停状态中恢复进程
SIGHUP系统挂断,终端挂起或断开的信号,通常用于重新读取配置文件或重启进程
SIGUSR1自定义信号1,可以用于进程间通信
SIGUSR2自定义信号2,可以用于进程间通信

信号的生命周期

从信号发送到信号处理函数执行完毕的全过程称为信号的生命周期。可以分为三个重要的阶段,主要事件:信号产生;信号在进程中注册;信号在进程中注销;信号处理函数执行完毕。

  • 信号产生。指触发信号的事件发生(如硬件异常、定时器超时以及调用信号发送函数kill()sigqueue()等。)
#include <sys/types.h>
#include <signal.h>int kill(pid_t pid, int signo);

其中signo为要发送的信号值。调用成功返回0,否则返回-1。

#include <signal.h>int sigqueue(pid_t pid, int signo, const union sigval value);

其中,signo为要发送的信号类型,value是一个union类型,用于传递信号的附加信息。

  • 信号在进程中注册。 注册是指进程知道需要处理某个信号,但还没来得及处理,或该信号被进程阻塞,则先将该信号保存到某个链表中(如未决信号链)。
  • 信号在进程中注销。注销是指进程等待处理某个信号,且该信号没有被进程阻塞,则在运行相应的信号处理函数前,进程会把信号从未决信号链中卸载。
  • 信号处理。进程注销信号后,立即执行相应的信号处理函数,执行完毕后,信号的本次发送对进程的影响彻底结束。

用户进程对于信号的响应可以有以下三种处理方式:

  • 忽略信号:即对信号不做任何处理,但有两个信号不能忽略,SIGKILL和SIGSTOP。
  • 捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。
  • 执行默认操作:Linux对每种信号都规定了默认的动作。
signal()函数
void (*signal(int sig, void (*func)(int)))(int);//可替换为以下方式理解typedef void sign(int);sign *signal(int, handler *);

关于signal函数的使用:

  1. 定义一个信号处理函数,其函数形式为:void func(int)
  2. 调用signal函数,将要处理的信号和型号处理函数作为参数传递进去,进行处理。
// 对信号SIGINT的响应函数
void sig_int(int sig) {printf("Received signal %d\n", sig);// 进行信号处理
}
// 注册信号
signal(SIGINT, sig_int);

当收到Ctrl+C组合键时,将会打印信号“Received signal 2”。

2. 管道

管道(“|”)是Linux中一种非常强大的特性,是进程间通信(IPC,Inter Process Communication),用于将一个命令的输出作为另一个命令的输入。通过使用管道,可以将多个命令串联在一起,从而实现更多的复杂操作。
无名管道(PIPE),可用于具有亲缘关系进程间的通信。
有名管道(FIFO),除具有管道所具有的功能外,还允许无亲缘关系进程间的通信。

关于(无名)管道的特性:

  • 管道是半双工的,数据只能向一个方向流动:需要双方通信时,需要建立起两个管道;
  • 只能用于父子进程或兄弟进程之间(具有亲缘关系的进程);
  • 管道对于管道两端的进程而言,就是一个文件,但不是普通的文件,不属于某个文件系统,而是单独构成一种文件系统,且只存在于内存中。
  • 一个进程向管道中写的内容被管道另一端的进程读出。

2.1 管道(pipe)的使用

管道是由系统调用pipe()函数创建,具体使用操作包括创建、写、读、关闭。

步骤1:创建管道

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

pipe函数用于创建一对无名的管道文件描述符。

  • pipefd[0]:管道读取端
  • pipefd[1]:管道写入端
//用于保存无名管道的文件描述符
int pipefd[2];
if(pipe(pipefd)<0)
{
//创建管道失败
}

步骤二:写管道

#include <stdio.h>
char buf[BUFSIZ];
//将buf中内容写入到管道
write(pipefd[1], buf, BUFSIZ);

步骤三:读管道

//从管道中读取内容到buf
rcount = read(pipefd[0], buf, BUFSIZ);

可以看到,通过创建之后的管道文件描述符进行管道的操作。

步骤四:关闭管道
在管道操作完成之后,通过close函数关闭管道。

close(pipefd[0]);
close(pipefd[1]);

2.2 FIFO的使用

FIFO,也成为有名管道(Named Pipe),是Linux中一种特殊类型的文件。它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。因此,即使与FIFO创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信。
另外,从FIFO的命名来看,可知道FIFO管道中数据的方式为,先进先出,(First In First Out),即读从开始处返回,写则添加到末尾。

FIFO的操作包括,创建、打开和关闭、写与读、删除等操作。

创建FIFO

使用mkfifo命令创建FIFO文件。

int mkfifo(const char *pathname, mode_t mode);

创建成功返回0。模式方式有:

  • O_RDONLY,读管道;
  • O_WRONLY,写管道;
  • O_RDWR,读写管道;
  • O_NONBLOCK,非阻塞;
  • O_CREAT,如果该文件不存在,就创建一个新的文件;
  • O_EXCL,如果配合O_CREAT时文件存在,返回错误信息。
#include <unistd.h>
//创建FIFO之前,通过access函数来检查文件是否存在,或权限
int access(const char *pathname, int mode);//当文件检查失败失败则调用mkfifo创建
if(access(FIFO_NAME, F_OK)==-1)
{res = mkfifo(FIFO_NAME, 0777);
}
打开和关闭FIFO

创建FIFO文件之后,可以使用文件IO操作打开FIFO,语法为 fd = open(pathname, flags),其中pathname为FIFO文件的路径名,flags为打开文件的方式。
打开成功后,可以使用read和write函数向FIFO中写入和读取数据。
关闭FIFO使用close函数,语法为 close(fd)

使用cat和echo向FIFO中写入数据

在文件中,则使用write(pipe_fd, buf, BUFFER_SIZE来写入。

命令行操作,可以使用cat和echo命令向FIFO中写入数据,语法为echo "data" > filenamecat file.txt > filename

使用tail和cat从FIFO中读取数据

对于管道的另一端,则通过read函数读取FIFO中数据。

在命令行操作中,可以使用tail和cat命令从FIFO中读取数据,语法为tail -f filenamecat filename

如果有进程写打开FIFO,且当前FIFO内没有数据,则对于设置了阻塞标志的读操作来说,将一直阻塞。对于没有设置阻塞标志的读操作来说则返回-1。

3. 共享内存

共享内存是一种高效的进程间通信方式。两个不同的进程A、B共享内存的意义是通过映射之后,在进程地址空间访问同一块物理内存。A、B之间可以及时看到共享内存中数据的更新。

共享内存的一种实现方式是通过mmap()系统调用。通过mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以像访问普通内存一样对文件进行访问,不必再调用read、write等操作。mmap系统调用配合使用的系统调用还有munmap()msync()等,函数原型定义如下:

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);//映射解除,解除映射关系后,对原来映射地址的访问将导致段错误
int munmap(void *addr, size_t len);
//实现磁盘文件内容与共享内存区内存同步,保持一致
int msync(void *addr, size_t len, int flags);

一般来说,进程在映射空间的对共享内容的改变并不直接写回到磁盘文件中,往往在调用munmap()后才执行该操作。可以通过msync()实现磁盘上文件内容与共享内存区的内容一致。

除系统调用mmap以外,Linux中还引入了System V共享内存。
内存专门留出了一块共享内存区域,所有需要访问该共享区域的进程都要把该共享区域映射到本进程的地址空间中。每个内存区域都有一个标示符(shmid),进程通过这个标示符访问内存区域。

  1. 首先创建或打开一个共享内存对象,可以使用shmget()函数或者shm_open()函数。
#include <sys/shm.h>
int shmget(key_t key, size_t size, int flag);
void *shmat(int shmid, const void *addr, int flag);
void shmdt(void *addr);
  1. 分配一段内存空间来存储共享内存对象,可以使用shmat()函数将内存挂接到当前进程的地址空间中。

  2. 对挂接到进程地址空间的内存进行读写操作,可以使用memcpy()等相关函数。

  3. 使用shmdt()函数将共享内存对象从进程地址空间中分离,使其不再被当前进程使用。

  4. 最后使用shmctl()函数对共享内存对象进行进一步的控制,如删除共享内存对象、查询共享内存信息等。

需要注意的是,在使用共享内存时需要对共享内存进行加锁保护,避免多个进程同时对共享内存进行读写产生竞争和错误。此外,在多进程环境下,使用共享内存需要使用信号量等同步机制来保证进程间的同步和互斥。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/114985.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ubuntu20.04安装MySQL8、MySQL服务管理、mysql8卸载

ubuntu20.04安装MySQL8 #更新源 sudo apt-get update #安装 sudo apt-get install mysql-serverMySQL服务管理 # 查看服务状态 sudo service mysql status # 启动服务 sudo service mysql start # 停止服务 sudo service mysql stop # 重启服务 sudo service mysql restart登…

中间件安全-CVE复现WeblogicJenkinsGlassFish漏洞复现

目录 服务攻防-中间件安全&CVE复现&Weblogic&Jenkins&GlassFish漏洞复现中间件-Weblogic安全问题漏洞复现CVE_2017_3506漏洞复现 中间件-JBoos安全问题漏洞复现CVE-2017-12149漏洞复现CVE-2017-7504漏洞复现 中间件-Jenkins安全问题漏洞复现CVE-2017-1000353漏…

idea设置字体大小快捷键 Ctrl+鼠标上下滑 字体快捷键缩放设置

双击 按住ctrl鼠标滑轮上划放大就好了 这个双击设置为&#xff0c;Ctrl鼠标下滑 字体缩小就好了

03-垃圾收集策略与算法

垃圾收集策略与算法 程序计数器、虚拟机栈、本地方法栈随线程而生&#xff0c;也随线程而灭&#xff1b;栈帧随着方法的开始而入栈&#xff0c;随着方法的结束而出栈。这几个区域的内存分配和回收都具有确定性&#xff0c;在这几个区域内不需要过多考虑回收的问题&#xff0c;因…

手把手创建属于自己的ASP.NET Croe Web API项目

第一步&#xff1a;创建项目的时候选择ASP.NET Croe Web API 点击下一步&#xff0c;然后配置&#xff1a; 下一步&#xff1a;

Adobe Photoshop 基本操作

PS快捷键 图层 选择图层 Ctrl T&#xff1a;可以对图层的大小和位置进行调整 填充图层 MAC: AltBackspace (前景) or CtrlBackspace (背景) WINDOWS: AltDelete (前景) or CtrlDelete (背景) 快速将图层填充为前景色或背景色 平面化图层&#xff08;盖印图层&#xff09…

性能测试LoadRunner02

本篇主要讲&#xff1a;通过Controller设计简单的测试场景&#xff0c;可以简单的分析性能测试报告。 Controller 设计场景 Controller打开方式 1&#xff09;通过VUG打开 2&#xff09;之间双击Controller 不演示了&#xff0c;双击打开&#xff0c;选择Manual Scenario自…

《视觉 SLAM 十四讲》V2 第 9 讲 后端优化1 【扩展卡尔曼滤波器 EKF BA+非线性优化(Ceres、g2o)】

文章目录 第9讲 后端19.1.2 线性系统和 KF9.1.4 扩展卡尔曼滤波器 EKF 不足 9.2 BA 与 图优化9.2.1 投影模型和 BA 代价函数9.2.2 BA 的求解9.2.3 稀疏性 和 边缘化9.2.4 鲁棒核函数 9.3 实践&#xff1a; Ceres BA 【Code】本讲 CMakeLists.txt 9.4 实践&#xff1a;g2o 求解 …

100 # mongoose 的使用

mongoose elegant mongodb object modeling for node.js https://mongoosejs.com/ 安装 mongoose npm i mongoose基本示例 const mongoose require("mongoose");// 1、连接 mongodb let conn mongoose.createConnection("mongodb://kaimo313:kaimo313loc…

如何从小白成长为AI工程师笔记

&#x1f4da;入门机器学习基础 对于本科生来说&#xff0c;需要打好数学基础&#xff0c;包括高数、概率论和线性代数。 对于已经上研究生或工作想转行的人来说&#xff0c;可以直接开始学习机器学习算法&#xff0c;重要的是理解算法的原理和推导过程。如果有时间和需要&am…

Roslyn 去除多余using

原因 当你添加新代码的时候VS会自动帮你添加对应的using&#xff0c;但是它只会帮你加不会帮你减 由于运行时并不能使用UnityEditor命名空间里面的东西。你就算加了也会在打包的时候给你报错&#xff0c;除非使用宏包裹起来 因为我们打包都是在打包机上操作的。一般情况下自己…

vue3 拖拽插件 Vue3DraggableResizable

Vue3DraggableResizable 拖拽插件的官方文档 一、Vue3DraggableResizable 的属性和事件 1、Vue3DraggableResizable 的属性配置 属性类型默认值功能描述示例initWNumbernull设置初始宽度&#xff08;px&#xff09;<Vue3DraggableResizable :initW“100” />initHNumb…

sql高级教程-索引

文章目录 架构简介1.连接层2.服务层3.引擎层4.存储层 索引优化背景目的劣势分类基本语法索引结构和适用场景 性能分析MySq| Query Optimizerexplain 索引优化单表优化两表优化三表优化 索引失效原因 架构简介 1.连接层 最上层是一些客户端和连接服务&#xff0c;包含本地sock通…

1 Go的前世今生

概述 Go语言正式发布于2009年11月&#xff0c;由Google主导开发。它是一种针对多处理器系统应用程序的编程语言&#xff0c;被设计成一种系统级语言&#xff0c;具有非常强大和有用的特性。Go语言的程序速度可以与C、C相媲美&#xff0c;同时更加安全&#xff0c;支持并行进程。…

[架构之路-241]:目标系统 - 纵向分层 - 企业信息化与企业信息系统(多台企业应用单机组成的企业信息网络)

目录 前言&#xff1a; 一、什么是信息系统&#xff1a;计算机软件硬件系统 1.1 什么是信息 1.2 什么是信息系统 1.3 什么是信息技术 1.4 什么是信息化与信息化转型 1.5 什么是数字化与数字化转型&#xff08;信息化的前提&#xff09; 1.6 数字化与信息化的比较 1.7 …

力扣每日一题58:最后一个单词的长度

题目描述&#xff1a; 给你一个字符串 s&#xff0c;由若干单词组成&#xff0c;单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 示例 1&#xff1a; 输入&#xff1a;s "Hello World&q…

Arduino IDE + Esp32 Cam + 实现视频流 + 开发环境部署

1、开发环境 Arduino ide 版本&#xff1a;2.2.1 esp32工具&#xff1a;2.0.5 示例代码 #include "esp_camera.h" #include <WiFi.h>// // WARNING!!! PSRAM IC required for UXGA resolution and high JPEG quality // Ensure ESP32 Wrover Modu…

Zookeeper、Kafka集群与Filebeat+Kafka+ELK架构、部署实例

Zookeeper、Kafka集群与FilebeatKafkaELK架构、部署实例 一、Zookeeper1.1、Zookeeper 定义1.2、Zookeeper 工作机制1.3、Zookeeper 特点1.4、Zookeeper 数据结构1.5、Zookeeper 应用场景1.5、Zookeeper 选举机制1.5.1、 第一次启动选举机制1.5.2、 非第一次启动选举机制 二、Z…

二、UI入门

1. QWidget类 QWidget类是Qt所有图形用户界面&#xff08;组件&#xff09;的基类&#xff0c;因此QWidget类内部规定了所有最基础的UI相关功能。例如以下成员&#xff1a; ● width : const int 宽度&#xff08;单位&#xff1a;像素&#xff0c;后文同&#xff09; Qt中的…

优先级总结

目录 越小越优先 1.路由协议 2.路由开销 3.STP 4.Ethernet-trunk&#xff08;LACP&#xff09; 越大越优先 1.VRRP 2.Router-id 3.DR/BDR 越小越优先 1.路由协议 取值范围&#xff1a;0~255 直连路由0 静态路由/默认路由60 RIP路由100 OSPF路由10或150 BGP路由255 2…