Linux9:内存映射,键值含义,共享内存,消息队列,信号量

内存映射,键值含义,共享内存,消息队列,信号量

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操作:

猜你喜欢

转载自blog.csdn.net/weixin_40734514/article/details/109132631
今日推荐