lv14 内核内存管理、动态分频及IO访问 12

2024-01-28 05:52

本文主要是介绍lv14 内核内存管理、动态分频及IO访问 12,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、内核内存管理框架

内核将物理内存等分成N块4KB,称之为一页,每页都用一个struct page来表示,采用伙伴关系算法维护

补充:

Linux内存管理采用了虚拟内存机制,这个机制可以在内存有限的情况下提供更多可用的内存空间。每个进程都有自己独立的虚拟地址空间,应用程序只能访问自己的地址空间,而不能直接访问其他进程的地址空间或内核空间。

当应用程序需要访问某些数据时,它会使用虚拟地址来引用这些数据。实际上,这些虚拟地址并不是直接映射到物理内存,而是由操作系统进行转换。操作系统将虚拟地址映射到物理内存中的一些页框(Page Frame),这个过程称为页表映射(Page Table Mapping)

因此,每个应用程序都有自己的页表,它们被存储在内存中,这些页表以及对应的物理页框在不同的时间可能会被交换到硬盘上,从而释放内存空间。当应用程序访问虚拟地址时,操作系统会根据页表映射的结果找到相应的物理页框,然后将数据从物理内存中读取出来。

由于每个应用程序都有自己独立的虚拟地址空间和页表,所以应用程序之间的内存访问是相互隔离的。这种隔离机制可以保证每个应用程序只能访问自己的内存空间,而不能对其他应用程序或操作系统造成干扰。这也是为什么应用程序内存不会影响到其他内存的原因。

0~1G虚拟地址给内核用

1~4G虚拟地址给应用程序用(我们这次研究这块)

内核地址空间划分图:

3G~3G+896M:低端内存,直接映射 虚拟地址 = 3G + 物理地址(虚拟地址连续,物理地址也连续)

细分为:ZONE_DMA、ZONE_NORMAL

分配方式:

1. kmalloc:小内存分配,slab算法
2. get_free_page:整页分配,2的n次方页,n最大为10

大于3G+896M:高端内存

细分为:vmalloc区、持久映射区、固定映射区

分配方式:

vmalloc:虚拟地址连续,物理地址不连续

二、内核中常用动态分配

2.1 kmalloc

函数原型:

void *kmalloc(size_t size, gfp_t flags);

  kmalloc() 申请的内存位于直接映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。   

较常用的 flags(分配内存的方法):

  • GFP_ATOMIC —— 分配内存的过程是一个原子过程,分配内存的过程不会被(高优先级进程或中断)打断;

  • GFP_KERNEL —— 正常分配内存;

  • GFP_DMA —— 给 DMA 控制器分配内存,需要使用该标志(DMA要求分配虚拟地址和物理地址连续)。

flags 的参考用法:  

|– 进程上下文,可以睡眠     GFP_KERNEL  

|– 异常上下文,不可以睡眠    GFP_ATOMIC  

|  |– 中断处理程序       GFP_ATOMIC  

|  |– 软中断          GFP_ATOMIC  

|  |– Tasklet         GFP_ATOMIC  

|– 用于DMA的内存,可以睡眠   GFP_DMA | GFP_KERNEL  

|– 用于DMA的内存,不可以睡眠  GFP_DMA |GFP_ATOMIC   

对应的内存释放函数为:

void kfree(const void *objp);
void *kzalloc(size_t size, gfp_t flags)

2.2 vmalloc

void *vmalloc(unsigned long size);

  vmalloc() 函数则会在虚拟内存空间给出一块连续的内存区,但这片连续的虚拟内存在物理内存中并不一定连续。由于 vmalloc() 没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就需要用此函数了。

对应的内存释放函数为:

void vfree(const void *addr);

注意:vmalloc() 和 vfree() 可以睡眠,因此不能从异常上下文调用

2.3 kmalloc & vmalloc 的比较

kmalloc()、kzalloc()、vmalloc() 的共同特点是:

  1. 用于申请内核空间的内存;

  2. 内存以字节为单位进行分配;

  3. 所分配的内存虚拟地址上连续;

kmalloc()、kzalloc()、vmalloc() 的区别是:

  1. kzalloc 是强制清零的 kmalloc 操作;(以下描述不区分 kmalloc 和 kzalloc)

  2. kmalloc 分配的内存大小有限制(128KB),而 vmalloc 没有限制;

  3. kmalloc 可以保证分配的内存物理地址是连续的,但是 vmalloc 不能保证;

  4. kmalloc 分配内存的过程可以是原子过程(使用 GFP_ATOMIC),而 vmalloc 分配内存时则可能产生阻塞

  5. kmalloc 分配内存的开销小因此 kmalloc 比 vmalloc 要快

一般情况下,内存只有在要被 DMA 访问的时候才需要物理上连续,但为了性能上的考虑,内核中一般使用 kmalloc(),而只有在需要获得大块内存时才使用 vmalloc()。

2.4 分配选择原则:

  1. 小内存(< 128k)用kmalloc,大内存用vmalloc或get_free_page

  2. 如果需要比较大的内存,并且要求使用效率较高时用get_free_page,否则用vmalloc

三、IO访问-------访问外设控制器的寄存器

访问外设控制器的寄存器两种方式:

  1. IO端口:X86上用IO指令访问

  2. IO内存:外设寄存器在SOC芯片手册上都有相应物理地址

IO内存访问接口:

static inline void __iomem *ioremap(unsigned long offset, unsigned long size)
/*
功能:实现IO管脚的映射
参数:offset:该管脚的偏移地址Size:该管脚映射空间的大小
返回值:成功返回映射的虚拟地址,失败NULL
*/
​
static inline void iounmap(volatile void __iomem *addr)
/*
功能:解除io管脚的映射
参数:addr:io管脚映射的地址
*/
​
unsigned readb(void *addr);//1字节   或ioread8(void *addr)
unsigned readw(void *addr);//2字节   或ioread16(void *addr)
unsigned readl(void *addr);//4字节   或ioread32(void *addr)
/*
功能:读取寄存器的值
参数:addr  地址
返回值:读到的数据
*/
​
void writeb(unsigned value, void *addr);//1字节   或iowrite8(u8 value, void *addr)
void writew(unsigned value, void *addr);//2字节  或iowrite16(u16 value, void *addr)
void writel(unsigned value, void *addr);//4字节  或iowrite32(u32 value, void *addr)
/*功能:向指定的寄存器中,写入数据。参数:value:待写入寄存器中的数据Address:寄存器的虚拟地址
*/

四、led驱动

  1. 读原理图

  2. 查阅SOC芯片手册

    GPX2_7 led2 GPX2CON----0x11000C40---28~31-----0001 GPX2DAT-----0x11000C44-----7

    GPX1_0 led3 GPX1CON----0x11000C20---0~3-----0001 GPX1DAT----0x11000C24-----0

    GPF3_4 led4 GPF3CON----0x114001E0---16~19-----0001 GPF3DAT----0x114001E4-----4

    GPF3_5 led5 GPF3CON----0x114001E0---20~23-----0001 GPF3DAT----0x114001E4-----5

五、示例

实现led驱动,点亮关闭led

leddrv.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/io.h>
#include <asm/uaccess.h>
#include <asm/atomic.h>#include "leddrv.h"#define GPX1CON 0x11000C20
#define GPX1DAT 0x11000C24#define GPX2CON 0x11000C40
#define GPX2DAT 0x11000C44#define GPF3CON 0x114001E0
#define GPF3DAT 0x114001E4int major = 11;
int minor = 0;
int myled_num  = 1;struct myled_dev
{struct cdev mydev;volatile unsigned long *pled2_con;  volatile unsigned long *pled2_dat;volatile unsigned long *pled3_con;volatile unsigned long *pled3_dat;volatile unsigned long *pled4_con;volatile unsigned long *pled4_dat;volatile unsigned long *pled5_con;volatile unsigned long *pled5_dat;
/*
volatile 防止优化。对这块指针指向的内存,有时候cpu会把外设寄存器中的值读到内部寄存器中,方便下次读的时候更快.加了voltatile就不会优化,否则cpu可能会从内部寄存器中读取,而不是去外设寄存器中读取。
*/
};struct myled_dev *pgmydev = NULL;int myled_open(struct inode *pnode,struct file *pfile)
{pfile->private_data =(void *) (container_of(pnode->i_cdev,struct myled_dev,mydev));return 0;
}int myled_close(struct inode *pnode,struct file *pfile)
{return 0;
}void led_on(struct myled_dev *pmydev,int ledno)
{switch(ledno){case 2:writel(readl(pmydev->pled2_dat) | (0x1 << 7),pmydev->pled2_dat);break;case 3:writel(readl(pmydev->pled3_dat) | (0x1),pmydev->pled3_dat);break;case 4:writel(readl(pmydev->pled4_dat) | (0x1 << 4),pmydev->pled4_dat);break;case 5:writel(readl(pmydev->pled5_dat) | (0x1 << 5),pmydev->pled5_dat);break;}
}void led_off(struct myled_dev *pmydev,int ledno)
{switch(ledno){case 2:writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)),pmydev->pled2_dat);break;case 3:writel(readl(pmydev->pled3_dat) & (~(0x1)),pmydev->pled3_dat);break;case 4:writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)),pmydev->pled4_dat);break;case 5:writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)),pmydev->pled5_dat);break;}
}long myled_ioctl(struct file *pfile,unsigned int cmd,unsigned long arg)
{struct myled_dev *pmydev = (struct myled_dev *)pfile->private_data;if(arg < 2 || arg > 5){return -1;}switch(cmd){case MY_LED_ON:led_on(pmydev,arg);break;case MY_LED_OFF:led_off(pmydev,arg);break;default:return -1;}return 0;
}struct file_operations myops = {.owner = THIS_MODULE,.open = myled_open,.release = myled_close,.unlocked_ioctl = myled_ioctl,
};void ioremap_ledreg(struct myled_dev *pmydev)
{pmydev->pled2_con = ioremap(GPX2CON,4);pmydev->pled2_dat = ioremap(GPX2DAT,4);pmydev->pled3_con = ioremap(GPX1CON,4);pmydev->pled3_dat = ioremap(GPX1DAT,4);pmydev->pled4_con = ioremap(GPF3CON,4);pmydev->pled4_dat = ioremap(GPF3DAT,4);pmydev->pled5_con = pmydev->pled4_con; //最后一个led5不能remap,因为使用的是同一个组寄存器不同的位pmydev->pled5_dat = pmydev->pled4_dat;
}void set_output_ledconreg(struct myled_dev *pmydev)
{writel((readl(pmydev->pled2_con) & (~(0xF << 28))) | (0x1 << 28),pmydev->pled2_con);writel((readl(pmydev->pled3_con) & (~(0xF))) | (0x1),pmydev->pled3_con);writel((readl(pmydev->pled4_con) & (~(0xF << 16))) | (0x1 << 16),pmydev->pled4_con);writel((readl(pmydev->pled5_con) & (~(0xF << 20))) | (0x1 << 20),pmydev->pled5_con);writel(readl(pmydev->pled2_dat) & (~(0x1 << 7)),pmydev->pled2_dat);writel(readl(pmydev->pled3_dat) & (~(0x1)),pmydev->pled3_dat);writel(readl(pmydev->pled4_dat) & (~(0x1 << 4)),pmydev->pled4_dat);writel(readl(pmydev->pled5_dat) & (~(0x1 << 5)),pmydev->pled5_dat);
}void iounmap_ledreg(struct myled_dev *pmydev)
{iounmap(pmydev->pled2_con);pmydev->pled2_con = NULL;iounmap(pmydev->pled2_dat);pmydev->pled2_dat = NULL;iounmap(pmydev->pled3_con);pmydev->pled3_con = NULL;iounmap(pmydev->pled3_dat);pmydev->pled3_dat = NULL;iounmap(pmydev->pled4_con);pmydev->pled4_con = NULL;iounmap(pmydev->pled4_dat);pmydev->pled4_dat = NULL;pmydev->pled5_con = NULL;pmydev->pled5_dat = NULL;
}int __init myled_init(void)
{int ret = 0;dev_t devno = MKDEV(major,minor);/*申请设备号*/ret = register_chrdev_region(devno,myled_num,"myled");if(ret){ret = alloc_chrdev_region(&devno,minor,myled_num,"myled");if(ret){printk("get devno failed\n");return -1;}major = MAJOR(devno);//容易遗漏,注意}pgmydev = (struct myled_dev *)kmalloc(sizeof(struct myled_dev),GFP_KERNEL);if(NULL == pgmydev){unregister_chrdev_region(devno,myled_num);printk("kmalloc failed\n");return -1;}memset(pgmydev,0,sizeof(struct myled_dev)); //这里的memset并非c库的函数,而是内核自己实现的memset函数/*给struct cdev对象指定操作函数集*/	cdev_init(&pgmydev->mydev,&myops);/*将struct cdev对象添加到内核对应的数据结构里*/pgmydev->mydev.owner = THIS_MODULE;cdev_add(&pgmydev->mydev,devno,myled_num);/*ioremap*/ioremap_ledreg(pgmydev);/*con-register set output*/set_output_ledconreg(pgmydev);return 0;
}void __exit myled_exit(void)
{dev_t devno = MKDEV(major,minor);/*iounmap*/iounmap_ledreg(pgmydev);cdev_del(&pgmydev->mydev);unregister_chrdev_region(devno,myled_num);kfree(pgmydev);pgmydev = NULL;
}MODULE_LICENSE("GPL");module_init(myled_init);
module_exit(myled_exit);

leddrv.h

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>#include <stdio.h>#include "leddrv.h"int main(int argc,char *argv[])
{int fd = -1;int onoff = 0;int no = 0;if(argc < 4){printf("The argument is too few\n");return 1;}sscanf(argv[2],"%d",&onoff);sscanf(argv[3],"%d",&no);if(no < 2 || no > 5){printf("len-no is invalid\n");return 2;}fd = open(argv[1],O_RDONLY);if(fd < 0){printf("open %s failed\n",argv[1]);return 3;}if(onoff){ioctl(fd,MY_LED_ON,no);}else{ioctl(fd,MY_LED_OFF,no);}close(fd);fd = -1;return 0;
}

Makefile

ifeq ($(KERNELRELEASE),)ifeq ($(ARCH),arm)
KERNELDIR ?= /home/linux/Linux_4412/kernel/linux-3.14
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)modules:$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesmodules_install:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_installclean:rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versionselseCONFIG_MODULE_SIG=n
obj-m += mychar.o
obj-m += mychar_poll.o
obj-m += openonce_atomic.o
obj-m += openonce_spinlock.o
obj-m += mychar_sema.o
obj-m += mychar_mutex.o
obj-m += second.o
obj-m += leddrv.oendif

testled_app.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>#include <stdio.h>#include "leddrv.h"int main(int argc,char *argv[])
{int fd = -1;int onoff = 0;int no = 0;if(argc < 4){printf("The argument is too few\n");return 1;}sscanf(argv[2],"%d",&onoff);sscanf(argv[3],"%d",&no);if(no < 2 || no > 5){printf("len-no is invalid\n");return 2;}fd = open(argv[1],O_RDONLY);if(fd < 0){printf("open %s failed\n",argv[1]);return 3;}if(onoff){ioctl(fd,MY_LED_ON,no);}else{ioctl(fd,MY_LED_OFF,no);}close(fd);fd = -1;return 0;
}

编译ko文件: 

编译testled_app

这篇关于lv14 内核内存管理、动态分频及IO访问 12的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/652661

相关文章

MySQL 内存使用率常用分析语句

《MySQL内存使用率常用分析语句》用户整理了MySQL内存占用过高的分析方法,涵盖操作系统层确认及数据库层bufferpool、内存模块差值、线程状态、performance_schema性能数据... 目录一、 OS层二、 DB层1. 全局情况2. 内存占js用详情最近连续遇到mysql内存占用过高导致

最新Spring Security的基于内存用户认证方式

《最新SpringSecurity的基于内存用户认证方式》本文讲解SpringSecurity内存认证配置,适用于开发、测试等场景,通过代码创建用户及权限管理,支持密码加密,虽简单但不持久化,生产环... 目录1. 前言2. 因何选择内存认证?3. 基础配置实战❶ 创建Spring Security配置文件

在macOS上安装jenv管理JDK版本的详细步骤

《在macOS上安装jenv管理JDK版本的详细步骤》jEnv是一个命令行工具,正如它的官网所宣称的那样,它是来让你忘记怎么配置JAVA_HOME环境变量的神队友,:本文主要介绍在macOS上安装... 目录前言安装 jenv添加 JDK 版本到 jenv切换 JDK 版本总结前言China编程在开发 Java

Spring Boot Actuator应用监控与管理的详细步骤

《SpringBootActuator应用监控与管理的详细步骤》SpringBootActuator是SpringBoot的监控工具,提供健康检查、性能指标、日志管理等核心功能,支持自定义和扩展端... 目录一、 Spring Boot Actuator 概述二、 集成 Spring Boot Actuat

go动态限制并发数量的实现示例

《go动态限制并发数量的实现示例》本文主要介绍了Go并发控制方法,通过带缓冲通道和第三方库实现并发数量限制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录带有缓冲大小的通道使用第三方库其他控制并发的方法因为go从语言层面支持并发,所以面试百分百会问到

MySQL多实例管理如何在一台主机上运行多个mysql

《MySQL多实例管理如何在一台主机上运行多个mysql》文章详解了在Linux主机上通过二进制方式安装MySQL多实例的步骤,涵盖端口配置、数据目录准备、初始化与启动流程,以及排错方法,适用于构建读... 目录一、什么是mysql多实例二、二进制方式安装MySQL1.获取二进制代码包2.安装基础依赖3.清

java内存泄漏排查过程及解决

《java内存泄漏排查过程及解决》公司某服务内存持续增长,疑似内存泄漏,未触发OOM,排查方法包括检查JVM配置、分析GC执行状态、导出堆内存快照并用IDEAProfiler工具定位大对象及代码... 目录内存泄漏内存问题排查1.查看JVM内存配置2.分析gc是否正常执行3.导出 dump 各种工具分析4.

WinForm跨线程访问UI及UI卡死的解决方案

《WinForm跨线程访问UI及UI卡死的解决方案》在WinForm开发过程中,跨线程访问UI控件和界面卡死是常见的技术难题,由于Windows窗体应用程序的UI控件默认只能在主线程(UI线程)上操作... 目录前言正文案例1:直接线程操作(无UI访问)案例2:BeginInvoke访问UI(错误用法)案例

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

一文详解SpringBoot中控制器的动态注册与卸载

《一文详解SpringBoot中控制器的动态注册与卸载》在项目开发中,通过动态注册和卸载控制器功能,可以根据业务场景和项目需要实现功能的动态增加、删除,提高系统的灵活性和可扩展性,下面我们就来看看Sp... 目录项目结构1. 创建 Spring Boot 启动类2. 创建一个测试控制器3. 创建动态控制器注