地址映射控制硬件/内核传参(4) 📅 发布时间:2026/8/22 14:02:34 👁 浏览次数: 一、简介在单片机和裸机通过操作寄存器进而操作硬件但是在Linux上是不行的在Linux上如果想要操作硬件需要先把物理地址转换为虚拟地址因为Linux使能了MMU所以在linux上不能直接操作物理地址问题使能了MMU以后的好处1让虚拟地址成为了可能2可以让系统更加的安全因为有了MMU上层应用能看到的内存都是虚拟内存应用不能直接访问硬件所以保证了系统的安全问题MMU非常复杂那么我们如何完成物理地址到虚拟地址的转换呢内核提供的相关函数ioremap,iounmap二 相关函数2.1 ioremap/iounmap函数头文件#include asm/io.h函数原型void __iomem *ioremap(resource_size_t phys_addr, size_t size);函数作用建立映射,将一段物理地址(通常是外设寄存器空间)映射到内核的虚拟地址空间函数参数param1phys_addr: 要映射的起始物理地址param2size: 要映射的区域长度字节数返回值成功返回一个void __iomem *类型的内核虚拟地址指针。失败返回NULL。__iomem: 这是一个重要的代码空间标识用于提醒开发者和代码检查工具该指针指向的是“I/O内存”空间其访问行为可能与普通内存不同头文件#include asm/io.h函数原型void iounmap(volatile void __iomem *addr);函数作用解除映射释放由ioremap建立的映射防止内存泄漏和地址错误访问函数参数addr:ioremap返回的地址指针即要解除映射的虚拟地址起始位置返回值无注意物理地址只能被映射一次多次映射会失败问题如何查看哪些物理地址被映射过直接通过命令cat /proc/iomem 查看已经被映射的物理地址三、实践需求1.通过杂项设备完成一个LED的驱动2.完成一个上层测试应用应用要求在上层传如参数1为LED亮传入参数为0LED灭思路填充杂项设备的结构体文件操作集结构体、使用设备节点完成open,read,write函数驱动框架#include linux/module.h #include linux/init.h #include linux/miscdevice.h #include linux/fs.h #include linux/uaccess.h #include linux/io.h /*5.定义LED引脚的宏该宏对应LED引脚的物理地址需要通过地址映射为虚拟地址才可以操作*/ #define GPIO0_DR 0xFD060020 unsigned int *vir_gpio_dr; //定义一个指针变量用于保存映射后的虚拟地址 /*2.定义文件操作函数当在应用层使用open,close,read,write等函数时会在驱动层执行对应函数对应*/ static int misc_open(struct inode *inode, struct file *file){} static int misc_release(struct inode *inode, struct file *file){} static ssize_t misc_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { /*当在应用层调用read函数时会执行当前函数*/ //char __user *buf:指向用户缓冲区的指针用于存放读取到的数据 //size_t count用户请求读取的字节数 char kbuf[64]嵌入式Linux驱动 printk(misc device is read:hello misc read\n); /*当用户层read时通过copy_to_user函数将kbuf中的内容拷贝到buf中从而实现驱动层向应用层传数据*/ if(copy_to_user(buf,kbuf,sizeof(kbuf))!0) { printk(misc device copy to user error\n); return -1; } return 0; } static ssize_t misc_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos) { /*当在应用层执行write函数时驱动层会执行当前函数*/ //const char __user *buf:指向用户空间缓冲区的指针用户要写入的数据就存放在这里 //size_t count:用户层请求写入的字节数 char kbuf[64]{0}; /*通过copy_from_user函数将buf中的内容拷贝到kbuf中从而实现应用层向驱动层传数据*/ if(copy_from_user(kbuf,buf,count)!0) { printk(misc device copy from user error\n); return -1; } /*6*得到映射后的地址后可以对地址的位进行|、|操作*/ if(kbuf[0]1) *vir_gpio_dr |(17); else if(kbuf[0]0) *vir_gpio_dr ~(17); printk(misc device is write:hello misc write\n); return 0; } /*3.填充struct file_operations结构体*/ struct file_operations xxx_fops { //该结构体里面包含了在驱动层里面定义的open,close,read,write函数 .owner THIS_MODULE, //固定写法 .open .release .read .write } /*4.填充杂项设备结构体*/ struct miscdevice misc_dev { .minor MISC_DYNAMIC_MINOR, //自动分配次设备号 .name hello_misc, //定义自己的杂项设备节点名称 .fops misc_fops //设备操作函数集合 }; /*1.定义模块加载函数与模块卸载函数*/ static int misc_init(void) { //加载模块时会执行该函数 int ret; ret misc_register(misc_dev); //注册杂项设备 if(ret0) { printk(misc device is register error\n); return -1; } vir_gpio_dr ioremap(GPIO0_DR,4); //映射寄存器虚拟地址 if(vir_gpio_dr NULL) { printk(ioremap failed\n); return -EBUSY; } printk(ioremap succeed\n); return 0; } static void misc_exit(void) { //卸载函数时会执行该函数 misc_deregister(misc_dev); //注销杂项设备 iounmap(vir_gpio_dr); //取消映射 } module_init(misc_init);//模块加载函数 module_exit(misc_exit);//模块卸载函数 MODULE_LICENSE(GPL);应用层框架#include stdio.h #include fcntl.h #include unistd.h #include sys/stat.h #include sys/types.h #include stdlib.h int main(int argc, char *argv[]) { int fd; char buf[64]{0}; /*当执行open函数时会执行驱动层对应open函数*/ fd open(/dev/hello_misc, O_RDWR); //打开驱动对应的设备文件 if(fd 0) { printf(misc device open failed\n); return -1; } printf(app layer device open success\n); /*当执行write函数时会执行驱动层对应write函数*/ buf[0]atoi(argv[1]); write(fd,buf,sizeof(buf)); printf(app layer write data : %s\n,buf); /*当执行close函数时会执行驱动层对应release函数*/ close(fd); return 0; }四、内核传参Linux 内核提供了 module_param(name, type, perm)、 module_param_array(name, type, nump, perm)宏和 module_param_string(name, string, len, perm)宏 分别进行基本类型、 数组和字符串参数的传递。头文件 #includelinux/module.h名称module_param(name, type, perm)功能将模块的全局变量声明为可加载参数允许在加载模块时通过命令行指定参数值参数:name全局变量名type参数类型int, bool, charp等permsysfs文件系统的访问权限如06440表示不在sysfs中创建文件参数读写权限头文件 #includelinux/module.h名称 module_param_array(name, type, nump, perm)功能将模块的数组类型全局变量声明为可加载参数允许在加载模块时通过命令行指定多个参数值参数:name数组变量名type数组元素类型int, bool, charp等nump指向整数变量的指针用于记录实际传入的数组元素个数方向由该函数内到外permsysfs文件系统的访问权限如06440表示不在sysfs中创建文件参数读写权限头文件 #includelinux/module.h名称module_param_string(name, string, len, perm功能将模块的字符串类型全局变量声明为可加载参数允许在加载模块时通过命令行指定字符串值参数:name参数名称在命令行中使用的名称type字符数组变量名程序中定义的len字符串缓冲区的最大长度permsysfs文件系统的访问权限如06440表示不在sysfs中创建文件参数读写权限问题什么是驱动传参就是传递参数给我们的驱动eg:insmod LED.ko a1问题驱动传参有什么作用1设置驱动的相关参数比如设置缓冲区的大小2设置安全校验防止写的驱动被盗用问题怎么给驱动传参数1传递普通的参数比如char,int类型使用module_param(name, type, perm)函数2传递数组使用module_param_array函数3传递字符串module_param_string(name, string, len, perm)#include linux/init.h #include linux/module.h static int a; static int arr[10]; static char para[10]; static int num; module_param(a,int,0644); module_param_array(arr,int,num,0644); module_param_string(hehe, para, sizeof(para), 0644); static int param_init(void) { int i; printk(param init insmod succeed!\n); for(i0;inum;i) { printk(arr[%d]%d\n,i,arr[i]); } printk(para%s\n,para); return 0; } static void param_exit(void) { printk(param exit rmmod succeed!\n); } module_init(param_init); module_exit(param_exit); MODULE_LICENSE(GPL);问题若是多传进去参数导致数组越界会发生什么会发生错误加载不成功