进程间如何进行通信[IPC](二)共享内存

talk命令

talk使用socket进行通信,从键盘和socket接收数据,键盘的输入被复制到上半窗口,通过socket发出,同时通过socket读入的字符显示到下半窗口

select系统调用

  #include <sys/time.h>
   #include <sys/types.h>
   #include <unistd.h>

   int select(int nfds, fd_set *readfds, fd_set *writefds,
              fd_set *exceptfds, struct timeval *timeout);
	//成功返回满足要求的文件描述符数目,超时返回0,失败返回-1
   void FD_CLR(int fd, fd_set *set);
   int  FD_ISSET(int fd, fd_set *set);
   void FD_SET(int fd, fd_set *set);
   void FD_ZERO(fd_set *set);

select允许程序挂起,并等待不止一个文件描述符的输入

原理:

  • 获得所需的文件描述符列表
  • 将列表传给select函数
  • select挂起直到任何一个文件描述符有数据到达
  • select设置一个变量中的若干位,用于通知哪一个文件描述符有输入的数据(监听文件描述符)
//等待两个设备上的数据到达
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/time.h>
#include<string.h>
#include<stdlib.h>

//等待两个设备上的数据到达


//定义宏,用于错误处理
#define opps(m,x){ perror(m);exit(x); }
#define BUF_SIZE 1024
void showdata(char *fname,int fd)
{
    char buf[BUF_SIZE];
    int n;
    printf("%s:",fname);
    fflush(stdout);
    n = read(fd,buf,BUF_SIZE);
    if(n==-1)
    {
        opps(fname,5);
    }
    write(1,buf,n);
    write(1,"\n",1);//向标准输出写出
}

int main(int argc,char*argv[])
{
    int fd1,fd2;//关注两个文件描述符,分别对应两个设备
    struct timeval timeout;//超过此事件则select返回
    fd_set readfds;//关注的文件描述符输入列表
    int maxfd;//max fd +1
    int retval;//返回值
    if(argc!=4)
    {
        fprintf(stderr,"usage:%s file file timeout",*argv);
        exit(1);
    }
    //打开文件
    if((fd1=open(argv[1],O_RDONLY))==-1){
        opps(argv[1],2);
    }
    
    if((fd2=open(argv[2],O_RDONLY))==-1){
        opps(argv[2],3);
    }
    maxfd = 1+(fd1>fd2?fd1:fd2);

    while(1)
    {
        //select调用的标准流程
        FD_ZERO(&readfds);//置所有位为空
        FD_SET(fd1,&readfds);//关注fd1(置位fd1)
        FD_SET(fd2,&readfds);//关注fd2(置位fd2)
        //超时时间设置
        timeout.tv_sec = atoi(argv[3]);
        timeout.tv_usec = 0;
        //等待输入到来
        retval = select(maxfd,&readfds,NULL,NULL,&timeout);
        if(retval==-1)
        {
            opps("select",4);
        }
        if(retval>0)
        {
            //检查哪个文件描述符来了输入
            if(FD_ISSET(fd1,&readfds))
                showdata(argv[1],fd1);
            if(FD_ISSET(fd2,&readfds))
                showdata(argv[2],fd2);
        }
        else{
            printf("no input after %d seconds\n",atoi(argv[3]));
        }
    }
}

image

Ubuntu的鼠标在/dev/input/mouse0,mouse1

进程通信方式

高级通信方式:

文件 (通过磁盘)

image

管道 (通过内核)

使用命名管道可以连接不想关的进程,且可以独立于进程存在,类似FIFO先进先出队列

image

image

共享内存 (通过用户空间)

允许两个或多个进程共享物理内存的同一块区域(段)

一个共享内存段可以认做(称为)是一个进程用户空间的一部分,内核介入较少

效率最高的IPC,因为不涉及进程之间的任何数据传输(管道等通信方式需要将数据从用户空间的缓冲区复制到内核内存,且接收的进程需要将数据从内核内存再复制到其用户空间缓冲区)

共享内存一般要和其他通信方式一起使用,因为需要使用辅助手段来同步进程对共享内存的访问,以防止产生竟态条件

字节流通过FIFO传输:write将数据从内存复制到内核缓存,read将数据从内核缓存复制到内存

如果进程运行在用户空间的不同部分,同一系统中的两个进程通过使用共享的内存段来交换数据

设置访问权限后,每个进程都可以访问此段

每个进程都有指向次内存段的指针

image

// 创建一段共享内存/获取一段已经存在的共享内存:
	   #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmget(key_t key, size_t size, int shmflg);
//key全局唯一的标识了一段共享内存,一般是用16进制表示,非0值
//size为共享内存段大小,单位为字节,如果是获取已经存在的内存,则可以指定为0
//成功返回共享内存的标识符,失败返回-1并设置errno
shmflg:
IPC_CREAT 创建
IPC_EXCL 判断共享内存是否存在,需要和上一个一起使用 IPC_EXCL | IPC_CREAT
SHM_HUGETLB  类似mmap的MAP_HUGETLB,系统将使用大页面来为共享内存分配空间
SHM_HUGE_2MB,SHM_HUGE_1GB
SHM_NORESERVE 类似MAP_NORESERVE ,不为共享内存保留交换分区,物理内存不足时,对该共享内存执行写操作将触发SIGSEGC信号

如果成功创建,则这段共享内存所有的字节都会被初始化为0

与之关联的内核shmid_ds结构体会被初始化(初始化具体看Linux高性能服务器编程第13章:13.6.1)

共享内存创建之后不能立刻访问,需要将其关联到进程的地址空间中,使用后也需要将它从进程地址空间中分离

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

       void *shmat(int shmid, const void *shmaddr, int shmflg);
//返回addr,指向进程的虚拟地址空间中该共享内存起点
       int shmdt(const void *shmaddr);
//用于分离共享内存段
       int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//删除共享内存段,只有当前所有附加内存段的进程都与此进程段分离,内存段才会销毁,只有一个进程需要执行这个步骤

联到进程的地址空间中,使用后也需要将它从进程地址空间中分离

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

       void *shmat(int shmid, const void *shmaddr, int shmflg);
//返回addr,指向进程的虚拟地址空间中该共享内存起点
       int shmdt(const void *shmaddr);
//用于分离共享内存段,解除当前进程和共享内存之间的关联
shmaddr为共享内存的首地址,成功返回0,失败-1
       int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//删除共享内存段,只有当前所有附加内存段的进程都与此进程段分离,内存段才会销毁,只有一个进程需要执行这个步骤

image

image

参数的详细内容:

image

image

image

使用共享内存段

例1:

//使用共享内存段的时间和日期服务器,客户端交互
#include<stdio.h>
#include<sys/shm.h>
#include<time.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

//定义共享内存段的名字key
#define TIME_MEM_KEY 99
#define SEG_SIZE ((size_t)100)//内存段大小
#define oops(m,x){ perror(m);exit(1);}

int main()
{
    int seg_id;
    char *mem_ptr,*t;
    long now;
    int n;
    seg_id = shmget(TIME_MEM_KEY,SEG_SIZE,IPC_CREAT|0777);
    if(seg_id==-1)
    {
        oops("shmget",1);
    }
    mem_ptr = shmat(seg_id,NULL,0);
    if(mem_ptr==(void*)-1)
    {
        oops("shmat",2);
    }
    for(n = 0;n<60;n++)
    {
        time(&now);//获取时间
        strcpy(mem_ptr,ctime(&now));//每次将mem_ptr拷贝为ctime(&now),并非拼接,而是类似删除再写
        sleep(1);
    }
    //删除共享内存段
    shmctl(seg_id,IPC_RMID,NULL);
    return 0;
}
#include<stdio.h>
#include<sys/shm.h>
#include<time.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>

//定义共享内存段的名字key
#define TIME_MEM_KEY 99
#define SEG_SIZE ((size_t)100)//内存段大小
#define oops(m,x){ perror(m);exit(1);}

int main()
{
    int seg_id;
    char *mem_ptr,*t;
    long now;
    int n;
    seg_id = shmget(TIME_MEM_KEY,SEG_SIZE,0777);
    if(seg_id==-1)
    {
        oops("shmget",1);
    }
    mem_ptr = shmat(seg_id,NULL,0);
    if(mem_ptr==(void*)-1)
    {
        oops("shmat",2);
    }
    printf("the time,direct from memory:..%s",mem_ptr);
    // 与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
    shmdt(mem_ptr);

    return 0;
}

![image-](image
image-20211023184459165.png)

image

例2:

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<assert.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
    //创建一段大小4k的共享内存
    int shmid = shmget(100,4096,IPC_CREAT|0644);
    //和当前进程进行关联
    void *ptr = shmat(shmid,NULL,0);
    char* str = "hello,world";
    //向共享内存中写数据
    memcpy(ptr,str,strlen(str));
    getchar();
    shmdt(ptr);
    shmctl(shmid,IPC_RMID,NULL);
    return 0;
}
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<assert.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
    //获取内存
    int shmid = shmget(100,0,IPC_CREAT);
    //和当前进程进行关联
    void *ptr = shmat(shmid,NULL,0);
    printf("%s\n",(char*)ptr);
    shmdt(ptr);
    shmctl(shmid,IPC_RMID,NULL);
    return 0;
}

image

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

   key_t ftok(const char *pathname, int proj_id);

根据指定的路径,int值(但是只会用到其中的一个字节(8位)),生成一个共享内存的key值

第二个参数的范围:0-255,一般指定一个字符:例如:'a'

操作系统如何得知一段共享内存关联了几个进程?

使用此结构体:

          struct shmid_ds {
               struct ipc_perm shm_perm;    /* Ownership and p
ermissions */
               size_t          shm_segsz;   /* Size of segment
 (bytes) */
               time_t          shm_atime;   /* Last attach time */
               time_t          shm_dtime;   /* Last detach time */
               time_t          shm_ctime;   /* Last change time */
               pid_t           shm_cpid;    /* PID of creator */
               pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
               shmatt_t        shm_nattch;  /* No. of current attaches */ //记录了此共享内存关联发进程个数
               ...
           };

shmctl是标记删除共享内存,而不是直接删除

关联的进程数 = 0时才会真正删除

image

本文作者:ziggystardust

本文链接:https://www.cnblogs.com/ziggystardust-pop/p/16147631.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   ziggystardust  阅读(93)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
  1. 1 404 not found REOL
404 not found - REOL
00:00 / 00:00
An audio error has occurred.