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中,将其映射到0xffc00000到0xffeffff区间的内核虚拟空间,通过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