Linux输入子系统框架原理解析_Linux

来源:脚本之家  责任编辑:小易  

linux已经驱基本自配置编译除非自另外再发驱网买发板都本回答被网友采纳,关系实际上就是关系模式2113在某一5261时刻的状态或内容。也就是4102说,关系模式是型,1653关系是它的值。关系模式是静态的、稳定的,而关系是动态的、随时间不断变化的,因为关系操作在不断地更新着数据库中的数据。但在实际当中,常常把关系模式和关系统称为关系,读者可以从上下文中加以区别www.zgxue.com防采集请勿采集本网。

input输入子系统框架

Linux中输入设备的事件类型有:EV_SYN 0x00 同步事件EV_KEY 0x01 按键事件EV_REL 0x02 相对坐标EV_ABS 0x03 绝对坐标EV_MSC 0x04 其它EV_LED 0x

linux输入子系统(linux input subsystem)从上到下由三层实现,分别为:输入子系统事件处理层(EventHandler)、输入子系统核心层(InputCore)和输入子系统设备驱动层。

Linux 和 Unix 文件系统被组织成一个有层次的树形结构。文件系统的最上层是 /,或称为 根目录。在 Unix 和 Linux 的设计理念中,一切皆为文件——包括

一个输入事件,如鼠标移动,键盘按键按下,joystick的移动等等通过 input driver -> Input core -> Event handler -> userspace 到达用户空间传给应用程序。

首先,对linux的内存要有整体认识:建议看《深入理解linux内核》,英文好的看原版,没有完全理解不要急,毕竟操作系统没有那么容易学。 其次,导致linux内存

【注意】keyboard.c不会在/dev/input下产生节点,而是作为ttyn终端(不包括串口终端)的输入。

在linux下,按键、触摸屏、鼠标等都可以利用input接口函数来实现设备驱动。 从上图可知: 输入子系统由三部分构成: 1 驱动 2 输入子系统 3 处理函数

驱动层

对于输入子系统设备驱动层而言,主要实现对硬件设备的读写访问,中断设置,并把硬件产生的事件转换为核心层定义的规范提交给事件处理层。将底层的硬件输入转化为统一事件形式,想输入核心(Input Core)汇报。

一、Linux device driver 的概念 系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。设备驱动程序为应用程序屏蔽

输入子系统核心层

对于核心层而言,为设备驱动层提供了规范和接口。设备驱动层只要关心如何驱动硬件并获得硬件数据(例如按下的按键数据),然后调用核心层提供的接口,核心层会自动把数据提交给事件处理层。它承上启下为驱动层提供输入设备注册与操作接口,如:input_register_device;通知事件处理层对事件进行处理;在/Proc下产生相应的设备信息。

事件处理层

对于事件处理层而言,则是用户编程的接口(设备节点),并处理驱动层提交的数据处理。主要是和用户空间交互(Linux中在用户空间将所有的设备都当作文件来处理,由于在一般的驱动程序中都有提供fops接口,以及在/dev下生成相应的设备文件nod,这些操作在输入子系统中由事件处理层完成)。

/dev/input目录下显示的是已经注册在内核中的设备编程接口,用户通过open这些设备文件来打开不同的输入设备进行硬件操作。

事件处理层为不同硬件类型提供了用户访问及处理接口。例如当我们打开设备/dev/input/mice时,会调用到事件处理层的Mouse Handler来处理输入事件,这也使得设备驱动层无需关心设备文件的操作,因为Mouse Handler已经有了对应事件处理的方法。

输入子系统由内核代码drivers/input/input.c构成,它的存在屏蔽了用户到设备驱动的交互细节,为设备驱动层和事件处理层提供了相互通信的统一界面。

由上图可知输入子系统核心层提供的支持以及如何上报事件到input event drivers。

作为输入设备的驱动开发者,需要做以下几步: 在驱动加载模块中,设置你的input设备支持的事件类型 注册中断处理函数,例如键盘设备需要编写按键的抬起、放下,触摸屏设备需要编写按下、抬起、绝对移动,鼠标设备需要编写单击、抬起、相对移动,并且需要在必要的时候提交硬件数据(键值/坐标/状态等等) 将输入设备注册到输入子系统中

///////////////////////////////////////////////////////////////////分割线/////////////////////////////////////////////////////////////////////////////////

输入核心提供了底层输入设备驱动程序所需的API,如分配/释放一个输入设备:

struct input_dev *input_allocate_device(void);

void input_free_device(struct input_dev *dev);

/** * input_allocate_device - allocate memory for new input device * * Returns prepared struct input_dev or NULL. * * NOTE: Use input_free_device() to free devices that have not been * registered; input_unregister_device() should be used for already * registered devices. */struct input_dev *input_allocate_device(void){ struct input_dev *dev; /*分配一个input_dev结构体,并初始化为0*/ dev = kzalloc(sizeof(struct input_dev), GFP_KERNEL); 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;}

注册/注销输入设备用的接口如下:

int __must_check input_register_device(struct input_dev *);

void input_unregister_device(struct input_dev *);

/** * input_register_device - register device with input core * @dev: device to be registered * * This function registers device with input core. The device must be * allocated with input_allocate_device() and all it's capabilities * set up before registering. * If function fails the device must be freed with input_free_device(). * Once device has been successfully registered it can be unregistered * with input_unregister_device(); input_free_device() should not be * called in this case. */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 成员来表示。具体类型在后面归纳。 /* Every input device generates EV_SYN/SYN_REPORT events. */ __set_bit(EV_SYN, dev->evbit); /* KEY_RESERVED is not supposed to be transmitted to userspace. */ __clear_bit(KEY_RESERVED, dev->keybit); /* Make sure that bitmasks not mentioned in dev->evbit are clean. */ input_cleanse_bitmasks(dev); //初始化 timer 定时器,用来处理重复点击按键。(去抖) /* * If delay and period are pre-set by the driver, then autorepeating * is handled by the driver itself and we don't do it in input.c. */ 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 ? 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;}

而对于所有的输入事件,内核都用统一的数据结构来描述,这个数据结构是input_event

/* * The event structure itself */struct input_event { struct timeval time; //<输入事件发生的时间 __u16 type; //<输入事件的类型 __u16 code; //<在输入事件类型下的编码 __s32 value; //<code的值};

输入事件的类型--input_event.type

/* * Event types */#define EV_SYN 0x00 //< 同步事件#define EV_KEY 0x01 //< 按键事件#define EV_REL 0x02 //<相对坐标(如:鼠标移动,报告相对最后一次位置的偏移) #define EV_ABS 0x03 //< 绝对坐标(如:触摸屏或操作杆,报告绝对的坐标位置) #define EV_MSC 0x04 //< 其它 #define EV_SW 0x05 //<开关 #define EV_LED 0x11 //<按键/设备灯 #define EV_SND 0x12 //<声音/警报 #define EV_REP 0x14 //<重复 #define EV_FF 0x15 //<力反馈 #define EV_PWR 0x16 //<电源 #define EV_FF_STATUS 0x17 //<力反馈状态 #define EV_MAX 0x1f //< 事件类型最大个数和提供位掩码支持 #define EV_CNT (EV_MAX+1)

Linux输入子系统提供了设备驱动层上报输入事件的函数

报告输入事件用的接口如下:

/* 报告指定type、code的输入事件 */void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);/* 报告键值 */static inline void input_report_key(struct input_dev *dev, unsigned int code, int value){ input_event(dev, EV_KEY, code, !!value);}/* 报告相对坐标 */static inline void input_report_rel(struct input_dev *dev, unsigned int code, int value){ input_event(dev, EV_REL, code, value);}/* 报告绝对坐标 */static inline void input_report_abs(struct input_dev *dev, unsigned int code, int value){ input_event(dev, EV_ABS, code, value);}...

当提交输入设备产生的输入事件之后,需要调用下面的函数来通知输入子系统,以处理设备产生的完整事件:

void input_sync(struct input_dev *dev);

【例子】驱动实现——报告结束input_sync()同步用于告诉input core子系统报告结束,触摸屏设备驱动中,一次点击的整个报告过程如下:

input_reprot_abs(input_dev,ABS_X,x); //x坐标input_reprot_abs(input_dev,ABS_Y,y); // y坐标input_reprot_abs(input_dev,ABS_PRESSURE,1);input_sync(input_dev);//同步结束

【例子】按键中断程序

//按键初始化static int __init button_init(void){//申请中断 if(request_irq(BUTTON_IRQ,button_interrupt,0,”button”,NUll)) return –EBUSY; set_bit(EV_KEY,button_dev.evbit); //支持EV_KEY事件 set_bit(BTN_0,button_dev.keybit); //支持设备两个键 set_bit(BTN_1,button_dev.keybit); // input_register_device(&button_dev);//注册input设备}

/*在按键中断中报告事件*/Static void button_interrupt(int irq,void *dummy,struct pt_regs *fp){ input_report_key(&button_dev,BTN_0,inb(BUTTON_PORT0));//读取寄存器BUTTON_PORT0的值 input_report_key(&button_dev,BTN_1,inb(BUTTON_PORT1)); input_sync(&button_dev);}

【小结】input子系统仍然是字符设备驱动程序,但是代码量减少很多,input子系统只需要完成两个工作:初始化和事件报告(这里在linux中是通过中断来实现的)。

Event Handler层解析

Input输入子系统数据结构关系图

input_handler结构体

struct input_handle;/** * struct input_handler - implements one of interfaces for input devices * @private: driver-specific data * @event: event handler. This method is being called by input core with * interrupts disabled and dev->event_lock spinlock held and so * it may not sleep * @filter: similar to @event; separates normal event handlers from * "filters". * @match: called after comparing device's id with handler's id_table * to perform fine-grained matching between device and handler * @connect: called when attaching a handler to an input device * @disconnect: disconnects a handler from input device * @start: starts handler for given handle. This function is called by * input core right after connect() method and also when a process * that "grabbed" a device releases it * @fops: file operations this driver implements * @minor: beginning of range of 32 minors for devices this driver * can provide * @name: name of the handler, to be shown in /proc/bus/input/handlers * @id_table: pointer to a table of input_device_ids this driver can * handle * @h_list: list of input handles associated with the handler * @node: for placing the driver onto input_handler_list * * Input handlers attach to input devices and create input handles. There * are likely several handlers attached to any given input device at the * same time. All of them will get their copy of input event generated by * the device. * * The very same structure is used to implement input filters. Input core * allows filters to run first and will not pass event to regular handlers * if any of the filters indicate that the event should be filtered (by * returning %true from their filter() method). * * Note that input core serializes calls to connect() and disconnect() * methods. */struct input_handler { void *private; void (*event)(struct input_handle *handle, unsigned int type, unsigned int code, int value); bool (*filter)(struct input_handle *handle, unsigned int type, unsigned int code, int value); bool (*match)(struct input_handler *handler, struct input_dev *dev); int (*connect)(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id); void (*disconnect)(struct input_handle *handle); void (*start)(struct input_handle *handle); const struct file_operations *fops; int minor; const char *name; const struct input_device_id *id_table; struct list_head h_list; struct list_head node;};

【例子】以evdev.c中的evdev_handler为例:

static struct input_handler evdev_handler = { .event = evdev_event, //<向系统报告input事件,系统通过read方法读取 .connect = evdev_connect, //<和input_dev匹配后调用connect构建 .disconnect = evdev_disconnect, .fops = &evdev_fops, //<event设备文件的操作方法 .minor = EVDEV_MINOR_BASE, //<次设备号基准值 .name = "evdev", .id_table = evdev_ids, //<匹配规则 };

输入设备驱动的简单案例

documentation/input/input-programming.txt文件,讲解了编写输入设备驱动程序的核心步骤。

Programming input drivers~~~~~~~~~~~~~~~~~~~~~~~~~1. Creating an input device driver~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~1.0 The simplest example~~~~~~~~~~~~~~~~~~~~~~~~Here comes a very simple example of an input device driver. The device hasjust one button and the button is accessible at i/o port BUTTON_PORT. Whenpressed or released a BUTTON_IRQ happens. The driver could look like:#include <linux/input.h>#include <linux/module.h>#include <linux/init.h>#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; 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; } 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);1.1 What the example does~~~~~~~~~~~~~~~~~~~~~~~~~First it has to include the <linux/input.h> file, which interfaces to theinput subsystem. This provides all the definitions needed.In the _init function, which is called either upon module load or whenbooting the kernel, it grabs the required resources (it should also checkfor the presence of the device).Then it allocates a new input device structure with input_allocate_device()and sets up input bitfields. This way the device driver tells the otherparts of the input systems what it is - what events can be generated oraccepted by this input device. Our example device can only generate EV_KEYtype events, and from those only BTN_0 event code. Thus we only set thesetwo bits. We could have used set_bit(EV_KEY, button_dev.evbit); set_bit(BTN_0, button_dev.keybit);as well, but with more than single bits the first approach tends to beshorter.Then the example driver registers the input device structure by calling input_register_device(&button_dev);This adds the button_dev structure to linked lists of the input driver andcalls device handler modules _connect functions to tell them a new inputdevice has appeared. input_register_device() may sleep and therefore mustnot be called from an interrupt or with a spinlock held.While in use, the only used function of the driver is button_interrupt()which upon every interrupt from the button checks its state and reports itvia the input_report_key()call to the input system. There is no need to check whether the interruptroutine isn't reporting two same value events (press, press for example) tothe input system, because the input_report_* functions check thatthemselves.Then there is the input_sync()call to tell those who receive the events that we've sent a complete report.This doesn't seem important in the one button case, but is quite importantfor for example mouse movement, where you don't want the X and Y valuesto be interpreted separately, because that'd result in a different movement.1.2 dev->open() and dev->close()~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~In case the driver has to repeatedly poll the device, because it doesn'thave an interrupt coming from it and the polling is too expensive to be doneall the time, or if the device uses a valuable resource (eg. interrupt), itcan use the open and close callback to know when it can stop polling orrelease the interrupt and when it must resume polling or grab the interruptagain. To do that, we would add this to our example driver:static int button_open(struct input_dev *dev){ 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; } return 0;}static void button_close(struct input_dev *dev){ free_irq(IRQ_AMIGA_VERTB, button_interrupt);}static int __init button_init(void){ ... button_dev->open = button_open; button_dev->close = button_close; ...}Note that input core keeps track of number of users for the device andmakes sure that dev->open() is called only when the first user connectsto the device and that dev->close() is called when the very last userdisconnects. Calls to both callbacks are serialized.The open() callback should return a 0 in case of success or any nonzero valuein case of failure. The close() callback (which is void) must always succeed.1.3 Basic event types~~~~~~~~~~~~~~~~~~~~~The most simple event type is EV_KEY, which is used for keys and buttons.It's reported to the input system via: input_report_key(struct input_dev *dev, int code, int value)See linux/input.h for the allowable values of code (from 0 to KEY_MAX).Value is interpreted as a truth value, ie any nonzero value means keypressed, zero value means key released. The input code generates events onlyin case the value is different from before.In addition to EV_KEY, there are two more basic event types: EV_REL andEV_ABS. They are used for relative and absolute values supplied by thedevice. A relative value may be for example a mouse movement in the X axis.The mouse reports it as a relative difference from the last position,because it doesn't have any absolute coordinate system to work in. Absoluteevents are namely for joysticks and digitizers - devices that do work in anabsolute coordinate systems.Having the device report EV_REL buttons is as simple as with EV_KEY, simplyset the corresponding bits and call the input_report_rel(struct input_dev *dev, int code, int value)function. Events are generated only for nonzero value.However EV_ABS requires a little special care. Before callinginput_register_device, you have to fill additional fields in the input_devstruct for each absolute axis your device has. If our button device had alsothe ABS_X axis: button_dev.absmin[ABS_X] = 0; button_dev.absmax[ABS_X] = 255; button_dev.absfuzz[ABS_X] = 4; button_dev.absflat[ABS_X] = 8;Or, you can just say: input_set_abs_params(button_dev, ABS_X, 0, 255, 4, 8);This setting would be appropriate for a joystick X axis, with the minimum of0, maximum of 255 (which the joystick *must* be able to reach, no problem ifit sometimes reports more, but it must be able to always reach the min andmax values), with noise in the data up to +- 4, and with a center flatposition of size 8.If you don't need absfuzz and absflat, you can set them to zero, which meanthat the thing is precise and always returns to exactly the center position(if it has any).1.4 BITS_TO_LONGS(), BIT_WORD(), BIT_MASK()~~~~~~~~~~~~~~~~~~~~~~~~~~These three macros from bitops.h help some bitfield computations: BITS_TO_LONGS(x) - returns the length of a bitfield array in longs for x bits BIT_WORD(x) - returns the index in the array in longs for bit x BIT_MASK(x) - returns the index in a long for bit x1.5 The id* and name fields~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~The dev->name should be set before registering the input device by the inputdevice driver. It's a string like 'Generic button device' containing auser friendly name of the device.The id* fields contain the bus ID (PCI, USB, ...), vendor ID and device IDof the device. The bus IDs are defined in input.h. The vendor and device idsare defined in pci_ids.h, usb_ids.h and similar include files. These fieldsshould be set by the input device driver before registering it.The idtype field can be used for specific information for the input devicedriver.The id and name fields can be passed to userland via the evdev interface.1.6 The keycode, keycodemax, keycodesize fields~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~These three fields should be used by input devices that have dense keymaps.The keycode is an array used to map from scancodes to input system keycodes.The keycode max should contain the size of the array and keycodesize thesize of each entry in it (in bytes).Userspace can query and alter current scancode to keycode mappings usingEVIOCGKEYCODE and EVIOCSKEYCODE ioctls on corresponding evdev interface.When a device has all 3 aforementioned fields filled in, the driver mayrely on kernel's default implementation of setting and querying keycodemappings.1.7 dev->getkeycode() and dev->setkeycode()~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~getkeycode() and setkeycode() callbacks allow drivers to override defaultkeycode/keycodesize/keycodemax mapping mechanism provided by input coreand implement sparse keycode maps.1.8 Key autorepeat~~~~~~~~~~~~~~~~~~... is simple. It is handled by the input.c module. Hardware autorepeat isnot used, because it's not present in many devices and even where it ispresent, it is broken sometimes (at keyboards: Toshiba notebooks). To enableautorepeat for your device, just set EV_REP in dev->evbit. All will behandled by the input system.1.9 Other event types, handling output events~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~The other event types up to now are:EV_LED - used for the keyboard LEDs.EV_SND - used for keyboard beeps.They are very similar to for example key events, but they go in the otherdirection - from the system to the input device driver. If your input devicedriver can handle these events, it has to set the respective bits in evbit,*and* also the callback routine: button_dev->event = button_event;int button_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);{ if (type == EV_SND && code == SND_BELL) { outb(value, BUTTON_BELL); return 0; } return -1;}This callback routine can be called from an interrupt or a BH (although thatisn't a rule), and thus must not sleep, and must not take too long to finish.input-programming.txt

该例子提供的案例代码描述了一个button设备,产生的事件通过BUTTON_PORT引脚获取,当有按下/释放发生时,BUTTON_IRQ被触发,以下是驱动的源代码:

#include <linux/input.h>#include <linux/module.h>#include <linux/init.h>#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子系统,向用户态程序提供按键输入信息。

一、Linux device driver 的概念  系统调用是2113操5261作系统内核和应用程4102序之间的接口,设备驱动程序是操作系统内核和机器硬1653件之间的接口。设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。设备驱动程序是内核的一部分,它完成以下的功能:  1、对设备初始化和释放;  2、把数据从内核传送到硬件和从硬件读取数据;  3、读取应用程序传送给设备文件的数据和回送应用程序请求的数据;  4、检测和处理设备出现的错误。  在Linux操作系统下有三类主要的设备文件类型,一是字符设备,二是块设备,三是网络设备。字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待。  已经提到,用户进程是通过设备文件来与实际的硬件打交道。每个设备文件都都有其文件属性(c/b),表示是字符设备还是块设备?另外每个文件都有两个设备号,第一个是主设备号,标识驱动程序,第二个是从设备号,标识使用同一个设备驱动程序的不同的硬件设备,比如有两个软盘,就可以用从设备号来区分他们。设备文件的的主设备号必须与设备驱动程序在登记时申请的主设备号一致,否则用户进程将无法访问到驱动程序。  最后必须提到的是,在用户进程调用驱动程序时,系统进入核心态,这时不再是抢先式调度。也就是说,系统必须在你的驱动程序的子函数返回后才能进行其他的工作。如果你的驱动程序陷入死循环,不幸的是你只有重新启动机器了,然后就是漫长的fsck。  二、实例剖析  我们来写一个最简单的字符设备驱动程序。虽然它什么也不做,但是通过它可以了解Linux的设备驱动程序的工作原理。把下面的C代码输入机器,你就会获得一个真正的设备驱动程序。  由于用户进程是通过设备文件同硬件打交道,对设备文件的操作方式不外乎就是一些系统调用,如 open,read,write,close…, 注意,不是fopen, fread,但是如何把系统调用和驱动程序关联起来呢?这需要了解一个非常关键的数据结构:  STruct file_operatiONs {  int (*seek) (struct inode * ,struct file *, off_t ,int);  int (*read) (struct inode * ,struct file *, char ,int);  int (*write) (struct inode * ,struct file *, off_t ,int);  int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);  int (*select) (struct inode * ,struct file *, int ,select_table *);  int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);  int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);  int (*open) (struct inode * ,struct file *);  int (*release) (struct inode * ,struct file *);  int (*fsync) (struct inode * ,struct file *);  int (*fasync) (struct inode * ,struct file *,int);  int (*check_media_change) (struct inode * ,struct file *);  int (*revalidate) (dev_t dev);  }  这个结构的每一个成员的名字都对应着一个系统调用。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。这是linux的设备驱动程序工作的基本原理。既然是这样,则编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。  下面就开始写子程序。  #include <linux/types.h> 基本的类型定义  #include <linux/fs.h> 文件系统使用相关的头文件  #include <linux/mm.h>  #include <linux/errno.h>  #include <asm/segment.h>  unsigned int test_major = 0;  static int read_test(struct inode *inode,struct file *file,char *buf,int count)  {  int left; 用户空间和内核空间  if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )  return -EFAULT;  for(left = count ; left > 0 ; left--)  {  __put_user(1,buf,1);  buf++;  }  return count;  }  这个函数是为read调用准备的。当调用read时,read_test()被调用,它把用户的缓冲区全部写1。buf 是read调用的一个参数。它是用户进程空间的一个地址。但是在read_test被调用时,系统进入核心态。所以不能使用buf这个地址,必须用__put_user(),这是kernel提供的一个函数,用于向用户传送数据。另外还有很多类似功能的函数。请参考,在向用户空间拷贝数据之前,必须验证buf是否可用。这就用到函数verify_area。为了验证BUF是否可以用。  static int write_test(struct inode *inode,struct file *file,const char *buf,int count)  {  return count;  }  static int open_test(struct inode *inode,struct file *file )  {  MOD_INC_USE_COUNT; 模块计数加以,表示当前内核有个设备加载内核当中去  return 0;  }  static void release_test(struct inode *inode,struct file *file )  {  MOD_DEC_USE_COUNT;  }  这几个函数都是空操作。实际调用发生时什么也不做,他们仅仅为下面的结构提供函数指针。  struct file_operations test_fops = {?  read_test,  write_test,  open_test,  release_test,  };  设备驱动程序的主体可以说是写好了。现在要把驱动程序嵌入内核。驱动程序可以按照两种方式编译。一种是编译进kernel,另一种是编译成模块(modules),如果编译进内核的话,会增加内核的大小,还要改动内核的源文件,而且不能动态的卸载,不利于调试,所以推荐使用模块方式。  int init_module(void)  {  int result;  result = register_chrdev(0, "test", &test_fops); 对设备操作的整个接口  if (result < 0) {  printk(KERN_INFO "test: can't get major number\n");  return result;  }  if (test_major == 0) test_major = result; /* dynamic */  return 0;  }  在用insmod命令将编译好的模块调入内存时,init_module 函数被调用。在这里,init_module只做了一件事,就是向系统的字符设备表登记了一个字符设备。register_chrdev需要三个参数,参数一是希望获得的设备号,如果是零的话,系统将选择一个没有被占用的设备号返回。参数二是设备文件名,参数三用来登记驱动程序实际执行操作的函数的指针。  如果登记成功,返回设备的主设备号,不成功,返回一个负值。  void cleanup_module(void)  {  unregister_chrdev(test_major,"test");  }  在用rmmod卸载模块时,cleanup_module函数被调用,它释放字符设备test在系统字符设备表中占有的表项。  一个极其简单的字符设备可以说写好了,文件名就叫test.c吧。  下面编译 :  $ gcc -O2 -DMODULE -D__KERNEL__ -c test.c –c表示输出制定名,自动生成.o文件  得到文件test.o就是一个设备驱动程序。  如果设备驱动程序有多个文件,把每个文件按上面的命令行编译,然后  ld ?-r ?file1.o ?file2.o ?-o ?modulename。  驱动程序已经编译好了,现在把它安装到系统中去。  $ insmod ?–f ?test.o  如果安装成功,在/proc/devices文件中就可以看到设备test,并可以看到它的主设备号。要卸载的话,运行 :  $ rmmod test  下一步要创建设备文件。  mknod /dev/test c major minor  c 是指字符设备,major是主设备号,就是在/proc/devices里看到的。  用shell命令  $ cat /proc/devices  就可以获得主设备号,可以把上面的命令行加入你的shell script中去。  minor是从设备号,设置成0就可以了。  我们现在可以通过设备文件来访问我们的驱动程序。写一个小小的测试程序。  #include <stdio.h>  #include <sys/types.h>  #include <sys/stat.h>  #include <fcntl.h>  main()  {  int testdev;  int i;  char buf[10];  testdev = open("/dev/test",O_RDWR);  if ( testdev == -1 )  {  printf("Cann't open file \n");  exit(0);  }  read(testdev,buf,10);  for (i = 0; i < 10;i++)  printf("%d\n",buf[i]);  close(testdev);  }  编译运行,看看是不是打印出全1   以上只是一个简单的演示。真正实用的驱动程序要复杂的多,要处理如中断,DMA,I/O port等问题。这些才是真正的难点。上述给出了一个简单的字符设备驱动编写的框架和原理,更为复杂的编写需要去认真研究LINUX内核的运行机制和具体的设备运行的机制等等。希望大家好好掌握LINUX设备驱动程序编写的方法,这个才1W字 难度太大。。2113。 而且5261说得明白都可以出书了。。。大体4102上就是挂载到操作系统的抽象接口。驱动1653是以模块化加载到操作系统中,首先init()函数启动模块,就是到相应的硬件驱动抽象接口注册,并进行一系列的抽象类的交互,如果是总线,则要向更高级的总线挂载,如PCI,没有2.4内核时代那么简单了哦,之后就是probe(),这个过程是在你要使用的时候启动的,是驱动程序和驱动设备互相绑定的一个过程,也就是特定的设备都会有相应的驱动来绑定,之后就是什么read,write,ioctl等一系列的操作,这些都是具体的硬件操作,但是必须给操作系统提供统一接口就是那些read,write。。来进行硬件封装。这些read,write,ioctl就是具体要参照芯片的datesheet操作而且要参考具体芯片的特性,例如nandflash,norflash,cfcard,sdcard每个都不一样。就如我们写裸机程序一样操作这些硬件,如果要加载操作系统,我们则必须要按照一定的规范写。吃饭了 就写这么多。。。本回答被网友采纳内容来自www.zgxue.com请勿采集。


  • 本文相关:
  • linux云主机安装pdo详细教程
  • linux下如何查杀stopped进程详解
  • linux中ssh免密通信的实现
  • centos系统搭建本地yum服务器的方法
  • linux7下虚拟主机的三种实现方式
  • keepalived实现nginx高可用
  • linux tee命令使用详解
  • linux下修改mac地址问题解决方法
  • centos 6.7 下安装 redis-3.2.5的步骤
  • linux系统下安装rz/sz命令及使用说明(详解)
  • 解释一下linux驱动程序结构框架及工作原理
  • Linux输入子系统、平台设备、平台驱动、混杂设备,...
  • 如何读取linux键值,输入子系统,key,dev/input/e...
  • 深度探索linux操作系统系统构建和原理解析 这本书...
  • linux驱动程序开发中, 输入子系统总共能产生哪些...
  • 给出LINUX系统基本构成框架 图或者语言描述
  • 如何通过阅读linux源代码深入学习linux内存子系统?
  • linux 怎么使用input子系统
  • linux驱动程序结构框架及工作原理分别是什么?
  • input 子系统应该理解到什么层次
  • 网站首页网页制作脚本下载服务器操作系统网站运营平面设计媒体动画电脑基础硬件教程网络安全星外虚拟主机华众虚拟主机linuxwin服务器ftp服务器dns服务器tomcat nginxzabbix云和虚拟化服务器其它首页服务器windows10安装linux子系统的两种方式(图文详解)linux时间子系统之时间的表示示例详解手把手教你启用win10的linux子系统(图文超详细)谈一谈linux系统重要的子目录问题win10安装linux子系统图文教程简单掌握linux系统中fork()函数创建子进程的用法linux云主机安装pdo详细教程linux下如何查杀stopped进程详解linux中ssh免密通信的实现centos系统搭建本地yum服务器的方法linux7下虚拟主机的三种实现方式keepalived实现nginx高可用linux tee命令使用详解linux下修改mac地址问题解决方法centos 6.7 下安装 redis-3.2.5的步骤linux系统下安装rz/sz命令及使用说明(详解)apache开启.htaccess及.htaccessservice temporarily unavailabllinux下实现免密码登录(超详细)apache rewrite url重定向功能的linux下用cron定时执行任务的方法apache性能测试工具ab使用详解centos 6.4安装配置lamp服务器(a阿里云服务器ping不通解决办法(centos+nginx+php+mysql详细配置apache you don""t have permisslinux vps备份教程 手动备份网站数据haproxy+keepalived实现高可用负载均衡(理linux 在线安装软件 gcc在线安装的操作方gerrit设置开机启动方法linux中install命令和cp命令的使用与区别总结centos7系统加固知识点windows系统下apache服务器无法启动的问题xshell实现windows上传文件到linux主机的ubuntu mysql更改tmp路径的方法linux ubuntu中安装、卸载和删除python-i
    免责声明 - 关于我们 - 联系我们 - 广告联系 - 友情链接 - 帮助中心 - 频道导航
    Copyright © 2017 www.zgxue.com All Rights Reserved