6. 命名管道
命名管道可以实现两个毫不相干的进程之间的通信
(一)创建一个命名管道
- 命令行上创建
mkfifo + 管道名
- 函数调用
int mkfifo(const char *filename,mode_t mode)
filename:
文件名
mode:
权限
代码
(二)匿名管道与命名管道的区别
- 匿名管道由pipe函数创建并打开 ;命名管道由mkfifo函数创建,打开用open
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义
(三)命名管道的打开规则
命名管道创建后,必须需要既有 进程以读方式打开命名管道,又有进程以写方式打开命名管道(例如,如果只有读方式打开了,open会阻塞,只有别的进程以写方式打开,读的进程才会继续向后执行代码)
7. system V共享内存
共享内存区是最快的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 */
};
(二)共享内存函数
shmget函数
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字 (保证进程看到的是同一份共享内存,且在操作系统内标定唯一性)
size:创建的共享内存大小 (单位字节)
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的 (标定如何创建共享内存)
返回值 shmid:成功返回一个非负整数,即该共享内存段的标识码(进程内,标定资源的唯一性);失败返回-1
注意:
共享内存的大小一般建议设置成4096(4KB)的整数倍
- 更详细讲 参数shmflg
- IPC_CREAT(单独使用):创建一块共享内存,如果没有,就申请;如果有,就获取并返回
- IPC_CREAT | IPC_EXCL : 创建一块共享内存,没有就申请,如果已经有了,就出错返回(确保获取到的共享内存一定是新申请的)
注意:
IPC_EXCL不单独使用
- 更详细讲 参数key
- key是一个数字,它在内核中具有唯一性,能让不同的进程进行唯一性标识
- 第一个进程可以通过key创建共享内存,第二个进程只要拿着key,就能找到和第一个进程访问一样的共享内存
- 对于一个已经创建好的共享内存,key在共享内存描述对象中
ftok函数
功能:得到key
原型
int ftok(const char* pathname,int proj_id);
参数
pathname: 路径
proj_id: 用户自己定义id
返回值:返回 key ;失败返回-1 , 如果失败,则表示已经有对应key的共享内存,把 proj_id换一下即可
注意:
这里有一套算法,将 pathname 和 proj_id 进行数值计算得到 key
shmat函数
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址 ,可以设置称NULL
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY ,也可以直接设置成0
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
最简单的方法,设置 shmaddr = NULL , shmflg = 0
- 更详细讲 参数shmaddr
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍 公式:shmaddr - (shmaddr % SHMLBA)
shmflg = SHM_RDONLY,表示连接操作用来只读共享内存
shmdt函数
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:
将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的 模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
- ipcs -m
显示已有的共享内存信息
- ipcrm -m + shmid
删除对应共享内存标识符的共享内存
(三)共享内存的特性
- 共享内存没有同步互斥之类的保护机制
- 共享内存是所有的进程间通信中最快的
- 共享内存的数据,是由用户自己维护的(记得要去关联和释放)
(四)共享内存存在的一些问题
当存在两个进程,进程A正在向共享内存写入数据,写入一部分时,B拿走了,导致双方发送和接受的数据不完整
- 两个进程看到同一份资源 ------ 共享资源,如果不加保护,会导致数据不一致的问题
- 任何时刻,只允许一个执行流访问一个共享资源时,叫做加锁
- 任何时刻,只有一个执行流访问一个共享资源叫临界资源
- 访问临界资源的代码叫做临界区
8. system V消息队列
(一)画图了解消息队列
- ipcs -q
查看已存在的消息队列
- icprm -q + msqid
删除该消息队列标识符的消息队列
(二)消息队列函数(简写)
msgget函数
功能:创建消息队列
原型:
int msgget(key_t key,int msgflg);
msgctl函数
功能:用于控制消息队列
原型:
int msgctl(int msqid,int cmd,struct msqid_ds *buf);
注意:
IPC在内核中的数据结构设计,在操作系统中,所有的IPC资源都是整合进操作系统的IPC模块中
(所有IPC数据结构都有 ipc_perm 类型的成员变量,这些都被放进 struct ipc_perm* 的数组里面管理起来,数组的下标对应的就是 xxx标识符)
9. system V信号量
(一)理解信号量
信号量/信号灯本质是一把计数器,用来描述临界资源中资源数量的多少
(二)信号量总结点
注意:
- 申请信号量资源成功,就代表该进程具有访问资源的权限
- 申请了信号量资源,不代表就一定访问了该资源,只是对这个资源的预定机制
- 信号量可以有效保证共享资源的执行流的数量
- 每一个执行流,想要访问共享资源中的一部分时,不是直接访问,而是先申请信号量资源
- 把值为1,0的"计数器"叫做 二元信号量 -------- 本质就是一个锁
- 申请信号量,本质是对计数器减减,即P操作;释放信号量,本质是对计数器加加,即V操作
- 申请和释放都是原子的(即要么做完,要么不做)
- 信号量本质上也是一种共享资源,所以要先保护好自己,才能对别的临界资源进行保护
- 信号量也是进程通信的一种:通信不仅仅是通信数据,互相协同也是