百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术分析 > 正文

Linux基础知识(六) 细说linux基础知识

liebian365 2024-11-03 15:46 20 浏览 0 评论

本篇介绍一些Linux进程间通信IPC相关内容.

1. 进程间通信 IPC(inter-Process communication)

进程间通信就是在不同进程之间传播或交换信息,其主要包含以下目的

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

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

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

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

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

2. 进程间交换数据的方法

1) 文件

父进程把运算结果写入文件中, 子进程去读取文件信息(需要加文件锁)

2) 信号

只能传递一个值 1~64 号信号中的一个

3) 管道

4) 共享内存

5) 消息队列

6) 信号量集, 与信号没有任何关系

3. 管道的使用

管道有以下特点:

1) 最古老的进程间通信方式, unix 最开始使用的进程交换数据的方式

2) 以文件作为交换数据的媒介, 管道分为匿名管道和命名管道

3) 历史上的管道指的是半双工管道

3.1 匿名管道

主要用于父子进程之间通信

int pipe ( int pipefd[2] ); 
pipe()函数会建立管道,并将文件描述词由参数pipefd数组返回。
pipefd : 用于存放文件描述符
 pipefd [0]为管道里的读取端
 pipefd [1]则为管道的写入端。
返回值 : 若成功则返回0,否则返回-1,错误原因存于errno中。

使用步骤:

int main()
{
 int _pipe[2];
 int ret = pipe(_pipe);
 if(ret == -1)
 {
 printf("creat pipe error!errno code is:%d\n",errno);
 return -1;
 }
 pid_t id = fork();
 if(id < 0)
 {
 printf("fork error!");
 return 2;
 }
 else if(id==0) /* 子进程 */
 {
 close(_pipe[0]); /* 关掉读*/
 int i = 0;
 char* buf = NULL;
 while(i<20)
 {
 if(i<10)
 { 
 buf = " i am child!";
 write(_pipe[1], buf,strlen(buf)+1); /* 写入数据*/
 }
 sleep(1);
 i++;
 }
 }
 else /* 父进程 */
 {
 char buf[100];
 int j = 0;
 while(j<3)
 {
 memset(buf,'\0',sizeof(buf));
 int ret = read(_pipe[0],buf,sizeof(buf)); /* 读出数据 */
 printf("%s:code is %d\n",buf,ret);
 j++;
 }
 close(_pipe[0]);
 }
 return 0;
}

3.2 命名管道

两个无关进程之间通信

编程模型:

int mkfifo(const char *pathname, mode_t mode);
pathname,要创建管道文件的路径和名称
mode,要创建的管道文件的操作权限
返回值 : 若成功则返回0,否则返回-1,错误原因存于errno中

当 A B 两个进程打开操作管道的过程中, 如果一段关闭管道(管道断裂), 另一端会收到 SIGPIPE , 该信号默认处理方式是结束当前进程

4. 共享内存

共享内存允许两个不相关的进程访问同一段物理内存, 由于数据不需要在不同的进程间复制,所以它是在两个正在运行的进程之间传递数据的一种非常有效的方式,一个进程向共享内存区域写入数据,共享该区域的所有进程就可以立刻看到其中的数据内容

使用步骤:

(1) 键 key的选取

如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来替代。
如果两个进程没有任何关系,就用ftok()函数算出来一个标识符(或者自己定义一个)使用

key_t key=12345

(2) 服务器进程使用 key 来创建(申请) 共享内存

int shmget ( key_t key, int size, int flag ); 
shmget函数 : 用于开辟或指向一块共享内存,返回获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域
keyt key : 共享内存的标识符
int size : 以字节为单位指定需要共享的内存容量
int flag : 权限标志,它是这块内存的模式(mode)以及权限标识,模式可取如下值: 
 IPC_CREAT 新建(如果已创建则返回目前共享内存的id) 
 IPC_EXCL 与 IPC_CREAT结合使用,如果已创建则返回错误 
 将“模式” 和“权限标识”进行或运算,做为第三个参数
 如 IPC_CREAT | IPC_EXCL | 0640 其中0640为权限标识,4/2/1 分别表示读/写/执行3种权限,第一个0是UID,第一个6(4+2)表示拥有者的权限,第二个4表示同组权限,第3个0表示他人的权限
返回值 : 函数调用成功时返回共享内存的ID,失败时返回-1。

注:创建共享内存时,shmflg参数至少需要 IPC_CREAT | 权限标识,如果只有IPC_CREAT 则申请的地址都是k=0xffffffff,不能使用;

(3) 映射共享内存, 得到该物理内存的虚拟地址

void *shmat(int shmid, const void *shmaddr, int shmflg);
shmat函数 : 用来允许本进程访问一块共享内存的函数,第一次创建共享内存时,任何进程不能访问,要想启用对该共享内存的访问,必须将其连接到一个进程的地址空间中
int shmid : 共享内存的ID,即共享内存的标识
char *shmaddr : 共享内存连接到进程中的起始地址,如果shmaddr为NULL,内核会把共享内存映射到系统选定的地址空间中;如果shmaddr不为NULL,内核会把共享内存映射到shmaddr指定的位置。
int shmflag : 本进程对该内存的操作模式,可以有两个取值
 SHM_RND为读写模式,SHM_RDONLY是只读模式
返回值 : 函数调用成功时,返回共享内存的起始地址,失败时返回-1。

注:一般情况下很少需要控制共享内存连接的地址,通常都是让系统来选择一个地址,否则就会使应用程序对硬件的依赖性过高,所以一般把shmaddr设为NULL。

(4) 使用共享内存

类似于 p=malloc(1000);

(5) 解除映射

int shmdt(const void *shmaddr);
shmdt函数 : 用于函数删除本进程对这块内存的使用,shmdt()与shmat()相反,是用来禁止本进程访问一块共享内存的函数
char *shmaddr : 是那块共享内存的起始地址
返回值 : 函数调用成功时返回0,失败时返回-1

6) 释放/销毁共享内存

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl函数 : 控制对这块共享内存的使用
int shmid : 共享内存的ID,即共享内存标识
int cmd : 控制命令,表示要采取的动作,可取值如下:
 IPC_STAT 得到共享内存的状态:把shmid_ds结构中的数据设置为共享内存的当前关联值
 IPC_SET 改变共享内存的状态:把共享内存的当前关联值设置为shmid_ds结构中给出的值
 IPC_RMID 删除共享内存段
struct shmid_ds *buf : 结构体指针,IPC_STAT的时候,取得的状态放在这个结构体中,如果要改变共享内存的状态,用这个结构体指定,shmid_ds结构至少包含以下成员:
 struct shmid_ds {
 uid_t shm_perm.uid;
 uid_t shm_perm.gid;
 uid_t shm_perm.mode;
 }
返回值 : 函数调用成功时返回0,失败时返回-1

优点: 最快的进程间通信手段

缺点: 需要自行变成解决共享内存的同步访问,一般是通过信号量来解决的

5. 消息队列

消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法

每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型

编程步骤

(1) 键 key 的选取

如果是父子关系的进程间通信的话,这个标识符用IPC_PRIVATE来替代。
如果两个进程没有任何关系,就用ftok()函数算出来一个标识符(或者自己定义一个)使用

key_t key=12345

(2) 创建/获取消息队列

int msgget ( key_t key, int msgflg );
msgget函数 : 得到消息队列标识符或创建一个消息队列对象
key : 标识队列的唯一编号
msgflag : 创建消息队列时, flag 的取值为, IPC_CREAT|IPC_EXCL|权限
 获得消息队列时, flag 的取值为, 0
返回值 : 函数调用成功时返回0,失败时返回-1,错误原因存于error中

(3) 向消息队列这放入消息

int msgsnd (int msgid, const void *addr, size_t size, int msgflg );
msgsend函数 : 将消息写入到消息队列
msgid : 消息放入那个消息队列
addr : 消息数据地址
size : 消息的长度
msgflag : 发送消息通常情况下给 0, 阻塞执行, 参数IPC_NOWAIT为不阻塞执行
 当消息队列满了的情况下, 如果给 0, 就等到可以放进去位为止( 接受到其他 信号也可能停止, 如 ctrl +c ), IPC_NOWAIT 则会直接报错
返回值 : 函数调用成功时返回0,失败时返回-1,错误原因存于error中

从消息队列这取出消息

ssize_t msgrcv(int msgid, void *addr, size_t size, long msgtype, int msgflg);
msgrcv函数 : 从消息队列读取消息
msgid : 知道从哪个消息队列这取出消息
addr : 取出的消息存储什么位置
size : 期望取到的消息的长度
msgtype : 指定取出那个类型的消息
 =0, 接受任意类型的消息
 >0, 接受特定类型的消息
 <0, 接受消息类型小于等于 |msgtpye|的消息( msgtyoe 的绝对值), 按照消息类型从小到大返回接受的消息
msgflag : 发送消息通常情况下给 0, 阻塞执行, 参数IPC_NOWAIT为不阻塞执行
 IPC_NOERROR, 使用这个参数时如果接收的字节长度比发送的长, 则会把多余的丢弃, 如果不加这个参数则会报错返回-1
返回值 : 函数调用成功时返回0,失败时返回-1,错误原因存于error中

(4) 销毁/删除消息队列

int msgctl(int msgid, int cmd, struct msgid_ds *buf);
msgctl函数 : 获取和设置消息队列的属性, 销毁消息队列
msgid : 需要进行操作的消息队列的 id
cmd : IPC_RMID,销毁消息队列, 知道没有进程使用才真正销毁, 类似与 unlink
 IPC_STAT, 通过 buf 参数返回消息队列的属性信息
 IPC_SET,通过buf参数设置消息队列属性信息
buf : 与 IPC_STAT 一起使用的时候返回消息队列存的属性信息
返回值 : 函数调用成功时返回0,失败时返回-1,错误原因存于error中

(5) 发送消息格式

消息分为有类型消息和无类型消息

1) 无类型消息简单, 但是接受消息时无细分, 只能先进先出

2) 有类型的消息编程比较规范, 接受可以按照消息类型先进先出

格式如下所示

struct s_msg{ /*msgp定义的参照格式*/
 long type; /* 必须大于0,消息类型 */
 char mtext[256]; /*消息正文,可以是其他任何类型*/
} msgp;

6. 信号量(集)

信号量是一个计数器( 数字), 控制访问共享资源的最大并行进程总数, 使用时一般是给一个初始值( int 类型的变量)

假如该共享资源允许两个同时两个进程使用, 初始值设置为2, 如果有一个进程使用该资源则该计数-1, 如果有另一个进程放弃该资源计数+1, 如果计数为 0, 则不允许新的进程来访问共享资源, 新的进程阻塞等待, 直到计数重新大于 0 可以完成-1 操作, 解除阻塞

如果由多个共享资源需要控制访问, 继续要多个信号量, 把多个信号量存入一个, 数组中, 这个数组就叫信号量集

信号量集的编程步骤:

(1) 获得/确定 key

key_t key=nnnn; /*自己定义*/
key_t ftok = (pathname, proj_id); /*使用ftok函数生成*/
pathname : 存在的文件的名称
proj_id : 1~255 之间的任意值
返回值 : 通过 inode 值和 proj_id 编码生成的 key_t 

2) 创建/获取信号量集

int semget(key_t key, int nsems, int semflg);
semget函数 : 创建一个新的信号量或获取一个已经存在的信号量的键值
key : 创建/获取的信号量集对应的 key 键
nsems : 创建的信号量集中包含的信号量的个数
semflg : 信号量的创建方式或权限,有IPC_CREAT,IPC_EXCL
 IPC_CREAT如果信号量不存在,则创建一个信号量,否则获取
 IPC_EXCL只有信号量不存在的时候,新的信号量才建立,否则就产生错误
返回值 : 成功返回对应的信号量集的 id , 失败返回-1,错误原因存于error中

3) 设置信号量的初始值

int semctl(int semid, int semnum, int cmd, ...);
semctl函数 : 对信号量进行设置
semid : 要操作哪个信号量集
semnum : 指定要操作该集合中的哪个信号量
cmd : 对该信号量执行怎样的操作
 IPC_STAT, 从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中
 IPC_SET, 设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值
 IPC_RMID, 从内核中删除信号量集合
 SETVAL, 用联合体中val成员的值设置信号量集合中单个信号量的值
args : 第四个参数取决于cmd参数的取值
 union semun {
 short val; /*SETVAL用的值*/
 struct semid_ds* buf; /*IPC_STAT、IPC_SET用的semid_ds结构*/
 unsigned short* array; /*SETALL、GETALL用的数组值*/
 struct seminfo *buf; /*为控制IPC_INFO提供的缓存*/
 } arg;
返回值 : 成功返回大于或等于0, 失败返回-1,错误原因存于error中

4) 控制队关系资源的并发访问

在使用共享资源之前给信号量-1,-1 成功, 就操作共享资源, 使用完毕之后信号量+1,-1 不成功, 则阻塞

int semop(int semid, struct sembuf *sops, unsigned nsops);
semop函数 : 完成对信号量的操作
semid : 要操作哪个信号量集
nsops : 指明要操作的信号量的个数
sops : 对信号量集进进行哪种操作
该结构提包含的数据类型
{
 sem_num; // 要操作集合中的第几个
 sem_op; // 一般有两个值,-1 带表减 1 操作,1 代表加 1 操作
 sem_flg; // 取0,操作不成功阻塞,取IPC_NOWAIT不阻塞直接返回
}
返回值 : 成功返回0, 失败返回-1,错误原因存于error中

5) 信号量集不再使用时, 销毁信号量集

semctl函数配合IPC_RMID参数使用

总结:

(1) 共享内存是最快的 IPC 访问机制, 适用于进程间传递大批量的数据, 但自行编程控制多进程队共享内存的竞争访问, 一般使用信号量解决

(2) 如果进行间传递的数据具有很强的数据性, 可以考虑消息队列实现

(3) 进程间的同步更多的是考虑信号量机制

(4)操作共享内存、 消息队列、 信号量集的命令

ipcs -c 列出系统中共享内存、 消息队列、 信号量集

ipcrm -M key 删除指定的共享内存

ipcrm -Q key 删除指定的消息队列

相关推荐

“版本末期”了?下周平衡补丁!国服最强5套牌!上分首选

明天,酒馆战棋就将迎来大更新,也聊了很多天战棋相关的内容了,趁此机会,给兄弟们穿插一篇构筑模式的卡组推荐!老规矩,我们先来看10职业胜率。目前10职业胜率排名与一周前基本类似,没有太多的变化。平衡补丁...

VS2017 C++ 程序报错“error C2065:“M_PI”: 未声明的标识符&quot;

首先,程序中头文件的选择,要选择头文件,在文件中是没有对M_PI的定义的。选择:项目——>”XXX属性"——>配置属性——>C/C++——>预处理器——>预处理器定义,...

东营交警实名曝光一批酒驾人员名单 88人受处罚

齐鲁网·闪电新闻5月24日讯酒后驾驶是对自己和他人生命安全极不负责的行为,为守护大家的平安出行路,东营交警一直将酒驾作为重点打击对象。5月23日,东营交警公布最新一批饮酒、醉酒名单。对以下驾驶人醉酒...

Qt界面——搭配QCustomPlot(qt platform)

这是我第一个使用QCustomPlot控件的上位机,通过串口精确的5ms发送一次数据,再将读取的数据绘制到图表中。界面方面,尝试卡片式设计,外加QSS简单的配了个色。QCustomPlot官网:Qt...

大话西游2分享赢取种族坐骑手办!PK趣闻录由你书写

老友相聚,仗剑江湖!《大话西游2》2021全民PK季4月激燃打响,各PK玩法鏖战齐开,零门槛参与热情高涨。PK季期间,不仅各种玩法奖励丰厚,参与PK趣闻录活动,投稿自己在PK季遇到的趣事,还有机会带走...

测试谷歌VS Code AI 编程插件 Gemini Code Assist

用ClaudeSonnet3.7的天气测试编码,让谷歌VSCodeAI编程插件GeminiCodeAssist自动编程。生成的文件在浏览器中的效果如下:(附源代码)VSCode...

顾爷想知道第4.5期 国服便利性到底需优化啥?

前段时间DNF国服推出了名为“阿拉德B计划”的系列改版计划,截至目前我们已经看到了两项实装。不过关于便利性上,国服似乎还有很多路要走。自从顾爷回归DNF以来,几乎每天都在跟我抱怨关于DNF里面各种各样...

掌握Visual Studio项目配置【基础篇】

1.前言VisualStudio是Windows上最常用的C++集成开发环境之一,简称VS。VS功能十分强大,对应的,其配置系统较为复杂。不管是对于初学者还是有一定开发经验的开发者来说,捋清楚VS...

还嫌LED驱动设计套路深?那就来看看这篇文章吧

随着LED在各个领域的不同应用需求,LED驱动电路也在不断进步和发展。本文从LED的特性入手,推导出适合LED的电源驱动类型,再进一步介绍各类LED驱动设计。设计必读:LED四个关键特性特性一:非线...

Visual Studio Community 2022(VS2022)安装图文方法

直接上步骤:1,首先可以下载安装一个VisualStudio安装器,叫做VisualStudioinstaller。这个安装文件很小,很快就安装完成了。2,打开VisualStudioins...

Qt添加MSVC构建套件的方法(qt添加c++11)

前言有些时候,在Windows下因为某些需求需要使用MSVC编译器对程序进行编译,假设我们安装Qt的时候又只是安装了MingW构建套件,那么此时我们该如何给现有的Qt添加一个MSVC构建套件呢?本文以...

Qt为什么站稳c++GUI的top1(qt c)

为什么现在QT越来越成为c++界面编程的第一选择,从事QT编程多年,在这之前做C++界面都是基于MFC。当时为什么会从MFC转到QT?主要原因是MFC开发界面想做得好看一些十分困难,引用第三方基于MF...

qt开发IDE应该选择VS还是qt creator

如果一个公司选择了qt来开发自己的产品,在面临IDE的选择时会出现vs或者qtcreator,选择qt的IDE需要结合产品需求、部署平台、项目定位、程序猿本身和公司战略,因为大的软件产品需要明确IDE...

Qt 5.14.2超详细安装教程,不会来打我

Qt简介Qt(官方发音[kju:t],音同cute)是一个跨平台的C++开库,主要用来开发图形用户界面(GraphicalUserInterface,GUI)程序。Qt是纯C++开...

Cygwin配置与使用(四)——VI字体和颜色的配置

简介:VI的操作模式,基本上VI可以分为三种状态,分别是命令模式(commandmode)、插入模式(Insertmode)和底行模式(lastlinemode),各模式的功能区分如下:1)...

取消回复欢迎 发表评论: