Linux内存读写 -- 自制驱动调试工具
程序员文章站
2022-06-03 22:33:53
...
前言:
平时的驱动调试中,有时候想读取某个内存,看看内存的值或者变化情况。自制一个用来读取内存的工具,可以方便驱动的开发测试。工具分为驱动程序和应用程序,具体的编写步骤和源代码如下:
1. 编写 字符设备 驱动程序
- 先copy需要的 头文件,这里可以从之前的驱动程序里copy过来,也可借鉴网上相关资源
-
再写 入口函数(_init),出口函数(_exit),file_operations结构体
入口函数:注册字符设备驱动 和 fileoperations 结构体,以及创建一个类和类的设备(方便加载时系统自动创建设备节点)
出口函数:取消注册字符设备驱动 和 取消注册类 与 destroy类设备
file_operations结构体: 拿 THIS_MODULE 或者 方法 进行 填充
入口函数和出口函数是对称的,入口函数注册了字符设备驱动出口函数要取消掉,入口函数注册了类和类的设备则在出口函数应取消注册和destroy。 - 注册好了需要声明一下 告诉内核,下面的几行代码需要加入到驱动程序的末尾
module_init(drvFun_init);
module_exit(drvFun_exit);
MODULE_LICENSE("GPL");
- 这样一个字符设备的驱动框架就完成了。字符设备驱动的 file_operations 结构体非常重要,结构体内部都是指针,这样方便结构体的初始化和调用操作。
其实驱动程序有相当一部分工作是对 file_operations 结构体的填充,结构体的内容很全面,涵盖了所有对文件的操作方法(linux一切皆文件)。本设备驱动仅用到(填充)了 ioctl 方法,具体linux中 ioctl 这个系统调用的介绍在文末有一个连接可以参考。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags) (int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write) (struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read) (struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease) (struct file *, long, struct file_lock **);
};
- 完整的驱动代码
/*
* 字符设备驱动一般所要包含的头文件
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/device.h>
/*
* 宏定义:读写不同的字节数
*/
#define KER_RW_R8 0
#define KER_RW_R16 1
#define KER_RW_R32 2
#define KER_RW_W8 3
#define KER_RW_W16 4
#define KER_RW_W32 5
/*
* 主设备号
*/
static int major;
/*
* 类和类的设备,用来让系统自动创建 “设备节点” /dev/**
*/
static struct class *ker_rw_class;
static struct class_device *ker_rw_class_dev;
/*
* ioctl函数,file_operations结构体的系统调用接口函数
*/
static int ker_rw_ioctrl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
volatile unsigned char *p8;
volatile unsigned char *p16;
volatile unsigned char *p32;
unsigned int val = 0;
unsigned int addr = 0;
unsigned int buf[2];
/* 从用户空间获取数据(dest, src, len) */
copy_from_user(buf, (const void __user *)arg, 8);
addr = buf[0];
val = buf[1];
/* ioremap(IO地址,长度),返回虚拟地址;
* 将一个IO地址空间映射到内核的虚拟地址空间上去
* 为什么要映射?
* 内核空间只能访问虚拟地址的3~4G的地址空间
*/
p8 = (volatile unsigned char *)ioremap(addr, 4);
p16 = p8;
p32 = p8;
switch(cmd)
{
case KER_RW_R8:
//p8 = (volatile unsigned char *)ioremap(addr, 4);
val = *p8;
/* 把数据写到用户空间(dest, src, len) */
copy_to_user((void __user *)(arg+4), &val, 4);
break;
case KER_RW_R16:
//p16 = (volatile unsigned short *)ioremap(addr, 4);
val = *p16;
copy_to_user((void __user *)(arg+4), &val, 4);
break;
case KER_RW_R32:
//p32 = (volatile unsigned int *)ioremap(addr, 4);
val = *p32;
copy_to_user((void __user *)(arg+4), &val, 4);
break;
case KER_RW_W8:
//p8 = (volatile unsigned char *)ioremap(addr, 4);
*p8 = val;
break;
case KER_RW_W16:
//p16 = (volatile unsigned short *)ioremap(addr, 4);
*p16 = val;
break;
case KER_RW_W32:
//p32 = (volatile unsigned int *)ioremap(addr, 4);
*p32 = val;
break;
}
/* 取消映射 */
iounmap(p8);
return 0;
}
/* file_operations 结构体 */
struct file_operations ker_rw_fops = {
.owner = THIS_MODULE,
.ioctl = ker_rw_ioctrl,
//.open = ,
};
/* 驱动入口函数 */
static int ker_rw_init(void)
{
/*
* 注册一个字符设备驱动,名字不重要
* 主设备号填0,让系统自动分配一个主设备号并返回
*/
major = register_chrdev( 0, "ker_rw", &ker_rw_fops);
/* class_create, class_device_create
* 用来在模块加载的时候自动在/dev目录下创建相应设备节点,
* 并在卸载模块时删除该节点,当然前提条件是用户空间移植了udev
* 这里按照通用的格式写就ok,
*/
ker_rw_class = class_create(THIS_MODULE, "ker_rw");
/* 这里的名字比较重要,创建的设备节点不要和现有的系统重复了 /dev/ker_rw/ */
ker_rw_class_dev = class_device_create(ker_rw_class, NULL, MKDEV(major, 0), NULL, "ker_rw");
return 0;
}
/* 驱动出口函数 */
static int ker_rw_exit(void)
{
unregister_chrdev(major, "ker_rw");
class_device_unregister(ker_rw_class_dev);
class_destroy(ker_rw_class);
return 0;
}
/* 声明一下 入口,出口,"GPL" */
module_init(ker_rw_init);
module_exit(ker_rw_exit);
MODULE_LICENSE("GPL");
2. 编写 应用程序
- 先copy需要的 头文件,这里可以从之前的应用程序里copy过来,也可借鉴网络资源,编写时具体用到的再补充
-
再写 main 函数,print_usage 用法函数
int main(int argc, char **argv);
a> main函数一般先做一个判断,如果参数不对就打印用法
b> 然后 open 函数打开设备节点
c> 接下来就可以对设备进行 数据读写 传递数据 等操作了,本例程用到的是 ioctl 方法
ioctl 系统调用方法介绍:https://blog.csdn.net/w741627265/article/details/96151317 - 完整的应用程序代码
/*
* 头文件
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <poll.h>
#include <signal.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
/*
* 宏定义:读写不同的字节数
*/
#define KER_RW_R8 0
#define KER_RW_R16 1
#define KER_RW_R32 2
#define KER_RW_W8 3
#define KER_RW_W16 4
#define KER_RW_W32 5
/* Usage, APP usage
* ./regeditor r8 addr [num]
* ./regeditor r16 addr [num]
* ./regeditor r32 addr [num]
*
* ./regeditor w8 addr val
* ./regeditor w16 addr val
* ./regeditor w32 addr val
*/
void print_usage(char *file)
{
printf("Usage:\n");
printf("%s <r8 | r16 | r32> <phy addr> [num]\n", file);
printf("%s <w8 | w16 | w32> <phy addr> <val>\n", file);
}
int main(int argc, char **argv)
{
int fd;
unsigned int buf[2];
unsigned int num;
unsigned int i;
if((argc != 3) && (argc != 4))
{
print_usage(argv[0]);
return -1;
}
/* 字符设备驱动open函数,打开设备节点 */
fd = open("/dev/ker_rw", O_RDWR);
if(fd < 0)
{
printf("can't open /dev/ker_rw\n");
return -2;
}
/* addr */
buf[0] = strtoul(argv[2], NULL, 0);
if (argc == 4)
{
/* val/num */
buf[1] = strtoul(argv[3], NULL, 0);
num = buf[1];
}
else
{
num = 1;
}
if(strcmp(argv[1], "r8") == 0)
{
for(i = 0; i < num; i++)
{
ioctl(fd, KER_RW_R8, buf);/* val = buf[1] */
printf("%02d. [%08x] = %02x\n", i, buf[0], (unsigned char)buf[1]);
buf[0] += 1;
}
}
else if(strcmp(argv[1], "r16") == 0)
{
for(i = 0; i < num; i++)
{
ioctl(fd, KER_RW_R16, buf);/* val = buf[1] */
printf("%02d. [%08x] = %02x\n", i, buf[0], (unsigned char)buf[1]);
buf[0] += 2;
}
}
else if(strcmp(argv[1], "r32") == 0)
{
for(i = 0; i < num; i++)
{
ioctl(fd, KER_RW_R32, buf);/* val = buf[1] */
printf("%02d. [%08x] = %02x\n", i, buf[0], (unsigned char)buf[1]);
buf[0] += 4;
}
}
else if(strcmp(argv[1], "w8") == 0)
{
ioctl(fd, KER_RW_W8, buf);/* val = buf[1] */
printf("[%08x] = %02x\n", buf[0], (unsigned char)buf[1]);
}
else if(strcmp(argv[1], "w16") == 0)
{
ioctl(fd, KER_RW_W16, buf);/* val = buf[1] */
printf("[%08x] = %02x\n", buf[0], (unsigned char)buf[1]);
}
else if(strcmp(argv[1], "w32") == 0)
{
ioctl(fd, KER_RW_W32, buf);/* val = buf[1] */
printf("[%08x] = %02x\n", buf[0], (unsigned char)buf[1]);
}
else
{
printf(argv[0]);
return -1;
}
return 0;
}
上一篇: golang配置环境排坑
下一篇: GOROOT,GOPATH