034 UNIX再学习 -- XSI IPC通信方式

                                          
                                                                                   
                                                                                
                                           

有 3 种称作 XSI IPC 的IPC:消息队列、信号量以及共享存储器。我们先来介绍它们相类似的特征:

一、相似特征

1、标识符和键

每个内核中的 IPC 结构(消息队列、信号量和共享存储器)都用一个非负整数的标识符加以引用。
标识符是 IPC 对象的内部名。为使多个合作进程能够在同一 IPC 对象上汇聚,需要提供一个外部命名方案。为此,每个 IPC 对象都与一个键相关联。
无论何时创建 IPC 结构(通常调用 msgget、semget 或 shmget 创建),都应指定一个键。这个键的数据类型是基本系统数据类型 key_t,通常在头文件 <sys/types.h> 中被定义为长整型(并没找到)。这个键由内核变换成标识符。
有多种方法使客户进程和服务器进程在同一 IPC 结构上汇聚。
(1)服务器进程可以指定键 IPC_PRIVATE 创建一个新 IPC 结构,将返回的标识符存放在某处 (如一个文件)以便客户进程取用。键 IPC_PRIVATE 保证服务器进程创建一个新 IPC 结构。这种技术的缺点是:文件系统操作需要服务器进程将整型标识符写到文件中,此后客户进程又要读这个文件取得此标识符。
IPC_PRIVATE 键也可用于父进程子关系。父进程指定 IPC_PRIVATE 创建一个新 IPC 结构,所返回的标识符可供 fork 后的子进程使用。接着,子进程又可将此标识符作为 exec 函数的一个参数传给一个新程序。
(2)可以在一个公用头文件中定义一个客户进程和服务器都认可的键。然后服务器进程指定此键创建一个新的 IPC 结构。这种方法的问题是该键可能已与一个 IPC 结构相结合,在此情况下,get 函数(msgget、semget 或 shmget)出错返回。服务器进程必须处理这一错误,删除已存在的 IPC 结构,然后试着再创建它。
(3)客户进程和服务器进程认同一个路径名和项目 ID (项目ID是 0~255之间的字符值),接着,调用函数 ftok 将这两个值变换为一个键。然后在方法(2)中使用此键。fotk 提供的唯一服务就是由一个路径名和项目 ID 产生一个键。

    
    
  1. #include<sys/ipc.h> 
  2. key_t ftok( const char *path, int id);
  3. 返回值:成功则返回键,出错则返回( 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、权限结构

XSI IPC 为每一个 IPC 结构设置了一个 ipc_perm 结构。该结构规定了权限的所有者。它至少包含以下成员:

    
    
  1. struct ipc_perm{
  2. uid_t uid; //拥有者的有效用户ID
  3. gid_t gid; //拥有者有效组ID
  4. uid_t cuid; //创建者有效用户ID
  5. gid_t cgid; //创建者有效组ID
  6. mode_t mode; //访问权限
  7. .........
  8. };
在创建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、可靠
2、流是受控的
3、面向记录
4、可以用非先进先出方式处理

二、消息队列 (重点)

1、消息队列介绍

消息队列是一个由系统内核负责存储和管理,并通过消息队列标识符引用的消息链表。
我们消息队列简称为队列,其标识符简称为队列 ID。

可以通过 msgget 函数创建一个新的消息队列或获取一个已有的消息队列。可以通过 msgsnd 函数向消息队列的尾端追加消息,所追加的消息除了包含消息数据以外,还包含消息类型和数据长度(以字节为单位)。可以通过 msgrcv 函数从消息队列中提取消息,但不一定非按先进先出的顺序提取,也可以按消息的类型提取

2、基本特点

相较于其他几种 IPC 机制,消息队列具有明显的优势

(1)流量控制
如果系统资源(内存)短缺或者接受消息的进程来不及处理更多的消息,则发送消息的进程会在系统内核的控制下进入睡眠状体,待条件满足后再被内核唤醒,继续之前的发送过程。
(2)面向记录
每个消息都是完整的信息单元,发送端是一个消息一个消息地发,接收端也是一个消息一个消息地收,而不像管道那样收发两端所面对的都是字节流,彼此间没有结构上的一致性.
(3)类型过滤
先进先出是队列的固有特征,但消息队列支持按类型提取消息的做法,这就比严格先进先出的管道具有更大的灵活性。
(4)天然同步
消息队列本身就具有同步机制,空队列不可读,满队列不可写,不发则不收,无需像共享内存那样编写额外的同步代码。

不同系统对消息队列的限制是不一样的

在 Linux 系统中,最大消息数是根据最大队列数和队列中所允许的最大数据量来决定的。其中最大队列数还要根据系统上安装的 RAM 的数量来决定。注意,队列的最大字节数限制进一步限制了队列中将要存储的消息的最大长度。

3、常用函数

(1)函数 msget:创建/获取消息队列


    
    
  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/msg.h>
  4. int msgget(key_t key, int msgflg);
  5. 返回值:成功返回消息队列标识符,失败返回 -1
《1》参数解析
key:消息队列键
msgflg:创建标志,可取以下值:
    0                     获取,不存在即失败
    IPC_CREAT    创建,不存在即创建,已存在即获取
    IPC_EXCL       排斥,已存在即失败

(2)函数 msgsnd:发送信息


    
    
  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/msg.h>
  4. int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
  5. 返回值:成功返回 0,失败返回 -1
《1》参数解析
msqid:消息队列标识符
msgp:指向一个包含消息类型和消息数据的内存块。该内存块的前 4 个字节必须是一个大于 0 的整数,代表消息类型,其后紧跟消息数据。消息数据长度用 msgsz 参数表示。
msgsz:期望发送消息数据(不含消息类型)的字节数。
msgflg:发送标志,一般取 0 即可。
《2》函数解析
注意 msgsnd 函数的 msgp 参数所指向的内存块中包含消息类型,其值必须大于 0,但该函数的 msgsz 参数所表示的期望发送字节数中却不包含消息类型所占的 4 个字节。

如果系统内核中的消息未达上限,则 msgsnd 函数会将欲发送消息加入指定的消息队列并立即返回 0,否则该函数会阻塞,直到系统内核允许加入新消息为止(比如有消息因被接收而离开消息队列)。
msgflg 参数中包含 IPC_NOWAIT 位,则 msgsnd 函数在系统内核中的消息已达上限的情况下不会阻塞,而是返回 -1,并置 errno 为 EAGAIN。

(3)函数 msgrcv:接收消息


     
     
  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/msg.h>
  4. ssize_t msgrcv( int msqid, void *msgp, size_t msgsz, long msgtyp,
  5.                       int msgflg);
  6. 返回值:成功返回所接收消息数据的字节数,失败返回 -1.
《1》参数解析
msqid:消息队列标识符
msgp:指向一块包含消息类型(4 字节)和消息数据的内存。
msgsz:期望接收消息数据(不含消息类型)的字节数。
msgtyp:消息类型,可取以下值:
    msgtyp == 0    提取消息队列的第一条消息
    msgtyp > 0    若 msgflg 参宿不包含 MSG_EXCEPT 位,则提取消息队列的第一条类型为 msgtyp 的消息;若             msgflg 参数包含 MSG_EXCEPT 位,则提取消息队列的第一条类型不为 msgtyp 的消息。
    msgtyp < 0    提取消息队列中类型小于等于 msgtyp 的绝对值的消息,类型越小的消息越被优先提取。
msgflg:接收标志,一般取 0 即可。
《2》函数解析
注意 msgrcv 函数的 msgp 参数所指向的内核块中包含消息类型,其值由该函数输出,但该函数的 msgsz 参数所表示的期望接收字节数以及该函数所返回的实际接收字节数都不包含消息类型所占的 4 个字节。

若存在与 msgtyp 参数匹配的消息,但其数据长度大于 msgsz 参数,且 msgflg 参数包含 MSG_NOERROR 位,则只截取该消息数据的前 msgsz 字节返回,剩余部分直接丢弃;但如果 msgflg 参数不包含 MSG_NOERROR 位,则不处理该消息,直接返回 -1,并置 errno 为 E2BIG。
msgrcv 函数根据 msgtyp 参数对消息队列中的消息有选择地接收,只有满足条件的消息才会被复制到应用程序缓冲区并从内核中删除。如果满足 msgtyp 条件的消息不只一条,则按照先进先出的规则提取。

若消息队列中有可接收消息,则 msgrcv 函数会将该消息移出消息队列,并立即返回所接收到的消息数据的字节数,表示接收成功,否则此函数会阻塞,直到消息队列中有可接收消息为止。
若 msgflg 参数包含 IPC_NOWAIT 位,则 msgrcv 函数在消息队列中没有可接收消息的情况下不会阻塞,而是返回 -1,并置 errno 为 ENOMSG。

(4)函数 msgctl:销毁或控制消息队列


    
    
  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/msg.h>
  4. int msgctl(int msqid, int cmd, struct msqid_ds *buf);
  5. 返回值:成功返回 0,失败返回 -1.
《1》参数解析
msqid:消息队列标识符
cmd:控制命令,可取以下值:
    IPC_STAT    获取消息队列的属性,通过 buf 参数输出。
    IPC_SET      设置消息队列的属性,通过 buf 参数输入。
    仅以下四个属性可以设置:

    
    
  1.         msqid_ds::msg_perm.uid  //拥有者用户 ID
  2.         msqid_ds::msg_perm.gid  //拥有者组 ID
  3.         msqid_ds::msg_perm.mode  //权限
  4.         msqid_ds::msg_qbytes  //队列最大字节数
    IPC_RMID    立即删除消息队列,所有处于阻塞状态的对该消息队列的 msgsnd 和 msgrcv 函数调用,都会立即返回失败,且 errno 为 EIDRM。
buf    msqid_ds 类型的消息队列属性结构。

4、示例说明


    
    
  1. //msgA.c  接收端
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <signal.h>
  7. #include <sys/ipc.h>
  8. #include <sys/msg.h>
  9. #include <errno.h>
  10. #include <sys/stat.h>
  11. #define MSG_FILE "/home/tarena/project/c_test/a.txt"
  12. #define BUFFER 255
  13. #define PERM S_IRUSR | S_IWUSR
  14. struct msgtype
  15. {
  16.   long mtype;
  17.   char buffer[BUFFER + 1];
  18. };
  19. int main (void)
  20. {
  21.   struct msgtype msg;
  22.   key_t key;
  23.   int msgid;
  24.   if ((key = ftok (MSG_FILE, 'a')) == -1)
  25.   perror ( "ftok"), exit ( 1);
  26.   if ((msgid = msgget (key, PERM | IPC_CREAT | IPC_EXCL)) == -1)
  27.   perror ( "msgget"), exit ( 1);
  28.   while ( 1)
  29.  {
  30.   msgrcv (msgid, &msg, sizeof (struct msgtype), 1, 0);
  31.    printf ( "Server Receive: %s\n", msg.buffer);
  32.   msg.mtype = 2;
  33.   msgsnd (msgid, &msg, sizeof (struct msgtype), 0);
  34.  }
  35.   return 0;
  36. }

    
    
  1. //msgB.c 发送端
  2. #include <stdio.h>
  3. #include <string.h>
  4. #include <stdlib.h>
  5. #include <errno.h>
  6. #include <sys/types.h>
  7. #include <sys/ipc.h>
  8. #include <sys/msg.h>
  9. #include <sys/stat.h>
  10. #define MSG_FILE "/home/tarena/project/c_test/a.txt"
  11. #define BUFFER 255
  12. #define PERM S_IRUSR | S_IWUSR
  13. struct msgtype
  14. {
  15.   long mtype;
  16.   char buffer[BUFFER + 1];
  17. };
  18. int main (void)
  19. {
  20.   struct msgtype msg;
  21.   key_t key;
  22.   int msgid;
  23.   if ((key = ftok (MSG_FILE, 'a')) == -1)
  24.   perror ( "ftok"), exit ( 1);
  25.   if ((msgid = msgget (key, PERM)) == -1)
  26.   perror ( "msgget"), exit ( 1);
  27.  msg.mtype = 1;
  28.   strcpy (msg.buffer, "这是客户端发出的消息内容");
  29.  msgsnd (msgid, &msg, sizeof (struct msgtype), 0);
  30.   memset (&msg, '\0', sizeof (struct msgtype));
  31.  msgrcv (msgid, &msg, sizeof (struct msgtype), 2, 0);
  32.   printf ( "Client Receive: %s\n", msg.buffer);
  33.   return 0;
  34. }

    
    
  1. 输出结果:
  2. 在一个终端执行
  3. # ./msgA
  4. Server Receive: 这是客户端发出的消息内容
  5. Server Receive: 这是客户端发出的消息内容
  6. Server Receive: 这是客户端发出的消息内容
  7. Server Receive: 这是客户端发出的消息内容
  8. 在另一个终端执行
  9. # ./msgB
  10. Client Receive: 这是客户端发出的消息内容
  11. # ./msgB
  12. Client Receive: 这是客户端发出的消息内容
  13. # ./msgB
  14. Client Receive: 这是客户端发出的消息内容
  15. # ./msgB
  16. Client Receive: 这是客户端发出的消息内容

5、相关指令

(1)ipcs 命令

《1》功能
ipcs 命令用于报告Linux中进程间通信设施的状态,显示的信息包括消息列表、共享内存和信号量的信息。
《2》选项

     
     
  1. -a:显示全部可显示的信息;
  2. -q:显示活动的消息队列信息;
  3. -m:显示活动的共享内存信息;
  4. -s:显示活动的信号量信息。
《3》示例
ipcs -q 表示查看当前系统中存在的消息队列

    
    
  1. //查看
  2. #  ipcs -q
  3. ------ Message Queues --------
  4. key        msqid      owner      perms      used-bytes   messages   
  5. 0x61017a67 0          root       600        0            0          
  6. 0x61017a6c 32769      root       600        0            0               

(2)ipcrm 命令

《1》功能
ipcrm命令用来删除一个或更多的消息队列、信号量集或者共享内存标识。
《2》选项

     
     
  1. -m SharedMemory id 删除共享内存标识 SharedMemoryID。与 SharedMemoryID 有关联的共享内存段以及数据结构都会在最后一次拆离操作后删除。
  2. -M SharedMemoryKey 删除用关键字 SharedMemoryKey 创建的共享内存标识。与其相关的共享内存段和数据结构段都将在最后一次拆离操作后删除。
  3. -q MessageID 删除消息队列标识 MessageID 和与其相关的消息队列和数据结构。
  4. -Q MessageKey 删除由关键字 MessageKey 创建的消息队列标识和与其相关的消息队列和数据结构。
  5. -s SemaphoreID 删除信号量标识 SemaphoreID 和与其相关的信号量集及数据结构。
  6. -S SemaphoreKey 删除由关键字 SemaphoreKey 创建的信号标识和与其相关的信号量集和数据结构。
《3》示例
ipcrm -q ID 表示删除指定的消息队列

    
    
  1. //删除
  2. # ipcrm -q 32769
  3. //复查
  4. # ipcs -q
  5. ------ Message Queues --------
  6. key        msqid      owner      perms      used-bytes   messages   
  7. 0x61017a67 0          root       600        0            0           

三、信号量

1、信号量介绍

信号量与其它几种 IPC 机制(管道、共享内存和消息队列)都不一样,它的目的不是在进程之间搭建数据流通的桥梁,而是提供一个可为多个进程共同访问的计数器,实时跟踪可用资源的数量,以解决多个用户分享有限资源时的竞争与冲突问题。

2、基本特点

为了获得共享资源,进程需要按以下步骤进行。

(1)测试控制该资源的信号量
(2)若信号量的值大于 0,说明还有可分配资源,则进程获得该资源,并将信号量的值减 1,表示可分配资源少了一个。
(3)若信号量的值等于 0,说明没有可分配资源,则进程进入睡眠状态,直到信号量的值再度大于 0,这时会有一个正在睡眠中等待该资源的进程被系统内核唤醒,它将返回执行步骤 1,而其他进程则继续在睡眠中等待。
(4)当进程不再使用所获得的资源时,应将控制该资源的信号量的值加 1,表示可分配资源多了一个。此时那些正在睡眠中等待该资源的进程中的一个将被系统内核唤醒。
从有关信号的凑走步骤不难看出,对信号量所做的测试和加减操作都是必须是原子化的,用户空间的全局变量显然无法胜任,因此信号量通常被实现在系统内核之中。

system V 的信号量比起通常意义上的信号量要复杂一些

(1)信号量并非被定义为一个简单的非负整数,相反必须把一个或多个信号量放在一起,组成一个信号量集来使用
(2)信号量的创建和初始化必须被分作两步而不能在一个原子操作中完成。
(3)与其它几种 XSI IPC 对象一样,信号量也是系统级对象。如果某进程在其正常或异常终止前,没有恢复信号量里的资源计数,也没有通过函数或命令删除该信号对象,那么这种状态将一直保持下去,并将以后运行的进程构成影响。

内核为每个信号量集合维护着一个 semid_ds 结构:


    
    
  1. struct semid_ds{
  2. struct ipc_perm sem_perm;
  3. unsigned short sem_nsems; //信号量在信号量集中的编号
  4. time_t sem_otime; 最后调用semop()的时间。
  5. time_t sem_ctime; 最后进行change的时间。
  6. ....
  7. }

每个信号量由一个无名结构表示,它至少包含下列成员:


    
    
  1. struct{
  2. unsigned short semval; //信号量值,>=0
  3. pid_t sempid; //最后使用信号量的pid
  4. unsigned short semcnt; //等待semval变为大于其当前值的线程或进程数
  5. unsigned short semzcnt; //等待semval变成0的线程或进程数
  6. }

下图列出了影响信号量集合的系统限制:


3、常用函数 

(1)函数 semget:创建/获取信号量


    
    
  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/sem.h>
  4. int semget(key_t key, int nsems, int semflg);
  5. 返回值:成功返回信号量标识符,失败返回 -1
《1》参数解析
key:信号量键
nsems:信号量个数
semflg:创建标志,可取以下值:
    0                    获取,不存在即失败
    IPC_CREAT    创建,不存在即创建,已存在即获取
    IPC_EXCL       排斥,已存在即失败

(2)函数 semop:操作信号量


    
    
  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/sem.h>
  4. int semop(int semid, struct sembuf *sops, unsigned nsops);
  5. 返回值:成功返回 0,失败返回 -1
《1》参数解析
semid:信号量标识符
sops:操作结构体数组
nsops:操作结构体数组长度
《2》函数解析
作为 semop 函数的参数,sops 和 nsops 所表示的数组由若干操作结构体组成,操作结构体的类型为 sembuf

    
    
  1. struct sembuf {
  2.     unsigned short sem_num;  //信息量编号
  3.     short          sem_op;   //操作数
  4.     short          sem_flg;  //操作标志
  5. };

该结构体数组中的每个元素通过其信号量编号成员与信号量集中的一个特定的信号量对应,表示对该信号量的操作。

semop 函数对 sops 指向的包含 nsops 个元素的操作结构体数组中的每个元素执行如下操作:
1)若 sem_op 大于 0,则将其加到 semid 信号量集第 sem_num 号信号量的值上,以表示对资源的释放。
2)若 sem_op 小于 0,则从 semid 信号量集第 sem_num 号信号量的值中减去其绝对值,以表示对资源的获取;如果不够减(信号量的值不能为负),则此函数会阻塞,直到够减为止,以表示对资源的等待;但如果 sem_flg 包含 IPC_NOWAIT 位,则即使不够减也不会阻塞,而是返回 -1,并置 errno 为 EAGAIN,以便在等待资源时还可做其他处理。
3)若 sem_op 等于 0,则直到 semid 信号量第 sem_num 号信号量的值为 0 时才返回,除非 sem_flg 含 IPC_NOWAIT 位。

(3)函数 semctl:销毁或控制信号量


    
    
  1. #include <sys/types.h>
  2. #include <sys/ipc.h>
  3. #include <sys/sem.h>
  4. int semctl(int semid, int semnum, int cmd, ...);
  5. 返回值:成功返回 0(cmd 取某些值存在例外),失败返回 -1
《1》参数解析
semid:信号量集标识符
semnum:信号量编号。只有针对信号量集中某个具体信号量的操作,才需要此参数;针对整个信号量集的操作,此参数将被忽略,置 0 即可。
cmd:控制命令
《2》函数解析 
该函数的复杂性在于因控制命令 cmd 参数取值的不同,函数参数的个数、类型以及返回值的意义也会有所不同。
1)获取信号量集的属性
int semctl (int semid, 0, IPC_STAT, struct semid_ds* buf);
    
    
2)设置信号量集的属性
int semctl (int semid, 0, IPC_SET, struct semid_ds* buf);
    
    
仅以下四个属性可以设置:

    
    
  1. semid_ds::sem_perm.uid  //拥有者用户ID
  2. semid_ds::sem_perm.gid  //拥有者组 ID
  3. 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、示例说明


    
    
  1. //semA.c  使用信号量集实现进程间的通信
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <sys/ipc.h>
  7. #include <sys/sem.h>
  8. #include <signal.h>
  9. //定义全局变量保存信号量集的ID
  10. int semid;
  11. void fa(int signo)
  12. {
  13.   printf( "正在删除信号量集,请稍后...\n");
  14.  sleep( 2);
  15.   int res = semctl(semid, 0,IPC_RMID);
  16.   if( -1 == res)
  17.  {
  18.   perror( "semctl"), exit( -1);
  19.  }
  20.   printf( "删除信号量集成功\n");
  21.   exit( 0);
  22. }
  23. int main(void)
  24. {
  25.   //1.获取key值,使用ftok函数
  26.   key_t key = ftok( ".", 200);
  27.   if( -1 == key)
  28.  {
  29.   perror( "ftok"), exit( -1);
  30.  }
  31.   printf( "key = %#x\n",key);
  32.   //2.创建信号量集,使用semget函数
  33.  semid = semget(key, 1 /*信号量集的大小*/,IPC_CREAT|IPC_EXCL| 0644);
  34.   if( -1 == semid)
  35.  {
  36.   perror( "semget"), exit( -1);
  37.  }
  38.   printf( "semid = %d\n",semid);
  39.   //3.初始化信号量集,使用semctl函数
  40.   int res = semctl(semid, 0 /*信号量集的下标*/,SETVAL, 5 /*初始值*/);
  41.   if( -1 == res)
  42.  {
  43.   perror( "semctl"), exit( -1);
  44.  }
  45.   printf( "信号量集初始化完毕\n");
  46.   //4.删除信号量集,使用信号2处理
  47.   printf( "删除信号量集,请按ctrl+c...\n");
  48.  signal( 2,fa);
  49.   while( 1);
  50.   return 0;
  51. }

    
    
  1. //semB.c  使用信号量集实现进程间的通信
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <sys/ipc.h>
  7. #include <sys/sem.h>
  8. int main(void)
  9. {
  10.   //1.获取key值,使用ftok函数
  11.   key_t key = ftok( ".", 200);
  12.   if( -1 == key)
  13.  {
  14.   perror( "ftok"), exit( -1);
  15.  }
  16.   printf( "key = %#x\n",key);
  17.   //2.获取信号量集,使用semget函数
  18.   int semid = semget(key, 1, 0);
  19.   if( -1 == semid)
  20.  {
  21.   perror( "semget"), exit( -1);
  22.  }
  23.   printf( "semid = %d\n",semid);
  24.   //3.创建10个子进程模拟抢占资源
  25.   int i = 0;
  26.   for(i = 0; i < 10; i++)
  27.  {
  28.    //创建子进程
  29.    pid_t pid = fork();
  30.    if( 0 == pid) //子进程
  31.   {
  32.     //准备结构体变量
  33.     struct sembuf op;
  34.    op.sem_num = 0; //下标
  35.    op.sem_op = -1; //计数减1
  36.    op.sem_flg = 0; //标志
  37.     //使用semop函数占用资源
  38.    semop(semid,&op, 1 /*大小*/);
  39.     printf( "申请共享资源成功\n");
  40.    sleep( 20);
  41.    op.sem_op = 1;
  42.     //使用semop函数释放资源
  43.    semop(semid,&op, 1);
  44.     printf( "释放共享资源完毕\n");
  45.     exit( 0); //终止
  46.   }
  47.  }
  48.   return 0;
  49. }

    
    
  1. 输出结果:
  2. 在一个终端执行:
  3. # ./semA
  4. key = 0xc801135e
  5. semid = 196608
  6. 信号量集初始化完毕
  7. 删除信号量集,请按ctrl+c...
  8. ^C正在删除信号量集,请稍后...
  9. 删除信号量集成功
  10. 在另一个终端执行:
  11. # ./semB
  12. key = 0xc801135e
  13. semid = 196608
  14. 申请共享资源成功
  15. 申请共享资源成功
  16. 申请共享资源成功
  17. 申请共享资源成功
  18. 申请共享资源成功
  19. # ./semB
  20. key = 0xc801135e
  21. semid = 196608
  22. 申请共享资源成功
  23. 申请共享资源成功
  24. 申请共享资源成功
  25. 申请共享资源成功
  26. 申请共享资源成功
  27. 释放共享资源完毕
  28. 释放共享资源完毕
  29. 释放共享资源完毕
  30. 释放共享资源完毕
  31. 释放共享资源完毕
  32. 释放共享资源完毕

5、相关指令

ipcs -s 表示查看当前系统中存在的信号量

    
    
  1. //查看
  2. # ipcs -s
  3. ------ Semaphore Arrays --------
  4. key        semid      owner      perms      nsems    
  5. 0xc801135e 32768      root       644        1    
ipcrm -s ID 表示删除指定的信号量

    
    
  1. //删除
  2. # ipcrm -s 32768
  3. //复查
  4. # ipcs -s
  5. ------ Semaphore Arrays --------
  6. key        semid      owner      perms      nsems  

四、共享存储

1、共享存储介绍

两个或者更多进程,共享同一块由系统内核负责维护的内存区域,其地址空间通常被映射到堆和栈之间。

2、基本特点

多个进程通过共享内存通信,所传输的数据通过各个进程的虚拟内存被直接反映到同一块物理内存中,这就避免了在不同进程之间来回复制数据的开销。因此,基于共享内存的进程通信,是速度最快的进程间通信方式。
共享内存本身缺乏足够的同步机制,这就需要程序员编写额外的代码来实现。例如服务器进程正在把数据写入共享内存,在这个写入过程完成之前,客户机进程就不能读取该共享内存中的数据。为了建立进程之间的这种同步,可能需要借助于其它的进程间通信机制,如信号或者信号量等,甚至文件锁,而这无疑会增加系统开销。

3、常用函数

(1)函数 shmget:创建新的或获取已有的共享内存


    
    
  1. #include <sys/ipc.h>
  2. #include <sys/shm.h>
  3. int shmget(key_t key, size_t size, int shmflg);
  4. 返回值:成功返回共享内存标识符,失败返回 -1
《1》参数解析
key:共享内存键
size:共享内存大小(以字节为单位),自动向上圆整至页(4096)的整数倍。若欲创建新的共享内存,必须指定                size 参数;若只为获取已有的共享内存,size 参数可取 0.
shmflg:创建标志,可取以下值:
    0                     获取,不存在即失败
    IPC_CREAT    创建,不存在即创建,已存在即获取
    IPC_EXCL       排斥,已存在即失败

(2)函数 shmat:加载共享内存


    
    
  1. #include <sys/types.h>
  2. #include <sys/shm.h>
  3. void *shmat(int shmid, const void *shmaddr, int shmflg);
  4. 返回值:成功返回共享内存起始地址,失败返回 -1
《1》参数解析
shmid:共享内存标识符
shmaddr:指定映射地址,可置 NULL,由系统自动选择
shmflg:加载标志,可取以下值:
    0                           以读写方式使用共享内存
    SHM_RDONLY    以只读方式使用共享内存
    SHM_RND           只在 shmaddr 参数非 NULL 时起作用,表示对该参数自动向下圆整至页(4096)的整数倍
《2》函数解析
shmat 函数负责将给定共享内存映射到调用进程的虚拟内存空间,返回映射区的起始地址,同时将系统内核中共享内存对象的加载计数(shmid_ds::shm_nattch)加 1
调用进程在获得 shmat 函数返回的共享内存起始地址以后,就可以像访问普通内存一样访问共享内存中的数据。

(3)卸载共享内存


    
    
  1. #include <sys/types.h>
  2. #include <sys/shm.h>
  3. void *shmat(int shmid, const void *shmaddr, int shmflg);
  4. int shmdt(const void *shmaddr);
  5. 返回值:成功返回 0,失败返回 -1
《1》参数解析
shmaddr:共享内存起始地址
《2》函数解析
shmdt 函数负责从调用进程的虚拟内存中解除 shmaddr 所指向的映射区到共享内存的映射,同时将系统内核中共享内存对象的加载计数(shmid_ds::shm_nattch)减 1

(4)函数 shmctl:销毁/控制共享内存


    
    
  1. #include <sys/ipc.h>
  2. #include <sys/shm.h>
  3. int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  4. 返回值:成功返回 0,失败返回 -1
《1》参数解析
shmid:共享内存标识符
cmd:控制命令,可取以下值:
    IPC_STAT    获取共享内存的属性,通过 buf 参数输出
    IPC_SET       设置共享内存的属性,通过 buf 参数输入
    仅以下三个属性可以设置:

    
    
  1.         shmid_ds::shm_perm.uid  //拥有者用户 ID
  2.         shmid_ds::shm_perm.gid  //拥有者组 ID
  3.         shmid_ds::shm_perm.mode  //权限
    IPC_RMIF    销毁共享内存,其实并非真的销毁,而只是做一个销毁标志,禁止任何进程对该共享内存形成新的加载,但已有的加载依然保留。只有当其使用者们纷纷卸载,直至其加载计数降为 0 时,共享内存才会真的被销毁。
    buf    shmid_ds 类型的共享内存属性结构

4、示例说明


    
    
  1. //msgA.c
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. #include <stdlib.h>
  5. #include <fcntl.h>
  6. #include <string.h>
  7. #include <sys/stat.h>
  8. #include <sys/shm.h>
  9. #define MSG_FILE "/home/tarena/project/c_test/a.txt"
  10. #define BUFFER 4096
  11. #define PERM S_IRUSR|S_IWUSR
  12. int main()
  13. {
  14.     key_t key;
  15.     if((key=ftok(MSG_FILE, 'a'))== -1)
  16.     {
  17.         perror( "Creat Key Error");
  18.         exit( 1);
  19.     }
  20.    
  21.     int shmid = shmget (key, BUFFER, PERM | IPC_CREAT | IPC_EXCL);
  22.     if (shmid == -1)
  23.     {
  24.         perror ( "shmget");
  25.         exit (EXIT_FAILURE);
  26.     }
  27.    
  28.     void* shmaddr = shmat (shmid, NULL, 0);
  29.     if (shmaddr == ( void*) -1)
  30.     {
  31.         perror ( "shmat");
  32.         exit (EXIT_FAILURE);
  33.     }
  34.     strcpy (shmaddr, "这是放入共享内存的内容!");
  35.    
  36.     if (shmdt (shmaddr) == -1)
  37.     {
  38.         perror ( "shmdt");
  39.         exit (EXIT_FAILURE);
  40.     }
  41.    
  42.     sleep( 10);
  43.    
  44.     if (shmctl (shmid, IPC_RMID, NULL) == -1)
  45.     {
  46.         perror ( "shmctl");
  47.         exit (EXIT_FAILURE);
  48.     }
  49.     return 0;
  50. }

    
    
  1. //msgB.c
  2. #include <stdio.h>
  3. #include <unistd.h>
  4. #include <stdlib.h>
  5. #include <fcntl.h>
  6. #include <string.h>
  7. #include <sys/stat.h>
  8. #include <sys/shm.h>
  9. #define MSG_FILE "/home/tarena/project/c_test/a.txt"
  10. #define BUFFER 4096
  11. #define PERM S_IRUSR|S_IWUSR
  12. int main(int argc,char **argv)
  13. {
  14.     key_t key;
  15.     if((key=ftok(MSG_FILE, 'a'))== -1)
  16.     {
  17.         perror( "Creat Key Error");
  18.         exit( 1);
  19.     }
  20.     int shmid = shmget (key, BUFFER, PERM);
  21.     if (shmid == -1)
  22.     {
  23.         perror ( "shmget");
  24.         exit (EXIT_FAILURE);
  25.     }
  26.    
  27.     void* shmaddr = shmat (shmid, NULL, 0);
  28.     if (shmaddr == ( void*) -1)
  29.     {
  30.         perror ( "shmat");
  31.         exit (EXIT_FAILURE);
  32.     }
  33.     printf ( "%s\n", ( char*)shmaddr);
  34.    
  35.     if (shmdt (shmaddr) == -1)
  36.     {
  37.         perror ( "shmdt");
  38.         exit (EXIT_FAILURE);
  39.     }
  40.     return 0;
  41. }

    
    
  1. 输出结果:
  2. 在一个终端执行:
  3. # ./msgA
  4. 在另一个终端执行:
  5. # ./msgB
  6. 这是放入共享内存的内容!

5、相关指令

ipcs -m 表示查看当前系统中存在的共享存储

    
    
  1. //查看
  2. # ipcs -m
  3. ------ Shared Memory Segments --------
  4. key        shmid      owner      perms      bytes      nattch     status     
  5. 0x00000000 0          root       600        393216     2          dest        
  6. 0x00000000 196609     root       700        25740      2          dest        
  7. 0x00000000 163842     root       700        3219768    2          dest        
  8. 0x00000000 98307      root       700        130752     2          dest        
  9. 0x00000000 229380     root       700        17028      2          dest        
  10. 0x00000000 262149     root       700        13332      2          dest        
  11. 0x00000000 294918     root       700        15180      2          dest        
  12. 0x61017b76 393223     root       600        4096       0                      
ipcrm -m ID 表示删除指定的共享存储

    
    
  1. //删除
  2. # ipcrm -m 393223
  3. //复查
  4. # ipcs -m
  5. ------ Shared Memory Segments --------
  6. key        shmid      owner      perms      bytes      nattch     status     
  7. 0x00000000 0          root       600        393216     2          dest        
  8. 0x00000000 196609     root       700        25740      2          dest        
  9. 0x00000000 163842     root       700        3219768    2          dest        
  10. 0x00000000 98307      root       700        130752     2          dest        
  11. 0x00000000 229380     root       700        17028      2          dest        
  12. 0x00000000 262149     root       700        13332      2          dest        
  13. 0x00000000 294918     root       700        15180      2          dest 

五、未讲部分

协同进程
POSIX 信号量
客户进程-服务器进程属性


                                   
                                   
               
                   
  •                                                
  •                                                     点赞                         2                        
  •                        
  •                                                     收藏
  •                        
  •                                                     分享
  •                                                                                                                        
  •                                                        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值