一、文件系统
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()等函数来控制设备文件了。
本文深入探讨了Linux系统下设备驱动与文件系统的交互机制。首先介绍了Linux中的字符设备、块设备和网络设备,并强调字符设备在开发中的普遍使用。接着,详细阐述了驱动层的构造驱动基本对象过程,包括`cdev_init`和`cdev_add`函数的作用。文章还提到了两个关键的哈希表`chrdevs`和`cdev_map->probe`,用于设备号管理和file_operations的注册。最后,总结了文件系统层如何通过`mknod`、`cdev_init`和`cdev_add`操作实现设备文件的创建和操作接口的绑定,使得应用程序能够通过标准的文件操作函数控制设备。
286





