Linux下的进程间通信(2)

消息队列
消息队列是内核地址空间中的内部链表,通过linux内核在各个进程之间传递内容,
消息顺序地发送到消息队列中,并且以几种不同的方式,从队列中获取,每个消息队列可以用IPC标识符唯一的进行标识,内核中的消息队列是通过IPC的标识符来区别的,不同的消息队列之间是相互独立的,每个消息队列中的消息又构成一个独立的链表。
消息队列提供了一种从一个进程向另一个进程发送一个数据块的方法。
每个数据块都被认为含有一个类型,接收进程可以独立地接收含有不同类型的数据结构。我们可以通过发送消息来避免命名管道的同步和阻塞问题。但是消息队列与命名管道一样,每个数据块都有一个最大长度的限制。
?ipcs和ipcrm命令
ipcs提供IPC设备
ipcs -m  查看系统共享内存信息
ipcs -q  查看系统消息队列信息
ipcs -s  查看系统信号量信息
ipcs [-a] 系统默认输出信息,显示系统内所有的IPC信息的信息
ipcrm
通过指定ID删除删除IPC资源,同时将与IPC对象关联的数据一并删除,只有超级用户或IPC资源创建者能够删除
ipcrm -M shmkey
    移除用shmkey创建的共享内存段
ipcrm -m shmid
    移除用shmid标识的共享内存段
ipcrm -S semkey
    移除用semkey创建的信号量
ipcrm -s semid
    移除用semid标识的信号量
ipcrm -Q msgkey
    移除用msgkey创建的消息队列
ipcrm -q msgid
    移除用msgid标识的消息队列
    
msgget:获取消息队列的标识符
创建和访问一个消息队列
int msgget(key_t key, int msgflag)
key:某个消息队列的名字,用ftok()产生 (函数ftok的返回值)或IPC_PRIVATE
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

根据路径和proj_id生成,key(唯一性)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
读写不一致在这里插入图片描述
在这里插入图片描述在这里插入图片描述

把一条消息添加到消息队列中
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
是从一个消息队列接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
消息队列与命名管道的比较

相同点:
1、同命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。
2、不管是命名管道还是消息队列,它们对每个数据都有一个最大长度的限制。
不同点:
在命名管道中,发送数据用write,接收数据用read,而在消息队列中,发送数据用msgsnd,接收数据用msgrcv。
与命名管道相比,消息队列的优势在于:
1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。
2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。
3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。
共享内存
它是不同进程之间共享的内存区域
共享内存是在多个进程之间共享内存区域的一种进程间的通信方式,它是在多个进程之间对内存段进行映射的方式实现内存共享的
是IPC最快的方式
共享内存方式直接将某段内存段进行映射,多个进程间的共享内存是同一块的物理空间,仅仅是地址不同而已,因此不需要进行复制,可以直接使用此段空间

?共享内存数据结构
struct 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 */
};
?共享内存操作函数
共享内存是通过把同一块内存分别映射到不同的进程空间中实现进程间通信。而共享内存本身不带任何互斥与同步机制,由用户实现同步和互斥(例如信号量)
获取内存页大小
getpagesize()
int shmget(key_t key, size_t size, int shmflg);
创建一块共享内存
key 共享内存在系统中的唯一键值
size 共享内存大小
shmflg :
IPC_CREAT 如果共享内存不存在则创建
IPC_EXCL 如果共享内存存在就报错
void *shmat(int shmid, const void *shmaddr, int shmflg);
将共享内存映射进自己的进程空间
shmid 共享内存的上下文标识符
shmaddr 共享内存的起始映射地址,通常设置为空,让系统为我们自动分配
shmflg
SHM_RDONLY 共享内存的权限(只读)
0 同时具有读写权限
int shmdt(const void *shmaddr);
对共享内存解除映射
shmaddr shmat返回的起始映射地址

?nt shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid 共享内存的上下文标识符
cmd:
IPC_RMID 删除共享内存(共享内存这样删除之后,系统并不会立即将之删除,而是会阻止其他进程再次映射共享内存, 知道没有其他进程使用这块共享内存的时候,才会删除)
?共享内存的特点
(1)共享内存就是允许两个不相关的进程访问同一个内存
(2)共享内存是两个正在运行的进程之间共享和传递数据的最有效的方式
(3)不同进程之间共享的内存通常安排为同一段物理内存
(4)共享内存不提供任何互斥和同步机制,一般用信号量对临界资源进行保护。
(5)接口简单
在这里插入图片描述
第一列就是共享内存的key;
第二列是共享内存的编号shmid;
第三列就是创建的用户owner;
第四列就是权限perms;
第五列为创建的大小bytes;
第六列为连接到共享内存的进程数nattach;
第七列是共享内存的状态status。
其中显示“dest”表示共享内存段已经被删除,但是还有用户在使用它,当该段内存的mode字段设置为 SHM_DEST时就会显示“dest”。当用户调用shmctl的IPC_RMID时,内存先查看多少个进程与这个内存关联着,如果关联数为0,就会销 毁这段共享内存,否者设置这段内存的mod的mode位为SHM_DEST,如果所有进程都不用则删除这段共享内存。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
终端1在这里插入图片描述
终端2在这里插入图片描述
在这里插入图片描述

当我们结束读进程后,观察到nattch列值编程了1,表示此时只有一个进程在使用共享内存

在这里插入图片描述
在这里插入图片描述

当我们再结束写进程后,观察到nattch列值编程了0,表示此时没有进程在使用共享内存

shmget
IPC_CREAT|IPC_EXCL : 如果创建的共享内存已经存在,这个返回-1
IPC_CREAT:如果创建的共享内存已经存在则直接放回shmid,如果不存在就创建
shmctl
IPC_RMID :删除共享内存

shmat 将共享内存附加到当前进程,以后就可以访问这个共享内存了
shmdt 将共享内存从当前进程解除,以后就无法再访问这个共享内存了

信号量
操作系统中有个PV操作
信号量是一种计数器,用来控制对多个进程共享的资源所进行的访问。
信号量是用来调协进程对共享资源的访问的。其中共享内存的使用就要用到信号量。
简单的说,信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量
PV操作:
P减,是申请资源的过程
V加,是释放资源的过程。
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1
信号量相关的数据结构:
内核为每个信号量集合设置了一个semid_ds结构
struct semid_ds {
struct ipc_perm sem_perm;
struct sem* sem_base; //信号数组指针
ushort sem_nsem; //此集中信号个数
time_t sem_otime; //最后一次semop时间
time_t sem_ctime; //最后一次创建时间
};
相关函数:
int semget(key_t key, int nsems, int semflg);
创建一个信号量,并返回一个上下文操作的信号量id
key 信号量的唯一标识
nsems 要创建的信号量个数
semflg 选项标准:
IPC_CREAT 如果信号量不存在则创建
IPC_EXCL 如果信号量存在则报错
semctl(sem_id, 0, SETVAL, val)
初始化信号量(为信号量赋予初值)
int semop(int semid, struct sembuf *sops, unsigned nsops);
信号量可以理解为对一个数据进行 +1 -1操作,当信号量为0的时候则不能获取信号量,每次操作之前为信号量进行-1操作,是为了告诉其他进程现在信号量正在被使用, 操作完毕之后需要将信号量进行+1操作,这样其他进程才能获取到信号量
semid 信号量上下文的标识id
struct sembuf
sem_num 信号量编号
sem_op 对信号量要进行的操作 +1 -1
sem_flg SEM_UNDO(为了是程序在不正常退出时,恢复信号量)
nsops 最大操作数,不能超过信号量的个数
semctl(sem_id, 1, IPC_RMID, NULL);
删除信号量,
int semctl(int semid, int semnum, int cmd, …);
cmd:
IPC_RMID 用于删除信号量
GETALL 获取所有信号量的值
GETVAL 获取单个信号量的值
SETALL 设置所有信号量的值
SETVAL 设置单个信号量的值
信号量的使用规则(重点):
1:若信号量为正,则进程可使用该资源。
2:若信号量为0,则进程阻塞等待,并将进程插入等待队列,直到该信号量的值大于0从等待队列中执行进程请求。
3:加锁操作(P):如果信号量大于0,则信号量-1;如果信号量为0,则挂起该进程,并将这个进程插入等待队列。
4:解锁操作(V):如果等待队列中有进程则唤醒该进程,让它恢复运行;否则,信号量+1。
在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值