一、文件系统
linux下设备分为三大类:
1、字符设备(开发中最常用 使用最多,相比下面两个框架简单)
2、块设备
3、网络设备
linux下一切皆文件,也会把字符设备抽象成文件。
字符设备是使用最多的设备
复习文件描述符本质
linxu下 文件
又被抽象成 inode 结构体
。文件的操作接口
也被记录在这个 inode 结构体
中。
struct inode
定义在文件 fs.h
中。对应一个文件。struct inode
中含有成员变量 struct file_operations
。
open() 函数
:在文件系统中找到 指定文件的操作接口
后,绑定到进程管理结构体 task_struct->files_struct->fd_array[]->file_operations
,此函数返回值就是对应数组元素的下标。
struct task_struct
:定义在 sched.h
struct files_struct
:定义在 fdtable.h
struct file
:定义在 fs.h,用来管理一个文件。(open函数获得的 文件描述符
其实就是上图 fd_array数组的下标
)
struct file_operations
就是一个 文件的操作接口
。定义在 fs.h。
文件操作接口中的成员操作函数
其实就是就是 底层寄存器操作函数
,应用程序通过 这些 文件操作接口
来实现 对文件的操作
。
二、驱动层原理
把 struct file_operations
文件操作接口注册到 内核
,内核通过 主、次设备号
来登记、记录它。
1、构造驱动基本对象:
struct cdev
,此结构体中包含 struct file_operations
和 dev_t
两个成员变量。
/**
* 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().
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
/* 初始化 kobject 内部相关成员标志。
* 设置 kobject->ktype 操作接口。
* 详见 101.
*/
kobject_init(&cdev->kobj, &ktype_cdev_default);
/* 指定 fops */
cdev->ops = fops;
}
/**
* 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.
*/
/* 此函数用来保存驱动基本对象 */
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
int error;
// dev 起始设备号
p->dev = dev;
p->count = count;
/* 在此函数中将 cdev 绑定到 哈希表cdev_map 中 */
/* cdev_map 详见下 */
error = kobj_map(cdev_map, dev, count, NULL,
exact_match, exact_lock, p);
if (error)
return error;
kobject_get(p->kobj.parent);
return 0;
}
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;
// 申请 probe 结构体,申请数目为:要用到的主设备号的数目
p = kmalloc_array(n, sizeof(struct probe), 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;
// 这里的probe类型实例都是指向同一个struct cdev
p->data = data;
}
mutex_lock(domain->lock);
// 一个主设备号(包含其附带的若干个次设备号)在下面的哈希表占据一个坑
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
// 此处似乎是要按range从小到大来排列
while (*s && (*s)->range < range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
2、两个hash表
使用 两个hash表
来找到对应的 cdev 结构体。
1、chrdevs:管理设备号
第一个哈希表 chrdevs:管理设备号
登记设备号,防止设备号冲突
全局变量 chrdevs
定义如下:
static struct char_device_struct {
struct char_device_struct *next;
// 主设备号
unsigned int major; /* 重点关注 */
// 次设备号
unsigned int baseminor; /* 重点关注 */
// 次设备号数目
int minorct;
char name[64];
struct cdev *cdev; /* 重点关注,此结构体内包含 file_operations 和 dev_t */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
/* 此函数负责登记设备号
* 详见95
*/
__register_chrdev_region()
2、cdev_map->probe:管理 file_operation
第二个哈希表 cdev_map->probe:管理 file_operation
保存驱动基本对象 struct cdev
一个cdev,占据几个主设备号,就在该哈希表下占据几个坑。
cdev_map
类型为:struct kobj_map
,定义如下:
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 *);
/* 此指针指向一个 struct cdev 结构体 */
void *data;
} *probes[255];
struct mutex *lock;
};
三、文件系统层原理
mknod,cdev_init,cdev_add
1、使用指令 mknod指令+主从设备号
来构建一个新的设备文件;
2、把 cdev->file_operations
绑定到新的设备文件中( cdev_add 函数,内有 kobj_map 函数
);
到这里,应用程序就可以使用open()、write()、read()等函数来控制设备文件了。