Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1977914
  • 博文数量: 610
  • 博客积分: 11499
  • 博客等级: 上将
  • 技术积分: 5511
  • 用 户 组: 普通用户
  • 注册时间: 2008-03-12 19:27
文章分类

全部博文(610)

文章存档

2016年(5)

2015年(18)

2014年(12)

2013年(16)

2012年(297)

2011年(45)

2010年(37)

2009年(79)

2008年(101)

分类: LINUX

2008-04-10 23:48:11




笔记:Linux Device Driver 3   第三章     字符设备SCULL

 

笔记原创: 山涛
联系邮件: epost_guo@126.com
说明:本文档供学习交流使用,我是学习者,文档属于自己的体会,看本文档一分为二。希望大家共同交流,共同进步!


一,

SCULLSimple Character Utility for Loading Localities

SCULL驱动模块操作PC上的一块内存,它是一个char设备。


二,

SCULL的主设备号和次设备号:

主设备号标志着一个与设备相关的驱动;现代Linux内核允许多个驱动共享一个主设备号,但是绝大多数的设备仍然遵循着“一个驱动对应一个主设备号”的原则。

次设备号被内核使用,用于区分具体的逻辑设备。

(个人理解,对一个设备而言,由于设备有不同的功能和配置,按功能和配置划分的话,一个设备相当于若干个子设备的集合;驱动实现了所有这些子设备的功能,但是当内核被告知使用其中的某一个子设备时,就需要使用次设备号来区分)

设备号,类型是:dev_t,它由“主设备号+次设备号”组成(dev_t类型32位,12位用于主设备号,20位用于次设备号)。


分配一个设备号:

静态分配设备号函数原型:

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


建议使用动态分配设备函数;使用该函数的缺点是不能预先建立设备节点(在/dev下),因为你不知道动态分配的号到底是多少。不过,这个缺点很容易解决,即当驱动加载成功后,你通过读取/proc/devices得知该驱动所分配到的设备号。

三,

一些重要的结构(这些结构均只能在内核中使用)

file_operations:表示文件操作结构。

file:表示一个打开的文件。

inode:表示一个文件。

fileinode的区别在于:一个文件可以被打开多次,就会有多个file结构;而一个文件只对应一个inode结构。

四,

内核使用struct cdev来表示内部的字符设备。在一般的字符设备程序中,cdev结构被嵌入到特定的设备结构中,成为其中的一个域。Cdev结构的初始化使用下面的函数原型:

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

cdev域被初始化后,最后的一步就是将它的信息告知内核(这就是所谓的注册),调用如下的函数原型:

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

在调用cdev_add()之前,要确保驱动的初始化完成,对设备的操作已经完全准备好。

从内核释放字符设备的函数原型如下:

void cdev_del(struct cdev *dev);

五,

open方法的功能:

(1)检查设备相关的错误;

2)第一次打开设备时,初始化设备;

3)如果需要,更新f_op指针;

4)分配和填充设备使用的数据结构,将它存入filp->privat_data域中。

Open方法的函数原型:

int (*open) (struct inode *inode, struct file *filp);


release方法的功能:

1)释放任何在filp->private_data中所分配的数据结果;

2)关闭设备。

Release方法的函数原型:

int (*release)(struct inode *inode, struct file *filp);

六,

SCULL的内存使用

SCULL以一片系统内存作为一个字符设备,对其进行操作。SCULL的内存使用是很有趣的。

代表SCULL设备的数据结构是scull_devscull_dev通过一个链表来管理内存,链表的具体结构为scull_qset,每个scull_qset指向一块内存区域:这块内存区域被分成1000份,每份4000个字节。所以,每份称为quantum,份数成为quantum set,其实份数就是指针了。 SCULL设备的规划如下图:

这个内存使用模型是以scull_qset结构为单位的,当你向SCULL中写入一个byte的数据时,SCULL需要分配给你一个scull_qet结构,这至少需要1000个指针(占用4000字节或8000字节,这取决于平台是32位还是64位)和一个quantum4000字节)。所以写一个字节对于这样一个内存使用模型的开销是很大的。SCULL内存使用模型并没有限制最大使用内存的数目,你可以用完你的机器上所有的实际内存。SCULL只限制了一个scull_qset可以容纳4000x1000大小的内存。

七,

读方法函数原型:

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


内核读/写方法需要与用户进行数据交换的函数,原因是上面的原型中的buff参数是用户空间的地址,内核空间不能直接访问用户空间的数据变量。因此,内核和用户空间交换数据时,需要两个辅助函数,原型如下:

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, unsinged long count);

这两个函数会首先检查用户空间指针是否有效。

读方法实现的功能

读方法是系统调用sys_read()的具体实现。读方法的返回值由调用read系统调用的应用程序来进行解释。

1)如果返回值与count参数的值相同,则说明被要求传输的数据完全接收完毕。这是最理想的情况;

2)如果返回值是正值,但是小于count值,说明仅有一部分数据被传输。这种情况的发生有多种原因,依赖于具体的设备。通常情况下,应用程序会尝试重新读取。

3)如果返回值为0,表示到了文件尾(即无数据可读)。

4)如果返回值是负值,表示发生了错误。

5)还有一种情况,read系统调用可以阻塞,这意味着“现在没有数据,但是等会就来”。

写方法实现的功能:

写方法,是系统调用sys_write()的具体实现。写方法的返回值同样由调用write系统调用的应用程序来解释。

1)如果返回值与count值相等,表明按要求传输的数据发送完毕。

2)如果返回值为正值,但小于count值,表明一部分数据发送成功。应用程序通常会尝试重新发送剩余的数据。

3)如果返回值为0,表明没有发送数据,这不是个错误。再一次,标准数据库会重新调用write系统调用。

4)如果返回值为负值,表明发送数据出错。


阅读(1153) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~