欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux内存读写 -- 自制驱动调试工具

程序员文章站 2022-06-03 22:33:53
...

前言:
平时的驱动调试中,有时候想读取某个内存,看看内存的值或者变化情况。自制一个用来读取内存的工具,可以方便驱动的开发测试。工具分为驱动程序和应用程序,具体的编写步骤和源代码如下:

1. 编写 字符设备 驱动程序

  1. 先copy需要的 头文件,这里可以从之前的驱动程序里copy过来,也可借鉴网上相关资源
  2. 再写 入口函数(_init),出口函数(_exit),file_operations结构体
    入口函数:注册字符设备驱动fileoperations 结构体,以及创建一个类和类的设备(方便加载时系统自动创建设备节点)
    出口函数:取消注册字符设备驱动取消注册类destroy类设备
    file_operations结构体: 拿 THIS_MODULE 或者 方法 进行 填充
    入口函数和出口函数是对称的,入口函数注册了字符设备驱动出口函数要取消掉,入口函数注册了类和类的设备则在出口函数应取消注册和destroy。
  3. 注册好了需要声明一下 告诉内核,下面的几行代码需要加入到驱动程序的末尾
    module_init(drvFun_init);
    module_exit(drvFun_exit);
    MODULE_LICENSE("GPL");
  1. 这样一个字符设备的驱动框架就完成了。字符设备驱动的 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 **);  
}; 
  1. 完整的驱动代码
/*
* 字符设备驱动一般所要包含的头文件
*/
#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. 编写 应用程序

  1. 先copy需要的 头文件,这里可以从之前的应用程序里copy过来,也可借鉴网络资源,编写时具体用到的再补充
  2. 再写 main 函数,print_usage 用法函数
    int main(int argc, char **argv);
    a> main函数一般先做一个判断,如果参数不对就打印用法
    b> 然后 open 函数打开设备节点
    c> 接下来就可以对设备进行 数据读写 传递数据 等操作了,本例程用到的是 ioctl 方法
    ioctl 系统调用方法介绍:https://blog.csdn.net/w741627265/article/details/96151317
  3. 完整的应用程序代码
/*
* 头文件
*/
#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;
}