一、 信号量问题说明
信号量是一个控制量,作用类似于门卫,资源是一个没有围墙的车库,车辆可以通过门卫进入车库(执行P操作),也可以不通过门卫直接开进去(不执行P操作)。信号量的初值设置为0,表示门卫那里不能通过车辆,如果有车辆要通过门卫使用车库(执行P操作),必须等待;但是如果不通过门卫而直接使用车库(不执行P操作),则车辆不需要等待。对于不通过门卫直接使用车库的车辆,使用完毕后在离开前,可以告诉门卫,里面有一个空车位(相当于执行V操作),这样门卫就知道有空车位,外面等待的车子就可以执行P操作。
【注意】对于一个已经存在的信号量,运行semget可以获取这个信号量集的标识符,但必须指定正确的键值。进程间通信时,不论是进程之间是否具有亲缘关系,都可以通过这种方式使用同一个信号量。
【例】 进程1:semget(ftok(“.” , ’a’) , 1 , 0666) //创建一个新的信号量集
进程2:semget(ftok(“.” , ’a’) , 1 , 0666) //获取这个信号量集标识符
进程3:semget(ftok(“.” , ’a’) , 1 , 0666) //获取这个信号量集标识符
二、 共享内存
1. 共享内存概述
共享内存是一种最为高效的进程间通信方式,因为进程可以直接读写内存,不需要任何数据的复制。为了在多个进程间交换信息,内核专门留出了一块内存区,这段内存区可以由需要访问的进程将其映射到自己的私有地址空间。因此,进程就可以直接读写这一内存区而不需要进行数据的复制,从而大大提高了效率。当然,由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等。其原理示意图如下图所示。
2. 共享内存的应用
1) 函数说明
共享内存的实现分为两个步骤,第一步是创建共享内存或获取已存在的共享内存的标识符,这里用到的函数是shmget(),也就是从内存中获得一段共享内存区域,第二步映射共享内存,也就是把这段创建的共享内存映射到具体的进程空间中,这里使用的函数是shmat()。到这里,就可以使用这段共享内存了,也就是可以使用不带缓冲的 I/O 读写命令对其进行操作。除此之外,还有撤销映射的操作,其函数为shmdt()。最后,还需要删除共享内存,函数为shmctl()。这里就主要介绍这 4个函数。
2) 函数格式
表20列举了shmget()函数的语法要点。
表20 shmget()函数语法要点 | |
所需头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> |
函数原型 | int shmget(key_t key, int size, int shmflg) |
函数传入值 | key:共享内存的键值,多个进程可以通过它访问同一个共享内存,其中有个特殊值IPC_PRIVATE,用于创建当前进程的私有共享内存 |
size:共享内存区大小 | |
shmflg:同open()函数的权限位,也可以用八进制表示法 | |
返回值 | 成功:共享内存段标识符 |
出错:-1(错误原因在errno中) |
表21列举了shmat()函数的语法要点。
表21 shmat()函数语法要点 | ||
所需头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> | |
函数原型 | char *shmat(int shmid, const void *shmaddr, int shmflg) | |
函数传入值 | shmid:要映射的共享内存区标识符 | |
shmaddr:将共享内存映射到指定地址(若为0 则表示系统自动分配地址并把该段共享内存映射到调用进程的地址空间) | ||
shmflg | SHM_RDONLY:共享内存只读 | |
默认0:共享内存可读写 | ||
返回值 | 成功:被映射的段地址 | |
出错:-1(错误原因在errno中) |
表22列举了shmdt()函数的语法要点。
表22 shmdt()函数语法要点 | |
所需头文件 | #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> |
函数原型 | int shmdt(const void *shmaddr) |
函数传入值 | shmaddr:被映射的共享内存段地址 |
返回值 | 成功:0 |
出错:-1(错误原因在errno中) |
表23列举了shmctl()函数的语法要点。
表23 shmctl()函数语法要点 | ||
所需头文件 | #include <sys/types.h> #include <sys/shm.h> | |
函数原型 | int shmctl(int shmid, int cmd, struct shmid_ds *buf) | |
函数传入值 | shmid:共享内存区标识符 | |
cmd | PC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中 | |
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内 | ||
IPC_RMID:删除这片共享内存 | ||
buf:共享内存管理结构体,作用和semctl()中的结构体类似 | ||
返回值 | 成功:0 | |
出错:-1(错误原因在errno中) |
3) 使用实例
该实例说明如何使用基本的共享内存函数。
l 首先是创建一个共享内存区(采用的共享内存的键值为IPC_PRIVATE),之后创建子进程,在父子两个进程中将共享内存分别映射到各自的进程地址空间之中。
l 父进程先等待用户输入,然后将用户输入的字符串写入到共享内存,之后往共享内存的头部写入“ WROTE”字符串表示父进程已成功写入数据。
l 子进程一直等到共享内存的头部字符串为“ WROTE”,然后将共享内存的有效数据(在父进程中用户输入的字符串)在屏幕上打印。
l 父子两个进程在完成以上工作之后,分别解除与共享内存的映射关系。
l 最后在子进程中删除共享内存(也可以在父进程中删除)。
l 因为共享内存自身并不提供同步机制,所以应该额外实现不同进程之间的同步(例如:信号量)。为了简单起见,在本实例中用标志字符串来实现非常简单的父子进程之间的同步。
这里要介绍的一个命令是ipcs,这是用于报告进程间通信机制状态的命令。它可以查看共享内存(-m)、消息队列(-q)、信号量(-s)等各种进程间通信机制的情况,这里使用了system()函数用于调用 shell 命令“ipcs”。程序源代码如下所示:
/* shmem.c */ #include<sys/types.h> #include<sys/ipc.h> #include<sys/shm.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #define BUFFER_SIZE 2048 int main() { pid_t pid; int shmid; /*共享内存标识符 */ char *shm_addr; /* 共享内存映射地址 */ char flag[] = "WROTE"; char buff[BUFFER_SIZE]; /* 创建共享内存 */ if ((shmid = shmget(IPC_PRIVATE, BUFFER_SIZE, 0666)) < 0) { perror("shmget"); exit(1); } else { printf("Create shared-memory: %d\n",shmid); } /* 显示共享内存情况 */ system("ipcs -m"); pid = fork(); if (pid == -1) { perror("fork"); exit(1); } else if (pid == 0) /* 子进程处理 */ { /*映射共享内存*/ if ((shm_addr = shmat(shmid, 0, 0)) == (void*)-1) { perror("Child: shmat"); exit(1); } else { printf("Child: Attach shared-memory: %p\n", shm_addr); } system("ipcs -m"); /* 通过检查在共享内存的头部是否标志字符串"WROTE"来确认父进程已经向共享内存写入有效数据 */ while (strncmp(shm_addr, flag, strlen(flag))) { printf("Child: Wait for enable data...\n"); sleep(5); } /* 获取共享内存的有效数据并显示 */ strcpy(buff, shm_addr + strlen(flag)); printf("Child: Shared-memory :%s\n", buff); /* 解除共享内存映射 */ if ((shmdt(shm_addr)) < 0) { perror("shmdt"); exit(1); } else { printf("Child: Deattach shared-memory\n"); } system("ipcs -m"); /* 删除共享内存 */ if (shmctl(shmid, IPC_RMID, NULL) == -1) { perror("Child: shmctl(IPC_RMID)\n"); exit(1); } else { printf("Delete shared-memory\n"); } system("ipcs -m"); } else/* 父进程处理 */ { /*映射共享内存*/ if ((shm_addr = shmat(shmid, 0, 0)) == (void*)-1) { perror("Parent: shmat"); exit(1); } else { printf("Parent: Attach shared-memory: %p\n", shm_addr); } sleep(1); printf("\nInput some string:\n"); fgets(buff, BUFFER_SIZE, stdin); strncpy(shm_addr + strlen(flag), buff, strlen(buff)); strncpy(shm_addr, flag, strlen(flag)); /* 解除共享内存映射 */ if ((shmdt(shm_addr)) < 0) { perror("Parent: shmdt"); exit(1); } else { printf("Parent: Deattach shared-memory\n"); } system("ipcs -m"); waitpid(pid, NULL, 0); printf("Finished\n"); } exit(0); } |
下面是运行结果。从该结果可以看出,nattch的值随着共享内存状态的变化而变化,共享内存的值根据不同的系统会有所不同。
[root@localhost between_process]# ./a.out Create shared-memory: 1081365 /* 在刚创建共享内存时(尚未有任何地址映射)共享内存的情况 */ ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000000 98304 root 600 393216 2 dest …… 0x00000000 983060 root 666 2048 0 0x00000000 1081365 root 666 2048 0 Parent: Attach shared-memory: 0xb7723000/* 共享内存的映射地址 */ Child: Attach shared-memory: 0xb7723000 /* 在父子进程中进行共享内存的地址映射后共享内存的情况 */ ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000000 98304 root 600 393216 2 dest …… 0x00000000 983060 root 666 2048 0 0x00000000 1081365 root 666 2048 2 Child: Wait for enable data... Input some string: hello/* 用户输入字符串“hello” */ Parent: Deattach shared-memory /* 在父进程中解除共享内存的映射关系后共享内存的情况 */ ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000000 98304 root 600 393216 2 dest …… 0x00000000 983060 root 666 2048 0 0x00000000 1081365 root 666 2048 1 /* 在子进程中读取共享内存的有效数据并打印 */ Child: Shared-memory :hello Child: Deattach shared-memory /* 在子进程中解除共享内存的映射关系后共享内存的情况 */ ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000000 98304 root 600 393216 2 dest …… 0x00000000 983060 root 666 2048 0 0x00000000 1081365 root 666 2048 0 Delete shared-memory /* 在删除共享内存后共享内存的情况,shmid为1081365的内存空间已经释放*/ ------ Shared Memory Segments -------- key shmid owner perms bytes nattch status 0x00000000 98304 root 600 393216 2 dest …… 0x00000000 983060 root 666 2048 0 Finished |