92 Linux内核是怎么设计字符设备的

一、文件系统

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_operationsdev_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()等函数来控制设备文件了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值