System V共享内存是一种在Unix和类Unix操作系统上用于进程间通信的机制。它允许多个进程共享同一块物理内存区域,从而可以在这些进程之间传递数据。
应用场景:
优点:
缺点:
ftok函数用于生成一个System V IPC对象(如消息队列、共享内存等)的key。它将pathname和proj_id组合起来,生成一个唯一的key,用于标识一个System V IPC对象。
key_t ftok(const char *pathname, int proj_id);
创建一个新的共享内存或获取一个已存在的共享内存的标识符。
int shmget(key_t key, size_t size, int shmflg);
shmflg
选项:
IPC_CREAT
:如果共享内存不存在,则创建一个新的共享内存段。IPC_EXCL
:与 IPC_CREAT
一起使用时,如果共享内存已经存在,则返回错误。IPC_PRIVATE
或者 IPC_CREAT | 0666
,用于指定共享内存的访问权限将共享内存连接到当前进程的地址空间,使得进程可以访问共享内存中的数据。
void *shmat(int shmid, const void *shmaddr, int shmflg);
shmflg
选项:
SHM_RDONLY
:将共享内存连接为只读模式,进程无法对共享内存进行写操作。0
:通常使用0,表示默认的连接方式。将共享内存从当前进程的地址空间分离,使得进程不再能够访问共享内存中的数据。
int shmdt(const void *shmaddr);
对共享内存执行各种控制操作,比如删除共享内存、获取共享内存状态等。
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
cmd
选项:
IPC_STAT
:获取共享内存的状态信息。IPC_SET
:设置共享内存的状态信息。IPC_RMID
:删除共享内存。IPC_INFO
:获取系统关于共享内存的信息。struct shmid_ds {
struct ipc_perm shm_perm; // 共享内存的权限和拥有者信息
size_t shm_segsz; // 段的大小(字节)
time_t shm_atime; // 最后一次连接时间
time_t shm_dtime; // 最后一次分离时间
time_t shm_ctime; // 最后一次改变时间
pid_t shm_cpid; // 创建者的进程ID
pid_t shm_lpid; // 最后一次调用shmat(2)/shmdt(2)的进程ID
unsigned short shm_nattch; // 当前连接的进程数
};
struct ipc_perm {
key_t __key; // 键值
uid_t uid; // 所有者的用户ID
gid_t gid; // 所有者的组ID
uid_t cuid; // 创建者的用户ID
gid_t cgid; // 创建者的组ID
unsigned short mode; // 权限
unsigned short __seq; // 序列号
};
ipcs命令用于显示系统中的IPC资源信息,包括消息队列、共享内存和信号量。-m选项表示只显示共享内存的信息:
ipcs -m
ipcrm命令用于删除指定的 IPC 对象,包括共享内存:
ipcrm -m
测试代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MEM_FILE_PATH "/home/mem"
#define MEM_SIZE 1024
// 打印时分秒的宏
#define PRINT_MIN_SEC() do { \
time_t t = time(NULL); \
struct tm *tm_ptr = localtime(&t); \
printf("%02d:%02d:%02d:", tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec); \
} while (0)
int main(int argc, char *argv[])
{
key_t key;
int shmid;
char *data = NULL;
int SendNum = 0;
char DataTemp[16] = {0};
// 文件不存在则创建文件
if (-1 == access(MEM_FILE_PATH, F_OK))
{
system("touch "MEM_FILE_PATH);
}
// 获取key
if((key = ftok(MEM_FILE_PATH, 'a')) < 0)
{
return 0;
}
shmid = shmget(key, MEM_SIZE, IPC_CREAT | 0666);
// 命令行参数
// 第一个参数 W表示每2秒写入一次数据 R表示每1秒读取数据
if (argc != 2)
{
printf("Usage: %s W|R|D\n", argv[0]);
return 0;
}
data = (char*)shmat(shmid, (void*)0, 0);
if (!strcmp(argv[1], "W"))
{
while(1)
{
bzero(DataTemp, sizeof(DataTemp));
bzero(data, MEM_SIZE);
sprintf(DataTemp, "Data-%d", SendNum);
SendNum++;
strcpy(data, DataTemp);
PRINT_MIN_SEC();
printf("Write:%s\n",DataTemp);
if(SendNum == 5)
{
break;
}
sleep(2);
}
shmdt(data);
}
else if (!strcmp(argv[1], "R"))
{
while(1)
{
bzero(DataTemp, sizeof(DataTemp));
SendNum++;
strcpy(DataTemp, data);
PRINT_MIN_SEC();
printf("Read:%s\n",DataTemp);
if(SendNum == 10)
{
break;
}
sleep(1);
}
shmdt(data);
}
else if (!strcmp(argv[1], "D"))
{
if(!shmctl(shmid, IPC_RMID, NULL))
{
printf("Delete OK\n");
}
}
else
{
printf("Usage: %s W|R|D\n", argv[0]);
return 0;
}
}
运行程序指定不同参数实现向共享内存进行读写:
测试删除功能:
上述3.1的示例中程序通过读取频率超多写入频率的方式保证获取到所有数据,此方法在正常编程过程中不会使用,需要通过信号量等方式实现进程间读写的同步,信号量编程可参考之前的文章:linux应用 进程间通信之信号量(System V)。
编写测试用例,使用信号量实现读写同步,测试代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MEM_FILE_PATH "/home/mem"
#define SEM_FILE_PATH "/home/sem1"
#define MEM_SIZE 1024
// 打印时分秒的宏
#define PRINT_MIN_SEC() do { \
time_t t = time(NULL); \
struct tm *tm_ptr = localtime(&t); \
printf("%02d:%02d:%02d:", tm_ptr->tm_hour, tm_ptr->tm_min, tm_ptr->tm_sec); \
} while (0)
int main(int argc, char *argv[])
{
key_t key, key_sem;
int shmid, semid;
char *data = NULL;
int SendNum = 0;
char DataTemp[16] = {0};
struct sembuf sops = {0};
// 文件不存在则创建文件
if (-1 == access(MEM_FILE_PATH, F_OK))
{
system("touch "MEM_FILE_PATH);
}
// 文件不存在则创建文件
if (-1 == access(SEM_FILE_PATH, F_OK))
{
system("touch "SEM_FILE_PATH);
}
// 获取key
if((key = ftok(MEM_FILE_PATH, 'a')) < 0)
{
return 0;
}
// 获取key
if((key_sem = ftok(SEM_FILE_PATH, 'a')) < 0)
{
return 0;
}
shmid = shmget(key, MEM_SIZE, IPC_CREAT | 0666);
// 信号量[0]用于写P操作 读V操作
// 信号量[1]用于写V操作 读P操作
semid = semget(key_sem, 2, IPC_CREAT | 0666);
if(semid < 0)
{
perror("Failed to create semaphore");
}
// 命令行参数
// 第一个参数 W表示写入 R表示读取
if (argc != 2)
{
printf("Usage: %s W|R|D\n", argv[0]);
return 0;
}
data = (char*)shmat(shmid, (void*)0, 0);
// 信号量值初始化
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
} arg;
arg.val = 0;
semctl(semid, 0, SETVAL, arg);
semctl(semid, 1, SETVAL, arg);
if (!strcmp(argv[1], "W"))
{
// 设置信号量[1]的值为0
arg.val = 0;
semctl(semid, 1, SETVAL, arg);
while(1)
{
// 写前执行信号量[0]P操作 等读操作发起后将信号量设置为1 然后进行写操作
sops.sem_flg = 0;
sops.sem_op = -1;
sops.sem_num = 0;
semop(semid, &sops, 1);
// 向共享内存写入数据
bzero(DataTemp, sizeof(DataTemp));
bzero(data, MEM_SIZE);
sprintf(DataTemp, "Data-%d", SendNum);
SendNum++;
strcpy(data, DataTemp);
// 写完后执行信号量[1]V操作 通知读进程可以读取
sops.sem_flg = 0;
sops.sem_op = 1;
sops.sem_num = 1;
semop(semid, &sops, 1);
PRINT_MIN_SEC();
printf("Write:%s\n",DataTemp);
if(SendNum == 5)
{
break;
}
}
shmdt(data);
}
else if (!strcmp(argv[1], "R"))
{
// 设置信号量[0]的值为1 通知写进程可以写入
arg.val = 1;
semctl(semid, 0, SETVAL, arg);
while(1)
{
// 读前执行信号量[1]P操作 等待写进程写完数据
sops.sem_flg = 0;
sops.sem_op = -1;
sops.sem_num = 1;
semop(semid, &sops, 1);
// 从共享内存读取数据
bzero(DataTemp, sizeof(DataTemp));
SendNum++;
strcpy(DataTemp, data);
// 读后执行信号量[0]V操作 让写进程进行下一次写入
sops.sem_flg = 0;
sops.sem_op = 1;
sops.sem_num = 0;
semop(semid, &sops, 1);
PRINT_MIN_SEC();
printf("Read:%s\n",DataTemp);
if(SendNum == 5)
{
break;
}
}
shmdt(data);
}
else if (!strcmp(argv[1], "D"))
{
if(!shmctl(shmid, IPC_RMID, NULL))
{
printf("Delete OK\n");
}
}
else
{
printf("Usage: %s W|R|D\n", argv[0]);
return 0;
}
}
使用两个信号量实现读写同步,不再需要时间函数,测试结果如下:
本文阐述了进程间通信之共享内存(System V)的定义,列举了编程中使用的接口和linux命令,编写了测试用例测试相关功能。