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_deviceplatform_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);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值