linux驱动
知识结构:
1. Linux驱动程序设计模式(40%)2.内核相关知识(30%)3. 硬件相关知识(30%)z
驱动分类:字符,网络,块
字符设备:以字节为最小单位,不可以乱序读写。
块设备: 一次传送一个整体数据(512字节),Linux可以以字节访问块设备(仅仅是驱动与内核的接口不同,访问的顺序的不同(字符只可顺序访问,块驱动可随机访问))
网络接口:硬件(eth0),纯软件(lo)
驱动的安装:模块,编译进内核(Linux启动的时候会自动加载init段)
使用驱动程序:字符设备文件—〉字符设备驱动—〉字符设备
文件系统—〉块设备文件—〉块设备驱动—〉块设备
套接字—〉协议栈—〉网络设备驱动—网络接口设备
主设备号用来标示与设备文件相连的驱动程序,次编号被驱动程序用来辨别操作哪个设备
主设备号反映设备类型,此设备号区分同类型的设备
dev_t 高12位为主设备号,低20位为次设备号
MAJOR(dev_t dev)从dev_t分解出主设备号
MINOR(dev_t dev)从dev_t分解出此设备号
MKDEV(major,minor) 构造设备号;
静态申请:1.根据Documentation/devices.txt驱动没有使用的主设备号
2. 使用register_chrdev_region(dev_t form, unsigned count ,const char *name)函数注册(容易冲突)
From希望使用的设备号,count希望申请使用设备号的数目,name设备名(体现在/proc/devices)
2. 动态分配 alloc_chardev_region(安装驱动前无法创建设备文件)创建设备文件后,通过/proc/devices 察看
alloc_chardev_region(dev_t *dev,unsigned baseminor,unsigned count, const char *name)
dev分配的设配号,baseminor起始的此设备号,count要分配的设备数目,name设备名
注销设备号:unregister_chrdev_region(dev_t dev, unsigned baseminor)
创建设备节点:
Mknod filename type major minor type是字符或块 mknod serial0 c 100 0
Linux字符设备驱动3个重要数据结构
Struct file每打开一次都有一个关联的struct file 重要结构loff_t f_pos文件读写位置 struct file_operations*f_op
Struct inode 记录文件的物理上的信息(设备号等),一个文件可以有多个file,但只有一个inode
Struct file_operation *f_op函数指针的集合
struct file_operations mem_fops= {
.owner = THIS_MODULE,
.llseek = mem_seek,
.read = mem_read
…….
}
读内核代码应用程序怎样访问驱动程序(read_write,c)
系统调用read找到vfs_read根据file结构中找到file_operations中的read
读文件过程:系统调用read -à vfs_read -à file_operations -à read
字符设备使用 structcdev 来描述
字符设备注册可分为如下3个部分:
1.分配cdev struct cdev *cdev_alloc(void)
2.初始化cdevcdev_init(struct cedev *p, const struct file_operations *fops)
3. 添加cdevcdev_add(struct cdev *p ,dev_t dev ,unsigned count )
dev设备号count设备号的数目
字符设备的注销:cdev_dev(struct cdev *p)
设备操作
int (*open)(struct *inode , struct file *)
如果该项为NULL,设备打开永远成功
void(*release)(struct*inode , struct file *)
ssize_t (*read)(struct file *,char __user *buff , size_t , loff_t *)
ssize_t (*write)(struct file *,char __user *, size_t , loff_t *)
file是文件指针(来与内核),*buff是数据缓冲(用户空间),count传输的数据量(用户空间),offp访问位置(来与内核)
*buff是用户空间的,不能直接使用
int copy_from_user(void *to ,const void __user *from, int n)
int copy_to_user(void __user *to,const void *to , int n)
loff_t llseek(struct *file ,loff_t offset , intwhence)
open方法主要完成如下工作:1. 初始化设备 2. 标明此设备号
在open(struct inode *inode,struct file *filp) 函数可使用 MINOR(inode->i_rdev); 获取此设备号
filp->private_data = dev; 将设备描述指针赋值给私有文件指针 ,区分出了那种设备
在read()函数使用struct mem_dev *dev =filp->private_data 可根据私有文件指针指定找到具体的设备,read函数参数没有inode,无法获取此设备号。
kmalloc分配内存,返回地址,根据其返回的地址就可操作内存中的数据
copy_to_user(buf,(void*)(dev->data+p),count) 这里的(dev->data+p)为什么要用(void *)强制转换呢?copy_to_user是这么定义的
int copy_to_user(void __user *to ,const void *to , int n),但还是不理解。
驱动程序调试分类:打印调试,调试器调试(kgdb),查询调试(proc文件系统)
合理的使用printk可以全局的打开或关闭它们。
并发:多个执行单元同时被执行
竟态:并发的执行单元对共享资源(硬件资源或全局变量等)的共享访问
通过semaphore机制和spin_lock机制实现
获取信号量不成功该阻塞或者睡眠
1. 定义信号量 struct semaphore sem;
2. 初始化信号量 void sema_init(struct semaphore *sem,int val) 初始化信号量的初值为val
3. voidinit_MUTEX(struct semaphore *sem)初始化一个互斥锁,把sem的值设为1
4. voidinit_MUTEX_LOCKED(struct semaphore *sem) 初始化一个互斥锁,把sem的值设为0
定义与初始化工作可由如下宏一步完成
DECLARE_MUTEX(name)定义一个信号量,并初始化为1
DECLARE_MUTEX_LOCK(name)定义一个信号量,并初始化为0,为已锁状态
5. 获取信号量void down(struct semaphore *sem)可能会导致进程睡眠,故不能在中断上下文中使用,如果sem非负直接返回,否则挂起(TASK_UNINTERRUPTIBLE),不建议使用
6. voiddown_interrruptible(struct semaphore *sem)信号量不可用,置为TASK_INTERRUPTIBLE
7. voiddown_killable(struct semaphore *sem)信号量不可用 ,置为TASK_KILLABLE
8. voidup(struct semaphore *sem) 释放信号量
自旋锁不会引起调用者的睡眠,线程会移植忙循环,移植等待下去
1. spin_lock_init(x)初始化自旋锁
2. spin_lock(lock)获取自旋锁,不成功自旋在那
3. spin_trylock(lock)不会一直等待
4. spin_unlock
信号量可以有多个持有者(1个互斥信号量),自旋锁只有一个持有者
信号量适合保持时间较长,自旋锁适合保持时间较短
Ioctl对硬件进行控制(改变波特率,报告错误信息)
用户使用方法:int ioctl(int fd, unsigned long cmd, …) 点表示可选参数
int(*ioctl)(struct inode*inode, struct file *filp, unsigned int cmd, unsigned long arg)
cmd用户空间传下来的,arg用户传下来的参数
ioctl命令实现方法:1.定义命令 2.实现命令
Documentation/ioctl-number.txt定义了使用的幻数
ioctl被划分为几个位段,include/asm/ioctl.h定义了这些字段:
1. 类型(幻数):8位宽,属于哪一类设备
2. 序号:表明设备命令的第几个
3. 传送方向:可能的值是_IOC_NONE, _IOC_READ, _IOC_WRITE是从应用程序的观点来看的
4 .参数的大小(数据的类型)
内核提供下列宏来帮助定义命令
_IO(type,nr)没有参数传递
_IOR(type, nr, datatype)从驱动中读数据
_I0W(type, nr, datatype)从数据到驱动
_IOWR(type, nr, datatype)type和number成员作为参数被传递
Ioctl函数的实现 1. 返回值 2. 参数使用 3. 命令操作
通常是个switch语句,不支持的返回 –EINVAL
使用ioctl中的参数:整数可以直接使用, 指针则使用前需进行正确的检查
参数检查
不需要检测的函数:copy_from_user, copy_to_user, get_user, put_user
需要检测的函数:__get_user, __put_user
int access_ok(int type, const void *addr, unsingned long size)
type是VERIFY_READ或者VERIFY_WRITE, addr是要操作的用户内存的地址,size是操作的长度。access_ok返回一个布尔值:1.存取没问题 0. 失败,如果返回失败,
则ioctl应当返回-EFAULT.
等待队列:实现进程的阻塞,保存进程的容器,阻塞时放入等待队列,唤醒时,取出进程
1. 定义等待队列 wait_queue_head_t my_queue
2. 初始化等待队列 wait_waitqueue_head(&my_queue)
3. 定义并初始化等待队列 DECLARE_WAIT_QUEUE_HEAD(my_queue)
有条件睡眠
wait_event(queue, condition)当condition为真,返回。当condition为假,进入TASK_UNINTERRUTIBLE睡眠,并挂在queue指定的等待队列上
wait_event_interruptible(queue, condition)
wait_event_killable(queue, conditon)
无条件睡眠(老版本,不建议使用)sleep_on(wait_queue_head_t *q) interruptible_sleep_on(wait_queue_head_t*q)
等待队列中唤醒进程
Wake_up(wait_queue_t *q)唤醒等待队列中的所有进程都唤醒
Wake_up_interruptible(wait_queue_t *q)唤醒为TASK_INTERRUPTIBLE进程
阻塞方式是文件读写的默认方式,应用程序可以使用O_NONBLOCK标志非阻塞的
设置了O_NONBLOCK,系统只是简单的返回-EAGAIN
Select系统调用对应于poll
Select用于多路监控,如没有一个文件满足要求,select将阻塞进程
Int select(int maxfd, fd_set *reedfds, fd_set*writefds, fd_set *exceptfds, const struct timeval *timeout)
maxfd:文件描述符的范围,比检测的最大文件描述符大1
Readfds:被读监控的文件描述符
Writefds:被写监控的
Exceptfds:被异常监控的
Timeout:定时器
Timeout
1. 为0时不管有没有文件满足要求,立即返回,无文件满足,返回0
2. 为NULL时,select将阻塞进程,直到文件满足要求为止
3. 为正整数的时候,等待的最长时间,即select在timeout时间内阻塞进程
Select返回值
1. 正常返回满足要求的文件描述符个数
2. 没有满足的返回0
3. select被某个信号打断,返回-1 , errno为EINTR
Select系统调用:
1. 将要监控的文件添加到文件描述符集
2. 调用select开始监控
3. 判断文件是否满足要求
Void FD_SET(int fd, fd_set *fdset) 将fd添加到fdset中
Void FD_CLR(int fd, fd_set *fdset) 在fdset中清楚fd
Void FD_ZERO(fd_set *fdset) 清空fdset
Void FD_ISSET(int fd, fd_set *fdset) 检测文件描述集中的需判断的fd发生变化
驱动通常由poll实现
Unsigned int(*poll)(struct file *filp, poll_table *wait)
负责完成:
1. 使用poll_wait将等待队列添加到poll_table中
2. 返回描述设备是否可读可写的掩码
位掩码:
POLLIN设备可读,POLLRDNORM数据可读,POLLOUT设备可写,POLLWRNORM数据可写
设备可读通常返回(POLLIN | POLLRDNORM)
设备可写通常返回(POLLOUT |POLLWRNORM)
Poll方法只是做一个登记,真正的阻塞发生在select.c中的do_select函数(分析了do_select函数)
自动创建设备文件
2.4内核使用devfs_register(devfs_handle_tdir, const char *name, unsigned int flags, unsigned int major unsigned intminor, umode_t mode, void *ops, void *info)
Dir:目录名,name:文件名;flags:创建标志;major:主设备号;minor此设备号;mode:创建模式;ops:操作函数集;info:通常为空
从2.6.13开始,devfs不复存在,udev成为替代
使用
1. class_create为设备创建一个class,
2. 使用device_create创建对应的设备
例:struct class *myclass = class_create(THIS_MODULE, “my_device_driver”);
Device_create(myclass, NULL,MKDEV(major_num, 0), NULL, “my_device”)
void *mmap(void*addr, size_t len, int prot, int flags, int fd, off_t offset)
负责把文件内容映射到进程的虚拟内存空间,通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。
addr: 映射的起始地址,通常为NULL,由系统指定
length:映射文件的长度
prot:映射区的保护方式 PROT_EXEC:映射区可被执行,PROT_READ:映射区可被读取,PROT_WRITE:映射区可被写入
flags:映射区的特性 MAP_SHARED:写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享。 MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy-on-write),对此区域的修改不会写回源文件。
fd:由open返回的文件描述符,代表要映射的文件
offset:以文件开始处的偏移量,必须是分页大小的整数倍,通常为0,表示从文件头开始映射。
注意mmap不能映像原有文件的长度
int munmap(void *start, size_tlength)
成功返回0,失败返回-1. start的取值一般是mmap返回的地址
虚拟内存区域:是虚拟地址空间的一个同质区间,即有同样特性的连续地址范围。一个进程的内存映像由以下几部分组成:程序代码,数据,BSS和栈区域,以及内存映射区域
一个进程的内存区域可以通过查看 /proc/pid/maps
每一行的域为:start_end perm offset major:minor inode
linux内核使用结构vm_area_struct来描述虚拟内存区域,其中主要成员如下:
unsigned long vm_start 虚拟内存区域起始地址
unsigned long vm_end 虚拟内存区域结束地址
unsigned long vm_flags
映射一个设备是指把用户空间的一段地址关联到设备内存上。
mmap做了三件事:1.找到用户空间地址(内核完成) 2.找到设备的物理地址(原理图) 3.关联(通过页式管理)\
mmap设备方法需要做的就是建立虚拟地址到物理地址的页表。
int(*mmap)(struct file *, struct vm_area_struct *)
建立页表有两种方法:
1.使用remap_pfn_range一次建立所有页表;
2.使用nopage VMA方法每次建立一个也表。
int remap_pfn_range(structvm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size,pgprot_t prot)
vma:虚拟内存区域指针 virt_addr:虚拟地址的起始值 pfn:要映射的物理地址所在的物理页祯号,可将物理地址>>PAGE_SHIFT得到(PAGE_SHIFT为12,相当于除以4KB)
size:要映射的区域的大小 prot:VMA的保护属性
硬件访问
寄存器和内存的区别:寄存器和RAM主要不同在于寄存器操作由副作用(side
effect或边际效果):读取某刻地址可能导致该地址内容变化,读中断状态寄存器,便自动清零
内存与IO
在X86存在IO空间(串口 并口等),在32为x86
IO空间是64K,内存空间是4G,ARM,PowerPC只有内存地址空间的
IO端口:一个寄存器或内存位于IO空间时,称为IO端口
IO内存:一个寄存器或内存位于内存空间时,称为IO内存
对IO端口的操作需要按如下步骤完成:1,申请 2,访问 3,释放
申请:struct resource*request_region(unsigned long first, unsigned long n,
const char *name) 从first开始的n个端口,name设备名字
系统中端口的分配情况记录在/proc/ioports中
访问:intb outb intw outw intl outl
释放:voidrelease_region(unsigned long start, unsigned long n)
IO内存有4步:1.申请 2,映射 3,访问 4,释放
申请:structresource *request_mem_region(unsigned long start, unsigned long
len,char
*name)从start开始,长度为len字节的内存区。成功,返回非NULL,否则返回NULL。
可在/proc/iomem中列出
访问:在访问IO内存之前,必须进行物理地址到虚拟地址的转化,使用下面函数
void *ioremap(unsignedlong phys_addr, unsigned long size)
访问:ioread8(void *addr) iowrite8(u8 value, void*addr) 老版本 使用readbwriteb
释放: 1,void iounmap(void *addr) 2,void release_mem_region(unsigned long
start, unsigned long len)
混杂设备驱动:共享一个主设备号10,成为混杂设备
Linux内核使用structmiscdevice描述混杂设备
struct miscdevice{
int minor;
const char *name;
const struct file_operations*fops;
struct list_head list;
struct device *parent;
struct device *this_device;
}
使用misc_register函数来注册一个混杂设备驱动 misc_register(struct miscdevice *misc)
使用上拉下拉避免悬浮
Linux总线设备驱动模型(2.6内核难点)
Sysfs文件系统(基于内存,展示内核数据结构属性,关系),与proc同类别的文件系统,sysfs把连接在系统上的设备和总线组织成分级的文件,使其从用户空间可以访问到
sysfs在/sys/目录下
block目录:块设备信息
bus:总线(ide pci scsi sb pcmcia)里边还有devices和drivers目录,devices目录下都是软链接
class目录:按照功能进行分类(网络)
devices:包含系统所有的设备
kernel:内核中的配置参数
Module:系统中所有模块信息
firmware:系统中的固件
fs:描述系统中的文件系统
power:系统电源选项
Kobject实现了基本的面向对象管理机制,与sysfs文件系统紧密相连,在内核中注册的每个kobject对象对应sysfs文件系统中的一个目录(作用:在sys下创建一个目录)类似于C++中的基类。
void kobject_init(struct kobject *kobj) 初始化kobject结构
int kobject_add(strut kobject *kobj)将kobject对象注册到Linux系统
int kobject_init_and_add(struct kobject *kobj, struct kobj_type *ktype,struct
kobject *parent, bonst char *fmt, ...) 初始化kobject,并注册到Linux
void kobject_del(struct kobject *kobj)从Linux系统中删除kobject对象
struct kobject *kobject_get(struct kobject
*kobj)将kobject对象引用计数加1,同时返回该对象指针
void kobject_put(struct kobject
*kobj)将kobject对象的引用计数减1,如果引用计数降为0,则调用release方法释放该kobject对象
kobject的ktype成员是一个特指向kobj_type结构的指针,该结构记录了kobject对象的一些属性
struct kobj_type{
void(*release)(struct kobject*kobj);
struct sysfs_ops *sysfs_ops;
struct attribute **default_attrs;
}
release:用于释放kobject占用的资源,当kobject的引用为0时被调用
struct attribute{
char *name;
struct module *owner;
mode_t mode; /*属性的保护位*/
}对应于kobject的目录下的一个文件,name成员就是文件名
struct sysfs_ops
{
ssize_t(*show)(struct kobject *,struct attribute*, char *)
ssize_t(*store)(struct kobject *,struct attribute *, const char *,
size_t)
}
show:当用户读属性文件时,该函数被调用,该函数将属性值存入buffer中返回给用户态
store:当用户写属性文件时,该函数被调用,用于存储用户传入的属性值
(学习函数指针三点
1.参数是怎么样的(从哪来到哪去)2.这个函数指针什么时候被调用3.这个函数用来做什么)
kset是具有相同类型的kobject的结合,在sysfs中体现一个目录,kobject里边只能是文件不能是目录
struct kset
{
struct list_head list; //链接该kset中所有kobject的链表头
spinlock_t list_losk;
struct kobject kobj; //内嵌的kobject
struct kset_uevent_ops *uevent_ops//处理热插拔事件的操作集合
}
kset操作:
int kset_register(struct kset *kset) 在内核中注册一个kset
void kset_unregister(struct kset *kset) 在内核中注销一个kset
热插拔事件:当系统配置发生变化时,如:添加kset到系统;移动kobject,一个通知会从内核空间发送到用户空间,这就是热插拔事件。热插拔事件导致用户空间相应的处理程序(如udev,mdev)被调用,这些处理程序会通过加载驱动程序,创建设备节点等来响应热插拔事件
操作集合
struct kset_uevent_ops{
int(*filter)(struct kset *kset,struct kobject *kobj);
const char *(*name)(struct kset*kset, struct kobject *kobj);
int(*uevent)(struct kset *kset,struct kobject *kobj, struct
kobj_uevent_env *env);
}
当该kset所管理的kobject和kset状态发生变化时(如被加入,移动),这三个函数将被调用。
filter函数:决定是否将事件传递到用户空间。如果filter返回0,不传递事件
name函数:用于将字符串传递给用户空间的热插拔处理程序
uevent函数:将用户空间需要的参数添加到环境变量中
linux2.6内核提供了全新的内核设备模型
设备模型元素:总线,设备,驱动
总线:处理器和设备之间的通道,所有的设备都通过总线相连,包括platform总线(虚拟平台总线),总线由bus_type结构表示
总线的注册使用:bus_register(struct bus_type *bus)
若成功,可在sysfs的/sys/bus下看到
总线的删除使用:void bus_unregister(struct bus_type *bus)
int(*match)(struct device *dev, struct device_driver *drv)
当一个新设备或者驱动添加到这个总线时,用于判断指定的驱动程序是否能处理指定的设备。可以返回非零值
int(*uevent)(struct device *dev, char **envp, int num_envp, char *buffer,
int buffer_size) 在为用户空间产生热插拔事件之前,这个方法允许总线添加环境变量
总线的属性由bus_attribute描述,定义如下:
struct bus_attribute{
struct attribute attr;
ssize_t(*show)(struct bus-type *,char *buf);
ssize_t(*store)(struct bus_type *,const char *buf, size_t count);
}
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr) 创建属性
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr) 删除属性
Linux系统中每个设备由一个struct device描述
int device_register(struct device *dev) 注册设备
void device_unregister(struct device *dev)注销设备
一条总线也是个设备,也必须按设备注册
struct device_attribute
{
struct attribute attr;
ssize_t(*show)(struct device *dev,struct device_attribute *attr, char
*buf);
ssize_t(*store)(struct device *dev,struct device_attribute *attr,
const char *buf, size_t count);
}
int device_create_file(struct device *device, struct device_attribute*entry)
创建属性
void device_remove_file(struct device *dev, struct device_attribute *attr)
删除属性
驱动程序由 struct device_driver描述
int driver_register(struct device_driver *drv) 注册驱动
void driver_unregister(struct device_driver *drv) 注销驱动
驱动的属性使用struct driver_attribute来描述
stryct drever_attribute{
struct attribute attr;
ssize_t(*show)(structdevice_driver *drv, char *buf);
ssize_t(*store)(structdevice_driver *drv, const char *buf, size_t
count);
}
int driver_create_file(struct device_driver *drv, struct driver_attribute
*attr) 创建属性
void driver_remove_file(struct device_driver *drv, struct driver_attribute
*attr) 删除属性
platform总线:2.6内核加入的虚拟总线,由platform_device和platform_driver两部分组成
platform优势在于platform机制将设备本身的资源注册进内核,由内核统一管理,在驱动程序使用这些资源提供统一的接口,提高可以执行。
通过platform流程:1.定义platform_device2.注册platform_device
3,定义platform_driver 4.注册platform_driver
平台设备使用struct platform_device来描述
struct platform_device{
const char *name; 设备名
int id; 设备编号,配合设备名使用
struct device dev;
u32 num_resources;
struct resource *resource; 设备资源(中断号,基地址)
}
platform_device的分配使用:
structplatform_device *platform_device_alloc(const char *name, int id)
name:设备名 id:设备id,一般为-1
注册平台设备,使用函数
intplatform_device_add(struct platform_device *pdev)
平台设备资源使用struct resource来描述
struct resource{
resource_size_t start; //资源起始的物理地址
resource_size_t end; //资源结束物理地址
const char *name; //资源的名称
unsigned long flags; //资源的类型,比如MEM,IO,IRQ类型
struct resource*parent,*sibling,*child; //资源链表指针
}
获取资源: struct resource*platform_get_resource(struct platform_device *dev,
unsigned int type, unsigned int num)
dev:资源所属的设备 type:获取的资源类型 num:获取的资源数
平台驱动使用struct platfor_driver来描述
struct platform_driver{
......
int(*probe)(struct platform_device*)
......
}
平台驱动的注册使用函数
intplatform_driver_register(struct platform_driver *)
platfor_driver_register 原理分析(什么时候遍历设备,能够处理某个设备的标准)
为什么需要中断:
1.外设的处理速度一般慢于CPU
2.CPU不能一直等待外部事件
在Linux驱动中,实现中断包含两个步骤:
1. 向内核注册中断
2. 2.实现中断处理函数
中断注册:intrequest_irq(unsigned int irq,void(handler)(int,void*,struct
pt_regs *),unsigned long flags, const char *devname, void *dev_id)
返回0表示成功,或者返回一个错误码
irq:中断号 handler中断处理程序 flags:中断管理有关的选项*devname:设备名
*dev_id:共享中断时使用
flags参数:IRQF_DISABLED(SA_INTERRUPT)快速中断处理程序
IPQF_SHARED(SA_SHIRQ)这位表明中断可以在设备间共享
这两者的区别:快速中断处理的原子性(不被打断),而慢速中断不保证.快速中断时处理程序是不会被其他类型的中断打断的,默认的都是慢速中断处理程序.
共享中断:将不同的设备挂到同一个中断线上
1.申请共享中断,必须在flags参数中指定IRQF_SHARED位
2.dev_id参数必须是唯一的
释放中断(通常在驱动卸载时):void free_irq(unsigned intirq, void *dev_id)
3. 共享中断的处理程序中,不能使用disable_irq(unsigned int irq)把其他的页关掉了
中断处理程序是在中断上下文中运行的,受到某些限制
1.不能向用户空间发送或接受数据
2.不能使用可能引起阻赛的函数
3.不能使用可能引起调度的函数
中断处理函数流程
void short_sh_inerrupt(intirq, void *dev_id, struct pt_regs *regs)
{
//判断是否产生了中断,避免共享中断产生的误调用
value = inb(short_base);
if(!(value & 0x80)) return;
//清除中断位(如果设备支持自动清除,不需要这步)
outb(value & 0x7F, short_base);
//中断处理,通常是数据接受
.......
//唤醒等待数据的进程
wake_up_interruptible(&short_queue);
}
最后更新:2017-04-03 20:19:50