技术标签: Linux 运维 进程间通信 linux 共享内存 管道 开发语言
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间(匿名管道),完成数据传递。调用pipe系统函数即可创建一个匿名管道。
有如下特质:
ps: 管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道特点
只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
管道提供流式服务
一般而言,进程退出,管道释放,所以管道的生命周期随着进程
一般而言,内核会对管道操作进行同步与互斥 (互斥:同一时间要么写要么读)
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道
管道的局限性
管道读写规则
总而言之:
管道是最简单,效率最差的一种通信方式。
管道本质上就是内核中的一个缓存,当进程创建一个管道后,Linux会返回两个文件描述符,一个是写入端的描述符,一个是输出端的描述符,可以通过这两个描述符往管道写入或者读取数据。
如果想要实现两个进程通过管道来通信,则需要让创建管道的进程fork子进程,这样子进程们就拥有了- 父进程的文件描述符,这样子进程之间也就有了对同一管道的操作。
缺点:
pipe
头文件: unistd.h
int pipefd[2]={
0};
int pipe(int pipefd[2]);
pipe函数的应用
#include<iostream>
#include<cassert>
#include<unistd.h>
#include<cstdio> //等价于 stdio.h
#include<cstring> //等价于 string.h 以c++的风格将c语言的string头文件改写
int main()
{
int pipefd[2]={
0};
int n = pipe(pipefd); //创建管道
assert(n == 0); //在debug下,assert会存在 realease 下,该行代码不起作用
(void)n; //防止编译器在realease下告警 n被定义未使用
cout<<"pipefd[0]:" <<pipefd[0]<<", pipefd[1]:"<<pipefd[1]<<endl;
return 0;
}
创建匿名管道图文详解:
父进程调用pipe函数
fork创建子进程
子进程关闭读端,父进程关闭写端
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件
注意:管道文件是以 p 开头的,如图:
创建命名管道
命令行上创建
#include <sys/stat.h> //头文件
mkfifo filename //创建一个FIFO命名管道文件
命名管道可以从命令行上创建
程序内创建
//命名管道也可以从程序里创建
int mkfifo(const char *filename,mode_t mode);
// 文件名 文件权限
int main(int argc, char *argv[])
{
mkfifo("p2", 0644);
return 0;
}
功能: 创建一命名管道
参数:
返回值:
命名管道的特性
删除管道文件可以用如下命令
unlink fifo //删除管道文件
rm -f fifo
管道通信本质是基于文件的,也就是说操作系统并没有为此做过多的设计工作,而system V IPC是操作系统特地设计的一种通信方式。但是不管怎么样,它们的本质都是一样的,都是在想尽办法让不同的进程看到同一份资源。
system V IPC提供的通信方式有以下三种:
其中,system V共享内存和system V消息队列是以传送数据为目的的,而system V信号量是为了保证进程间的同步与互斥而设计的,虽然system V信号量和通信好像没有直接关系,但属于通信范畴。
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到系统内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据,减少了拷贝的次数,加快的程序的运行。
共享内存示意图
共享内存数据结构
truct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
共享内存本质上就是内存中的一块区域,用于进程间通信使用。该内存空间由操作系统分配与管理。与文件系统类似的是,操作系统在管理共享内存时,不仅仅有内存数据块,同时还会创建相应结构体来记录该共享内存属性,以便于管理。
因此,共享内存不只有一份,可以根据需求申请多个。
进程之间进行通信的时候,会获取 到共享内存的地址,写端进程写入数据,读端进程通过直接访问内存完成数据读取。
共享内存优点
相比于管道而言,共享内存不仅能够用于非父子进程之间的通信,而且访问数据的速度也比管道要快。这得益于通信直接访问内存,而管道则需要先通过操作系统访问文件再获得内存数据。
共享内存缺点
用于进程间通信时,共享内存本身不支持阻塞等待操作。这是因为当读端读取数据后,数据并不会在内存中清空。因此读端和写端可以同时访问内存空间,即全双工。因为共享内存本质是进程直接访问内存,无法主动停止读取,如果读端不加以限制,那么将持续读取数据。同理,写端也会持续写入数据。换句话说,共享内存本身没有访问控制。
共享内存**没有提供同步的机制**,这使得我们在使用共享内存进行进程间通信时,往往要借助其他的手段来进行进程间的同步工作。
其他注意事项
头文件:
#include <sys/ipc.h>
#include <sys/shm.h>
ftok
函数 ftok 把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键值(也称IPC key键值)。该 IPC 为后续创建共享内存做准备。
该函数需要包含以下两个头文件:
#include <sys/types.h>
#include <sys/ipc.h>
fork函数原型
//把从pathname导出的信息与id的低序8位组合成一个整数IPC键
key_t ftok(const char *pathname, int proj_id);
//pathname:指定的文件,此文件必须存在且可存取
//proj_id:计划代号(project ID)
返回值(key_t 一般为32位的 int 型的重定义):
shmget
int shmget(key_t key, size_t size, int shmflg);
//示例
int main()
{
key_t n = ftok("/Linux/code",0x11223344);
int id = shmget(n , 4096 , IPC_CREAT | IPC_EXCL | 0664);
}
功能:用来创建共享内存
参数
key: 这个共享内存段名字(IPC 值,即key_t )
size: 共享内存大小,一般设置为 4096 的倍数
shmflg: 创建共享内存的方式以及设置权限,由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
//IPC_CREAT 可以单独使用
//如果共享内存不存在,则开辟内存,函数返回值是新开辟的共享内存的ID
//如果已经存在,则沿用已有的共享内存,函数返回值是已有的共享内存的
//IPC_EXCL 无法单独使用
//需要配合 IPC_CREAT 使用,即 IPC_CREAT | IPC_EXCL
//IPC_CREAT | IPC_EXCL
//如果共享内存不存在,则重新开辟,函数返回值是新开辟的共享内存的ID
//如果已经存在,则报错
//IPC_CREAT | IPC_EXCL | 0664
//开辟共享内存的同时,设置共享内存的访问权限
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
shmat
void *shmat(int shmid, const void *shmaddr, int shmflg);
// 一般为nullptr 一般为0
//示例
char* = (char*)shmat(shmid,nullptr,0);
功能:挂接共享内存至进程
参数:
shmid: 通过 shmget 创建的共享内存的返回值
shmaddr: 指定挂接地址, 通常传入 nullptr 即可, 让OS自动选择挂接地址
shmflg: 通常为0即可
返回值:
shmdt
int shmdt(const void *shmaddr);
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
// IPC_RMID nullptr
功能:⽤于控制共享内存
参数:
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
// IPC_ STAT:把shmid_ ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值
//IPC_ SET:在进程有足够权限的前提下,把共享内存的当前关联值设置为shmid_ds数据结构中给出的值
//IPC_RMID:删除共享内存段
buf: 指向⼀个保存着共享内存的模式状态和访问权限的数据结构。
返回值:
消息队列是消息的链表,存放在内核中并由消息队列标识符表示。
消息队列提供了一个从一个进程向另一个进程发送数据块的方法,每个数据块都可以被认为是有一个类型,接受者接受的数据块可以有不同的类型。
但是同管道类似,它有一个不足就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数(MSGMNB),系统上消息队列的总数上限(MSGMNI)。可以用cat /proc/sys/kernel/msgmax查看具体的数据。
内核为每个IPC对象维护了一个数据结构struct ipc_perm,用于标识消息队列,让进程知道当前操作的是哪个消息队列。每一个msqid_ds表示一个消息队列,并通过msqid_ds.msg_first、msg_last维护一个先进先出的msg链表队列,当发送一个消息到该消息队列时,把发送的消息构造成一个msg的结构对象,并添加到msqid_ds.msg_first、msg_last维护的链表队列。在内核中的表示如下:
头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
msgget
int msgget(key_t key, int msgflag);
//key =ftok() IPC_CREAT|IPC_EXCL|0666
//例: int msqid = msgget(key, IPC_CREAT | 0666);
功能:创建和访问一个消息队列
参数:
key:某个消息队列的名字,用ftok()产生
msgflag:有两个选项 IPC_CREAT和IPC_EXCL,单独使用IPC_CREAT,如果消息队列不存在则创建之,如果存在则打开返回;单独使用IPC_EXCL是没有意义的;两个同时使用,如果消息队列不存在则创建之,如果存在则出错返回。
返回值:成功返回一个非负整数,即消息队列的标识码,失败返回-1
msgsnd
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
struct msgbuf {
long mtype; /* 标志,代表接收特定标记的数据块 */
char mtext[1]; /* message data */
};
//例:
int send_msg(int msqid, int m_type, char *in)
{
//#define cilent_type 1
struct my_msgbuf buf;
buf.mtype = m_type;
strncpy(buf.mtext, in, sizeof in);
int n = msgsnd(msqid, (void *)&buf, sizeof buf.mtext, 0);
if (n < 0)
{
cerr << "msg_send error! eoorr:" << errno << " strerror:" << strerror(errno) << endl;
exit(1);
}
return 1;
}
功能:把一条消息添加到消息队列中
参数:
返回值:成功返回0,失败返回-1
msgrcv
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgctl
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
例: int n = msgctl(msgid, IPC_RMID, nullptr);
功能:消息队列的控制函数
参数:
msqid:由msgget函数返回的消息队列标识码
cmd:有三个可选的值,在此我们使用IPC_RMID
buf:一般为 nullptr
//IPC_STAT 把msqid_ds结构中的数据设置为消息队列的当前关联值
//IPC_SET 在进程有足够权限的前提下,把消息队列的当前关联值设置为msqid_ds数据结构中给出的值
//IPC_RMID 删除消息队列
返回值:成功返回0,失败返回-1
semget
int semget(key_t key, int num_sems, int sem_flags);
功能:它的作用是创建一个新信号量或取得一个已有信号量。
参数:
key:整数值,可以理解为文件目录索引值、不同于id为该文件id名。
num_sems: 指定需要的信号量数目,它的值几乎总是1。
sem_flags:一组标志,当想要当信号量不存在时创建一个新的信号量 IPC_CREAT|0666
返回值:成功返回一个相应信号量集ID,失败返回-1.
semop
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
功能:它的作用是改变信号量的值,进行PV操作的函数。原型为:
参数:
sem_id: 是由semget返回的信号量标识符,
sem_opa:信号量结构体struct sembuf指针,该指针改变后的信号量
num_sem_ops: struct sembuf变量成员数量
//sembuf结构的定义如下:
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv)
P操作:sem_op= -1 <0,使得共享临界资源上锁,其它进程不得访问
V操作:sem_op= 1 >0,使得共享临界资源解锁,其它进程可以访问
semctl在初始化信号量的时候,union semum结构体中val就是信号量的初始化值,一般==1;
进行PV操作时,semop函数把 struct sembuf结构体中设置好的参数储存,此时val+1或-1,从而达到加锁解锁的目的,控制进程访问。
举个例子,就是两个进程共享信号量sv=val=1,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,sem_op使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。
semctl函数
int semctl(int sem_id, int sem_num, int cmd, ...);
查看相关通信方式
ipcs -m //查看共享内存
ipcs -q //查看消息队列
ipcs -s //查看信号量
① 查看共享内存:
② 查看消息队列:
③ 查看信号量
nattch: 当前有多少进程挂接到该共享内存
删除相关通信方式
ipcrm -m [shmid]
ipcrm -q [msgid]
ipcrm -sv [semid]
ipcrm -a
//使用ipcrm -a选项可以删除所有进程间通信资源
结构代码
struct ipc_ids{
int size; /*entries数组的大小*/
int in_use; /*entries数组已使用的元素个数*/
int max_id;
unsigned short seq;
unsigned short seq_max;
struct semaphore sem; /*控制对ipc_ids结构的访问*/
spinlock_t ary; /*自旋锁控制对数组entries的访问*/
struct ipc_id* entries;
//柔性数组,可以一直扩大大小
//ipc_id_ary [size,*p[0/1/2/3];
// *p[0] *p[1] 指向 shmid_kerne sem_array msg_queue 这些数据结构
};
struct ipc_id{
struct ipc_perm *p;};
ipc_perm
struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
shmid_kerne
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
size_t shm_segsz; /* Size of segment (bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */
...
};
msg_queue
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
struct msqid_ds {
struct ipc_perm msg_perm;
struct msg *msg_first; /* 队列上第一条消息,即链表头*/
struct msg *msg_last; /* 队列中的最后一条消息,即链表尾 */
time_t msg_stime; /* 发送给队列的最后一条消息的时间 */
time_t msg_rtime; /* 从消息队列接收到的最后一条消息的时间 */
time_t msg_ctime; /* 最后修改队列的时间*/
ushort msg_cbytes; /*队列上所有消息总的字节数 */
ushort msg_qnum; /*在当前队列上消息的个数 */
ushort msg_qbytes; /* 队列最大的字节数 */
ushort msg_lspid; /* 发送最后一条消息的进程的pid */
ushort msg_lrpid; /* 接收最后一条消息的进程的pid */
};
sem_array
struct semid_ds {
struct ipc_perm sem_perm; /*IPC权限 */
long sem_otime; /* 最后一次对信号量操作(semop)的时间 */
long sem_ctime; /* 对这个结构最后一次修改的时间 */
struct sem /*sem_base; /* 在信号量数组中指向第一个信号量的指针 */
struct sem_queue *sem_pending; /* 待处理的挂起操作*/
struct sem_queue **sem_pending_last; / * 最后一个挂起操作 */
struct sem_undo *undo; /* 在这个数组上的undo 请求 */
ushort sem_nsems; /* 在信号量数组上的信号量号 */
};
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
struct semid_ds {
struct ipc_perm sem_perm; /* Ownership and permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* No. of semaphores in set */
};
文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js
文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6
文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输
文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...
文章浏览阅读101次。1986年,深度学习(Deep Learning)火爆,它提出了一个名为“深层神经网络”(Deep Neural Networks)的新型机器学习模型。随后几年,神经网络在图像、文本等领域取得了惊艳成果。但是,随着深度学习的应用范围越来越广泛,神经网络在遇到新的任务时出现性能下降或退化的问题。这主要是由于深度神经网络在训练初期面临着“梯度消失”或“梯度爆炸”的问题。_在人工神经网络研究的初始阶段,辛顿针对训练过程中常出现的梯度消失现象, 提供相
文章浏览阅读461次。我们会先使用 ps、top 等命令获得进程的 PID,然后使用 kill 命令来杀掉该进程。killall和pkill是相似的,不过如果给出的进程名不完整,killall会报错。当然我们可以向进程发送一个终止运行的信号,此时的 kill 命令才是名至实归。,这样结束掉的进程不会进行资源的清理工作,所以如果你用它来终结掉 vim 的进程,就会发现临时文件 *.swp 没有被删除。命令:pid of xx进程,显示进程的进程号,同上pgrep。这是 kill 命令最主要的用法,也是本文要介绍的内容。_如何kill掉一个进程
文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答
文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。
文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入
文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf
文章浏览阅读3.2k次,点赞3次,收藏4次。vscode打开markdown文件 不显示图片 预览markdown文件_vscodemarkdown图片无法显示
文章浏览阅读516次。在做ACM题时,经常都会遇到一些比较大的整数。而常用的内置整数类型常常显得太小了:其中long 和 int 范围是[-2^31,2^31),即-2147483648~2147483647。而unsigned范围是[0,2^32),即0~4294967295。也就是说,常规的32位整数只能够处理40亿以下的数。 那遇到比40亿要大的数怎么办呢?这时就要用到C++的64位扩展了。不同的编译器对6_c++ long64