閱讀294 返回首頁    go 阿裏雲 go 技術社區[雲棲]


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

}

最後更新:2017-04-03 20:19:50

  上一篇:go httpclient上傳文件的文件名的中文問題
  下一篇:go POJ 2451 求半麵交