Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1885727
  • 博文数量: 496
  • 博客积分: 12043
  • 博客等级: 上将
  • 技术积分: 4778
  • 用 户 组: 普通用户
  • 注册时间: 2010-11-27 14:26
文章分类

全部博文(496)

文章存档

2014年(8)

2013年(4)

2012年(181)

2011年(303)

2010年(3)

分类: 嵌入式

2012-06-21 10:06:09

原文地址作者


一、用到的API与数据结构
先看用户空间使用的API

  1. #include <sys/mman.h>
  2. void *mmap(void *start, //映射的范围首地址,通常设NULL,让系统自动选地址,映射成功后返回该地址
  3.          size_t length, //映射的范围的大小
  4.          int prot,      //映射区的保护属性 PROT_EXEC PROT_READ PROT_WRITE PROT_NONE
  5.          int flags,     //映射区的属性,注意MAP_SHARED、MAP_PRIVATE必选其一
  6.          int fd,        //文件描述符
  7.          off_t offsize);//偏移量,后面讲
  8. 返回值:若映射成功则返回映射区的内存起始地址,否则返回MAP_FAILED(-1),错误原因存于errno 中。

  9. 另外注意: offsize参数是有限制的。。。(以下摘自man手册) 
  10. EINVAL  We don’t like start or length or offset. 
  11.         (E.g., they are too large, or not aligned on a PAGESIZE boundary.)

在字符设备驱动模块里,有一个stuct file_operations结构
其中的fop->mmap() 指向你自己的mmap钩子函数。
用户空间里对一个字符设备文件进行mmap()系统调用后,最终会调用驱动模块里的mmap钩子函数
在mmap钩子函数里,需要调用下面这个API
  1. int remap_pfn_range(struct vm_area_struct *vma, //这个结构很重要!!后面讲
  2.                     unsigned long virt_addr,    //要映射的范围的首地址
  3.                     unsigned long pfn,          //要映射的范围对应的物理内存的页帧号!!重要
  4.                     unsigned long size,         //要映射的范围的大小
  5.                     pgprot_t prot);             //PROTECT属性,mmap()中来的

内核维护VMA的链表和树形结构(即vm_area_struct),我们没事别建新的VMA,否则会打破这种组织结构。
用户空间每次调用mmap(),内核都会新建一个vm_area_struct数据结构,我们只关注几个重要的成员
  1. struct vm_area_struct{
  2.    unsigned long vm_start; //要映射的范围的首地址
  3.    unsigned long vm_end;   //要映射的范围的尾地址

  4.    pgprot_t vm_page_prot; //来自mmap()的形参prot
  5.    sturct file *vm_file;   //字符设备文件所对应的file数据结构
  6.    unsigned long vm_pgoff; //来自mmap()的形参offsize,注意它是以PAGE为单位的,不是字节
  7.    unsigned long vm_flags; //来自mmap()的形参flags
  8. }

在你的mmap钩子函数里,象下面这样就可以了
  1. int my_mmap(struct file *filp, struct vm_area_struct *vma){
  2.    //......省略,page很重要,其他的参数一般照下面就可以了
  3.    remap_pfn_range(vma, vma->vm_start, page,
  4.         (vma->vm_end - vma->vm_start), vma->vm_page_prot);
  5.    //......省略
  6. }

二、代码示例

内核模块里分配内存,通过proc把此内存的EA地址、大小告诉用户空间。
用户空间通过proc获取内存相关信息后,调用mmap,往内存里写字符串。

内核模块代码:
  1. #include <linux/module.h>
  2. #include <linux/types.h>
  3. #include <linux/fs.h>
  4. #include <asm/io.h>
  5. #include <asm/system.h>
  6. #include <linux/cdev.h>
  7. #include <linux/proc_fs.h>
  8. #include <linux/mm.h>
  9. #define PROC_SHM_MAP_DIR "shm_dir"
  10. #define PROC_SHM_MAP_INFO "shm_info"
  11. #define PAGE_ORDER   0
  12. #define PAGES_NUMBER 1

  13. static int dbg_major = 215;
  14. struct cdev dgb_dev;
  15. struct proc_dir_entry *proc_shm_map_dir;
  16. unsigned long kernel_memaddr = 0;
  17. unsigned long kernel_memsize = 0;

  18. int get_shm_proc_info(char *page, char **start, off_t off, int count)
  19. {
  20.     return sprintf(page, "lx %lu\n", __pa(kernel_memaddr), kernel_memsize);
  21. }

  22. void create_shm_proc(void)
  23. {
  24.     if(NULL == (proc_shm_map_dir = proc_mkdir(PROC_SHM_MAP_DIR, NULL)) ){
  25.         printk("proc create error!\n");
  26.         return ;
  27.     }
  28.     create_proc_info_entry(PROC_SHM_MAP_INFO, 0, proc_shm_map_dir, get_shm_proc_info);
  29. }

  30. void destroy_shm_proc(void)
  31. {
  32.     remove_proc_entry(PROC_SHM_MAP_INFO, proc_shm_map_dir);
  33.     remove_proc_entry(PROC_SHM_MAP_DIR, NULL);
  34. }

  35. int shm_mmap(struct file *filp, struct vm_area_struct *vma)
  36. {
  37.     unsigned long page;
  38.     page = virt_to_phys((void *)kernel_memaddr) >> PAGE_SHIFT;

  39.     if( remap_pfn_range(vma, vma->vm_start, page, (vma->vm_end - vma->vm_start),
  40.          vma->vm_page_prot) )
  41.         return -1;
  42.     vma->vm_flags |= VM_RESERVED;
  43.     printk("remap_pfn_rang page:[%lu] ok.\n", page);
  44.     return 0;
  45. }

  46. int create_shm_mem(void)
  47. {
  48.     if( NULL == (kernel_memaddr =__get_free_pages(GFP_KERNEL, PAGE_ORDER) )) {
  49.         printk("alloc kernel memory failed!\n");
  50.         return -1;
  51.     }
  52.     SetPageReserved(virt_to_page(kernel_memaddr)); // important !! only 1 page
  53.     kernel_memsize = PAGES_NUMBER * PAGE_SIZE;
  54.     printk("The kernel mem addr=lx, size=%lu\n",__pa(kernel_memaddr), kernel_memsize);
  55.     return 0;
  56. }

  57. void destroy_shm_mem(void)
  58. {
  59.     printk("The string written by user is: %s\n", (unsigned char *)kernel_memaddr);
  60.     ClearPageReserved(virt_to_page(kernel_memaddr));// important !!
  61.     free_pages(kernel_memaddr, PAGE_ORDER);
  62.     return;
  63. }

  64. static const struct file_operations dbg_fops ={
  65.     .owner = THIS_MODULE,
  66.     .mmap = shm_mmap,
  67. };

  68. static void dbg_exit(void)
  69. {
  70.     cdev_del(&dgb_dev);
  71.     unregister_chrdev_region(MKDEV(dbg_major, 0), 1);
  72.     destroy_shm_proc();
  73.     destroy_shm_mem();
  74. }

  75. int dbg_init(void)
  76. {
  77.     int result;
  78.     dev_t devno ;
  79.     struct cdev *p_cdev = &dgb_dev;

  80.     if (dbg_major){
  81.         devno =MKDEV(dbg_major,0);
  82.         result = register_chrdev_region(devno,1, "leon");
  83.     }
  84.     else{
  85.         result=alloc_chrdev_region(&devno, 0, 1,"leon");
  86.         dbg_major = MAJOR(devno);
  87.     }

  88.     if (result < 0)
  89.         return result;
  90.     printk("the major device No. is %d\n", dbg_major );

  91.     cdev_init(p_cdev,&dbg_fops);
  92.     p_cdev->owner = THIS_MODULE;
  93.     result = cdev_add(p_cdev,devno,1);

  94.     if(result){
  95.         printk(KERN_NOTICE "Error %d while adding dbg",result);
  96.         return result;
  97.     }

  98.     create_shm_mem();
  99.     create_shm_proc();
  100.     return 0;
  101. }

  102. MODULE_AUTHOR("leonwang202");
  103. MODULE_LICENSE("Dual BSD/GPL");
  104. module_init(dbg_init);
  105. module_exit(dbg_exit);

用户空间代码:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <string.h>
  5. #include <fcntl.h>
  6. #include <sys/stat.h>
  7. #include <sys/types.h>
  8. #include <sys/mman.h>
  9. #include <errno.h>

  10. int main(int argc, char* argv[])
  11. {
  12.     if(argc != 2) {
  13.         printf("Usage: %s string\n", argv[0]);
  14.         return 0;
  15.     }
  16.     unsigned long phymem_addr, phymem_size;
  17.     char *map_addr;
  18.     char s[256];
  19.     int fd;

  20.     if( (fd = open("/proc/shm_dir/shm_info", O_RDONLY)) < 0) {
  21.         printf("cannot open file /proc/shm_dir/shm_info\n");
  22.         return 0;
  23.     }

  24.     read(fd, s, sizeof(s));
  25.     if(2 != sscanf(s, "lx %lu", &phymem_addr, &phymem_size)){
  26.         printf("data format from /proc/shm_dir/shm_info error!\n");
  27.         close(fd);
  28.         return -1;
  29.     };
  30.     close(fd);
  31.     printf("phymem_addr=%lx, phymem_size=%lu\n", phymem_addr, phymem_size);

  32.     if( (fd = open("/dev/enetdbg", O_RDWR|O_NONBLOCK)) < 0){
  33.         printf("open /dev/enetdbg error!\n");
  34.         return -1;
  35.     }

  36.     map_addr = mmap(NULL,
  37.                     phymem_size,
  38.                     PROT_READ|PROT_WRITE,
  39.                     MAP_SHARED,
  40.                     fd,
  41.                     0); // ignored, because we don't use "vm->pgoff" in driver.

  42.     if( MAP_FAILED == map_addr){
  43.         printf("mmap() error:[%d]\n", errno);
  44.         return -1;
  45.     }

  46.     strcpy(map_addr, argv[1]);
  47.     munmap(map_addr, phymem_size);

  48.     close(fd);
  49.     return 0;
  50. }
注意:我们在shm_mmap里没使用vm->pgoff,所以mmap()的形参offsize可以无视。

三、使用/dev/mem
想偷懒的,可以使/dev/mem,就不用费劲写内核模块里的mmap钩子函数了
(此用法在帖子中有写)

内核代码不用改,图干净的话只要把mmap钩子函数相关的东东全删掉就ok了
用户空间代码得改:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <string.h>
  5. #include <fcntl.h>
  6. #include <sys/stat.h>
  7. #include <sys/types.h>
  8. #include <sys/mman.h>
  9. #include <errno.h>

  10. int main(int argc, char* argv[])
  11. {
  12.     if(argc != 2) {
  13.         printf("Usage: %s string\n", argv[0]);
  14.         return 0;
  15.     }
  16.     unsigned long phymem_addr, phymem_size;
  17.     char *map_addr;
  18.     char s[256];
  19.     int fd;

  20.     if( (fd = open("/proc/shm_dir/shm_info", O_RDONLY)) < 0) {
  21.         printf("cannot open file /proc/shm_dir/shm_info\n");
  22.         return 0;
  23.     }
  24.     read(fd, s, sizeof(s));
  25.     if(2 != sscanf(s, "lx %lu", &phymem_addr, &phymem_size)){
  26.         printf("data format from /proc/shm_dir/shm_info error!\n");
  27.         close(fd);
  28.         return -1;
  29.     };
  30.     close(fd);
  31.     printf("phymem_addr=%lx, phymem_size=%lu\n", phymem_addr, phymem_size);

  32.     if( (fd = open("/dev/mem", O_RDWR|O_NONBLOCK)) < 0){
  33.         printf("open /dev/mem error!\n");
  34.         exit(0);
  35.     }
  36.     map_addr = mmap(NULL,
  37.                     phymem_size,
  38.                     PROT_READ|PROT_WRITE,
  39.                     MAP_SHARED,
  40.                     fd,
  41.                     phymem_addr);

  42.     strcpy(map_addr, argv[1]);
  43.     munmap(map_addr, phymem_size);
  44.     close(fd);
  45.     return 0;
  46. }
注意:
1.你的linux环境下没有/dev/mem的话,敲命令 mknod /dev/mem c 1 1       原因后面讲
2.这次mmap()形参offsize不能无视,因为创建/dev/mem的内核模块的mmap钩子函数里用到了vm->pgoff


看到底是哪个模块创建了/dev/mem
  1. start_kernel --> kernel_init --> do_basic_setup --> do_initcalls --> chr_dev_init

文件 linux_2_6_24/drivers/char/mem.c 中 chr_dev_init() 函数
  1. static int __init chr_dev_init(void)
  2. {
  3.    if (register_chrdev(MEM_MAJOR,"mem",&memory_fops)) //cat /proc/devices 时显示 1 mem
  4.       printk("unable to get major %d for memory devs\n", MEM_MAJOR);

  5.    for (i = 0; i < ARRAY_SIZE(devlist); i )
  6.       device_create(mem_class, NULL,        //据数组新建N多设备,主设备号为1,从设备号来自数组
  7.                   MKDEV(MEM_MAJOR, devlist[i].minor),devlist[i].name);
  8. }

  9. static const struct {
  10.     unsigned int minor;
  11.     char *name;
  12.     umode_t mode;
  13.     const struct file_operations *fops;
  14. } devlist[] = { /* list of minor devices */             //我擦,major为1的字符设备这么多....
  15.     {1, "mem", S_IRUSR | S_IWUSR | S_IRGRP, &mem_fops}, //mknod /dev/mem c 1 1 原因知道了吧
  16.     {2, "kmem", S_IRUSR | S_IWUSR | S_IRGRP, &kmem_fops},
  17.     {3, "null", S_IRUGO | S_IWUGO, &null_fops},
  18. #ifdef CONFIG_DEVPORT
  19.     {4, "port", S_IRUSR | S_IWUSR | S_IRGRP, &port_fops},
  20. #endif
  21.     {5, "zero", S_IRUGO | S_IWUGO, &zero_fops},
  22.     {7, "full", S_IRUGO | S_IWUGO, &full_fops},
  23.     {8, "random", S_IRUGO | S_IWUSR, &random_fops},
  24.     {9, "urandom", S_IRUGO | S_IWUSR, &urandom_fops},
  25.     {11,"kmsg", S_IRUGO | S_IWUSR, &kmsg_fops},
  26. #ifdef CONFIG_CRASH_DUMP
  27.     {12,"oldmem", S_IRUSR | S_IWUSR | S_IRGRP, &oldmem_fops},
  28. #endif
  29. };

---------------------------------- 华丽的分割线 -------------------------------------------

若共享小块连续内存,上面例子用get_free_pages可以分配多达几M的连续空间(MAX_ORDER目前是11)。
若共享大块连续内存,就得靠uboot帮忙了,给linux kernel传参数的时候,用mem=大小

《ldd3》中的分配内存那章有专门一节讲解,通过API
  1. void *alloc_bootmem(unsigned long size);
  2. void free_bootmem(unsigned long addr, unsigned long size);
阅读(1570) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~