[Linux]input 子系统学习笔记(简单范例和四个基本函数)

本文详细解析了Linux输入子系统的实现原理,介绍了input_dev结构体的分配与注册过程,包括关键函数input_allocate_device()和input_register_device()的作用及其实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文唯一地址:http://blog.youkuaiyun.com/dearsq/article/details/51381671
欢迎转载,转载请注明!

输入子系统是为了将输入设备的功能呈现给应用程序
它支持 鼠标、键盘、蜂鸣器、触摸屏、传感器等需要不断上报数据的设备。

简单的例子

这个例子中的设备只有一个按键key,当key按下时,将产生中断,内核检测到中断并对其进行处理。

#include <asm/irq.h> 
#include <asm/io.h> 
static struct input_dev *button_dev;    /*输入设备结构体*/  
/*中断处理函数*/  
static irqreturn_t button_interrupt(int irq, void *dummy)                                                       
{  
    /*向输入子系统报告产生按键事件*/  
    input_report_key(button_dev, BTN_0, inb(BUTTON_PORT) & 1);     
    /*通知接收者,一个报告发送完毕*/  
    input_sync(button_dev);             
    return IRQ_HANDLED;  //?
}  
/*加载函数*/  
static int __init button_init(void)
{  
    int error;  
    /*申请中断处理函数*/ //返回0表示成功,返回-INVAL表示无效
    if(request_irq(BUTTON_IRQ,button_interrupt,0,"button",NULL))
    {  
        /*申请失败,则打印出错信息*/  
        printk(KERN_ERR "button.c: Can't allocate irq %d\n", button_irq);  
        return -EBUSY;  
    }  
    /*分配一个设备结构体*/ 
    //将在 sys/class/input/input-n 下面创建设备属性文件
    button_dev = input_allocate_device();     
    if (!button_dev)                        /*判断分配是否成功*/  
    {  
        printk(KERN_ERR "button.c: Not enough memory\n");  
        error = -ENOMEM;  
        goto err_free_irq;  
    }  
    button_dev->evbit[0] = BIT_MASK(EV_KEY);    /*设置按键信息*/  
    button_dev->keybit[BIT_WORD(BTN_0)] = BIT_MASK(BTN_0);  
    error = input_register_device(button_dev);  /*注册一个输入设备*/  
    if (error)  
    {  
        printk(KERN_ERR "button.c: Failed to register device\n");  
        goto err_free_dev;  
    }  
    return 0;  
    err_free_dev:                               /*以下是错误处理*/  
        input_free_device(button_dev);  
    err_free_irq:  
       free_irq(BUTTON_IRQ, button_interrupt);  
    return error;  
}  
static void __exit button_exit(void)            /*卸载函数*/  
{  
    input_unregister_device(button_dev);        /*注销按键设备*/  
    free_irq(BUTTON_IRQ, button_interrupt);     /*释放按键占用的中断线*/  
}  
module_init(button_init);  
module_exit(button_exit); 

从这个简单的例子中可以看到。
在初始化函数 button_init() 中注册了一个中断处理函数,然后调用 input_allocate_device() 函数分配了一个 input_dev 结构体,并调用 input_register_device() 对其进行注册。
在中断处理函数 button_interrupt() 中,实例将接收到的按键信息上报给 input 子系统,从而通过 input子系统,向用户态程序提供按键输入信息。

input 子系统的关键函数

input_allocate_device()
input_register_device()->input_attach_handler()->input_match_device()

input_allocate_device()

这个函数在内存中为输入设备结构体分配一个空间,并对其主要成员进行初始化。
其代码如下

struct input_dev *input_allocate_device(void)  
{  
    struct input_dev *dev;  
    dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL);              
    /*分配一个input_dev结构体,并初始化为0*/  
    if (dev) {  
        dev->dev.type = &input_dev_type;        /*初始化设备的类型*/  
        dev->dev.class = &input_class;          /*设置为输入设备类*/  
        device_initialize(&dev->dev);           /*初始化device结构*/  
        mutex_init(&dev->mutex);                /*初始化互斥锁*/  
        spin_lock_init(&dev->event_lock);       /*初始化事件自旋锁*/  
        INIT_LIST_HEAD(&dev->h_list);           /*初始化链表*/  
        INIT_LIST_HEAD(&dev->node);             /*初始化链表*/  
        __module_get(THIS_MODULE);              /*模块引用技术加1*/  
    }  
    return dev;  
} 

其返回一个指向 input_dev 类型的指针,该结构体是一个输入设备结构体,包含了输入设备的相关信息(按键码、设备名、支持的事件)。

input_register_device()

这个函数是输入子系统核心(input core)提供的函数。它将input_dev 结构体注册到输入子系统核心中(input_dev 结构体必须由 input_allocate_device()函数来分配的)。
如果函数注册失败,必须调用 input_free_device() 函数来释放分配的空间。
如果函数注册成功,在卸载函数中应该调用 input_unregister_device() 函数来注销输入设备结构体。

我们看一下函数原型:

int input_register_device(struct input_dev *dev)  
{ 
    //定义一些函数中将用到的局部变量
    static atomic_t input_no = ATOMIC_INIT(0);  
    struct input_handler *handler;
    const char *path;
    int error;
    //设置 input_dev 所支持的事件类型,由 evbit 成员来表示。具体类型在后面归纳。
    __set_bit(EV_SYN, dev->evbit);  
    //初始化 timer 定时器,用来处理重复点击按键。(去抖)
    init_timer(&dev->timer);  
    //如果 rep[REP_DELAY] 和 [REP_PERIOD] 没有设值,则赋默认值。为了去抖。
    if (!dev->rep[REP_DELAY] && !dev->rep[REP_PERIOD]) {  
        dev->timer.data = (long) dev;  
        dev->timer.function = input_repeat_key;  
        dev->rep[REP_DELAY] = 250;  
        dev->rep[REP_PERIOD] = 33;  
    }  
    //检查下列两个函数是否被定义,没有被定义则赋默认值。
    if (!dev->getkeycode)  
        dev->getkeycode = input_default_getkeycode;  //得到指定位置键值
    if (!dev->setkeycode)  
        dev->setkeycode = input_default_setkeycode;  //设置指定位置键值
    //设置 input_dev 中 device 的名字为 inputN
    //将如 input0 input1 input2 出现在 sysfs 文件系统中
    dev_set_name(&dev->dev, "input%ld",(unsigned long) atomic_inc_return(&input_no) - 1);  
    //将 input->dev 包含的 device 结构注册到 Linux 设备模型中。
    //并在文件系统中表现出来
    error = device_add(&dev->dev);  
    if (error)  
        return error;  
    //打印设备的路径并输出调试信息
    path = kobject_get_path(&dev->dev.kobj, GFP_KERNEL);  
    printk(KERN_INFO "input: %s as %s\n",  
        dev->name ? dev->name : 
            "Unspecified device" , path ? : "N/A");  
    kfree(path);  
    error = mutex_lock_interruptible(&input_mutex);  
    if (error) {  
       device_del(&dev->dev);  
       return error;  
    }  
    //将 input_dev 加入 input_dev_list 链表中(这个链表中包含有所有 input 设备)
    list_add_tail(&dev->node, &input_dev_list);  
    list_for_each_entry(handler, &input_handler_list, node);
    //调用 input_attatch_handler()函数匹配 handler 和 input_dev。
    //这个函数很重要,在后面单独分析。
    input_attach_handler(dev, handler);  
    input_wakeup_procfs_readers();  
    mutex_unlock(&input_mutex);  
    return 0;  
} 

给 evbit 设置的,input_dev所支持的事件类型:

#define EV_SYN          0x00    /*表示设备支持所有的事件*/  
#define EV_KEY          0x01    /*键盘或者按键,表示一个键码*/  
#define EV_REL          0x02    /*鼠标设备,表示一个相对的光标位置结果*/  
#define EV_ABS          0x03    /*手写板产生的值,其是一个绝对整数值*/  
#define EV_MSC          0x04    /*其他类型*/  
#define EV_LED          0x11    /*LED灯设备*/  
#define EV_SND          0x12    /*蜂鸣器,输入声音*/  
#define EV_REP          0x14    /*允许重复按键类型*/  
#define EV_PWR          0x16    /*电源管理事件*/ 

input_attatch_handler()

这个函数用来匹配 input_dev 和 handler,匹配成功才进行关联。
函数代码如下

static int input_attach_handler(struct input_dev *dev, 
struct   input_handler *handler)  
{  
    // input_device_id 这个结构体表示设备的标识,存储了设备信息。
    const struct input_device_id *id;       /*输入设备的指针*/  
    int error;  
    //先判断 handler 的 blacklist 有无赋值,然后判断是否匹配
    //blacklist 是一个 input_device_id *类型,指向了一个表,表中存放的是该驱动程序应该忽略的设备
    if (handler->blacklist && input_match_device(handler->blacklist,        dev))  
        return -ENODEV;                     
    /*** 设备和处理函数之间的匹配 ***/
    //匹配 handler->id_table指向的列表中的设备 和 dev->id 数据
    id = input_match_device(handler->id_table, dev);  
    if (!id)  
        return -ENODEV;  
    //匹配成功则调用 handler->connect,连接 handler 和 input_dev
    error = handler->connect(handler, dev, id);/*连接设备和处理函数*/  
    if (error && error != -ENODEV)  
        printk(KERN_ERR  
            "input: failed to attach handler %s to device %s, "  
            "error: %d\n",  
            handler->name, kobject_name(&dev->dev.kobj), error);  
    return error;  
} 

input_device_id 结构体的定义:

struct input_device_id {
    kernel_ulong_t flags;           /*标志信息*/  
    __u16 bustype;                  /*总线类型*/  
    __u16 vendor;                   /*制造商ID*/  
    __u16 product;                  /*产品ID*/  
    __u16 version;                  /*版本号*/  
    ...  
    kernel_ulong_t driver_info;     /*驱动额外的信息*/  
}; 

input_match_device()

这个函数用来将 input_dev 和 handler 进行匹配。
handler->id_table 中定义了其支持 input_dev 设备。

static const struct input_device_id *input_match_device(const struct  
    input_device_id *id,struct input_dev *dev)  
{  
    int i;  
    //匹配 id 和 dev->id 中的信息
    for (; id->flags || id->driver_info; id++) {  
        if (id->flags & INPUT_DEVICE_ID_MATCH_BUS)  
            if (id->bustype != dev->id.bustype)  //总线类型
                continue;  
        if (id->flags & INPUT_DEVICE_ID_MATCH_VENDOR)  
            if (id->vendor != dev->id.vendor)  //厂商信息
                continue;  
        if (id->flags & INPUT_DEVICE_ID_MATCH_PRODUCT)  
            if (id->product != dev->id.product)  //匹配设备号
               continue;  
        if (id->flags & INPUT_DEVICE_ID_MATCH_VERSION)  
            if (id->version != dev->id.version)  //匹配版本号
               continue;  
        MATCH_BIT(evbit,  EV_MAX);  
        MATCH_BIT(keybit, KEY_MAX);  
        MATCH_BIT(relbit, REL_MAX);  
        MATCH_BIT(absbit, ABS_MAX);  
        MATCH_BIT(mscbit, MSC_MAX);  
        MATCH_BIT(ledbit, LED_MAX);  
        MATCH_BIT(sndbit, SND_MAX);  
        MATCH_BIT(ffbit,  FF_MAX);  
        MATCH_BIT(swbit,  SW_MAX);  
        return id;  
    }  
    return NULL;  
} 

在上面,只有 flags 中的信息匹配成功,或者 flags 没有定义才会调用下面。

#define MATCH_BIT(bit, max) \  
        for (i = 0; i < BITS_TO_LONGS(max); i++) \  
            if ((id->bit[i] & dev->bit[i]) != id->bit[i]) \  
                break; \  
        if (i != BITS_TO_LONGS(max)) \  
            continue; 

从宏定义中可以看到,
只有当 input device和input handler 的 ID 成员在 evbit、keybit、… swbit 项相同才会匹配成功。而且匹配的顺序是从evbit、keybit到swbit。只要有一项不同,就会循环到ID中的下一项进行比较。

总结

在 input 的分配和注册中,我们分析了四个函数。
1. input_allocate_device 在内存中为输入设备结构体分配空间并进行初始化。
2. input_register_device()->input_attach_handler()->input_match_device()
input_register_device
将input_dev 结构体注册到输入子系统核心中。主要操作是 初始化 input_dev 并将其 device_add 进 Linux 设备驱动模型中(在文件系统中创建 inputN 等文件);打印其路径;调用 input_attach_handler 匹配 handler 和 input_dev。
input_attach_handler
匹配 handler 和 input_dev。主要操作是,判断 dev 在不在 devices 的黑名单中,不在就 调用 input_match_device 进行匹配,成功就调用 handler->connect 连接设备和处理函数。
input_match_device
真正的 将 input_dev 和 handler 进行匹配。主要操作是匹配 id 和 dev->id 中的信息。包括 bustype、vendor、product、version 等;再匹配 evbit 事件类型、keybit 按键类型 等。

本文唯一地址:http://blog.youkuaiyun.com/dearsq/article/details/51381671
欢迎转载,转载请注明!

<think>我们正在处理两个问题: 1. 用户当前的问题:如何在Dymola中将一个模块的输出连接到另一个模块的输入? 2. 之前的问题中,用户遇到了关于FanSystemOperation.bisectionSolver的报错,我们已经给出了解答。但当前问题是一个新的独立问题。 根据用户当前的问题,我们需要提供关于Dymola中连接模块的指导。 注意:用户提供了两个引用,但引用内容与当前问题不直接相关。引用[1]是Dymola用户手册的入门教程,引用[2]是用户的学习笔记资源分享。我们可以建议用户参考官方手册,但不需要在回答中直接引用这些内容,因为用户的问题非常基础,且引用中并没有具体讲如何连接。 因此,我们将重点回答如何连接模块。 步骤: 1. 在Dymola中,模块之间的连接是通过连线(connect)语句实现的。 2. 每个模块都有输入(input输出(output)端口(或其他类型的接口,如物理连接器)。 3. 连接时,确保两个端口的类型兼容(即相同的变量类型因果关系)。 具体操作(在Dymola图形界面中): - 打开模型图(Diagram View)。 - 从组件库(Library)中拖放两个模块(例如,模块A模块B)到模型图中。 - 将鼠标移动到模块A的输出端口(通常是一个小三角形或箭头,标注为输出变量名),点击并拖动到模块B的输入端口(同样是一个小三角形或箭头,标注为输入变量名)。 - 释放鼠标,Dymola会自动绘制一条连接线。 在文本模式(Text View)中: - 打开模型的文本视图。 - 在equation部分,使用connect语句连接两个端口,例如: ```modelica equation connect(moduleA.outputPort, moduleB.inputPort); ``` 注意事项: - 确保连接的两个端口类型一致(例如,都是RealInput或RealOutput)。 - 如果端口是物理连接器(如Modelica.Fluid.Interfaces.FluidPort),则连接会传递多个变量(如压力、流量等)。 - 连接输入到输出,不能将两个输入或两个输出直接连接。 示例模型: 假设我们有两个模块:一个正弦信号源(Modelica.Blocks.Sources.Sine)一个增益模块(Modelica.Blocks.Math.Gain)。连接方式如下: 图形界面: - 拖放Sine模块Gain模块。 - 将Sine模块的y(输出)连接到Gain模块的u(输入)。 文本视图: ```modelica model SineGainExample Modelica.Blocks.Sources.Sine sine(freqHz=1); Modelica.Blocks.Math.Gain gain(k=2); equation connect(sine.y, gain.u); end SineGainExample; ``` 如果用户遇到连接问题,常见原因包括: - 端口类型不匹配(例如,尝试连接一个Real输出到一个Boolean输入)。 - 在同一个模型内多次连接同一个输入端口(一个输入只能连接一个输出)。 - 未声明连接器(如果使用自定义模块,确保输入输出端口已正确定义)。 建议: - 对于初学者,使用图形界面连接更直观。 - 参考Dymola自带的示例模型(如Modelica标准库中的例子)学习连接方法。 由于用户之前遇到了函数内输入变量赋值的问题,但当前问题与之前无关,我们专注于当前问题。 最后,生成相关问题。 注意:按照要求,数学表达式使用LaTeX格式,但当前问题不涉及数学表达式,所以主要用代码块文字描述。</think>在 Dymola 中将一个模块的输出连接到另一个模块的输入是建模的核心操作,以下是详细步骤注意事项: ### 🔌 连接模块的三种方法 #### 1. **图形界面连接(推荐初学者)** ```modelica // 示例:连接正弦信号源到增益模块 Modelica.Blocks.Sources.Sine sineSource(freqHz=1); // 信号源模块 Modelica.Blocks.Math.Gain gainBlock(k=2); // 增益模块 equation connect(sineSource.y, gainBlock.u); // 关键连接语句 ``` - **操作步骤**: 1. 将两个模块拖入 Diagram 视图 2. 鼠标悬停在源模块输出端口(如 `sineSource.y`) 3. 拖动连线至目标模块输入端口(如 `gainBlock.u`) 4. Dymola 会自动生成 `connect()` 语句 #### 2. **文本模式直接连接** ```modelica model SystemConnection SourceModule source; // 自定义源模块 ProcessorModule processor; // 自定义处理模块 equation processor.input = source.output; // 直接赋值连接 end SystemConnection; ``` - **适用场景**:简单变量传递(非物理连接器) - **注意**:需确保变量维度匹配 #### 3. **物理端口连接(针对多变量系统)** ```modelica model FluidSystem Pump pump; Valve valve; equation connect(pump.port_out, valve.port_in); // 连接流体端口 end FluidSystem; ``` - **特点**:自动传递压力、流量等关联变量 - **要求**:端口必须使用相同连接器类型(如 `Modelica.Fluid.Interfaces.FluidPort`) ### ⚠️ 常见错误及解决方法 | 错误类型 | 原因 | 解决方案 | |---------|------|---------| | **类型不匹配** | 输出/输入变量类型不一致 | 检查变量类型(Real/Integer/Boolean) | | **维度不一致** | 向量/矩阵维度不同 | 使用 `connect(vectorA[1:3], vectorB[4:6])` 指定子集 | | **连接器不兼容** | 物理端口定义不一致 | 确保连接器继承自相同基类 | | **多重连接冲突** | 单个输入被多次连接 | 每个输入只能连接一个输出源 | ### 🔧 调试技巧 1. **连接验证**:右键点击连线 → "Check Connection" 验证兼容性 2. **信号追踪**:右键变量 → "Trace Variable" 查看连接路径 3. **类型检查**:在文本视图按 F8 检查类型一致性 4. **可视化验证**:运行瞬态仿真后,在绘图窗口拖放变量观察关联性 > **最佳实践**:对于复杂系统,建议采用分层连接:先建立子系统内部连接,再通过顶层接口连接子系统[^1]。参考 Dymola 自带示例库(`File > Open > Modelica Standard Library`)中的连接案例。 --- ### 📚 学习资源推荐 1. **官方教程**:安装目录下的 `Documentation/DymolaUserManual.pdf`(基础连接操作详见第4章) 2. **连接器规范**:查看 `Modelica.Icons` 包中的连接器设计范例 3. **视频教程**:Dassault 官网提供的 "Connecting Components" 系列视频 ```modelica // 完整连接示例:温度控制系统 model TemperatureControl Sources.HeatSource heat; // 热源模块 Sensors.TemperatureSensor sensor; // 温度传感器 Controllers.PID controller; // PID控制器 equation connect(heat.heatOutput, sensor.heatInput); // 物理连接 connect(sensor.T, controller.measured); // 信号连接 connect(controller.output, heat.powerInput); // 闭环控制 end TemperatureControl; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值