Coder
分类: LINUX
2010-08-10 22:06:08
Linux字符设备管理
设备文件
类Unix操作系统都是基于文件概念的,一切皆文件是Unix的传统啊。在系统看来,文件都是无结构的,是由字节序列而构成的信息载体。根据这一点,可以把I/O设备当做设备文件(device file)这种所谓的特殊文件来处理,与磁盘上的普通文件进行交互所用的同一系统调用可直接用于I/O设备。
根据设备驱动程序的基本特性,设备文件可以分为两种:块设备文件和字符设备文件。块设备是那些能够随机访问,并且每次都以一个比较大的单位——块来进行信息存取的,块通常是2的某次方个字节。而字符设备则通常是只能顺序的逐字符的访问的,比如说键盘,把一旦把缓冲区中的数据读走了,数据就不在了,没有办法再回到之前的状态了。而从内核实现块设备子系统和字符设备子系统的角度来看,到底是只能逐字符访问还是支持随机的,以块为单位的访问,并不是那么重要的,而只是访问设备时所经过的路径不同罢了。网络设备是第三种设备类型,网卡是不直接与设备文件相对应的硬件设备。
设备文件是存放在文件系统中的实际文件,许多具体的文件系统,比如EXT3等都会提供例程来在其下创建设备文件。然而,设备文件的索引节点并不包含指向磁盘上数据块的(文件的数据)的指针,因为它们是空的。相反,索引节点必须包含硬件设备的一个标识符,它对应字符或块设备文件。
传统上,设备标识符由设备文件的类型(字符或块)和一对参数组成。第一个参数称为主设备号(major number),它标识了设备的类型。通常具有相同主设备号和类型的所有设备共享相同的文件操作集合,因为它们是由同一个设备驱动程序处理的。第二个参数为次设备号(minor number),它标识了主设备号相同的设备组中一个特定的设备。
mknod()系统调用用来创建设备文件。
字符设备驱动程序
字符设备驱动程序是一种相对比较简单的驱动程序,指的是其访问方式,字符设备,通常并不需要复杂的缓冲策略,也不涉及磁盘高速缓存等复杂的东西。当然,字符设备在它们的需求方面有所不同;有些必须实现复杂的通信协议以驱动硬件设备,而有些仅仅需要从硬件设备的I/O端口读几个值。
字符设备驱动程序是由一个cdev结构描述的,其定义为:
---------------------------------------------------------------------
include/linux/cdev.h
12
struct cdev {
13 struct kobject kobj;
14 struct module *owner;
15 const struct file_operations *ops;
16 struct list_head list;
17 dev_t dev;
18 unsigned int count;
19
};
---------------------------------------------------------------------
各字段说明:
kobj:内嵌的kobject
owner:指向实现驱动程序的模块(如果有的话)的指针
ops:指向设备驱动程序文件操作表的指针
list:与字符设备文件对应的索引节点链表的头,该链表用于收集相同字符设备驱动程序所对应的设备文件的索引节点。可能很多设备文件具有相同的设备号,并对应于相同的字符设备。此外,一个设备驱动程序对应的设备号可以是一个范围,而不仅仅是一个号;设备号位于同一范围内的所有设备文件均由同一个字符设备驱动程序处理。
dev:给设备驱动程序所分配的初始主设备号和此设备号
count:给设备驱动程序分配的设备号的范围大小。
设备驱动程序模型为字符设备定义了一个kobject映射域用于管理字符设备系统,该映射域由一个kobj_map类型的描述符描述,并由全局变量cdev_map引用。来看下在系统初始化过程中调用的建立文件系统的vfs_caches_init()中调用的chrdev_init()函数:
---------------------------------------------------------------------
fs/char_dev.c
26 /*
27 *
capabilities for /dev/mem, /dev/kmem and similar directly mappable
28 * character
devices
29 * -
permits shared-mmap for read, write and/or exec
30 * -
does not permit private mmap in NOMMU mode (can't do COW)
31 * -
no readahead or I/O queue unplugging required
32 */
33 struct backing_dev_info
directly_mappable_cdev_bdi = {
34
.name = "char",
35
.capabilities = (
36 #ifdef CONFIG_MMU
37
/* permit private copies of the data to be taken */
38
BDI_CAP_MAP_COPY |
39 #endif
40 /* permit direct mmap, for
read, write or exec */
41
BDI_CAP_MAP_DIRECT |
42
BDI_CAP_READ_MAP |
BDI_CAP_WRITE_MAP | BDI_CAP_EXEC_MAP),
43 };
44
45 static struct kobj_map *cdev_map;
558
static struct kobject *base_probe(dev_t dev, int *part, void *data)
559
{
560 if
(request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev)) > 0)
561 /* Make old-style 2.4 aliases
work */
562
request_module("char-major-%d", MAJOR(dev));
563 return NULL;
564
}
566
void __init chrdev_init(void)
567
{
568 cdev_map = kobj_map_init(base_probe,
&chrdevs_lock);
569
bdi_init(&directly_mappable_cdev_bdi);
570
}
---------------------------------------------------------------------
这里,先来看一下kobj_map描述符,其定义如下:
---------------------------------------------------------------------
drivers/base/map.c
struct
kobj_map {
struct probe {
struct probe *next;
dev_t dev;
unsigned long range;
struct module *owner;
kobj_probe_t *get;
int (*lock)(dev_t, void *);
void *data;
} *probes[255];
struct mutex *lock;
};
---------------------------------------------------------------------
kobj_map描述符包括一个散列表,它有255个表项,并由0——255范围的主设备号进行索引。散列表存放probe类型的对象,每个对象都拥有一个已注册的主设备号和此设备号。
probe对象中的各个字段:
next:散列冲突链表中的下一个元素
dev:设备号范围的初始设备号(主、此设备号)
range:设备号范围的大小
owner:如果有的话,指向实现设备驱动程序模块的指针
get:探测谁拥有这个设备号范围
lock:增加设备号范围内拥有者的引用计数器
data:设备号范围拥有者的私有数据。
接着来看kobj_map_init()的定义:
---------------------------------------------------------------------
drivers/base/map.c
struct
kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock)
{
struct kobj_map *p = kmalloc(sizeof(struct
kobj_map), GFP_KERNEL);
struct probe *base = kzalloc(sizeof(*base),
GFP_KERNEL);
int i;
if ((p == NULL) || (base == NULL)) {
kfree(p);
kfree(base);
return NULL;
}
base->dev = 1;
base->range = ~0;
base->get = base_probe;
for (i = 0; i < 255; i++)
p->probes[i] = base;
p->lock = lock;
return p;
}
---------------------------------------------------------------------
看得出,申请了一个struct kobj_map对象,并由局部变量p指向它,然后最后返回的也是p,即最后把一切都献给了cdev_map。然后又为一个probe对象分配了内存,由base指向它。初始化设备号为1,范围为最大,get方法为传递进来的base_probe。而这里真正干的事情无非就是让bdev_kmap->probes[]数组全都等于base。换言之,它们的get指针全都等于了咱们这里传递进来的base_probe函数。
内核提供了cdev_alloc()函数,用于动态的分配cdev描述符,并初始化内嵌的kobject数据结构,因此引用计数器的值变为0时会自动释放该描述符。这个函数定义为:
---------------------------------------------------------------------
fs/char_dev.c
527
/**
528 * cdev_alloc() - allocate a cdev structure
529 *
530 * Allocates and returns a cdev structure, or
NULL on failure.
531 */
532
struct cdev *cdev_alloc(void)
533
{
534 struct cdev *p = kzalloc(sizeof(struct
cdev), GFP_KERNEL);
535 if (p) {
536
INIT_LIST_HEAD(&p->list);
537 kobject_init(&p->kobj,
&ktype_cdev_dynamic);
538 }
539 return p;
540
}
---------------------------------------------------------------------
这里初始化cdev内嵌kobject时使用的kobj_type参数为ktype_cdev_dynamic,ktype_cdev_dynamic的定义为:
---------------------------------------------------------------------
fs/char_dev.c
523
static struct kobj_type ktype_cdev_dynamic = {
524 .release = cdev_dynamic_release,
525
};
---------------------------------------------------------------------
仅仅初始化了release字段为cdev_dynamic_release,这个函数将要在cdev引用计数为0时调用cdev_dynamic_release()定义为:
---------------------------------------------------------------------
fs/char_dev.c
512
static void cdev_dynamic_release(struct kobject *kobj)
513
{
514 struct cdev *p = container_of(kobj,
struct cdev, kobj);
515 cdev_purge(p);
516 kfree(p);
517
}
---------------------------------------------------------------------
这个函数调用了调用cdev_purge(p),将cdev从各个设备文件inode的设备链表中移除,然后调用kfree(p)来释放分配的内存。
也可以静态定义一个cdev,比如内嵌在某一个特定的设备结构体中,但在添加进系统之前则要调用cdev_init()函数来对cdev进行初始化。cdev_init()函数定义为:
---------------------------------------------------------------------
fs/char_dev.c
542
/**
543 * cdev_init() - initialize a cdev structure
544 * @cdev: the structure to initialize
545 * @fops: the file_operations for this device
546 *
547 * Initializes @cdev, remembering @fops,
making it ready to add to
548 * the system with cdev_add().
549 */
550
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
551
{
552 memset(cdev, 0, sizeof *cdev);
553 INIT_LIST_HEAD(&cdev->list);
554 kobject_init(&cdev->kobj,
&ktype_cdev_default);
555 cdev->ops = fops;
556
}
---------------------------------------------------------------------
值得注意的就是cdev_init()在初始化cdev内嵌kobject时,其kobj_type设置的是ktype_cdev_default。其定义为:
---------------------------------------------------------------------
fs/char_dev.c
506
static void cdev_default_release(struct kobject *kobj)
507
{
508 struct cdev *p = container_of(kobj,
struct cdev, kobj);
509 cdev_purge(p);
510
}
519
static struct kobj_type ktype_cdev_default = {
520 .release = cdev_default_release,
521
};
---------------------------------------------------------------------
和上面的ktype_cdev_dynamic一样,也是只初始化了release字段,但不同的是release字段被初始化为了cdev_default_release()函数,而cdev_default_release()函数是不释放cdev结构体的(因为是静态定义的嘛)。
系统提供了cdev_add()函数向系统添加一个cdev字符设备。其定义为:
---------------------------------------------------------------------
fs/char_dev.c
480
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
481
{
482 p->dev = dev;
483 p->count = count;
484
return kobj_map(cdev_map, dev, count,
NULL, exact_match, exact_lock, p);
485
}
---------------------------------------------------------------------
它接收三个参数,p指向设备的cdev结构体,dev是设备管理的第一个设备号,count为对应于设备的次设备号的范围大小。
cdev_add()初始化cdev描述符的dev和count字段,然后调用kobj_map()函数。kobj_map()函数定义为:
---------------------------------------------------------------------
drivers/base/map.c
int
kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
struct module *module, kobj_probe_t *probe,
int (*lock)(dev_t, void *), void *data)
{
unsigned n = MAJOR(dev + range - 1) -
MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n > 255)
n = 255;
p = kmalloc(sizeof(struct probe) * n,
GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
for (i = 0; i < n; i++, p++) {
p->owner = module;
p->get = probe;
p->lock = lock;
p->dev = dev;
p->range = range;
p->data = data;
}
mutex_lock(domain->lock);
for (i = 0, p -= n; i < n; i++, p++,
index++) {
struct probe **s =
&domain->probes[index % 255];
while (*s && (*s)->range <
range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
---------------------------------------------------------------------
这个函数完成如下操作:
1、获得设备号所跨越的主设备号的范围大小,保存在局部变量n中。
2、获得第一个主设备号,保存在index中。
3、分配元素个数为n的probe结构体数组。如果分配失败,则返回-ENOMEM。
4、初始化分配的probe结构体数组,每个元素都被初始化为相同的值,owner 字段为传递进来的module,对于cdev_add()来说为NULL,get字段为传递进来的probe,对于cdev_add()来说为exact_match函数,lock字段为传递进来的lock参数,cdev_add()来说为exact_lock函数,dev字段为传递进来的dev参数,即初始设备号, range字段为传递进来的range参数,都为总的设备号范围大小,而data字段为设备号范围拥有者的私有数据,对于cdev_add()来说则为对应的cdev描述符地址。
probe的get方法用于探测谁拥有这个设备号范围,lock方法用于增加设备号范围内拥有者的引用计数器,对于cdev_add(),这两个方法分别为:
---------------------------------------------------------------------
fs/char_dev.c
458
static struct kobject *exact_match(dev_t dev, int *part, void *data)
459
{
460 struct cdev *p = data;
461 return &p->kobj;
462
}
463
464
static int exact_lock(dev_t dev, void *data)
465
{
466 struct cdev *p = data;
467 return cdev_get(p) ? 0 : -1;
468
}
---------------------------------------------------------------------
5、将创建的n个probe对象添加进kobject映射域的散列表中,在散列冲突链表中,各个probe对象是按照设备号范围大小从小到大排列的。在分配设备号的时候,保证分配的设备号不会有重叠。
设备号管理
内核使用了散列表chrdevs来记录字符设备设备号的分配,表的大小不超过设备号范围。两个不同的设备号范围可能共享同一个主设备号,但是范围不能重叠,因此它们的次设备号应该完全不同。Chrdevs包含255个表项,但由于散列函数屏蔽了设备号的高四位,因此主设备号的个数少于255个,它们被散列到不透光的表项中。每个表项指向冲突链表的第一个元素,而该链表是按主次设备号的递增顺序进行排序的。冲突链表中的每一个元素是一个char_device_struct 结构。chrdevs定义为:
---------------------------------------------------------------------
fs/char_dev.c
49 static struct
char_device_struct {
50
struct char_device_struct *next;
51
unsigned int major;
52
unsigned int baseminor;
53
int minorct;
54
char name[64];
55
struct cdev *cdev; /*
will die */
56 } *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
---------------------------------------------------------------------
上面的个字段的意义基本上都比较清楚。minorct指的是次设备号范围的大小。
内核正是利用chrdevs散列表来管理字符设备设备号的管理的。内核提供了几个函数用以分配一个范围内的设备号。使用alloc_chrdev_region()和register_chrdev_region()函数为驱动分配任意范围内的设备号。例如,为了获得从dev(类型为dev_t)开始的大小为size的一个设备号范围:
register_chrdev_region(dev, size, “foo”);
使用register_chrdev()函数分配一个固定的设备号范围,该范围包含唯一一个主设备号以及0——255的次设备号。在这种情形下,设备驱动程序不必调用cdev_add()函数。
alloc_chrdev_region()函数和register_chrdev_region()函数
register_chrdev_region()函数注册一个设备号范围,它接收三个参数:需要的设备号范围的第一个设备号,必须包含主设备号,请求的连续设备号的数量,设备或驱动的名字。分配成功的时候,返回0,否则返回一个负的错误码。register_chrdev_region()函数定义为:
---------------------------------------------------------------------
fs/char_dev.c
193
int register_chrdev_region(dev_t from, unsigned count, const char *name)
194
{
195 struct char_device_struct *cd;
196 dev_t to = from + count;
197 dev_t n, next;
198
199 for (n = from; n < to; n = next) {
200 next = MKDEV(MAJOR(n)+1, 0);
201 if (next > to)
202 next = to;
203 cd = __register_chrdev_region(MAJOR(n),
MINOR(n),
204 next - n,
name);
205 if (IS_ERR(cd))
206 goto fail;
207 }
208 return 0;
209
fail:
210 to = n;
211 for (n = from; n < to; n = next) {
212 next = MKDEV(MAJOR(n)+1, 0);
213
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
214 }
215 return PTR_ERR(cd);
216
}
---------------------------------------------------------------------
这个函数将设备号范围划分为属于一个个不同主设备号的小的范围,然后在每个相应的小的范围上调用__register_chrdev_region()。
alloc_chrdev_region()函数动态的为驱动程序分配设备号,它接收四个参数:dev用于返回分配的第一个设备号,baseminor为请求范围的第一个次设备号,count为请求的次设备号的数量,name为相关的设备或驱动的名称。分配成功返回0,否则返回负的错误码。alloc_chrdev_region()函数的定义为:
---------------------------------------------------------------------
fs/char_dev.c
229
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
230 const char *name)
231
{
232 struct char_device_struct *cd;
233 cd = __register_chrdev_region(0,
baseminor, count, name);
234 if (IS_ERR(cd))
235 return PTR_ERR(cd);
236 *dev = MKDEV(cd->major,
cd->baseminor);
237 return 0;
238
}
---------------------------------------------------------------------
结束时,它也调用__register_chrdev_region()函数。
__register_chrdev_region()函数针对一个主设备号来注册一个给定的次设备号范围,如果给定的主设备号为0,则将自动分配。如果主设备号大于0,则将分配给定的次设备号范围的设备号,成功时返回char_device_struct描述符地址,出错时返回错误码。其定义如下:
---------------------------------------------------------------------
fs/char_dev.c
58 /* index in the above */
59 static inline int
major_to_index(int major)
60 {
61 return major % CHRDEV_MAJOR_HASH_SIZE;
62 }
91 static struct
char_device_struct *
92 __register_chrdev_region(unsigned int
major, unsigned int baseminor,
93 int minorct, const
char *name)
94 {
95
struct char_device_struct *cd, **cp;
96
int ret = 0;
97
int i;
98
99
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
100 if (cd == NULL)
101 return ERR_PTR(-ENOMEM);
102
103 mutex_lock(&chrdevs_lock);
104
105 /* temporary */
106 if (major == 0) {
107 for (i =
ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
108 if (chrdevs[i] == NULL)
109 break;
110 }
111
112 if (i == 0) {
113 ret = -EBUSY;
114 goto out;
115 }
116 major = i;
117 ret = major;
118 }
119
120 cd->major = major;
121 cd->baseminor = baseminor;
122 cd->minorct = minorct;
123 strlcpy(cd->name, name,
sizeof(cd->name));
124
125 i = major_to_index(major);
126
127 for (cp = &chrdevs[i]; *cp; cp =
&(*cp)->next)
128 if ((*cp)->major > major
||
129 ((*cp)->major == major
&&
130 (((*cp)->baseminor
>= baseminor) ||
131 ((*cp)->baseminor +
(*cp)->minorct > baseminor))))
132 break;
133
134 /* Check for overlapping minor
ranges. */
135 if (*cp && (*cp)->major ==
major) {
136 int old_min = (*cp)->baseminor;
137 int old_max =
(*cp)->baseminor + (*cp)->minorct - 1;
138 int new_min = baseminor;
139 int new_max = baseminor +
minorct - 1;
140
141 /* New driver overlaps from
the left. */
142 if (new_max >= old_min
&& new_max <= old_max) {
143 ret = -EBUSY;
144 goto out;
145 }
146
147 /* New driver overlaps from
the right. */
148 if (new_min <= old_max
&& new_min >= old_min) {
149 ret = -EBUSY;
150 goto out;
151 }
152 }
153
154 cd->next = *cp;
155 *cp = cd;
156 mutex_unlock(&chrdevs_lock);
157 return cd;
158
out:
159 mutex_unlock(&chrdevs_lock);
160 kfree(cd);
161 return ERR_PTR(ret);
162
}
---------------------------------------------------------------------
这个函数执行以下步骤:
1、分配一个新的char_device_struct结构,并用0填充。
2、如果设备号范围内的主设备号为0,那么设备驱动程序请求动态分配一个主设备号。函数从散列表的末尾表项开始继续向前寻找一个尚未使用的主设备号对应的空冲突链表(NULL指针)。若没有找到,则返回-EBUSY。若找到,则设置major为找到的主设备号。在这里,我们可以看到,动态分配设备号的一些局限性,那就是其主设备好不能够大于255,而实际上主设备号通常为12位的。同时也没有办法多个设备共用主设备号,这对于设备号资源来说岂不是一大浪费。
3、初始化char_device_struct结构中的主设备号、初始次设备号、范围大小以及设备或驱动程序名称。
4、执行散列函数major_to_index(major),计算与主设备号对应的散列表索引。
5、遍历冲突链表,为新的char_device_struct结构寻找正确的位置。在冲突链表中,按主设备号由小到大排列,若主设备号相同,则按次设备号由小到大排列。如果找到与请求的设备号范围重叠的一个范围,则释放先前分配的char_device_struct结构并返回-EBUSY。
6、将新的char_device_struct结构描述符插入冲突链表中。
7、返回新的char_device_struct描述符的地址。
register_chrdev()函数
驱动程序使用register_chrdev()函数时需要一个老式的设备号范围:一个单独的0——255的此设备号范围。干函数接收的参数为:请求的主设备号major(如果是0,则动态分配)、设备驱动程序的名称name和一个指针fops(它指向设备号范围内的特定字符设备文件的文件操作表)。该函数定义如下:
---------------------------------------------------------------------
include/linux/fs.h
2005
static inline int register_chrdev(unsigned int major,
2006 const
char *name, const struct file_operations *fops)
2007
{
2008 return __register_chrdev(major, 0,
256, name, fops);
2009
}
---------------------------------------------------------------------
它只是对__register_chrdev()函数的一个封装而已,而__register_chrdev()函数定义为:
---------------------------------------------------------------------
fs/char_dev.c
261
int __register_chrdev(unsigned int major, unsigned int baseminor,
262 unsigned int count,
const char *name,
263 const struct
file_operations *fops)
264
{
265 struct char_device_struct *cd;
266 struct cdev *cdev;
267 int err = -ENOMEM;
268
269 cd = __register_chrdev_region(major,
baseminor, count, name);
270 if (IS_ERR(cd))
271 return PTR_ERR(cd);
272
273 cdev = cdev_alloc();
274 if (!cdev)
275 goto out2;
276
277 cdev->owner = fops->owner;
278 cdev->ops = fops;
279 kobject_set_name(&cdev->kobj,
"%s", name);
280
281 err = cdev_add(cdev,
MKDEV(cd->major, baseminor), count);
282 if (err)
283 goto out;
284
285 cd->cdev = cdev;
286
287 return major ? 0 : cd->major;
288
out:
289 kobject_put(&cdev->kobj);
290
out2:
291
kfree(__unregister_chrdev_region(cd->major, baseminor, count));
292 return err;
293
}
---------------------------------------------------------------------
__register_chrdev()函数执行下列操作:
1、调用__register_chrdev_region(major,
baseminor, count, name)函数分配请求的设备号范围。如果返回一个错误码(不能分配该范围),函数将终止运行。
2、为设备分配一个cdev结构。
3、初始化cdev->owner为fops->owner,初始化cdev->ops为fops,将设备或驱动的名称拷贝到cdev内嵌的kobject结构的那么字段中。
4、调用cdev_add()函数(前面解释过)。
5、将__register_chrdev_region()函数在第1步中返回的char_device_struct描述符的cdev字段值为设备驱动程序的cdev描述符的地址。
6、返回分配的设备号范围的主设备号。
字符设备文件的VFS处理
当进程访问普通文件时,它会通过文件系统访问磁盘分区中的一些数据;而在进程访问设备文件时,它只要驱动硬件设备就可以了。为应用程序隐藏设备文件与普通文件之间的差异正是VFS的责任。为了做到这点,VFS在设备文件打开时改变其缺省文件操作;因此,可以把设备文件的每个系统调用都转换成与设备相关的函数的调用,而不是对主文件系统相应函数的调用。与设备相关的函数对设备进行操作以完成进程所请求的操作。
在设备文件上执行open()系统调用,从本质上说,相应的服务例程解析到设备文件的路径名,并建立相应的索引节点对象、目录项对象和文件对象。
通过适当的文件系统函数读取磁盘上相应索引节点来对索引节点对象进行初始化(通常为文件系统目录inode节点表的lookup方法)。当这个函数确定磁盘索引节点与设备文件对应时,则调用init_special_inode(),该函数把索引节点对象的i_rdev字段初始化为设备文件的主设备号和次设备号,而把索引节点对象的i_fop字段设为def_chr_fops或def_blk_fops文件操作表的地址(根据设备文件的类型)。
---------------------------------------------------------------------
fs/inode.c
1594
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
1595
{
1596 inode->i_mode = mode;
1597 if (S_ISCHR(mode)) {
1598 inode->i_fop =
&def_chr_fops;
1599 inode->i_rdev = rdev;
1600 } else if (S_ISBLK(mode)) {
1601 inode->i_fop =
&def_blk_fops;
1602 inode->i_rdev = rdev;
1603 } else if (S_ISFIFO(mode))
1604 inode->i_fop =
&def_fifo_fops;
1605 else if (S_ISSOCK(mode))
1606 inode->i_fop =
&bad_sock_fops;
1607 else
1608 printk(KERN_DEBUG
"init_special_inode: bogus i_mode (%o) for"
1609 " inode
%s:%lu\n", mode, inode->i_sb->s_id,
1610
inode->i_ino);
1611
}
1612
EXPORT_SYMBOL(init_special_inode);
---------------------------------------------------------------------
open()系统调用的服务例程也调用__dentry_open()函数(sys_open()-> do_sys_open()->do_filp_open()->do_last()->finish_open()->nameidata_to_filp()->
__dentry_open()),后者把一个新文件对象的f_op字段设置为i_fop中存放的地址,即再一次指向def_chr_fops或def_blk_fops文件操作表的地址。正是这两个表的引入,才使得在设备文件上所发出的任何系统调用都将激活设备驱动程序的函数而不是基本文件系统的函数。
def_chr_fops文件操作表几乎为空,它仅仅只定义了open()方法。这个方法有__dentry_open()函数直接调用。chrdev_open()接收的参数为设备文件索引节点的地址inode,指向所打开文件对象的指针filp。这个函数定义如下:
---------------------------------------------------------------------
fs/char_dev.c
366
static int chrdev_open(struct inode *inode, struct file *filp)
367
{
368 struct cdev *p;
369 struct cdev *new = NULL;
370 int ret = 0;
371
372 spin_lock(&cdev_lock);
373 p = inode->i_cdev;
374 if (!p) {
375 struct kobject *kobj;
376 int idx;
377 spin_unlock(&cdev_lock);
378 kobj = kobj_lookup(cdev_map, inode->i_rdev,
&idx);
379 if (!kobj)
380 return -ENXIO;
381 new = container_of(kobj,
struct cdev, kobj);
382 spin_lock(&cdev_lock);
383 /* Check i_cdev again in case
somebody beat us to it while
384 we dropped the lock. */
385 p = inode->i_cdev;
386 if (!p) {
387 inode->i_cdev = p =
new;
388
list_add(&inode->i_devices, &p->list);
389 new = NULL;
390 } else if (!cdev_get(p))
391 ret = -ENXIO;
392 } else if (!cdev_get(p))
393 ret = -ENXIO;
394 spin_unlock(&cdev_lock);
395 cdev_put(new);
396 if (ret)
397 return ret;
398
399 ret = -ENXIO;
400 filp->f_op = fops_get(p->ops);
401 if (!filp->f_op)
402 goto out_cdev_put;
403
404 if (filp->f_op->open) {
405 ret =
filp->f_op->open(inode,filp);
406 if (ret)
407 goto out_cdev_put;
408 }
409
410 return 0;
411
412 out_cdev_put:
413 cdev_put(p);
414 return ret;
415
}
---------------------------------------------------------------------
这个函数完成以下操作:
1、设置cdev指针类型局部变量p为inode->i_cdev。检查p指向的设备驱动程序的cdev描述符。如果不为空,则inode结构已经被访问,跳转到第6步。
2、调用kobj_lookup(cdev_map,
inode->i_rdev, &idx)函数搜索包括该设备号在内的范围。kobj_lookup()函数定义为:
---------------------------------------------------------------------
drivers/base/map.c
int
kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
struct module *module, kobj_probe_t *probe,
int (*lock)(dev_t, void *), void *data)
{
unsigned n = MAJOR(dev + range - 1) -
MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n > 255)
n = 255;
p = kmalloc(sizeof(struct probe) * n,
GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
for (i = 0; i < n; i++, p++) {
p->owner = module;
p->get = probe;
p->lock = lock;
p->dev = dev;
p->range = range;
p->data = data;
}
mutex_lock(domain->lock);
for (i = 0, p -= n; i < n; i++, p++,
index++) {
struct probe **s =
&domain->probes[index % 255];
while (*s && (*s)->range <
range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
---------------------------------------------------------------------
kobj_lookup()函数使用一个循环来查找设备号包含于其范围内的probe结构体。如果范围不存在,则返回NULL,否则,返回设备cdev结构内嵌的kobject对象的地址。
3、调用container_of(kobj,
struct cdev, kobj)获得cdev结构体的地址,将这个地址赋给局部cdev指针变量new。
4、获得cdev_lock锁,将inode->i_cdev赋值给局部变量p,再一次检查指向设备驱动程序的cdev描述符的指针inode->i_cdev,若为NULL,则使inode->i_cdev为新查找到的cdev对象的地址。并将inode添加进cdev的索引节点链表list中。设置局部变量new为NULL。跳到第7步。
5、否则,再一次检查的时候inode->i_cdev已为有效值,则增加cdev的引用计数,则增加p指向的对象的引用计数。,若失败,则设置ret值为-ENXIO,并跳转到第7步。
6、inode->i_cdev一开始即是有效的,则增加其引用计数,若失败,则设置ret值为-ENXIO。
7、解锁cdev_lock,释放new指向的cdev对象。检查ret值,若为非零值,则返回ret值。
8、将filp->f_op文件操作指针赋值为cdev描述符的ops字段的值。
9、如果filp->f_op为NULL,则释放p指向的cdev,并返回-ENXIO。
10、如果定义了filp->f_op->open方法,chrdev_open()函数就会执行该方法。若该方法返回错误码,则释放cdev,并返回-ENXIO。若设备驱动程序处理一个以上的设备号,则chrdev_open()一般会再次设置file对象的文件操作,这样可以为所访问的设备文件安装合适的文件操作方法。
11、成功时返回0。