技术标签: 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.6k次。安装配置gi、安装数据库软件、dbca建库见下:http://blog.csdn.net/kadwf123/article/details/784299611、检查集群节点及状态:[root@rac2 ~]# olsnodes -srac1 Activerac2 Activerac3 Activerac4 Active[root@rac2 ~]_12c查看crs状态
文章浏览阅读1.3w次,点赞45次,收藏99次。我个人用的是anaconda3的一个python集成环境,自带jupyter notebook,但在我打开jupyter notebook界面后,却找不到对应的虚拟环境,原来是jupyter notebook只是通用于下载anaconda时自带的环境,其他环境要想使用必须手动下载一些库:1.首先进入到自己创建的虚拟环境(pytorch是虚拟环境的名字)activate pytorch2.在该环境下下载这个库conda install ipykernelconda install nb__jupyter没有pytorch环境
文章浏览阅读5.2k次,点赞19次,收藏28次。选择scoop纯属意外,也是无奈,因为电脑用户被锁了管理员权限,所有exe安装程序都无法安装,只可以用绿色软件,最后被我发现scoop,省去了到处下载XXX绿色版的烦恼,当然scoop里需要管理员权限的软件也跟我无缘了(譬如everything)。推荐添加dorado这个bucket镜像,里面很多中文软件,但是部分国外的软件下载地址在github,可能无法下载。以上两个是官方bucket的国内镜像,所有软件建议优先从这里下载。上面可以看到很多bucket以及软件数。如果官网登陆不了可以试一下以下方式。_scoop-cn
文章浏览阅读4.5k次,点赞2次,收藏3次。首先要有一个color-picker组件 <el-color-picker v-model="headcolor"></el-color-picker>在data里面data() { return {headcolor: ’ #278add ’ //这里可以选择一个默认的颜色} }然后在你想要改变颜色的地方用v-bind绑定就好了,例如:这里的:sty..._vue el-color-picker
文章浏览阅读640次。基于芯片日益增长的问题,所以内核开发者们引入了新的方法,就是在内核中只保留函数,而数据则不包含,由用户(应用程序员)自己把数据按照规定的格式编写,并放在约定的地方,为了不占用过多的内存,还要求数据以根精简的方式编写。boot启动时,传参给内核,告诉内核设备树文件和kernel的位置,内核启动时根据地址去找到设备树文件,再利用专用的编译器去反编译dtb文件,将dtb还原成数据结构,以供驱动的函数去调用。firmware是三星的一个固件的设备信息,因为找不到固件,所以内核启动不成功。_exynos 4412 刷机
文章浏览阅读2w次,点赞24次,收藏42次。Linux系统配置jdkLinux学习教程,Linux入门教程(超详细)_linux配置jdk
文章浏览阅读3.3k次,点赞5次,收藏19次。xlabel('\delta');ylabel('AUC');具体符号的对照表参照下图:_matlab微米怎么输入
文章浏览阅读119次。顺序读写指的是按照文件中数据的顺序进行读取或写入。对于文本文件,可以使用fgets、fputs、fscanf、fprintf等函数进行顺序读写。在C语言中,对文件的操作通常涉及文件的打开、读写以及关闭。文件的打开使用fopen函数,而关闭则使用fclose函数。在C语言中,可以使用fread和fwrite函数进行二进制读写。 Biaoge 于2024-03-09 23:51发布 阅读量:7 ️文章类型:【 C语言程序设计 】在C语言中,用于打开文件的函数是____,用于关闭文件的函数是____。
文章浏览阅读3.4k次,点赞2次,收藏13次。跟随鼠标移动的粒子以grid(SOP)为partical(SOP)的资源模板,调整后连接【Geo组合+point spirit(MAT)】,在连接【feedback组合】适当调整。影响粒子动态的节点【metaball(SOP)+force(SOP)】添加mouse in(CHOP)鼠标位置到metaball的坐标,实现鼠标影响。..._touchdesigner怎么让一个模型跟着鼠标移动
文章浏览阅读178次。项目运行环境配置:Jdk1.8 + Tomcat7.0 + Mysql + HBuilderX(Webstorm也行)+ Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。项目技术:Springboot + mybatis + Maven +mysql5.7或8.0+html+css+js等等组成,B/S模式 + Maven管理等等。环境需要1.运行环境:最好是java jdk 1.8,我们在这个平台上运行的。其他版本理论上也可以。_基于java技术的停车场管理系统实现与设计
文章浏览阅读3.5k次。前言对于MediaPlayer播放器的源码分析内容相对来说比较多,会从Java-&amp;gt;Jni-&amp;gt;C/C++慢慢分析,后面会慢慢更新。另外,博客只作为自己学习记录的一种方式,对于其他的不过多的评论。MediaPlayerDemopublic class MainActivity extends AppCompatActivity implements SurfaceHolder.Cal..._android多媒体播放源码分析 时序图
文章浏览阅读2.4k次,点赞41次,收藏13次。java 数据结构与算法 ——快速排序法_快速排序法