分类: LINUX
2013-09-07 15:43:04
#ifndef _LINUX_CDEV_H
#define _LINUX_CDEV_H
#include
#include
#include
struct file_operations;
struct inode;
struct module;
struct cdev { /* 表示字符设备 */
struct kobject kobj; /*内嵌的kobj内核对象 */
struct module *owner; /* =THIS_MODULE */
const struct file_operations *ops; /* 指向我们定义的操作函数 */
struct list_head list; /* 用于构造字符设备链表 (描述了与cdev对应的字符设备文件的inode->i_devices的链表的表头)*/
dev_t dev; /* 设备号 */
unsigned int count; /* 隶属于同一主设备号的次设备号的个数,用于表示当前设备驱动程序控制的实际同类设备的数量*/
};
void cdev_init(struct cdev *, const struct file_operations *);
struct cdev *cdev_alloc(void);
void cdev_put(struct cdev *p);
int cdev_add(struct cdev *, dev_t, unsigned);
void cdev_del(struct cdev *);
void cd_forget(struct inode *);
extern struct backing_dev_info directly_mappable_cdev_bdi;
#endif
/*********************************************************************************************************************************/
/* char_dev.c*/
/*
* linux/fs/char_dev.c
*
* Copyright (C) 1991, 1992 Linus Torvalds
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "internal.h"
/*
* capabilities for /dev/mem, /dev/kmem and similar directly mappable character
* devices
* - permits shared-mmap for read, write and/or exec
* - does not permit private mmap in NOMMU mode (can't do COW)
* - no readahead or I/O queue unplugging required
*/
struct backing_dev_info directly_mappable_cdev_bdi = { /* 备份设备信息 */
.capabilities = (
#ifdef CONFIG_MMU
/* permit private copies of the data to be taken */
BDI_CAP_MAP_COPY |
#endif
/* permit direct mmap, for read, write or exec */
BDI_CAP_MAP_DIRECT |
BDI_CAP_READ_MAP | BDI_CAP_WRITE_MAP | BDI_CAP_EXEC_MAP),
};
static struct kobj_map *cdev_map;
static DEFINE_MUTEX(chrdevs_lock);
static struct char_device_struct { /* 描述字符设备的结构 */
struct char_device_struct *next; /* 用于形成同类设备的链表 */
unsigned int major; /* 主设备号 */
unsigned int baseminor; /* 基次设备号 */
int minorct; /* 此设备号数量 */
char name[64]; /* 字符设备的名称 */
struct cdev *cdev; /* will die *//* 用于表示一个字符设备 */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; /* 系统中对多可以有255个字符设备 */
/* index in the above */
static inline int major_to_index(int major) /* 主设备号来引索 */
{
return major % CHRDEV_MAJOR_HASH_SIZE; /* major %255 */
}
#ifdef CONFIG_PROC_FS
/* 显示同类设备的主设备号和设备的名字 */
void chrdev_show(struct seq_file *f, off_t offset)
{
struct char_device_struct *cd;
if (offset < CHRDEV_MAJOR_HASH_SIZE) {
mutex_lock(&chrdevs_lock);
for (cd = chrdevs[offset]; cd; cd = cd->next)
seq_printf(f, "%3d %s\n", cd->major, cd->name);
mutex_unlock(&chrdevs_lock);
}
}
#endif /* CONFIG_PROC_FS */
/*
* Register a single major with a specified minor range.
*
* If major == 0 this functions will dynamically allocate a major and return
* its number.
*
* If major > 0 this function will attempt to reserve the passed range of
* minors and will return zero on success.
*
* Returns a -ve errno on failure.
*/
/* 分配一个struct char_device_struct 结构并放入链表中即用于创建同类设备的子设备并注册
如果参数major=0则该函数将动态分配一个主设备号并且返回 ,这个主设备号如果major>0
则该函数将尝试分配baseminor开始minorct个次设备号,如果成功的则返回0*/
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = 0;
int i;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL); /* 分配一个struct char_device_struct 结构体 */
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/* temporary */
if (major == 0) { /* 如果参数major=0,则 */
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) { /* ARRAY_SIZE(chrdevs)用来获取数组chrdevs的数组项的个数 ,从254项向前搜索*/
if (chrdevs[i] == NULL) /* 如果数组项中没有被使用的个数 */
break;
}
if (i == 0) { /* 说明设备列表已经满了 */
ret = -EBUSY;
goto out;
}
major = i; /* major=i表示使用数组中未使用的数组项的第一个 */
ret = major;
}
/* 初始化struct char_device_struct *cd*/
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name));
i = major_to_index(major); /* i=major %255 */
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) /* 扫描chrdevs,寻找符合下面条件的数组项直到 *cp=0*/
if ((*cp)->major > major || /* 数组项中使用的主设备号大于当前主设备号 */
((*cp)->major == major && /* 或者 数组项中使用的主设备号等于当前主设备号*/
(((*cp)->baseminor >= baseminor) || /* 并且数组项中使用的基次设备号等于当前基次设备号*/
((*cp)->baseminor + (*cp)->minorct > baseminor)))) /* 或者 数组项中使用的基次设备号加上次设备号个数等于当前基次设备号*/
break; /* 则停止扫描 ,表示分配成功,不会与已经占用的设备发生冲突*/
/* Check for overlapping minor ranges. */
if (*cp && (*cp)->major == major) { /* 如果是重复的主设备号则 */
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) { /* 判断次设备号是否有重叠 */
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) { /* 判断次设备号是否有重叠 */
ret = -EBUSY;
goto out;
}
}
cd->next = *cp; /* 将当前数组项加入链表 */
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
/* 卸载同类设备的符合参数要求的其他设备 */
static struct char_device_struct *
__unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct)
{
struct char_device_struct *cd = NULL, **cp;
int i = major_to_index(major);
mutex_lock(&chrdevs_lock);
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) /* 寻找符合传进来的参数的数组项 */
if ((*cp)->major == major &&
(*cp)->baseminor == baseminor &&
(*cp)->minorct == minorct)
break;
if (*cp) {
cd = *cp;
*cp = cd->next; /* 从链表中删除 */
}
mutex_unlock(&chrdevs_lock);
return cd;
}
/**
* register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
* the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure.
*/
/*
*该函数分配从from开始count个设备编号,其代表当前驱动程序所管理的同类设备的个数,first为预先设定的
设备号,成功返回0,失败返回负的错误码,name 是应当连接到这个编号范围的设备的名子;
* 它会出现在 /proc/devices 和 sysfs 中(此函数用在已知主设备号的情况下)
*linux内核用主设备号来定位对应的设备驱动程序,而次设备号则由驱动程序使用,用来标识它所管理的
*若干同类设备
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd; /* 定义一个指向字符设备链表的指针 */
dev_t to = from + count; /*需要分配 设备号的最大值 */
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0); /* 主设备号+1和次设备号0 转换为设备编号 */
if (next > to) /* 分配的设备号大于 需要分配 设备号的最大值 */
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),THIS_MODULE
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
/**
* alloc_chrdev_region() - register a range of char device numbers
* @dev: output parameter for first assigned number
* @baseminor: first of the requested range of minor numbers
* @count: the number of minor numbers required
* @name: the name of the associated device or driver
*
* Allocates a range of char device numbers. The major number will be
* chosen dynamically, and returned (along with the first minor number)
* in @dev. Returns zero or a negative error code.
*/
/* 动态分配设备号, firstminor通常为0,分配的结果保存在第一个参数里(此函数用在未知主设备号的情况下)
成功返回0*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name); /* 动态分配设备号 */
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
/**
* register_chrdev() - Register a major number for character devices.
* @major: major device number or 0 for dynamic allocation
* @name: name of this range of devices
* @fops: file operations associated with this devices
*
* If @major == 0 this functions will dynamically allocate a major and return
* its number.
*
* If @major > 0 this function will attempt to reserve a device with the given
* major number and will return zero on success.
*
* Returns a -ve errno on failure.
*
* The name of this device has nothing to do with the name of the device in
* /dev. It only helps to keep track of the different owners of devices. If
* your module name has only one type of devices it's ok to use e.g. the name
* of the module here.
*
* This function registers a range of 256 minor numbers. The first minor number
* is 0.
*/
/* 注册字符设备(这是早期的方法,以后会被淘汰)
成功注册则返回0,否则返回-ENOMEM*/
int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
char *s;
int err = -ENOMEM;
cd = __register_chrdev_region(major, 0, 256, name); /* 分配设备号 */
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc(); /* 分配一个struct cdev结构体 */
if (!cdev)
goto out2;
/* 初始化 */
cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name); /* 设置在sysfs中的名字 */
for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))
*s = '!';
err = cdev_add(cdev, MKDEV(cd->major, 0), 256); /* 注册字符设备 */
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
out:
kobject_put(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, 0, 256));
return err;
}
/**
* unregister_chrdev_region() - return a range of device numbers
* @from: the first in the range of numbers to unregister
* @count: the number of device numbers to unregister
*
* This function will unregister a range of @count device numbers,
* starting with @from. The caller should normally be the one who
* allocated those numbers in the first place...
*/
void unregister_chrdev_region(dev_t from, unsigned count) /* 卸载设备号 */
{
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n)); /* 释放 */
}
}
/* 注销字符设备(这是早期的方法,以后会被淘汰) */
void unregister_chrdev(unsigned int major, const char *name)
{
struct char_device_struct *cd;
cd = __unregister_chrdev_region(major, 0, 256); /* 卸载设备号 */
if (cd && cd->cdev)
cdev_del(cd->cdev); /* 删除字符设备 */
kfree(cd); /* 释放字符设备描述结构体 */
}
static DEFINE_SPINLOCK(cdev_lock);
/* 增加设备的引用计数 */
static struct kobject *cdev_get(struct cdev *p)
{
struct module *owner = p->owner;
struct kobject *kobj;
if (owner && !try_module_get(owner))
return NULL;
kobj = kobject_get(&p->kobj);
if (!kobj)
module_put(owner);
return kobj;
}
/* 减小设备的引用计数 */
void cdev_put(struct cdev *p)
{
if (p) {
struct module *owner = p->owner;
kobject_put(&p->kobj);
module_put(owner);
}
}
/*
* Called every time a character special file is opened
*/
/* 打开字符设备,系统调用open->do_sys_open->do_filp_open->nameidata_to_filp->__dentry_open->chrdev_open*/
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
spin_lock(&cdev_lock);
p = inode->i_cdev; /* 获取该字符设备描述结构体 */
if (!p) {
struct kobject *kobj;
int idx;
spin_unlock(&cdev_lock);
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); /* */
if (!kobj)
return -ENXIO;
new = container_of(kobj, struct cdev, kobj);
spin_lock(&cdev_lock);
/* Check i_cdev again in case somebody beat us to it while
we dropped the lock. */
p = inode->i_cdev;
if (!p) {
inode->i_cdev = p = new; /* 指向新的字符设备*/
inode->i_cindex = idx;
list_add(&inode->i_devices, &p->list); /* 将p添加到inode->i_devices指向的链表 */
new = NULL;
} else if (!cdev_get(p))
ret = -ENXIO;
} else if (!cdev_get(p))
ret = -ENXIO;
spin_unlock(&cdev_lock);
cdev_put(new);
if (ret)
return ret;
ret = -ENXIO;
filp->f_op = fops_get(p->ops); /* 将字符设备的ops赋值给文件的操作函数集filp->f_op ,这样就会最终调用到字符设备的ops*/
if (!filp->f_op)
goto out_cdev_put;
if (filp->f_op->open) { /* 如果字符设备中定义了open函数,则调用字符设备里的open函数来打开设备 */
ret = filp->f_op->open(inode,filp);
if (ret)
goto out_cdev_put;
}
return 0;
out_cdev_put:
cdev_put(p);
return ret;
}
/* 删除inode->i_devices链表中所有的设备 */
void cd_forget(struct inode *inode)
{
spin_lock(&cdev_lock);
list_del_init(&inode->i_devices);
inode->i_cdev = NULL;
spin_unlock(&cdev_lock);
}
static void cdev_purge(struct cdev *cdev)
{
spin_lock(&cdev_lock);
while (!list_empty(&cdev->list)) {
struct inode *inode;
inode = container_of(cdev->list.next, struct inode, i_devices);
list_del_init(&inode->i_devices);
inode->i_cdev = NULL;
}
spin_unlock(&cdev_lock);
}
/*
* Dummy default file-operations: the only thing this does
* is contain the open that then fills in the correct operations
* depending on the special file...
*/
const struct file_operations def_chr_fops = { /* 表示字符设备 ,在init_special_inode函数中与inode关联起来,此时的inode表示用户空间的设备文件*/
.open = chrdev_open,
};
static struct kobject *exact_match(dev_t dev, int *part, void *data)
{
struct cdev *p = data;
return &p->kobj;
}
static int exact_lock(dev_t dev, void *data)
{
struct cdev *p = data;
return cdev_get(p) ? 0 : -1;
}
/**
* cdev_add() - add a char device to the system
* @p: the cdev structure for the device
* @dev: the first device number for which this device is responsible
* @count: the number of consecutive minor numbers corresponding to this
* device
*
* cdev_add() adds the device represented by @p to the system, making it
* live immediately. A negative error code is returned on failure.
*/
/* 该函数用于向linux内核系统添加一个字符设备(也即注册),并且使这个设备立即可用
该函数返回0则表示添加成功,否则返回-ENOMEM*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev; /* 将我们定义或分配的设备号赋值给字符设备描述结构体成员 dev*/
p->count = count; /* 注册的设备个数 */
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p); /* 该函数通过操作全局变量cdev_map来把设备*p加入
加入到其中的哈希链表中*/
}
/* 取消映射 */
static void cdev_unmap(dev_t dev, unsigned count)
{
kobj_unmap(cdev_map, dev, count);
}
/**
* cdev_del() - remove a cdev from the system
* @p: the cdev structure to be removed
*
* cdev_del() removes @p from the system, possibly freeing the structure
* itself.
*/
/* 该函数用于从linux内核系统中移除cdev结构体变量所描述的字符设备,函数执行之后
,输入参数所代表的字符设备将不可用(即卸载设备) */
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj); /* 减小计数 */
}
static void cdev_default_release(struct kobject *kobj)
{
struct cdev *p = container_of(kobj, struct cdev, kobj);
cdev_purge(p);
}
static void cdev_dynamic_release(struct kobject *kobj)
{
struct cdev *p = container_of(kobj, struct cdev, kobj);
cdev_purge(p);
kfree(p);
}
static struct kobj_type ktype_cdev_default = {
.release = cdev_default_release,
};
static struct kobj_type ktype_cdev_dynamic = {
.release = cdev_dynamic_release,
};
/**
* cdev_alloc() - allocate a cdev structure
*
* Allocates and returns a cdev structure, or NULL on failure.
*/
/* 该函数用于动态申请并分配一个struct cdev结构体,并对这个结构体变量进行初始化,经过该
函数分配的结构体的变量还需手动初始化cdev->owner和cdev->ops对象之后才能被加入linux内核系统*/
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); /* 分配内存 */
if (p) {
INIT_LIST_HEAD(&p->list); /* 初始化链表头 */
kobject_init(&p->kobj, &ktype_cdev_dynamic); /* 初始化字符设备内嵌的kobject,设置kobject的动态属性*/
}
return p;/* 返回一个指向已经分配的结构体指针 */
}
/**
* cdev_init() - initialize a cdev structure
* @cdev: the structure to initialize
* @fops: the file_operations for this device
*
* Initializes @cdev, remembering @fops, making it ready to add to the
* system with cdev_add().
*/
/* 该函数用于初始化已分配的字符设备结构(主要cdev->owner对象没有在此函数中初始化) */
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev); /* 将已经分配的内存清零 */
INIT_LIST_HEAD(&cdev->list);/* 将该结构添加到链表头 */
kobject_init(&cdev->kobj, &ktype_cdev_default); /* 初始化字符设备内嵌的kobject,设置kobject的静态属性*/
cdev->ops = fops;/* 将我们定义的fileoprations结构体指针赋值给字符设备描述结构体的成员 */
}
/* 该函数调用用户空间装载器来装载一个模块 */
static struct kobject *base_probe(dev_t dev, int *part, void *data)
{
/* 使用用户空间的模块装载器"/sbin/modprob" 来装载模块,如果成功返回0,否则返回-errno
如果系统不支持模块自动装载,那么这个函数变成不可操作*/
if (request_module("char-major-%d-%d", MAJOR(dev), MINOR(dev)) > 0)
/* Make old-style 2.4 aliases work */
request_module("char-major-%d", MAJOR(dev));
return NULL;
}
void __init chrdev_init(void) /*字符设备初始化(start_kernel()---> vfs_caches_init()----> chrdev_init,可见这个函数是在系统初始化的时候被调用的)*/
{
cdev_map = kobj_map_init(base_probe, &chrdevs_lock);
bdi_init(&directly_mappable_cdev_bdi);
}
/* Let modules do char dev stuff */
EXPORT_SYMBOL(register_chrdev_region);
EXPORT_SYMBOL(unregister_chrdev_region);
EXPORT_SYMBOL(alloc_chrdev_region);
EXPORT_SYMBOL(cdev_init);
EXPORT_SYMBOL(cdev_alloc);
EXPORT_SYMBOL(cdev_del);
EXPORT_SYMBOL(cdev_add);
EXPORT_SYMBOL(register_chrdev);
EXPORT_SYMBOL(unregister_chrdev);
EXPORT_SYMBOL(directly_mappable_cdev_bdi);