廢話少說,先來介紹幾個必須要知道的和字符設(shè)備有關(guān)的結(jié)構(gòu)體,然后結(jié)合代碼詳細講解。 第一部分 必要的設(shè)備結(jié)構(gòu)體 1)linux 2.6內(nèi)核中使用cdev結(jié)構(gòu)體表示字符設(shè)備: struct cdev { struct kobject kobj;//內(nèi)嵌的kobject對象 struct module *owner;//所屬模塊 struct file_operations *ops;//文件操作結(jié)構(gòu)體 struct list_head list; dev_t dev;//設(shè)備號,長度為32位,其中高12為主設(shè)備號,低20位為此設(shè)備號 unsigned int count; }; 可以使用下列宏從dev_t中獲得主次設(shè)備號: 也可以使用下列宏通過主次設(shè)備號生成dev_t: MAJOR(dev_t dev); MKDEV(int major,int minor); MINOR(dev_t dev); 說明:在2.6內(nèi)核中可以容納大量的設(shè)備,而先前的內(nèi)核版本卻限于255個主設(shè)備號和255個此設(shè)備號。 2)file_operations結(jié)構(gòu)體中的成員函數(shù)是字符設(shè)備驅(qū)動程序設(shè)計中的主體內(nèi)容,這些函數(shù)實際會在應(yīng)用程序進行l(wèi)inux的open(),write(),read(),close()等系統(tǒng)調(diào)用時被最終調(diào)用。目前的file_operations結(jié)構(gòu)已經(jīng)變得非常大,在這里我們就關(guān)心和我這個設(shè)備程序有關(guān)的幾個函數(shù),以后用到了,咱們再提也不遲: static const struct file_operations globalmem_fops = { .owner= THIS_MODULE, .llseek = globalmem_llseek,//修改一個文件的當前讀寫位置并將新位置返回,出錯時,返回一個負值 .read = globalmem_read, .write = globalmem_write, .ioctl = globalmem_ioctl,//設(shè)備相關(guān)控制命令的實現(xiàn),內(nèi)核可以識別一部分控制命令(這時就不用調(diào)用ioctl ()),如果設(shè)備不提供這個函數(shù),而內(nèi)核又不識別該命令,則返回-EINVAL. .open = globalmem_open, .release = globalmem_release, }; 3)file結(jié)構(gòu)與用戶空間的File沒有任何關(guān)聯(lián),strcut file是一個內(nèi)核結(jié)構(gòu),它不會出現(xiàn)在用戶程序中。它代表一個打開的文件(不局限于設(shè)備驅(qū)動程序,系統(tǒng)中每個打開的文件在內(nèi)核空間中都有一個對應(yīng)的file結(jié)構(gòu))。它由內(nèi)核在open時創(chuàng)建,并傳遞給在該文件進行操作的所有函數(shù),直到最后的close函數(shù)。在文件的所有實例都被關(guān)閉之后,內(nèi)核會釋放掉這個數(shù)據(jù)結(jié)構(gòu)。 4)內(nèi)核用它node結(jié)構(gòu)在內(nèi)部表示文件,其和file結(jié)構(gòu)不同。后者表示打開的文件描述符。對于單個文件,可能會有很多表示打開的文件描述符的file結(jié)構(gòu),但它們都指向單個inode結(jié)構(gòu)。在它里邊和我們驅(qū)動程序有用的字段只有兩個: dev_t i_rdev; //對表示設(shè)備文件的inode結(jié)構(gòu),該字段包含了真正的設(shè)備編號 struct cdev *i_cdev; //是表示字符設(shè)備的內(nèi)核的內(nèi)部結(jié)構(gòu)。當inode指向一個字符設(shè)備文件時,該字段包含了指向struct cdev結(jié)構(gòu)的指針。 我么可以使用下邊兩個宏從inode中獲得主設(shè)備號和此設(shè)備號: unsigned int iminor(struct inode *inode); unsigned int imajor(struct inode *inode); 為了程序的可移植性,我們應(yīng)該使用上述宏,而不是直接操作i_rdev。 第二部分 源代碼詳解
#define GLOBALMEM_SIZE 0X1000 /*全局內(nèi)存大小4kb*/ #define MEM_CLEAR 0x1 //清零全局內(nèi)存 static globalmem_major = GLOBALMEM_MAJOR; //globalmem設(shè)備結(jié)構(gòu)體 struct globalmem_dev *globalmem_devp;//設(shè)備結(jié)構(gòu)指針 //文件打開函數(shù) int globalmem_open(struct inode *inode, struct file *filp) { filp->private_data= globalmem_devp; return 0; } //文件釋放函數(shù) int globalmem_release(struct inode *inode, struct file *filp) { return 0; } //globalmem_ioctl函數(shù) static int globalmem_ioctl(struct inode *inodep, struct file *filp,unsigned int cmd, unsigned long arg) { struct globalmem_dev *dev = filp->private_data; switch (cmd) { case MEM_CLEAR://清除全局內(nèi)存 memset(dev->mem, 0,GLOBALMEM_SIZE); printk(KERN_INFO "globalmem is set to zero\n"); break; default: return - EINVAL;//其他不支持的命令 } return 0; } //globalmem_read函數(shù) static ssize_t globalmem_read(struct file *filp,char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct globalmem_dev *dev=filp->private_data;//獲得設(shè)備結(jié)構(gòu)指針 //分析和獲取有效的寫長度 if (p >= GLOBALMEM_SIZE) return count ? -ENXIO: 0; if (count > GLOBALMEM_SIZE-p)//如果要求讀取的比實際可用的少 count = GLOBALMEM_SIZE-p; if (copy_to_user(buf,(void *)(dev->mem + p),count)) { ret = -EFAULT; } else { *ppos += count; ret = count; printk(KERN_INFO "read %d byte(s) from %d",count,p); } return ret; } //globalmem_write static ssize_t globalmem_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; struct globalmem_dev *dev = filp->private_data;//獲得設(shè)備結(jié)構(gòu)指針 //分析和獲取有效的寫長度 if( p >= GLOBALMEM_SIZE) return count ? -ENXIO: 0; if (count > GLOBALMEM_SIZE-p)//如果要求讀取的比實際可用的少 count = GLOBALMEM_SIZE-p; if (copy_from_user(dev->mem + p,buf, count)) ret = -EFAULT; else { *ppos+= count; ret = count; printk(KERN_INFO "written %d bytes(s) from %d\n", count, p); } return ret; } //globalmem_seek函數(shù) static loff_t globalmem_llseek(struct file *filp,loff_t offset,int orig) { loff_t ret=0; switch(orig) { case 0://從文件開頭開始偏移 if(offset < 0) { ret = - EINVAL; break; } if((unsigned int)offset > GLOBALMEM_SIZE)//偏移越界 { ret = - EINVAL; break; } filp->f_pos= (unsigned int)offset; ret = filp->f_pos; break; case 1://從當前位置偏移 if((filp->f_pos+offset) > GLOBALMEM_SIZE) //偏移越界 { ret = - EINVAL; break; } if((filp->f_pos+offset)<0) { ret = - EINVAL; break; } filp->f_pos += offset; ret = filp->f_pos; break; default: ret = - EINVAL; break; } return ret; } 以上介紹了Linux簡單字符設(shè)備中涉及到的基本而要特別重要的數(shù)據(jù)結(jié)構(gòu),還有就是源代碼部分中有關(guān)file_operations中的所有操作,這些都將在應(yīng)用程序進行Linux的 open(),write(),read(),close()等系統(tǒng)調(diào)用時最終被調(diào)用。這些都是從源代碼中直接copy出來的,無論順序還是結(jié)構(gòu)都保持了實際代碼的完整性,可以和下篇部分完整拷貝下來實際測試使用。 |
|