Linux进程间的通信方式之共享内存 + 信号量

系列文章目录

Linux进程间的通信方式(一)System V 消息队列
Linux进程间的通信方式(二)System V 共享内存
Linux进程间的通信方式(三)System V 信号量



前言

提示:关于共享内存以及信号量我已经在前面的章节讲过了,学习本章节之前请先翻阅前两章节

本文主要探讨进程间的通信方式之共享内存+信号量,最后通过一个典型的的生产者与消费者功能场景实现一个进程间数据读写的同步功能。


1. 共享内存+信号量应用示例

在前面关于共享内存这一章节的最后我们留下了一个还没解决的问题。那就是共享内存本身并不提供同步机制, 当多个进程同时访问临界资源的时候就有可能会造成数据的读写冲突从而导致读取到不正确的数据,同时也不能做到控制进程间的读写顺序。那么在学习完信号量这一章节以后我们了解到信号量提供的同步与互斥机制恰好就能弥补共享内存的这一缺点。

本文主要介绍 System V 接口类型的共享内存+信号量实现进程间的通信以及同步与互斥功能,接下来让我们看一下示例代码吧。

1.1 应用示例代码

共享内存头文件:share_memory.h

#ifndef _SHAREMEMORY_H
#define _SHAREMEMORY_H

#ifdef __cplusplus
extern "C" {
#endif

int shm_init(const char *path, int proj_id, int shm_pagesize);

#ifdef __cplusplus
}
#endif

#endif // !_SHAREMEMORY_H

共享内存实现文件:share_memory.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>

#include "share_memory.h"

/* 申请或获取一段共享内存并返回操作标识符 */
int shm_init(const char *path, int proj_id, int shm_pagesize) 
{
    key_t keyval = ftok(path, proj_id);

    int shm_id = shmget(keyval, shm_pagesize, IPC_CREAT | IPC_EXCL | 0666);
    if (shm_id < 0) {
        if (errno == EEXIST) {
            shm_id = shmget(keyval, 0, 0);
            printf("the shared memory segment whose key is %d and shmid is %d is already exist!\n", keyval, shm_id);
            return shm_id;
        } else
            perror("shmget");
    }

    return shm_id;
}

信号量头文件:semaphore.h

#ifndef _SEMAPHORE_H
#define _SEMAPHORE_H

#ifdef __cplusplus
extern "C" {
#endif

#include <sys/types.h>
#include <sys/sem.h>
#include <sys/ipc.h>

// 错误处理宏
#define ERR_EXIT(m) \
    do { \
        perror(m); \
        exit(EXIT_FAILURE); \
    } while (0)

union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *__buf;
};

int create_sem(const char *path, int proj_id, int nsems);
int init_sem(int semid, int semnum, int value);
int del_sem(int semid);
int sem_p(int semid, int semnum);
int sem_v(int semid, int semnum);

#ifdef __cplusplus
}
#endif

#endif // !_SEMAPHORE_H

信号量实现文件:semaphore.c

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include "semaphore.h"

/* 创建或者获取一个信号量集 */
int create_sem(const char *path, int proj_id, int nsems) 
{
    key_t keyval = ftok(path, proj_id);

    int semid = semget(keyval, nsems, IPC_CREAT | IPC_EXCL | 0666);
    if (semid == -1) {
        if (errno == EEXIST) {
            semid = semget(keyval, 0, 0);
            printf("the semaphore set whose key is %d and semid is %d is already exist!\n", keyval, semid);
            return semid;
        } else 
            perror("semget");
    }

    return semid;
}

/* 初始化信号量集 */
int init_sem(int semid, int semnum, int value) {
    union semun su;
    su.val = value;
    if (semctl(semid, semnum, SETVAL, su) == -1) {
        ERR_EXIT("semctl");
    }
    return 0;
}

/* 删除信号量集 */
int del_sem(int semid) {
    if (semctl(semid, 0, IPC_RMID) == -1) {
        ERR_EXIT("semctl");
    }
    return 0;
}

/* P 操作(-1)阻塞获取一个信号量 */
int sem_p(int semid, int semnum) {
    struct sembuf sb = {semnum, -1, 0};
    if (semop(semid, &sb, 1) == -1) {
        ERR_EXIT("semop");
    }
    return 0;
}

/* V 操作(+1)释放一个信号量 */
int sem_v(int semid, int semnum) {
    struct sembuf sb = {semnum, 1, 0};
    if (semop(semid, &sb, 1) == -1) {
        ERR_EXIT("semop");
    }
    return 0;
}

公共头文件:comm_struct.h

#ifndef _COMM_STRUCT_H
#define _COMM_STRUCT_H

#ifdef __cplusplus
extern "C" {
#endif

#define SEM_PATH_NAME "/tmp"
#define SEM_PROJ_ID 0x6666

#define SHM_PATH_NAME "/"
#define SHM_PROJ_ID 0x7777
#define SHM_PROJ_ID2 0x8888

#define MSG_SIZE 1024
#define SHM_PAGE_SIZE 1024*4

typedef struct {
    int msg_type;
    char msg[MSG_SIZE];
}MsgFrame;

#ifdef __cplusplus
}
#endif

#endif // !_COMM_STRUCT_H

生产者文件:producer.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <time.h>
#include <signal.h>

#include "comm_struct.h"
#include "semaphore.h"
#include "share_memory.h"

int g_running = 1;

void handle_sigint(int sig) {
    printf("Caught signal %d, stopping...\n", sig);
    g_running = 0;
}

//获取当前系统时间并格式化输出
void get_formatted_time(char *buffer, size_t buffer_size) {
    struct timespec ts;
    struct tm *tm_info;

    // 获取当前时间
    clock_gettime(CLOCK_REALTIME, &ts);

    // 将时间转换为可读的格式
    tm_info = localtime(&ts.tv_sec);
    strftime(buffer, buffer_size, "%Y年%m月%d日%H时%M分%S秒", tm_info);
}

int main(int argc, char *argv[])
{
    const char *shm_msg = "hello world!";
    char time_info[MSG_SIZE];
    int ret, shm_id, sem_id;

    // 信号注册
    signal(SIGINT, handle_sigint);

    /*创建或者获取一个共享内存标识符*/
    shm_id = shm_init(SHM_PATH_NAME, SHM_PROJ_ID, SHM_PAGE_SIZE);
    if (shm_id < 0) {
        printf("get share memory fail !\n");
        return -1;
    }

    /*将当前进程地址空间映射到共享内存中*/
    MsgFrame* msg_ptr = (MsgFrame*)shmat(shm_id, NULL, 0);
    if (msg_ptr == (MsgFrame*)(-1)) {
        perror("shmat");
        return -1;
    }

    /*创建或者获取一个信号量集并获取一个操作符,信号量个数为2,一个用于互斥,一个用于同步*/
    sem_id = create_sem(SEM_PATH_NAME, SEM_PROJ_ID, 2);
    if (sem_id < 0)
        return -1;

    /*初始化信号量sem0值为1,互斥信号量*/
    init_sem(sem_id, 0, 1);

    /*初始化信号量sem1值为0,同步信号量*/
    init_sem(sem_id, 1, 0);
    
    /*sem0 = 1, sem1 = 0*/
    /*往共享内存中写入数据*/
    for (int i = 0; i < 10 && g_running; i++) {
        printf("producer,pid:%d,waiting for a signal !\n", getpid());
        sem_p(sem_id, 0);  // P(sem0 -1)操作,进入临界区,获取互斥信号
        msg_ptr->msg_type = i;
        memcpy(msg_ptr->msg, shm_msg, strlen(shm_msg));
        memset(time_info, 0, sizeof(time_info));
        get_formatted_time(time_info, sizeof(time_info));
        printf("producer,pid:%d,write tiem:%s\nmsg_type:%d,msg:%s\n", getpid(), time_info, msg_ptr->msg_type, msg_ptr->msg);
        sleep(1);
        sem_v(sem_id, 1);  // V(sem1 +1)操作,离开临界区,释放同步信号
        
    }

    /*将共享内存从当前进程分离出来*/
    ret = shmdt(msg_ptr);
    if (ret)
        perror("shmdt");

    return 0;
}

消费者文件:consumer.c

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/shm.h>
#include <time.h>

#include "comm_struct.h"
#include "share_memory.h"
#include "semaphore.h"

//获取当前系统时间并格式化输出
void get_formatted_time(char *buffer, size_t buffer_size) {
    struct timespec ts;
    struct tm *tm_info;

    // 获取当前时间
    clock_gettime(CLOCK_REALTIME, &ts);

    // 将时间转换为可读的格式
    tm_info = localtime(&ts.tv_sec);
    strftime(buffer, buffer_size, "%Y年%m月%d日%H时%M分%S秒", tm_info);
}

int main(int argc, char *argv[])
{
    int ret, shm_id, sem_id;
    char time_info[MSG_SIZE];

    /*创建或者获取一个共享内存标识符*/
    shm_id = shm_init(SHM_PATH_NAME, SHM_PROJ_ID, SHM_PAGE_SIZE);
    if (shm_id < 0) {
        printf("get share memory fail !\n");
        return -1;
    }

    /*将当前进程地址空间映射到共享内存中*/
    MsgFrame* msg_ptr = (MsgFrame*)shmat(shm_id, NULL, 0);
    if (msg_ptr == (MsgFrame*)(-1)) {
        perror("shmat");
        return -1;
    }

    /*创建或者获取一个信号量集并获取一个操作符,信号量个数为2,一个用于互斥sem0,一个用于同步sem1*/
    int semid = create_sem(SEM_PATH_NAME, SEM_PROJ_ID, 2);
    if (semid < 0)
        return -1;

    /*sem0 = 1, sem1 = 0*/
    /*从共享内存中读取数据*/
    while (1) {
        printf("consumer,pid:%d waiting for a synchronizing signal!\n", getpid());
        sem_p(semid, 1); // P(sem1 -1)操作,进入临界区,阻塞等待同步信号
        memset(time_info, 0, sizeof(time_info));
        get_formatted_time(time_info, sizeof(time_info));
        printf("consumer,pid:%d,read time:%s\nmsg_type:%d,msg:%s\n", getpid(), time_info, msg_ptr->msg_type, msg_ptr->msg);
        sem_v(semid, 0); // V(sem0 + 1)操作,离开临界区,释放互斥信号
    }

    /*将共享内存从当前进程分离出来*/
    ret = shmdt(msg_ptr);
    if (ret)
        perror("shmdt");

    return 0;
}

Makefile文件

all:producer consumer

producer:producer.c share_memory.c semaphore.c
	gcc -g -o $@ $^
consumer:consumer.c share_memory.c semaphore.c
	gcc -g -o $@ $^

clean:
	rm -f producer consumer

1.2 应用示例代码调试与分析

1. 先另起两个终端分别执行./consumer后输出如下

终端1:consumer

jeff@jeff:~/jeffPro/practice/IPC/shm_sem$ ./consumer
consumer,pid:2824 waiting for a synchronizing signal!
consumer,pid:2824,read time:2024年07月08日06时50分41秒
msg_type:0,msg:hello world!
consumer,pid:2824 waiting for a synchronizing signal!
consumer,pid:2824,read time:2024年07月08日06时50分43秒
msg_type:2,msg:hello world!
consumer,pid:2824 waiting for a synchronizing signal!
consumer,pid:2824,read time:2024年07月08日06时50分45秒
msg_type:4,msg:hello world!
consumer,pid:2824 waiting for a synchronizing signal!
consumer,pid:2824,read time:2024年07月08日06时50分47秒
msg_type:6,msg:hello world!
consumer,pid:2824 waiting for a synchronizing signal!
consumer,pid:2824,read time:2024年07月08日06时50分49秒
msg_type:8,msg:hello world!
consumer,pid:2824 waiting for a synchronizing signal!



终端2:consumer

jeff@jeff:~/jeffPro/practice/IPC/shm_sem$ ./consumer
the shared memory segment whose key is 1996488706 and shmid is 0 is already exist!
the semaphore set whose key is 1711276034 and semid is 1 is already exist!
consumer,pid:2827 waiting for a synchronizing signal!
consumer,pid:2827,read time:2024年07月08日06时50分42秒
msg_type:1,msg:hello world!
consumer,pid:2827 waiting for a synchronizing signal!
consumer,pid:2827,read time:2024年07月08日06时50分44秒
msg_type:3,msg:hello world!
consumer,pid:2827 waiting for a synchronizing signal!
consumer,pid:2827,read time:2024年07月08日06时50分46秒
msg_type:5,msg:hello world!
consumer,pid:2827 waiting for a synchronizing signal!
consumer,pid:2827,read time:2024年07月08日06时50分48秒
msg_type:7,msg:hello world!
consumer,pid:2827 waiting for a synchronizing signal!
consumer,pid:2827,read time:2024年07月08日06时50分50秒
msg_type:9,msg:hello world!
consumer,pid:2827 waiting for a synchronizing signal!

2. 然后再另起一个终端执行./producer后输出如下:

jeff@jeff:~/jeffPro/practice/IPC/shm_sem$ ./producer
the shared memory segment whose key is 1996488706 and shmid is 0 is already exist!
the semaphore set whose key is 1711276034 and semid is 1 is already exist!
producer,pid:2830,waiting for a signal !
producer,pid:2830,write tiem:2024年07月08日06时50分40秒
msg_type:0,msg:hello world!
producer,pid:2830,waiting for a signal !
producer,pid:2830,write tiem:2024年07月08日06时50分41秒
msg_type:1,msg:hello world!
producer,pid:2830,waiting for a signal !
producer,pid:2830,write tiem:2024年07月08日06时50分42秒
msg_type:2,msg:hello world!
producer,pid:2830,waiting for a signal !
producer,pid:2830,write tiem:2024年07月08日06时50分43秒
msg_type:3,msg:hello world!
producer,pid:2830,waiting for a signal !
producer,pid:2830,write tiem:2024年07月08日06时50分44秒
msg_type:4,msg:hello world!
producer,pid:2830,waiting for a signal !
producer,pid:2830,write tiem:2024年07月08日06时50分45秒
msg_type:5,msg:hello world!
producer,pid:2830,waiting for a signal !
producer,pid:2830,write tiem:2024年07月08日06时50分46秒
msg_type:6,msg:hello world!
producer,pid:2830,waiting for a signal !
producer,pid:2830,write tiem:2024年07月08日06时50分47秒
msg_type:7,msg:hello world!
producer,pid:2830,waiting for a signal !
producer,pid:2830,write tiem:2024年07月08日06时50分48秒
msg_type:8,msg:hello world!
producer,pid:2830,waiting for a signal !
producer,pid:2830,write tiem:2024年07月08日06时50分49秒
msg_type:9,msg:hello world!
jeff@jeff:~/jeffPro/practice/IPC/shm_sem$ 

此时系统中相关ipc的信息如下:

jeff@jeff:~$ ipcs -s -i 1

Semaphore Array semid=1
uid=1000         gid=1000        cuid=1000       cgid=1000
mode=0666, access_perms=0666
nsems = 2
otime = Mon Jul  8 06:51:24 2024
ctime = Mon Jul  8 06:51:14 2024
semnum     value      ncount     zcount     pid
0          1          0          0          2827
1          0          2          0          2827

3. 程序分析:
这是一个典型的生产者与消费者功能场景的程序。具体表现为,一开始创建了两个信号量sem0(互斥信号量),sem1(同步信号量)并初始化sem0 = 1, sem1 = 0。假如消费者进程先执行,消费者因为获取不到同步信号量而阻塞(因为信号量未初始化,默认为0)。假如生产者先执行然后再对信号量进行初始化,那么消费者还是会阻塞,直到生产者发出同步信号,消费者才可以解除阻塞进行消费,这样就能实现一个同步的功能。

具体实现逻辑如下图所示

在这里插入图片描述

  • 30
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值