LDD字符驱动学习
来源:互联网 发布:淘宝大学官网 编辑:程序博客网 时间:2024/05/20 18:42
字符驱动程序
scull ,即“simple character utility for loading localities, 区域装载的简单字符工具’”。
是一个操作内存区域的字符设备驱动程序,这片内存区域就相当于一个字符设备。
字符设备的执行流程
获取设备号 -> 注册设备 -> 关联File operations结构 -> open(打开设备) -> write ->read -> release资源 -> close(关闭设备)
主设备号与次设备号
主设备号标识设备对应的驱动程序,次设备号由内核使用,用于正确确定设备文件所指的设备。
设备编号
在内核中,dev_t类型用来保存设备编号,在
#include <linux/kedv_t.h>MAJOR(dev_t dev);MINOR(dev_t dev);
如果需要将主设备号和次设备号转换成dev_t类型,则使用:
MKDEV(int major, int minor);
分配和释放设备编号
在建立字符设备之前,驱动程序首先要做的是获得一个或多个设备的编号。完成该工作的必要函数是register_chrdev_region,该函数在
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, unsigned int firstminor, unsigned int count, char *name);
dev 是一个只输出的参数, 它在函数成功完成时持有你的分配范围的第一个数。 fisetminor 应当是请求的第一个要用的次编号;它常常是 0。count 和 name 参数如同给 request_chrdev_region 的一样。
为了防止选定的设备号出现冲突和麻烦,驱动程序应尽量使用动态分配而不是静态分配。
不管哪种方法,不使用时需要释放设备号,函数如下:
void unregister_chrdev_region(dev_t first, unsigned int count);
通常我们会在模块的清除函数中调用unregister_chrdev_region函数。
由于动态分配的主设备号无法预先创建设备节点,所以需要从/proc/devices中读取得到。可以将对insmod的调用替换成一个脚本,该脚本在调用insmod之后读取/proc/devices以获得分配的主设备号,然后创建对应的设备文件。其脚本为:
#!/bin/shmodule="scull"device="scull"mode="664"# 使用传入到该脚本的所有参数调用insmod,同时使用路径名来指定具体模块位置# 这是因为新的modutiles默认不会在当前目录中查找模块/sbin/insmod ./$module.ko $* || exit 1# 删除原有节点rm -f /dev/${device}[0-3]major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices)mknod /dev/${device}0 c $major 0mknod /dev/${device}1 c $major 1mknod /dev/${device}2 c $major 2mknod /dev/${device}3 c $major 3# 给予适当的组属性许可,并修改组# 并非所有的发行版都具有staff组,有些有wheel组group="staff"grep -q '^staff:' /etc/group || group="wheel"chgrp $group /dev/${device}[0-3]chmod $mode /dev/${device}[0-3]
分配主设备号的最佳方式是:默认采用动态分配, 同时保留在加载甚至是编译时批定设备号的余地。常用的获取主设备号的代码为:
if (scull_major){ dev = MKDEV(scull_major, scull_minor); result = register_chrdev_region(dev, scull_nr_devs, "scull");} else { result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull"); scull_major = MAJOR(dev);}if (result < 0){ printk(KERN_WARNING "scull: can't get major %d\n", scull_major); return result;}
数据结构
file_operation结构
struct file_operations scull_fops = { .owner = THIS_MODULE, .llseek = scull_llseek, .read = scull_read, .write = scull_write, .ioctl = scull_ioctl, .open = scull_open, .release = scull_release,};
这种结构体初始化定义是C语言最新C99标准,称为指定初始化(designated initializer)。采用这种方式的优势就在于由此初始化不必严格按照定义时的顺序。这带来了极大的灵活性。
file结构
此file结构不是用户空间中的FILE,两者无关系,FILE是在C库中定义的,不会出现在内核代码中,而struct file是一个内核结构,它不会出现在用户程序中。file结构代表一个打开的文件,同open创建,直到最后的close之后才释放这个结构,指向struct file的指针为file和filp,其中file指的是结构本身,filp指向该结构的指针。
inode结构
内核用inode结构在内部表示文件,和file结构不同,file表示打开的文件描述符,对单个文件,可能会有许多个表示打开的文件描述符的file结构,但它们都指向单个inode结构。
字符设备注册
struct cdev结构表示字符设备,获取一个独立的cdev结构并嵌入到自己的设备特定结构中,其代码如下:
struct cdev *my_cdev = cdev_alloc();my_cdev->ops = &my_fops;
之后初始化已分配到的结构:
void cdev_init(struct cdev *cdev, struct file_operations *fops);
cdev的字段需有初始化,它有一个所有者字段,应该设置成THIS_MODULE。
cdev设置好后,通过下面代码告诉内核该结构的消息:
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
dev 是 cdev 结构,num 是这个设备对应的第一个设备号,count 是应当关联到设备的设备号的数目.通常是1, 但在某些情况下,会有多个设备号对应于一个特定的设备。
如果要移除一个字符设备,做如下调用:
void cdev_del(struct cdev *dev);
注册实例,scull:
struct scull_dev { //quantum,量子,一个内存区称为一个量子,而这个指针数组称为量子集 struct scull_qset *data; /* Pointer to first quantum set */ int quantum; /* the current quantum size */ int qset; /* the current array size */ unsigned long size; /* amount of data stored here */ unsigned int access_key; /* used by sculluid and scullpriv */ struct semaphore sem; /* mutual exclusion semaphore */ struct cdev cdev; /* Char device structure */};
内核和设备间的接口struct cdev必须如上所述的被初始化并添加到系统中,其代码如下:
static void scull_setup_cdev(struct scull_dev *dev, int index){ int err, devno = MKDEV(scull_major, scull_minor + index); cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; err = cdev_add (&dev->cdev, devno, 1); /* Fail gracefully if need be */ if (err) printk(KERN_NOTICE "Error %d adding scull%d", err, index);}
open和release
open原型:
int (*open)(struct inode *inode, struct file *filp);
其作用为:
- 检查设备特定的错误(例如设备没准备好, 或者类似的硬件错误
- 如果它第一次打开, 初始化设备
- 如果需要, 更新 f_op 指针.
- 分配并填充要放进 filp->private_data 的任何数据结构
scull_open代码:
int scull_open(struct inode *inode, struct file *filp){ struct scull_dev *dev; /* device information */ dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; /* for other methods */ /* now trim to 0 the length of the device if open was write-only */ if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { scull_trim(dev); /* ignore errors */ } return 0; /* success */}
release方法作用与open相反,该方法实现被称为device_close或device_release,作用如下:
- 释放 open 分配在 filp->private_data 中的任何东西
- 在最后的 close 关闭设备
scull中的release代码:
int scull_release(struct inode *inode, struct file *filp){ return 0;}
并不是每个close系统调用都会引起release方法的调用,只有真正释放设备数据的close调用才会调用这个方法,所以scull中的release代码这么少。
read和write
read和write原型相似,如下 :
ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
filp 是文件指针,count 是请求的传输数据大小, buff 参数指向用户空间的缓存区, 最后, offp 是一个指针指向一个”long offset type,长偏移类型”对象, 它指出用户正在存取的文件位置. 返回值是一个”signed size type,有符号的尺寸类型”;
大多数read和write方法实现的核心部分如下,用于拷贝任意的一段字节序列。
unsigned long copy_to_user(void __user *to,const void *from,unsigned long count);unsigned long copy_from_user(void *to,const void __user *from,unsigned long count);
这两个函数的作用并不限于在内核空间和用户空间之间拷贝数据,它们还检查用户空间的指针是否有效。
快速参考
#include <linux/types.h>dev_tdev_t 是用来在内核里代表设备号的类型.int MAJOR(dev_t dev);int MINOR(dev_t dev);从设备编号中抽取主次编号的宏.dev_t MKDEV(unsigned int major, unsigned int minor);从主次编号来建立 dev_t 数据项的宏定义.#include <linux/fs.h>"文件系统"头文件是编写设备驱动需要的头文件. 许多重要的函数和数据结构在此定义.int register_chrdev_region(dev_t first, unsigned int count, char *name)int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name)void unregister_chrdev_region(dev_t first, unsigned int count);允许驱动分配和释放设备编号的范围的函数. register_chrdev_region 应当用在事先知道需要的主编号时; 对于动态分配, 使用alloc_chrdev_region 代替.int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);老的( 2.6 之前) 字符设备注册函数. 它在 2.6 内核中被模拟, 但是不应当给新代码使用. 如果主编号不是 0, 可以不变地用它; 否则一个动态编号被分配给这个设备.int unregister_chrdev(unsigned int major, const char *name);恢复一个由 register_chrdev 所作的注册的函数. major 和 name 字符串必须包含之前用来注册设备时同样的值.struct file_operations;struct file;struct inode;大部分设备驱动使用的 3 个重要数据结构. file_operations 结构持有一个字符驱动的方法; struct file 代表一个打开的文件, struct inode 代表磁盘上的一个文件.#include <linux/cdev.h>struct cdev *cdev_alloc(void);void cdev_init(struct cdev *dev, struct file_operations *fops);int cdev_add(struct cdev *dev, dev_t num, unsigned int count);void cdev_del(struct cdev *dev);cdev 结构管理的函数, 它代表内核中的字符设备.#include <linux/kernel.h>container_of(pointer, type, field);一个传统宏定义, 可用来获取一个结构指针, 从它里面包含的某个其他结构的指针.#include <asm/uaccess.h>这个包含文件声明内核代码使用的函数来移动数据到和从用户空间.unsigned long copy_from_user (void *to, const void *from, unsigned long count);unsigned long copy_to_user (void *to, const void *from, unsigned long count);在用户空间和内核空间拷贝数据
- LDD字符驱动学习
- LDD之字符设备驱动
- 字符设备驱动学习
- 字符驱动学习
- LDD chapter2:编译简单驱动
- LDD之USB设备驱动
- 学习ldd的过程:)
- LDD学习课程之一
- ldd学习准备
- Linux驱动学习笔记;字符设备驱动
- linux字符设备驱动学习
- Linux字符设备驱动学习
- Linux驱动学习字符设备
- 字符设备驱动学习笔记
- linux学习--字符设备驱动
- 字符设备驱动学习总结
- LINUX字符设备驱动学习
- ldd(linux设备驱动程序)scull驱动
- 幼儿园都教什么?
- 微信自动回复和自动抢红包实现原理(一):AccessibilityService的介绍和配置
- SessionFactory (org.hibernate.SessionFactory)
- HDU 5152 线段树+欧拉函数
- tjut 2889
- LDD字符驱动学习
- 存储过程和函数的区别
- Android 开发资源收集(开源项目、教程等)
- Spring Tool Suite(sts)中安装svn插件简介
- 微信自动回复和自动抢红包实现原理(二):自动回复
- ubuntu 14.04 安装 gcc 6.1 心得
- 各种位置和高度计算:.position()、.offset()、.outerHeight()、.scrollTop、.scrollHeight、.clientHeight
- 2016.08.13【初中部 NOIP提高组 】模拟赛C总结
- JS实现简单的显示时间