490 likes | 619 Views
讓驅動程式不再神祕. Linux 下的設備驅動程式. Linux 簡介. Linux 是類 UNIX 作業系統的一個分支,最初是由 Linus 于 1991 年為基于 Intel80386 的 IBM 兼容機開發的。 Linux 只是一個內核的標識,不同于我們平時所說的 RedHat Linux , Turb Linux 等發行版本,這些發行版本除了內核外還包括了不同的外部應用程式以方便用戶使用和管理作業系統。(我們以 RedHat 為例). 什麼是設備驅動程式. 設備驅動程式就是外部設備的軟體抽象,或者說是軟體表現. 用戶程式. 用戶空間. 系統調用界面. 內核空間.
E N D
讓驅動程式不再神祕 Linux下的設備驅動程式
Linux簡介 • Linux是類UNIX作業系統的一個分支,最初是由Linus于1991年為基于Intel80386的IBM兼容機開發的。 • Linux只是一個內核的標識,不同于我們平時所說的RedHat Linux,Turb Linux等發行版本,這些發行版本除了內核外還包括了不同的外部應用程式以方便用戶使用和管理作業系統。(我們以RedHat為例)
什麼是設備驅動程式 設備驅動程式就是外部設備的軟體抽象,或者說是軟體表現
用戶程式 用戶空間 系統調用界面 內核空間 內核子系統 其它模塊 其它模塊 驅動1 驅動2 驅動3 設備2 設備1 設備3 硬體
驅動程式有什麼用 設備驅動程式都是一個個獨立的“黑盒子”,使某個特定的硬體附應一個定義良好的內部編程界面,同時完全隱藏了設備的工作細節。用戶操作透過一組標準化的系統調用完成。驅動程式就是將這些調用映射到作用于實際硬體的設備特定的操作上。
作業系統內核的功能 • 進程管理 • 內存管理 • 文件系統 • 設備控制 • 網路功能
作業系統內核的功能 • 進程管理︰內核的進程管理活動就是在單個或多個CPU上實現多個進程的抽象。 • 內存管理︰內核在有限的可用內存資源上為每個進程都創建了一個虛擬尋址空間。 • 文件系統︰Linux(Unix)中的每個對象幾乎都可以被看作文件。內核在沒有架構的硬體上構造架構化的文件系統,所構造的文件系統抽象在整個系統中被廣泛使用。另外,Linux支持多種文件系統類型,如符合Linux標準的ext2文件系統和常用的FAT文件系統等。
作業系統內核的功能 • 網路功能︰大部分網路操作都和具體進程無關──數據包的傳入是異步事件 。所以網路功能也必須由作業系統來管理 • 設備控制︰幾乎每個系統操作最終都會映射到物理設備上。除了處理器、內存以及其它很有限的幾個實體外,所有設備控制操作都由與被控制設備相關的代碼來完成,這段代碼就叫做設備驅動程式
模塊化的驅動程式 • 為了使系統更有效的營運,Linux支持內核的動態擴展,即在系統營運時給內核增加新的功能(模塊)。 • 驅動程式就是幾個可以模塊化的功能之一。這也是Linux下驅動程式與Windows下驅動程式的最大區別。
什麼是模塊 模塊是一段沒有鏈接的目標代碼(.o),它可由insmod程式動態的鏈接到正在營運的內核。鏈接后,它就成了內核的一部分,直到用rmmod程式解除鏈接。
系統內核 驅動程式
將驅動程式加入到內核中 系統內核 驅動程式
世界上最簡單的驅動程式 #define MODULE #include <linux/module.h> int init_module(void) { printk(“<1> Hello World !\n”); return 0; } void cleanup_module(void) { printk(“<1> Goodbye !\n”); }
root# gcc –c helloworld.c root# insmod helloworld.o Hello World !
root# lsmod Module Size Used by helloworld 464 0 (unused) ……
root# rmmod helloworld Goodbye !
設備驅動程式分類 • 字符設備 • 塊設備 • 網路界面設備
字符設備 字符設備是能夠像位元組流(例如文件)一樣被訪問的設備,一般不使用緩存技術。 字符設備驅動程式實現這種特性至少需要實現open、close、read和write系統調用。
塊設備 對塊設備來說,最大的不同就是能夠容納文件系統(例如磁片),並且大都使用緩存技術。 塊設備和字符設備的區別僅僅在于內核內部管理數據的模式,也就是內核和驅動程式的界面不同。然而這些差異對用戶是透明的。 另外,塊設備的界面必須支持掛載(mount)文件系統。
網路界面 任何網路事務都要經過一個網路界面來完成。網路界面由內核中的網路子系統驅動,負責發送和接收數據包,但它無需了解每項事務是如何映射到實際傳送的數據包的。 內核和網路驅動程式之間的通信完全不同于內核和字符以及塊設備驅動程式之間的通信,內核調用一套和數據包傳輸相關的函數,而不是read、write等。
初識兩個架構 • File架構:在內核中標識一個打開的設備 • file_operations架構 ︰用于訪問驅動程式的函數 • 如果用面向對象的概念來考慮問題 ,那么file可以看作一個對象,而操作它的函數(由file_operations架構標識)就是對象的方法。
File架構 file架構是在<linux/fs.h>中定義的一個數據架構,用來代表一個打開的文件(包括設備文件和普通文件)。它與用戶空間程式中的FILE沒有任何關聯。
Struct file{ mode_t f_mode , Loff_t f_pos , unsigned int f_flags , struct file_operations *f_op , void *private_data , struct dentry *f_dentry , …… }
file_operations架構 file_operations架構是一個定義在<linux/fs.h>中的函數指針數組。每個文件都透過file架構中的f_op字段與它自己的函數集相關聯。這些函數負責系統調用的實現,而這個架構負責系統調用的映射。
Struct file_operations{ ssize_t (*read) (struct file * , char * , size_t , loff_t *) , ssize_t (*write) (struct file* , char * , size_t , loff_t *) int (*ioctl) (struct inode * , struct file * , unsigned int , unsigned long) , int (*open) (struct inode * , strct file *) , …… }
這兩個數據架構是我們理解一個驅動程式的最基本的數據架構。file架構是系統生成的,編寫驅動程式時會用就行。而file_operations是要開發者自己來寫的。這個數據架構是我們學習的重點,它可採用如下的模式來聲明︰這兩個數據架構是我們理解一個驅動程式的最基本的數據架構。file架構是系統生成的,編寫驅動程式時會用就行。而file_operations是要開發者自己來寫的。這個數據架構是我們學習的重點,它可採用如下的模式來聲明︰
struct file_operations mydev_fops = { llseek : mydev_llseek , read : mydev_read , write : mydev_write , ioctl : mydev_ioctl , open : mydev_open , release: mydev_release , };
設備驅動在系統內部的組織 • 主設備號 • 次設備號 • 在系統中,設備類型和主設備號唯一標識驅動程式,而次設備號是由主設備號確定的驅動程式來使用的,它對系統沒有任何意義。 • 在驅動程式中,次設備號用來區分共享同一驅動的不同設備或者不同功能。
驅動程式向系統註冊 • int register_chrdev(unsigned int major , const char *name , struct file_operations *fops); • 在道統模式下,向系統添加一個驅動程式必須首先為其分發一個主設備號。 • 參數major就是顯式的為該驅動分發的主設備號。也可以動態的分發(major=0)。
一個完整的驅動程式框架 #include <linux/module.h> #include <linux/kernel.h> #include <linux/mm.h> #include <asm/uaccess.h> #include <linux/init.h> #define DEV_NAME "TestDevice" #define DEV_MAJOR 0 #define READ_BUF_SIZE xxx #define WRITE_BUF_SIZE xxx
int major = DEV_MAJOR struct Mydevice { const char *name; /* 設備的名字 */ unsigned int major; /* 主設備號 */ unsigned int minor; /* 次設備號 */ devfs_handle_t devfs; /* 設備文件節點 */ unsigned char *read_buffer; /* 讀緩沖區 */ unsigned char *write_buffer; /* 寫緩沖區 */ wait_queue_head_t read_queue;/* 讀等待隊列 */ wait_queue_head_t write_queue;/* 寫等待隊列 */ struct semaphore sem; /* 競態時用到的信號量 */ };
int my_open(struct inode *inode,struct file *filp) • { • struct Mydevice *dev = kmalloc(sizeof(struct Mydevice), GFP_KERNEL); • if(dev == NULL) { • printk(" KERN_ALERT allocate device memory failed.\n"); • return(-ENOMEM); • } • dev->name = DEV_NAME; • dev->major = MAJOR(inode->i_rdev); • dev->minor = MINOR(inode->i_rdev); • dev->read_buffer = kmalloc(sizeof(READ_BUF_SIZE),GFP_KERNEL); • if(dev->read_buffer == NULL) • printk(" KERN_ALERT allocate read buffer memory failed.\n"); • dev->write_buffer = kmalloc(sizeof(WRITE_BUF_SIZE),GFP_KERNEL); • if(dev->read_buffer == NULL) • printk(" KERN_ALERT allocate write buffer memory failed.\n"); • init_waitqueue_head(&dev->read_queue); • init_waitqueue_head(&dev->write_queue); • if(filp->private_data == NULL) • filp->private_data = dev; • MOD_INC_USE_COUNT; • printk("The function of Myopen has been called !\n"); • return 0; • }
int my_release(struct inode *inode,struct file *filp) • { • struct Mydevice *dev = filp->private_data; • if(dev->read_buffer != NULL) • kfree(dev->read_buffer); • if(dev->write_buffer != NULL) • kfree(dev->write_buffer); • kfree(dev); • MOD_DEC_USE_COUNT; • printk("The function of Myrelease has been called ﹗\n"); • return 0; • }
ssize_t my_read(struct file *filp,char *buf,size_t count,loff_t *offp) • { • char *pdata = kmalloc(sizeof(count),GFP_KERNEL); • if(pdata == NULL) • return (-ENOMEM); • copy_to_user(buf,pdata,count); • *offp += count; • printk("The function of Myread has been called ﹗\n"); • return count; • }
ssize_t my_write(struct file *filp,char *buf,size_t count,loff_t *offp) • { • char *pdata = kmalloc(sizeof(count),GFP_KERNEL); • if(pdata == NULL) • return (-ENOMEM); • copy_from_user(pdata,buf,count); • *offp += count; • printk("The function of Myread has been called ﹗\n"); • return count; • }
int my_ioctl (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg) • { • switch(cmd){ • case 1 :{ • printk("This is command 1 !\n"); • break; • } • case 2 :{ • printk("This is command 2 !\n"); • break; • } • case 3 :{ • printk("This is command 3 !\n"); • break; • } • default :{ • printk("There is no such command !\n"); • return -1; • } • } • return 0; • }
struct file_operations fops = { • open : my_open, /* open函數 */ • release: my_release, /* write函數 */ • read : my_read, /* read函數 */ • write: my_write, /* write函數 */ • ioctl: my_ioctl, /* ioctl函數 */ • }; • 用上面聲明的各函數聲明文件操作架構file_operations。
int my_init(void){ • int res = register_chrdev(DEV_MAJOR,DEV_NAME,&fops); • if(res < 0){ • printk("My device register failed !\n"); • return res; • } • if(res > 0) major = res; • printk("My device register success !\n"); • return 0; • }
int my_cleanup(void){ • unregister_chrdev(major,DEV_NAME); • printk("My device release success !\n"); • return 0; • } • module_init(my_init); • module_exit(my_cleanup); • MODULE_LICENSE("GPL");
設備文件節點 應用程式如何知道這個看起來很醜陋的主設備號(就像IP一樣難以記憶),聰明的內核開發者早就為我們考慮到了──給設備起個名字(就像域名一樣)。 給設備起名字,就是在文件系統上顯式的創建一個文件節點,即設備文件 。 因此,應用程式看到的設備和文件是一樣的,他們都是文件系統上的一個節點,有著相同的界面。
用mknod命令創建設備文件節點 • mknod /dev/mydev1 c 254 0 • mydev1就是設備的名字,它是/dev目錄下的一個文件節點 • c表示字符設備 • 254是主設備號 • 0是次設備號
全局變量chrdevs[] struct device_struct { const char *name; struct file_oprations * fops; }; static struct device_struct chrdevs[MAX_CHRDEV]
chrdevs[]是個全局變量,它在字符設備管理中處于核心地位。Linux透過它組織起MAX_CHRDEV(255)個device_struct架構來記錄相關設備的名稱以及其對應的設備操作函數界面(fops)。而主設備號就是該架構的數組下標。chrdevs[]是個全局變量,它在字符設備管理中處于核心地位。Linux透過它組織起MAX_CHRDEV(255)個device_struct架構來記錄相關設備的名稱以及其對應的設備操作函數界面(fops)。而主設備號就是該架構的數組下標。
chrdevs file_operations{} 0 1 device_struct{} 2 3 4 5 6 ……
傻瓜步驟 • 設計驅動程式 1.1實現各個文件操作函數 1.2聲明函數指針數組 1.3完成初始化函數,並向系統註冊。完成清除函數。 • 建立設備文件節點 mknod /dev/testdev c xxx 0 • 設計測試應用程式
傻瓜步驟 • 加載驅動程式 insmod testdev.o • 營運應用程式進行測試 • 卸載驅動程式 rmmod testdev