Chinaunix首页 | 论坛 | 博客
  • 博客访问: 45757
  • 博文数量: 11
  • 博客积分: 490
  • 博客等级: 下士
  • 技术积分: 140
  • 用 户 组: 普通用户
  • 注册时间: 2009-07-24 11:23
文章分类
文章存档

2011年(1)

2009年(10)

我的朋友

分类: LINUX

2009-07-29 21:58:20

Linux设备驱动程序学习(3)
-字符设备驱动程序
今天进入《Linux设备驱动程序(第3版)》第三章字符设备驱动程序的学习。
这一章主要通过介绍字符设备scull(Simple Character Utility for Loading Localities,区域装载的简单字符工具)的
驱动程序编写,来学习Linux设备驱动的基本知识。scull可以为真正的设备驱动程序提供样板。


一、主设备号和此设备号
主设备号表示设备对应的驱动程序;次设备号由内核使用,用于正确确定设备文件所指的设备。
内核用dev_t类型()来保存设备编号,dev_t是一个32位的数,12位表示主设备号,20为表示次设备号。
在实际使用中,是通过中定义的宏来转换格式。
 (dev_t)-->主设备号、次设备号(获取主次设备号  MAJOR(dev_t dev)
 MINOR(dev_t dev)
 主设备号、次设备号-->(dev_t)  MKDEV(int major,int minor) 

建立一个字符设备之前,驱动程序首先要做的事情就是获得设备编号。其这主要函数在中声明:

int register_chrdev_region(dev_t first, unsigned int count,
char *name);   //指定设备编号
/*

first 是你要分配的起始设备编号. first 的次编号部分常常是 0, 但是没有要求是那个效果. count
是你请求的连续设备编号的总数. 注意, 如果 count 太大, 你要求的范围可能溢出到下一个次编号;
但是只要你要求的编号范围可用, 一切都仍然会正确工作. 最后, name 是应当连接到这个编号范围
的设备的名子; 它会出现在 /proc/devices 和 sysfs 中.
如同大部分内核函数, 如果分配成功进行, register_chrdev_region 的返回值是 0. 出错的情况下, 返回一个负的错误码, 你不能存取请求的区域.

*/
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,
unsigned int count, char *name);   //动态生成设备编号

/*

dev 是一个只输出的参数, 它在函数成功完成时持有你的分配范围的第一个数.
fisetminor 应当是请求的第一个要用的次编号; 它常常是 count 和 name 参数如同给
register_chrdev_region 的一样.
*/
void unregister_chrdev_region(dev_t first, unsigned int count)
;      //释放设备编号


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

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

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

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

二、一些重要的数据结构
大部分基本的驱动程序操作涉及及到三个重要的内核数据结构,分别是file_operations、file和inode,它们的定义都在

1、file_operations

(1)传统上, 一个 file_operation 结构或者其一个指针称为 fops( 或者它的一些变体). 结构中的每个成员必须指向驱动中的函数, 这些函数实现一个特别的操作, 或者对于不支持的操作留置为 NULL.

(2)参数如果包含字串 __user. 这种注解是一种文档
形式, 表明指针是一个用户空间地址. 因此不能被直接解引用,对于正常的编译, __user 没有效果,但是它可被外部检查软件使用来找出对用户空间地址的错误使用.
(3)相关参数介绍:*owner:内核使用这个字段避免在模块的操作正在被使用时卸载该模块. 几乎在所有的情况下, 它被简单初始化为
THIS_MODULE,它是定义在的一个宏;*poll:如果一个驱动的 poll方法为 NULL, 该设备认为既可读也可写,并且不会被阻塞;*mmap:用来请求将设备内存映射到进程的地址空间

三、字符设备的注册

内核内部使用struct cdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或多个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%3

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