第二部分Linux設備驅動程序_第1頁
第二部分Linux設備驅動程序_第2頁
第二部分Linux設備驅動程序_第3頁
第二部分Linux設備驅動程序_第4頁
第二部分Linux設備驅動程序_第5頁
已閱讀5頁,還剩81頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

1、第二部分 Linux設備驅動程序1第一章 設備驅動簡介2設備驅動程序的角色是內核的一部分,屬于內核中的設備管理子系統是應用程序和實際設備間的軟件層提供對硬件的基本操作,如open,read,write,ioctl,close等只提供硬件操作機制,如何使用硬件(操作策略)應由應用決定,驅動不應該包含策略驅動程序既可以直接編譯到內核中(zImage),或者編譯為可動態加載的模塊(.ko文件,用insmod程序加載)3Linux設備的分類字符( char ) 設備-是一種可以按字節流來存取的設備-實現 open, close, read, 和 write等系統調用-文本控制臺( /dev/conso

2、le )和串口( /dev/ttyS0)、內存、Flash等塊(block)設備-按整塊數據存取(如512字節)-如磁盤設備(/dev/hda)-可以帶有文件系統網絡設備-負責網絡數據包的發送和接收,如eth04第二章 內核模塊基礎5模塊代碼結構hello world模塊實例分析hello.c6初始化和退出函數初始化函數module_init()-由insmod調用-注冊設備,請求資源等退出函數module_exit()-由rmmod調用-取消設備注冊、釋放資源等初始化中的錯誤處理(goto的使用)int _init my_init_function(void) int err; /* reg

3、istration takes a pointer and a name */ err = register_this(ptr1, skull); if (err) goto fail_this; err = register_that(ptr2, skull); if (err) goto fail_that; err = register_those(ptr3, skull); if (err) goto fail_those; return 0; /* success */fail_those: unregister_that(ptr2, skull);fail_that: unregi

4、ster_this(ptr1, skull);fail_this: return err; /* propagate the error */7模塊的編譯和加載模塊編譯Makefile分析Makefile8模塊加載參數參數的值可由 insmod 或者 modprobe 在加載時指定參數類型可以是bool,charp,int等聲明方式module_param()如static char *whom = world;static int howmany = 1;module_param(howmany, int, S_IRUGO);module_param(whom, charp, S_IRUGO

5、);hellop.c9課后練習輸入hello world模塊例子代碼,編譯并加載模塊給hello world模塊增加參數,重新編譯并加載10第三章 字符設備驅動程序11設備文件和設備號Linux上的設備操作都是通過設備文件進行如crw-rw-rw- 1 root tty 4, 64 Apr 11 2002 /dev/ttyS0 c表示字符設備,b表示塊設備主設備號標識設備相連的驅動,次設備號決定引用哪個設備12設備號的分配和釋放靜態分配int register_chrdev_region(dev_t first, unsigned int count, char *name);-first :

6、起始設備編號(通常為0)-count:請求的設備編號個數-name:設備名動態分配int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);-dev:內核分配的主次設備號釋放設備號void unregister_chrdev_region(dev_t first, unsigned int count); 注意:一定要檢查返回值,確保分配成功!13關鍵數據結構include/linux/fs.hstruct (定義設備操作方法)struct struct module

7、 *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char _user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, char _user *, size_t, loff_t);ssize_t (*write) (struct file *, const char _user *, size_t, loff_t *);。int (*ioctl) (struct inode *, struct file *

8、, unsigned int, unsigned long);。int (*open) (struct inode *, struct file *);int (*flush) (struct file *);int (*release) (struct inode *, struct file *);struct file(對應 每個打開的文件,在open時創建)struct inode(內核內部表示文件的結構)14字符設備初始化和注冊(1)字符設備用cdev結構表示cdev結構的初始化struct cdev *my_cdev = cdev_alloc();my_cdev-ops = &my

9、_fops;或者void cdev_init(struct cdev *cdev, struct *fops);15字符設備初始化和注冊(2)設備注冊int cdev_add(struct cdev *dev, dev_t num, unsigned int count);-dev:cdev結構-num:設備編號-count:設備數(通常是1)設備注銷void cdev_del(struct cdev *dev);16設備的方法(1)open方法(打開設備)-初始化設備、分配資源等int (*open)(struct inode *inode, struct file *filp);relea

10、se方法(釋放設備)-釋放資源、關閉設備等-并不是在應用程序每次調用close時都會調用release17設備的方法(2)read方法(從設備讀取數據)ssize_t read(struct file *filp, char _user *buff, size_t count, loff_t *offp);count:請求傳輸的數據大小buff:緩沖區offp:正在存取的文件位置返回值:等于count,完整讀取 大于0但小于count,部分傳輸 等于0,已到達文件尾 小于0,出錯write方法(往設備寫入數據)ssize_t write(struct file *filp, const cha

11、r _user *buff, size_t count, loff_t *offp);返回值:等于count,完整寫入 大于0但小于count,部分寫入 等于0,沒有寫入 小于0,出錯18設備的方法(3)用戶空間和內核空間的數據傳送unsigned long copy_to_user(void _user *to,const void *from,unsigned long count); unsigned long copy_from_user(void *to,const void _user *from,unsigned long count); 注意:需要檢查返回值19設備的方法(4)

12、llseek方法(用于設備定位)-有些設備不能定位,如串口等字節流設備-調用read、write時會更新文件當前位置指針-如果設備不支持llseek,需要調用以下函數打開設備:int nonseekable_open(struct inode *inode; struct file *filp); 同時 結構中設置 llseek 方法為 no_llseek20設備的方法(5)ioctl方法(用于其他的設備控制操作,如獲取參數、改變設置、硬件控制等)int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, un

13、signed long arg);-cmd:命令參數-arg:指針或者數值ioctl命令-系統范圍內唯一-Documentation/ioctl-number.txt(內核使用的ioctl幻數)-由4個字段組成type-幻數number-序號direction-數據傳送方向size-數據大小21設備的方法(6)定義ioctl命令的宏_IO(type,nr)(沒有參數的命令)_IOR(type, nr, datatype)(讀數據)_IOW(type,nr,datatype)(寫數據) _IOWR(type,nr,datatype)(讀寫)返回值:對于無效的命令,通常返回-EINVAL命令參數傳

14、遞:put_user(datum, ptr)-返回datum變量的值給應用程序get_user(local, ptr)-從應用程序獲取參數值保存在local變量中22設備的方法(7)scull的ioctl命令 分析main.c應用程序的調用方法(數值或指針方式):int quantum;ioctl(fd,SCULL_IOCSQUANTUM, &quantum); ioctl(fd,SCULL_IOCTQUANTUM, quantum); ioctl(fd,SCULL_IOCGQUANTUM, &quantum); quantum = ioctl(fd,SCULL_IOCQQUANTUM); 2

15、3實例分析scull設備驅動代碼分析main.c24課后練習輸入scull驅動代碼,編譯并加載,使用cp、dd、cat等命令對scull設備進行讀寫操作25第四章 內核時間和定時器26內核時間單位jiffies-時鐘滴答計數器-兩個jiffies的間隔一般為10ms(1/HZ,HZ=100,即1秒產生的時鐘滴答數)-volatile類型,如unsigned long j, stamp_1, stamp_half, stamp_n;j = jiffies; (當前時間)stamp_1 = j + HZ;(1秒)stamp_half = j + HZ/2;(半秒)stamp_n = j + n *

16、 HZ / 1000; (n毫秒)27延時函數(1)忙等待while (time_before(jiffies, j1) cpu_relax();-浪費CPU資源,降低性能釋放CPUwhile (time_before(jiffies, j1) schedule();-延時不準確,可能大于預期的值28延時函數(2)超時set_current_state(TASK_INTERRUPTIBLE);schedule_timeout (delay);-jiffies為單位短延時(忙等待)void ndelay(unsigned long nsecs);-納秒void udelay(unsigned l

17、ong usecs);-微秒void mdelay(unsigned long msecs);-毫秒jit模塊代碼分析29內核定時器(1)調度一個函數在將來一個特定的時間執行,如查詢設備、關閉硬件等定時器函數是異步執行的,屬于軟中斷類型定時器函數的一些限制-不允許存取用戶空間-不能存取 current 指針-不能進行睡眠或者調度. 不能調用 schedule 或者某種 wait_event, 也不能調用任何其他可能睡眠的函數. 例如kmalloc定時器函數執行后可以再次注冊30內核定時器(2)定時器API:#include struct timer_list /* . */ unsigned

18、long expires; void (*function)(unsigned long); unsigned long data;void init_timer(struct timer_list *timer);struct timer_list TIMER_INITIALIZER(_function, _expires, _data);void add_timer(struct timer_list * timer);int del_timer(struct timer_list * timer);-expires:定時器將要運行的jiffies值-function:定時器到期時執行的函

19、數-data:傳遞給function的參數,可以使指針jit模塊代碼分析31課后練習輸入jit驅動代碼,編譯并加載,讀取/proc目錄下的相應文件,觀察不同延時方法的表現32第五章 并發和競態33什么是并發和競態CPU的多處理特性,導致多個線程同時執行,如內核搶占、中斷、異步執行(定時器)、SMP(多處理器)等資源共享容易導致競態scull的問題分析-內存泄露34如何避免競態用內核提供的并發控制原語(信號量、鎖定等)減少資源共享(如全局變量等)信號量和互斥-臨界區(操作共享資源)-信號量是一個整數,一個進程只有在信號量大于0時才能進入臨界區,同時信號量減1,小于0時需要等待(休眠)-信號量初始

20、值為1時就成為互斥35并發控制(1)-linux信號量初始化void sema_init(struct semaphore *sem, int val);-val:信號量初始值互斥體初始化編譯時:DECLARE_MUTEX(name); DECLARE_MUTEX_LOCKED(name);-初始處于鎖定狀態運行時:void init_MUTEX(struct semaphore *sem);void init_MUTEX_LOCKED(struct semaphore *sem);36并發控制(2)-自旋鎖特性:-可以在不能休眠的代碼中使用-提高性能-主要用于可搶占內核和多CPU系統使用規則

21、:-擁有鎖時不能休眠(不能調用任何可能導致休眠的函數,如kmalloc,copy_from_user等)-持有自旋鎖的時間應盡可能短,否則內核延遲將增加,高優先級進程將被迫長時間等待,影響性能37自旋鎖API初始化:spinlock_t my_lock = SPIN_LOCK_UNLOCKED; (編譯時)void spin_lock_init(spinlock_t *lock); (運行時)獲取自旋鎖:void spin_lock(spinlock_t *lock);void spin_lock_irqsave(spinlock_t *lock, unsigned long flags);-

22、禁止中斷,同時保存當前中斷允許狀態void spin_lock_irq(spinlock_t *lock);-禁止中斷void spin_lock_bh(spinlock_t *lock)-禁止軟中斷釋放自旋鎖:void spin_unlock(spinlock_t *lock);void spin_unlock_irqrestore(spinlock_t *lock, unsigned long flags);void spin_unlock_irq(spinlock_t *lock);void spin_unlock_bh(spinlock_t *lock);判斷能否獲得自旋鎖,同時不會阻

23、塞:int spin_trylock(spinlock_t *lock);int spin_trylock_bh(spinlock_t *lock);38第六章 內存分配39kmalloc函數(1)和malloc相似有可能阻塞不對所分配的內存區段清零所分配的內存區段在物理上是連續的40kmalloc函數(2)#include void *kmalloc(size_t size, int flags); -s ize:需要分配的內存大小-flags:標志-GFP_KERNEL(代表進程分配,可能導致休眠,最常用)-GFP_ATOMIC(在中斷服務程序、定時器函數環境中使用,不會導致休眠)41km

24、alloc函數(3)只能分配預定義的,固定大小的字節數實際分配的大小可能大于請求的字節數kmalloc能分配的最小字節數是32或64不要分配太大的內存(大于128K)42kfree函數釋放由kmalloc分配的內存void kfree(void *obj); obj:kmalloc返回的指針43IO內存(1)通常指外設的寄存器或設備內存,如顯存或網卡緩沖區等映射到內存地址空間。也是通過CPU地址總線和數據總線讀寫需要將IO內存的物理地址映射到內核的虛擬地址(ioremap)不要直接使用指針訪問IO內存,應使用內核提供的讀寫函數(可讀性、可移植性好,經過優化)44IO內存(2)IO內存的分配和映

25、射-請求分配IO內存區域#include struct resource *request_mem_region(unsigned long start, unsigned long len, char *name);start:起始地址len:長度成功返回非NULL指針-釋放IO內存區域void release_mem_region(unsigned long start, unsigned long len); 45IO內存(3)將設備的IO地址(如寄存器地址等)映射到內核的虛擬地址空間#include void * ioremap(unsigned long offset, unsign

26、ed long size); -offset:設備的IO地址(物理地址)-size:映射范圍由于體系結構差異,不能直接操作返回的指針,應使用內核提供的IO操作函數取消ioremap所做的映射void iounmap(void *addr); -addr:ioremap返回的內核虛擬地址46IO內存(4)讀IO內存unsigned int ioread8(void *addr);-8位unsigned int ioread16(void *addr);-16位unsigned int ioread32(void *addr);-32位addr:ioremap返回的指針寫IO內存void iowr

27、ite8(u8 value, void *addr);void iowrite16(u16 value, void *addr);void iowrite32(u32 value, void *addr);連續讀寫void ioread8_rep(void *addr, void *buf, unsigned long count);void ioread16_rep(void *addr, void *buf, unsigned long count);void ioread32_rep(void *addr, void *buf, unsigned long count);void iow

28、rite8_rep(void *addr, const void *buf, unsigned long count);void iowrite16_rep(void *addr, const void *buf, unsigned long count);void iowrite32_rep(void *addr, const void *buf, unsigned long count);buf:數據緩沖區count:數據大小讀寫整塊IO內存void memset_io(void *addr, u8 value, unsigned int count);void memcpy_fromio

29、(void *dest, void *source, unsigned int count);void memcpy_toio(void *dest, void *source, unsigned int count);47第七章 中斷處理48設備通訊的三種方式輪詢中斷DMA(直接內存存取)49什么是中斷中斷是外設給CPU的信號,可以臨時打斷CPU執行的代碼,轉而執行中斷處理程序一個中斷通常和一個處理程序關聯中斷具有優先級,高優先級的中斷可以嵌套低優先級的中斷在CPU相應中斷時,同級的中斷會被自動屏蔽中斷處理程序時異步執行的,需要注意防止競態中斷處理程序必須是原子執行的,不能進入休眠狀態50中

30、斷處理程序(1)注冊和釋放#include int request_irq(unsigned int irq, irqreturn_t (*handler)(int, void *, struct pt_regs *), unsigned long flags, const char *dev_name, void *dev_id);irq:請求的中斷號(可以參考CPU數據手冊)handler:和irq相關聯的中斷處理程序flags:中斷標志,通常取SA_INTERRUPTdev_name:中斷的所有者dev_id:私有數據,如不使用,可以設為NULLvoid free_irq(unsigne

31、d int irq, void *dev_id);通常應該在打開設備的時候請求中斷,而不是在模塊初始化時,防止沒有使用設備而占用中斷資源。/proc/interrupts可以顯示系統中斷的狀態51中斷處理程序(2)運行限制:-不能和用戶空間傳遞數據-不能等待任何事件-分配內存時應使用GFP_ATOMIC參數-不能給信號量加鎖-不能執行調度程序中斷處理程序的主要工作是響應中斷(設置中斷響應標志),將設備的數據讀入驅動的緩沖區,同時喚醒等待數據的用戶進程中斷處理程序的返回值-IRQ_HANDLED:已處理-IRQ_NONE:未處理,或者不是本設備產生的中斷short模塊代碼分析52前半部和后半部(

32、1)當中斷處理程序要做較長時間的處理時,應分為兩部分前半部執行時關閉中斷,因此執行過程要盡可能短(request_irq注冊的處理程序)后半部由前半部調度,在推后的更安全的時間執行(此時可以允許中斷)典型的處理過程如前半部只是獲取設備數據到緩沖,然后直接退出,數據的處理、進程喚醒等耗時的操作由后半部執行53前半部和后半部(2)內核提供的后半部處理機制tasklet-較快,但必須是原子執行的tasklet的聲明:DECLARE_TASKLET(name, function, data);name:tasklet名稱function:tasklet被調度時執行的函數data:指針參數如:void

33、short_do_tasklet(unsigned long);DECLARE_TASKLET(short_tasklet, short_do_tasklet, 0);tasklet的調度(一般有前半部調用)tasklet_schedule(&short_tasklet);54前半部和后半部(3)工作隊列-運行周期較長,但允許休眠工作隊列的聲明:static struct work_struct short_wq;INIT_WORK(&short_wq, (void (*)(void *) short_do_tasklet, NULL);工作隊列的調度:schedule_work(&short

34、_wq);short模塊代碼分析55第八章 塊設備驅動程序56塊設備的特性可以隨機讀寫固定大小的數據塊可以提高性能塊大小通常是4096字節內核使用的扇區大小是512字節57塊設備的注冊和注冊塊設備的注冊int register_blkdev(unsigned int major, const char *name); major:主設備號,如為0,由內核動態分配name:設備名塊設備的注銷int unregister_blkdev(unsigned int major, const char *name); 58塊設備的操作#include struct block_device_operat

35、ionsint (*open)(struct inode *inode, struct file *filp); int (*release)(struct inode *inode, struct file *filp); int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); 大部分的塊設備ioctl命令都有內核處理,驅動實現的較少int (*media_changed) (struct gendisk *gd); int (*revalidate_disk) (s

36、truct gendisk *gd); 對包含可移動介質的塊設備(如光驅等),用來判斷設備介質是否改變并作出響應和字符驅動的主要區別是沒有讀寫函數59gendisk結構內核用來顯示磁盤驅動器或分區主要成員struct block_device_operations *fops; 設備操作集合.struct request_queue *queue; 設備I/O 請求隊列結構分配和初始化struct gendisk *alloc_disk(int minors); void add_disk(struct gendisk *gd); 釋放void del_gendisk(struct gendi

37、sk *gd);sbull驅動模塊初始化函數分析60塊設備操作函數open和close-設置驅動和硬件的狀態. 包括起停磁盤, 加鎖一個可移出設備的門, 分配 DMA 緩沖等等-不一定由應用程序調用,可能由內核直接調用,如mount等ioctl-大部分ioctl由內核處理,驅動處理的很少sbull模塊設備操作代碼分析61I/O請求I/O請求的處理-在內核認為需要啟動對設備讀寫的時候調用-請求隊列包含當前需要處理的請求,由內核對所有I/O請求進行調度(合并或重排等,性能考慮)后將請求加入隊列請求隊列的初始化和清除#include request_queue_t blk_init_queue(re

38、quest_fn_proc *request, spinlock_t *lock); request:請求處理函數void blk_cleanup_queue(request_queue_t *); 從隊列中獲取請求struct request *elv_next_request(request_queue_t *queue); (不刪除請求)從隊列中刪除請求void blkdev_dequeue_request(struct request *req); 通知內核請求已處理void end_request(struct request *req, int success); sucess:指

39、示請求是否成功完成62請求的結構(1)一個request可能包含多個bio,一個bio可能包含多個bio_vec(段)bio結構圖63請求隊列圖請求的結構(2)64請求的結構(3)遍歷request中的biorq_for_each_bio(bio, request)遍歷bio中的段bio_for_each_segment(bvec, bio, segno); 65請求完成函數int end_that_request_first(struct request *req, int success, int count); success:驅動是否完成請求的扇區的傳送count:完成傳送的扇區數-告

40、知內核驅動已完成count個扇區傳送void end_that_request_last(struct request *req); -通知等待請求完成的進程,同時釋放request結構sbull模塊代碼分析66第九章 網絡設備驅動程序67網絡設備的特點將接口注冊到內核中,供內核在需要時調用沒有設備文件(沒有read,write等調用)異步接收數據,需要將數據推送給內核驅動和協議相互獨立需要支持設置網絡地址, 修改發送參數, 以及維護流量和錯誤統計等操作68net_device結構分配#include struct net_device *alloc_netdev(int sizeof_pri

41、v, const char *name, void (*setup)(struct net_device *);sizeof_priv :私有數據區的大小name:接口名(如eth0等)setup:初始化函數的指針, 用來設置 net_device 結構的剩余部分(snull的net_device初始化函數分析)如:snull_devs0 = alloc_netdev(sizeof(struct snull_priv), sn%d, snull_init);對于以太網,可以簡化為:#include struct net_device *alloc_etherdev(int sizeof_pri

42、v); 默認使用eth%d 作為name參數. ether_setup()為初始化函數。釋放void free_netdev(struct net_device *dev); 69網絡設備的注冊和注銷int register_netdev(struct net_device *dev); void unregister_netdev(struct net_device *dev); 70網絡接口的打開和關閉(1)open和close接口由ifconfig調用open時所做的處理:-申請必要的資源(中斷、I/O地址空間等)-設置硬件地址(MAC地址)啟動發送隊列void netif_start_queue(struct net_device *dev); 71網絡接口的打開和關閉(2)close所做的處理:-釋放所申請到的資源-停止發送隊列-void netif_stop_queue(struct net_device *dev); 72數據包的發送sk_buff結構#in

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論