5-5 Linux内存、IO与实例
来源:互联网 发布:快递软件是什么 编辑:程序博客网 时间:2024/05/18 22:55
5-5 Linux内存、IO与实例讲解
什么是物理地址
什么是虚拟地址
物理地址与虚拟地址的关系
Linux内存分配的常用方法及区别
I/O端口和I/O内存访问流程
地址类型
物理地址和页
内存映射和页结构
页表
1、用户虚拟地址(2^32)
1.1用户空间程序所有能看到的常规地址
1.2每个进程都有自己的虚拟空间
2、物理地址:该地址在处理器和系统内存之间使用
3、总线地址
3.1该地址在外围总线和内存之间使用
3.2它实现总线和主内存之间的重新映射
3.3通常他们与处理器使用的物理地主相同。
4、内核逻辑地址
4.1内核逻辑地址组成了内核的常规地址
4.2该地址映射了部分(或全部)内存,并经常被视为物理地址
4.3与物理地址是线性映射(一一映射的)(且是连续的)
4.4例如:kmalloc返回的是逻辑地址
5、内核虚拟地址
5.1内核虚拟地址和逻辑地址的相同之处在于,他们都将内核空间的地址映射到物理地址上。
5.2与物理地址不必是线性映射关系(不一定是连续的)
5.3例如:vmalloc与kmap都是返回内核虚拟地址
6、地址的转化
6.1 __pa(logical-addr)//逻辑地址–>物理地址
6.2 __va(vphysical-addr) //物理地址->逻辑地址
7、虚拟地址
7.1Linux操作系统采用虚拟内存管理技术,使得每个进程都有独立的进程地址空间,该空间的大小为3G,用户看到和接触到的都是虚拟地址,无法看到时间的物理地址。利用这种虚拟的地址不但能起到保护操作系统的作用,而且更重要的是用户程序可使用比实际物理内存更大的地址空间。
7.2Linux将4G的虚拟地址空间划分为两个部分:用户空间和内核空间。
用户空间:0x0—0xBF FF FF FF即0-3G
内核空间:0xC—0xFF FF FF FF即3G-4G
7.3用户进程通常情况下能访问到用户空间的虚拟地址,不能访问内核空间。例外情况是用户进程通过系统调用间接访问内核空间。
8、物理地址和页
8.1物理地址被分成离散的大小相等单元,称为页
8.2Linux系统内部许多对内存的操作都是基于页的
8.3每个页的大小通常为4096个字节,集体的大小在<asm/page.h?中使用PAGE—_SIZE定义。
8.4内存地址,物理是虚拟的还是物理的,他们都为分为页号和一个页内的偏移量。
31-12:页号,11-0:PAGE_SHIFT页内偏移量。
8.5页帧号:忽略地址偏移量,并将除去偏移量的剩余位移到右端,称该结果为页帧号。
9、内存映射和页结构
9.1 page的数据结构:struct page{…};
<linux\mm.h>
Atomic_t count; //对该页的访问计数。当计数值为0时,该页将放回给空闲链表。
Void* virtual;//如果页面被映射,则指向页的内核虚拟地址;如果未被映射为NULL
Unsigned long flags;
描述页状态的一系列标志
PG_locked表示内存中的页已经被锁住
PG_reserved表示禁止内存管理系统访问该页。
9.2内核维护了一个或者多个page结构数组,用用来跟踪系统中的物理内存。
9.3 page结构指针与虚拟地址之间进行转换
Struct page* virt_to_page(void* kaddr);//内核逻辑地址 ->page结构指针
Struct page* pfn_to_page(int pfn);//页帧号->page结构指针
Void* kmap(struct page *page);//page结构指针->内核虚拟地址
Void kunmap(struct page* page);//释放映射
//基于原子操作的kmap
Void* kmap_atomic(struct page *page,enum km_type type);//type参数指定使用哪个槽(专用页表入口)
Void kunmap_atomic(void* addr,enum km_type type);//KM_USER0和KM_USER1(针对在用户空间中直接运行的代码) KM_IRQ0和KM_IRQ1(针对中端处理程序)
10、页表
10.1通常处理器必修使用某种机制,将虚拟地址转换为相应的物理地址,这个机制被称为页表。
10.2页表是一个多层树形结构,结构化的数组中包含了虚拟地址到物理地址的映射和相关的标志位。
10.3幸运的是,对驱动程序开发者来说,在2.6版内核中删除了对页表直接操作的需求。
11、分配内存
11.1用户空间内存动态申请:malloc/free
11.2内核空间内存动态申请:
Kmalloc()
__get_free_page()和相关函数
Vmalloc()极其辅助函数
12、 kmalloc()函数
Kmalloc申请的内存位于物理内核映射区域,而且在物理上也是连续的,与真实物理地址至于一个固定的偏移。
#include<linux/slab.h>
void* kmalloc(size_t size,int flags);
size:分配的内存大小(字节数)
flags: GRP_KERNEL:由运行在内核态的进程调用,分配内存,可能引起睡眠。说明该内存分配是有运行在内核态的进程调用。也就是说,调用它的函数是属于某个进程的,当空闲内存太少时,kmalloc函数会使当前进程进入睡眠,等待空闲页的出现。
GRP_ATOMIC:在中断处理函数、tasklet、内核定时器和持有自旋锁的时候申请内核内存,必修使用GFP_ATOMIC分配标志。
13、后备高速缓存:设备驱动程序常常会反复地分配很多同一个大小的内存块。为了满足这样的应用,内核实现了这种形式的内存池,通常称为后备高速缓存(lookaside cache)
14、_get_free_page和相关函数
14.1如果模块需要分配打开的内存,那么使用面向页的分配技术会更好
14.2分配页面函数或宏
unsigned long get_zeroed_page(unsigned int flags);//返回指向新页面的指针,将页面清零
unsigned long __get_free_page(unsigned int flags);//同上,但不清零页面
unsigned long __get_free_pages(unsigned int flags, unsigned int order);//分配页面数为2^order ,分配若干个连续的页面,返回指向该内存区域的指针,但也不清零这些内存区域
14.3释放页面函数
void free_page(unsigned long addr);
void free_pages(unsigned long addr,unsigned long order); //分配页面数为2^order
15、vmalloc及其辅助函数
15.1 vmalloc分配虚拟地址空间的连续区域,但这段区域在物理上可能是不连续的。
15.2 vamlloc不能用在原子上下文。因为它的内部实现使用了标志位GFP_KERNEL的kmalloc。
#include<linux/vmalloc.h>
void* vmalloc(unsigned long size);
void vfree(void* addr);
15.3 vmalloc分配得到的地址是不能再微处理器之外使用的,当驱动程序需要真正的物理地址时(像外设用以驱动系统总线的DMA地址),就不能使用vmalloc;
15.4使用vmalloc函数的正确场合是在分配一大块连续的、只在软件中存在的、用于缓冲的内存区域的时候。
15.5因为vmalloc不但获取内存,还要建立页表,它的开销比__get_free_pages大,因此,用vmalloc函数分配仅仅一夜的内存空间时不值得。
15.6通过vmalloc获得的内存使用起来效率不高,在大多数情况下不鼓励使用。尽可能直接与单个的页面打交道。
16、vmalloc及其辅助函数
void* ioreamap(unsigned long offset,unsigned long size);
void iounmap(void* addr);
16.1和vmalloc一样,ioremap也建立新的页表,但和vmalloc不同的是,ioremap并不实际分配内存。
16.2使用ioremap()函数将设备所处的物理地址映射到虚拟地址。
16.3为了保持可移植性,不应把ioremap返回的地址当做指向内存的指针而直接访问。相反应使用readb或其他I/O函数(完成设备内存映射的虚拟地址的读写)。
16.4vmalloc和kmalloc的区别
kmalloc使用的(虚拟)地址范围与物理内存是一一对应的;vmalloc使用的地址范围完全是虚拟的,每次分配都要通过对页表的设置来建立(虚拟)内存区域。vmalloc申请的内存不一定是连续的。
Vmalloc分配得到的地址是不能再微处理器之外使用的,因为他们只在处理器的内存单元上才有意义。当驱动程序需要真正的物理地址时(像外设用以驱动系统总线的DMA地址),就不能使用vmalloc;
通常,kmalloc分配小于128KB的内存,vmalloc可以分配更大的内存;
vmalloc不能再原子上下文中使用,因为它的内部实际调用了kmalloc(size,GFP_KERNEL);
17、虚拟地址与物理地址关系
17.1内核虚拟地址转化为物理地址
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)//物理地址=虚拟地址-偏移地址(通常为3GB)
extern inline unsigned long virt_to_phys(volatile void* address){
return __pa(address);
}
17.2物理地址转化为内核虚拟地址
#define __va(x) ((void*)((unsigned long)(x)+PAGE_OFFSET)) //虚拟地址=物理地址+偏移量(通常为3GB)
extern inline void* phys_to_virt(unsigned long address){
return __va(address);
}
18、I/O端口和I/O内存访问接口
18.1 I/O端口的操作(asm-generic/io.h)
unsigned inb(unsigned port);//读字节端口(8位宽)
void outb(unsigned char byte,unsigned port);//写字节端口(8位宽)
unsigned inw(unsigned port);//读字端口(16位宽)
void outw(unsigned char byte,unsigned port);//写字端口(16位宽)
unsigned inl(unsigned port);//读长字端口(32位宽)
void outl(unsigned char byte,unsigned port);//写长字端口(32位宽)
unsigned insb(unsigned port,void* addr, unsigned long count);//读一串字节
void outsb(unsigned port,void* addr, unsigned long count);//写一串字节
unsigned insw(unsigned port,void* addr, unsigned long count);//读一串字
void outsw(unsigned port,void* addr, unsigned long count);//写一串字
unsigned insl(unsigned port,void* addr, unsigned long count);//读一串长字
void outsl(unsigned port,void* addr, unsigned long count);//写一串长字
18.2 I/O内存(asm-generic/io.h)
在内核中访问I/O内存之前,需要先使用ioremap()函数将设备所处的物理地址映射到虚拟地址,ioremap()的原型如下(asm-generic/io.h):
Void* ioremap(unsigned long offset,unsigned long size);
访问函数:
unsigned int ioread8(void* addr); //addr为通过ioremap获取的地址
unsigned int ioread16(void* addr);
unsigned int ioread32(void* addr);
unsigned readb(address);
unsigned readw(address);
unsigned readl(address);
void iowrite8(u8 value,void* addr);
void iowrite16(u16 value,void* addr);
void iowrite32(u32 value,void* addr);
void writeb(unsigned value,address);
void writeb(unsigned value,address);
void writeb(unsigned value,address);
18.3 I/O端口申请与释放
18.4 I/O内存的申请与释放
18.5上述request_region()和request_men_region()都不是必须的,建议使用。其任务是检查申请的资源是否可用,如果申请可用则申请成功,并标志为已经使用,启动驱动想再次申请该资源时就会失败。
19、I/O端口和I/O内存操作函数代码分析
20、I/O端口的访问流程
(1)request_region() //在设备驱动模块加载或open( )函数中进行
(2)inb()、outb()等 //在设备驱动中初始化、read()、write()、ioctl()等函数中进行
(3)release_region() //在设备驱动模块卸载或release()函数中进行
21、I/O内存的访问流程
(1)request_mem_region()//在设备驱动模块加载或open( )函数中进行
(2) ioremap() //在设备驱动模块加载或open( )函数中进行
(3) ioread8()、ioread16()、ioread32()、iowrite8()等//在设备驱动中初始化、read()、write()、ioctl()等函数中进行
(4)iounmap() //在设备驱动模块卸载或release()函数中进行
(5) release_mem_region()//在设备驱动模块卸载或release()函数中进行
22、将设备地址映射到用户空间
22.1用户空间不能直接访问设备
22.2设备驱动程序中可实现mmap()函数,可使得用户空间能直接访问设备的物理地址
mmap()将用户空间的一段内存与设备内存关联
当用户访问用户空间的这段地址范围时,会转化为对设备的访问
22.3 mmap()必须以PAGE_SIZE为单位进行映射
内存只能以页为单位进行映射,并且进行页对齐,以PAGE_SIZE的倍数大小进行映射。
23、将设备地址映射到用户空间
mmap函数的原型
int(*mmap)(struct file* flie,struct vm_area_strct* vma);
24、实例
24.1驱动程序
(1)驱动程序代码\beep_ioremap\drive\beep_ioremap.c
#include <linux/errno.h>#include <linux/kernel.h>#include <linux/module.h>#include <linux/slab.h>#include <linux/input.h>#include <linux/init.h>#include <linux/serio.h>#include <linux/delay.h>#include <linux/clk.h>#include <linux/miscdevice.h>#include <linux/gpio.h>#include <asm/io.h>#include <asm/irq.h>#include <asm/uaccess.h>#include <mach/regs-clock.h>#include <plat/regs-timer.h> #include <mach/regs-gpio.h>//linux-2.6.32.2/arch/arm/mach-s3c2410/include/mach/regs-gpio.h#include <linux/cdev.h>#define DEVICE_NAME"beep"//Port GPBx Register address declaration#define GPBCON (unsigned long)ioremap(0x56000010,4) //IO映射物理地址0x56000010映射为一个虚拟地址并返回 GPB控制寄存器虚拟地址#define GPBDAT (unsigned long)ioremap(0x56000014,4)#define GPBUP (unsigned long)ioremap(0x56000018,4)MODULE_AUTHOR("Hanson He");MODULE_LICENSE("Dual BSD/GPL");#define BEEP_MAGIC 'k'#define BEEP_START_CMD _IO (BEEP_MAGIC, 1)#define BEEP_STOP_CMD _IO (BEEP_MAGIC, 2)static unsigned int port_status =0;/* * Open the device; in fact, there's nothing to do here. */int beep_open (struct inode *inode, struct file *filp){return 0;}ssize_t beep_read(struct file *file, char __user *buff, size_t count, loff_t *offp){return 0;}ssize_t beep_write(struct file *file, const char __user *buff, size_t count, loff_t *offp){return 0;}/*void beep_stop( void ){//add your src HERE!!!//set GPB0 as outputs3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT);s3c2410_gpio_setpin(S3C2410_GPB(0),0);}void beep_start( void ){//add your src HERE!!!//set GPB0 as outputs3c2410_gpio_pullup(S3C2410_GPB(0),1);s3c2410_gpio_cfgpin(S3C2410_GPB(0), S3C2410_GPIO_OUTPUT);s3c2410_gpio_setpin(S3C2410_GPB(0),1);}*/void beep_start( void ){/*config GPBCON, set GPB0 as output port*/ port_status = readl(GPBCON); //读port_status &= ~0x03; /*最低两位清零*/port_status |= 0x01;writel(port_status,GPBCON); //写port_status = readl(GPBDAT);port_status |= 0x01; // set 1 to GPB0writel(port_status,GPBDAT);}void beep_stop( void ){/*config GPBCON, set GPB0 as output port*/ port_status = readl(GPBCON);port_status &= ~0x03; port_status |= 0x01;writel(port_status,GPBCON);port_status = readl(GPBDAT);port_status &= ~0x01;// set 0 to GPB0writel(port_status,GPBDAT);}static int beep_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg){//add your src HERE!!!switch ( cmd ) {case BEEP_START_CMD: {beep_start(); break;}case BEEP_STOP_CMD: {beep_stop(); break;}default: {break;}}return 0;}static int beep_release(struct inode *node, struct file *file){return 0;}/* * Our various sub-devices. *//* Device 0 uses remap_pfn_range */static struct file_operations beep_remap_ops = {.owner = THIS_MODULE,.open = beep_open,.release = beep_release,.read = beep_read,.write = beep_write,.ioctl = beep_ioctl,};/* * There's no need for us to maintain any * special housekeeping info, so we just deal with raw cdevs. */static struct miscdevice misc = {.minor = MISC_DYNAMIC_MINOR, //动态设备号.name = DEVICE_NAME,.fops = &beep_remap_ops,};/* * Module housekeeping. */static int beep_init(void){int ret;ret = misc_register(&misc);printk("The device name is: %s\n", DEVICE_NAME);return 0;}static void beep_cleanup(void){misc_deregister(&misc);printk("beep device uninstalled\n");}module_init(beep_init);module_exit(beep_cleanup);
(2)Makefie文件
ifeq ($(KERNELRELEASE),)#KERNELDIR ?= /your/target/source/directory/KERNELDIR ?=/home/student/linux-2.6.32.2PWD := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesmodules_install:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_installclean:rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions.PHONY: modules modules_install cleanelse obj-m := beep_ioremap.oendif
24.2用户程序
(1)用户代码\beep_ioremap\user\main.c
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <fcntl.h>#include <linux/ioctl.h>#define BEEP_MAGIC 'k'#define BEEP_START_CMD _IO (BEEP_MAGIC, 1)#define BEEP_STOP_CMD _IO (BEEP_MAGIC, 2)int main(){int i = 0;int dev_fd;dev_fd = open("/dev/beep",O_RDWR | O_NONBLOCK);if ( dev_fd == -1 ) {printf("Cann't open file /dev/beep\n");exit(1);}printf("Start beep\n");ioctl (dev_fd, BEEP_START_CMD,0);getchar();ioctl (dev_fd, BEEP_STOP_CMD,0);printf("Stop beep and Close device\n");close(dev_fd);return 0;}
(2)Makefie文件
KERNELDIR ?=/home/student/linux-2.6.32.2all: beep_test beep_test : main.c#arm-linux-gcc -I$(KERNELDIR) -s -Wl,-warn-common --static -o $@ $^arm-linux-gcc -I$(KERNELDIR) -o $@ $^clean :rm beep_test
声明:本文非原创,整理自申嵌。
- 5-5 Linux内存、IO与实例
- linux 实例分析内核IO内存映射
- Linux异步IO+实例(POSIX IO与 libaio)
- io端口与io内存
- IO端口与IO内存
- IO端口与IO内存
- IO端口与IO内存
- IO端口与IO内存
- io端口与io内存
- IO端口与IO内存
- IO端口与IO内存
- IO端口与IO内存
- IO端口与IO内存
- 内存与IO访问
- [TZ]内存与IO访问(2)--Linux地址映射
- linux VM与容器的cpu、内存、文件IO测试
- Linux的IO端口和IO内存
- Linux的IO端口和IO内存
- Unity 应用程序的一些方法,
- ACM 推荐blog汇总及OJ
- Ubuntu12.10 下搭建基于KVM-QEMU的虚拟机环境(十六)
- Linux commands you should know
- iOS正则表达式最佳实践
- 5-5 Linux内存、IO与实例
- 一封写给副总理的信:中小企业危情时刻
- 10420 - List of Conquests
- 哪个对象才是锁?
- iOS 字符和数字一起算起总共位数
- 每日一题(33) - 树的子结构
- 关于alert和Response.redirect()
- Android AlarmManager实现不间断轮询服务
- php获取远程文件内容与大小的函数代码