首页 > 系统相关 >IO进程线程(十二)进程间通信 共享内存 信号灯集

IO进程线程(十二)进程间通信 共享内存 信号灯集

时间:2024-06-10 21:01:24浏览次数:18  
标签:IPC semid int 信号灯 间通信 线程 key 进程 共享内存

文章目录

一、共享内存 shared memory(shm)

(一)特点

在内核中创建共享内存,让进程A和进程B都能够访问到,通过这段内存进行数据的传递。
共享内存是所有进程间通信方式中效率最高的(不需要来回进行数据的拷贝)
在这里插入图片描述

(二) 相关API

1. 创建共享内存

#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
功能:
    创建共享内存
参数:
    key:键值
        key 通过ftok获取
        IPC_PRIVATE:只能用于亲缘进程间的通信
    size:共享内存的大小  PAGE_SIZE(4k)的整数倍
    shmflg:共享的标志位
        IPC_CREAT|0666 
        或 
        IPC_CREAT|IPC_EXCL|0666
返回值:
    成功 共享内存编号
    失败 -1 重置错误码
  • 注:
  • 共享内存大小必须要4k的整数倍,因为一页是4k。如果申请时不要求4
    的整数倍,分配时也是分配4k的整数倍。
  • 内核空间越界会直接段错误
  • 同一个key值可以同时用于消息队列,共享内存,信号灯集

2. 映射共享内存到当前的进程空间

#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:
    映射共享内存到当前的进程空间
参数:
    shmid:共享内存编号
    shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL,让系统自动分配
    shmflg:共享内存操作方式
        0    读写
        SHM_RDONLY    只读
返回值:
    成功 指向共享内存的地址
    失败 (void *)-1 重置错误码

3. 取消地址映射

#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr);
功能:
    取消地址映射
参数:
    shmaddr:指向共享内存的指针
返回值:
    成功 0
    失败 -1 重置错误码

4. 共享内存控制

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:
    共享内存控制的函数
参数:
    shmid:共享内存编号
    cmd:操作的命令码
        IPC_STAT:获取
        IPC_SET:设置
        IPC_RMID:删除共享内存
            标记要销毁的段。实际上,只有在最后一个进程将其分离之后 
            (也就是说,关联结构shmid_ds的shm_nattch成员为零时), 
            段才会被销毁。
            调用者必须是段的所有者或创建者,或具有特权。buf参数被忽略。
    buf:共享内存属性结构体指针
返回值:
    成功 0
    失败 -1 重置错误码

(三)使用示例

read.c

#include <my_head.h>

#define SHM_PAGE 1024*4
int main(int argc, char const *argv[])
{
    //获取键值
    key_t key = ftok("/home/linux/05work",'A');
    if(-1 == key)
        ERR_LOG("ftok error");

    //创建共享内存
    int shmid = shmget(key, SHM_PAGE,IPC_CREAT|0666);
    if(-1 == shmid)
        ERR_LOG("shmget error");
    
    //映射共享内存
    char *addr = (char *)shmat(shmid,NULL,SHM_RDONLY);

    while(1){
        getchar();//防止刷屏,回车一次打印一次  
        if(!strcmp(addr,"quit")){
            break;
        }    
        printf("%s",addr);
    }
    //解除映射
    if(-1 == shmdt(addr)){
        ERR_LOG("shmid error");
    }

    //第三个参数会被忽略
    if(-1 == shmctl(shmid,IPC_RMID,NULL)){
        if(EINVAL == errno){
            return 0;
        }
        ERR_LOG("shmctl error");
    }
    return 0;
}

write.c

#include <my_head.h>

#define SHM_PAGE 1024*4
int main(int argc, char const *argv[])
{
    //获取键值
    key_t key = ftok("/home/linux/05work",'A');
    if(-1 == key)
        ERR_LOG("ftok error");

    //创建共享内存
    int shmid = shmget(key, SHM_PAGE,IPC_CREAT|0666);
    if(-1 == shmid)
        ERR_LOG("shmget error");
    
    //映射共享内存
    char *addr = (char *)shmat(shmid,NULL,0);

    while(1){
        printf("请输入要发送的内容:");
        scanf("%s",addr);
        if(!strcmp(addr,"quit")){
            break;
        }
    }
    //解除映射
    if(-1 == shmdt(addr)){
        ERR_LOG("shmid error");
    }
    //销毁
    if(-1 == shmctl(shmid,IPC_RMID,NULL)){
        if(EINVAL == errno){
            return 0;
        }
        ERR_LOG("shmctl error");
    }
    return 0;
}

(四) 属性

struct shmid_ds{
    struct ipc_perm shm_perm;    //权限结构体
    size_t shm_segsz;             //共享内存大小,单位是字节 
    __time_t shm_atime;         //最后一次映射的时间 
    __pid_t shm_cpid;             //创建共享内存进程的pid 
    __pid_t shm_lpid;             //最后一次操作共享内存进程的pid 
    shmatt_t shm_nattch;         //共享内存映射的次数
};
struct ipc_perm{
    __key_t __key;                //ftok获取的key
    __uid_t uid;                 //用户的ID
    __gid_t gid;                 //组ID
    __uid_t cuid;                //创建共享内存的用户的ID
    __gid_t cgid;                //创建共享内存的组的ID
    unsigned short int mode;     //消息队列的权限
};

二、信号灯集—控制进程间同步

(一)特点

信号灯集:又叫做信号量数组,他是实现进程间同步的机制

在一个信号灯集中可以有很多个信号灯,这些信号灯之间工作相互互不干扰。
一般使用时使用的都是二值信号灯

(二) 相关API

1. 创建一个信号灯集

#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
功能:
    创建一个信号灯集
参数:
    key:键值
    	IPC_PRIVATE/key
    nsems:信号灯集合中信号灯的个数
    semflag:创建的标志位
        IPC_CREAT|0666 或 IPC_CREAT|IPC_EXCL|0666
返回值:
    成功 semid
    失败 -1  重置错误码
  • 补充:使用IPC_PRIVATE创建的IPC对象, key值属性为0,和IPC对象的编号就没有了对应关系。这样毫无关系的进程,就不能通过key值来得到IPC对象的编号(因为这种方式创建的IPC对象的key值都是0)。因此,这种方式产生的IPC对象,和无名管道类似,不能用于毫无关系的进程间通信。但也不是一点用处都没有,仍然可以用于有亲缘关系的进程间通信。

2. 信号灯集控制函数

int semctl(int semid, int semnum, int cmd, ...);

功能:信号灯集的控制函数

参数:
    semid信号灯集的ID
    senum:信号灯的编号 从0开始
    cmd:命令码
        SETVAL:设置信号灯的值 --->第四个参数val选项
        GETVAL:获取信号灯的值 --->不需要第四个参数
        IPC_STAT:获取信号灯集的属性--->第二参数被忽略,第四个参数buf选项
        IPC_SET :设置信号灯集的属性--->第二参数被忽略,第四个参数buf选项
        IPC_RMID:删除信号灯集 第二参数被忽略,第4个参数不用填写 
    @...:可变参
    union semun{
        int val; /* Value for SETVAL */
        struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
    };

返回值:
    成功:
        GETVAL:成功返回信号灯的值
        其余的命令码成功返回0
    失败 -1  重置错误码
  • 注:*初始化操作在两个进程中必须只能进行一次,因为进程之间运行是没有顺序的,可以出现其中一个进程已经进行了信号灯集的相关操作后,另一个进程进行初始化而导致出错。

3. 信号灯集的操作函数

int semop(int semid, struct sembuf *sops, size_t nsops);

功能:
    信号灯集中信号灯的操作函数

参数:
    semid:信号灯集的编号
    sops:操作方式
        struct sembuf{
            unsigned short sem_num; //信号灯的编号
            short sem_op; //操作方式(PV)-1:P操作,申请资源 1:V操作,释放资源
            short sem_flg; //操作的标志位 0:阻塞 IPC_NOWAIT:非阻塞方式操作
        }
    nsops:本次操作信号灯的个数

返回值:
    成功 0
    失败 -1  重置错误码
  • 注:同时对多个信号灯进行操作时,可以定义一个结构体数组

(三)封装函数

原生函数直接使用会比较繁琐,因此会做二次封装。

初始化(解决可能重复初始化的问题):

int sem_init_pack(key_t key, int nsem){
    int semid = 0;
    //不存在创建,存在退出返回semid
    if(-1 == (semid = semget(key,nsem,IPC_CREAT|IPC_EXCL|0666))){
        if(EEXIST == errno){//已存在导致的错误
            if((-1 == (semid = semget(key,nsem,IPC_CREAT|0666)))){
                return -1;
            }
            return semid;
        }else{//不是已存在导致的错误,说明出错
            return -1;
        }
    }
    //初始化,第一个置1,其余置0
    if(semctl(semid,0,SETVAL,1)){
        return -1;
    }
    for(int i = 1; i < nsem; i++){
        if(semctl(semid,i,SETVAL,0)){
            return -1;
        }
    }
    return semid;
}

单信号灯PV操作

int sem_p_pack(int semid,int semnum){
    struct sembuf sembuff={
        .sem_num=semnum,
        .sem_op=-1,
        .sem_flg=0};
    if(-1 == semop(semid,&sembuff,1)){
        return errno;
    }
    return 0;
}

int sem_v_pack(int semid,int semnum){
    struct sembuf sembuff={
        .sem_num=semnum,
        .sem_op=1,
        .sem_flg=0
    };
    if(-1 == semop(semid,&sembuff,1)){
        return errno;
    }
    return 0;
}

多信号灯PV操作
传入一个int型数组指针,数组成员就是要进行p操作的信号灯编号

int sem_p_set_pack(int semid, int *semnum, int num){
    struct sembuf sembuff[num];

    for(int i=0; i<num; i++){
        sembuff[i].sem_flg=1;
        sembuff[i].sem_op=-1;
        sembuff[i].sem_num=semnum[i];

        if(-1 == semop(semid,sembuff+i,1)){
            return errno;
        }
    }
    return 0;
}

int sem_v_set_pack(int semid, int *semnum, int num){
    if(NULL==semnum) return -1;
    struct sembuf sembuff[num];

    for(int i=0; i<num; i++){
        sembuff[i].sem_flg=1;
        sembuff[i].sem_op=1;
        sembuff[i].sem_num=semnum[i];

        if(-1 == semop(semid,sembuff+i,1)){
            return errno;
        }
    }
    return 0;
}

销毁

int sem_destroy_pack(int semid){
    if(-1 == semctl(semid,0,IPC_RMID))
    {
        //如果出现这个错误说明是已经销毁过了,无视这个错误
        if(EINVAL == errno){
            return 0;
        }
        //否则就是出错了,返回错误信息
        printf("semop error:%s",strerror(errno));
        return -1;
    }
    return 0;
}

标签:IPC,semid,int,信号灯,间通信,线程,key,进程,共享内存
From: https://blog.csdn.net/weixin_44254079/article/details/139579279

相关文章

  • linux内核空间进程为什么无论如何切换,内核地址空间转换到物理地址的关系是永远不变的?
    在Linux内核中,无论如何切换进程,内核地址空间转换到物理地址的关系是永远不变的,主要原因是内核地址空间在所有进程中是共享的。这种设计有几个关键点:1.内核地址空间共享在Linux操作系统中,每个进程都有自己独立的用户空间地址范围,但内核空间地址范围对所有进程是共享的。具体来说......
  • 进程间通信
    进程间通信1.什么是通信数据传输:一个进程需要将自己的数据传输给另一个进程资源共享:多个进程同时共享一个资源进程事件:一个进程向一组(或一个)进程通知某一事件,如:子进程结束要通知父进程来回收资源进程控制:有些进程需要知道另一个进程的状态,控制拦截另一个进程陷入异常等,如:gd......
  • 记一次锁使用不当导致Dubbo线程阻塞问题
    背景线上环境一个后台项目,提供基于dubbo实现的事件分发服务,最近突然出现心跳超时。问题分析检查内存是否溢出jstat-gcutil81661000意料之中,内存正常,因为内部有接入内存溢出告警,如果是内存溢出应该有收到通知,但是这次没有溢出通知。查看线程栈jstack-l8166发现有大......
  • 线程池原理及c语言实现线程池
    线程池线程池是一种多线程处理机制,其主要目的是提高系统资源利用率、降低系统资源消耗,并通过控制并发线程数量来优化性能。以下是关于线程池的详细解释:定义:线程池是一种线程使用模式,它维护着一组线程,这些线程等待监督管理者分配可并发执行的任务。通过将任务添加到队列中,并......
  • python实现自定义线程池
    线程池ThreadPool对象的几个关键方法:get_idle_num():获得当前空闲线程的数量submit(task:callable):把一个任务(实际上就是一个函数)提交到线程池中执行.如果没有空闲线程则阻塞.wait_idle():阻塞,直到有空闲线程stop():停止线程池中的所有线程.(注意:非强制停止,......
  • 第1讲:进程和线程
    扫盲课。对Linux系统下,进程和线程的基本概念和对比进行阐述。一、进程进程是处于执行期的程序及相关资源的总称。操作系统为进程提供两种虚拟机制:虚拟处理器&虚拟内存,目的是让进程有一种假象:“独享处理器和整个内存空间”。关于进程描述符structtask_struct放在后续内容......
  • 避免 OOMKilled:在 Kubernetes 环境中优化 Java 进程的内存配置
    避免OOMKilled:在Kubernetes环境中优化Java进程的内存配置DevOps云学堂译 奇妙的Linux世界 2024-06-1009:53 重庆 听全文公众号关注 「奇妙的Linux世界」设为「星标」,每天带你玩转Linux! 管理KubernetesPod中运行的Java进程的内存使用情况比人们想象......
  • 第3讲:进程调度
    分为两类:抢占式多任务非抢占式多任务进程可分为:IO消耗型、CPU消耗型。调度方式(1)优先级调度nice值(-20~19):值越大、优先级越低。nice值映射到时间片问题:(1)绝对时间片无法保证最优解;(2)nice值越靠近边界、波动越大;(3)定时器节拍问题实时优先级。(2)时间片CFS调度分配的是......
  • 第2讲:进程管理
    本文的主要内容:一个进程从生到死的过程。一、任务队列和task_struct任务描述符Linux的“任务队列”是一个双向链表,链表中每一项为进程描述符task_struct,它包含了一个正在执行的程序的完整信息:它打开的文件、进程的地址空间、挂起的信号、进程的状态等等。Linux通过slab分......
  • 线程池代码详解
    线程池概念线程池是一种基于池化技术的多线程管理机制。在这种模式下,一组线程被创建并且维护在一个"池"中,这些线程可以被循环利用来执行多个任务。当有新的任务到来时,线程池会尝试使用已经存在的空闲线程,而不是每次都创建新线程。这样做的目的是为了减少因频繁创建和销毁线程所带......