字符设备驱动测试(随笔)

一、任务要求

1、使用register_chrdev_region或alloc_chrdev_region、cdev_init、cdve_add等相关API进行字符设备驱动注册。
2、在开发板终端,输入"on" 开灯,“off” 关灯。

二、完整代码

1、module_test.c 驱动部分代码

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>

#include <linux/gpio.h>
#include <plat/gpio-cfg.h>
#include <mach/gpio.h>
#include <mach/gpio-exynos4.h>

#include <plat/map-base.h>
#include <plat/map-s5p.h>

#include <linux/string.h>
#include <asm/uaccess.h>

#include <linux/io.h>
#include <linux/ioport.h>

#include <linux/cdev.h>

#define MYMAJOR 0
#define NAME	"MyModule"

#define GPL2_PA_BASE	0X11000100   //GPL2的虚拟地址基地址
#define rGPL2CON (*GPL2CON)			 //用于读写GPL2的CON寄存器
#define rGPL2DAT (*GPL2DAT)			 //用于读写GPL2的DAT寄存器,两者相差4字节地址
unsigned int *GPL2CON = NULL;		 //保持地址,用ioremap分配
unsigned int *GPL2DAT = NULL;

#define COMEDI_MAJOR 248			//主设备号
#define MAJORCNT	 4				//次设备号的数量
static struct cdev mycdev;			
int major;							//保存返回的设备号(主+次)
dev_t dev;							//返回设备号,给alloc_chrdrv_region函数调用

//打开
static int module_open(struct inode *inode, struct file *file)
{
	printk("module_open\n");
	return 0;
}

//写
static ssize_t module_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
	char data_buffer;
	if (copy_from_user(&data_buffer, user_buf, strlen(user_buf))) {
		printk("copy_from_user failee!\n");
	}

	if (data_buffer == '1')
		rGPL2DAT = (1 << 0);
	else if (data_buffer == '0')
		rGPL2DAT = (0 << 0);

	printk("module_write\n");
	return 0;
}

//读
static ssize_t module_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
	printk("module_read\n");	
	return 0;
}

//关闭文件
static int module_release(struct inode *inode, struct file *file)
{
	printk("module_release\n");
	return 0;
}

static const struct file_operations fops = {
	.open = module_open,
	.write = module_write,
	.read = module_read,
	.release = module_release,
	.owner = THIS_MODULE,
};

static int __init module_test(void)
{
	int retval;
	dev_t dev;
	printk(KERN_DEBUG "install module_test");
	
	// major = MKDEV(COMEDI_MAJOR, 2);
	// retval = register_chrdev_region(major, MAJORCNT, NAME);
	// if (retval)
		// return -EINVAL;
	retval = alloc_chrdev_region(&dev, 2, MAJORCNT, NAME);	//次设备号从2开始,连续4个
	if (retval < 0) {
		printk(KERN_ERR "alloc_chrdev_region failed!\n");
		goto ERR1;
	}
	printk("\n");
	printk("show MAJOR: %d\n", MAJOR(dev));		//获取主设备号
	printk("show MINOR: %d\n", MINOR(dev));		//获取次设备号
	
	cdev_init(&mycdev, &fops);					//初始化mycdev结构体,将fops绑定上去
	
	if (cdev_add(&mycdev, dev, MAJORCNT)) {
		printk(KERN_ERR "cdev_add failed!\n");
		goto ERR2;
	}

	if (!request_mem_region(GPL2_PA_BASE, 8, "GPL2_BASE")) {
		printk(KERN_ERR "request_mem_region failed!\n");
		goto ERR3;
	}
	
	GPL2CON = ioremap(GPL2_PA_BASE, 8);
	GPL2DAT = GPL2CON + 1;

	rGPL2CON = 0X11111111;
	rGPL2DAT = (0 << 0);

	printk("GPL2CON:%p\n", GPL2CON);
	printk("GPL2DAT:%p\n", GPL2DAT);
	return 0;
	
ERR3:
	cdev_del(&mycdev);
ERR2:
	unregister_chrdev_region(dev, MAJORCNT);
ERR1:
	return -EINVAL;
}

static void __exit module_ex(void)
{
	printk(KERN_DEBUG "uninstall module_test");
	rGPL2DAT = (0 << 0);		//灭灯
	iounmap(GPL2CON);			//注销映射
	release_mem_region(GPL2_PA_BASE, 8);	//注销在内核上的登记
	
	cdev_del(&mycdev);
	unregister_chrdev_region(dev, MAJORCNT);
	
}

module_init(module_test);
module_exit(module_ex);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Zhou");

2、app.c 应用程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define DEV_DIR "/dev/MyModule" 	//需要在/dev上新建MyModule文件,要对应上驱动的主次设备号
		
int main(void)
{
	int fd;
	char buf[10];
	fd = open(DEV_DIR, O_RDWR);
	if (fd < 0)
	{
		perror("open failed!\n");
	}
	
	//write(fd, "12345", 5);
	//read(fd, (char*)buf, 10);
	while (1)
	{
		memset((char*)buf, 0, sizeof(buf));
		scanf("%s", buf);
		if (!strcmp(buf, "on"))
			write(fd, "1", 1);
		else if (!strcmp(buf, "off"))
			write(fd, "0", 1);
		else if (!strcmp(buf, "quit"))
			break;
	}

	
	close(fd);
	return 0;
}

3、Makefile

obj-m = module_test.o
KER_DIR = /home/iTOP4421/iTop4412_Kernel_3.0
PWD = $(shell pwd)

all:
	make -C $(KER_DIR) M=$(PWD) modules
	arm-linux-gnueabi-gcc -o app app.c

.PHONY:clean
clean:
	make -C $(KER_DIR) M=$(PWD) clean

三、程序分析

Q1:注册的流程是什么?

在这里插入图片描述

Q2: register_chrdev 与 register_chrdev_region/alloc_chrdev_region有什么区别?

首先,register_chrdev是旧的API接口,它使用起来确实很方便,一个函数就能完成注册的工作。但它只能注册主设备号,次设备永远默认为0。

而新的API接口,它将注册工作分成多个步骤。register_chrdev_region/alloc_chrdev_region只是用来申请设备号,可以理解为,向内核拿一个号。比如,你去医院看病,先要做的是排个队,取个号,而不是直接看医生。

在复杂的设备驱动中,往往需要多个驱动来协同工作。那么,多个驱动怎么联系在一起呢?它们设备号往往是 主设备号一致,次设备号按顺序往下排列。主设备号相当于一个总的大类,次设备号相当于具体的动作。对于 register_chrdev函数,无法创建多个次设备号的驱动。

Q3: register_chrdev_region/alloc_chrdev_region如何使用?

int register_chrdev_region(dev_t first, unsigned int count, char *name);

  • first
    是你要分配的起始设备编号.
    first 的次编号部分常常是 0, 但是没有要求是那个效果.
  • count
    是你请求的连续设备编号的总数. 注意, 如果 count 太大, 你要求的范围可能溢出到下一个主设备号;
    但是只要你要求的编号范围可用, 一切都仍然会正确工作.
  • name
    是应当连接到这个编号范围
    的设备的名字; 它会出现在 /proc/devices 和 sysfs 中.
  • 返回值
    如果分配成功进行, register_chrdev_region 的返回值是 0
    出错的情况下, 返回一个负的错误码, 你不能存取请求的区域.

int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);

  • dev: 这是个传出参数,用来保存设备号
  • firstminor: 你要分配的起始设备编号。
  • count:请求的连续设备编号的总数。
  • name:设备名字

对比:
register_chrdev_region 静态分配设备号。需要传入一个确定的设备号,需要使用MKDEV宏来创建。要确保你的主设备号没有被占用,否则创建失败。次设备号的配置,有点类似于内存分配的操作。

alloc_chrdev_region 动态分配设备号。通过一个传出参数保存设备号,其它参数与register_chrdev_region类似。

动态分配设备号可以避免手动指定设备号时带来的缺点,但是它却也有自己的缺点,那就是无法预知在/dev下创建设备节点,因为动态分配设备号不能保证在每次加载驱动module时始终一致(其实若在两次加载同一个驱动module之间并没有加载其他的module,那么自动分配的设备号还是一致的,因为内核分配设备号并不是随机的,但是书上说某些内核开发人员预示不久的将来会用随机方式进行处理),不过,这个缺点可以避免,因为在加载驱动module后,我们可以读取/proc/devices文件以获得Linux内核分配给该设备的主设备号。

Q4:MKDEV、MAJOR、MINOR三个宏有什么作用?

1、MKDEV宏可以帮你凑出一个完整的设备号。一般一个设备号是32位,前16位主设备号,后16位是次设备号,但也可能前8位是主设备号。不同的版本的linux,主次设备号安排的位数可能不同。而使用MKDEV的好处是,我们摆脱这种细节的东西,只需输入我们需要的主次设备号进去,返回就是一个”组装“好的设备号。

2、MAJOR,用来读取”组装“好的设备号中的主设备号。
3、MINOR, 用来读取”组装“好的设备号中的次设备号。

使用宏的好处是,它们能使你的代码更具可移植性。要修改,也只需修改宏的部分。

Q5: 真正用来注册的是哪个函数?

答案是 cdev_add。记住,出错或卸载模块都要调用它们的注销函数。
在注册之前,我们先要对 struct cdev 结构体进行一番初始化。主要是将 file_operarions结构体与 struct cdev 绑定在一起。

Q6: 在模块入口函数中,为什么采用这种形式处理错误?

这种处理错误的方式,我尚且称它为 倒影式错误处理。
goto有个特点,可以跳到指定的标签,然后往下执行。比如,我跳到 ERR2,那么ERR1的内容也会被执行。在内核中源码,常常用到次方式。

这种处理错误的顺序是:假如标签的顺序是 1 2 3 4 ,错误处理的顺序是 4 3 2 1.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux字符设备驱动实验是指在Linux操作系统中编写和测试字符设备驱动程序的过程。字符设备驱动程序负责与字符设备进行交互,包括输入输出数据、控制设备和处理设备的状态等。 在进行Linux字符设备驱动实验之前,首先需要了解字符设备字符设备驱动的基本概念及其工作原理。字符设备是指以字符为单位进行输入输出的设备,如串口、打印机等。字符设备驱动是指将操作系统与字符设备进行交互的程序。 在实验中,我们通常需要编写一个字符设备驱动程序,包括初始化设备、读写数据、控制设备等功能。首先,我们需要定义字符设备驱动的数据结构,包括设备号、驱动程序打开、关闭等函数的实现。然后,我们需要实现字符设备驱动的读写函数来实现数据的输入输出。最后,我们可以进行一些附加功能的实现,如控制设备的状态、处理中断等。 在实验过程中,我们需要使用Linux内核提供的字符设备接口来进行字符设备驱动的编写和测试。可以使用一些工具和命令来加载和测试字符设备驱动程序,如insmod、rmmod等。通过这些工具和命令,我们可以加载和卸载字符设备驱动程序,并在用户空间进行数据的读写操作,来测试字符设备驱动的功能和性能。 Linux字符设备驱动实验可以帮助我们深入了解字符设备字符设备驱动的工作原理,并学习Linux内核的开发和调试技术。通过实验,我们可以更好地理解操作系统和驱动程序之间的关系,提高我们在Linux系统开发和嵌入式系统开发中的能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值