linux设备驱动学习(3) 字符设备驱动程序

时间:2023-01-03 22:50:18

主设备号,次设备号

主设备号表示设备对应的驱动程序;次设备号由内核使用,用于正确确定设备文件所指的设备。
内核用dev_t类型(<linux/types.h>)来保存设备编号,dev_t是一个32位的数,12位表示主设备号,20为表示次设备号。
在实际使用中,是通过<linux/kdev_t.h>中定义的宏来转换格式。

 (dev_t)-->主设备号、次设备号  MAJOR(dev_t dev)
 MINOR(dev_t dev)
 主设备号、次设备号-->(dev_t)  MKDEV(int major,int minor) 

 

分配和释放设备编号

#include<linux/fs.h>

静态指定 int register_chrdev_region(dev_t first,unsigned int count,char *name);
动态分配 int alloc_chrdev_region(dev *dev,unsigned int firstminor,unsigned int count,char *name);
释放设备号 void unregister_chrdev_region(dev_t first,unsigned int count);

分配之设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。

以下是在scull.c中用来获取主设备好的代码:

if(scull_major)

{

dev=MKDEV(scull_major,scull_minor);

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 not get scull major %d",scull_major);

return result;

}

在这部分中,比较重要的是在用函数获取设备编号后,其中的参数name是和该编号范围关联的设备名称,它将出现在/proc/devicessysfs中。

看到这里,就可以理解为什么mdev和udev可以动态、自动地生成当前系统需要的设备文件。
udev就是通过读取sysfs下的信息来识别硬件设备的.

一些重要的数据结构

file_operations,file,inode

大多数设备驱动程序都会用到的三个重要数据结构。file_operations结构保存了字符驱动程序的方法,struct file表示一个打开的文件,而struct inode表示一个磁盘上的文件。

 

字符设备的注册

1.获取一个cdev设备

struct cdev *my_dev = cdev_alloc();

my_cdev->ops = &my_fops;

2.初始化分配到的结构

void cdev_init(struct cdev *cdev,struct file_operation *fops);

另外还有一个struct cdev的字段需要初始化。和file_operations结构类似,struct cdev也有一个所有者字段,应被设置为THIS_MODULE。

cdev.owner = THIS_MODULE

3.cdev设置好之后,最后的步骤是通过下面的调用告诉内核该结构的信息:

int cdev_add(struct cdev *dev,dev_t num,unsigend int count);

dev是cdev结构,num是该设备对应的第一个设备 编号,count是应该和设备关联的设备编号的数量。

4.移除一个字符设备

void cdev_del(struct cdev *dev);

 

以下为scull中的设备注册

struct scull_dev{

struct scull_qset *data;

int quantum;

int qset;

unsigned long size;

unsigend int access_key;

struct semaphore sem;

struct cdev 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->cev,devno,1);

/*faile gracefully if need be*/

if(err)

printk(KERN_NOTICE "error %d adding scull %d",err,index);

}

open和release

open方法

open方法提供给驱动程序以初始化的能力,从而为以后的操作完成初始化做准备。在大部分驱动程序中,open应完成以下工作:

1.检查设备特定的错误(诸如设备未就绪,或类似的硬件问题)

2.如果设备是首次打开,则对其进行初始化

3.如有必要,更新f_op指针

4.分配并填写置于filp->private_data里的数据结构

 

open方法原型:int (*open)(struct inode *inode,struct file *filp)

其中的inode参数在其i_cdev字段中包含了我们所需要的信息,即我们先前设置的cdev结构。唯一的问题是,我们通常不需要cdev结构本身,而是希望得到包含cdev结构的scull_dev结构。c语言可帮助程序员通过一些技巧完成这类转换,但不应滥用这类技巧。幸运的是,在这种情况下我们已经实现了这类技巧,它通过定义在<linux/kernel.h>中的container_of宏实现:

container_of(pointer,container_type,container_field);

这个宏要一个container_field字段的指针,该字段包含在container_type类型的结构中,然后返回包含该字段的结构指针。

在scull_open中,这个宏用来找到适当的设备:

struct scull_dev *dev;

dev = container_of(inode->i_cdev,struct scull_dev,cdev);

filp->private_data = dev; /*for other methods*/

而根据scull的实际情况,他的open函数只要完成第四步(将初始化过的struct scull_dev dev的指针传递到filp->private_data里,以备后用)就好了,所以open函数很简单。但是其中用到了定义在<linux/kernel.h>中的container_of宏,源码如下:

#define container_of(ptr,
type, member)
(
{            \
    const typeof(
((type
*
)0)->member
) *__mptr
=
(ptr);    \
    (type *)(
(char
*
)__mptr -
offsetof(type,member)
);})

其实从源码可以看出,其作用就是:通过指针ptr,获得包含ptr所指向数据(是member结构体)type结构体的指针。即是用指针得到另外一个指针。

 

release方法提供释放内存,关闭设备的功能。应完成的工作如下:

(1)释放由open分配的、保存在file->private_data中的所有内容;

(2)在最后一次关闭操作时关闭设备。

由于前面定义了scull是一个全局且持久的内存区,所以他的release什么都不做。

 

 

read和write

 read和write方法的主要作用就是实现内核与用户空间之间的数据拷贝。因为Linux的内核空间和用户空间隔离的,所以要实现数据拷贝就必须使用在<asm/uaccess.h>中定义的

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);

而值得一提的是以上两个函数和

#define __copy_from_user(to,from,n)    (memcpy(to,
(void __force
*
)from, n), 0)
#define __copy_to_user(to,from,n)    (memcpy((void __force *)to, from, n), 0)

之间的关系:通过源码可知,前者调用后者,但前者在调用前对用户空间指针进行了检查。

至于read和write 的具体函数比较简单,就在实验中验证好了。