热门关键字:  ubuntu  分区  Fedora  linux系统进程  函数

当前位置 :| 主页>Linux教程>内核研究>

Linux 2.6 字符设备驱动程序

来源: 作者: 时间:2007-11-16 Tag: 点击:

2、 初始化代表设备的scull_dev结构体
scull源代码中定义了一个scull_dev结构体,包括qset,qutuam,信号量sem以及cdev等字段。其中qset和qutuam的初始化对于Linux驱动程序的知识点来说毫不相关,因此不加讨论。
我只要知道,在加载module时所调用的module初始化函数中,可以初始化一些设备相关的变量。但是根据Linux Device Drvier3作者的意思,设备相关的变量或者一些资源最好应当在open函数中初始化,比如像中断号等,虽然在module初始化函数中注册也是允许的,但最好是在第一次打开设备,也就是open函数中再行分配。

3、 初始化互斥体init_MUTEX
互斥体MUTEX,也就是信号量的一个变种,与completion,自旋锁spinlock等等都与驱动中的并发和竞态相关,以后再说。

4、 初始化在内核中代表设备的cdev结构体
其实在Linux内核中,cdev结构体才是真正代表了某个设备。在内核调用设备的open,read等操作之前,必须先分配并注册一个或者多个cdev结构。
我想可以这么理解,主次设备号是涉外的,主要用来在与外部(指的是驱动module和Linux内核以外)交互时确定身份;而cdev结构体则是涉内的,当需要在module内部,或者与Linux内核之间传递一些变量,指针,buffer等东东,或者要调用驱动module中的某个服务函数时,就要用到 cdev结构体了。

在scull函数中,cdev结构体的分配,注册与初始化使用了一个自定义的scull_setup_cdev函数,在该函数中,主要由以下4条语句对cdev进行初始化:
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add (&dev->cdev, devno, 1);
(dev变量是scull程序定义的代表设备的一个结构体,它包含了cdev结构体,对于dev来说,cdev结构体应该说就是它的核心)

第一条语句是初始化cdev结构体,比如为cdev结构体分配内存,为cdev结构体指定file_operations等等,而第三条语句的作用初看起来似乎与第一条有所重复。但scull程序中既然这么写想必就有它的用意,也许需要看Linux内核源代码才能搞明白,但目前我是这么理解的:第一条语句中有关file_operations的部分是为了告诉Linux内核,该 cdev结构体相关的file_operations是scull_fops;而第二条语句则是真正为cdev指定了它的file_operations 字段就是scull_fops。
scull_fops是file_operations类型的变量,file_operations也是一个结构体,而且是Linux驱动程序中很重要的一个结构体,在scull程序中,定义如下:
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,
};
以上定义中,第一条.owner字段说明本file_operations结构体的拥有者是本驱动module,而接下来的几个字段则是告诉驱动 module,当有相应的系统调用到达该module时,module应该调用哪一个函数来为该系统调用服务。比如说,若有一个open系统调用到达 module,则module通过查询file_operations结构体就知道了,与open系统调用相关的是scull_open函数,于是 module就调用scull_open函数来为open系统调用服务了。其他几个字段也完全类似。
当然,Linux内核定义的file_operations结构体还包括其他一些字段,比如异步读写等等,但还是等用到的时候再说吧。

对cdev初始化的第二条语句是dev->cdev.owner = THIS_MODULE,这条语句就是说正在初始化的cdev结构体的拥有者就是本module

对cdev初始化的最后一条语句是err = cdev_add (&dev->cdev, devno, 1),该语句的目的就是告诉内核,该cdev结构体信息。因为cdev_add函数有可能调用失败,所以需要检测该函数调用的返回值。而一旦 cdev_add调用成功返回,那么我们的设备就“活”了!也就是说,外部应用程序对它的操作就会被内核允许且调用。因此在驱动程序还没有完全准备好处理设备上的操作时,就绝不能调用cdev_add。

三、 设备操作

驱动module因为由insmod的加载而进行了初始化之后,就会进入“潜伏”状态,也就是说,如果没有系统调用(如open,read等),那么module中定义的其他函数就绝不会运行!
这里所说的设备操作,是指当有系统调用到达驱动module时,module就该调用某个或某些函数有所动作。
对于驱动开发来说,我主要关心的只有一点,那就是系统调用怎样把一些外部应用程序中的变量值传递给驱动module。
scull程序中与设备操作相关的函数主要分为三类:初始化函数,实际的操作服务函数和清理函数。其中初始化函数只有一个,就是open函数,而操作服务函数则包括read,write,llseek等等函数,至于清理函数则是release函数。

1、open函数
open函数提供给驱动程序以初始化的能力,从而为以后的操作做准备。
说起来在用insmod加载驱动后也有一个初始化动作,但那个初始化是相对于整个Linux内核,或者说是针对整个module在涉外时的全局意义上的初始化;而open函数的初始化则是相对于设备操作来说的,是属于驱动内部的初始化,比如为以后read操作时用到的某个变量(如file结构体)作一下初始化,再比如初始化一下设备,清空一下buffer等等。
在大部分的驱动程序中,open应该完成如下工作:
a、 确定要打开的具体设备
b、 检查设备特定的错误(诸如设备未就绪或类似的硬件问题)
c、 如果设备是首次打开,则对其作一下初始化
d、 如有必要,更新f_op指针
e、 分配并填写置于filp->private_data里的数据结构

open函数的原型如下(指的是在file_operations结构体中的定义):
int (*open)(struct inode *inode, struct file *filp)
在驱动开发时要做的,就是为该函数作具体实现,当然对open函数的名称可以自定义,只要在填写file_operations结构体中的open字段时,将自定义的open函数名称填上就可以了。在scull程序中,用的就是scull_open函数名。

在open函数原型中,有inode和filp两个参数,都是外部应用程序在操作设备时通过调用系统调用传递给驱动module的。于是驱动module就可以通过这两个参数来确定要打开的具体设备了。其实这里所说的具体设备,并不是说驱动module需要从系统安装的所有设备中确定它所要服务的设备,而是指module需要从某一类拥有相同主设备号的设备中确定它要服务的设备。
之所以这么说,是因为驱动module是对应于某一个主设备号的所有设备的。换一句话说,就是Linux内核只管设备的主设备号,而不理会设备的次设备号是什么,如果有两个,三个或者更多个设备拥有同一个主设备号,那么不管外部应用程序要操作这些设备中的哪一个,Linux内核都只会调用同一个驱动 module。但是驱动module却不能不管次设备号了,因为它是跟某一个具体的设备打交道的,所以它需要根据open系统调用时传递给它的参数中找到次设备号,从而确定那唯一的一个设备(也许驱动module也可以同时操作几个设备,但一时也想不起来)。
但是上面所说的通过次设备号找到具体的设备,只是其中一种方法;另外还有一种方法就是通过cdev结构体确定某个具体设备。
设备所拥有的cdev结构体,或者次设备号,都保存在open函数的inode参数中。我们可以使用container_of宏通过inode所拥有的 cdev确定具体设备,也可以使用iminor宏从inode所拥有的i_rdev确定次设备号(i_rdev是inode结构体中的一个dev_t类型的变量,其中保存了真正的设备主次编号)。

对于open函数中的file参数,scull程序主要用它来做两件事:其一是将根据cdev获得的代表设备的scull_dev结构体保存到file->private_data中,这样就可以方便今后对该设备结构体的访问了,而不用每次都调用container_of宏或者iminor宏来找到设备结构体了;其二是根据file结构体中的f_flags字段来确定,这次的 open调用,是以写方式打开设备,还是以读方式来打开设备。



相关文章:
精通initramfs构建step by step
Linux利用kexec迅速切换内核
进程上下文VS中断上下文
内核通知链 学习笔记
linux spi子系统驱动分析
menuconfig 配置选项
《Linux操作系统内核实习》之练习一
udev详解
什么叫微内核,宏内核?
Linux 信号signal处理机制
开发简单的 Linux2.6 内核模块
删除内核的perl脚本
Linux2.6内核usb gadget驱动移植
GCC hacks in the Linux kernel
iomem
kernel学习的想法
让自己的驱动支持udev
linux内核编译步骤
内核的等待队列
Linux内核wait_queue深入分析
升级和删除内核
SD卡驱动分析2
Linux Kernel VDSO本地权限提升漏洞
内核中的TCP的追踪分析-15-TCP(IPV4)的客户端与
linux 2.6内核可加载模块的编译
内核模块HelloWorld
在环回接口上发送一个数据报
ARP初始化
1分钟编译FreeBSD内核
linux设备模型之uart驱动架构分析