嵌入式Linux设备驱动程序开发指南13(Linux驱动中分配内存)——读书笔记

十三、Linux驱动中分配内存

13.1 简介

Linux是一个虚拟内存系统,内核进程和用户进程均使用虚拟地址,地址转换是硬件的内存管理单元(MMU)。几个关键的概念:
虚拟地址:又名VA,用户进程使用的地址;
物理地址:又名PA,硬件系统使用的地址;
转换表:ARM体系中,虚拟地址对应到物理地址的表格;
查询页表:MMU在必要时,自动读取页表的过程;
遍历页表:完整的转换表查询;
快表:又名TLB,通过缓存转换表来降低访问的平均成本;

虚拟地址是编译器和链接器在内存中放置代码时使用的地址,是用户使用的地址。
当进程访问NULL指针或者试图访问不属于它的内存时,就会发生非法的缺页异常,非法的缺页异常会导致处理程序终止进程,在终止进程前打印“”oops”信息。

当处理器访问内存时候,MMU执行的动作内容如下:
1、在相关指令或数据的micro-TLB中查找CPU请求的虚拟地址、当前的地址空间标识符以及安全状态;
2、如果未命中,则继续执行判断1;
3、若main-TLB也未命中,则需要查询硬件;
地址转换表如下:
请添加图片描述

13.3 Linux地址类型

13.3.1 Linux地址类型

Linux使用的地址包括:
	用户虚拟地址
	物理地址:物理地址长度为32bit或64bit
	总线地址:外设总线和内存之间使用的地址
	内核逻辑地址:位于CONFIG_PAGE_OFFSET上的虚拟地址,kmalloc()函数
	内核虚拟地址:内核态地址到物理地址的映射,vmalloc()函数,ioremap()函数也将被动态放置在该区域

13.3.2 用户进程虚拟地址到物理地址映射

在Linux系统中,内核态一直存在,内核代码和数据始终是可以寻址的,随时可以再中断或系统调用中使用。相反,用户态进程地址空间在发生进程切换时地址空间会发生改变。

用户态进程的虚拟内存分为4个逻辑区域:
	文本段:程序代码
	数据段:包括data、bss、heap
	内存映射段:通过map()系统调用请求的映射
	栈段

13.3.3 内核的虚拟地址到物理地址的映射

在系统启动期间,内核消息缓冲区会打印内核虚拟地址空间布局,

vitrtual kernel memory layout:
		vector	:
		fixmap	:
		vmalloc	:
		pkmap	:
		modules	:
		.text	:
		.init	:
		.data	:
		.bss	:
内核物理地址分为4类:
ZONE_DMA		:映射到内核空间的地址空间,在32bit的ARM中,将其映射到0xffc000000xffeffff区间的内核虚拟空间,通过dma_alloc_xx()函数获取
ZONE_NORMAL		:映射到内核逻辑地址空间
ZONE_HIGHMEM	:用于分配系统缓冲区,用户态分配等,通过vmalloc
内存映射IO		:通过ioremap()函数

13.3.4 内核内存分配器

Linux提供了一些内存分配方法。有几个概念:

页面分配器:负责管理整个系统的页面分配;
SLAB分配器:允许创建高速缓存cache,内核使用双向链表来链接所创建的高速缓存;
SLAB分配器接口:
#include <linux/slab.h>

kmem_cache_create()
kmem_cache_destory()
kmem_cache_allow()
kmem_cache_free()
kmalloc()		//驱动程序使用的分配器
kzmalloc()		//驱动程序使用的分配器
devm_kmalloc()  //推荐使用的统一设备模型,可以将内存分配到关联设备上

13.4 链表内存分配案例

13.4.1 案例模块介绍

创建一个由多个节点组成的循环单链表,在内核内存。

每个节点由两个变量组成:

typedef struct dnode
{
    char *buffer;		//指向内存缓冲区
    struct dnode *next;	//指向下一个节点
} data_node;

typedef struct  lnode
{
    data_node *head;
    data_node *cur_write_node;
    data_node *cur_read_node;
    int cur_read_offset;
    int cur_write_offset;
}liste;  //管理链表

创建链表,通过createlist()函数,调用devm_kmalloc()函数。

13.4.2 设备树

/ {
	model = "Atmel SAMA5D2 Xplained";
	compatible = "atmel,sama5d2-xplained", "atmel,sama5d2", "atmel,sama5";

	chosen {
		stdout-path = "serial0:115200n8";
	};

	clocks {
		slow_xtal {
			clock-frequency = <32768>;
		};

		main_xtal {
			clock-frequency = <12000000>;
		};
	};

	linked_memory {
		compatible = "arrow, memory";
	};

13.4.3 代码

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>

static int BlockNumber = 10;
static int BlockSize = 5;
static int size_to_read = 0;
static int node_count= 1;
static int cnt = 0;

typedef struct dnode
{
    char *buffer;
    struct dnode *next;
} data_node;

typedef struct  lnode
{
    data_node *head;
    data_node *cur_write_node;
    data_node *cur_read_node;
    int cur_read_offset;
    int cur_write_offset;
}liste;

static liste newListe;

static void createlist (struct platform_device *pdev)
{
    data_node *newNode, *previousNode, *headNode;
    int i;

    /* new node creation */
    newNode = devm_kmalloc(&pdev->dev, sizeof(data_node), GFP_KERNEL);
    if (newNode)
    	newNode->buffer = devm_kmalloc(&pdev->dev, BlockSize*sizeof(char), GFP_KERNEL);
    if (!newNode || !newNode->buffer)
    	return -ENOMEM;
    newNode->next = NULL;

    newListe.head = newNode;
    headNode = newNode;
    previousNode = newNode;


    for (i = 1; i < BlockNumber; i++)
    {
        newNode = devm_kmalloc(&pdev->dev, sizeof(data_node), GFP_KERNEL);
        if (newNode)
        	newNode->buffer = devm_kmalloc(&pdev->dev, BlockSize*sizeof(char), GFP_KERNEL);
        if (!newNode || !newNode->buffer)
        	return -ENOMEM;
        newNode->next = NULL;
        previousNode->next = newNode; 
        previousNode = newNode; 
    }
    
    newNode->next = headNode;

    newListe.cur_read_node = headNode;
    newListe.cur_write_node = headNode;
    newListe.cur_read_offset = 0;
    newListe.cur_write_offset = 0;
}

static ssize_t my_dev_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
	int size_to_copy;
	pr_info("my_dev_write() is called.\n");
	pr_info("node_number_%d\n", node_count);

	if ((*(offset) == 0) || (node_count == 1))
	{
	    size_to_read += size;
	}

	if (size < BlockSize - newListe.cur_write_offset)
		size_to_copy = size;
	else
		size_to_copy = BlockSize - newListe.cur_write_offset;

	if(copy_from_user(newListe.cur_write_node->buffer + newListe.cur_write_offset, buf, size_to_copy))
	{
		return -EFAULT;
	}
	*(offset) += size_to_copy; 
	newListe.cur_write_offset +=  size_to_copy;
	if (newListe.cur_write_offset == BlockSize)
	{
	    newListe.cur_write_node = newListe.cur_write_node->next;
	    newListe.cur_write_offset = 0;  
	    node_count = node_count+1;
	    if (node_count > BlockNumber)
	    {
	    	newListe.cur_read_node = newListe.cur_write_node;
	    	newListe.cur_read_offset = 0;
		node_count = 1;
		cnt = 0;
		size_to_read = 0;
	    }
	 }
	return size_to_copy;
}


static ssize_t my_dev_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
{

	int size_to_copy;
	int read_value;

	read_value = (size_to_read - (BlockSize * cnt));

	if ((*offset) < size_to_read)
	{
		if (read_value < BlockSize - newListe.cur_read_offset)
			size_to_copy = read_value;
		else
			size_to_copy = BlockSize - newListe.cur_read_offset;
		if(copy_to_user(buf, newListe.cur_read_node->buffer + newListe.cur_read_offset, size_to_copy))
		{
			return -EFAULT;
		}
		newListe.cur_read_offset += size_to_copy;
		(*offset)+=size_to_copy;

		if (newListe.cur_read_offset == BlockSize)
		{
			cnt = cnt+1;
			newListe.cur_read_node = newListe.cur_read_node->next;
			newListe.cur_read_offset = 0;
		}
		return size_to_copy;
	}
	else
	{
		msleep(250);
		newListe.cur_read_node = newListe.head;
		newListe.cur_write_node = newListe.head;
		newListe.cur_read_offset = 0;
		newListe.cur_write_offset = 0;
		node_count = 1;
		cnt = 0;
		size_to_read = 0;
		return 0;
	}
}


static int my_dev_open(struct inode *inode, struct file *file)
{
	pr_info("my_dev_open() is called.\n");
	return 0;
}
static int my_dev_close(struct inode *inode, struct file *file)
{
	pr_info("my_dev_close() is called.\n");
	return 0;
}


static const struct file_operations my_dev_fops = {
	.owner = THIS_MODULE,
	.open = my_dev_open,
	.write = my_dev_write,
	.read = my_dev_read,
	.release = my_dev_close,
};

static struct miscdevice helloworld_miscdevice = {
		.minor = MISC_DYNAMIC_MINOR,
		.name = "mydev",
		.fops = &my_dev_fops,
};

static int __init my_probe(struct platform_device *pdev)
{
	int ret_val;
	pr_info("platform_probe enter\n");
	createlist(pdev);
	ret_val = misc_register(&helloworld_miscdevice);
	if (ret_val != 0)
	{
		pr_err("could not register the misc device mydev");
		return ret_val;
	}
	pr_info("mydev: got minor %i\n",helloworld_miscdevice.minor);

	return 0;
}
static int __exit my_remove(struct platform_device *pdev)
{
	misc_deregister(&helloworld_miscdevice);
	pr_info("platform_remove exit\n");
	return 0;
}

static const struct of_device_id my_of_ids[] = {
	{ .compatible = "arrow,memory"},
	{},
};

MODULE_DEVICE_TABLE(of, my_of_ids);

static struct platform_driver my_platform_driver = {
	.probe = my_probe,
	.remove = my_remove,
	.driver = {
		.name = "memory",
		.of_match_table = my_of_ids,
		.owner = THIS_MODULE,
	}
};

static int demo_init(void)
{
	int ret_val;
	pr_info("demo_init enter\n");

	ret_val = platform_driver_register(&my_platform_driver);
	if (ret_val !=0)
	{
		pr_err("platform value returned %d\n", ret_val);
		return ret_val;

	}
	pr_info("demo_init exit\n");
	return 0;
}

static void demo_exit(void)
{
	pr_info("demo_exit enter\n");

	platform_driver_unregister(&my_platform_driver);

	pr_info("demo_exit exit\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("  ");
MODULE_DESCRIPTION("This is a platform driver that writes in and read from a linked list of several buffers ");

13.4.4 Makefile

obj-m += linkedlist_sam_platform.o


KERNEL_DIR ?= $(HOME)/my-linux-sam

all:
	make -C $(KERNEL_DIR) \
		ARCH=arm CROSS_COMPILE=arm-poky-linux-gnueabi- \
		SUBDIRS=$(PWD) modules

clean:
	make -C $(KERNEL_DIR) \
		ARCH=arm CROSS_COMPILE=arm-poky-linux-gnueabi- \
		SUBDIRS=$(PWD) clean

deploy:
	scp *.ko root@10.0.0.10:

13.4.5 测试调试

insmod linkedlist_sam_platform.ko
echo abcdefg > /dev/mydev  //将值写到节点缓冲区
cat /dev/mydev //从链表第一个节点读
rmmod linkedlist_sam_platform.ko

感谢阅读,祝君成功!
-by aiziyou

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jack.Jia

感谢打赏!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值