UNIX再学习 -- XSI IPC通信方式
有 3 种称作 XSI IPC 的IPC:消息队列、信号量以及共享存储器。我们先来介绍它们相类似的特征:
一、相似特征
1、标识符和键
#include<sys/ipc.h>
key_t ftok(const char *path, int id);
返回值:成功则返回键,出错则返回(key_t)-1.
path 参数必须引用一个现存文件。当产生键是,只使用 id 参数的低 8 位。
ftok 创建的键通常是用下列方式构成的:按给定的路径名取得其 stat 结构,从该结构中取出部分 st_dev 和 st_ino 字段,然后再与项目 ID 结合起来。如果两个路径名引用两个不同的文件,那么对这两个路径名调用 ftok 通常返回不同的键。但是因为i节点号和键通常都存放在长整型中,于是创建键时可能会丢失信息,这意味着,如果使用用一个项目 ID,那么对于不同文件的两个路径名可能产生相同的键。
3 个 get函数(msgget,semget 和 shmget)都有两个类似的参数:一个 key 和一个整型 flag。如果满足下列两个条件之一,则创建一个新的 IPC 结构:
1. key 是 IPC_PRIVATE。
2. key 当前未与特定类型的 IPC 结构相结合,并且 flag 中指定了 IPC_CREAT位。
注意:
1.为了访问现存的队列,key 必须等于创建该队列时所指定的键,并且不应指定 IPC_CREAT。
2.为了访问一个现存队列,决不能指定 IPC_PROVATE 作为键。因为这是一个特殊的键值,用于创建一个新队列。
3.如果希望创建一个新的 IPC 结构,而且要确保不是引用具有同一标识符的一个现行 IPC 结构,那么必须在 flag 中同时指定 IPC_CREAT 和 IPC_EXECL 位。这样做了以后,如果 IPC 结构已经存在,就会造成出错,返回 EEXIST。
2、权限结构
struct ipc_perm{
uid_t uid; //拥有者的有效用户ID
gid_t gid; //拥有者有效组ID
uid_t cuid; //创建者有效用户ID
gid_t cgid; //创建者有效组ID
mode_t mode; //访问权限
.........
};
在创建IPC结构时,对所有字段都赋初值。以后,可以调用 msgctl,semctl 或 shmctl 修改 uid,gid 和 mode 字段,为了改变这些值,调用进程必须是 IPC 结构的创建者或超级用户。更改这些字段类似于对文件调用 chown 和 chmod。mode 字段值类似于普通文件的访问权限,但是没有执行权限。
3、优点缺点
1、IPC 结构在系统范围内起作用,没有访问计数,例如,如果进程创建了一个消息队列,在该队列中放入了几则消息,然后终止,但是该消息队列及其内容不会被删除。与管道相比,当最后一个访问管道的进程终止时,管道就完全被删除了,对于 FIFO 而言,虽然当最后一个引用 FIFO 的进程终止时其名字仍保留在系统中,直至显式地删除它,但是留在 FIFO 中的数据却在此时全部被删除。
2、这些 IPC 结构在文件系统中没有名字,我们不能使用 stat 系列函数访问和修改他们的特性。为此不得不增加全新的系统调用(msgget,semop,shmat等)。我们不能使用 ls 命令见到 IPC 对象,不能使用 rm 命令删除它们,也不能使用 chmod 等函数更改他们的访问权限。于是就不得不增加新的命令 ipcs 和 ipcrm。
3、IPC 不使用文件描述符,所以不能对它们使用多路转换 IO 函数:select 和 poll。
优点:
1、可靠
二、消息队列 (重点)
1、消息队列介绍
2、基本特点
相较于其他几种 IPC 机制,消息队列具有明显的优势
不同系统对消息队列的限制是不一样的
3、常用函数
(1)函数 msget:创建/获取消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
返回值:成功返回消息队列标识符,失败返回 -1
《1》参数解析
(2)函数 msgsnd:发送信息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
返回值:成功返回 0,失败返回 -1
《1》参数解析
《2》函数解析
(3)函数 msgrcv:接收消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
返回值:成功返回所接收消息数据的字节数,失败返回 -1.
《1》参数解析
《2》函数解析
(4)函数 msgctl:销毁或控制消息队列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
返回值:成功返回 0,失败返回 -1.
《1》参数解析
msqid_ds::msg_perm.uid //拥有者用户 IDmsqid_ds::msg_perm.gid //拥有者组 IDmsqid_ds::msg_perm.mode //权限msqid_ds::msg_qbytes //队列最大字节数
IPC_RMID 立即删除消息队列,所有处于阻塞状态的对该消息队列的 msgsnd 和 msgrcv 函数调用,都会立即返回失败,且 errno 为 EIDRM。
4、示例说明
//msgA.c 接收端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#include <sys/stat.h>#define MSG_FILE "/home/tarena/project/c_test/a.txt"
#define BUFFER 255
#define PERM S_IRUSR | S_IWUSRstruct msgtype
{long mtype;char buffer[BUFFER + 1];
};int main (void)
{struct msgtype msg;key_t key;int msgid;if ((key = ftok (MSG_FILE, 'a')) == -1)perror ("ftok"), exit (1);if ((msgid = msgget (key, PERM | IPC_CREAT | IPC_EXCL)) == -1)perror ("msgget"), exit (1);while (1){msgrcv (msgid, &msg, sizeof (struct msgtype), 1, 0);printf ("Server Receive: %s\n", msg.buffer);msg.mtype = 2;msgsnd (msgid, &msg, sizeof (struct msgtype), 0);}return 0;
}
//msgB.c 发送端
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/stat.h>#define MSG_FILE "/home/tarena/project/c_test/a.txt"
#define BUFFER 255
#define PERM S_IRUSR | S_IWUSRstruct msgtype
{long mtype;char buffer[BUFFER + 1];
};int main (void)
{struct msgtype msg;key_t key;int msgid;if ((key = ftok (MSG_FILE, 'a')) == -1)perror ("ftok"), exit (1); if ((msgid = msgget (key, PERM)) == -1)perror ("msgget"), exit (1);msg.mtype = 1;strcpy (msg.buffer, "这是客户端发出的消息内容");msgsnd (msgid, &msg, sizeof (struct msgtype), 0);memset (&msg, '\0', sizeof (struct msgtype));msgrcv (msgid, &msg, sizeof (struct msgtype), 2, 0);printf ("Client Receive: %s\n", msg.buffer);return 0;
}
输出结果:
在一个终端执行
# ./msgA
Server Receive: 这是客户端发出的消息内容
Server Receive: 这是客户端发出的消息内容
Server Receive: 这是客户端发出的消息内容
Server Receive: 这是客户端发出的消息内容在另一个终端执行
# ./msgB
Client Receive: 这是客户端发出的消息内容
# ./msgB
Client Receive: 这是客户端发出的消息内容
# ./msgB
Client Receive: 这是客户端发出的消息内容
# ./msgB
Client Receive: 这是客户端发出的消息内容
5、相关指令
(1)ipcs 命令
-a:显示全部可显示的信息;
-q:显示活动的消息队列信息;
-m:显示活动的共享内存信息;
-s:显示活动的信号量信息。
//查看
# ipcs -q ------ Message Queues --------
key msqid owner perms used-bytes messages
0x61017a67 0 root 600 0 0
0x61017a6c 32769 root 600 0 0
(2)ipcrm 命令
-m SharedMemory id 删除共享内存标识 SharedMemoryID。与 SharedMemoryID 有关联的共享内存段以及数据结构都会在最后一次拆离操作后删除。
-M SharedMemoryKey 删除用关键字 SharedMemoryKey 创建的共享内存标识。与其相关的共享内存段和数据结构段都将在最后一次拆离操作后删除。
-q MessageID 删除消息队列标识 MessageID 和与其相关的消息队列和数据结构。
-Q MessageKey 删除由关键字 MessageKey 创建的消息队列标识和与其相关的消息队列和数据结构。
-s SemaphoreID 删除信号量标识 SemaphoreID 和与其相关的信号量集及数据结构。
-S SemaphoreKey 删除由关键字 SemaphoreKey 创建的信号标识和与其相关的信号量集和数据结构。
《3》示例
//删除
# ipcrm -q 32769//复查
# ipcs -q------ Message Queues --------
key msqid owner perms used-bytes messages
0x61017a67 0 root 600 0 0
三、信号量
1、信号量介绍
2、基本特点
为了获得共享资源,进程需要按以下步骤进行。
system V 的信号量比起通常意义上的信号量要复杂一些
内核为每个信号量集合维护着一个 semid_ds 结构:
struct semid_ds{
struct ipc_perm sem_perm;
unsigned short sem_nsems; //信号量在信号量集中的编号
time_t sem_otime; 最后调用semop()的时间。
time_t sem_ctime; 最后进行change的时间。
....
}
每个信号量由一个无名结构表示,它至少包含下列成员:
struct{
unsigned short semval; //信号量值,>=0
pid_t sempid; //最后使用信号量的pid
unsigned short semcnt; //等待semval变为大于其当前值的线程或进程数
unsigned short semzcnt; //等待semval变成0的线程或进程数
}
下图列出了影响信号量集合的系统限制:
3、常用函数
(1)函数 semget:创建/获取信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
返回值:成功返回信号量标识符,失败返回 -1
《1》参数解析
(2)函数 semop:操作信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops);
返回值:成功返回 0,失败返回 -1
《1》参数解析
《2》函数解析
struct sembuf {unsigned short sem_num; //信息量编号short sem_op; //操作数short sem_flg; //操作标志
};
该结构体数组中的每个元素通过其信号量编号成员与信号量集中的一个特定的信号量对应,表示对该信号量的操作。
(3)函数 semctl:销毁或控制信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
返回值:成功返回 0(cmd 取某些值存在例外),失败返回 -1
《1》参数解析
《2》函数解析
1)获取信号量集的属性
int semctl (int semid, 0, IPC_STAT, struct semid_ds* buf);
2)设置信号量集的属性
int semctl (int semid, 0, IPC_SET, struct semid_ds* buf);
仅以下四个属性可以设置:
semid_ds::sem_perm.uid //拥有者用户ID
semid_ds::sem_perm.gid //拥有者组 ID
semid_ds::sem_perm.mode //权限
3)删除信号量集
int semctl (int semid , 0, IPC_RMID);
立即删除信号量集,所有处于阻塞装填的对该信号量的 semop 函数调用,都会立即返回失败,且 errno 为 EIDRM。
4)获取信号量集中每个信号量的值
int semctl (int semid , 0, GETALL, unisigned short* array);
5)设置信号量集中每个信号量的值
int semctl (int semid , 0, SETALL, unisigned short* array);
6)获取信号量集中指定信号量的值
int semctl (int semid ,int semnum, GETVAL);
成功返回 semid 信号量集中第 semnum 号信号量的值。
7)设置信号量集中指定信号量的值
int semctl (int semid ,int semnum, SETVAL, int val);
8)获取信号量集的内核参数
int semctl (int semid ,0, IPC_INFO, struct seminfo* buf);
4、示例说明
//semA.c 使用信号量集实现进程间的通信
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <signal.h>//定义全局变量保存信号量集的ID
int semid;void fa(int signo)
{printf("正在删除信号量集,请稍后...\n");sleep(2);int res = semctl(semid,0,IPC_RMID);if(-1 == res){perror("semctl"),exit(-1);}printf("删除信号量集成功\n");exit(0);
}int main(void)
{//1.获取key值,使用ftok函数key_t key = ftok(".",200);if(-1 == key){perror("ftok"),exit(-1);}printf("key = %#x\n",key);//2.创建信号量集,使用semget函数semid = semget(key,1/*信号量集的大小*/,IPC_CREAT|IPC_EXCL|0644);if(-1 == semid){perror("semget"),exit(-1);}printf("semid = %d\n",semid);//3.初始化信号量集,使用semctl函数int res = semctl(semid,0/*信号量集的下标*/,SETVAL,5/*初始值*/);if(-1 == res){perror("semctl"),exit(-1);}printf("信号量集初始化完毕\n");//4.删除信号量集,使用信号2处理printf("删除信号量集,请按ctrl+c...\n");signal(2,fa);while(1);return 0;
}
//semB.c 使用信号量集实现进程间的通信
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int main(void)
{//1.获取key值,使用ftok函数key_t key = ftok(".",200);if(-1 == key){perror("ftok"),exit(-1);}printf("key = %#x\n",key);//2.获取信号量集,使用semget函数int semid = semget(key,1,0);if(-1 == semid){perror("semget"),exit(-1);}printf("semid = %d\n",semid);//3.创建10个子进程模拟抢占资源int i = 0;for(i = 0; i < 10; i++){//创建子进程pid_t pid = fork();if(0 == pid)//子进程{//准备结构体变量struct sembuf op;op.sem_num = 0;//下标op.sem_op = -1;//计数减1op.sem_flg = 0;//标志//使用semop函数占用资源semop(semid,&op,1/*大小*/);printf("申请共享资源成功\n");sleep(20);op.sem_op = 1;//使用semop函数释放资源semop(semid,&op,1);printf("释放共享资源完毕\n");exit(0);//终止}}return 0;
}
输出结果:
在一个终端执行:
# ./semA
key = 0xc801135e
semid = 196608
信号量集初始化完毕
删除信号量集,请按ctrl+c...
^C正在删除信号量集,请稍后...
删除信号量集成功在另一个终端执行:
# ./semB
key = 0xc801135e
semid = 196608
申请共享资源成功
申请共享资源成功
申请共享资源成功
申请共享资源成功
申请共享资源成功
# ./semB
key = 0xc801135e
semid = 196608
申请共享资源成功
申请共享资源成功
申请共享资源成功
申请共享资源成功
申请共享资源成功
释放共享资源完毕
释放共享资源完毕
释放共享资源完毕
释放共享资源完毕
释放共享资源完毕
释放共享资源完毕
5、相关指令
//查看
# ipcs -s------ Semaphore Arrays --------
key semid owner perms nsems
0xc801135e 32768 root 644 1
ipcrm -s ID 表示删除指定的信号量
//删除
# ipcrm -s 32768//复查
# ipcs -s------ Semaphore Arrays --------
key semid owner perms nsems
四、共享存储
1、共享存储介绍
2、基本特点
3、常用函数
(1)函数 shmget:创建新的或获取已有的共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
返回值:成功返回共享内存标识符,失败返回 -1
《1》参数解析
(2)函数 shmat:加载共享内存
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
返回值:成功返回共享内存起始地址,失败返回 -1
《1》参数解析
《2》函数解析
(3)卸载共享内存
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
返回值:成功返回 0,失败返回 -1
《1》参数解析
《2》函数解析
(4)函数 shmctl:销毁/控制共享内存
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
返回值:成功返回 0,失败返回 -1
《1》参数解析
shmid_ds::shm_perm.uid //拥有者用户 IDshmid_ds::shm_perm.gid //拥有者组 IDshmid_ds::shm_perm.mode //权限
IPC_RMIF 销毁共享内存,其实并非真的销毁,而只是做一个销毁标志,禁止任何进程对该共享内存形成新的加载,但已有的加载依然保留。只有当其使用者们纷纷卸载,直至其加载计数降为 0 时,共享内存才会真的被销毁。
4、示例说明
//msgA.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/shm.h>
#define MSG_FILE "/home/tarena/project/c_test/a.txt"
#define BUFFER 4096
#define PERM S_IRUSR|S_IWUSR
int main()
{key_t key;if((key=ftok(MSG_FILE,'a'))==-1){perror("Creat Key Error");exit(1);}int shmid = shmget (key, BUFFER, PERM | IPC_CREAT | IPC_EXCL);if (shmid == -1){perror ("shmget");exit (EXIT_FAILURE);}void* shmaddr = shmat (shmid, NULL, 0);if (shmaddr == (void*)-1){perror ("shmat");exit (EXIT_FAILURE);}strcpy (shmaddr, "这是放入共享内存的内容!");if (shmdt (shmaddr) == -1){perror ("shmdt");exit (EXIT_FAILURE);}sleep(10);if (shmctl (shmid, IPC_RMID, NULL) == -1){perror ("shmctl");exit (EXIT_FAILURE);}return 0;
}
//msgB.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/shm.h>
#define MSG_FILE "/home/tarena/project/c_test/a.txt"
#define BUFFER 4096
#define PERM S_IRUSR|S_IWUSR
int main(int argc,char **argv)
{key_t key;if((key=ftok(MSG_FILE,'a'))==-1){perror("Creat Key Error");exit(1);}int shmid = shmget (key, BUFFER, PERM);if (shmid == -1){perror ("shmget");exit (EXIT_FAILURE);}void* shmaddr = shmat (shmid, NULL, 0);if (shmaddr == (void*)-1){perror ("shmat");exit (EXIT_FAILURE);}printf ("%s\n", (char*)shmaddr);if (shmdt (shmaddr) == -1){perror ("shmdt");exit (EXIT_FAILURE);}return 0;
}
输出结果:
在一个终端执行:
# ./msgA在另一个终端执行:
# ./msgB
这是放入共享内存的内容!
5、相关指令
//查看
# ipcs -m------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 0 root 600 393216 2 dest
0x00000000 196609 root 700 25740 2 dest
0x00000000 163842 root 700 3219768 2 dest
0x00000000 98307 root 700 130752 2 dest
0x00000000 229380 root 700 17028 2 dest
0x00000000 262149 root 700 13332 2 dest
0x00000000 294918 root 700 15180 2 dest
0x61017b76 393223 root 600 4096 0
ipcrm -m ID 表示删除指定的共享存储
//删除
# ipcrm -m 393223//复查
# ipcs -m------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
0x00000000 0 root 600 393216 2 dest
0x00000000 196609 root 700 25740 2 dest
0x00000000 163842 root 700 3219768 2 dest
0x00000000 98307 root 700 130752 2 dest
0x00000000 229380 root 700 17028 2 dest
0x00000000 262149 root 700 13332 2 dest
0x00000000 294918 root 700 15180 2 dest
五、未讲部分
UNIX再学习 -- XSI IPC通信方式相关推荐
- UNIX再学习 -- 网络IPC:套接字
一.基本概念 1.编程接口 什么是伯克利套接字(Berkeley Socket)? 美国加利福尼亚大学比克利分校于 1983年发布 4.2 BSD Unix 系统.其中包含一套用 C 语言编写的应用程 ...
- UNIX再学习 -- 进程间通信之管道
一.进程间通信概念 首先,需要了解一下什么是进程间通信. 进程之间的相互通信的技术,称为进程间通信(InterProcess Communication,IPC). 下图列出 4 种实现所支持的不同形 ...
- UNIX再学习 -- 信号
终于讲到信号部分,很多比较重要的应用程序都需处理信号.第 9 章需要先了解信号机制再看,所以先跳过不讲.现在开始详解信号. 一.信号概念 信号是提供异步事件处理机制的软件中断. 这些异步事件可能来自硬 ...
- UNIX再学习 -- 内存管理
C 语言部分,就一再的讲内存管理,参看:C语言再学习 -- 再论内存管理 UNIX.Linux 部分还是要讲,足见其重要. 一.存储空间布局 1.我们先了解一个命令 size,继而引出我们今天要讲的 ...
- UNIX再学习 -- 记录锁
APUE第 3 章,参看:UNIX再学习 -- 文件I/O fcntl 函数它的记录锁功能我们当时没讲.接下来就详细说明下. 一.读写冲突 1.如果两个或两个以上的进程同时向一个文件的某个特定的区域 ...
- UNIX再学习 -- 守护进程(转)
参看:守护进程 一.什么是守护进程 守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程.它是一个生存期较长的进程,通常独立于控制 ...
- UNIX再学习 -- 线程
终于要讲到线程部分,线程和进程让人够头痛的内容. 一.线程概念 老样子,我们还是按我们讲进程时的方式说起,参看:UNIX再学习 -- 进程环境 首先需要了解下,什么是线程. Linux 下的线程,可能 ...
- UNIX再学习 -- 进程关系
APUE 第 10 章信号讲完,回过头来看一下第 9 章的进程关系.终端登录和网络登录部分,我们只讲 Linux 系统的. 一.终端登录 我记得我们讲 root 登录设置时有提到,参看:C语言再学习 ...
- UNIX再学习 -- 函数abort
abort 函数之前有讲过的,参看:C语言再学习 -- 关键字return和exit ()函数 然后我们在讲 8 中进程终止时,也说过.参看:UNIX再学习 -- exit 和 wait 系列函数 下 ...
最新文章
- python写出表白_用Python写一个简单的表白-Go语言中文社区
- jQuery :nth-child前有无空格的区别
- 【Python基础】学习用Pandas处理分类数据!
- ACM:回溯,八皇后问题,素数环
- ​电赛 | 19年全国一等奖,北航学子回忆录。
- Exchange Online Whitelist
- 利用dynamoRIO实现codeCoverage
- SketchUp Pro 2019 for Mac(草图大师)
- 马哥 linux 活动,马哥linux | Linux系统性能和使用活动监控工具 sysstat
- 计算机文化与社会发展
- 小程序中如何引用阿里图标
- 原装win10下安装双系统win7
- 深度学习——人生为数不多的好出路
- 火狐浏览器不支持html5,解决火狐浏览器扩展版本不兼容问题
- 2022年11月网络工程师考试知识点分布
- https://blog.csdn.net/qq_43412289
- 【附源码】计算机毕业设计SSM企业合同管理系统
- 单调队列优化dp--bzoj5185: [Usaco2018 Jan]Lifeguards
- 机器学习 day15异常检测
- 使用 JS-SDK 与 FLOW 交互