0.前言html
研究生生活一切都在步入正軌,我也開始了新的學習,由於實在不想搞存儲,因此就決定跟師兄學習設備驅動,看了兩星期書,終於有點頭緒了,開始記錄吧!node
1.準備工做linux
a)查看內核版本shell
uname -r數組
b)安裝內核源碼樹(http://www.cnblogs.com/Jezze/archive/2011/12/23/2299871.html)數據結構
在www.linux.org上下載源碼編譯,這裏是.xz格式,須要安裝解壓工具,xz-utils; 併發
解壓方法示例:xz -d linux-3.1-rc4.tar.xz
tar -xf linux-3.1-rc4.tar 框架
c)安裝內核函數man手冊函數
編譯 make mandocs; 安裝 make installmandocs; 測試 man printk。工具
2.對字符驅動的簡單認識
a)我所理解的驅動程序就是使用Linux內核函數編寫一個內核模塊,實現對設備文件的打開,關閉,讀寫,控制等操做,這要對設備文件結構體的構成有深刻的瞭解,讓人寬慰的是驅動程序基本框架不變,難點就是程序要涉及併發控制,內存分配,中斷等問題,所以會較爲複雜,因此任重而道遠呀。
b)三個重要的數據結構:file_operations結構體裏面定義的主要是各類函數指針,經過定義設備的一系列函數,再將函數指針賦值,就完成了設備和函數的關聯,驅動程序會實現系統調用到實際硬件設備的操做的映射;file結構體表示一個打開的文件描述符,裏面有打開的標誌(只讀只寫),文件指針等等,該結構體和一個打開的設備文件相對應;inode結構體主要是用來和硬盤上的文件意義對應,硬盤上每新建一個文件都對應一個inode節點,包括inode號,塊號,大小等信息(不懂時看這個,阮一峯,理解inode:http://www.ruanyifeng.com/blog/2011/12/inode.html),查看他們的位置在 /usr/src/linux-3.**.pae/include/linux/fs.h 中。
c) Linux內核驅動模塊的剖析,這篇文章講了模塊加載到內核的具體過程。
http://www.ibm.com/developerworks/cn/linux/l-lkm/
d)設備文件:實現對硬件設備的抽象,使得對硬件的讀寫等價於對設備文件的讀寫。
e)主設備號和次設備號:主設備號用來表示不一樣種類的設備,次設備號主要用來區分設備。(http://blog.csdn.net/gqb_driver/article/details/8805179)。
3.測試HelloWorld模塊
a)源碼
#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void){ printk(KERN_ALERT "Hello, world\n"); return 0; } static void hello_exit(void){ printk(KERN_ALERT "Goodbye, cruel world\n"); } module_init(hello_init); module_exit(hello_exit);
b)Makefile
ifneq ($(KERNELRELEASE),) obj-m := hello.o else KERNELDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif
c)加載和卸載,這裏要切換到命令行模式(ctrl Alt f1 切換到 命令行模式, ctrl alt f7 切換到圖形界面模式)纔會顯示出結果,不然須要在 /var/log/syslg 中查看。
4.一個簡單的字符設備驅動程序
a)程序源碼 scull.h scull.c
#ifndef __SCULL_H__ #define __SCULL_H__ #include <linux/init.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/fcntl.h> #include <linux/cdev.h> #include <linux/ioctl.h> #include <asm/uaccess.h> #define SCULL_MAJOR 0 #define SCULL_NR_DEVS 4 #define SCULL_QUANTUM 100 #define SCULL_QSET 10 #define SCULL_IOC_MAGIC 'C' #define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0) #define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int) #define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int) #define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3) #define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4) #define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int) #define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int) #define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7) #define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8) #define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int) #define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC, 10, int) #define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11) #define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12) #define SCULL_IOC_MAXNR 12 struct scull_qset{ void **data; //量子集數組 struct scull_qset *next; }; struct scull_dev{ struct scull_qset *data; //量子集鏈表指針 int quantum; //量子集大小 int qset; //量子集數組的大小 unsigned long size; //數據的大小 struct semaphore sem; struct cdev cdev; }; int scull_init_module(void); void scull_cleanup_module(void); int scull_open(struct inode *, struct file *); int scull_release(struct inode *, struct file *); loff_t scull_llseek(struct file *, loff_t, int); long scull_ioctl(struct file *, unsigned int, unsigned long); ssize_t scull_read(struct file *, char __user *, size_t, loff_t*); ssize_t scull_write(struct file *, const char __user*, size_t, loff_t *); void scull_setup_cdev(struct scull_dev *, int); int scull_trim(struct scull_dev *); struct scull_qset *scull_follow(struct scull_dev *, int); #endif #include "scull.h" int scull_major = SCULL_MAJOR; int scull_minor = 0; int scull_nr_devs = SCULL_NR_DEVS; int scull_quantum = SCULL_QUANTUM; int scull_qset = SCULL_QSET; struct scull_dev *scull_devices; struct file_operations scull_fops = { .owner = THIS_MODULE, .llseek = scull_llseek, .read = scull_read, .write = scull_write, .unlocked_ioctl = scull_ioctl, .open = scull_open, .release = scull_release, }; int scull_open(struct inode *inode, struct file *filp){ struct scull_dev *dev; dev = container_of(inode->i_cdev,struct scull_dev, cdev); filp->private_data = dev; printk(KERN_WARNING "In OPen\n"); if((filp->f_flags & O_ACCMODE) == O_WRONLY){ if(down_interruptible(&dev->sem)) return -ERESTARTSYS; scull_trim(dev); up(&dev->sem); } return 0; } int scull_release(struct inode *inode, struct file *filp) { return 0; } loff_t scull_llseek(struct file *filp, loff_t off, int whence){ struct scull_dev *dev = filp->private_data; loff_t newpos = 0; switch(whence){ case 0: //SEEK_SET newpos = off; break; case 1: //SEEK_CUR newpos += off; break; case 2: //SEEK_END newpos += dev->size + off; break; default: return -EINVAL; } if(newpos < 0) return -EINVAL; filp->f_pos = newpos; return newpos; } ssize_t scull_read(struct file* filp, char __user *buf, size_t count, loff_t *f_pos){ struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; int quantum = dev->quantum; int qset = dev->qset; int itemsize = quantum * qset; int item, s_pos, q_pos, rest; ssize_t retval = 0; if(down_interruptible(&dev->sem)) return -ERESTARTSYS; if(*f_pos >= dev->size) goto out; if(*f_pos + count > dev->size) count = dev->size - *f_pos; item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; dptr = scull_follow(dev, item); if(dptr == NULL || !dptr->data || !dptr->data[s_pos]) goto out; if(count > quantum - q_pos) count = quantum - q_pos; if(copy_to_user(buf, dptr->data[s_pos] + q_pos, count)){ retval = -EFAULT; goto out; } *f_pos += count; retval = count; out: up(&dev->sem); return retval; } ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos){ struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; int quantum = dev->quantum; int qset = dev->qset; int itemsize = quantum * qset; int item, rest, s_pos, q_pos; ssize_t retval = -ENOMEM; printk(KERN_INFO "before down_interruptible!\n"); if(down_interruptible(&dev->sem)) return -ERESTARTSYS; item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; dptr = scull_follow(dev, item); if(dptr == NULL) goto out; printk(KERN_INFO "before kmalloc!\n"); if(!dptr->data){ dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL); if(!dptr->data) goto out; memset(dptr->data, 0, qset * sizeof(char *)); } if(!dptr->data[s_pos]){ dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL); if(!dptr->data[s_pos]) goto out; } if(count > quantum - q_pos) count = quantum - q_pos; if(copy_from_user(dptr->data[s_pos] + q_pos, buf, count)){ retval = -EFAULT; goto out; } *f_pos += count; retval = count; if(dev->size < *f_pos) dev->size = *f_pos; out: up(&dev->sem); return retval; } long scull_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){ int err = 0, tmp; int retval = 0; if(_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY; if(_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY; if(_IOC_DIR(cmd) & _IOC_READ) err = ! access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd)); else if(_IOC_DIR(cmd) & _IOC_WRITE) err = ! access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd)); if(err) return -EFAULT; switch(cmd){ case SCULL_IOCRESET: scull_quantum = SCULL_QUANTUM; scull_qset = SCULL_QSET; break; case SCULL_IOCSQUANTUM: if(!capable(CAP_SYS_ADMIN)) return -EPERM; retval = __get_user(scull_quantum, (int __user*)arg); // arg 是個指針 break; case SCULL_IOCTQUANTUM: if(!capable(CAP_SYS_ADMIN)) // arg 是個數值 return -EPERM; scull_quantum = arg; break; case SCULL_IOCGQUANTUM: retval = __put_user(scull_quantum, (int __user*)arg); break; case SCULL_IOCQQUANTUM: return scull_quantum; case SCULL_IOCXQUANTUM: if(!capable(CAP_SYS_ADMIN)) return -EPERM; tmp = scull_quantum; retval = __get_user(scull_quantum, (int __user*)arg); if(retval == 0) retval = __put_user(tmp, (int __user*)arg); break; case SCULL_IOCHQUANTUM: if(!capable(CAP_SYS_ADMIN)) return EPERM; tmp = scull_quantum; scull_quantum = arg; return arg; case SCULL_IOCSQSET: if(!capable(CAP_SYS_ADMIN)) return -EPERM; retval = __get_user(scull_qset, (int __user*)arg); break; case SCULL_IOCTQSET: if(!capable(CAP_SYS_ADMIN)) return -EPERM; scull_qset = arg; break; case SCULL_IOCGQSET: retval = __put_user(scull_qset, (int __user*)arg); break; case SCULL_IOCQQSET: return scull_qset; case SCULL_IOCXQSET: if(!capable(CAP_SYS_ADMIN)) return -EPERM; tmp = scull_qset; retval = __get_user(scull_qset, (int __user*)arg); if(retval == 0) retval = __put_user(tmp, (int __user*)arg); break; case SCULL_IOCHQSET: if(!capable(CAP_SYS_ADMIN)) return -EPERM; tmp = scull_qset; scull_qset = arg; return tmp; default: return -ENOTTY; } return retval; } struct scull_qset *scull_follow(struct scull_dev *dev, int n){ struct scull_qset *qs = dev->data; if(!qs){ qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL) ; if(qs == NULL) return NULL; memset(qs, 0, sizeof(struct scull_qset)); } while(n--){ if(!qs->next){ qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL); if(qs->next == NULL) memset(qs->next, 0, sizeof(struct scull_qset)); } qs = qs->next; continue; } return qs; } void scull_setup_cdev(struct scull_dev *dev, int index){ int err, devno = MKDEV(scull_major, scull_minor + index); //設備號 //1.初始化字符設備 cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; //2.把該字符設備添加到內核 err = cdev_add(&dev->cdev, devno, 1); if(err) printk(KERN_NOTICE "Errno %d adding scull %d", err, index); } int scull_trim(struct scull_dev *dev){ //清空scull的data數據域 struct scull_qset *next, *dptr; int q_set = dev->qset; int i; for(dptr = dev->data; dptr; dptr = next){ if(dptr->data){ for(i = 0; i < q_set; i++) kfree(dptr->data[i]); kfree(dptr->data); dptr->data = NULL; } next = dptr->next; kfree(dptr); } dev->size = 0; dev->quantum = scull_quantum; dev->qset = scull_qset; dev->data = NULL; return 0; } void scull_cleanup_module(void){ int i; dev_t devno = MKDEV(scull_major, scull_minor); //1.釋放內存空間 if(scull_devices){ for(i = 0; i < scull_nr_devs; i++){ scull_trim(scull_devices + i); cdev_del(&scull_devices[i].cdev); } kfree(scull_devices); } printk(KERN_WARNING "rmmod module!"); //2.釋放設備號 unregister_chrdev_region(devno, scull_nr_devs); } int scull_init_module(void){ int result, i; //返回值 索引 dev_t dev = 0; //設備號 //1.申請設備號 if(scull_major){ //已知主設備號 dev = MKDEV(scull_major, scull_minor); result = register_chrdev_region(dev, scull_nr_devs, "scull"); } else{ result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull"); //不知道主設備號 scull_major = MAJOR(dev); //內核動態分配合適的 } if(result < 0){ printk(KERN_WARNING "scull:can't get major %d\n", scull_major); return result; } //2.爲設備申請內存空間 scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL); if(!scull_devices){ result = -ENOMEM; goto fail; } memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev)); //3.初始化每一個設備 for(i = 0; i < scull_nr_devs; i++){ scull_devices[i].quantum = scull_quantum; scull_devices[i].qset = scull_qset; sema_init(&scull_devices[i].sem, 1); scull_setup_cdev(&scull_devices[i], i); //初始化字符設備結構體 } return 0; fail: scull_cleanup_module(); return result; } module_param(scull_major, int, S_IRUGO); module_param(scull_minor, int, S_IRUGO); module_param(scull_nr_devs, int, S_IRUGO); module_param(scull_quantum, int, S_IRUGO); module_param(scull_qset, int, S_IRUGO); MODULE_AUTHOR("Monica Lee"); MODULE_LICENSE("Dual BSD/GPL"); module_init(scull_init_module); module_exit(scull_cleanup_module);
c)測試文件
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <errno.h> #include <string.h> int main(int argc, const char *argv[]) { char buffer[20] = "Hello World!"; int fd, count; // 向字符設備中寫數據 fd = open("/dev/scull0", O_WRONLY); if(fd == -1) perror("Open failed"); count = write(fd, buffer, strlen(buffer)); if(count == -1) perror("Write failed"); else printf("Write count :%d\n", count); close(fd); //從字符設備中讀數據 memset(buffer, 0, sizeof buffer); fd = open("/dev/scull0", O_RDONLY); if(fd == -1) perror("Open failed"); count = read(fd, buffer, sizeof buffer); if(count == -1) perror("Read failed"); printf("Read data: %s\n", buffer); close(fd); return 0; }
d)編譯和執行過程(sudo模式)
make
insmod加載到內核;
cat /proc/devices 查看主設備號;
mknod /dev/scull0 c 250 0 建立設備文件;
執行測試程序;
rmmod 卸載模塊。
e)調試過程當中遇到的錯誤:
最新版本的內核中 ioctl 函數和init_MUTEX 函數都不存在了,原先的 int (*ioctl)(struct inode*, struct file*, unsigned int, unsigned long);被改成了long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);init_MUTEX函數,使用sema_init(sem, 1)代替。