正在研究linux设备驱动程序,现在把平时的学习心得以笔记的形式发到博客上,方便跟同行们交流与讨论!因为是初学者,对linux的认识还不够深入,所以在博文中会有很多错误,我乃抛砖引玉,请大侠们指教!!
说明:博文的内容主要参考好朋友Tekkaman Ninja同学博客http://blog.chinaunix.net/u1/34474/index.html上的文章。
linux驱动程序学习-字符设备驱动程序(第三章)
一、主设备号和次设备号
对字符设备的访问是通过文件系统内的设备名称进行的。那些名称称为特殊文件、设备文件或者简单称之为文件系统树的节点,它们通常位于/dev目录下。
主设备号:标识设备对应的驱动程序 次设备号:标识确定设备文件所指的设备
同一个主设备号下有不同的从设备号,对应同一类驱动程序下的不同具体设备,如:同属于字符设备的有控制台和串口终端等。
注意理解:主设备号、次设备号、设备文件之间的关系!!
二、设备编号的内部表达
内核用dev_t类型(<linux/types.h>)来保存设备编号,dev_t是一个32位的数,12位表示主设备号,20为表示次设备号。在实际使用中,是通过<linux/kdev_t.h>中定义的宏来转换格式。
如果:想获得主设备号或者次设备号,应使用:MAJOR(dev_t dev)--获得主设备号 MINOR(dev_t dev)--获得次设备号
如果:已知了主设备号与次设备号,想把他转换成dev_t类型,则使用MKDEV(int major,int minor);
三、分配和释放设备编号
在建立一个字符设备之前,驱动程序首先要做的事情是获得一个或者多个设备编号。
有2种情况:一种是在已经知道设备编号的情况下,调用函数分配;一种是先前不知道驱动所需的设备编号,调用函数去分配
第一种情况:调用函数 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); //释放设备编号
四、一些重要的数据结构:
设备编号的注册是驱动程序代码必须完成的许多工作中第一件事情而已,后面还有很多事情等着我们去做呢!!大部分基本的驱动程序操作涉及到三个重要的内核数据结构,分别是file_operations、file、inode。下面详细阐述:
struct file_operations fops 设备驱动程序接口
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char *, size_t, 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);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
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);
};
上面结构体内的每个字段大部分是函数指针,这些函数指针指向驱动程序实现具体操作的函数。我们可以看到上面的指针所指向函数的参数里面有一种结构体很常见:struct file 还有struct inode。
下面来分析struct file:file结构与用户空间中的FILE没有任何关联,struct file是一个内核结构,他不会出现在用户程序中。file结构代表一个打开的文件,是由内核在open时创建的,并传递给在该文件上进行操作的所有函数,直到最后的close函数,在文件的所有实例都被关闭之后,内核会释放掉这个数据结构。在这个数据结构中有一个重要的字段:struct file_operations *f_op,内核在执行open操作时,对这个指针赋值,以后需要处理这些操作时就读取这个指针。filp->f_op中的值决不会为了方便引用而保存起来,也就是说,我们可以在任何时候修改文件的关联操作,在返回给调用者之后,新的操作方法立即生效。例如:对应于主设备号1的open代码根据要打开的次设备号替换filp->f_op中的操作。注意:也就是说,struct file与struct file_operations这2个结构体是通过这样的方式进行相关联的。
inode结构:内核用inode结构在内部表示文件,因此它和file结构不同,后者表示打开的文件描述符。对于单个文件,可能会有许多个表示打开的文件描述符的file结构。但他们都指向单个inode结构。对于编写驱动程序,只有2个字段比较常用:dev_t i_rdev; struct cdev *i_cdev;
struct cdev表示字符设备的内核的内部结构。当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针。
内核内部使用struct cdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或者多个上述结构。
注册一个独立的cdev设备的基本过程如下:
1、为struct cdev 分配空间(如果已经将struct cdev 嵌入到自己的设备的特定结构体中,并分配了空间,这步略过!)
struct cdev *my_cdev = cdev_alloc();
2、初始化struct cdev
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
3、初始化cdev.owner
cdev.owner = THIS_MODULE;
4、cdev设置完成,通知内核struct cdev的信息(在执行这步之前必须确定你对struct cdev的以上设置已经完成!)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
从系统中移除一个字符设备:void cdev_del(struct cdev *p)
/*
* Set up the char_dev structure for this device.
*/
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;
//这句可以省略,在cdev_init中已经做过
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be 这步值得注意*/
if
(err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}
scull模型的结构体:
/*
* Representation of scull quantum sets.
*/
struct scull_qset {
void
**data;
struct scull_qset *next;
};
struct scull_dev {
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 */