Linux的mmap内存映射机制解析

 在讲述文件映射的概念时,不可避免的要牵涉到虚存(SVR 4VM).实际上,文件映射是虚存的中心概念文件映射一方面给用户提供了一组措施,好似用户将文件映射到自己地址空间的某个部分,使用简单的内存访问指令读写文件;另一方面,它也可以用于内核的基本组织模式,在这种模式种,内核将整个地址空间视为诸如文件之类的一组不同对象的映射.中的传统文件访问方式是,首先用open系统调用打开文件,然后使用read, write以及lseek等调用进行顺序或者随即的I/O.这种方式是非常低效的,每一次I/O操作都需要一次系统调用.另外,如果若干个进程访问同一个文件,每个进程都要在自己的地址空间维护一个副本,浪费了内存空间.而如果能够通过一定的机制将页面映射到进程的地址空间中,也就是说首先通过简单的产生某些内存管理数据结构完成映射的创建.当进程访问页面时产生一个缺页中断,内核将页面读入内存并且更新页表指向该页面.而且这种方式非常方便于同一副本的共享.

     VM是面向对象的方法设计的,这里的对象是指内存对象:内存对象是一个软件抽象的概念,它描述内存区与后备存储之间的映射.系统可以使用多种类型的后备存储,比如交换空间,本地或者远程文件以及帧缓存等等. VM系统对它们统一处理,采用同一操作集操作,比如读取页面或者回写页面等.每种不同的后备存储都可以用不同的方法实现这些操作.这样,系统定义了一套统一的接口,每种后备存储给出自己的实现方法.这样,进程的地址空间就被视为一组映射到不同数据对象上的的映射组成.所有的有效地址就是那些映射到数据对象上的地址.这些对象为映射它的页面提供了持久性的后备存储.映射使得用户可以直接寻址这些对象.

    值得提出的是, VM体系结构独立于Unix系统,所有的Unix系统语义,如正文,数据及堆栈区都可以建构在基本VM系统之上.同时, VM体系结构也是独立于存储管理的,存储管理是由操作系统实施的,:究竟采取什么样的对换和请求调页算法,究竟是采取分段还是分页机制进行存储管理,究竟是如何将虚拟地址转换成为物理地址等等(Linux中是一种叫Three Level Page Table的机制),这些都与内存对象的概念无关.


一、LinuxVM的实现.

      一个进程应该包括一个mm_struct(memory manage struct), 该结构是进程虚拟地址空间的抽象描述,里面包括了进程虚拟空间的一些管理信息: start_code, end_code, start_data, end_data, start_brk, end_brk等等信息.另外,也有一个指向进程虚存区表(vm_area_struct: virtual memory area)的指针,该链是按照虚拟地址的增长顺序排列的.Linux进程的地址空间被分作许多区(vma),每个区(vma)都对应虚拟地址空间上一段连续的区域, vma是可以被共享和保护的独立实体,这里的vma就是前面提到的内存对象

  下面是vm_area_struct的结构,其中,前半部分是公共的,与类型无关的一些数据成员,:指向mm_struct的指针,地址范围等等,后半部分则是与类型相关的成员,其中最重要的是一个指向vm_operation_struct向量表的指针vm_ops, vm_pos向量表是一组虚函数,定义了与vma类型无关的接口.每一个特定的子类,即每种vma类型都必须在向量表中实现这些操作.这里包括了: open, close, unmap, protect, sync, nopage, wppage, swapout这些操作

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. struct vm_area_struct {   
  2.     /*公共的, 与vma类型无关的 */   
  3.     struct mm_struct * vm_mm;   
  4.     unsigned long vm_start;   
  5.     unsigned long vm_end;   
  6.     struct vm_area_struct *vm_next;   
  7.     pgprot_t vm_page_prot;   
  8.     unsigned long vm_flags;   
  9.     short vm_avl_height;   
  10.     struct vm_area_struct * vm_avl_left;   
  11.     struct vm_area_struct * vm_avl_right;   
  12.     struct vm_area_struct *vm_next_share;   
  13.     struct vm_area_struct **vm_pprev_share;   
  14.     /* 与类型相关的 */   
  15.     struct vm_operations_struct * vm_ops;   
  16.     unsigned long vm_pgoff;   
  17.     struct file * vm_file;   
  18.     unsigned long vm_raend;   
  19.     void * vm_private_data;  
  20. };   

vm_ops: open, close, no_page, swapin, swapout……


二、驱动中的mmap()函数解析

       设备驱动的mmap实现主要是将一个物理设备的可操作区域(设备空间)映射到一个进程的虚拟地址空间。这样就可以直接采用指针的方式像访问内存的方式访问设备。在驱动中的mmap实现主要是完成一件事,就是实际物理设备的操作区域到进程虚拟空间地址的映射过程。同时也需要保证这段映射的虚拟存储器区域不会被进程当做一般的空间使用,因此需要添加一系列的保护方式。

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. /*主要是建立虚拟地址到物理地址的页表关系,其他的过程又内核自己完成*/  
  2. static int mem_mmap(struct file* filp,struct vm_area_struct *vma)  
  3. {  
  4.     /*间接的控制设备*/  
  5.     struct mem_dev *dev = filp->private_data;  
  6.       
  7.     /*标记这段虚拟内存映射为IO区域,并阻止系统将该区域包含在进程的存放转存中*/  
  8.     vma->vm_flags |= VM_IO;  
  9.     /*标记这段区域不能被换出*/  
  10.     vma->vm_flags |= VM_RESERVED;  
  11.   
  12.     /**/  
  13.     if(remap_pfn_range(vma,/*虚拟内存区域*/  
  14.         vma->vm_start, /*虚拟地址的起始地址*/  
  15.         virt_to_phys(dev->data)>>PAGE_SHIFT, /*物理存储区的物理页号*/  
  16.      dev->size,    /*映射区域大小*/          
  17.         vma->vm_page_prot /*虚拟区域保护属性*/      
  18.         ))  
  19.         return -EAGAIN;  
  20.   
  21.     return 0;  
  22. }  

具体的实现分析如下:

vma->vm_flags |= VM_IO;
vma->vm_flags |= VM_RESERVED;

上面的两个保护机制就说明了被映射的这段区域具有映射IO的相似性,同时保证这段区域不能随便的换出。就是建立一个物理页与虚拟页之间的关联性。具体原理是虚拟页和物理页之间是以页表的方式关联起来,虚拟内存通常大于物理内存,在使用过程中虚拟页通过页表关联一切对应的物理页,当物理页不够时,会选择性的牺牲一些页,也就是将物理页与虚拟页之间切断,重现关联其他的虚拟页,保证物理内存够用。在设备驱动中应该具体的虚拟页和物理页之间的关系应该是长期的,应该保护起来,不能随便被别的虚拟页所替换。具体也可参看关于虚拟存储器的文章。

接下来就是建立物理页与虚拟页之间的关系,即采用函数remap_pfn_range(),具体的参数如下:

int remap_pfn_range(structvm_area_struct *vma, unsigned long addr,unsigned long pfn, unsigned long size, pgprot_t prot)

1、struct vm_area_struct是一个虚拟内存区域结构体,表示虚拟存储器中的一个内存区域。其中的元素vm_start是指虚拟存储器中的起始地址。
2、addr也就是虚拟存储器中的起始地址,通常可以选择addr = vma->vm_start。
3、pfn是指物理存储器的具体页号,通常通过物理地址得到对应的物理页号,具体采用virt_to_phys(dev->data)>>PAGE_SHIFT.首先将虚拟内存转换到物理内存,然后得到页号。>>PAGE_SHIFT通常为12,这是因为每一页的大小刚好是4K,这样右移12相当于除以4096,得到页号。
4、size区域大小
5、区域保护机制。
返回值,如果成功返回0,否则正数。


三、系统调用mmap函数解析

        介绍完VM的基本概念后,我们可以讲述mmapmunmap系统调用了.mmap调用实际上就是一个内存对象vma的创建过程,

1、mmap函数

       Linux提供了内存映射函数mmap,它把文件内容映射到一段内存上(准确说是虚拟内存上),通过对这段内存的读取和修改,实现对文件的读取和修改 。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作

先来看一下mmap的函数声明:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. 头文件:   
  2. <unistd.h>   
  3. <sys/mman.h>   
  4.   
  5. 原型: void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offsize);   
  6.   
  7. /* 
  8. 返回值: 成功则返回映射区起始地址, 失败则返回MAP_FAILED(-1).  
  9.  
  10. 参数:  
  11.     addr: 指定映射的起始地址, 通常设为NULL, 由系统指定.  
  12.     length: 将文件的多大长度映射到内存.  
  13.     prot: 映射区的保护方式, 可以是:  
  14.         PROT_EXEC: 映射区可被执行.  
  15.         PROT_READ: 映射区可被读取.  
  16.         PROT_WRITE: 映射区可被写入.  
  17.         PROT_NONE: 映射区不能存取.  
  18.     flags: 映射区的特性, 可以是:  
  19.         MAP_SHARED: 对映射区域的写入数据会复制回文件, 且允许其他映射该文件的进程共享.  
  20.         MAP_PRIVATE: 对映射区域的写入操作会产生一个映射的复制(copy-on-write), 对此区域所做的修改不会写回原文件.  
  21.         此外还有其他几个flags不很常用, 具体查看linux C函数说明.  
  22.     fd: 由open返回的文件描述符, 代表要映射的文件.  
  23.     offset: 以文件开始处的偏移量, 必须是分页大小的整数倍, 通常为0, 表示从文件头开始映射. 
  24. */  

mmap的作用是映射文件描述符fd指定文件的 [off,off + len]区域至调用进程的[addr, addr + len]的内存区域, 如下图所示:


mmap系统调用的实现过程是

1.先通过文件系统定位要映射的文件;

2.权限检查,映射的权限不会超过文件打开的方式,也就是说如果文件是以只读方式打开,那么则不允许建立一个可写映射; 

3.创建一个vma对象,并对之进行初始化; 

4.调用映射文件的mmap函数,其主要工作是给vm_ops向量表赋值;

5.把该vma链入该进程的vma链表中,如果可以和前后的vma合并则合并;

6.如果是要求VM_LOCKED(映射区不被换出)方式映射,则发出缺页请求,把映射页面读入内存中.


2、munmap函数

      munmap(void * start, size_t length):

      该调用可以看作是mmap的一个逆过程.它将进程中从start开始length长度的一段区域的映射关闭,如果该区域不是恰好对应一个vma,则有可能会分割几个或几个vma.

      msync(void * start, size_t length, int flags):

     把映射区域的修改回写到后备存储中.因为munmap时并不保证页面回写,如果不调用msync,那么有可能在munmap后丢失对映射区的修改.其中flags可以是MS_SYNC, MS_ASYNC, MS_INVALIDATE, MS_SYNC要求回写完成后才返回, MS_ASYNC发出回写请求后立即返回, MS_INVALIDATE使用回写的内容更新该文件的其它映射.该系统调用是通过调用映射文件的sync函数来完成工作的.

     brk(void * end_data_segement):

将进程的数据段扩展到end_data_segement指定的地址,该系统调用和mmap的实现方式十分相似,同样是产生一个vma,然后指定其属性.不过在此之前需要做一些合法性检查,比如该地址是否大于mm->end_code, end_data_segementmm->brk之间是否还存在其它vma等等.通过brk产生的vma映射的文件为空,这和匿名映射产生的vma相似,关于匿名映射不做进一步介绍.库函数malloc就是通过brk实现的.


四、实例解析

       下面这个例子显示了把文件映射到内存的方法,源代码是:

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. /************关于本文 档******************************************** 
  2. *filename: mmap.c 
  3. *purpose: 说明调用mmap把文件映射到内存的方法 
  4. *wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com) 
  5. Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言 
  6. *date time:2008-01-27 18:59 上海大雪天,据说是多年不遇 
  7. *Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途 
  8. * 但请遵循GPL 
  9. *Thanks to: 
  10. *                Ubuntu 本程序在Ubuntu 7.10系统上测试完全正常 
  11. *                Google.com 我通常通过google搜索发现许多有用的资料 
  12. *Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力 
  13. * 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献! 
  14. *********************************************************************/  
  15. #include <sys/mman.h> /* for mmap and munmap */  
  16. #include <sys/types.h> /* for open */  
  17. #include <sys/stat.h> /* for open */  
  18. #include <fcntl.h>     /* for open */  
  19. #include <unistd.h>    /* for lseek and write */  
  20. #include <stdio.h>  
  21.   
  22. int main(int argc, char **argv)  
  23. {  
  24.     int fd;  
  25.     char *mapped_mem, * p;  
  26.     int flength = 1024;  
  27.     void * start_addr = 0;  
  28.   
  29.     fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);  
  30.     flength = lseek(fd, 1, SEEK_END);  
  31.     write(fd, "\0", 1); /* 在文件最后添加一个空字符,以便下面printf正常工作 */  
  32.     lseek(fd, 0, SEEK_SET);  
  33.     mapped_mem = mmap(start_addr, flength, PROT_READ,        //允许读  
  34.         MAP_PRIVATE,       //不允许其它进程访问此内存区域  
  35.             fd, 0);  
  36.       
  37.     /* 使用映射区域. */  
  38.     printf("%s\n", mapped_mem); /* 为了保证这里工作正常,参数传递的文件名最好是一个文本文件 */  
  39.     close(fd);  
  40.     munmap(mapped_mem, flength);  
  41.     return 0;  
  42. }  

编译运行此程序:

gcc -Wall mmap.c
./a.out text_filename

上面的方法因为用了PROT_READ,所以只能读取文件里的内容,不能修改,如果换成PROT_WRITE就可以修改文件的内容了。又由于 用了MAAP_PRIVATE所以只能此进程使用此内存区域,如果换成MAP_SHARED,则可以被其它进程访问,比如下面的

[cpp] view plaincopy
在CODE上查看代码片派生到我的代码片
  1. #include <sys/mman.h> /* for mmap and munmap */  
  2. #include <sys/types.h> /* for open */  
  3. #include <sys/stat.h> /* for open */  
  4. #include <fcntl.h>     /* for open */  
  5. #include <unistd.h>    /* for lseek and write */  
  6. #include <stdio.h>  
  7. #include <string.h> /* for memcpy */  
  8.   
  9. int main(int argc, char **argv)  
  10. {  
  11.     int fd;  
  12.     char *mapped_mem, * p;  
  13.     int flength = 1024;  
  14.     void * start_addr = 0;  
  15.   
  16.     fd = open(argv[1], O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);  
  17.     flength = lseek(fd, 1, SEEK_END);  
  18.     write(fd, "\0", 1); /* 在文件最后添加一个空字符,以便下面printf正常工作 */  
  19.     lseek(fd, 0, SEEK_SET);  
  20.     start_addr = 0x80000;  
  21.     mapped_mem = mmap(start_addr, flength, PROT_READ|PROT_WRITE,        //允许写入  
  22.         MAP_SHARED,       //允许其它进程访问此内存区域  
  23.         fd, 0);  
  24.   
  25.     * 使用映射区域. */  
  26.     printf("%s\n", mapped_mem); /* 为了保证这里工作正常,参数传递的文件名最好是一个文本文 */  
  27.     while((p = strstr(mapped_mem, "Hello"))) { /* 此处来修改文件 内容 */  
  28.         memcpy(p, "Linux", 5);  
  29.         p += 5;  
  30.     }  
  31.       
  32.     close(fd);  
  33.     munmap(mapped_mem, flength);  
  34.     return 0;  
  35. }  


五、mmap和共享内存对比

      共享内存允许两个或多个进程共享一给定的存储区,因为数据不需要来回复制,所以是最快的一种进程间通信机制。共享内存可以通过mmap()映射普通文件(特殊情况下还可以采用匿名映射)机制实现,也可以通过系统V共享内存机制实现。应用接口和原理很简单,内部机制复杂。为了实现更安全通信,往往还与信号灯等同步机制共同使用。

对比如下:

      mmap机制:就是在磁盘上建立一个文件,每个进程存储器里面,单独开辟一个空间来进行映射。如果多进程的话,那么不会对实际的物理存储器(主存)消耗太大。

      shm机制:每个进程的共享内存都直接映射到实际物理存储器里面。

1、mmap保存到实际硬盘,实际存储并没有反映到主存上。优点:储存量可以很大(多于主存);缺点:进程间读取和写入速度要比主存的要慢。

2、shm保存到物理存储器(主存),实际的储存量直接反映到主存上。优点,进程间访问速度(读写)比磁盘要快;缺点,储存量不能非常大(多于主存)

使用上看:如果分配的存储量不大,那么使用shm;如果存储量大,那么使用mmap

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

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

相关文章

svn add Default@2x.png的文件含有@的文件名注意事项

为什么80%的码农都做不了架构师&#xff1f;>>> iOS的Icon里面&#xff0c;包含符号 &#xff0c;svn add Icon2x.png&#xff0c;没法加进去。 解决的办法是&#xff0c;在文件名最后加一个&#xff0c;例如 svn add Icon2x.png 或者svn add Icon\2x.png\ 转载于…

Linux 设备驱动的固件加载

作为一个驱动作者, 你可能发现你面对一个设备必须在它能支持工作前下载固件到它里面. 硬件市场的许多地方的竞争是如此得强烈, 以至于甚至一点用作设备控制固件的 EEPROM 的成本制造商都不愿意花费. 因此固件发布在随硬件一起的一张 CD 上, 并且操作系统负责传送固件到设备自身…

Linux USB 驱动开发(二)—— USB 驱动几个重要数据结构

前面我们学习了USB 驱动的一个描述符&#xff0c;下面来学习 USB 驱动的几个重要数据结构 一、struct usb_interface 接口函数 [cpp] view plaincopy struct usb_interface { struct usb_host_interface *altsetting; struct usb_host…

Linux USB 驱动开发(三)—— 编写USB 驱动程序

前面学习了USB驱动的一些基础概念与重要的数据结构&#xff0c;那么究竟如何编写一个USB 驱动程序呢&#xff1f;编写与一个USB设备驱动程序的方法和其他总线驱动方式类似&#xff0c;驱动程序把驱动程序对象注册到USB子系统中&#xff0c;稍后再使用制造商和设备标识来判断是否…

Linux USB 驱动开发(一)—— USB设备基础概念

在终端用户看来&#xff0c;USB设备为主机提供了多种多样的附加功能&#xff0c;如文件传输&#xff0c;声音播放等&#xff0c;但对USB主机来说&#xff0c;它与所有USB设备的接口都是一致的。一个USB设备由3个功能模块组成&#xff1a;USB总线接口、USB逻辑设备和功能单元&am…

Linux USB 驱动开发(四)—— 热插拔那点事

学习USB热插拔之前&#xff0c;先学习一些USB的硬件知识&#xff1a; 一、USB基础概念 1、硬件知识&#xff08;USB插座和插头&#xff09; 在最初的标准里&#xff0c;USB接头有4条线&#xff1a;电源&#xff0c;D-,D,地线。我们暂且把这样的叫做标准的USB接头吧。后来OTG出现…

Linux USB 驱动开发(五)—— USB驱动程序开发过程简单总结

设备驱动程序是操作系统内核和机器硬件之间的接口&#xff0c;由一组函数和一些私有数据组成&#xff0c;是应用程序和硬件设备之间的桥梁。在应用程序看来&#xff0c;硬件设备只是一个设备文件&#xff0c;应用程序可以像操作普通文件一样对硬件设备进行操作。 设备驱动程序是…

android软键盘上推ui解决

为什么80%的码农都做不了架构师&#xff1f;>>> http://bbs.csdn.net/topics/340198955 android软键盘上推ui解决 good job 转载于:https://my.oschina.net/macleo/blog/204882

Linux USB 驱动开发实例(一) —— USB摄像头驱动实现源码分析

Spac5xx的实现是按照标准的USB VIDEO设备的驱动框架编写&#xff08;其具体的驱动框架可参照/usr/src/linux/drivers/usb/usbvideo.c文件&#xff09;&#xff0c;整个源程序由四个主体部分组成&#xff1a; 设备模块的初始化模块和卸载模块&#xff0c;上层软件接口模块&#…

System Center 2012R2之SCVMM云部署SCOM(2-2)

SCVMM云部署SCOM安装过程1、在SCVMM中&#xff0c;使用WINDOWS SERVER 2012母盘创建云主机SCOM在SCVMM中先创建到一个私有云指定一个私有云名称选择资源主机指定逻辑网络默认选负载衡器跳过VIP模板跳过选择端口&#xff0c;下一步选择存储分类指定存储的VM路径各只读共享默认设…

Linux USB 驱动开发实例(二)—— USB 鼠标驱动注解及测试

参考2.6.14版本中的driver/usb/input/usbmouse.c。鼠标驱动可分为几个部分&#xff1a;驱动加载部分、probe部分、open部分、urb回调函数处理部分。 一、驱动加载部分 [cpp] view plaincopy static int __init usb_mouse_init(void) { int retval usb_register(&a…

MySQL5.6 更改字段属性仍旧会锁全表,注意这个坑!

如图&#xff1a;如果开发让修改表字段属性&#xff0c;建议用pt-online-schema-change。MySQL5.6的在线DDL会锁全表。注意这个坑。另外&#xff0c;增加、删除字段或索引不会锁全表&#xff0c;删除主键会锁全表。

Linux USB 驱动开发实例 (三)—— 基于USB总线的无线网卡浅析

回顾一下USB的相关知识 USB(Universal Serial Bus)总线又叫通用串行外部总线&#xff0c;它是20世纪90年代发展起来的。USB接口现在得到了广泛的应用和普及&#xff0c;现在的PC机中都带有大量的USB接口。它最大的特点就是方便通用、支持热插拔并且可以在一个接口上插上多个设备…

Linux 设备驱动开发思想 —— 驱动分层与驱动分离

前面我们学习I2C、USB、SD驱动时&#xff0c;有没有发现一个共性&#xff0c;就是在驱动开发时&#xff0c;每个驱动都分层三部分&#xff0c;由上到下分别是&#xff1a; 1、XXX 设备驱动 2、XXX 核心层 3、XXX 主机控制器驱动 而需要我们编写的主要是设备驱动部分&#xff0c…

CortexM0开发 —— UART时序分析

通用异步收发传输器(UniversalAsynchronousReceiver/Transmitter)&#xff0c;通常称作UART&#xff0c;是一种异步收发传输器。将数据由串行通信与并行通信间作传输转换&#xff0c;作为并行输入成为串行输出的芯片UART是一种通用串行数据总线&#xff0c;用于异步通信。该总线…

CortexM0开发 —— LPC11C14的UART使用方法

LPC1100系列微控制器UART LPC1100系列Cortex-M0微控制器具有一个符合16C550工业标准的异步串行口&#xff08;UART&#xff09;。此口同时增加了调制解调器&#xff08;Modem&#xff09;接口&#xff0c;DSR、DCD和RI Modem信号是只用于LQFP48和PLCC44封装的管脚配置。 特性…

Linux SD卡驱动开发(一) —— SD 相关基础概念

一.SD/MMC卡基础概念 1.1.什么是MMC卡 MMC&#xff1a;MMC就是MultiMediaCard的缩写&#xff0c;即多媒体卡。它是一种非易失性存储器件&#xff0c;体积小巧(24mm*32mm*1.4mm)&#xff0c;容量大,耗电量低,传输速度快&#xff0c;广泛应用于消费类电子产品中。 1.2.什么是SD卡…

Linux SD卡驱动开发(二) —— SD 卡驱动分析HOST篇

回顾一下前面的知识&#xff0c;MMC 子系统范围三个部分&#xff1a; HOST 部分是针对不同主机的驱动程序&#xff0c;这一部是驱动程序工程师需要根据自己的特点平台来完成的。 CORE 部分: 这是整个MMC 的核心存&#xff0c;这部分完成了不同协议和规范的实现&#xff0c;并为…

MVC应用程序显示RealPlayer(rm)视频

本篇博文是演示MVC应用程序显示RealPlayer视频。 客户端能观看到RealPlayer视频&#xff0c;前提条件是需要安装RealPlayer客户端&#xff0c;就是想看Falsh或理WMV视频一样&#xff0c;均要安装客户端或相关插件等。 Insus.NET实现方法&#xff0c;还是在控制器中Render RealP…

Linux从入门到精通系列之PPTP

Linux从入门到精通系列之PPTP今天我们来说下怎么在linux环境下如何搭建PPTP-&#xff0c;PPTP&#xff08;Point to Point Tunneling Protocol&#xff09;&#xff0c;即点对点隧道协议。该协议是在PPP协议的基础上开发的一种新的增强型安全协议&#xff0c;支持多协议虚拟专用…