Linux进程间通信学习记录(IPC 机制、共享内存以及信号灯集)

news2024/11/14 14:55:14

0.System V IPC机制:

        ①.IPC对象包含:共享内存、消息队列和信号灯集。

        ②.每个IPC对象有唯一的ID。

        ③.IPC对象创建后一直存在,直到被显示地删除。

        ④.每一个IPC对象有一个关联的KEY。(其他进程通过KEY访问对应的IPC对象)

        ⑤.ipcs(显示系统所有的IPC对象)、ipcrm

一. 生成KEY值 - ftok

功能

        成功时返回(合法的key值),失败时返回(EOF)。

        若传入的参数一样,那么得到的key值也一样

参数

        path:存在且可访问的文件的路径

        proj_id:用于生成key的数字,不能为0,一般为字符常量

int key_t ftok(const char *path,int proj_id);
int main(int argc,char *argv[])
{
    key_t key;

    key = ftok(".",'a');
    if(-1 == key)
    {
        perror("key error");
        exit(-1);
    }
}

二.共享内存

0.特点

        ①.共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。

        ②.共享内存在内核空间创建,可被进程映射到用户空间访问,使其灵活。

        ③.由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用

        ④.共享内存的大小有限制

1.创建/打开共享内存 - shmget()

功能

        创建/打开共享内存对象

        成功时返回(共享内存的id),失败时(EOF)

参数

        key:和共享内存关联的key,IPC_PRIVATE或由ftok生成。

        size:共享内存的大小,单位为字节。

        shmflg:共享内存标志位 IPC_CREATE | 0666(不存在就创建,存在了就打开)。

int shmget(key_t key,int size,int shmflg);

示例1:创建一个私有的共享内存,大小为512字节,权限为0666

int shmid;

shmid = shmget(IPC_PRIVATE,512,0666);
if(0 > shmid)
{
    perror("shmget error");
    exit(-1);
}

示例2:创建/打开一个和key关联的共享内存,大小为1024字节,权限为0666

key_t key;
int shmid;

/* 1.先生成key(相关的进程调用ftok时,传入相同的参数才能得到相同的key) */
key = ftok(".",'m');
if(-1 == key)
{
    perror("ftok");
    exit(-1);
}

/* 2.获取共享内存 */
shmid = shmget(key,1024,IPC_CREATE | 0666);
if(0 > shmid)
{
    perror("shmget");
    exit(-1);
}

2.映射共享内存 - shmat

功能

        映射共享内存;

        成功时返回(映射后的地址),失败时返回((void *)-1),通过指针访问共享内存,指针类型取决于共享内存中存放的数据的类型;

参数

        shmid:要映射的共享内存的id。

        shmaddr:指定映射后的地址(通常传入NULL),NULL表示由系统自动映射。

        shmflg:标志位,0表示可读写;SHM_RDONLY表示只读。

void *shmat(int shmid,const void *shmaddr,int shmflg);

示例1:在共享内存中存放键盘输入的字符串

char *addr;    //保存映射后的首地址
int shmid;

/* 映射共享内存 */
addr = (char *)shmat(shmid,NULL,0);
id((char *)-1 == addr)
{
    perror("shmat");
    exit(-1);
}
fgets(addr,N,stdin);

3.读写共享内存

        操作映射的共享内存的地址

4.撤销共享内存映射 - shmdt

功能

        撤销共享内存映射

        成功时返回(0),失败时返回(EOF)

参数

        shmaddr:映射共享内存的首地址

int shmdt(void *shmaddr);

5.共享内存控制 - shmctl

功能

        控制共享内存。

        成功时返回(0),失败时返回(-1)并设置errno。

参数

        shmid:要操作的共享内存的id。

        cmd   :要执行的操作

                        IPC_STAT:获取当前共享内存的属性(大小,关联的key值,权限等),存入到                                                shmid_ds *buf中

                        IPC_SET  :将属性(先在shmid_ds *buf中填充)设置到共享内存的id中

                        IPC_RMID:删除一个共享内存的id

        buf:保存或设置共享内存属性的地址

int shmctl(int shmid,int cmd,struct shmid_ds *buf);

6.删除共享内存对象 - shmctl

 用shmctl来删除共享内存对象(应该由最后一个进程来执行此操作)

 功能

        添加删除标记,当每一个进程都撤销共享内存映射后(nattach == 0)才真正的删除。        

shmctl(shmid,IPC_RMID,NULL);

三.信号灯

1.含义:

        信号灯也叫信号量,用于进程/线程同步或互斥的机制;

        System V 信号灯是一个或多个计数信号灯的集合;

        System V 信号灯可同时操作集合中的多个信号灯,要么都能申请到资源,要么都申请不到资          源;

        System V 信号灯申请多个资源时避免死锁;

 2.信号灯的类型:

         Posix无名信号灯:通常用于线程同步

         Posix有名信号灯:通常用于进程间的同步和互斥

         System V 信号灯:通常用于进程间的同步和互斥

四.System V 信号灯使用步骤

1.打开/创建信号灯 - semget

功能

        打开/创建信号灯

        成功时返回(信号灯集合的id),失败时返回(-1),返回(-1)有可能是 打开/创建 失败。所以在返回(-1)时 要对 errno 进行判断,若 errno = EEXIST ,则是该信号灯已经存在,此时进行打开操作;否则应该退出程序,并打开错误信息。

参数

        key:和信号灯关联的key值(IPC_PRIVATE 或 ftok生成的);

        nsems:集合中包含的计数信号灯的个数;

        semflg:标志位

                        IPC_CREAT | 0666:没有则创建;

                        IPC_EXCL:检查信号灯集合是不是第一次被创建,若不是第一次被创建则会返回                                             (EEXIST),保证信号灯只被初始化一次;

int semget(key_t key,int nsems,int semflg);
semid = semget(sem_key,1,IPC_CREATE|IPC_EXCL|0666);
/* 若打开/创建失败 */
if(-1 == semid) 
{
    /* errno == EEXIST 时表示该信号灯已经存在,只能进行打开操作 */
    if(errno == EEXIST)
    {
        semid = semget(sem_key,1,0777);
    }
    /* 否则为出错,应该退出程序 */
    else
    {
        perror("fail to semget");
        exit(-1);
    }
}
/* 若创建成功,则初始化信号灯 */
else
{
    /* 初始化操作 */
}

2.信号灯初始化 - semctl

功能

        初始化信号灯集合;

        成功时返回(0),失败时返回(EOF);

参数

        semid:要操作的信号灯集合id

        semnum:要操作的集合中的信号灯编号(信号灯集合中的哪一个信号灯)

        cmd:要执行的操作

                        SETVAL:设置信号灯的值

                        IPC_RMID:删除信号灯集合

        union semun  :要设置的信号灯的值

int semctl(int semid,int semnum,int cmd,...);
union semun myun;

/* 1.填充第一个要初始化的信号的值 */
myun.val = 2;
/* 把第一个信号灯的值初始化为2 */
if(0 > semctl(semid,0,SETVAL,myun))
{
    perror("semctl");
    exit(-1);
}

/* 1.填充第二个要初始化的信号的值 */
myun.val = 0;
/* 把第二个信号灯的值初始化为0 */
if(0 > semctl(semid,1,SETVAL,myun))
{
    perror("semctl");
    exit(-1);
}

3.P/V 操作 - semop

功能

        对信号灯集合中的一个或多个信号灯执行操作;

        成功时返回(0),失败时返回(-1);

参数

        semid:要操作的信号灯集id;

        sops:描述对某一个信号灯操作的结构体。若要操作多个信号灯,则要传入结构体数组;

        nsops:要操作的信号灯的个数;

int semop(int semid,struct sembuf *sops,unsigned nsops);
struct sembuf
{
    short semnum;    //指定要操作的信号灯的编号

    /* 指定要执行的操作(-1:P操作,1:V操作) */
    short sem_op;    

    /* 一般为 0/IPC_NOWAIT/SEM_UNDO (0         :表示以阻塞的方式,直到完成操作
                                     IPC_NOWAIT : 表示不阻塞,直接返回) 
                                     SEM_UNDO : 进程退出后,将信号量返回到其初始值(防止死锁)   */
    short sem_flg;    
};
    struct sembuf buf[3];

    /* 填充第 0 个信号 */
    buf[0].sem_num = 0;     //操作第 0 个信号灯
    buf[0].sem_op = -1;     //指定执行 P 操作
    buf.sem_flg = 0;        //以阻塞的方式完成操作

    /* 填充第 1 个信号 */
    buf[1].sem_num = 1;     //操作第 1 个信号灯
    buf[1].sem_op = -1;     //指定执行 P 操作
    buf[1].sem_flg = 0;     //以阻塞的方式完成操作


    /* 调用 semop 完成P/V操作,操作的信号灯的个数为 2  */
    semop(semid,buf,2);

3.删除信号灯 - semctl

功能

        删除信号灯集合;

        成功时返回(0),失败时返回(EOF);

参数

        semid:要操作的信号灯集合id

        semnum:要操作的集合中的信号灯编号(信号灯集合中的哪一个信号灯)

        cmd:要执行的操作

                        SETVAL:设置信号灯的值

                        IPC_RMID:删除信号灯集合

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

五.用信号灯集实现共享内存同步

1.目的:

        父子进程通过System V 信号灯同步对共享内存的读写,需要创建两个信号灯,一个用于写资源,一个用于读资源

        父进程从键盘输入字符串到共享内存

        子进程删除字符串中的空格并打印

        父进程输入 quit 后,删除共享内存和信号灯集,程序结束

2.流程图

3.代码:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>

void init_sem(int semid , int s[] , int n);
void pv(int semid,int num,int op);

#define N   64      //共享内存的大小
#define READ    0   //信号灯集合中的信号灯的编号,代表可读缓冲区
#define WRITE   1   //信号灯集合中的信号灯的编号,代表可写缓冲区

/* 用于信号灯集合操作, man 手册里有这个共用体,但是得自己定义 */
union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *__buf;
};


int main(void)
{
    /**
     * 0.定义所要用到的变量
     * (1)共享内存的id
     * (2)信号灯集合id
     * (3)信号灯初始化数组
     * (4)pid
     * (5)key
     * (6)共享内存映射后的首地址
    */
    int shmid;          //共享内存的id
    int semid;          //信号灯集合id
    int s[] = {0,1};    //信号灯初始化数组,s[0]用于读,s[1]用于写
    pid_t pid;          //用于创建进程
    key_t key;          //用于生成key
    char *shmaddr;      //用于接收映射的共享内存的首地址

    /* 1.创建/获取key */
    key = ftok(".",'s');
    if(-1 == key)
    {
        perror("ftok error");
        exit(-1);
    }

    /* 2.创建/获取共享内存 */
    shmid = shmget(key,N,IPC_CREAT | 0666);
    if(0 > shmid)
    {
        perror("shmget error");
        exit(-1);
    }

    /* 3.创建信号灯集合(若失败则在退出程序前,要先把创建号的共享内存删除) */
    semid = semget(key,2,IPC_CREAT | 0666);
    if(0 > semid)
    {
        perror("semget error");
        goto _error1;       //处理错误
    }

    /* 4.对信号灯集合进行初始化,初始化 2 个信号量 */
    init_sem(semid,s,2);

    /* 5.映射共享内存(64)个字节,(若映射共享内存失败,在退出程序前,要先把之前创建的信号灯集合删除) */
    shmaddr = (char *)shmat(shmid,NULL,0);
    if(((void *)-1) == shmaddr)
    {
        perror("shmat error");
        goto _error2;
    }

    /* 6.创建子进程 */
    pid = fork();
    switch(pid)
    {
        case -1:
            perror("fork error");
            goto _error2;
            break;
        /* 子进程 */
        case 0:
        {
            char *p,*q;
            while(1)
            {
                /* 1.对可读资源进行 P 操作 */
                pv(semid,READ,-1);

                /* 2.进行读操作(去除共享内存中的空格) */
                p = q = shmaddr;
                while(*q)
                {
                    if(*q != ' ')
                    {
                        *p++ = *q;
                    }
                    q++;
                }
                *p = '\0';
                printf("result:%s\n",shmaddr);

                /* 3.对可写资源执行 V 操作,使其他进程可写 */
                pv(semid,WRITE,1);
            }
            break;
        }

        /* 此进程 */
        default:
            while(1)
            {
                /* 7.要写缓冲区,所以先进行P操作,看有没有资源可写。若没有资源可写,则阻塞等待 */
                pv(semid,WRITE,-1);
                
                /* 8.执行写操作 */
                printf("input >\n");
                fgets(shmaddr,N,stdin);
                
                /* 9.若输入了quit,则跳出循环 */
                if(0 == strcmp(shmaddr,"quit\n"))
                    break;
                
                /* 10.执行完写操作后,执行 V 操作,使其他进程可读 */
                pv(semid,READ,1);
            }
            /* 发出信号,结束子进程 */
            kill(pid,SIGUSR1);
            break;
    }


/* 若创建信号灯集合失败,则在退出程序前,要先把创建号的共享内存删除 */
_error1:
    shmctl(shmid,IPC_RMID,NULL);

/* 若映射共享内存失败,在退出程序前,要先把之前创建的信号灯集合删除 */
_error2:
    semctl(semid,0,IPC_RMID);


    return 0;
}


/**
 * @description:    初始化信号灯集合
 * @param - semid:  信号灯集合的id
 * @param - s[]  :  信号灯初始化值的数组
 * @param - n    :  初始化信号灯的个数  
 * @return       :  无 
*/
void init_sem(int semid , int s[] , int n)
{
    int i;
    union semun myun;       //创建信号灯集合共用体

    /* 循环完成信号灯集合的初始化 */
    for(i = 0;i < n;i++)
    {
        myun.val = s[i];
        semctl(semid,i,SETVAL,myun);
    }


}


/**
 * @description:        对信号灯集合的某一个信号灯实现P/V操作
 * @param - semid   :   信号灯集合id
 * @param - num     :   信号灯集合内的信号灯的编号
 * @param - op      :   实现的操作
 * @return          :   无
*/
void pv(int semid,int num,int op)
{
    struct sembuf buf;

    buf.sem_num = num;      //指定操作编号为 num 的信号灯
    buf.sem_op = op;        //指定要执行的操作
    buf.sem_flg = 0;        //阻塞等待,直到该操作完成

    /* 进行P/V操作 */
    semop(semid,&buf,1);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2057937.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringCloud远程调用为啥要采用HTTP,而不是RPC?

关于SpringCloud远程调用采用HTTP而非RPC。 1. 首先SpringCloud开启Web服务依赖于内部封装的Tomcat容器&#xff0c;而今信息飞速发展&#xff0c;适应大流量的微服务&#xff0c;采用Tomcat处理HTTP请求&#xff0c;开发者编写Json作为资源传输&#xff0c;服务器做出相应的响…

Flutter【01】状态管理

声明式编程 Flutter 应用是 声明式 的&#xff0c;这也就意味着 Flutter 构建的用户界面就是应用的当前状态。 当你的 Flutter 应用的状态发生改变时&#xff08;例如&#xff0c;用户在设置界面中点击了一个开关选项&#xff09;你改变了状态&#xff0c;这将会触发用户界面…

flume--数据从kafka到hdfs发生错误

解决&#xff1a; #1.将flume自带的依赖删除 mv /opt/installs/flume1.9/lib/guava-11.0.2.jar /opt/installs/flume1.9/lib/guava-11.0.2.jar.bak #2.将hadoop的依赖发送到flume下 cp /opt/installs/hadoop3.1.4/share/hadoop/common/lib/guava-27.0-jre.jar /opt/installs/f…

招商期货:以超融合支撑期货重要业务,承载80%信创系统

招商期货有限公司&#xff08;以下简称“招商期货”&#xff09;成立于 1993 年&#xff0c;是招商证券股份有限公司的全资子公司&#xff0c;注册资本 35.98 亿元&#xff0c;是中国首批券商全资控股期货公司。 随着数字化进程快速推进、交易模式不断创新&#xff0c;系统建设…

Axure设计之三级菜单导航教程(中继器)

中继器作为复杂的元件&#xff0c;通常被用来制作“高保真”的动态原型&#xff0c;以达到良好的视觉效果和交互效果。本文将教大家通过AxureRP9工具如何使用中继器设计三级菜单导航。 一、案例效果 原型预览&#xff1a;https://1zvcwx.axshare.com 主要效果&#xff1a; 1…

异步交互技术Ajax-Axios

目录 一、同步交互和异步交互 二、Ajax 1.概述 2.如何实现ajax请求 三、异步传输数据乱码的问题 regist.html页面代码 服务端代码处理 四、Axios 1. Axios的基本使用 &#xff08;1&#xff09;引入Axios文件 &#xff08;2&#xff09;使用Axios发送请求&#xff0…

Chapter 42 递归

欢迎大家订阅【Python从入门到精通】专栏&#xff0c;一起探索Python的无限可能&#xff01; 文章目录 前言一、基本概述二、案例分析 前言 递归是一种在编程中广泛使用的技术&#xff0c;通过让函数调用自身来逐步解决问题。本章详细讲解了 Python 中递归的基本原理以及应用场…

SSRF服务器请求伪造

目录 SSRF服务器请求伪造 一、SSRF漏洞概述 二、SSRF常见的函数 1、file_get_contents() 2、fsockopen() 3、exec()发送GET请求 4、exec()发送POST请求 三、SSRF主要危害 1、先准备以下脚本 2、读取文件和信息 3、内网扫描 4、获取指纹信息 四、SSRF漏洞挖掘技巧 …

Nginx---Web服务器

简介 介绍nginx中Web服务器的相关配置 环境配置 mkdir /data/web/html -p mkdir /data/web/html/test{1..5} echo test1 > /data/web/html/test1/index.html echo test2 > /data/web/html/test2/index.html echo test3 > /data/web/html/test3/index.html echo tes…

FPGA时序约束

目录 一、概述二、时序分析基本概念时钟抖动时钟偏差时钟不确定性Clock Uncertainty同步电路和异步电路建立时间和保持时间发起沿和采样沿关键路径 三、时序分析的基本公式时序分析的基本路径数据到达时间和时钟到达时间建立时间的裕量&#xff08;Setup slack&#xff09;保持…

STM32CubeMX 配置串口通信 HAL库

一、STM32CubeMX 配置串口 每个外设生成独立的 ’.c/.h’ 文件 不勾&#xff1a;所有初始化代码都生成在 main.c 勾选&#xff1a;初始化代码生成在对应的外设文件。 如 GPIO 初始化代码生成在 gpio.c 中。 二、重写fputc函数 ​ #include <stdio.h>#ifdef __GNUC__#def…

“LOCAL_LISTENER”参数导致业务无法连接数据库,文末附Oracle连接故障检查监听的排查流程

1. 背景及问题 今天在Oracle BCV技术[1]做数据同步&#xff0c;建立生产库的测试库&#xff0c;需要DBA配合同步前后的停库和起库。在同步完起库后&#xff0c;有部门反应同步好的测试库连接不上去。 2. 问题排查 以我当前的知识储备&#xff0c;能想到的可能就是以下几点进…

【NLP】注意力机制:规则、作用、原理、实现方式

文章目录 1、本章目标2、注意力机制介绍2.1、注意力概念2.2、注意力机制2.3、翻译举例 3、注意力计算规则3.1、打个比喻3.2、公式3.2.1、线性变换 点积注意力3.2.2、加性注意力3.2.3、点积注意力3.2.4、对比与总结3.2.5、bmm运算 4、注意力机制的作用5、注意力机制原理⭐5.1、…

基于java的美食信息推荐系统的设计与实现论文

摘 要 使用旧方法对美食信息推荐系统的信息进行系统化管理已经不再让人们信赖了&#xff0c;把现在的网络信息技术运用在美食信息推荐系统的管理上面可以解决许多信息管理上面的难题&#xff0c;比如处理数据时间很长&#xff0c;数据存在错误不能及时纠正等问题。这次开发的美…

Linux系统-vi/vim编辑器权限管理文档处理三剑客

1.vi/vim文本编辑器 vim是vi的增强版&#xff0c;vi是系统自带的。以下命令在vi/vim中通用&#xff1a; 刚打开的默认模式 快捷键&#xff1a;gg 跳到文件开头&#xff0c;G 跳到文件最后一行。 快捷键&#xff1a;0 跳到行首&#xff0c;$ 跳到行尾。 快捷键&#xff1a;…

C++ | Leetcode C++题解之第355题设计推特

题目&#xff1a; 题解&#xff1a; class Twitter {struct Node {// 哈希表存储关注人的 Idunordered_set<int> followee;// 用链表存储 tweetIdlist<int> tweet;};// getNewsFeed 检索的推文的上限以及 tweetId 的时间戳int recentMax, time;// tweetId 对应发送…

vue3--定时任务cron表达式组件比较

## 背景&#xff1a; 之前使用vue2开发项目时&#xff0c;使用了cron组件&#xff0c;比较了两种组件的使用效果。现在需要把原有的vue2项目升级为vue3&#xff0c;需要对应的cron组件。 方案一&#xff0c;vue3-cron-plus 具体实现&#xff1a; 安装插件 npm install vue3-…

浅谈shell中的while true

目录 shell实现死循环你了解while true中的true吗重新认识true和falsewhile true存在的问题实现shell死循环的另一种方法 在shell中实现死循环&#xff0c;一般都会用 while true&#xff0c;那你知道执行while true时&#xff0c;进程都在做些什么吗&#xff1f; shell实现死…

云计算实训32——安装nginx(修改端口为8080)、roles基本用法、使用剧本安装nginx、使用roles实现lnmp

一、安装nginx并更改其端口 编辑hosts配置文件 [rootmo ~]# vim /etc/ansible/hosts 创建目录 [rootmo ~]# mkdir /etc/ansible/playbook 编辑配置文件 [rootmo ~]# vim /etc/ansible/playbook/nginx.yml 执行测试 [rootmo ~]# ansible-playbook /etc/ansible/playbook/n…

【案例49】ORA-01000:超出打开游标的最大数

问题现象 在登录系统时提示报错&#xff1a;ORA-01000 超出打开游标的最大数。 问题分析 游标就是看成是指向结果集的指针。可以把它看成一种资源&#xff0c;或者一种数据结构。 ORA-01000是开发中常见的异常。这个异常表示程序中打开的游标数目> 数据库中设定的可以打开…