字符设备驱动程序


前言

字符设备指那些必须以串行顺序依次访问的设备,并不需要缓冲,通常用于不需要大量数据请求传送的设备类型

字符设备驱动开发的基本步骤:

①、确定主设备号次设备号(初始化)
②、实现字符驱动程序

  • 实现file_operations结构体
  • 实现初始化函数,注册字符设备
  • 实现销毁函数,释放字符设备

③.创建设备文件节点

在这里插入图片描述

在这里插入图片描述


一、驱动初始化

主设备号是内核识别一类设备的标识。
整数(占12bits),范围从0到4095,通常使用1到255
次设备号由内核使用,用于正确确定设备文件所指的设备。整数(占20bits),范围从0到1048575,一般使用0到255
设备号
dev_t类型(32位); 用于保存设备编号(包括主设备号12位和次设备号20位)

将主和次设备号转换成dev_t类型的宏
MKDEV (int major,int minor);
eg:dev_t devno = MKDEV(222,0);

将dev_t转换成主和次设备号的宏:
MAJOR(dev_t);
MINOR(dev_t);

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)
#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))

可以通过cat /proc/devices查看当前已经加载的设备驱动程序的主设备号。

1.1申请主设备号

/	静态申请设备号,并把设备号与设备名绑定,并注册到内核系统里
int register_chrdev_region( dev_t devno,   unsigned int count,    char *name );
devno:要分配的设备编号范围的起始值,次设备号经常为0 
count:请求的连续设备编号个数
name:和该设编号范围关联的设备名称 

/	动态申请设备号,并把设备号与设备名绑定,并注册到内核系统
int  alloc_chrdev_region(dev_t *devno,unsigned int firstminor,unsigned int count,char *name);

1.2初始化cdev

void cdev_init( struct cdev *, struc t file_operations *);
 cdev_init()函数用于初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接。其中 cdev->owner = THIS_MODULE;

1.3注册cdev(注册设备)

int cdev_add(struct cdev *, dev_t, unsigned) ;
cdev_add()函数向系统添加一个 cdev,完成设备与设备号绑定,并注册设备。

1.4自动创建设备节点

struct class *class_create(struct module *owner, const char *name);
struct class_device *class_device_create(struct class        *cls, //(p_hello_class, NULL, devno, NULL, HELLO_NAME)
                                         struct class_device *parent,
                                         dev_t               devt,
                                         struct device       *device,
                                         const char          *fmt, ...
//用class_create函数来创建一个名字为name的类,这个类存放于sys/class下面.
//一旦创建好了这个类,再调用 device_create函数。这样,加载模块的时候,用户空间中的udev或mdev会自动响应 device_create函数,去sys/class下寻找对应的类从而在dev目录下创建相应的设备节点。

二、实现设备操作

file_operations结构体
字符驱动和内核的接口在include/linux/fs.h定义字符驱动只要实现一个file_operations结构体,并注册到内核中,内核就有了操作此设备的能力。

file_operations 的主要成员:
struct module *owner: 指向模块自身
open:打开设备
release:关闭设备
read:从设备上读数据
write:向设备上写数据
ioctl:I/O控制函数
llseek:定位读写指针
mmap:映射设备空间到进程的地址空间

struct file_operations hello_fops = {
	.owner = THIS_MODULE,  
	.open = hello_open, //函数指针,指向hello_open
	.read = hello_read,
	.write = hello_write,
	.release = hello_release,
	//.ioctl = hello_ioctl

上面的hello_open、hello_write、hello_read、hello_release是要在驱动中自己实现的。file_operations结构体成员函数有很多个,下面就选几个常见的来展示

2.1.open

原型
1int(*open)(struct inode *, struct file*);
函数
static int hello_open(struct inode *inode, struct file *file)
-模块使用计数加1
-识别次设备号
-硬件操作:
	·检查设备相关错误(诸如设备未就绪或类似的	硬件问题);
	·如果设备是首次打开,则对其初始化;     
	·如果有中断操作,申请中断处理程序

2.2.release

原型
int(*release)(struct inode *, struct file*);
函数
static int hello_release(struct inode *inode, struct file *file)
-模块使用计数减1
-释放由open分配的,保存在filp>private_data里的所有内容。
-硬件操作:
	·如果申请了中断,则释放中断处理程序。     
	·在最后一次关闭操作时关闭设备。

2.3.read

原型
ssize_t(*read)(struct file *, char __user*, size_t, loff_t*);
函数
ssize_t hello_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
__user *buf:指向用户空间的缓冲区,这个缓冲区或者保存将写入的数据,或者是一个存放新读入数据的空缓冲区。
loff_t *f_pos : 用户在文件中存取操作的位置
内核数据传到用户层数据
unsigned long copy_to_user(void __user *to,  const void *from,  unsigned long count);

2.4.write

原型
ssize_t(*write)(struct file *, const char__user *, size_t, loff_t*);
函数
ssize_t hello_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
__user *buf:指向用户空间的缓冲区,这个缓冲区或者保存将写入的数据,或者是一个存放新读入数据的空缓冲区。
loff_t *f_pos : 用户在文件中存取操作的位置
用户层数据到内核数据传
unsigned long copy_from_user(void *to,  const void __user *from,unsigned long count);

2.5.ioctl

原型
int ioctl(struct inode *inode, 
		  struct file *filp, 
          unsigned int cmd, 
          unsigned long arg);
函数
static int hello_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
 unsigned int cmd:事先定义的IO控制命令 代码
unsigned long arg: arg为对应于cmd命令的参数 

cmd参数定义
在这里插入图片描述

——构造命令编号的宏:
·· _IO(type,nr)用于构造无参数的命令编号;
··_IOR(type,nr,datatype)用于构造从驱动程序中读取数据的命令编号;
··_IOW(type,nr,datatype)用于写入数据的命令;
··_IOWR(type,nr,datatype)用于双向传输。
··type和number位字段通过参数传入,而size位字段通过对datatype参数取sizeof获得。

常用格式:

#define XXX_CMD1 _IOR(‘X’, 1, int)
#define XXX_CMD2 _IOR(‘X’, 2, int)
int xxx_ioctl( struct inode *inode, struct f ile *filp, unsigned int cmd,
unsigned long arg){

switch (cmd){
case XXX_CMD1:

break;
case XXX_CMD2:

break;
default: ///*不能支持的命令 */
return - ENOTTY;
}
return 0;
}

三、驱动注销

注意:有 注册,就一定要对应着注销。且注销跟注册顺序要相反,即先注销设备再class再cdev再设备号

3.1释放设备

原型
void device_destroy(struct class *cls, dev_t devt);
函数
device_destroy(p_hello_class, devno);
内核系统设备驱动程序模型中移除一个设备,并删除/sys/devices/virtual目录下对应的设备目录及/dev/目录下对应的设备文件

3.2释放类class

原型
void class_destroy(struct class *cls)
函数
class_destroy(p_hello_class);
删除类

3.3释放cdev

原型:
void cdev_del(struct cdev *);
例:
cdev_del(&hello_cdev);
在驱动模块卸载函数中通过cdev_del()函数向系统删除一个cdev,使得字符设备的释放。

3.4释放设备号cdev

原型:
void unregister_chrdev_region(dev_t from, unsigned count);
例:
unregister_chrdev_region(devno, 1);
在驱动模块卸载函数中通过unregister_chrdev_region函数向系统删除一个设备号。

四、添加驱动程序到内核

方法:

1、Linux 2.6内核的配置系统由以下3个部分组成。
①Makefile:分布在Linux内核源代码中的Makefite,定义Linux内核的编译规则
②配置文件(Kconfig):给用户提供配置选择的功能。
③配置工具:

a.包括配置命令解释器(对配置脚本中使用的配置命令进行解释)
b.配置用户界面(提供字符界面和图形界面)。
c.这些配置工具都是使用脚本语言编写的,如Tcl/TK、Perl等。

2、在Linux内核中增加程序需要完成以下3项工作。
①将编写的源代码复制到Linux内核源代码的相应目录。
②在目录的Kconfig文件中增加新源代码对应项目的编译配置选项。
③在目录的Makefile文件中增加对新源代码的编译条目

实例:在内核源码drivers/char下新增hello字符驱动

1、把我们编写好的hello.c放到drivers/char目录下
2、修改drivers/char目录下的Kconfig文件,在该文件增加如下语句
config MINI2440_HELLO
tristate “Mini2440 hello”
depends on MACH_MINI2440
default m if MACH_MINI2440
help
Mini2440 hello.
3、修改drivers/char目录下的Makefile文件,在该文件增加如下语句:
obj-$(CONFIG_MINI2440_HELLO) += hello.o
4、进入内核源代码根目录,运行make uImage

五、用户空间调用设备驱动程序

main.c

int main(void){
	int dev_fd;  char read_buf[128] = {0};

	dev_fd = open("/dev/hello",O_RDWR | O_NONBLOCK);
	if ( dev_fd == -1 ) {
		printf("open dev/hello fail=%d\n",dev_fd);
		return -1;
	}
	
	read(dev_fd, read_buf, 20);
	printf("0--read buf = %s\n",read_buf);
	
	strcpy(read_buf,"goodbye,hello");
	write(dev_fd, read_buf, 20);

	memset(read_buf,0x00,sizeof(read_buf));
	read(dev_fd, read_buf, 20);
	printf("1--read buf = %s\n",read_buf);
	
	//ioctl (dev_fd, HELLO_CMD_1,0);
	close(dev_fd);
	return 0;
}

makefile

#KERNELDIR ?=/your_kernel_path/include
#KERNELDIR ?=/imx/linux-2.6.32.2  #本地编译
KERNELDIR ?= /lib/modules/$(shell uname -r)/build  #虚拟编译
all: main 

main : main.c
	gcc -I$(KERNELDIR) -o $@ $^
clean :
	rm main

最后

补充:

$mknod /dev/node_name c major minor
如
mknod /dev/hello c  250 0  

说明:如果通过class_create和device_create
成功创建了设备节点,就不需要上述操作了。

hello.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/major.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <asm/uaccess.h>	/* for VERIFY_READ/VERIFY_WRITE/copy_from_user */

#define BUF_RW_MAX_SIZE 128
#define HELLO_NAME "hello"
#define HELLO_CMD_1 _IOR('h', 1, int)
#define HELLO_CMD_2 _IOW('h', 2, int)

static struct cdev hello_cdev;                  // 字符设设备结构
static struct class * p_hello_class = NULL;			// 设备类 /sys/class
static struct device *p_hello_device = NULL;			// hello的设备结构

static int hello_major = 0;     						// 主设备号变量
static int hello_minor	  = 0;							// 次设备号变量
static int ioctl_value = -1; 
static char buf_rw[BUF_RW_MAX_SIZE] = "hello,world";


/*
 * 模块参数
 */
module_param(hello_major, int, S_IRUGO);
module_param(hello_minor, int, S_IRUGO);

static int hello_open(struct inode *inode, struct file *file)
{
	printk("hello_open\n");
	return 0;
}
/*
 * @brief			读hello设备
 * @param[in]		filp						文件结构
 * @param[in]		count						读数据的字节数	
 * @param[out]		buf							输出数据的缓冲区
 * @param[in|out]	f_pos						文件指针的位置
 * @return			读取的数据量	
 *					@li >= 0					读取的字节数目
 *					@li < 0						错误码
 */
 //ret = read(dev_fd, read_buf, 20);
ssize_t hello_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
	printk("hello_read=%d,%s\n",count,buf_rw);
	
    count %= BUF_RW_MAX_SIZE;
	copy_to_user(buf, buf_rw, count);
	
	return count;
}

/*
 * @brief			写hello设备
 * @param[in]		filp						文件结构
 * @param[in]		count						读数据的字节数	
 * @param[in]		buf							输出数据的缓冲区
 * @param[in|out]	f_pos						文件指针的位置
 * @return			写出结果	
 *					@li >= 0					写入的字节数量
 *					@li < 0						错误码
 */
 //write(fd, buf, len)
ssize_t hello_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
  printk("hello_write= %d,%s\n", count,buf);

  count %= BUF_RW_MAX_SIZE;
	copy_from_user(buf_rw, buf, count);
	
	return count;
}

/*
static int hello_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
	int temp;
	void __user *argp = (void __user *)arg;
	int __user *p = argp;
	
	printk("hello_ioctl:cmd:%x\n",cmd);

	switch(cmd)
	{
		case HELLO_CMD_1:
			temp = ioctl_value;
			put_user(temp, p);
			break;
		case HELLO_CMD_2:
			get_user(temp, p);
			ioctl_value = temp;
			break;
		default:
			return -ENOTTY;
	}
	
	return 0;
}
*/
static int hello_release(struct inode *inode, struct file *file)
{
	
	printk("hello_release\n");
	return 0;
}

static const struct file_operations hello_fops = {
	.owner = THIS_MODULE,
	.open = hello_open,
	.read = hello_read,
	.write = hello_write,
	.release = hello_release,
	//.ioctl = hello_ioctl
};

static int hello_setup_cdev(struct cdev *cdev, dev_t devno)
{
	int ret = 0;

	cdev_init(cdev, &hello_fops);
	cdev->owner = THIS_MODULE;
	ret = cdev_add(cdev, devno, 1);

	return ret;
}

static int __init hello_init(void)
{
	int ret;
	dev_t devno;
	
	printk("hello_init\n");
	
	if(hello_major)
	{
		devno = MKDEV(hello_major, hello_minor);
		ret = register_chrdev_region(devno, 1, HELLO_NAME);
	}
	else
	{
		ret = alloc_chrdev_region(&devno, hello_minor, 1, HELLO_NAME);
		hello_major = MAJOR(devno);
		
	}
	printk("devno:%x, hello_major:%d\n",devno, hello_major);
	
	if(ret < 0)
	{
		printk("get hello_major fail\n");
		return ret;
	}

	ret = hello_setup_cdev(&hello_cdev, devno);
	if(ret)
	{
		printk("hello_setup_cdev fail = %d\n",ret);
		goto cdev_add_fail;
	}
	
	p_hello_class = class_create(THIS_MODULE, HELLO_NAME);
	ret = IS_ERR(p_hello_class);
	if(ret)
	{
		printk("hello class_create fail\n");
		goto class_create_fail;
	}

	//p_hello_device = class_device_create(p_hello_class, NULL, devno, NULL, HELLO_NAME); // 早期内核版本
	p_hello_device = device_create(p_hello_class, NULL, devno, NULL, HELLO_NAME);
	ret = IS_ERR(p_hello_device);
	if (ret)
	{
		printk(KERN_WARNING "hello device_create fail, error code %ld", PTR_ERR(p_hello_device));
		goto device_create_fail;
	}


	return 0;
	
device_create_fail:
	class_destroy(p_hello_class);
class_create_fail:
	cdev_del(&hello_cdev);
cdev_add_fail:
	unregister_chrdev_region(devno, 1);
	
	return ret;
	
}

static void __exit hello_exit(void)
{
	dev_t devno;
	
	printk("hello_exit\n");
	devno = MKDEV(hello_major, hello_minor);
	//class_device_destroy(p_hello_class, devno); // 早期内核版本
	device_destroy(p_hello_class, devno);
	class_destroy(p_hello_class);	
	
	cdev_del(&hello_cdev);
	unregister_chrdev_region(devno, 1);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_AUTHOR("shenrt");
MODULE_DESCRIPTION("hello Driver");
MODULE_LICENSE("GPL");


参考:https://blog.csdn.net/andylauren/article/details/51803331

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值