[读书笔记]字符设备驱动程序(第三章)

一、综述 本章开始,我们就要学会写一个最基本的字符设备驱动了,麻雀虽小五脏俱全。这是本章的重点,首先要弄明白一些基本的数据结构等相关知识,在去尝试编写驱动。
知识点 1.主设备号和次设备号
a.
一个主设备 对应 一个驱动程序
b.
次设备号 确定 具体的设备
c.
dev_t(在linux/types.h中)保存设备编号,
前12位 - 主设备号
后20位 - 次设备号
MAJOR(dev_t dev):获取主设备号
MANOR(dev_t dev) : 获得次设备号
MKDEV(int major,int minor) : 主设备号和次设备号转换成dev_t
d.
分配和释放设备编号
静态分配: int register_chrdev_region(dev_t first,unsigned int count ,char *name);
first:起始编号,通常为0
count: 连续请求的设备编号个数
name:设备名称,将会出现在 /proc/devices和sysfs中
成功:返回0
失败:返回负数
动态分配
int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name)
dev:仅用于输出的参数,在成功完成调用后将班车已分配范围的第一个编号
firstminor:第一个次设备号,通常为0
count和那么跟上面的一样
e.释放函数
释放函数:void unregister_chrdev_region(dev_t first,unsigned int count)
2.字符设备的注册
方式一:注册独立的cdev结构
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_ops;
方式2:将cdev内嵌到自己的设备结构中
调用以下函数来初始化
void cdev_init(strcuct cdev *cdev,struct file_operations *fops);
指定cdev的所有者和操作函数
cdev.ower = THIS_MODULE;
cdev.ops = &my_ops;
把字符设备添加到内核
int cdev_add(struct cdev *dev,dev_t num,unsigned int count);
在适当的地方删除
void cdev_del(struct cdev *dev);
3.三个重要的数据结构
struct file_operations:保存操作字符驱动程序的方法
strcut file:表示打开一个文件
strucr inode:表示一个磁盘上的文件
大多数设备驱动程序都会用到这三个数据结构
其他知识点
#include
container_of(pointer,type,field)
一个方便使用的宏,可用于从包含在某个数据结构中的指针获得结构体本身的指针。
#include
该头文件声明了内核代码和用户空间之间移动数据的函数
unsigned long copy(void *to,const void *from,unsigned long count);
unsigned long copy(void *to,const void *from,unsigned long count);
用户空间和内核空间拷贝数据
实践环节--在安卓系统中编写字符驱动 编写一个名字为hello的字符驱动设备(参照书本和罗升阳博客),自己跟着一遍遍敲打出来,还是会遇到很多编译错误,也学习到很多东西,自己动手,丰衣足食,理解也更深!
学习的过程中,大家别跟着代码从头敲到尾,要按照思路来写,比如我先定义一些需要的变量和方法,然后先写一些空方法,把框架搭好,module_init(),module_exit()等相关方法
再去传统操作设备的方法,接着再去写devfs文件系统的方法
有思路的去写!!!
有些东西已经抛弃了
init_MUTEX(),在内核2.6版本就抛弃了,取而代之的是sema_init();
具体实现 kernel-3.18/drivers/misc/mediatek/创建hello目录
kernel-3.18/drivers/misc/mediatek/hello/
--hello.h
--hello.c
--Kconfig
--Makefile
hello.h

#include #include //节点名称 #define HELLO_DEVICE_NODE_NAME "hello"//定义hello_dev结构体 struct hello_dev { int val; //变量--模拟寄存器 struct semaphore sem; //信号量 互斥访问 防止竞态 struct cdev dev; //字符设备 };

hello.c 引入头文件,定义相关的方法
#include #include #include #include #include #include #include #include #include #include "hello.h"/*主设备号 和 从设备号 变量*/ static int hello_major = 0; static int hello_minor = 0; /*设备类别和设备变量*/ static struct class *hello_class = NULL; static struct hello_dev* hello_dev = NULL; /*传统的设备文件操作方法*/ static int hello_open(struct inode *inode,struct file *filp); static int hello_release(struct inode *inode,struct file *filp); static ssize_t hello_read(struct file *filp,char __user *buf, size_t count,loff_t *f_ops); static ssize_t hello_write(struct file *filp,const char __user *buf,size_t count,loff_t *f_ops); /*设备文件操作方法表*/ static struct file_operations hello_fops = { .owner = THIS_MODULE, .open = hello_open, .release = hello_release, .write = hello_write,}; /*访问设置属性方法*/ static ssize_t hello_val_show(struct device *dev,struct device_attribute *attr,char *buf); static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count); /*定义设备属性*/ static DEVICE_ATTR(val,S_IRUGO | S_IWUSR,hello_val_show,hello_val_store);

传统的设备文件操作方法
定义传统的设备文件访问方法,主要是定义hello_open、hello_release、hello_read和hello_write这四个打开、释放、读和写设备文件的方法:
/*打开设备方法*/ static int hello_open(struct inode *inode,struct file *filp) { struct hello_dev *dev; dev = container_of(inode->i_cdev,struct hello_dev,dev); filp->private_data = https://www.it610.com/article/dev; return 0; } /*是否设备方法 这里空实现*/ static int hello_release(struct inode *inode,struct file * filp) {return 0; } /*读取寄存器设备 val的值*/ static ssize_t hello_read(struct file *filp,char __user *buf, size_t count,loff_t *f_ops) { ssize_t err = 0; struct hello_dev *dev = filp->private_data; /*同步访问*/ if(down_interruptible(&(dev->sem))); return -ERESTARTSYS; if(count < sizeof(dev->val)){ goto out; } /*将寄存器val的值拷贝到用户提供的缓存区*/ if(copy_to_user(buf,&(dev->val),sizeof(dev->val))){ err = -EFAULT; goto out; }out: up(&(dev->sem)); return err; }/*写设备寄存器val的值*/ return 0; } /*读取寄存器设备 val的值*/ static ssize_t hello_read(struct file *filp,char __user *buf, size_t count,loff_t *f_ops) { ssize_t err = 0; struct hello_dev *dev = filp->private_data; /*同步访问*/ if(down_interruptible(&(dev->sem))); return -ERESTARTSYS; if(count < sizeof(dev->val)){ goto out; } /*将寄存器val的值拷贝到用户提供的缓存区*/ if(copy_to_user(buf,&(dev->val),sizeof(dev->val))){ err = -EFAULT; goto out; }out: up(&(dev->sem)); return err; }/*写设备寄存器val的值*/ static ssize_t hello_write(struct file *filp,const char __user *buf, size_t count,loff_t * f_ops){ ssize_t err = 0; struct hello_dev *dev = filp->private_data; /*同步访问*/ if(down_interruptible(&(dev->sem))){ return -ERESTARTSYS; }if(count != sizeof(dev->val)) { goto out; } /*将用户提供的缓存区的值写到设备寄存器中去*/ if(copy_from_user(&(dev->val),buf,count)){ err = EFAULT; goto out; }err = sizeof(dev->val); //成功写入的数据大小out: up(&(dev->sem)); return err; } /* 字符设备注册和初始化的方式 方式2:将cdev内嵌到自己的设备结构中 调用以下函数来初始化 void cdev_init(strcuct cdev *cdev,struct file_operations *fops); 指定cdev的所有者和操作函数 cdev.ower = THIS_MODULE; cdev.ops = &my_ops; 把字符设备添加到内核 } /*写设备寄存器val的值*/ static ssize_t hello_write(struct file *filp,const char __user *buf, size_t count,loff_t * f_ops){ ssize_t err = 0; struct hello_dev *dev = filp->private_data; /*同步访问*/ if(down_interruptible(&(dev->sem))){ return -ERESTARTSYS; }if(count != sizeof(dev->val)) { goto out; } /*将用户提供的缓存区的值写到设备寄存器中去*/ if(copy_from_user(&(dev->val),buf,count)){ err = EFAULT; goto out; }err = sizeof(dev->val); //成功写入的数据大小out: up(&(dev->sem)); return err; }

devfs文件系统访问方法
定义通过devfs文件系统访问方法,这里把设备的寄存器val看成是设备的一个属性,通过读写这个属性来对设备进行访问,主要是实现hello_val_show和hello_val_store两个方法,同时定义了两个内部使用的访问val值的方法__hello_get_val和__hello_set_val
//---------------------------方式2---------------------------- /*读取寄存器val的值到缓存区buf中 内部使用*/ static ssize_t __hello_get_val(struct hello_dev *dev,char *buf) { int val = 0; /*同步访问*/ if(down_interruptible(&(dev->sem))){ return -ERESTARTSYS; }val = dev->val; up(&(dev->sem)); return snprintf(buf,PAGE_SIZE,"%d\n",val); } /*把缓冲区buf的值写到设备寄存器val中去 内部使用*/ static ssize_t __hello_set_val(struct hello_dev *dev,const char *buf,size_t count){ int val = 0; //把字符串转换成数字 val = simple_strtol(buf,NULL,10); //同步访问 if(down_interruptible(&(dev->sem))){ return -ERESTARTSYS; }dev->val = val; up(&(dev->sem)); return count; }/*读取设备属性 val*/ static ssize_t hello_val_show(struct device *dev,struct device_attribute *attr,char *buf){ struct hello_dev *hdev = (struct hello_dev*)dev_get_drvdata(dev); return __hello_get_val(hdev,buf); } /*写设备属性val*/ static ssize_t hello_val_store(struct device* dev, struct device_attribute* attr, const char* buf, size_t count){ struct hello_dev *hdev = (struct hello_dev*)dev_get_drvdata(dev); return __hello_set_val(hdev,buf,count); }

定义模块加载和卸载方法,这里只要是执行设备注册和初始化操作
/*初始化设备*/ static int hello_setup_dev(struct hello_dev *dev){ int err; dev_t devno = MKDEV(hello_major,hello_minor); //先清空dev,书上没有调用 memset(dev,0,sizeof(struct hello_dev)); //调用cdev_init初始化,接着绑定操作设备的方法 cdev_init(&(dev->dev),&hello_fops); dev->dev.owner = THIS_MODULE; dev->dev.ops = &hello_fops; //注册字符设备 err = cdev_add(&(dev->dev),devno,1); if(err) return err; //初始化信号量和寄存器的值 sema_init(&(dev->sem),1); dev->val = 0; return 0; }/*模块加载方法*/ static int hello_init(void){ int err = -1; dev_t dev = 0; struct device* temp = NULL; printk("[%s] is init\n",__func__); //动态分配主设备号和从设备好 err = alloc_chrdev_region(&dev,0,1,HELLO_DEVICE_NODE_NAME); if(err < 0){ printk("faile to alloc_chrdev_region()\n"); goto fail; } hello_major = MAJOR(dev); hello_minor = MINOR(dev); //分配内存 hello_dev = kmalloc(sizeof(struct hello_dev),GFP_KERNEL); if(!hello_dev){ err = -ENOMEM; printk("faile to alooc hello_dev\n"); goto unregister; }//初始化设备 err = hello_setup_dev(hello_dev); if(err){ printk("failed to set up hello device\n"); goto cleanup; }//在 /sys/class目录下创建设备类别目录hello hello_class = class_create(THIS_MODULE,"hello"); if(IS_ERR(hello_class)){ err = -1; printk(KERN_ALERT"Failed to create hello class.\n"); goto destroy_cdev; } //在dev目录 和 sys/class/hello目录下分别创建设备文件hello temp = device_create(hello_class,NULL,dev,NULL,"hello"); if(IS_ERR(temp)){ err = -2; printk(KERN_ALERT"Failed to create hello device."); goto destroy_class; }//在 sys/class/hello/hello目录下创建属性文件valerr = device_create_file(temp,&dev_attr_val); if(err < 0){ printk(KERN_ALERT"Failed to create attribute val."); goto destroy_device; }dev_set_drvdata(temp,hello_dev); printk("success to init hello device\n"); return 0; destroy_device: device_destroy(hello_class, dev); destroy_class: class_destroy(hello_class); destroy_cdev: cdev_del(&(hello_dev->dev)); cleanup: kfree(hello_dev); unregister: unregister_chrdev_region(MKDEV(hello_major,hello_minor),1); fail: return err; }/*模块卸载方法*/ static void hello_exit(void){ dev_t devno = MKDEV(hello_major,hello_minor); printk("destroy hello device \n"); //销毁设备类别和设备 if(hello_class){ device_destroy(hello_class,MKDEV(hello_major,hello_minor)); class_destroy(hello_class); }//删除字符设备和释放设备内存 if(hello_dev){ cdev_del(&(hello_dev->dev)); kfree(hello_dev); }//释放设备号 unregister_chrdev_region(devno,1); }MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("First Andorid Driver"); module_init(hello_init); module_exit(hello_exit);

Kconfig
config HELLO tristate "First Android Driver" default n help This is my first android driver.

Makefile
obj-y += hello.o

接着
kernel-3.18/drivers/misc/mediatek/Kconfig添加一句
source "drivers/misc/mediatek/hello/Kconfig"
kernel-3.18/drivers/misc/mediatek/Makefile添加一句
obj-y += hello/
最后 使用编译命令 ./mk bootimage
把生成的bootimage刷机即可

[读书笔记]字符设备驱动程序(第三章)
文章图片
out 结果 进入到dev目录,可以看到hello设备文件:

[读书笔记]字符设备驱动程序(第三章)
文章图片
adb 进入到sys/class目录,可以看到hello目录,hello文件,
进入到下一层hello目录,可以看到val文件:

[读书笔记]字符设备驱动程序(第三章)
文章图片
adb 重启问题 [读书笔记]字符设备驱动程序(第三章)
文章图片
log
static int hello_init(void){ int err = -1; dev_t dev = 0; printk("[%s] is init\n",__func__); //动态分配主设备号和从设备好 err = alloc_chrdev_region(dev,0,1,HELLO_DEVICE_NODE_NAME); //省略代码 }

【[读书笔记]字符设备驱动程序(第三章)】问题出在alloc_chrdev_region()这里,原来发现第一个参数dev需要的是地址,导致了重启
修改:alloc_chrdev_region(&dev,0,1,HELLO_DEVICE_NODE_NAME);

    推荐阅读