精华内容
下载资源
问答
  • 2021-03-07 19:52:59

    把共享内存区对象映射到调用进程的地址空间

    void *shmat(int shmid, const void *shmaddr, int shmflg);
    //such as:
            char* shm_p = shmat(shmId,NULL,0);
            if(shm_p == (void*)-1){
                    perror("shmat:");
                    return -1;
            }
    
    

    第一个参数 shmid

    shmget函数 的返回值,共享内存标识符

    第二个参数 shmaddr

    指定共享内存出现在进程内存地址的什么位置
    直接指定为NULL让内核自己决定一个合适的地址位置

    第三个参数 shmflag

    0:读写模式
    SHM_RDONLY:为只读模式

    返回值
    附加好的共享内存地址

    更多相关内容
  • shmat 和 shmdt

    2019-11-25 23:06:26
    shmat, shmdt 共享内存的操作 概要 #include <sys/types.h>#include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg); int shmdt(const void *shma...

    https://linux.die.net/man/2/shmat
    shmat, shmdt  共享内存的操作

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

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

    int shmdt(const void *shmaddr);

    描述

     shmat()把由shmid标识的共享内存关联到调用进程的地址空间。关联地址由 shmaddr指定,遵循如下规则的其中之一。

    • 如果 shmaddr 不是 NULL, 并且shmflg指定了SHM_RND, 关联的地址与shmaddr 向下舍入到最近的SHMLBA的倍数。否则, shmaddr必须是 page-aligned 地址。
    • 如果shmflg中设置了SHM_RDONLY,则该共享地址段是只读的,当前调用进程必须只设置读权限。如果shmflg中没有设置SHM_RDONLY,则该共享地址段是可读写的, 该进程必须设置读写权限。对于只写共享内存段没有指定和说明。
    • shmflg中可能会设置SHM_REMAP  (只有linux版本支持), 来标识共享内存段的映射可以替换替换已经存在的映射,起始地址是shmaddr, 连续共享内存段大小的内存。通常情况下,如果该地址范围已经存在一个映射,会返回EINVAL 错误。这种情况下,shmaddr不能是NULL。
    • 调用进行的brk(2) 值不能被attach操作改变。当进程退出时,共享内存段会自动的detach。相同的共享内存段可能被attach成只读或读写的,或多个,在进程的地址空间内。

    成功的shmat()调用会更新共享内存段所关联的shmid_ds 结构体的成员,如下所示:

    • shm_atime,设置成当前时间
    • shm_lpid,设置成当前调用进程的process-ID。
    • shm_nattch, 加1.

    shmdt()  是从调用进程的地址空间中detach  shmaddr指定的共享内存段所在的地址。shmat的逆操作。to-be-detached 的共享内存段必须是由shmat()调用返回的shmaddr。

    一个成功的shmdt()调用会更新共享内存段所关联的shmid_ds 结构体的成员,如下所示:

    • shm_atime,设置成当前时间
    • shm_lpid,设置成当前调用进程的process-ID。
    • shm_nattch, 减1.如果变成了0, 共享内存段被标识要进行deletion,该共享内存段会被删除。fork(2)的子进程会继承关联的共享内存段。
    • 在 execve(2)调用之后,所有关联的共享内存段会从该进程中detach。
    • _exit(2)调用后,该进程关联的所有共享内存段都会被detach。

    返回值

    如果shmat()成功,返回共享内存段在该进程中关联地址;如果出错,返回(void *) -1, errno 来标识出错的原因。

    如果shmdt()成功,则返回0;否则,返回(void *) -1, errno 来标识出错的原因。

    shmat()的出错码

    • EACCES, the calling process does not have the required permissions for the requested attach type, and does not have the CAP_IPC_OWNER capability.
    • EIDRM, shmid points to a removed identifier.
    • EINVAL, Invalid shmid value, unaligned (i.e., not page-aligned and SHM_RND was not specified) or invalid shmaddr value, or can't attach segment at shmaddr, or SHM_REMAP was specified and shmaddr was NULL.
    • ENOMEM, Could not allocate memory for the descriptor or for the page tables.

    shmdt() 的出错码

    • EINVAL, There is no shared memory segment attached at shmaddr; or, shmaddr is not aligned on a page boundary.

    说明:

    调用shmat()时,最好设置shmaddr为NULL,这样共享内存段的关联会具有可移植性。值得注意的是,这种方式进行的共享内存段的关联,不同的进程会关联到不同的地址。因此,任何一个保持共享内存的指针必须是相对地址(典型的是,从共享内存段的起始地址开始)而不是绝对地址。

    下面的系统参数会影响到shmat()操作:

    • SHMLBA:Segment low boundary address multiple. Must be page aligned. For the current implementation the SHMLBA value is PAGE_SIZE.
    • The implementation places no intrinsic limit on the per-process maximum number of shared memory segments (SHMSEG).

     

     

    展开全文
  • 一、实验目标 深入理解操作系统的段、页式内存管理,深入理解段表、页表、逻辑地址、线性地址、物理地址等概念; 实践段、页式内存管理的地址映射过程; 编程实现段、页式内存管理上的内存共享,从而深入理解操作...

    本文参考https://blog.csdn.net/laoshuyudaohou/article/details/103843023
    一、实验目标
    深入理解操作系统的段、页式内存管理,深入理解段表、页表、逻辑地址、线性地址、物理地址等概念;
    实践段、页式内存管理的地址映射过程;
    编程实现段、页式内存管理上的内存共享,从而深入理解操作系统的内存管理。
    二、实验1内容和结果
    (一). 跟踪地址翻译过程
      这节实验的目的是用 Bochs 的调试功能获取变量的虚拟地址映射的物理地址。
      在 Linux-0.11 中运行下面的死循环程序:

    #include <stdio.h>
    
    int i = 0x12345678;
    int main(void)
    {
        printf("The logical/virtual address of i is 0x%08x", &i);
        fflush(stdout);//stdout是系统定义的标准输出文件指针.默认情况下指屏幕,那就是把缓冲区的内容写到屏幕上。
                       //int fflush(FILE * stream);fflush()会强迫将缓冲区内的数据写回参数stream指定的文件中.
                       //清空标准输出缓冲区,即刷新输出缓冲区,即将缓冲区的东西输出到屏幕上
        while (i)
            ;
        return 0;
    }
    

    (二)在上述程序text.c的运行过程中,在命令行窗口按下 Ctrl+C,进入 Bochs 的调试状态,在Ubuntu 中的 Bochs下
    直接输入命令 c,continue 程序的运行
    如果显示的下一条指令不是 cmp …(这里指语句以 cmp 开头),就用 n 命令单步运行几步,直到停在 cmp …。
    使用 u /8命令获取反汇编代码,如图:
    在这里插入图片描述
    首先,cmp指令是比较指令,,比较第一个参数和第二个参数的大小
    现在第一个参数是 dword ptr[esp]
    dword ptr表示这是一个双字指针,即所要寻址的数据是一个双字(4字节)
    由于text.c中只有while (i){}语句有一个判断语句,即i为0吗,为0结束循环。可知 i 的虚拟地址为 ds:0x3004。

    (三)使用 sreg命令查看段寄存器的值如图:
    地址翻译相关的几张简图资料:
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述
    关于此地址翻译过程的更多细节,请参考《Linux0.11内核完全注释》一书中的 5.3.1-5.3.4 节。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
     1)16位的段选择符LDTR 的值为 0x0068 = 0000000001101 0 00。
     根据段选择符的结构,最后两位表示请求特权级 RPL = 00,倒数第三位为表指示标志 TI - 0,表示该选择符存放在 GDT 中,前面的 13 位为索引值 Index = 1101 (二进制) = 13 (十进制),表示该进程的 LDT 表存放在 GDT 表中的 13 号位置。
     2)GDTR 的base值为 0x00005cb8,GDT基址为 0x00005cb8。 LDT 表在GDT表的第13项描述符。于是目的 LDT 表的地址存在内存地址0x00005cb8+13*8:
     3)用 xp /32w 0x00005cb8 查看从地址0x00005cb8 开始,32 个字的内容
    在这里插入图片描述
    所得结果为 0x52d00068 (4~7)
    0x000082fd (0~3),根据段描述符的格式
    在这里插入图片描述
    这里将上述结果组合为 0x00fd52d0,即为 LDT 表的物理地址。查看 LDT 表的内容如图所示:
    在这里插入图片描述
    Linux-0.11 中,LDT 表的第一项为空,第二项为代码段,第三项为数据和堆栈段 ds&ss。
    可以看看地址翻译相关的几张简图资料的图5-8
    于是所需的 ds 的段描述符为 0x00003fff 0x10c0f300,组合为 0x10000000,即为 ds 段的起始地址。
      于是 i 即 ds:0x3004的线性地址为 0x10003004。
      接下来通过页表将线性地址映射到物理地址,规则如下:
    在这里插入图片描述
    线性地址为 0x10003004的二进制为0001 0000 00 00 0000 0011 0000 0000 0100
    首先计算线性地址的 页目录号为 64, 页表号为 3, 页内偏移为 4。
    页目录表的物理地址保存在 CR3 寄存器中,使用 creg命令获取其值:
    资料:一页内存为4KB,4x2^10
    通过缺页加载机制形成4G线性地址空间。
    4G线性地址空间=独享的内存空间+共享的内存空间x共享次数+占用的磁盘空间
    占用的磁盘空间涉及缓冲区,缓冲区是内存。主要涉及函数do_no_page()执行缺页处理,会调用get_empty_page();get_free_page();share_page();bread_page();put_page()。
    每个进程64M,就支持了126,当然挤了会卡,所有人工定义最多64个

    在这里插入图片描述
    可知页目录表的基址为 0,这是 Linux-0.11 启动时 boot/head.s设置的。目的页目录号为 64,查看该目录项:
    在这里插入图片描述
    于是线性地址对应的页帧为 0x00fa3,加上业内偏移 0x004,得到 0x00fa3004 即为变量 i 的物理地址。验证如下:
    在这里插入图片描述
    使用命令 setpmem 0x00fa3004 4 0直接修改内存,将变量 i 的值设为 0,再用 c 命令继续运行 Bochs,死循环程序退出。
    在这里插入图片描述

    三、实验2
    (一)基于共享内存的生产者—消费者程序
    信号量实现生产者消费者模型代码 pc.c:

    #include <unistd.h>
    #include <sys/types.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <fcntl.h>          
    #include <sys/stat.h>        
    #include <semaphore.h>
    #include <sys/types.h>  
    #include <sys/wait.h>
    
    #define M 530    		/*打出数字总数*/
    #define N 5       		/*消费者进程数*/
    #define BUFSIZE 10      /*缓冲区大小*/
    
    int main()
    {
    	sem_t *empty, *full, *mutex;/*3个信号量*/
    	int fd; /*共享缓冲区文件描述符*/
        int  i,j,k,child;
        int  data;/*写入的数据*/
        pid_t pid;
        int  buf_out = 0;  /*从缓冲区读取位置*/
        int  buf_in = 0;   /*写入缓冲区位置*/
        
    	/*打开信号量,O_CREAT|O_EXCL:如果没有指定的信号量就创建,第三个和第四个参数此时是需要的,如果有指定的信号量就打开,第三个和第四个参数此时是不需要的*/
    	empty = sem_open("empty", O_CREAT|O_EXCL, 0644, BUFSIZE); /*剩余资源,初始化为size。生产者消耗资源--empty,消费者消耗资源中的数据++empty*/
        full = sem_open("full", O_CREAT|O_EXCL, 0644, 0);         /*已使用资源,初始化为0.生产者消耗资源++full,消费者消耗资源中的数据--full*/
    	mutex = sem_open("mutex", O_CREAT|O_EXCL, 0644, 1);       /*互斥量,初始化为1*/
        
        fd = open("buffer.txt", O_CREAT|O_TRUNC|O_RDWR,0666); //只读模式打开或创建文件buffer.txt。 (O_CREAT如果指定文件不存在,则创建这个文件,O_TRUNC如果文件存在,并且以只写/读写方式打开,则清空文件全部内容,O_RDWR读写模式)
        lseek(fd,BUFSIZE*sizeof(int),SEEK_SET);/*lseek v.前后移动。刷新了10*4个字节的缓冲区,可存放10个数字*/
        write(fd,(char *)&buf_out,sizeof(int));//把buf_out中的4字节数据写入文件fd,即将待读取位置存入buffer.txt后,以便子进程之间通信*/
       
       
       
     /*生产者进程*/
        if((pid=fork())==0)
        {
    		printf("I'm producer. pid = %d\n", getpid());
    		/*生产多少个产品就循环几次*/
            for( i = 0 ; i < M; i++)
            {
    			                   /*empty大于0,才能生产*/
                sem_wait(empty);//此时信号量empty为初值10,减一变为9,9大于0不阻塞
                sem_wait(mutex);//此时信号量mutex初值1,减一变为0
                                   /*写入一个字符*/
                lseek(fd, buf_in*sizeof(int), SEEK_SET); //新的读写位置变为buf_in*4
                write(fd,(char *)&i,sizeof(int));//将i处4字节的数据写入文件buffer.txt
                                    /*更新写入缓冲区位置,保证在0-9之间*/
    			                    /*生产完一轮产品(文件缓冲区只能容纳BUFSIZE个产品编号)后*/
    			                    /*将缓冲文件的位置指针重新定位到文件首部。*/			
                buf_in = (buf_in + 1) % BUFSIZE;
                sem_post(mutex);
                sem_post(full);     /*共享区中已使用资源++,唤醒消费者线程*/
            }
            printf("producer end.\n");
            fflush(stdout);         /*确保将输出立刻输出到标准输出。*/
            return 0;
        }
    	else if(pid < 0)//fork()返回负值:创建子进程失败
        {
            perror("Fail to fork!\n");
            return -1;
        }
    
    
    
    /*消费者进程*/
        for( j = 0; j < N ; j++ )//循环fork()N次
        {
            if((pid=fork())==0)//fork()子进程返回0,父进程返回子进程ID
            {
    			for( k = 0; k < M/N; k++ )
                {
                    sem_wait(full);/*共享区中已使用资源--,一开始为0会阻塞此处*/
                    sem_wait(mutex);
    				
                    /*获得读取位置*/
                    lseek(fd,BUFSIZE*sizeof(int),SEEK_SET);
                    read(fd,(char *)&buf_out,sizeof(int));
                    /*读取数据*/
                    lseek(fd,buf_out*sizeof(int),SEEK_SET);
                    read(fd,(char *)&data,sizeof(int));
                    /*写入读取位置*/
                    buf_out = (buf_out + 1) % BUFSIZE;
                    lseek(fd,BUFSIZE*sizeof(int),SEEK_SET);
                    write(fd,(char *)&buf_out,sizeof(int));
    
                    sem_post(mutex);
                    sem_post(empty);/*共享区中剩余资源++,唤醒生产者进程*/
                    /*消费资源*/
                    printf("%d:  %d\n",getpid(),data);
                    fflush(stdout);
                }
    			printf("child-%d: pid = %d end.\n", j, getpid());
                return 0;
            }
    		else if(pid<0)//fork()返回负值:创建子进程失败
    		{
    			perror("Fail to fork!\n");
    			return -1;
    		}
    	}
    
    
    
    	/*回收线程资源*/
        child = N + 1;
        while(child--)
            wait(NULL);//父进程等待回收所有子进程
        /*释放信号量*/
        sem_unlink("full");
        sem_unlink("empty");
        sem_unlink("mutex");
        /*释放资源*/
        close(fd);
        return 0;
    }
    

    这个程序跟上次实验的不同之处在于:使用共享内存替换文件缓冲区;将生产者和消费者分成两个不同的程序,两个都是单进程的。
    Linux 中,将不同进程的虚拟地址空间通过页表映射到物理内存的同一区域即为共享内存。如图所示:
    在这里插入图片描述
     两个进程都可以访问共享内存,但是为了确保对共享内存操作的互斥,仍需要使用一个信号量在每次读写的时候进行限制,然后由另外两个信号量保来保证共享内存中每次至多有 10 个数字。
     用共享内存和信号量实现的 producer.c代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/shm.h>
    #include <semaphore.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    #define SIZE 10
    #define M 510
    
    int main()
    {
        int shm_id;
        int count = 0;
        int *p;
        int curr;
        sem_t *sem_empty, *sem_full, *sem_shm;
        
        sem_empty = sem_open("empty", O_CREAT|O_EXCL, 0644, SIZE);
        sem_full = sem_open("full", O_CREAT|O_EXCL, 0644, 0);
        sem_shm = sem_open("shm",  O_CREAT|O_EXCL, 0644, 1);
        
        shm_id = shmget(2521, SIZE, IPC_CREAT | IPC_EXCL | 0664); // 创建共享内存
        p = (int *)shmat(shm_id, NULL, 0);
        
        while (count <= M) {
            sem_wait(sem_empty);
            sem_wait(sem_shm);
            
            curr = count % SIZE;
            *(p + curr) = count;
            printf("Producer: %d\n", *(p + curr));
            fflush(stdout);
            
            sem_post(sem_shm);
            sem_post(sem_full);
            count++;
        }
        printf("producer end.\n");
        fflush(stdout);
        return 0;
    }
    

    consumer.c的代码如下:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/shm.h>
    #include <semaphore.h>
    #include <unistd.h>
    #include <fcntl.h>
    
    #define SIZE 10
    #define M 510
    
    int main()
    {
        int shm_id;
        int count = 0;
        struct shmid_ds buf;
        int *p;
        int curr;
        
        sem_t *sem_empty, *sem_full, *sem_shm;
        sem_empty = sem_open("empty", SIZE);
        sem_full = sem_open("full", 0);
        sem_shm = sem_open("shm", 1);
        
        shm_id = shmget(2521, 0, 0);//得到一个共享内存标识符或创建一个共享内存对象,并返回共享内存标识符
        p = (int *)shmat(shm_id, NULL, 0);//连接共享内存标识符为shmid的共享内存,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问
        
        while(count <= M) {
            sem_wait(sem_full);
            sem_wait(sem_shm);
            
            curr = count % SIZE;
            printf("%d:%d\n", getpid(), *(p + curr));
            fflush(stdout);
            
            sem_post(sem_shm);
            sem_post(sem_empty);
            count++;
        }
        printf("consumer end.\n");
        fflush(stdout);
        
        sem_unlink("empty");
        sem_unlink("full");
        sem_unlink("shm");
        shmctl(shm_id, IPC_RMID, &buf);//shmctl共享内存管理.IPC_RMID删除这片共享内存
        return 0;
    }
    

    为了让两个进程在同一个终端运行,这里使用终端的后台运行功能:

    ./producer &
    ./consumer
    

    在 Ubuntu 下的运行结果如图所示:
    在这里插入图片描述
    (二). 共享内存的实现

    1. 实现共享内存
        函数 int shmget(key_t key, size_t size, int shmflg)会新建或打开一页内存,然后返回该页共享内存的 shmid。忽略 shmflg 参数后,可知一页共享内存需要保存的信息有 唯一标识符 key、共享内存的大小 size,然后还需要一个参数保存共享内存页面的地址。于是共享内存信息的结构体如下:
    struct shm_tables     //共享内存信息结构体
    {
    	int key;            //标识符
    	int size;           //共享内存的大小
    	unsigned long page; //主内存页面号
    };
    

    根据要求,shmget()函数需要获取一块空闲的内存的物理页面来创建共享内存,
    shmat()函数需要将该物理页面映射到进程的虚拟内存空间,然后返回其首地址。

    函数 get_free_page()能够获取一块空闲的物理页面,并且返回该页面的起始物理地址,用于 shmget()的实现。
    函数 put_page()能够把物理页面映射到指定线性地址空间处。为了能让两个进程操作这块共享内存,需要把物理页面分别映射到该进程自己的虚拟空间。内核为每个进程虚拟了一块地址空间,然后分配数据段、代码段和栈段,由函数 do_execve()实现,虚拟空间的分配如下图:
    在这里插入图片描述
    其中 start_code为代码段起始地址,brk为代码段和数据段的总长度,start_stack为栈的起始地址,这些值保存在进程的 task_struct中。brk和 start_stack之间的空间为栈准备,栈底是闲置的,可将共享内存映射到这块空间。
      shm.c的代码如下:

    #include <asm/segment.h>
    #include <linux/kernel.h>
    #include <linux/sched.h>
    #include <linux/mm.h>
    #include <errno.h>
    
    #define _SHM_NUM 20//共享内存数量最大值
    
    struct shm_tables
    {
    	int key;
    	int size;
    	unsigned long page;
    } shm_tables[_SHM_NUM];
    
    int sys_shmget(int key, int size)
    {
        int i;
        unsigned long page;
        for (i = 0; i < _SHM_NUM; i++) /* 查看 key 对应的共享内存是否已存在 */
            if(shm_tables[i].key == key)
                return i;
        if (size > PAGE_SIZE) /* 内存大小超过一页 */
            return -EINVAL;
        page = get_free_page(); /* 获取物理内存页面 */
        if(!page)
            return -ENOMEM;
        for (i = 0; i < _SHM_NUM; i++) {
            if(shm_tables[i].key == 0) {
                shm_tables[i].key = key;
                shm_tables[i].size = size;
                shm_tables[i].page = page;
                return i;
            }
        }
        return -1;  /* 共享内存数量已满 */
    }
    
    //get_free_page(void)在主内存区中取空闲物理页面,搜索标志每个页面引用次数的数组mem_map[物理内存页面数]
    //put_page(page,address)把一给定物理内存页面映射到虚拟地址空间指定处,即在虚拟地址空间指定处映射物理内存。
    //据地址page判断指定页面是否是在用户区,是否空闲。据虚拟地址空间指定处address计算address对应的目录项指针和二级页表指针。
    //物理页面空闲是指没有被独占。把空闲页指针page置给二级页表指针。如果之前判断指定页面是不在用户区或不空闲,就要使用get_free_page()
    
    void * sys_shmat(int shmid)
    {
        int i;
        unsigned long data_base;
        if (shmid < 0 || shmid >= _SHM_NUM || shm_tables[shmid].key == 0) // 判断 shmid 是否合法,shid在0~20
            return -EINVAL;
        put_page(shm_tables[shmid].page, current->brk + current->start_code); // 把物理页面映射到进程的虚拟空间,物理页面要空闲且在用户区,物理页面空闲是指没有被独占。
        current->brk += PAGE_SIZE;  // 修改总长度brk,current->brk为代码段长度+数据段长度+bss段长度
        return (void*)(current->brk - PAGE_SIZE);
    }
    

    修改 mm/Makefile,将 shm.c一块编译进 Image:

    OBJS = memory.o page.o shm.o
    # add
    shm.o shm.c: ../include/asm/segment.h ../include/linux/kernel.h \
      ../include/linux/sched.h ../include/linux/mm.h ../include/errno.h
    

    然后对系统调用的实现做后续补充。在 include/linux/sys.h中添加:

    extern int sys_shmget();
    extern int sys_shmat();
    
    fn_ptr sys_call_table[] = { ..., sys_shmget, sys_shmat };
    

    在 include/unistd.h及 Linux-0.11 中的 usr/include/unistd.h添加:

    #define __NR_shmget     76
    #define __NR_shmat      77
    

    最后修改 kernel/system_call.s中系统调用的数量:

    nr_system_calls = 78
    

    共享内存实现完毕。
    2. 在 Linux-0.11 运行生产者——消费者程序
      ;修改 consumer.c和 producer.c,从而能够在 Linux-0.11 运行这两个程序:

    #define __LIBRARY__   /* 在第一行添加 */
    
    /* add */
    _syscall2(int,sem_open,const char*,name,unsigned int,value)
    _syscall1(int,sem_wait,sem_t *,sem)
    _syscall1(int,sem_post,sem_t *,sem)
    _syscall1(int,sem_value,sem_t *,sem)
    _syscall1(int,sem_unlink,const char *,name)
    _syscall2(int,shmget,int,key,int,size)
    _syscall1(int, shmat, int, shmid)
    
    int main()
    {
        ...
        /* change */
        sem_empty = sem_open("empty", SIZE);
        sem_full = sem_open("full", 0);
        sem_shm = sem_open("shm", 1);
        shm_id = shmget(2521, SIZE);
        p = (int *)shmat(shm_id);
        ...
        return 0;
    }
    

    然后在 Linux-0.11 中编译运行,结果如下(为了便于显示,这里将 M 改为 60、将 SIZE 改为 4):
    在这里插入图片描述

    展开全文
  • 简单介绍进程间通信的方式,以及使用共享内存shmat的步骤和API介绍,最后并提供了一个生产者-消费者程序示例进行说明。

    进程间通信三种方式

    在linux系统中,每个进程都有独立的虚拟空间地址,通过MMU地址转换将虚拟地址与物理地址进行映射,每个进程相同的虚拟地址空间都会映射到不同的物理地址,每个进程在物理内存空间都是相互独立和隔离的。
    不同的进程之间如果需要相互通信,该怎么办?因为不同的进程在物理内存上是相互隔离的,所以需要借助第三方工具来完成进程间通信。其实进程间通信的本质就是交换数据,进程间交换数据有三种方式:通过文件、通过内核、共享内存。

    • 通过文件:AB进程通过访问同一个磁盘文件(I/O访问)进行数据交换
    • 通过内核:进程间用户空间是相互独立,但是内核空间都是同一个, 因此可以通过内核这个中介去进行数据交换
    • 共享内存:每个进程间的虚拟地址会映射到不同的物理地址,如果允许映射到同一块物理地址就可以进行数据交换

    在这里插入图片描述

    共享内存特点

    • 共享内存 VS 通过文件:共享内存读写速度更快
    • 共享内存VS通过内核:抛弃了内核“代理人”角色,让两个进程直接通过一块内存通信。减少了内存拷贝(从用户拷贝到内核、从内核拷贝到用户空间),减少了2次系统调用,提高系统性能
    • 共享内存缺点:是共享内存并未提供同步机制,所以需要用其他机制来同步对共享内存的方位,这将由程序员来完成。一般可以通过信号量、互斥锁、文件锁等配合使用,防止数据的踩踏。

    共享内存原理

    共享内存是由IPC为进程创建的一个特殊的地址范围,出现在该进程的地址空间中,其他进程可以将同一段共享内存连接到他们自己的地址空间中。所有进程都可以访问共享内存中的地址,就好像他们是由malloc分配的一样。如果一个进程向共享内存中写了数据,那么其他进程将立刻能够看到。
    在这里插入图片描述

    共享内存使用

    在这里插入图片描述

    创建/获取共享内存shmget

    该函数用来创建/获取共享内存

    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmget(key_t key, size_t size, int shmflg);
    
    • key:IPC 对象的键值,一般为IPC_PRIVATE或ftok返回的key值
    • size:共享内存的大学,一般为存物理页的整数倍
    • shmflg:IPC_CREAT:如果不存在与制定的key对应的段,那么就创建一个新段;IPC_EXCL:若key制定的内存存在且制定了IPC_CREAT,返回EEXIST错误;
    • 返回值:共享内存的标识符ID

    映射共享内存shmat

    该函数将shmid标识的共享内存引入到当前进程的虚拟地址空间

    #include <sys/types.h>
    #include <sys/shm.h>
    void *shmat(int shmid, const void *shmaddr, int shmflg);
    
    • shmid:共享内存的IPC对象ID
    • shmaddr:若为NULL:共享内存会被attach到一个合适的虚拟地址空间,建议使用NULL;不为NULL:系统会根据参数及地址边界对齐等分配一个合适的地址
    • shmflg:IPC_RDONLY:附加只读权限,不指定的话默认是读写权限;IPC_REMAP:替换位于shmaddr处的任意既有映射:共享内存段或内存映射;
    • 返回值:共享内存段的地址

    共享内存读写

    共享内存的读写就要注意共享内存多进程访问同步,一般可以通过信号量、互斥锁、文件锁等配合使用,防止数据的踩踏。

    解除内存映射shmdt

    该函数解除内存映射,将共享内存分离出当前进程的地址空间

    #include <sys/types.h>
    #include <sys/shm.h>
    int shmdt(const void *shmaddr);
    
    • shmaddr:共享内存地址

    注意,函数shmdt仅仅是使进程和共享内存脱离关系,将共享内存的引用计数减1,并为删除共享内存。通过#ipc -m就可查看某个IPC对象的状态,其中“连接数”就是表示该共享内存对象被引用的计数。
    在这里插入图片描述

    删除共享内存

    上面看到,shmdt仅仅是使进程和共享内存脱离关系,并未删除共享内存。当共享内存的引用次数未0,可以调用shmctl的IPC_RMID命令才会删除共享内存。或者进程结束后,也会被删除掉。
    shmctl获取/设置共享内存对象属性

    #include <sys/ipc.h>
    #include <sys/shm.h>
    int shmctl(int shmid, int cmd, struct shmid_ds *buf);
    
    • shmid:共享内存的对象ID
    • cmd:IPC_RMID:删除共享内存段及关联的shmid_ds数据结构
    • buf:指向包含共享模式和访问权限的结构
    • 返回值:成功返回0,失败返回-1

    更多的cmd可以通过man shmct去查看。

    生产者-消费者代码示例

    下面是一个简单的生产者-消费者模型,生产者producer进程负责将用户输入的数据写到共享内存中,消费者customer进程负责将共享内存中的读出来并打印出来。下面的程序示例通过共享内存中的变量written_by_you标记进行一个读写的同步,保证读写操作是互斥的。

    //share.h
    #define TEXT_SZ 2048
    struct shared_use_st 
    {
    	int written_by_you;
    	char some_text[TEXT_SZ];
    };
    
    //customer.c
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include "share.h"
    int main()
    {
    	int running = 1;
    	void *shared_memory = (void *)0;
    	struct shared_use_st *shared_stuff;
    	int shmid;	
    	srand((unsigned int)getpid());    
    	shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
    	if (shmid == -1) 
    	{
    		fprintf(stderr, "shmget failed\n");
    		exit(EXIT_FAILURE);
    	}
    	shared_memory = shmat(shmid, (void *)0, 0);
    	if (shared_memory == (void *)-1) {
    		fprintf(stderr, "shmat failed\n");
    		exit(EXIT_FAILURE);
    	}
    	printf("Memory attached at %X\n", (int)shared_memory);
    	shared_stuff = (struct shared_use_st *)shared_memory;
    	shared_stuff->written_by_you = 0;
    	while(running) 
    	{
    		if (shared_stuff->written_by_you) 
    		{
    			printf("You wrote: %s", shared_stuff->some_text);
    			sleep( rand() % 4 );  
    			shared_stuff->written_by_you = 0;
    			if (strncmp(shared_stuff->some_text, "end", 3) == 0) 
    			{
    				running = 0;
    			}
    		}
    	}
    	if (shmdt(shared_memory) == -1) 
    	{
    		fprintf(stderr, "shmdt failed\n");
    		exit(EXIT_FAILURE);
    	}
    	if (shmctl(shmid, IPC_RMID, 0) == -1) 
    	{
    		fprintf(stderr, "shmctl(IPC_RMID) failed\n");
    		exit(EXIT_FAILURE);
    	}	
    	printf("customer exit.\n");
    	exit(EXIT_SUCCESS);
    }
    
    
    
    //producer.c
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/shm.h>
    #include "share.h"
    int main()
    {
    	int running = 1;
    	void *shared_memory = (void *)0;
    	struct shared_use_st *shared_stuff;
    	char buffer[BUFSIZ];
    	int shmid;
    
    	shmid = shmget((key_t)1234, sizeof(struct shared_use_st), 0666 | IPC_CREAT);
    
    	if (shmid == -1) 
    	{
    		fprintf(stderr, "shmget failed\n");
    		exit(EXIT_FAILURE);
        }
    
    	shared_memory = shmat(shmid, (void *)0, 0);
    	if (shared_memory == (void *)-1) 
    	{
    		fprintf(stderr, "shmat failed\n");
    		exit(EXIT_FAILURE);
    	}
    
    	printf("Memory attached at %X\n", (int)shared_memory);
    
    	shared_stuff = (struct shared_use_st *)shared_memory;
    	while(running) 
    	{
    		while(shared_stuff->written_by_you == 1) 
    		{
    			sleep(1);            
    			printf("waiting for client...\n");
    		}
    		printf("Enter some text: ");
    		fgets(buffer, BUFSIZ, stdin);
            
    		strncpy(shared_stuff->some_text, buffer, TEXT_SZ);
    		shared_stuff->written_by_you = 1;
    
    		if (strncmp(buffer, "end", 3) == 0) 
    		{
    			running = 0;
    		}
    	}
    
    	if (shmdt(shared_memory) == -1) 
    	{
    		fprintf(stderr, "shmdt failed\n");
    		exit(EXIT_FAILURE);
    	}
    	printf("producer exit.\n");
    	exit(EXIT_SUCCESS);
    }
    
    

    参考资料
    [1]嵌入式C语音自我修养,王立涛
    [2]大连理工大学《嵌入式软件设计》慕课

    展开全文
  • 共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成。下面的表格列出了这四个函数的函数原型及其具体说明。 shmget函数原型 shmget(得到一个共享内存标识符或创建一个共享内存对象) 所需头文件 #include <...
  • 2、shmat()函数 – 斜体样式at:attach—挂接共享内存 第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下...
  • ptest = (unsigned int *)shmat_wrapper(iShmId, RRM_PNULL, 0); fprintf(stderr,"ptest(%p)", ptest); if ((void *)-1 == ptest) { fprintf(stderr,"SHM(%d) attach failed", uiShmKey); shmctl_...
  • mmap和shmat区别

    千次阅读 2019-01-06 22:16:15
    (2)通过void *shmat(int shmid, constvoid shmaddr,int shmflg);连接成功后把共享内存区对象映射到调用进程的地址空间 (3)通过void *shmdt(constvoid* shmaddr);断开用户级页表到共享内存的那根箭头。 (4)...
  • 允许本进程使用共某块共享内存 shmat()3.写入/读出4.禁止本进程使用这块共享内存 shmdt()5.删除这块共享内存 shmctl()或者命令行下ipcrmftok()。它有两个参数,一个是字符串,一个是字符。字符串一般用当前进程的...
  • shmat——将共享内存与当前进程相关联 shmdt——将当前进程与共享内存间脱离关联 shmctl——操控共享内存 一、shmget——创建/打开共享内存 1.原函数 表头文件 #include <sys/ipc.h> #include &l
  • 本函数调用并不删除所指定的共享内存区,而只是将先前用shmat函数连接(attach)好的共享内存脱离(detach)目前的进程 错误代码 EINVAL:无效的参数shmaddr 4、shmctl函数...
  • 文章目录共享内存介绍shmget:创建共享内存shmat:将创建好的共享内存连接到某个进程,并指定内存空间shmdt:脱钩函数,把共享内存与当前进程脱离开代码实例 共享内存介绍 共享内存是IPC中效率最高的一个,它是原理...
  • 函数原型: void *shmat(int shmid, const void *shmaddr, int shmflg) 4,函数参数: msqid:共享内存标识符 shmaddr:指定共享内存出现在进程内存地址的什么位置,直接指定为NULL让内核自己决定一个合适的地址位置...
  • 共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成。下面的表格列出了这四个函数的函数原型及其具体说明。1.   shmget函数原型shmget(得到一个共享内存标识符或创建一个共享内存对象)所需头文件#include...
  • 2-映射共享内存shmat(); 当然还有撤销操作shmdt(); 为了使创建的共享内存使用时区分开来,可以用对应的IPC键值来一一对应 一、相关函数介绍 1.创建IPC键值 #include <sys/types.h> #include <sys/ipc.h&...
  • key_t key=ftok(".",4321); int shmid=shmget(key,4096,IPC_CREAT); if(shmid<0) { perror("shmget"); return -1;...关于存储映射问题,运行测试程序的...shmat: Permission denied 翻译之后:shmat:权限被拒绝 ...
  • 4.shmat ( ):挂接共享内存 void *shmat(int shmid, const void *shmaddr, int shmflg); [参数shmid]:共享存储段的标识符。 [参数*shmaddr]:shmaddr = 0,则存储段连接到由内核选择的第一个可以地址上(推荐使用...
  • 共享内存(shared memory)共享内存1、背景2、定义3、两种方式(mmap上一篇博客说明)3.2、shmget3.2.1、使用流程和基本原理3.2.2、函数原型(创建共享内存)3.2.3、其他相关AP函数3.2.3.1、shmat ( ):挂接共享内存...
  • 第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下: void * shmat (int shm_id, const void *shm_...
  • 56-System V 共享内存-shmat 与 shmdt

    千次阅读 2017-01-12 14:09:52
    在理解内核对象后,接下来谈谈 System V 共享内存的 shmat 和 shmdt 函数。1. shmget 函数再次回顾一下 shmget 函数。图1 中左侧表示的是一个进程空间(未区分内核空间与用户空间),进程空间中并非所有的线性地址被...
  • linux中的两种共享内存。一种是我们的IPC通信System V版本的共享内存,另外的一种就是我们今天提到的存储映射I/O(mmap函数) 在说mmap之前我们先说一下普通的读写文件的原理,进程调用read或是write后会陷入内核,...
  • 2014年4月2日共享内存函数(shmget、shmat、shmdt、shmctl)及其范例 - guoping16的专栏 - 博客频道 - http://doc.xuehai.net登录 | 注册guoping16的专栏目录视图摘要视图订阅个人资料2014开源技术大会(读书汇) ...
  • //获取共享内存id void *shmat(int shmid, const void *shmaddr, int shmflg); //挂载共享内存 int shmdt(const void *shmaddr); //卸载共享内存 int shmctl(int shmid, int cmd, struct shmid_ds *buf); //例如cmd...
  • 参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1. (4)控制共享内存 int shmctl(int shm_id, int cmd, struct shmid_ds *buf); shm_id: 共享内存 描述符。 cmd: 要采取的操作,它可以取...
  • if ((ret = shmat (shm_id, (char *) 0xb7f17000, 0)) == (void *)-1) { perror("shmat"); exit(1); } memset (ret, 0, size); } else {/* shm_id = shmget (key, size, 0); if (shm_id == -1) { perror ("init_shm...
  • 关键字:Linux、进程间的通信、信号通信、kill()、signal()、shmget()、shmat()、shmdt()、shmctl()、共享存储、消息通信、管道通信。
  • c程序实现 写内存 ...//shmat for share memory ...两者指向同一个内存区域是 “pshm = (char *) shmat(shmid, 0, 0) // 指针都指向内存为(0,0)的区域” 参考文献 IPC–共享内存 Share Memory 实例 ...
  • 第一次创建完共享内存时,它还不能被任何进程访问,shmat()函数的作用就是用来启动对该共享内存的访问,并把共享内存连接到当前进程的地址空间。它的原型如下: void * shmat(int shm_id, const void *shm_addr,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 12,954
精华内容 5,181
关键字:

shmat

友情链接: Hardcopy_WinCe.rar