自我测试
- | 什么是IPC进程间通信,有三种,分别用什么符号表示,他们三种都有id与key吗,id与key是什么怎么使用?怎么创建一个ipc的key值,怎么获得id,是先有id还是先有key,这个key的对象是文件夹
- | 如何显示所有的IPC对象
- | 如何根据id删除共享内存ipc;如何根据id删除队列ipc;如何根据id删除信号量ipc
- | 如何根据key删除共享内存ipc;如何根据key删除队列ipc;如何根据key删除信号量ipc
- | 如何获取其他进程的共享内存的key值
- | 怎么打开一个共享内存,没有就自动创建
- | 映射共享内存的函数是什么
- | 断开共享内存的函数是什么
- | 删除共享内存的函数是什么
- | 查看系统最大共享内存大小的指令是什么
- | 消息队列有什么特点,创建一个消息结构体
- | 如何打开一个消息队列,没有就自动创建
- | 如何向消息队列发送消息;如何从消息队列接收消息
- | 如何删除消息队列
- | 信号量/信号灯的特点,信号灯与信号灯集是什么
- | 信号灯集如何打开,没有就自动创建
- | 信号灯集如何初始化
- | 什么是P/V操作,如何进行P/V操作
- | 如何删除信号灯
第一讲
-
IPC对象是内核级别的多个进程间通信的工具,这些工具都有编号:共享内存(会议室)、消息队列(运送消息的火车,火车=车头+车厢)和信号量(摇旗)
-
每个IPC对象有唯一的ID与key值,key值对于一个文件夹,相当于开启一个钥匙,只有钥匙权限才能获取房间里面的ipc的id
-
IPC对象用完记得删除(会议室用完关门,火车回站台,旗子回收)
-
因为IPC是多个进程间通信的东西,创建IPC的进程有IPC的ID号可以直接打开,那么其他进程如何使用该IPC与创造IPC的进程通信呢;KEY是IPC的属性,使得不同进程可以打开同一个IPC,创建该IPC的进程可以通过ID号打开IPC,其他进程不知道这个ID只能通过KEY值获取到IPC的ID
-
key=0=IPC_PRIVATE代表这个私有的,意思就是这个IPC对象只能我自己这个进程私自使用,其他进程不能通过我的私有IPC来获取我的PIC号
命令 | 截图 |
---|---|
ipcs命令 //显示所有IPC对象 | |
ipcrm命令 | ipcrm -M key 根据key删除内存类型ipc ipcrm -m id根据pic号删除内存型pic ipcrm -Q key根据键值删除队列pic ipcrm -q id 根据pic号删除队列型pic ipcrm -S key根据键值删除信号量pic ipcrm -s id根据pic号删除信号量pic id=pic号;key=键值;m=内存类型;q=队列类型;s=信号量类型 |
1.1 生成key值/创建IPC (先创建ipc,再去ipc里面选哪个类型顺便给你一个身份证id) ftok函数file to key,创建ipc的key值
key_t ftok(const char*path, int proj_id);
path = 文件路径
proj_id = 生成key的数字,不等于0
返回值:key键值
功能:为当前进程生成一个键值 ====================================================================== 23 void main() 24 { 25 key_t key; 26 27 //为本进程深沉一个健 28 //参数1:本进程的文件路径 29 //参数2:一个随机值x,对应的经过函数处理后返回y,也就是健值,不同的x-->不同的Y 30 key=ftok("/home/farsight/Desktop",1); 31 32 //如果健值=-1代表深生成错误 33 if(key==-1) 34 { 35 perror("\nkey error:\n"); 36 } 37 else 38 { 39 //健是一个整数,可以用%d打印察看 40 printf("\n健:%d\n",key); 41 } 42 }
共享内存 | 1最高效的进程间通信方式 2可以直接读写内存 3由于共享内存在内核空间内创建,所有需要将其映射用户空间才能访问 4由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用 |
---|---|
共享内存的操作步骤 | 截图如下 |
步骤1:创建/打开共享内存 | int shmget(key_t key int size, int shmflg); 参数1:key就是进程的键值;IPC_PRIVATE的话就不用为进程新建键值,IPC_PRIVATE就是键值0; 参数2:共享内存的字节大小 参数3:IPC_CREAT 与 0666(记住) 返回值:共享内存的的id;小于0则错误 注意:第一个进程创建共享内存后,后面的进程使用该函数因为已经创建了共享内存,就不会再创建,直接返回该共享内存的id号就ok |
步骤2:映射共享内存 | void * shmat(int shmi, const voidshmaddr, int shmflg); 返回映射后的地址,失败返回(void)-1 参数1:共享内存ID 参数2:NULL表示自动映射 参数3:0表示对映射地址读写; SHM RDONLY表示映射地址只读 ;at是在什么什么地方 |
步骤3:读写共享内存 | 对映射地址的写=sprintf,然后直接用printf就能打印映射地址的内容 |
步骤4:断开内存映射 | int shmdt(void*shmaddr); 成功时返回0,失败时返回EOF 参数:被断开连接的映射地址;dt=disconnect |
步骤5:删除共享内存 | int shmctl(int shmi, int cmd, struct shmid_ds*buf); 1:成功时返回0,失败时返回EOF 参数1:要操作的共享内存的id 参数2:IPC_STAT(获取共享内存的属性);IPC_SET(设置共享内存的属性);IPC_RMID(释放内核中的共享内存) 参数3 shmid_ds结构体变量,用于接收shmctl函数返回的共享内存的属性 |
ipcs -l | 注意:共享内存的空间大小是限制的,为了不超过共享内存的最大字节数,我们先用ipcs -l 指令查看最大内存空间是多少,在决定共享内存的空间多大 l=limit |
---|
功能介绍: 1:创建两个.c的父进程文件分别为text1.c与text2.c 2:要求两个父进程通过共享内存,实现text1.c写数据,text2.c读数据 text1.c的代码 /*=============================================================== * Copyright (C) 2020 All rights reserved. * * 文件名称:text1.c * 创 建 者:liujing * 创建日期:2020年07月14日 * 描 述: * * 更新日志: * ================================================================*/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <strings.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include <sys/ipc.h> #include <sys/shm.h> void main() { /*******************************创建key值***********************************/ key_t key; //为本进程深沉一个健 //参数1:本进程的文件路径 //参数2:一个随机值x,对应的经过函数处理后返回y,也就是健值,不同的x-->不同的Y key=ftok("/home/farsight/Desktop",1); //如果健值=-1代表深生成错误 if(key==-1) { perror("\nkey error:\n"); } else { //健是一个整数,可以用%d打印察看 printf("\n健:%d\n",key); } /*******************************创建共享内存***********************************/ int picid = shmget(key,2000,IPC_CREAT|0666); if(-1 == picid) { perror("\n创建共享内存error\n"); } else { printf("\n创建共享内存成功,picid=%d\n",picid); } /*******************************映射共享内存***********************************/ char*addr; //在进程地址空间建立一个存放char的空间地址用于对接内存的映射地址 addr = (char*)shmat(picid,NULL,0);//由于shmat返回的是void*,所以需要强转 if(addr==(char*)-1) { perror("\n映射共享内存error\n"); } else { printf("\n映射共享内存成功\n"); } /****************************共享内存的映射区域的操作******************************/ int i = 0; while(i<5) { sprintf(addr,"hello word"); printf("\n写入hello word成功\n"); i++; sleep(1); } /****************************断开映射******************************/ if(EOF == shmdt((void*)addr)) { perror("\n 断开映射error\n"); } else { printf("\n断开映射成功\n"); } /****************************删除内核中的共享内存******************************/ if(EOF == shmctl(picid,IPC_RMID,NULL)) { perror("\n删除共享内存error\n"); } else { printf("\n删除共享内存成功\n"); } } text2.c的代码 /*=============================================================== * Copyright (C) 2020 All rights reserved. * * 文件名称:text2.c * 创 建 者:liujing * 创建日期:2020年07月14日 * 描 述: * * 更新日志: * ================================================================*/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <strings.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include <sys/ipc.h> #include <sys/shm.h> void main() { /*******************************创建key值***********************************/ key_t key; //为本进程深沉一个健 //参数1:本进程的文件路径 //参数2:一个随机值x,对应的经过函数处理后返回y,也就是健值,不同的x-->不同的Y key=ftok("/home/farsight/Desktop",1); //如果健值=-1代表深生成错误 if(key==-1) { perror("\nkey error:\n"); } else { //健是一个整数,可以用%d打印察看 printf("\n健:%d\n",key); } /*******************************创建共享内存***********************************/ int picid = shmget(key,2000,IPC_CREAT|0666); if(-1 == picid) { perror("\n创建共享内存error\n"); } else { printf("\n创建共享内存成功,picid=%d\n",picid); } /*******************************映射共享内存***********************************/ char*addr; //在进程地址空间建立一个存放char的空间地址用于对接内存的映射地址 addr = (char*)shmat(picid,NULL,0);//由于shmat返回的是void*,所以需要强转 if(addr==(char*)-1) { perror("\n映射共享内存error\n"); } else { printf("\n映射共享内存成功\n"); } /****************************共享内存的映射区域的操作******************************/ int i = 0; while(i<5) { if(*addr != 0) { puts(addr); i++; } } /****************************断开映射******************************/ if(EOF == shmdt((void*)addr)) { perror("\n 断开映射error\n"); } else { printf("\n断开映射成功\n"); } /****************************删除内核中的共享内存******************************/ if(EOF == shmctl(picid,IPC_RMID,NULL)) { perror("\n删除共享内存error\n"); } else { printf("\n删除共享内存成功\n"); } } ======================================================================== text1.c的运行结果: farsight@ubuntu:~/Desktop$ ./text1 健:16854188 创建共享内存成功,picid=753680 映射共享内存成功 写入hello word成功 写入hello word成功 写入hello word成功 写入hello word成功 写入hello word成功 断开映射成功 删除共享内存error text2.c的运行结果: farsight@ubuntu:~/Desktop$ ./text2 健:16854188 创建共享内存成功,picid=753680 映射共享内存成功 hello word hello word hello word hello word hello word 断开映射成功 删除共享内存成功
第二讲 消息队列机制
- 消息队列是头尾相连的消息链表组成,消息链表里面存放着数据类型一致的大量数据
>>消息队列的结构图
2.1 消息队列的使用步骤
消息格式 | 消息是一个结构体,需要自己定义,该结构体第一个成员是>0的long类型数据代表该消息的类型,后面的成员是若干数组,用于存放用户需要发送的数据 下面放上一个消息结构体的最简单的定义格式 |
---|
步骤1:打开/创建消息队列msgget | int msgget(key _t key ,int msgflg); 该函数与共享内存的创建/打开函数使用一致 参数1:这个key值与前面的共享内存的key一致,都是通过ftok函数产生,所有结论就是消息队列/共享内存/信号量的key都是IPC的key,都是通过ftok函数生成…如果是私有key就写IPC_PRIVATE,不是私有key就用ftok生成 参数2:该参数与共享内存的参数一样,也是填IPC_CREAT 与 0666(记住) |
---|---|
步骤2:向消息队列发送消息 msgsnd | int msgsnd (int msgid, const void*msgp, size t size int msgflg); 返回值:0=成功;EOF=失败 参数1:消息队列的ID 参数2:被发送的消息的地址 参数3:发送的消息长度 参数4: 0=发送完后该函数就立即返回成功=0 |
步骤3:·从消息队列接收消息 msgrcv | int msgrcv(int msgid, void*msgp, size t size, long msgtype, int msgflg): 返回值:成功时返回收到的消息长度,失败时返回-1 参数1:消息队列id 参数2:接收消息的队列消息结构体地址 参数3:接收消息的长度 参数4:接收消息的类型 参数5:0=阻塞式(常用),一直等接收到消息;IPC_NOWAIT,没有消息就立即返回错误 |
步骤4:·控制消息队列 msgctl | int msgctl(int msgid, int cmd, struct msqid_ds * buf); 成功时返回0,失败时返回-1 参数1:消息队列id 参数2:要执行的操作 IPC_STAT/ PC_SET/ IPC_RMID 参数3:存放消息队列属性的地址 //注意:消息队列执行IPC_RMID命令会立即释放队列,一般在最后一个进程对消息队列进行删除 |
使用消息队列,实现两个进程text1.c与text2.c之间的进程通信 text1.c的代码如下: ================================================================*/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <strings.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/ipc.h> #include <sys/msg.h> //队列消息的结构体 struct mymsg { long mtype; /* Message type. */ char mtext[32]; /* Message text. */ }; void main() { //第一步:创建IPC的key值 key_t key; key = ftok("/home/farsight/Desktop",1); if(key == EOF) { perror("\nkey create error:\n"); } else { printf("\nkey create success\n"); } //第二步:根据key值获取队列ID / 创建,打开队列 int queueid = 0; queueid = msgget(key,IPC_CREAT|0666); if(queueid == -1) { perror("\nqueueid get error:\n"); } else { printf("\nqueueid get success\n"); } //第三步:向队列发队列消息 struct mymsg mymsg1 = { 1,"hello word"}; //发送队列缓冲区 struct mymsg getmsg; //接受队列缓冲区 char getbuf[32] = { 0}; int i=0; //五次循环后退出 while(i<5) { sleep(1); msgsnd(queueid,(void*)&mymsg1,sizeof(mymsg1.mtext),0); printf("\n向队列发%s\n",mymsg1.mtext); sleep(1); //接受text2.c进程2发送的2号队列链表里面的队列数据 msgrcv(queueid,(void*)&getmsg,sizeof(mymsg1.mtext),2,0); printf("\n队列接受到%s\n",getmsg.mtext); i++; } //释放消息队列 if(-1 == msgctl(queueid,IPC_RMID,NULL)) { perror("\nmsgctl error:\n"); } else { printf("\n释放队列完成\n"); } } text2.c的代码如下: ================================================================*/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <strings.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <signal.h> #include <sys/ipc.h> #include <sys/shm.h> #include <sys/ipc.h> #include <sys/msg.h> //队列消息的结构体 struct mymsg { long mtype; /* Message type. */ char mtext[32]; /* Message text. */ }; void main() { //第一步:创建IPC的key值 key_t key; key = ftok("/home/farsight/Desktop",1); if(key == EOF) { perror("\nkey create error:\n"); } else { printf("\nkey create success\n"); } //第二步:打开队列,获得队列的ID int queueid = 0; queueid = msgget(key,IPC_CREAT|0666); if(queueid == -1) { perror("\nqueueid get error:\n"); } else { printf("\nqueueid get success\n"); } //第三步:向队列发队列消息 struct mymsg mymsg1 = { 2,"HELLO WORD"}; struct mymsg getmsg;//该消息结构体变量用于接受队列消息 int i = 0; while(i<5) { sleep(1); msgsnd(queueid,(void*)&mymsg1,sizeof(mymsg1.mtext),0); printf("\n向队列发%s\n",mymsg1.mtext); sleep(1); //接受另一个text1.c进程1发送的1号队列链表里面的队列数据 msgrcv(queueid,(void*)&getmsg,sizeof(mymsg1.mtext),1,0); printf("\n队列接受到%s\n",getmsg.mtext); i++; } //队列的使用,最后一个进程释放队列,其他进程不管 } ================================================================== text1.c的运行结果: farsight@ubuntu:~/Desktop$ ./text1 key create success queueid get success 向队列发hello word 队列接受到HELLO WORD 向队列发hello word 队列接受到HELLO WORD 向队列发hello word 队列接受到HELLO WORD 向队列发hello word 队列接受到HELLO WORD 向队列发hello word 队列接受到HELLO WORD 释放队列完成 ================================================================= text2.c 的运行结果 farsight@ubuntu:~/Desktop$ ./text2 key create success queueid get success 向队列发HELLO WORD 队列接受到hello word 向队列发HELLO WORD 队列接受到hello word 向队列发HELLO WORD 队列接受到hello word 向队列发HELLO WORD 队列接受到hello word 向队列发HELLO WORD 队列接受到hello word >>结果:成功!
第三讲
信号灯类型 | 信号灯=无名信号灯+有名信号灯+ system v信号灯集 |
---|---|
system v 信号灯集 / 系统5信号灯集 | 多个信号灯的集合 同时操作多个信号灯 系统5信号灯是产生解决了多个单个的信号灯的操作有死锁bug的问题 |
system 5 信号灯集的使用步骤 | 讲解 |
---|---|
步骤1:信号灯集的创建/打开 | int semget(key_t key, int nsems, int semflg); 成功时返回信号灯的d,失败时返回-1 参数1:PIC的key值 参数2:信号灯的个数; 参数3:IPC_CREAT , 0666 整体用法与前面的共享内存与消息队列的创建/打开函数类似 |
步骤2:信号灯集初始化 | 信号灯的值是脏数据,需要初始化 int semctl(int semid, int semnum, int cmd, …); 返回值:成功时返回0,失败时返回EOF 参数1:信号灯ID 参数2:0;表示初始化第一个信号灯;x表示初始化第x+1个信号灯 参数3:SETVAL(设置信号灯的值);IPC_RMID(删除ID) 如果参数3的值是SETVAL,设置信号灯的值,我们就设置的值封装成共用体的形式传递给参数4;参数4:需要传递一个union共用体类型,需要自己定义,具体查看man手册 |
步骤3:P/V操作 | int semop(int semid struct sembuf *sops, unsigned nsops); 返回值:成功时返回0,失败时返回-1 参数1被操作的信号灯集的ID 参数2:一个描述对怎么对信号灯操作的结构体;同理如果需要同时操作多个,就需要传入结构体数组 参数3:要操作的信号灯的个数 信号灯操作结构体如下(该结构体库里面有,直接调用就ok) op=option=选项,选p还是v |
步骤4:删除信号灯 | semctl,传递命令参数为_IPCRM |
================================================================*/ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <strings.h> #include <sys/ipc.h> #include <sys/sem.h> static int A = 0; static int B = 0; //初始化信号值 union semun { int val; struct semid_ds *buf; unsigned short *array; } arg[2]; void main() { //pic的key值创建 key_t semkey; semkey = ftok("/home/farsight/Desktop",1); if(semkey == -1) { perror("ftok error:"); } else { printf("\ncreate key success\n"); } //创建信号灯集,获取信号灯集ID int semid = 0; semid = semget(semkey,2,IPC_CREAT|0666); if(semid == -1) { perror("semget id error:"); } else { printf("\nreturn semid success\n"); } //初始化信号灯集合 arg[0].val = 0; arg[1].val = 0; //初始化信号1 semctl(semid,0,SETVAL,arg[0]); //初始化信号2 semctl(semid,1,SETVAL,arg[1]); printf("\ninit success\n"); //v操作 struct sembuf semcontol[2]; semcontol[0].sem_num = 0; semcontol[0].sem_op = 1; semcontol[0].sem_flg = 0; semcontol[1].sem_num = 1; semcontol[1].sem_op = 1; semcontol[1].sem_flg = 0; semop(semid,semcontol,2); printf("\nV option success\n"); //p操作 semcontol[0].sem_num = 0; semcontol[0].sem_op = 0; semcontol[0].sem_flg = 0; semcontol[1].sem_num = 1; semcontol[1].sem_op = 0; semcontol[1].sem_flg = 0; if( 0 == semop(semid,semcontol,2)) { printf("\nP option success\n"); } else { perror("P option error:"); } A++; B++; printf("A = %d",A); printf("B = %d",B); semctl(semid,0,IPC_RMID); semctl(semid,1,IPC_RMID); }