【 聲明:著作權,歡迎轉載,請勿用於商業用途。 聯絡信箱:feixiaoxing @163.com】
上次我們編寫了一個簡單的字元裝置,但是涉及的內容比較少,只有open和read兩個函數。今天,我們打算在此基礎上擴充一下內容。基本的思路是這樣的:(1)編寫字元裝置下需要處理的各個函數,包括open、release、read、write、ioctl、lseek函數;(2)編寫一個使用者側的程式來驗證我們編寫的驅動函數是否正確。當然,我們編寫的代碼部分參考了宋寶華先生的《linux裝置驅動開發詳解》一書,在此說明一下。
在開始今天的內容之前,其實有一些題外話可以和大家分享一下。自從工作以來,我個人一直都有一個觀點。那就怎麼樣利用簡單的代碼來說明開發中的問題,或者是解釋軟體中的原理,這是一個很高的學問。有些道理看上去雲裡霧裡說不清楚,其實都可以通過編寫代碼來驗證的。os可以、cpu可以、cache可以、編譯器可以、網路通訊協定也可以,很多很多的內容完全可以通過幾行代碼就可以表達得非常清楚,但是事實上我們並沒有這麼做。我想原因無非是這麼幾條,一來授業者對相關知識的學習也是停留在概念上而已,二來我們的學習過於死板和教條、太關注知識、不求實踐,三就是學習者自身缺少思考的能力、缺少自我反省的能力、對很多東西不求甚解。對於簡單的linux裝置,我們完全可以通過這幾行代碼說清楚問題,免得大家還要苦苦追尋,百思而不得入門。
好了,說了這麼多,我們看看現在的驅動代碼是怎麼修改的把。
#include <linux/module.h>#include <linux/types.h>#include <linux/fs.h>#include <linux/errno.h>#include <linux/mm.h>#include <linux/sched.h>#include <linux/init.h>#include <linux/cdev.h>#include <asm/io.h>#include <asm/system.h>#include <asm/uaccess.h>#define CHRMEM_SIZE 0x1000#define MEM_CLEAR 0x1static int chr_major;struct chr_dev{struct cdev cdev;unsigned char mem[CHRMEM_SIZE];};struct chr_dev* char_devp;int chr_open(struct inode* inode, struct file* filp){filp->private_data = char_devp;return 0;}int chr_release(struct inode* inode, struct file* filp){return 0;}static int chr_ioctl(struct inode* inode, struct file* filp, unsigned int cmd, unsigned long arg){struct chr_dev* dev = filp->private_data;switch(cmd){case MEM_CLEAR:memset(dev->mem, 0, CHRMEM_SIZE);break;default:return -EINVAL;}return 0;}static ssize_t chr_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 chr_dev* dev = filp->private_data;if(p >= CHRMEM_SIZE){return 0;}if(count > CHRMEM_SIZE - p){return 0;}if(copy_to_user(buf, (void*)(dev->mem + p), count)){return -EINVAL;}else{*ppos += count;ret = count;}return ret;}static ssize_t chr_write(struct file* filp, const char __user* buf, ssize_t size, loff_t *ppos){unsigned long p = *ppos;unsigned int count = size;int ret = 0;struct chr_dev* dev = filp->private_data;if(p >= CHRMEM_SIZE){return 0;}if(count > CHRMEM_SIZE - p){count = CHRMEM_SIZE - p;}if(copy_from_user(dev->mem + p, buf, count)){ret = -EINVAL;}else{*ppos += count;ret = count;}return ret;}static loff_t chr_llseek(struct file* filp, loff_t offset, int orig){loff_t ret = 0;/* orig can be SEEK_SET, SEEK_CUR, SEEK_END */switch(orig){case 0:if(offset < 0){ret = -EINVAL;break;}if((unsigned int) offset > CHRMEM_SIZE){ret = -EINVAL;break;}filp->f_pos = (unsigned int) offset;ret = filp->f_pos;break;case 1:if((filp->f_pos + offset) > CHRMEM_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;}static const struct file_operations chr_ops = {.owner = THIS_MODULE,.llseek = chr_llseek,.read = chr_read,.write = chr_write,.ioctl = chr_ioctl,.open = chr_open,.release = chr_release};static void chr_setup_cdev(struct chr_dev* dev, int index){int err;int devno = MKDEV(chr_major, index);cdev_init(&dev->cdev, &chr_ops);dev->cdev.owner = THIS_MODULE;err = cdev_add(&dev->cdev, devno, 1);if(err){printk(KERN_NOTICE "Error happend!\n");}}int chr_init(void){int result;dev_t ndev;result = alloc_chrdev_region(&ndev, 0, 1, "chr_dev"); if(result < 0 ) { return result; } printk("chr_init(): major = %d, minor = %d\n", MAJOR(ndev), MINOR(ndev)); chr_major = MAJOR(ndev);char_devp = kmalloc(sizeof(struct chr_dev), GFP_KERNEL);if(!char_devp){result = -ENOMEM;goto final;}memset(char_devp, 0, sizeof(struct chr_dev));chr_setup_cdev(char_devp, 0);return 0;final:unregister_chrdev_region(ndev, 1);return 0;}void chr_exit(){cdev_del(&char_devp->cdev);kfree(char_devp);unregister_chrdev_region(MKDEV(chr_major, 0), 1);}module_init(chr_init);module_exit(chr_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("feixiaoxing!163.com");MODULE_DESCRIPTION("A simple device example!");
不可否認,我們的代碼出現了更多的內容,但是基本架構還是一致的。要是說區別,無非就是我們在原來的基礎上添加了新的處理函數而已。說起來,我們對於裝置的主要操作也就是這麼幾種,大家如果對此的概念已經非常成熟了,那麼後面的學習就會輕鬆很多。當然和之前的驅動一樣,我們也需要make & insmod char.ko & mknod /dev/chr_dev c 249 0。接下來,為了驗證上述的內容是否正確,編寫一段簡單的測試代碼是必不可少的。
#include <stdio.h>#include <fcntl.h>#include <unistd.h>#define MEM_CLEAR 0x01#define CHAR_DEV_NAME "/dev/chr_dev"int main(){ int ret; int fd; int index; char buf[32];/* open device */ fd = open(CHAR_DEV_NAME, O_RDWR | O_NONBLOCK); if(fd < 0) { printf("open failed!\n"); return -1; }/* set buffer data, which will be stored into device */ for(index = 0; index < 32; index ++) { buf[index] = index; }/* write data */ write(fd, buf, 32); memset(buf, 0, 32);/* read data */ lseek(fd, 0, SEEK_SET); read(fd, buf, 32); for(index = 0; index < 32; index ++) { printf("data[%d] = %d\n", index, buf[index]); }/* reset all data to zero, read it and check whether it is ok */ioctl(fd, MEM_CLEAR, NULL);lseek(fd, 0, SEEK_SET); read(fd, buf, 32); for(index = 0; index < 32; index ++) { printf("data[%d] = %d\n", index, buf[index]); } close(fd); return 0;}
細心的朋友可能發現了,我們在使用者側代碼中使用了很多的處理函數,基本上從open、release、read、write、lseek、ioctl全部包括了。測試代碼處理的流程也非常簡單,首先開啟裝置,接著寫資料,後面就是讀取資料,最後利用ioctl清除資料,程式返回。因為代碼中包含了注釋的內容,在此我們就不過多贅述了。大家慢慢看代碼,應該都會瞭解和明白的。
希望以上的這段內容對大家有所協助。