一,无名管道
特点:
- 其本质是一个伪文件(实为内核缓冲区)
- 由两个文件描述符引用,一个表示读端(fd[0]),一个表示写端(fd[1])。
- 规定数据从管道的写端流入管道,从读端流出。
原理:
- 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现
局限性:
- ① 数据自己读不能自己写。
- ② 数据一旦被读走,便不在管道中存在,不可反复读取。
- ③ 由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
- ④ 只能在有公共祖先的进程间使用管道。
pipe函数(在fork函数之前调用)
#include <unistd.h>
int pipe(int pipefd[2]);
参数:pipefd[2]传出参数
返回值:成功:0;失败:-1,设置errno。会返回两个文件描述符
规定:fd[0] → r; fd[1] → w
必须在fork之前调用
无名管道阻塞问题(默认情况是阻塞模式)
①读管道:
1,管道里有数据,则read返回读取实际数据
2,管道里没有数据:
(1)写端关闭,则read返回0,
(2)待写端没有关闭,则read阻塞等。(最开始的情况)
②写管道:
1,管道读端全部关闭,产生SIGPIPE信号,默认是终止进程
2,管道读端没有全部关闭
(1)管道没有空间可写,则阻塞等待,直到有空间为止
(2)管道有空间可写,则write写入,并返回实际个数
设为非阻塞后,之前阻塞的都不会阻塞而是返回一个EAGAIN错误
管道缓冲区大小
可以使用ulimit –a 命令来查看当前系统中创建管道文件所对应的内核缓冲区大小
pipe size (512 bytes, -p) 8 4kb大小
管道的优劣
优点:简单,相比信号,套接字实现进程间通信,简单很多。
缺点:1. 只能单向通信,双向通信需建立两个管道。
2. 只能用于父子、兄弟进程(有共同祖先)间通信。
二,有名管道(FIFO)
FIFO常被称为命名管道,以区分管道(pipe)。管道(pipe)只能用于“有血缘关系”的进程间。但通过FIFO,不相关的进程也能交换数据。
FIFO是Linux基础文件类型中的一种。但,FIFO文件在磁盘上没有数据块,仅仅用来标识内核中一条通道。各进程可以打开这个文件进行read/write,实际上是在读写内核通道,这样就实现了进程间通信。
创建方式:
1,命令:mkfifo 管道名
2,mkfifo函数
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
成功:0; 失败:-1
一旦使用mkfifo创建了一个FIFO,就可以使用open打开它,常见的文件I/O函数都可用于fifo。如:close、read、write、unlink等。
mode:类似于open的第二个参数
mkfifo隐含指定了(O_CREAT|O_EXCL)也就是说,它要么创建一个新的fifo,要么返回一个EEXIST错误(如果所指定名字的FIFO已经存在)。要打开一个已存在的FIFO或创建一个新的FIFO,应该调用mkfifo,再检查它是否返回EEXIST错误,若返回错误就调用open。
三,存储映射I/O
存储映射I/O (Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。不需要用read和write,而是指针的形式操作缓冲区。这个缓冲区进程地址空间的内核区域。
void *mmap(void *adrr, size_t length, int prot, int flags, int fd, off_t offset);
返回:成功:返回创建的映射区首地址;失败:MAP_FAILED宏
参数:
addr: 建立映射区的首地址,由Linux内核指定。使用时,直接传递NULL
length: 欲创建映射区的大小
prot: 映射区权限PROT_READ、PROT_WRITE、PROT_READ|PROT_WRITE
flags: 标志位参数(常用于设定更新物理区域、设置共享、创建匿名映射区)
MAP_SHARED: 会将映射区所做的操作反映到物理设备(磁盘)上。
MAP_PRIVATE: 映射区所做的修改不会反映到物理设备。
fd: 用来建立映射区的文件描述符
offset: 映射文件的偏移(4k的整数倍)
三个作用:
①使用普通文件以提供内存映射I/O
②使用特殊文件以提供匿名映射
③使用shm_open以提供无亲缘关系进程间的Posix共享内存。
munmap函数
#include <sys/mman.h>
int munmap(void *addr, size_t length);
成功:0; 失败:-1
功能:从某个进程的地址空间删除一个映射关系。
msync函数
#include <sys/mman.h>
int msync(void *addr, size_t length, int flags);
addr:映射区的起始地址
length:长度
flags:
MS_ASYNC:执行异步写
MS_SYNC:执行同步写
MS_INVALIDATE:使高速缓存的数据失效
成功0,失败-1.
功能:确信文件内容和映射区数据同步
ftruncate函数
#include <unistd.h>
#include <sys/types.h>
int ftruncate(int fd, off_t length);
返回值,成功返回0,失败返回-1
功能:设置文件或者ipc对象大小
非匿名映射一定要通过文件或者对象名指定大小。
unlink函数
#include <unistd.h>
int unlink(const char *path);
功能:具备删除文件的条件,进程结束后就会删除文件。
如果该文件名为最后连接点,但有其他进程打开了此文件,则在所有关于此文件的文件描述词皆关闭后才会删除。如果有多个硬连接 则数目减1。如果参数pathname为一符号连接,则此连接会被删除。
注意事项:
1,可以用int ftruncate(int fd, off_t length);来指定文件大小,也就是映射区大小
2,创建映射区的过程中,隐含着一次对映射文件的读操作
3,当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE则无所谓,因为mmap中的权限是对内存的限制。
4,映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭(无论有没有血缘关系)。
5, 特别注意,当映射文件大小为0时,不能创建映射区。所以:用于映射的文件必须要有实际大小!! mmap使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。
6,munmap传入的地址一定是mmap的返回地址。坚决杜绝指针++操作
7, 如果文件偏移量必须为4K的整数倍
8,mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作
mmap父子进程通信
通过内存映射文件。open打开文件,mmap映射的方式。
flag的含义有点变化
MAP_PRIVATE: (私有映射) 父子进程各自独占映射区;
MAP_SHARED: (共享映射) 父子进程共享映射区;
结论:父子进程共享:1. 打开的文件 2. mmap建立的映射区(但必须要使用MAP_SHARED)
匿名映射
无需用文件来完成映射区建立。
1,4.4BSD
flag要使用使用MAP_ANONYMOUS (或MAP_ANON)
int *p = mmap(NULL, 4, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
大小可以任意指定,文件描述符用-1代替
2,SVR4
① fd = open("/dev/zero", O_RDWR);
② p = mmap(NULL, size, PROT_READ|PROT_WRITE, MMAP_SHARED, fd, 0);
匿名映射主要用于父子间的进程通信。不需要用文件来完成映射区的建立
四,posix共享内存
posix有两种在无亲缘关系进程间使用共享内存的方法
1,内存映射文件
2,共享内存区对象
共享内存区对象两个步骤:
1,指定一个名字参数调用shm_open,以创建一个新的共享内存区对象或打开一个已存在的共享内存区对象
2,调用mmap把这个共享内存区映射到调用进程的地址空间
shm_open函数
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
int shm_open(const char *name, int oflag, mode_t mode);
name:对象名,创建成功后会在/dev/shm在生成
oflag:和open函数一样
mode:和open函数一样,不需要时给0
返回值:成功返回一个非负描述符,出错则为-1
功能:创建或打开一个共享内存对象
shm_unlink函数
#include <sys/mman.h>
#include <sys/stat.h> /* For mode constants */
#include <fcntl.h> /* For O_* constants */
int shm_unlink(const char *name);
name:共享内存对象名
返回值:成功0,失败-1
功能:和unlink类似,可以删除共享内存区
使用这几个函数时链接时需要 -lrt
五,System V共享内存
对于每个System V共享内存区,内核会维护一个结构体
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 */
...
};
1,ftok函数
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
pathname:路径名
proj_id:为0整不数
返回值:成功返回IPC键,失败返回-1
2,shmget函数
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
key:可以是ftok返回值,也可以指定IPC_PRIVATE
size:创建新的共享内存时,必须指定不为0的size,如果是访问就为0
shmflg:和open函数有点不同以IPC_xxx.逻辑差不多,还包括权限值
IPC_xxx.:只限于IPC_CREAT,IPC_EXCL.权限:SHM_W,SHM_R等
注意不存在PIC_RDONLY,PIC_WRONLY,
返回值:成功返回一个标识符,失败-1
功能:创建或访问一个共享内存区
3,shmat函数
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmid:共享内存标示符
shmaddr:传NULL,由系统替调用者选择地址
shmflg:一般为0,SHM_RDONLY只读,SHM_WRONLY只写
返回值:成功为映射区的起始地址,失败-1
功能:附接到调用进程的地址空间。(和mmap功能3相似)
默认情况下(0)只要调用进程具有某个共享内存的读写权限,它附接该内存区后就能同时读写该内存区。,shmflg也可以指定SHM_RDONLY。(亲测SHM_R不会报错)
4,shmdt函数
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
shmaddr:地址
返回值:成功 返回0 失败 -1
功能:进程使用完某个共享内存时,用shmdt函数断接这个内存区。但不是删除
其实当一个进程终止时,它当前附接的所有共享内存都自动断接掉。
5,shmctl函数
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:共享内存区描述符
cmd:
IPC_STAT:向调用者返回所指定共享内存区当前的shmid_ds结构
IPC_SET :设置struct shmid_ds中的ipc_perm.uid,ipc_perm.gid,
ipc_perm.mode
IPC_RMID :从系统中删除由shmid标识的共享内存区并拆除它
buf:传出参数。
功能:对一个共享内存区的多种操作