设为首页 收藏本站
查看: 371|回复: 0

[经验分享] linux中mmap系统调用原理分析与实现

[复制链接]

尚未签到

发表于 2015-12-6 07:21:57 | 显示全部楼层 |阅读模式
  1、mmap系统调用(功能)
      void* mmap ( void * addr , size_t len , int prot , int flags ,int fd , off_t offset )
      内存映射函数mmap, 负责把文件内容映射到进程的虚拟内存空间, 通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。


  2、mmap系统调用(参数)
      1)addr: 指定映射的起始地址, 通常设为NULL, 由系统指定。
      2)length: 映射到内存的文件长度。
      3) prot:   映射区的保护方式, 可以是:
             PROT_EXEC: 映射区可被执行
             PROT_READ: 映射区可被读取
             PROT_WRITE: 映射区可被写入

        4)flags: 映射区的特性, 可以是:
            MAP_SHARED:写入映射区的数据会复制回文件, 且允许其他映射该文件的进程共享。
            MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy-on-write), 对此区域所做的修改不会写回原文件。

        5)fd: 由open返回的文件描述符, 代表要映射的文件。
      6)offset: 以文件开始处的偏移量, 必须是分页大小的整数倍, 通常为0, 表示从文件头开始映射。

  3、解除映射
      int munmap(void *start,size_t length)
     功能:取消参数start所指向的映射内存,参数length表示欲取消的内存大小。
    返回值:解除成功返回0,否则返回-1,错误原因存于errno中。

  实例分析
mmap系统调用

  4、虚拟内存区域
      虚拟内存区域是进程的虚拟地址空间中的一个同质区间,即具有同样特性的连续地址范围。一个进程的内存映象由下面几部分组成:程序代码、数据、BSS
和栈区域,以及内存映射的区域。

   一个进程的内存区域可以通过查看:/proc/pid/maps
08048000-0804f000 r-xp 00000000 08:01 573748 /sbin/rpc.statd #text
0804f000-08050000 rw-p 00007000 08:01 573748 /sbin/rpc.statd #data
08050000-08055000 rwxp 00000000 00:00 0 #bss
040000000-40015000 r-xp 00000000 08:01 933965 /lib/ld2.3.2.so #text
40015000-40016000 rw-p 00014000 08:01 933965 /lib/ld-2.3.2.so #data

         每一行的域为:start_end perm offset major:minor inode
       1) Start: 该区域起始虚拟地址
       2) End: 该区域结束虚拟地址
       3) Perm: 读、写和执行权限;表示对这个区域,允许进程做什么。这个域的最后一个字符要么是p表示私有的,要么是s表示共享的。
       4) Offset: 被映射部分在文件中的起始地址
       5) Major、minor:主次设备号
       6) Inode:索引结点

  5、vm_area_struct
      Linux内核使用结构vm_area_struct()来描述虚拟内存区域,其中几个主要成员如下:
     1)unsigned long vm_start   虚拟内存区域起始地址
     2)unsigned long vm_end    虚拟内存区域结束地址

       3)unsigned long vm_flags  该区域的标记。如:VM_IO和VM_RESERVED。VM_IO将该VMA标记为内存映射的IO区域,VM_IO会阻止系统将该区域包含在进程的存放转
存(core dump )中,VM_RESERVED标志内存区域不能被换出。

  6、mmap设备操作
      映射一个设备是指把用户空间的一段地址关联到设备内存上。当程序读写这段用户空间的地址时,它实际上是在访问设备。

        mmap设备方法需要完成什么功能?
      mmap方法是file_oprations结构的成员,在mmap系统调用发出时被调用。在此之前,内核已经完成了很多工作。mmap设备方法所需要做的就是建立
虚拟地址到物理地址的页表。
      int (*mmap) (struct file *, struct vm_area_struct *)

        mmap如何完成页表的建立?
     方法有二:
     1)使用remap_pfn_range一次建立所有页表;
     2)使用nopage VMA方法每次建立一个页表。

       构造页表的工作可由remap_pfn_range函数完成,原型如下:
     int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,unsigned long pfn, unsigned long size, pgprot_t prot)

       vma:           虚拟内存区域指针
     virt_addr:   虚拟地址的起始值

     pfn:             要映射的物理地址所在的物理页帧号,可将物理地址>>PAGE_SHIFT得到。
     size:           要映射的区域的大小。
     prot:            VMA的保护属性。


int memdev_mmap(struct file*filp, struct vm_area_struct *vma)
{
Vma->vm_flags |= VM_IO;
Vma->vm_flags |= VM_RESERVED;
if (remap_pfn_range(vma, vma->vm_start,
virt_to_phys(dev- >data)>> PAGE_SHIFT,
size,
vma->vm_page_prot))
return -EAGAIN;
return 0;
}

  7、mmap设备方法实例
       1)memdev.源码

  #ifndef _MEMDEV_H_
#define _MEMDEV_H_

  #ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 0   /*预设的mem的主设备号*/
#endif

  #ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2    /*设备数*/
#endif

  #ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif

  /*mem设备描述结构体*/
struct mem_dev                                    
{                                                        
  char *data;                     
  unsigned long size;      
};

  #endif /* _MEMDEV_H_ */
         2)memdev.c源码
  #include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

  #include
#include "memdev.h"

  static int mem_major = MEMDEV_MAJOR;
  module_param(mem_major, int, S_IRUGO);
  struct mem_dev *mem_devp; /*设备结构体指针*/
  struct cdev cdev;
  /*文件打开函数*/
int mem_open(struct inode *inode, struct file *filp)
{
    struct mem_dev *dev;
   
    /*获取次设备号*/
    int num = MINOR(inode->i_rdev);

      if (num >= MEMDEV_NR_DEVS)
            return -ENODEV;
    dev = &mem_devp[num];
   
    /*将设备描述结构指针赋值给文件私有数据指针*/
    filp->private_data = dev;
   
    return 0;
}

  /*文件释放函数*/
int mem_release(struct inode *inode, struct file *filp)
{
  return 0;
}
static int memdev_mmap(struct file*filp, struct vm_area_struct *vma)
{
      struct mem_dev *dev = filp->private_data; /*获得设备结构体指针*/
      
      vma->vm_flags |= VM_IO;
      vma->vm_flags |= VM_RESERVED;

       
      if (remap_pfn_range(vma,vma->vm_start,virt_to_phys(dev->data)>>PAGE_SHIFT, vma->vm_end - vma->vm_start, vma->vm_page_prot))
          return  -EAGAIN;
               
      return 0;
}

  /*文件操作结构体*/
static const struct file_operations mem_fops =
{
  .owner = THIS_MODULE,
  .open = mem_open,
  .release = mem_release,
  .mmap = memdev_mmap,
};

  /*设备驱动模块加载函数*/
static int memdev_init(void)
{
  int result;
  int i;

    dev_t devno = MKDEV(mem_major, 0);
    /* 静态申请设备号*/
  if (mem_major)
    result = register_chrdev_region(devno, 2, "memdev");
  else  /* 动态分配设备号 */
  {
    result = alloc_chrdev_region(&devno, 0, 2, "memdev");
    mem_major = MAJOR(devno);
  }  
  
  if (result < 0)
    return result;

    /*初始化cdev结构*/
  cdev_init(&cdev, &mem_fops);
  cdev.owner = THIS_MODULE;
  cdev.ops = &mem_fops;
  
  /* 注册字符设备 */
  cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
   
  /* 为设备描述结构分配内存*/
  mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
  if (!mem_devp)    /*申请失败*/
  {
    result =  - ENOMEM;
    goto fail_malloc;
  }
  memset(mem_devp, 0, sizeof(struct mem_dev));
  
  /*为设备分配内存*/
  for (i=0; i < MEMDEV_NR_DEVS; i++)
  {
        mem_devp.size = MEMDEV_SIZE;
        mem_devp.data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
        memset(mem_devp.data, 0, MEMDEV_SIZE);
  }
   
  return 0;

    fail_malloc:
  unregister_chrdev_region(devno, 1);
  
  return result;
}

  /*模块卸载函数*/
static void memdev_exit(void)
{
  cdev_del(&cdev);   /*注销设备*/
  kfree(mem_devp);     /*释放设备结构体内存*/
  unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
}

  MODULE_AUTHOR("yinjiabin);
MODULE_LICENSE("GPL");

  module_init(memdev_init);
module_exit(memdev_exit);

  
  3)测试程序源码
  #include
#include
#include
#include
#include
#include

  int main()
{
int fd;
char *start;
//char buf[100];
char *buf;

/*打开文件*/
fd = open("/dev/memdev0",O_RDWR);
        
buf = (char *)malloc(100);
memset(buf, 0, 100);
start=mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

/* 读出数据 */
strcpy(buf,start);
sleep (1);
printf("buf 1 = %s\n",buf);

   /* 写入数据 */
strcpy(start,"Buf Is Not Null!");

memset(buf, 0, 100);
strcpy(buf,start);
sleep (1);
printf("buf 2 = %s\n",buf);

        
munmap(start,100); /*解除映射*/
free(buf);
close(fd);  
return 0;
}


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-147659-1-1.html 上篇帖子: Linux下共享库(SO)有关的几个环境变量 下篇帖子: kali linux 安装wps提示系统缺少字体
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表