S3C2440按键中断驱动程序的设计.doc_第1页
S3C2440按键中断驱动程序的设计.doc_第2页
S3C2440按键中断驱动程序的设计.doc_第3页
S3C2440按键中断驱动程序的设计.doc_第4页
S3C2440按键中断驱动程序的设计.doc_第5页
已阅读5页,还剩15页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

S3C2440按键驱动的设计(内核)下图为S3C2440的按键连接电路图:在开始设计程序之前介绍一下与Linux设备中断处理程序相关的知识。首先是申请与释放IRQ的API request_irq()和free_irq(),request_irq()的原型为:int request_irq(unsigned int irq,void(*handler)(int irq,void *dev_id,struct pt_regs *regs),unsigned long irqflags,const char *devname,void *dev_id);irq是要申请的硬件中断号;handler是向系统登记的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递; irqflags是中断处理的属性,若设置SA_INTERRUPT,表明中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,而慢速处理程序不屏蔽;若设置SA_SHIRQ,则多个设备共享中断;dev_id在中断共享时会用到,一般设置为这个设备的device结构本身或者NULL。free_irq()的原型为: void free_irq(unsigned int irq,void *dev_id);下面我就开始写驱动程序,源码如下(详解在源码后面说明):#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEVICE_NAME IRQ-Teststruct button_irq_desc int irq; int pin; int pin_setting; int number; char *name;static struct button_irq_desc button_irqs = IRQ_EINT1,S3C2410_GPF1,S3C2410_GPF1_EINT1,0, KEY1, /* K1 */IRQ_EINT4,S3C2410_GPF4,S3C2410_GPF4_EINT4,1, KEY2, /* K2 */IRQ_EINT2,S3C2410_GPF2,S3C2410_GPF2_EINT2,2, KEY3, /* K3 */IRQ_EINT0,S3C2410_GPF0,S3C2410_GPF0_EINT0,3, KEY4, /* K4 */;static volatile char key_values = 0, 0, 0, 0;static DECLARE_WAIT_QUEUE_HEAD(button_waitq);static volatile int ev_press = 0;static irqreturn_t irq_interrupt(int irq, void *dev_id)struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id;int down;down = !s3c2410_gpio_getpin(button_irqs-pin);if (down != (key_valuesbutton_irqs-number & 1)key_valuesbutton_irqs-number = 0 + down;ev_press = 1;wake_up_interruptible(&button_waitq);return IRQ_RETVAL(IRQ_HANDLED);static int tq2440_irq_open(struct inode *inode, struct file *file)int i;int err = 0;for (i = 0; i sizeof(button_irqs)/sizeof(button_irqs0); i+)if (button_irqsi.irq = 0; i-)if (button_irqsi.irq 0)continue;disable_irq(button_irqsi.irq);free_irq(button_irqsi.irq, (void *)&button_irqsi);return -EBUSY;ev_press = 1;return 0;static int tq2440_irq_close(struct inode *inode, struct file *file)int i;for (i = 0; i sizeof(button_irqs)/sizeof(button_irqs0); i+)if (button_irqsi.irq f_flags & O_NONBLOCK)return -EAGAIN;elsewait_event_interruptible(button_waitq, ev_press);ev_press = 0;err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count);return err ? -EFAULT : min(sizeof(key_values), count);static unsigned int tq2440_irq_poll( struct file *file, struct poll_table_struct *wait)unsigned int mask = 0;poll_wait(file, &button_waitq, wait);if (ev_press)mask |= POLLIN | POLLRDNORM;return mask;static struct file_operations dev_fops = .owner= THIS_MODULE,.open= tq2440_irq_open,.release= tq2440_irq_close, .read= tq2440_irq_read,.poll= tq2440_irq_poll,;static struct miscdevice misc = .minor = MISC_DYNAMIC_MINOR,.name = DEVICE_NAME,.fops = &dev_fops,;static int _init dev_init(void)int ret;ret = misc_register(&misc);printk (DEVICE_NAME initializedn);return ret;static void _exit dev_exit(void)misc_deregister(&misc);module_init(dev_init);module_exit(dev_exit);MODULE_LICENSE(GPL);MODULE_AUTHOR(www./);MODULE_DESCRIPTION(IRQ Test for EmbedSky TQ2440 Board);下面是对这个驱动代码的的详细解释:头文件的解释:#include所有模块都需要这个头文件#includeinit和exit相关宏#include 声明了printk()这个内核态用的函数#include 文件系统有关的,结构体file_operations在头文件 linux/fs.h中定义#include 寄存器设置#include Linux 中的用户和内核态内存交互函数(这些函数在 include/asm/uaccess.h 中被声 明): unsigned long copy_from_user(void *to, const void *from, unsigned long n); unsigned long copy_to_user (void * to, void * from, unsigned long len)#include 在这个头文件中主要是misc(混合)设备注册和注销linux中用struct miscdevice来描述一个混杂设备#include Linux中断定义#include arm中断定义,如中断号的定义IRQ_EINT1#include interrupt.h中包含了与中断相关的大部分宏及struct结构的定义,如request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);#include IO空间的物理地址定义#include 延迟函数的定义声明(本程序未用延迟函数,所以这个头文件不是必须的)#include 本例中定义了POLLIN | POLLRDNORM#include 通常在Linux中,把SoC系统中集成的独立外设单元(如:I2C、IIS、RTC、看门狗等)都被当作平台设备来处理。在Linux中用platform_device结构体来描述一个平台设备,在内核中定义在:include/linux/platform_device.h中(本例中这个头文件是非必须的,因为没有把这个中断实现定义为平台设备,如果要注册为平台设备,这个头文件就需要包含进来了)#include 注册字符设备(在本例中,这个头文件也是非必须的)下面三段代码在设计中不是必须的:MODULE_LICENSE(GPL)用于声明模块的许可证MODULE_AUTHOR(www./)声明模块的作者MODULE_DESCRIPTION(IRQ Test for EmbedSky TQ2440 Board)对模块的描述代码的说明:#define DEVICE_NAME IRQ-Test加载模式后,执行“cat /proc/devices命令看到的设备名称struct button_irq_desc int irq; int pin; int pin_setting; int number; char *name;定义了一个类型名为button_irq_desc的结构体类型,由它把按钮中断的信息综合起来int irq 中断号,中断号唯一表示一个中断int pin; 中断控制的寄存器,该寄存器的值由中断引脚设置,我们希望从该寄存器读出控制信息int pin_setting;中断的引脚,该引脚的电平由按键来控制,从而最终我们由按键控制了寄存器的值int number;编号char *name;名称static struct button_irq_desc button_irqs = IRQ_EINT1,S3C2410_GPF1,S3C2410_GPF1_EINT1,0, KEY1, /* K1 */IRQ_EINT4,S3C2410_GPF4,S3C2410_GPF4_EINT4,1, KEY2, /* K2 */IRQ_EINT2,S3C2410_GPF2,S3C2410_GPF2_EINT2,2, KEY3, /* K3 */IRQ_EINT0,S3C2410_GPF0,S3C2410_GPF0_EINT0,3, KEY4, /* K4 */;定义了一个button_irq_desc结构体类型的数组变量button_irq,并对结构体的各个元素赋值。注:static的说明:Linux内核是一个十分庞大的系统。随着Linux内核的发展,其核心代码包括几千个文件,代码量超过百万行。对于用C语言编写的大型程序,需要解决的一个问题就是“名字空间污染”。由于C语言的函数和全局变量的作用空间都是全局的,在另外一个文件中,使用extern关键字,就可以实现对于其他文件中的全局变量和函数的访问。虽然这样很方便,但是源码中多个文件全局变量和函数定义的名称不允许重复;否则,编译器在连接时,就会报告命名重复的错误。这就是“名字空间污染”的问题。当系统庞大并且由多个程序员共同开发时,这个问题就更加突出。因此,需要引用一些封装的特性,限制源码中函数和变量作用的空间。C语言中的static关键字解决了这个问题。从上面代码中可以看到所有的全局变量和函数都是用static关键字修饰的。这是一个很好的习惯。被static关键字修饰的函数和全局变量,其名字作用的范围仅仅是当前的文件,而不是整个系统。static volatile char key_values = 0, 0, 0, 0;数组中是否有数据的标志,0表示无数据可读,1表示有数据可读。注:volatile就象大家更熟悉的const一样,volatile是一个类型修饰符(type specifier)。它是被设计用来修饰被不同线程访问和修改的变量。如果没有volatile,基本上会导致这样的结果:要么无法编写多线程程序,要么编译器失去大量优化的机会。下面我们来一个个说明。 考虑下面的代码:class Gadget public: void Wait() while (!flag_) Sleep(1000); / sleeps for 1000 milliseconds void Wakeup() flag_ = true; . private: bool flag_; ; 上面代码中Gadget:Wait的目的是每过一秒钟去检查一下flag_成员变量,当flag_被另一个线程设为true时,该函数才会返回。至少这是程序作者的意图,然而,这个Wait函数是错误的。 假设编译器发现Sleep(1000)是调用一个外部的库函数,它不会改变成员变量flag_,那么编译器就可以断定它可以把flag_缓存在寄存器中,以后可以访问该寄存器来代替访问较慢的主板上的内存。这对于单线程代码来说是一个很好的优化,但是在现在这种情况下,它破坏了程序的正确性:当你调用了某个Gadget的Wait函数后,即使另一个线程调用了Wakeup,Wait还是会一直循环下去。这是因为flag_的改变没有反映到缓存它的寄存器中去。编译器的优化未免有点太乐观了。 在大多数情况下,把变量缓存在寄存器中是一个非常有价值的优化方法,如果不用的话很可惜。C和C+给你提供了显式禁用这种缓存优化的机会。如果你声明变量是使用了volatile修饰符,编译器就不会把这个变量缓存在寄存器里每次访问都将去存取变量在内存中的实际位置。这样你要对Gadget的Wait/Wakeup做的修改就是给flag_加上正确的修饰: class Gadget public: . as above . private: volatile bool flag_; ; static DECLARE_WAIT_QUEUE_HEAD(button_waitq);睡眠的介绍对于一个进程睡眠意味着什么? 当一个进程被置为睡眠, 它被标识为处于一个特殊的状态并且从调度器的运行队列中去除. 直到发生某些事情改变了那个状态。这个进程将不被在任何 CPU 上调度, 并且, 因此, 将不会运行. 一个睡着的进程已被搁置到系统的一边, 等待以后发生事件.对于一个 Linux 驱动使一个进程睡眠是一个容易做的事情. 但是, 有几个规则必须记住以安全的方式编码睡眠.这些规则的第一个是: 当你运行在原子上下文时不能睡眠. 我们在第 5 章介绍过原子操作; 一个原子上下文只是一个状态, 这里多个步骤必须在没有任何类型的并发存取的情况下进行. 这意味着, 对于睡眠, 是你的驱动在持有一个自旋锁, seqlock, 或者 RCU 锁时不能睡眠. 如果你已关闭中断你也不能睡眠. 在持有一个旗标时睡眠是合法的, 但是你应当仔细查看这样做的任何代码. 如果代码在持有一个旗标时睡眠, 任何其他的等待这个旗标的线程也睡眠. 因此发生在持有旗标时的任何睡眠应当短暂, 并且你应当说服自己, 由于持有这个旗标, 你不能阻塞这个将最终唤醒你的进程.另一件要记住的事情是, 当你醒来, 你从不知道你的进程离开 CPU 多长时间或者同时已经发生了什么改变. 你也常常不知道是否另一个进程已经睡眠等待同一个事件; 那个进程可能在你之前醒来并且获取了你在等待的资源. 结果是你不能关于你醒后的系统状态做任何的假设, 并且你必须检查来确保你在等待的条件是, 确实, 真的.一个另外的相关的点, 当然, 是你的进程不能睡眠除非确信其他人, 在某处的, 将唤醒它. 做唤醒工作的代码必须也能够找到你的进程来做它的工作. 确保一个唤醒发生, 是深入考虑你的代码和对于每次睡眠, 确切知道什么系列的事件将结束那次睡眠. 使你的进程可能被找到, 真正地, 通过一个称为等待队列的数据结构实现的. 一个等待队列就是它听起来的样子:一个进程列表, 都等待一个特定的事件.在 Linux 中, 一个等待队列由一个等待队列头来管理, 一个 wait_queue_head_t 类型的结构, 定义在中. 一个等待队列头可被定义和初始化, 使用:DECLARE_WAIT_QUEUE_HEAD(name);或者动态地, 如下:wait_queue_head_t my_queue;init_waitqueue_head(&my_queue);我们将很快返回到等待队列结构, 但是我们知道了足够多的来首先看看睡眠和唤醒.6.2.2. 简单睡眠当一个进程睡眠, 它这样做以期望某些条件在以后会成真. 如我们之前注意到的, 任何睡眠的进程必须在它再次醒来时检查来确保它在等待的条件真正为真. Linux 内核中睡眠的最简单方式是一个宏定义, 称为 wait_event(有几个变体); 它结合了处理睡眠的细节和进程在等待的条件的检查. wait_event 的形式是:wait_event(queue, condition)wait_event_interruptible(queue, condition)wait_event_timeout(queue, condition, timeout)wait_event_interruptible_timeout(queue, condition, timeout)在所有上面的形式中, queue 是要用的等待队列头. 注意它是通过值传递的. 条件是一个被这个宏在睡眠前后所求值的任意的布尔表达式; 直到条件求值为真值, 进程继续睡眠. 注意条件可能被任意次地求值, 因此它不应当有任何边界效应.如果你使用 wait_event, 你的进程被置为不可中断地睡眠, 如同我们之前已经提到的, 它常常不是你所要的. 首选的选择是 wait_event_interruptible, 它可能被信号中断. 这个版本返回一个你应当检查的整数值; 一个非零值意味着你的睡眠被某些信号打断, 并且你的驱动可能应当返回 -ERESTARTSYS. 最后的版本(wait_event_timeout 和 wait_event_interruptible_timeout)等待一段有限的时间; 在这个时间期间(以嘀哒数表达的, 我们将在第 7 章讨论)超时后, 这个宏返回一个 0 值而不管条件是如何求值的.图片的另一半, 当然, 是唤醒. 一些其他的执行线程(一个不同的进程, 或者一个中断处理, 也许)必须为你进行唤醒, 因为你的进程, 当然, 是在睡眠. 基本的唤醒睡眠进程的函数称为 wake_up. 它有几个形式(但是我们现在只看其中 2 个):void wake_up(wait_queue_head_t *queue);void wake_up_interruptible(wait_queue_head_t *queue);wake_up 唤醒所有的在给定队列上等待的进程(尽管这个情形比那个要复杂一些, 如同我们之后将见到的). 其他的形式(wake_up_interruptible)限制它自己到处理一个可中断的睡眠. 通常, 这 2 个是不用区分的(如果你使用可中断的睡眠); 实际上, 惯例是使用 wake_up 如果你在使用 wait_event , wake_up_interruptible 如果你在使用 wait_event_interruptible.我们现在知道足够多来看一个简单的睡眠和唤醒的例子. 在这个例子代码中, 你可找到一个称为 sleepy 的模块. 它实现一个有简单行为的设备:任何试图从这个设备读取的进程都被置为睡眠. 无论何时一个进程写这个设备, 所有的睡眠进程被唤醒. 这个行为由下面的 read 和 write 方法实现:static DECLARE_WAIT_QUEUE_HEAD(wq);static int flag = 0;ssize_t sleepy_read (struct file *filp, char _user *buf, size_t count, loff_t *pos) printk(KERN_DEBUG process %i (%s) going to sleepn, current-pid, current-comm); wait_event_interruptible(wq, flag != 0); flag = 0; printk(KERN_DEBUG awoken %i (%s)n, current-pid, current-comm); return 0; /* EOF */ssize_t sleepy_write (struct file *filp, const char _user *buf, size_t count, loff_t *pos) printk(KERN_DEBUG process %i (%s) awakening the readers.n, current-pid, current-comm); flag = 1; wake_up_interruptible(&wq); return count; /* succeed, to avoid retrial */注意这个例子里 flag 变量的使用. 因为 wait_event_interruptible 检查一个必须变为真的条件, 我们使用 flag 来创建那个条件.有趣的是考虑当 sleepy_write 被调用时如果有 2 个进程在等待会发生什么. 因为 sleepy_read 重置 flag 为 0 一旦它醒来, 你可能认为醒来的第 2 个进程会立刻回到睡眠. 在一个单处理器系统, 这几乎一直是发生的事情. 但是重要的是要理解为什么你不能依赖这个行为. wake_up_interruptible 调用将使 2 个睡眠进程醒来. 完全可能它们都注意到 flag 是非零, 在另一个有机会重置它之前. 对于这个小模块, 这个竞争条件是不重要的. 在一个真实的驱动中, 这种竞争可能导致少见的难于查找的崩溃. 如果正确的操作要求只能有一个进程看到这个非零值, 它将必须以原子的方式被测试. 我们将见到一个真正的驱动如何处理这样的情况. 但首先我们必须开始另一个主题.static volatile int ev_press = 0;定义了一个整型变量用于判定按键是否被按下,0未按下,1按下数组可读标志位ev_pressstatic irqreturn_t irq_interrupt(int irq, void *dev_id)struct button_irq_desc *button_irqs = (struct button_irq_desc *)dev_id;int down;down = !s3c2410_gpio_getpin(button_irqs-pin);if (down != (key_valuesbutton_irqs-number & 1)key_valuesbutton_irqs-number = 0 + down;ev_press = 1;wake_up_interruptible(&button_waitq);return IRQ_RETVAL(IRQ_HANDLED);定义了一个返回值为irqreturn_t类型的中断服务程序。即当检测到有中断时,就会执行该中断服务程序,中断有很多种,该中断服务程序究竟该服务哪一个中断,显然,要把中断号与中断服务程序联接起来,构成一个整体。这个工作可以在open函数里做。参数irq-中断号,中断服务程序应该是与中断好一一对应的,对应于摸个中断号的中断一发生,就会调用该中断号对应的服务程序,那么,检测中断的发生,就成了先决条件。参数dev_id-具体是哪一个按钮首先定义一个button_irq_desc结构体类型的指针变量,对传入的资源进行处理获得中断控制寄存器的值(即是否有数据),取反后赋值给down,为0时说明有数据。要注意按下一次按钮,会发生两次中断,即按下是一次中断,放开又是一次中断通过电路原理图,可以知道没按下的时候,中断引脚应该是高电平对数组可读标志位进行设置,ev_press = 1; 表示数组已经可读了唤醒休眠的进程,button_waitq队列里存放有相应的处理进程,如读取数组的值的进程 * enum irqreturn * IRQ_NONEinterrupt was not from this device * IRQ_HANDLEDinterrupt was handled by this device * IRQ_WAKE_THREADhandler requests to wake the handler thread */enum irqreturn IRQ_NONE,IRQ_HANDLED,IRQ_WAKE_THREAD,;typedef enum irqreturn irqreturn_t;#define IRQ_RETVAL(x)(x) != IRQ_NONE)static int tq2440_irq_open(struct inode *inode, struct file *file)int i; /*循环变量,因为有4个按钮*/int err = 0; /*中断注册函数的返回值*/for (i = 0; i sizeof(button_irqs)/sizeof(button_irqs0); i+)if (button_irqsi.irq = 0; i-)if (button_irqsi.irq 0)continue;disable_irq(button_irqsi.irq);free_irq(button_irqsi.irq, (void *)&button_irqsi);return -EBUSY;ev_press = 1;return 0;驱动函数open调用的具体函数,由open函数具体实现硬件的初始化工作,以及软件的初始化工作,为我们的键盘设备的运行创造好环境sizeof(button_irqs)/sizeof(button_irqs0)sizeof是C/C+中的一个操作符(operator)是也,简单的说其作用就是返回一个对象或者类型所占的内存字节数。一般的,在32位编译环境中,sizeof(int)的取值为4。sizeof有三种语法形式,如下: 1) sizeof( object ); / sizeof( 对象 ); 2) sizeof( type_name ); / sizeof( 类型 ); 3) sizeof object; / sizeof 对象; 学过数据结构的你应该知道指针是一个很重要的概念,它记录了另一个对象的地址。既然是来存放地址的,那么它当然等于计算机内部地址总线的宽度。所以在32位计算机中,一个指针变量的返回值必定是4(注意结果是以字节为单位),可以预计,在将来的64位系统中指针变量的sizeof结果为8。由此可知上面的计算即为:(20+20+20+20)/20=4#define IRQ_TYPE_NONE0x00000000/* Default, unspecified type */#define IRQ_TYPE_EDGE_RISING0x00000001/* Edge rising type */#define IRQ_TYPE_EDGE_FALLING0x00000002/* Edge falling type */#define IRQ_TYPE_EDGE_BOTH (IRQ_TYPE_EDGE_FALLING | IRQ_TYPE_EDGE_RISING)request_irq 的返回值: 0 指示成功,或返回一个负的错误码,如 -EBUSY 表示另一个驱动已经占用了你所请求的中断线。enable_irq和disable_irq用来开启和关闭右参数irq指定的中断,这两个函数直接对8259的寄存器进行操作,因此irq对应的是实实在在的中断号,比如说X86下时钟中断一般为0号中断,那么启动时钟中断就需要调用enable_irq(0),而键盘一般占用2号中断,那么关闭键盘中断就需要调用disable_irq(2)。static int tq2440_irq_close(struct inode *inode, struct file *file)int i;for (i = 0; i sizeof(button_irqs)/sizeof(button_irqs0); i+)if (button_irqsi.irq f_flags & O_NONBLOCK)return -EAGAIN;elsewait_event_interruptible(button_waitq, ev_press);ev_press = 0;err = copy_to_user(buff, (const void *)key_values, min(sizeof(key_values), count);return err ? -EFAULT : min(sizeof(key_values), count);read调用的具体函数,由它读取键盘输入的结果,实质上就是读取key_values数组的值,它完成了键盘作为输入设备的核心功能,数组是否可读,要根据标志位ev_press来判断,如果数组可读,则读取数据到用户buffer中,如果数组不可读,则进程进入等待队列,等待到数组可读为止,等待队列机制,是中断管理中常用到的机制,因为有些进程经常需要等待某一事件的发生copy_to_user和copy_from_user就是在进行驱动相关程序设计的时候,要经常遇到的两个函数。由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_to_user()完成用户空间到内核空间的复制,函数copy_from_user()完成内核空间到用户空间的复制。下面我们来仔细的理一下这两个函数的来龙去脉。首先,我们来看一下这两个函数的在源码文件中是如何定义的:/arch/i386/lib/usercopy.cunsigned longcopy_to_user(void _user *to, const void *from, unsigned long n) might_sleep(); BUG_ON(long) n 0); if (access_ok(VERIFY_WRITE, to, n) n = _copy_to_user(to, from, n); return n;EXPORT_SYMBOL(copy_to_user);从注释中就可以看出,这个函数的主要作用就是从内核空间拷贝一块儿数据到用户空间,由于这个函数有可能睡眠,所以只能用于用户空间。它有如下三个参数, To 目标地址,这个地址是用户空间的地址; From 源地址,这个地址是内核空间的地址; N 将要拷贝的数据的字节数。如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数static unsigned int tq2440_irq_poll( struct file *file, struct poll_table_struct *wait)unsigned int mask = 0;poll_wait(file, &button_waitq, wait);if (ev_press)mask |= POLLIN | POLLRDNORM;return mask;poll调用的具体函数,poll实质上是select的调用函数如果有按键数据,则select会立刻返回如果没有按键数据,则等待实质上这是键盘等待输入的机制poll_wait()函数会监测进程队列button_waitq里的进程例如,如果tq2440_irq_read所在的进程的标志位ev_press置为1了那么就不会再等待了,这实质上就是select函数的运行机制static struct file_operations dev_fops = .owner= THIS_MODULE,.open= tq2440_irq_open,.release= tq2440_irq_close, .read= tq2440_irq_read,.poll= tq2440_irq_poll,;file_operations结构体,驱动函数的设置,分别将前面的驱动函数设置进来static struct miscdevice misc = .minor = MISC_DYNAMIC_MINOR,.name = DEVICE_NAME,.fops = &dev_fops,;misc(混合)设备注册和注销:其它类型不能严格划分的设备类型,也叫混合类型有:1.结构体:struct miscdevice int minor; const char *name; const struct file_operations *fops; struct list_head list; struct device *parent; struct device *this_device;2.misc设备注册:extern int misc_register(struct miscdevice * misc);misc设备注销:extern int misc_deregiste

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论