linux驱动学习笔记(五)内存空间与IO映射

前言

一般高性能处理器都会提供一个叫MMU(内存管理单元),MMU帮助操作系统进行内存管理,虚拟地址和物理地址的映射。
对于包含MMU的处理器来说,linux提供了一种复杂的存储管理系统,可以让进程访问可以访问的空间达到4GB,这4GB分别表示用户空间(0 ~ 3GB) ,内核空间(3 ~ 4GB)。用户空间通常只能访问用户空间的虚拟地址,不能访问内核空间的虚拟地址,用户空间只能通过系统调用才能访问内核空间

内存申请

用户空间的内存申请

用户空间中动态申请内存的函数为malloc,申请的内存的释放函数为free,对于linux而言,malloc函数一般用brk函数和mmap两个系统调用从内核中申请内存。由于用户空间C库的malloc算法具备一个二次管理能力, 所以malloc和free并不是每次都对内核进行系统调用。

void *malloc(size_t size);
void free(void *ptr);

内核空间的内存申请

内核空间主要申请内存的函数是kmalloc,vmalloc。kmalloc在申请的内存物理上是连续的,但是vmalloc申请的内存并不一定连续

kmalloc

/*
@flags:
		GFP_KERNEL 在内核空间的进程中申请内存
		GFP_ATOMIC 在内核中断中申请内存
		GFP_USER   用来为用户空间页分配内存, 可能阻塞 
		GFP_HIGHUSER 类似GFP_USER, 但是它从高端内存分配
		GFP_DMA		从DMA区域分配内存
		GFP_NOIO	不允许任何I/O初始化
		GFP_NOFS	不允许进行任何文件系统调用
		__GFP_HIGHMEM	指示分配的内存可以位于高端内存
		__GFP_COLD	请求一个较长时间不访问的页
		__GFP_NOWARN	当一个分配无法满足时, 阻止内核发出警告 
		__GFP_HIGH	高优先级请求, 允许获得被内核保留给紧急状况使用的最后的内存页
		__GFP_REPEAT	分配失败, 则尽力重复尝试
		__GFP_NOFAIL	标志只许申请成功, 不推荐
		__GFP_NORETRY	若申请不到, 则立即放弃
*/
void *kmalloc(size_t size, int flags);
void kfree(void * addr);

  1. 使用GFP_KERNEL标志申请内存时, 若暂时不能满足, 则进程会睡眠等待页, 即会引起阻塞, 因此不能在中断上下文或持有自旋锁的时候使用GFP_KERNE申请内存。
  2. 使用GFP_ATOMIC标志申请内存时, 若不存在空闲页, 则不等待, 直接返回。

vmalloc

vmalloc一般只为较大的顺序缓冲区分配内存,不适于分配少量的内存

void *vmalloc(unsigned long size);
void vfree(void * addr);

vmalloc不能用在原子上下文中, 因为它的内部实现使用了标志为GFP_KERNEL的kmalloc 。
vmalloc的虚拟地址和物理地址不是一个简单的线性映射。

slab

操作系统的运作过程中, 经常会涉及大量对象的重复生成、 使用和释放内存问题。slab算法可以在对象前后两次被使用时分配在同一块内存或同一类内存空间且保留了基本的数据结构,可以大大提高效率。kmalloc就是使用slab机制实现的。

/*
创建slab
*/
struct kmem_cache *kmem_cache_create(const char *name,
										size_t size,
										size_t align, 
										unsigned long flags,
										void (*ctor)(void*, struct kmem_cache *, unsigned long),
										void (*dtor)(void*, struct kmem_cache *, unsigned long));
//分配slab							
void *kmem_cache_alloc(struct kmem_cache *cachep, gfp_t flags);
//释放slab
void kmem_cache_free(struct kmem_cache *cachep, void *objp);
//回收
int kmem_cache_destroy(struct kmem_cache *cachep);

模板

/* 创建slab缓存 */
static kmem_cache_t *my_cachep;
my_cachep= kmem_cache_create("my", sizeof(struct my),
								0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL, NULL);

/* 分配slab缓存 */
struct my *slab;
slab= kmem_cache_alloc(my_cachep, GFP_KERNEL);


/* 释放slab缓存 */
kmem_cache_free(my_cachep, slab);
kmem_cache_destroy(my_cachep);

IO映射

在linux内核中 访问硬件寄存器之前需要先把寄存器所处的物理地址映射到虚拟地址上

//返回一个虚拟地址,该地址可用来存取特定的物理地址范围
void *ioremap(unsigned long offset, unsigned long size);
/*
	操作这个虚拟地址的api函数
*/
#define readb(c) ({ u8 __v = readb_relaxed(c); __iormb(); __v; })
#define readw(c) ({ u16__v = readw_relaxed(c); __iormb(); __v; })
#define readl(c) ({ u32 __v = readl_relaxed(c); __iormb(); __v; })
#define writeb(v,c) ({ __iowmb(); writeb_relaxed(v,c); })
#define writew(v,c) ({ __iowmb(); writew_relaxed(v,c); })
#define writel(v,c) ({ __iowmb(); writel_relaxed(v,c); })

实例
此例子根据正点原子IMX6ULL开发板平台
可以在/sys/class/demo/std节点进行LED灯的开和关

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/cdev.h>
#include <linux/device.h>

#define DEMO_CNT			1		  	/* 设备号个数 */
#define DEMO_NAME			"demo"	/* 名字 */

#define CCM_CCGR1_BASE				(0X020C406C)	
#define SW_MUX_GPIO1_IO03_BASE		(0X020E0068)
#define SW_PAD_GPIO1_IO03_BASE		(0X020E02F4)
#define GPIO1_DR_BASE				(0X0209C000)
#define GPIO1_GDIR_BASE				(0X0209C004)



static void __iomem *CCM_CCGR1;
static void __iomem *SW_MUX_GPIO1_IO03;
static void __iomem *SW_PAD_GPIO1_IO03;
static void __iomem *GPIO1_DR;
static void __iomem *GPIO1_GDIR;

struct demo_dev{
	dev_t devid;			/* 设备号 	 */
	struct cdev cdev;		/* cdev 	*/
	struct class *class;		/* 类 		*/
	struct class val_class;
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	int led_flag;
};
struct demo_dev demo;	/* demo设备 */



static struct file_operations demo_fops = {
	.owner = THIS_MODULE,
};
void led_switch(u8 sta)
{
	u32 val = 0;
	if(sta == 1) {
		val = readl(GPIO1_DR);
		val &= ~(1 << 3);	
		writel(val, GPIO1_DR);
	}else if(sta == 0) {
		val = readl(GPIO1_DR);
		val|= (1 << 3);	
		writel(val, GPIO1_DR);
	}	
}


static int led_init(void)
{
	u32 val = 0;
	/* 初始化LED */
	/* 1、寄存器地址映射 */
  	CCM_CCGR1 = ioremap(CCM_CCGR1_BASE, 4);
	SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE, 4);
  	SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE, 4);
	GPIO1_DR = ioremap(GPIO1_DR_BASE, 4);
	GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE, 4);

	/* 2、使能GPIO1时钟 */
	val = readl(CCM_CCGR1);
	val &= ~(3 << 26);	/* 清楚以前的设置 */
	val |= (3 << 26);	/* 设置新值 */
	writel(val, CCM_CCGR1);

	/* 3、设置GPIO1_IO03的复用功能,将其复用为
	 *    GPIO1_IO03,最后设置IO属性。
	 */
	writel(5, SW_MUX_GPIO1_IO03);
	
	/*寄存器SW_PAD_GPIO1_IO03设置IO属性
	 *bit 16:0 HYS关闭
	 *bit [15:14]: 00 默认下拉
     *bit [13]: 0 kepper功能
     *bit [12]: 1 pull/keeper使能
     *bit [11]: 0 关闭开路输出
     *bit [7:6]: 10 速度100Mhz
     *bit [5:3]: 110 R0/6驱动能力
     *bit [0]: 0 低转换率
	 */
	writel(0x10B0, SW_PAD_GPIO1_IO03);

	/* 4、设置GPIO1_IO03为输出功能 */
	val = readl(GPIO1_GDIR);
	val &= ~(1 << 3);	/* 清除以前的设置 */
	val |= (1 << 3);	/* 设置为输出 */
	writel(val, GPIO1_GDIR);

	/* 5、默认关闭LED */
	val = readl(GPIO1_DR);
	val |= (1 << 3);	
	writel(val, GPIO1_DR);

	return 0;
}
static void led_exit(void)
{
	iounmap(CCM_CCGR1);
	iounmap(SW_MUX_GPIO1_IO03);
	iounmap(SW_PAD_GPIO1_IO03);
	iounmap(GPIO1_DR);
	iounmap(GPIO1_GDIR);
}
static ssize_t std_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	return sprintf(buf, "%d\n",demo.led_flag);
}
static ssize_t std_store(struct device *dev,  
	struct device_attribute *attr, const char *buf, size_t len)
{
	sscanf( buf, "%d", &demo.led_flag);
	led_switch(demo.led_flag);
	return len;
}
static DEVICE_ATTR_RW(std);


static struct attribute *led_class_attrs[] = {
	&dev_attr_std.attr,
	NULL,
};

static const struct attribute_group led_group = {
	.attrs = led_class_attrs,
};

static const struct attribute_group *led_groups[] = {
	&led_group,
	NULL,
};
static int __init demo_init(void)
{
	led_init();
	/* 1、创建设备号 */
	if (demo.major) {		/*  定义了设备号 */
		demo.devid = MKDEV(demo.major, 0);
		register_chrdev_region(demo.devid, DEMO_CNT, DEMO_NAME);
	} else {						/* 没有定义设备号 */
		alloc_chrdev_region(&demo.devid, 0, DEMO_CNT, DEMO_NAME);	/* 申请设备号 */
		demo.major = MAJOR(demo.devid);	/* 获取分配号的主设备号 */
		demo.minor = MINOR(demo.devid);	/* 获取分配号的次设备号 */
	}
	printk("major=%d,minor=%d\r\n",demo.major, demo.minor);	
	
	/* 2、初始化cdev */
	demo.cdev.owner = THIS_MODULE;
	cdev_init(&demo.cdev, &demo_fops);
	
	/* 3、添加一个cdev */
	cdev_add(&demo.cdev, demo.devid, DEMO_CNT);

	/* 4、创建类 */
	demo.val_class.owner = THIS_MODULE;
	demo.val_class.name = DEMO_NAME;
	demo.val_class.dev_groups = led_groups;
	class_register(&demo.val_class);
	/* 5、创建设备 */
	demo.device = device_create(&demo.val_class, NULL, demo.devid, NULL, DEMO_NAME);
	if (IS_ERR(demo.device)) {
		return PTR_ERR(demo.device);
	}
	return 0;
}
static void __exit demo_exit(void)
{
	led_exit();
	/* 注销字符设备驱动 */
	cdev_del(&demo.cdev);/*  删除cdev */
	unregister_chrdev_region(demo.devid, DEMO_CNT); /* 注销设备号 */

	device_destroy(&demo.val_class, demo.devid);
	class_unregister(&demo.val_class);
	//class_destroy(demo.class);
}


module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bin");
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值