1.内存映射
1. 内存映射概述
内存映射(Memory Mapping)是一种将内核空间中的内存区域直接映射到用户空间的技术。通过这种方式,用户空间的应用程序可以直接访问内核内存,而无需通过 copy_to_user 和 copy_from_user 进行数据拷贝。这种机制可以显著提高数据传输的效率,尤其是在需要频繁访问大块内存的情况下。
内存映射的核心思想是利用虚拟内存机制,将内核内存的物理地址映射到用户空间的虚拟地址,使得用户空间可以直接访问内核内存。
2. 内核驱动中的 mmap 接口
在内核驱动中,为了支持内存映射,驱动程序需要实现 mmap 文件操作接口。
mmap 函数在内核中的定义如下:
int (*mmap) (struct file *, struct vm_area_struct *);
struct file *:指向文件结构体的指针,表示当前打开的文件。
struct vm_area_struct *:虚拟内存区域结构体,表示用户空间请求映射的内存区域。
mmap 函数的主要任务是将内核内存映射到用户空间,并设置相应的权限和属性。
3. 用户层调用的系统接口
用户层通过 mmap 系统调用来请求将内核内存映射到用户空间。
mmap 的函数原型如下:
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);int munmap(void *addr, size_t length);
addr:用户指定的映射起始地址,通常设为 NULL,表示由系统自动选择地址。
length:要映射的内存区域大小(以字节为单位)。
prot:映射区域的保护标志,常用的有:
PROT_READ:可读。
PROT_WRITE:可写。
PROT_EXEC:可执行。
PROT_NONE:不可访问。
flags:映射的类型和行为标志,常用的有:
MAP_SHARED:共享映射,多个进程可以共享同一块内存。
MAP_PRIVATE:私有映射,对映射区域的修改不会反映到原始内存。
MAP_ANONYMOUS:匿名映射,不与文件关联。
fd:文件描述符,通常为设备文件的文件描述符。
offset:文件映射的偏移量。
mmap 返回值:成功时返回映射区域的起始地址,失败时返回 MAP_FAILED(通常是 (void *)-1)。

munmap 用于取消映射,参数为映射的起始地址和长度。
4. 内核中分配内存
在内核中,内存映射通常需要先分配一块内存,然后将这块内存映射到用户空间。
内核提供了多种内存分配函数,常用的有:
4.1 kmalloc
kmalloc 用于分配物理上连续的内存块。它的函数原型如下:
static __always_inline void *kmalloc(size_t size, gfp_t flags);
size:要分配的内存大小。
flags:分配标志,常用的有:
GFP_KERNEL:在内核空间分配内存,可能会阻塞。
GFP_ATOMIC:在中断上下文中分配内存,不会阻塞。
返回值:成功时返回指向分配内存的指针,失败时返回 NULL。

kmalloc 分配的内存是物理上连续的,适合用于需要高性能的场景。
4.2 vmalloc
vmalloc 用于分配虚拟上连续但物理上不连续的内存块。它的函数原型如下:
void *vmalloc(unsigned long size);
size:要分配的内存大小。
返回值:成功时返回指向分配内存的指针,失败时返回 NULL。
vmalloc 分配的内存虚拟地址是连续的,但物理地址可能不连续。它适合用于分配大块内存,但性能不如 kmalloc。
4.3 kfree 和 vfree
kfree:用于释放通过 kmalloc 分配的内存。
vfree:用于释放通过 vmalloc 分配的内存。
5. 映射内存函数 remap_pfn_range
remap_pfn_range 是内核中用于将物理内存映射到用户空间的函数。它的函数原型如下:
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot);
vma:虚拟内存区域结构体,表示用户空间请求映射的内存区域。
addr:用户空间中的起始地址。
pfn:物理页帧号(Physical Frame Number),表示要映射的物理内存的起始页帧号。
size:要映射的内存大小。
prot:映射区域的保护标志,与 mmap 中的 prot 参数类似。
返回值:成功时返回 0,失败时返回负数错误码。

remap_pfn_range 的作用是将指定的物理内存区域映射到用户空间的虚拟地址空间中。
6. 示例代码
以下是一个简单的内存映射示例,展示了如何在内核驱动中实现 mmap 接口,并将内核内存映射到用户空间。
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
static struct file_operations fops;
static char *kernel_buffer;
static size_t buffer_size = 4096;
static int my_mmap(struct file *filp, struct vm_area_struct *vma){
unsigned long size = vma->vm_end - vma->vm_start;
if (size > buffer_size) {
return -EINVAL;
}
if (remap_pfn_range(vma, vma->vm_start, virt_to_phys(kernel_buffer) >> PAGE_SHIFT, size, vma->vm_page_prot)) {
return -EAGAIN;
}
return 0;}
static int my_open(struct inode *inode, struct file *filp){
kernel_buffer = kmalloc(buffer_size, GFP_KERNEL);
if (!kernel_buffer) {
return -ENOMEM;
}
return 0;}
static int my_release(struct inode *inode, struct file *filp){
kfree(kernel_buffer);
return 0;}
static struct file_operations fops = {
.mmap = my_mmap,
.open = my_open,
.release = my_release,};
static int __init my_init(void){
register_chrdev(240, "my_device", &fops);
return 0;}
static void __exit my_exit(void){
unregister_chrdev(240, "my_device");}
module_init(my_init);module_exit(my_exit);MODULE_LICENSE("GPL");
7. 总结
内存映射:通过 mmap 系统调用将内核内存映射到用户空间,避免数据拷贝,提高效率。
内核接口:驱动中需要实现 mmap 函数,使用 remap_pfn_range 将物理内存映射到用户空间。
用户接口:用户层通过 mmap 和 munmap 系统调用进行内存映射和取消映射。
内存分配:内核中使用 kmalloc 或 vmalloc 分配内存,映射时使用 remap_pfn_range。
通过内存映射,用户空间可以直接访问内核内存,避免了数据拷贝的开销,适用于需要高效数据传输的场景。
1. 平台设备框架概述
平台设备框架(Platform Device Framework) 是 Linux 内核中用于管理基于平台总线的设备和驱动的一种机制。
平台设备通常是那些不依赖于传统总线(如 PCI、USB)的设备,例如 SOC 内部的片上设备(如 GPIO、I2C、SPI 等)或外部外设。
平台设备框架提供了一种抽象层,使得设备驱动程序可以与设备独立开发,并通过总线(平台总线)进行匹配和交互。
平台设备框架的核心组件包括:
平台设备(Platform Device):表示系统中实际的硬件设备。
平台驱动(Platform Driver):驱动程序,负责与平台设备交互。
平台总线(Platform Bus):内核中的一种虚拟总线,用于连接平台设备和平台驱动。
通过平台设备框架,内核可以自动匹配设备和驱动,简化了设备和驱动的注册和匹配过程。
2. 平台设备的注册与销毁
2.1 注册平台设备
平台设备的注册通过 platform_device_register 函数完成。平台设备在内核中以 struct platform_device 结构体表示。
int platform_device_register(struct platform_device *pdev);
pdev:指向 struct platform_device 结构体的指针,表示要注册的平台设备。
返回值:
0:表示注册成功。
负数:表示注册失败,返回值为错误码。
struct platform_device 结构体
struct platform_device 结构体定义如下:
struct platform_device {
const char *name; // 设备名称,用于匹配驱动
int id; // 设备 ID,通常为 -1 表示唯一的设备
struct device dev; // 设备结构体,包含设备的基本信息
u32 num_resources; // 资源数量
struct resource *resource; // 资源数组,描述设备的硬件资源(如内存、IRQ)};
name:设备的名称,用于与对应的驱动程序匹配。
id:设备的唯一标识符,通常为 -1 表示唯一的设备。
dev:设备结构体,包含了设备的基本信息,如设备的父设备、设备的总线等。
resource:设备的硬件资源描述,通常包括内存映射区域、IRQ 等。
示例代码:注册平台设备
#include <linux/platform_device.h>
static struct resource my_device_resources[] = {
[0] = {
.start = 0x10000000, // 假设的内存起始地址
.end = 0x10000fff, // 假设的内存结束地址
.flags = IORESOURCE_MEM, // 内存资源
},
[1] = {
.start = 10, // 假设的 IRQ 号
.end = 10,
.flags = IORESOURCE_IRQ, // IRQ 资源
},};
static struct platform_device my_device = {
.name = "my_device", // 设备名称
.id = -1, // 唯一设备
.num_resources = ARRAY_SIZE(my_device_resources),
.resource = my_device_resources,};
static int __init my_device_init(void){
return platform_device_register(&my_device);}
static void __exit my_device_exit(void){
platform_device_unregister(&my_device);}
module_init(my_device_init);module_exit(my_device_exit);MODULE_LICENSE("GPL");
2.2 销毁平台设备
平台设备的销毁通过 platform_device_unregister 函数完成。该函数用于注销已经注册的平台设备。
void platform_device_unregister(struct platform_device *pdev);
pdev:指向要注销的平台设备的指针。
3. 平台驱动的注册与卸载
3.1 注册平台驱动
平台驱动的注册通过 platform_driver_register 函数完成。平台驱动在内核中以 struct platform_driver 结构体表示。
int platform_driver_register(struct platform_driver *drv);
drv:指向 struct platform_driver 结构体的指针,表示要注册的平台驱动。
返回值:
0:表示注册成功。
非零值:表示注册失败,返回值为错误码。
struct platform_driver 结构体
struct platform_driver 结构体定义如下:
struct platform_driver {
int (*probe)(struct platform_device *); // 设备匹配成功时调用
int (*remove)(struct platform_device *); // 设备移除时调用
void (*shutdown)(struct platform_device *); // 设备关闭时调用
int (*suspend)(struct platform_device *, pm_message_t state); // 设备挂起时调用
int (*resume)(struct platform_device *); // 设备恢复时调用
struct device_driver driver; // 驱动结构体
const struct platform_device_id *id_table; // 设备 ID 表,用于匹配设备};
probe:设备匹配成功时调用的函数,用于初始化设备。
remove:设备移除时调用的函数,用于释放资源。
driver:驱动结构体,包含了驱动程序的基本信息,如驱动的名称、拥有者等。
id_table:设备 ID 表,用于匹配设备。
示例代码:注册平台驱动
#include <linux/platform_device.h>
static int my_driver_probe(struct platform_device *pdev){
printk(KERN_INFO "My device probed!\n");
return 0;}
static int my_driver_remove(struct platform_device *pdev){
printk(KERN_INFO "My device removed!\n");
return 0;}
static struct platform_driver my_driver = {
.probe = my_driver_probe,
.remove = my_driver_remove,
.driver = {
.name = "my_device", // 驱动名称,用于匹配设备
.owner = THIS_MODULE,
},};
static int __init my_driver_init(void){
return platform_driver_register(&my_driver);}
static void __exit my_driver_exit(void){
platform_driver_unregister(&my_driver);}
module_init(my_driver_init);module_exit(my_driver_exit);MODULE_LICENSE("GPL");
3.2 卸载平台驱动
平台驱动的卸载通过 platform_driver_unregister 函数完成。该函数用于注销已经注册的平台驱动。
void platform_driver_unregister(struct platform_driver *drv);
drv:指向要注销的平台驱动的指针。
4. 平台总线的设备与驱动匹配机制
平台总线在内核中是一个虚拟总线,用于连接平台设备和平台驱动。
平台设备和驱动的匹配机制如下:
设备名称匹配:设备和驱动通过 name 字段进行匹配。如果设备的 name 与驱动的 name 相同,则匹配成功。
设备 ID 表匹配:如果驱动提供了 id_table,内核会根据设备的 id 进行匹配。
父设备匹配:如果设备和驱动有共同的父设备,也可能进行匹配。
一旦匹配成功,内核会调用驱动的 probe 函数,初始化设备。
5. 总结
平台设备框架:Linux 内核中用于管理平台设备和驱动的机制,简化了设备和驱动的注册与匹配。
平台设备:通过 platform_device_register 注册,platform_device_unregister 注销。
平台驱动:通过 platform_driver_register 注册,platform_driver_unregister 注销。
匹配机制:设备和驱动通过名称、ID 表等进行匹配,匹配成功后调用驱动的 probe 函数。
平台设备框架是嵌入式 Linux 系统中管理片上设备和外设的重要机制,通过它,设备和驱动可以独立开发,并通过总线进行自动匹配。
1667

被折叠的 条评论
为什么被折叠?



