一、背景
上节内容我们介绍了 内核中 一对一、一对多、多对多 的数据结构, 本节我们参照内核 input 子系统来模拟 input 子系统里
input_dev
↔ input_handler
多对多关系 ,用中间对象 input_handle
把两者连起来。
加载模块后,可以在 dmesg
里看到设备和处理器的绑定关系。
二、驱动
2.1 code
c
// demo_input_m2m.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/list.h>
struct my_input_dev;
struct my_input_handler;
struct my_input_handle;
/* 中间关系对象 */
struct my_input_handle {
struct my_input_dev *dev;
struct my_input_handler *handler;
struct list_head d_node; /* 挂到 dev->h_list */
struct list_head h_node; /* 挂到 handler->h_list */
};
/* 设备 */
struct my_input_dev {
const char *name;
struct list_head h_list; /* 所有关联的 handle */
struct list_head node; /* 挂到全局 dev_list */
};
/* 处理器 */
struct my_input_handler {
const char *name;
struct list_head h_list; /* 所有关联的 handle */
struct list_head node; /* 挂到全局 handler_list */
};
/* 全局链表 */
static LIST_HEAD(dev_list);
static LIST_HEAD(handler_list);
/* 工具函数:创建 handle 并建立关联 */
static void connect_dev_handler(struct my_input_dev *dev,
struct my_input_handler *handler)
{
struct my_input_handle *handle;
handle = kzalloc(sizeof(*handle), GFP_KERNEL);
if (!handle)
return;
handle->dev = dev;
handle->handler = handler;
INIT_LIST_HEAD(&handle->d_node);
INIT_LIST_HEAD(&handle->h_node);
/* 双向挂接 */
list_add(&handle->d_node, &dev->h_list);
list_add(&handle->h_node, &handler->h_list);
pr_info("Connect: dev=%s <-> handler=%s\n", dev->name, handler->name);
}
/* 初始化 demo */
static int __init demo_init(void)
{
struct my_input_dev *kbd, *mouse;
struct my_input_handler *evdev, *mousedev;
struct my_input_handle *h;
pr_info("demo_input_m2m init\n");
/* 创建设备 */
kbd = kzalloc(sizeof(*kbd), GFP_KERNEL);
mouse = kzalloc(sizeof(*mouse), GFP_KERNEL);
kbd->name = "keyboard";
mouse->name = "mouse";
INIT_LIST_HEAD(&kbd->h_list);
INIT_LIST_HEAD(&mouse->h_list);
list_add(&kbd->node, &dev_list);
list_add(&mouse->node, &dev_list);
/* 创建 handler */
evdev = kzalloc(sizeof(*evdev), GFP_KERNEL);
mousedev = kzalloc(sizeof(*mousedev), GFP_KERNEL);
evdev->name = "evdev";
mousedev->name = "mousedev";
INIT_LIST_HEAD(&evdev->h_list);
INIT_LIST_HEAD(&mousedev->h_list);
list_add(&evdev->node, &handler_list);
list_add(&mousedev->node, &handler_list);
/* 建立绑定关系 */
connect_dev_handler(kbd, evdev); // keyboard -> evdev
connect_dev_handler(kbd, mousedev); // keyboard -> mousedev
connect_dev_handler(mouse, evdev); // mouse -> evdev
/* 打印绑定关系 */
list_for_each_entry(kbd, &dev_list, node) {
pr_info("Device %s bound to: ", kbd->name);
list_for_each_entry(h, &kbd->h_list, d_node) {
pr_cont("%s ", h->handler->name);
}
pr_cont("\n");
}
return 0;
}
/* 清理 */
static void __exit demo_exit(void)
{
struct my_input_dev *d, *d_tmp;
struct my_input_handler *h, *h_tmp;
struct my_input_handle *hdl, *hdl_tmp;
/* 释放 handle */
list_for_each_entry(d, &dev_list, node) {
list_for_each_entry_safe(hdl, hdl_tmp, &d->h_list, d_node) {
list_del(&hdl->d_node);
list_del(&hdl->h_node);
kfree(hdl);
}
}
/* 释放 dev */
list_for_each_entry_safe(d, d_tmp, &dev_list, node) {
list_del(&d->node);
kfree(d);
}
/* 释放 handler */
list_for_each_entry_safe(h, h_tmp, &handler_list, node) {
list_del(&h->node);
kfree(h);
}
pr_info("demo_input_m2m exit\n");
}
module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("leo demo");
MODULE_DESCRIPTION("Demo: input_dev <-> input_handler many-to-many");
2.2 允许
c
/test # insmod demo_input_m2m.ko
[ 30.580747] demo_input_m2m init
[ 30.581018] Connect: dev=keyboard <-> handler=evdev
[ 30.581140] Connect: dev=keyboard <-> handler=mousedev
[ 30.581275] Connect: dev=mouse <-> handler=evdev
[ 30.581404] Device mouse bound to: evdev
[ 30.581569] Device keyboard bound to: mousedev evdev
/test #
/test #
三、对比 input 子系统的真实实现
-
真正的 input 子系统用
input_handle
作为中间结构,和上面 demo 一样。 -
input_register_device()
时,遍历所有 handler,看谁能处理这个 dev,就创建 handle 挂上。 -
input_register_handler()
时,也会遍历所有 dev,看谁能被处理,建立 handle。 -
多对多关系由 双向链表 + 中间对象 管理,支持设备和处理器的 动态添加删除。