《Linux C编程实战》笔记:共享内存

共享内存是分配一块能被其他进程访问的内存。每个共享内存段在内核中维护一个内部数据结构shmid_ds(和消息队列、信号量一样),该结构定义在头文件linux/shm.h中,这是我从源码里抄的

#include<linux/shm.h>
struct shmid_ds {
	struct ipc_perm		shm_perm;	/* 操作许可 */
	int			shm_segsz;	/* size of segment (bytes) */
	__kernel_old_time_t	shm_atime;	/* 最后一个进程访问共享内存的时间 */
	__kernel_old_time_t	shm_dtime;	/* 最后一个进程离开共享内存的时间 */
	__kernel_old_time_t	shm_ctime;	/* last change time */
	__kernel_ipc_pid_t	shm_cpid;	/* pid of creator */
	__kernel_ipc_pid_t	shm_lpid;	/* pid of last operator */
	unsigned short		shm_nattch;	/* 当前使用该共享内存段的进程数量 */
	unsigned short 		shm_unused;	/* compatibility */
	void 			*shm_unused2;	/* ditto - used by DIPC */
	void			*shm_unused3;	/* unused */
};

但是总结了上一节的经验,我发现sys/shm.h也同样定义了该结构体,所以真正编写代码时可能只用包含sys/shm.h头文件

共享内存的创建与操作

共享内存区的创建

Linux下使用函数shmget来创建一个共享内存区,或者访问一个已经存在的共享内存区。该函数定义在头文件linux/shm.h中?

但是根据我自己的linux来看,linux/shm.h并没有该函数,在sys/shm.h中定义了该函数,所以以事实为准,认为该函数定义在sys/shm.h中。

并且根据实践,linux/shm.h和sys/shm.h同时包含的话编译不给过,所以下面就只用sys/shm.h了

#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
  • key:用于标识共享内存段的关键字。
  • size:要创建的共享内存段的大小(以字节为单位)。
  • shmflg:用于指定创建共享内存段的访问权限和其他标志位。

返回值:

  • 如果成功,返回一个非负整数,该整数是共享内存段的标识符(即共享内存的ID)。
  • 如果失败,返回 -1,并设置 errno 来指示错误的原因。

该函数用法与消息队列和信号量集的创建一样。key一般通过ftok得到,shmflg的话一般是IPC_CREATE或者IPC_EXCL|IPC_CREATE 再加上权限的组合。具体含义请到消息队列的那一节查看:《Linux C编程实战》笔记:消息队列-CSDN博客

如果是创建的话,size要大于0;如果只是访问,size置为0.

共享内存区的操作

在使用共享内存区之前,必须通过shmat函数将其附加到进程的地址空间。进程与共享进程就建立了连接。shmat调用成功后就会返回一个指向共享内存区的指针,使用该指针就可以访问共享内存区了,如果失败返回-1.该函数定义在sys/shm.h中(这也是我根据实际改的,书上还是说是linux/shm.h)

#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);
  • shmid:要连接的共享内存段的标识符(由 shmget 函数返回的ID)。
  • shmaddr:指定共享内存连接到进程地址空间的起始地址,通常设置为 NULL,让系统自动选择一个合适的地址。
  • shmflg:用于指定连接共享内存的选项,通常为0。

返回值:

  • 如果成功,返回一个指向共享内存段第一个字节的指针。
  • 如果失败,返回 (void *) -1,并设置 errno 来指示错误的原因。

当进程结束时使用共享内存区时,要通过函数shmdt断开与共享区内存的连接。该函数声明在sys/shm.h

#include <sys/shm.h>

int shmdt(const void *shmaddr);

参数shmaddr为shmget的返回值。该函数调用成功后,返回0,否则返回-1.进程脱离共享内存区后,数据结构shmid_ds中的shm_nattch就会减1.但是共享内存段依然存在,只有shm_nattch为0后,即没有任何进程再使用该共享内存区,共享内存区才应该在内核中被删除。一般来说,一个进程终止时,他所附加的共享内存区都会自动脱离。

共享内存区的控制

#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
  • shmid 是共享内存区的标识符,通常由 shmget 函数返回。
  • cmd 是要执行的操作,可以是各种控制命令之一。
  • buf 是一个指向 shmid_ds 结构的指针,用于传递或获取与共享内存相关的信息。

常用的控制命令包括:

  • IPC_STAT:获取共享内存区的状态信息,并将其存储在 buf 中。
  • IPC_SET:设置共享内存区的状态信息,buf 中包含了要设置的信息。
  • IPC_RMID:删除共享内存区。

如果你想删除共享内存区,可以将 cmd 参数设置为 IPC_RMID,并将共享内存区的标识符 shmid 作为 shmctl 函数,buf设置为NULL。

示例程序

本例通过读写者问题(不考虑优先级)来演示共享内存和呃呃信号量如何配合使用。这里的读者写者问题要求一个进程读共享内存的时候,其他进程不能写内存;当一个进程写共享内存的时候,其他进程不能读内存

首先定义了一个包含公用函数的头文件sharemem.h

#pragma once//这是C++的,毕竟我用的都是g++编译器
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/sem.h>
#include<sys/shm.h>
#include<errno.h>
#define SHM_SIZE 1024
union semun{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
};
int createsem(const char *pathname,int proj_id,int members,int init_val){//创建信号量集
    key_t msgkey;
    int index,sid;
    union semun semopts;
    if((msgkey=ftok(pathname,proj_id))==-1){
        perror("ftok error!\n");
        return -1;
    }
    if((sid=semget(msgkey,members,IPC_CREAT|0666))==-1){//创建信号量集
        perror("semget call failed.\n");
        return -1;
    }
    semopts.val=init_val;
    for(index=0;index<members;index++){
        semctl(sid,index,SETVAL,semopts);//设置信号量的初值
    }
    return sid;
}
int opensem(const char *pathname,int proj_id){//打开信号量集
    key_t msgkey;
    int sid;
    if((msgkey=ftok(pathname,proj_id))==-1){
        perror("ftok error!\n");
        return -1;
    }
    if((sid=semget(msgkey,0,IPC_CREAT|0666))==-1){
        perror("semget call failed.\n");
        return -1;
    }
    return sid;//返回信号量集的id
}
int sem_p(int semid,int index){//p操作,消耗信号量
    struct sembuf buf={0,-1,IPC_NOWAIT};
    if(index<0){
        perror("index of array cannot equals a minus value");
        return -1;
    }
    buf.sem_num=index;
    if(semop(semid,&buf,1)==-1){
        perror("a wrong operation to semaphore occurred!");
        return -1;
    }
    return 0;
}
int sem_v(int semid,int index){//v操作,增加信号量
    struct sembuf buf={0,+1,IPC_NOWAIT};
    if(index<0){
        perror("index of array cannot equals a minus value");
        return -1;
    }
    buf.sem_num=index;
    if(semop(semid,&buf,1)==-1){
        perror("a wrong operation to semaphore occurred!");
        return -1;
    }
    return 0;
}
int sem_delete(int semid){//删除信号量集
    return semctl(semid,0,IPC_RMID);
}
int wait_sem(int semid,int index){//等待信号量
    while (semctl(semid,index,GETVAL)==0)//如果信号量等于0,也就是没有资源,就等待
    {
        sleep(1);
    }
    return 1;
}
int createshm(const char *pathname,int proj_id,size_t size){//创建共享内存
    key_t shmkey;
    int sid;
    if((shmkey=ftok(pathname,proj_id))==-1){
        perror("ftok error!\n");
        return -1;
    }
    if((sid=shmget(shmkey,size,IPC_CREAT|0666))==-1){
        perror("shmget call failed.\n");
        return -1;
    }
    return sid;
}

writer和reader程序,两程序在进入共享内存区之前,都要检查信号量的值是否为1(相当于是否能进入共享内存区),如果不为1,调用sleep()进入睡眠状态直到信号值变为1。进入共享内存区之后,将信号的值减1(相当于加锁),这样就实现了互斥的访问共享资源。在退出共享内存区的时候,将信号量加1(相当于解锁)。

writer:

#include"sharemem.h"
#include<string.h>
int main(){
    int semid,shmid;
    char *shmaddr;
    char write_str[SHM_SIZE];
    if((shmid=createshm(".",'m',SHM_SIZE))==-1){//创建或打开共享内存
        exit(1);
    }
    if((shmaddr=(char *)shmat(shmid,NULL,0))==(char *)-1){//获取共享内存的地址
        perror("attach shared memory error!\n");
        exit(1);
    }
    if((semid=createsem(".",'s',1,1))==-1){//创建信号量集
        exit(1);
    }
    while(1){
        wait_sem(semid,0);//先等信号量解锁
        sem_p(semid,0);//在获取资源,给信号量上锁
        printf("writer:");
        fgets(write_str,1024,stdin);//从标准输入读入
        int len=strlen(write_str)-1;
        write_str[len]='\0';
        strcpy(shmaddr,write_str);//写入共享内存
        sleep(10);
        sem_v(semid,0);//解锁
        sleep(10);
    }
}

reader:

#include"sharemem.h"
#include<string.h>
int main(){
    int semid,shmid;
    char *shmaddr;
    char write_str[SHM_SIZE];
    if((shmid=createshm(".",'m',SHM_SIZE))==-1){
        exit(1);
    }
    if((shmaddr=(char *)shmat(shmid,NULL,0))==(char *)-1){
        perror("attach shared memory error!\n");
        exit(1);
    }
    if((semid=opensem(".",'s'))==-1){//打开信号量集
        exit(1);
    }
    while(1){
        printf("reader:");
        wait_sem(semid,0);
        sem_p(semid,0);
        printf("%s\n",shmaddr);//读入共享内存
        
        sleep(10);
        sem_v(semid,0);
        sleep(10);
    }
}

编译,因为是自己编写的头文件,需要加-I选项,这里我的头文件和cpp文件是同一路径,所以用的就是.,请根据自己的路径来。

最后在运行

reader会在writer发送信息10s后打印信息。

写在最后

由于本人要考研了,播客可能不会再长更。这本《Linux C编程实战》其实也差不多完结了,还剩网络编程章节没有讲,我肯定是没时间讲了,可能考研失败了会回来继续。《Primer C++》的课后题还有第八章的存货,后续章节只能随缘更新。Qt部分真烂尾了,写Qt项目的注释实在太累了。最有可能更新的部分是力扣题讲解,因为写来准备复试上机。

最后祝大家,也祝我一切顺利

  • 28
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
《C MySQL8.0数据库跨平台编程实战笔记》是一本介绍如何在不同平台上使用C语言和MySQL8.0数据库进行编程的实用指南。本书从数据库的基本概念开始讲解,包括数据库的设计、表的创建和管理,以及SQL语句的基本使用方法。然后深入探讨了C语言和MySQL8.0数据库的结合,通过示例代码演示了如何在不同操作系统上使用C语言连接和操作MySQL8.0数据库。 本书以实战为主,介绍了在Windows、Linux和MacOS等不同平台上使用C语言编写跨平台的数据库程序的方法。读者可以通过学习本书,掌握在不同平台上使用C语言和MySQL8.0数据库的技巧和方法。此外,本书还介绍了一些实用的编程技巧和调试方法,帮助读者更好地应用C语言和MySQL8.0数据库进行开发和调试。 作者通过丰富的实例和详细的讲解,使读者能够快速掌握使用C语言和MySQL8.0数据库进行跨平台编程的技能。不论是初学者还是有一定开发经验的读者,都可以从本书中获得丰富的知识和经验。本书内容通俗易懂,深入浅出,适合作为C语言和数据库编程的入门指南,也适用于数据库开发人员和C语言程序员作为进阶学习和实践的参考。 总之,《C MySQL8.0数据库跨平台编程实战笔记》是一本实用性强、内容丰富的技术书籍,对于想要学习C语言和MySQL8.0数据库跨平台编程的读者来说是一本难得的好书。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值