内存映射,键值含义,共享内存,消息队列,信号量
1.内存映射:
概念:
系统在调用进程的虚拟地址空间创建一个新内存映射
文件映射:
将文件映射到进程空间得到一个可操作的地址,
对地址操作就是对文件直接读写
属性:
私有映射
MAP_PRIVATE
在映射内容上发生的变化对其他进程不可见
对文件映射来说不会影响底层文件
共享映射
MAP_SHARED
在映射内容上发生的变化对其他进程可见
对文件映射来说会影响底层文件
映射mmap:
功能
内存映射函数mmap, 负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。
原型
void *mmap(void *start, size_t length, int prot, int flags,int fd, off_t offset);
参数
start:指定映射到进程空间的地址,指定为NULL表示系统自动分配
length:指定映射的文件长度,单位是 以字节为单位,不足一内存页按一内存页处理
prot:期待获取的操作,需要两个权限的可以用或|
PROT_EXEC映射区可被执行
PROT_READ映射区可被读取
PROT_WRITE映射区可被写入
PROT_NONE映射区不可访问
flags标志
MAP_SHARED对此区域所做的修改内容将写入文件内;允许其他映射该文件的进程共享
MAP_PRIVATE:对此区域所做的修改不会更改原来的文件内容,对映射区的写入操作会产生一个映射区的复制
fd文件描述符要映射的文件
offset:文件起始偏移量
设置为0
返回值
成功返回映射成功后的地址
失败返回-1
注意
权限
解除munmap:
原型:
int munmap(void *start, size_t length);
解除映射操作
内存操作函数:
操作一个内存块
memcpy
内存拷贝函数
void *memcpy(void *dest, const void *src, size_t n);
目标地址
源地址
复制内容大小
返回一个指向dest的指针
memcpy(p,“hello”,6);
write();
memset
对一个内存块进行整体的赋值常用清空内存块
memset(char *p,int num,int size);
内存中某个首地址
需要赋的值
需要赋的字节数大小 (memset(a,0,sizeof(a))):清零
memset(p,0,10);
注意:
只能对已有的文件内容做修改:
代码演示:
#include <stdio.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
int fd = open("./demo1.c",O_RDWR);//不能打开时新建,因为新文件大小为0
//int fd = open("./demo1.c",O_RDWR|O_CREAT,0777);
int len= lseek(fd,0,SEEK_END);
char *p=mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,0);
//char *p=mmap(NULL,len,PROT_WRITE,MAP_PRIVATE,fd,0);//映射大小不能为0
if(p==(char *)-1)
{
perror("mmap");
return 0;
}
memcpy(p,"hell0 world",12);//覆盖前12字节
//memset(p,'1',20);//写入时:是将整形转化为 char
//strcpy(p,"hello");
//printf("%s\n",p);//%s遇/0结束
//printf("%s\n",p+12);
return 0;
}
说明:
char *p=mmap(NULL,len,PROT_WRITE,MAP_SHARED,fd,0);
char *p=mmap(NULL,len,PROT_WRITE,MAP_PRIVATE,fd,0);//映射大小不能为0
1.共享映射可以改变,私有映射无法改变(但能够打印出来),并且映射的内容大小不能为0;
2.必须对已有的文件进行映射,且已有文件大小>0,映射内容的大小可以超过文件的大小,不能打开时候新建,因为新建文件大小为0,会映射失败。
int fd = open("./demo1.c",O_RDWR|O_CREAT,0777);
//错误
3.memset:写入时:将整型转化为 char(0不需要)
memset(p,'1',20);
2.键值含义:
定义:
用于标识共享内存、消息队列以及信号量集
保证进程打开的共享内存、消息队列、信号量集是同一个
获取键值:
ftok
功能
获取键值
原型
key_t ftok(const char *pathname, int proj_id);
所属头文件
# include <sys/types.h>
# include <sys/ipc.h>
参数
pathname就时你指定的文件名(该文件必须是存在而且可以访问的)
proj_id是子序号,虽然为int,但是只有8个比特被使用(0-255)
返回值
当成功执行的时候,一个key_t值将会被返回,否则 -1 被返回
3.共享内存:
指令:
ipcs -m //看Shared Memory 共享内存
ipcs -s //看Semaphore Arrays 信号量
ipcs -q //看Message Queues 消息队列
定义:
在操作系统内核开辟一个空间,每一个进程都可以将这个空间映射到自己的进程空间里面,多个进程可以共享这块内存,对他的操作都可以立即实现。
过程:
创建共享内存
进程1映射内存进行读写
进程2映射共享内存进行读写
进程1、进程2 解除映射
删除共享内存
相关API:
创建:
功能
用来获得共享内存区域的ID,如果不存在指定的共享区域就创建相应的区域
原型
int shmget(key_t key, size_t size, int shmflg);
头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
参数
key:键值
IPC_PRIVATE//键值为0 表示创建的共享内存只能父子间用
ftok()//任意进程都可以用
size:大小
shmflg:标志(IPC_CREAT没有就创建)同 open 函数的权限位,也可以用八进制表示法
返回值
成功返回内存段标识符,失败-1
映射:
功能
把共享内存区域映射到调用进程的地址空间中去
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
参数
shmid要映射的共享内存区标识符
shmaddr:将共享内存映射到指定位置(若为 0 则表示把该段共享内存映射到调用进程的地址空间
shmflg:默认 0:共享内存可读写
SHM_RDONLY:共享内存只读
返回值
成功:被映射的段地址,失败返回-1
解除:
功能
调用用来解除进程对共享内存区域的映射
原型
int shmdt(const void *shmaddr);
头文件
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
参数
被映射的共享内存段地址
返回值
成功返回0失败返回-1
设置:
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid
shmget的返回值
cmd
选择的操作
IPC_STAT:获取共享内存属性
IPC_SET:修改共享内存
IPC_RMID:删除共享内存,此时第三个参数为NULL
buf
//获取共享内存属性
structshmid_ds*buf=malloc(sizeof(structshmid_ds));
structshmid_dsbuf;
shmctl(shm,IPC_STAT,&buf);
演示案例:创建共享内存,供父子之间进行使用:
演示代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
char *p =NULL;
char buf[100] = "IPC shmred memory";
//1.获取共享内存标识 或者 创建
int shm=shmget(IPC_PRIVATE,200,IPC_CREAT);//IPC_PRIVATE//键值为0 表示创建的共享内存只能父子间用
printf("shm=%d\n",shm);
if(shm==-1)
{
perror("shmget");
return -1;
}
pid_t pid = fork();
if(pid==0)
{
//将共享内存映射到进程里面
p=shmat(shm,NULL,0);//0--可以对共享内存可读可写
memset(p,0,200);//清理空间
memcpy(p,buf,strlen(buf));
//getchar();//等待输入一个字符
//shmdt(p);//解除共享内存
}
else
{
//getchar();//等待输入一个字符
//shmdt(p);//解除共享内存
p = shmat(shm,NULL,0);//0--可以对共享内存可读可写
wait(NULL);
printf("father: %s\n",p);
//sleep(5);
//shmctl(shm,IPC_RMID,0);//删除共享内存
}
return 0;
}
注意:
需要在管理员权限下运行程序。
查看shmdt解除映射的效果和shmctl删除共享内存的效果,可以通过 :
ipcs -m
查看。
案例演示2:
非亲缘关系之间的共享内存进行通信:
演示代码:
写操作:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
//int shmget(key_t key, size_t size, int shmflg);
int main()
{
int shmid;
char *shmaddr;
key_t key;
key=ftok(".",1);//获取键值--内核位置
shmid=shmget(key,1024*4,IPC_CREAT|0666);//共响内存以兆为单位,关键字:创造
if(shmid==-1)
{
printf("创建共响内存失败\n");
exit(-1);//通常设定,异常返回-1,正常返回0
}
// void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr=shmat(shmid,0,0);//连接共响内存,shmflg:0:代表映射进来的共响内存可读可写,,完成共项内存的映射
printf("连接成功\n");
strcpy(shmaddr,"liujinhui");//写入内容
sleep(5);//写完后睡5s,让别的程序读走
//int shmdt(const void *shmaddr);
shmdt(shmaddr);//断开连接
//int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmctl(shmid,IPC_RMID,0);//关闭共响内存/删除
printf("退出\n");
return 0;
}
读操作:
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
//int shmget(key_t key, size_t size, int shmflg);
int main()
{
int shmid;
char *shmaddr;
key_t key;
key=ftok(".",1);//获取键值--内核位置
shmid=shmget(key,1024*4,0);//共响内存以兆为单位,关键字:不创造
if(shmid==-1)
{
printf("创建共响内存失败\n");
exit(-1);//通常设定,异常返回-1,正常返回0
}
// void *shmat(int shmid, const void *shmaddr, int shmflg);
shmaddr=shmat(shmid,0,0);//连接共响内存,shmflg:0:代表映射进来的共响内存可读可写,,完成共项内存的映射
printf("连接成功\n");
printf("data:%s\n",shmaddr);//读出内容
//int shmdt(const void *shmaddr);
shmdt(shmaddr);//断开连接
printf("退出\n");
return 0;
}
运行结果:
注意:在./write之后5s内操作./read
4.消息队列:
4.1:消息队列的概念:
消息队列就是一个消息的队列。可以把消息看作一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向其中按照一定的规则添加新消息;对消息队列有读权限的进程则可以
从消息队列中读走消息。
4.2:消息队列使用流程:
获取键值
创建或者访问一个消息队列
向队列读/写消息
销毁队列
4.3:消息队列相关API:
创建或打开:
功能:
建新消息队列或取得已存在消息队列
原型:
int msgget(key_t key, int msgflg);
所属头文件:
#include <sys / types.h>
#include <sys / ipc.h>
#include <sys / msg.h>
参数:
key:可以认为是一个端口号,也可以由函数ftok生成。
IPC_PRIVATE:只能父子进程使用;
非0值–自己指定或者ftok获取—任意进程使用;
msgflg:
IPC_CREAT值,若没有该队列,则创建一个并返回新标识符;若已存在,则返回原标识符。
IPC_EXCL值,若没有该队列,则返回-1;若已存在,则返回0。
IPC_NOWAIT读写消息队列要求不满足不会阻塞。
返回值:
成功执行时,返回消息队列标识值。失败返回-1,
标识供发送,接收,删除使用
添加消息队列:
功能:
把消息添加到已打开的消息队列末尾
原型:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
所属头文件
#include <sys / types.h>
#include <sys / ipc.h>
#include <sys / msg.h>
参数:
msgid: 指明消息队列的 ID, 通常是 msgget 函数成功的返回值
msgp:发送数据缓冲区首地址
msgp:指向消息结构的指针。该消息结构 msgbuf 为:
struct msgbuf{
long mtype;//消息类型----必须是long类型
char mtext[1];//消息正文
}—需要自己定义
msgsz:数据大小,每个消息体最大不要超过 4K
msgflg:一般赋0,阻塞方式
IPC_NOWAIT 若消息并没有立即发送而调用进程会立即返回
返回值:
成功则返回0, 出错则返回-1
读取消息:
功能:
把消息从消息队列中取走,与 FIFO 不同的是,这里可以指定取走某一条消息
原型:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
所属头文件:
#include <sys / types.h>
#include <sys / ipc.h>
#include <sys / msg.h>
参数:
msqid:消息队列的队列 ID
msgp:消息缓冲区
size:消息的字节数,不要以 null 结尾
msgtyp:消息类型 =0 忽略类型 >0 只接收指定类型的数据
<0 只接受小于等于其绝对值的数据
接收的类型为0
默认接收当前消息队列第一条数据
接收类型>0
接收指定类型的第一条,如果没有,阻塞(需要参考flag)
接收类型小于0
接收小于等于该类型绝对值的最小类型的第一条消息
MSG_NOERROR:若返回的消息比 size 字节多,则消息就会截短到size 字节,且不通知消息发送进程
IPC_NOWAIT :若消息并没有立即接收而调用进程会立即返回
函数传入值.
0:msgrcv调用阻塞直到条件满足为止
返回值:
接收成功返回0,失败返回-1
控制消息队列:
功能:
控制消息队列使用
原型:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
所属头文件:
#include <sys / types.h>
#include <sys / ipc.h>
#include <sys / msg.h>
参数
msqid:消息队列的队列 ID
cmd:
IPC_STAT:读取消息队列的数据结构 msqid_ds,并将其存储在buf 指定的地址中
IPC_SET:设置消息队列的数据结构 msqid_ds 中的 ipc_perm 元素的值。这个值取自 buf 参数
IPC_RMID:从系统内核中移走消息队列
Buf:消息队列缓冲区
返回值:
接收成功返回0,失败返回-1
案例演示:
建立消息队列,一段负责写入,一段负责读取。
write.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
struct Msg
{
long mtype;
char mtext[100];
};
int main()
{
struct Msg w_buf;
//创建/获取消息队列标识符
int msg = msgget(12,IPC_CREAT);
printf("msg = %d\n",msg);
while(1)
{
memset(&w_buf,0,sizeof(w_buf));
printf("请输入消息类型:");
scanf("%ld",&w_buf.mtype);
printf("请输入消息内容:");
scanf("%s",w_buf.mtext);
msgsnd(msg,&w_buf,sizeof(w_buf),0);
perror("msgsnd");
}
// msgctl(msg,IPC_RMID,NULL);//从内核中删除消息队列
return 0;
}
read.c
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
struct Msg
{
long mtype;
char mtext[100];//非循环发送消息时,读空间应大于写,不然会段错误
};
int main()
{
struct Msg r_buf;
long type;
int ret;
//创建/获取消息队列标识符
int msg = msgget(12,IPC_CREAT);
printf("msg = %d\n",msg);
while(1)
{
memset(&r_buf,0,sizeof(r_buf));
printf("请输入读取消息的类型:");
scanf("%ld",&r_buf.mtype);
ret = msgrcv(msg,&r_buf,sizeof(r_buf),r_buf.mtype,0);
perror("msgrcv");
printf("ret = %d\n",ret);
printf("read: %ld\t%s\n",r_buf.mtype,r_buf.mtext);
}
return 0;
}
运行结果:
。。。
5.信号量:
5.1:信号量的定义:
信号量是多进程/多线程同步的一种方式,可以实现对共享资源的保护--------资源允许操作的最大数目
5.2:信号量工作原理:
对共享资源访问保护:
1) 初始化信号量值为 1
2) 获得信号量 — 》 把信号值-1 —》 变成 0
3) 操作要保护的代码
4) 释放信号量 —》 把信号值+1
原理:
访问临界资源的代码叫做临界区,临界区本身也会称为临界资源.
硬件资源(处理器、内存、存储器及其它外围设备等)和软件资源(共享代码段、共享结构和变量等)
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1
5.3:信号量使用流程:
1) 创建信号集。
信号集: 一个信号量变量是一个信号量,多个信号量就是组成一个集合,类似数组。
2)初始化信号集中的每个信号值。
3)需要保护的临界代码前进行 P 操作。
对信号值的获得操作 ,称为 P 操作
4)执行完成临界代码后进行 V 操作。
释放信号称为== V 操作==
5) 不需要时候删除信号集
5.4:信号量相关API:
创建/访问:
原型:
int semget(key_t key, int nsems, int semflg);
参数:
key:键值
IPC_PRIVATE//键值为0 表示创建一个只有创建者进程才可以访问的信号量
ftok()//可以被多个进程共享的信号量
nsems
需要使用的信号量数目
semflg
IPC_CREAT
返回值:
失败: -1;成功:正数,表示信号集的 id
设置初始值:
原型:
int semctl(int semid, int semnum, int cmd, ...);
参数:
semid
semget返回的信号量标识符
semnum
要进行的操作的集合中信号量的编号,一般取0
cmd
删除:IPC_RMID
设置一个:SETVAL
获取一个:GETVAL
获取全部:GETALL
设置全部:SETALL
设置属性:IPC_SET
获取属性:IPC_STAT
消耗/还原 信号量semop:
原型:
int semop(int semid, struct sembuf *sops, unsigned nsops);
功能:
修改信号集合中的信号量值,实现 PV 操作
参数:
semid:semget返回值
sops:对信号量的操作
封装了一个结构体里面有
unsigned short sem_num; /* 集合中的信号量编号,实际就是数组下标,从 0 开始 /
short sem_op; / 要调整的信号值,把原来的信号值进行+/- sem_op 操作,一般是-1 或 1 /
short sem_flg; / 操作标志, IPC_NOWAIT 表示当不能获得信号量时不阻塞,返回错误;
SEM_UNDO 表示内核自动跟踪进程是否异常终止,如果持有信号量的进程异常中止,内核会还原信号量,
避免其他进程得不到信号永久等待这种情况。一般设置为0。 */
nsops:表示要操作的信号量数量
案例演示:
创建2个进程,进行pv操作: