0.前言
研究生生活一切都在步入正轨,我也开始了新的学习,因为实在不想搞存储,所以就决定跟师兄学习设备驱动,看了两星期书,终于有点头绪了,开始记录吧!
1.准备工作
a)查看内核版本
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 "); return 0; } static void hello_exit(void){ printk(KERN_ALERT "Goodbye, cruel world "); } 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 "); 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! "); 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! "); 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 ", 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 ", 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 ", 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)代替。