Linux设备驱动学习(2) 全局内存“设备” scull

来源:互联网 发布:大数据存储和处理 编辑:程序博客网 时间:2024/06/14 21:56

这次分析的是LDD3中的第一个程序scull

scull设备也是一个内存”设备“

结构图如下,来自LDD3



/* * Representation of scull quantum sets. */struct scull_qset {        void **data;//指针的指针,(*data)是一个指针变量,指示一片内存空间,这片内存中间内存储的也是地址        struct scull_qset *next;};struct scull_dev {        struct scull_qset *data;  /* Pointer to first quantum set *///第一个scull_qset的地址        int quantum;              /* the current quantum size *///每一个数据块的大小        int qset;                 /* the current array size *///每个scull_qset包含多少个指针        unsigned long size;       /* amount of data stored here *///设备中有多少可用的数据内存,也就是Quantum中的内存数据。        unsigned int access_key;  /* used by sculluid and scullpriv */        struct semaphore sem;     /* mutual exclusion semaphore     *///信号量        struct cdev cdev;         /* Char device structure              *///字符设备结构};//主程序/* * main.c -- the bare scull char module * * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet * Copyright (C) 2001 O'Reilly & Associates * * The source code in this file can be freely used, adapted, * and redistributed in source or binary form, so long as an * acknowledgment appears in derived source files.  The citation * should list that the code comes from the book "Linux Device * Drivers" by Alessandro Rubini and Jonathan Corbet, published * by O'Reilly & Associates.   No warranty is attached; * we cannot take responsibility for errors or fitness for use. * */#include <linux/config.h>#include <linux/module.h>#include <linux/moduleparam.h>#include <linux/init.h>#include <linux/kernel.h>/* printk() */#include <linux/slab.h>/* kmalloc() */#include <linux/fs.h>/* everything... */#include <linux/errno.h>/* error codes */#include <linux/types.h>/* size_t */#include <linux/proc_fs.h>#include <linux/fcntl.h>/* O_ACCMODE */#include <linux/seq_file.h>#include <linux/cdev.h>#include <asm/system.h>/* cli(), *_flags */#include <asm/uaccess.h>/* copy_*_user */#include "scull.h"/* local definitions *//* * Our parameters which can be set at load time. */int scull_major =   SCULL_MAJOR;int scull_minor =   0;int scull_nr_devs = SCULL_NR_DEVS;/* number of bare scull devices */int scull_quantum = SCULL_QUANTUM;//4000int scull_qset =    SCULL_QSET;//1000module_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("Alessandro Rubini, Jonathan Corbet");MODULE_LICENSE("Dual BSD/GPL");struct scull_dev *scull_devices;/* allocated in scull_init_module *//* * Empty out the scull device; must be called with the device * semaphore held. *///释放数据结构,最后剩下scull_dev结构int scull_trim(struct scull_dev *dev){struct scull_qset *next, *dptr;int qset = dev->qset;   /* "dev" is not-null */int i;for (dptr = dev->data; dptr; dptr = next) { /* all the list items */if (dptr->data) {for (i = 0; i < qset; i++)//遍历指针数组中的每个元素,释放其所指的空间kfree(dptr->data[i]);kfree(dptr->data);//最后释放这个指针数组。dptr->data = NULL;//置空}next = dptr->next;kfree(dptr);//释放scull_qset结构}dev->size = 0;//表示设备拥有的可读写的数据内存dev->quantum = scull_quantum;//重置各个参数dev->qset = scull_qset;dev->data = NULL;return 0;}#ifdef SCULL_DEBUG /* use proc only if debugging *//* * The proc filesystem: function to read and entry */int scull_read_procmem(char *buf, char **start, off_t offset,                   int count, int *eof, void *data){int i, j, len = 0;int limit = count - 80; /* Don't print more than this */for (i = 0; i < scull_nr_devs && len <= limit; i++) {struct scull_dev *d = &scull_devices[i];struct scull_qset *qs = d->data;if (down_interruptible(&d->sem))return -ERESTARTSYS;len += sprintf(buf+len,"\nDevice %i: qset %i, q %i, sz %li\n",i, d->qset, d->quantum, d->size);for (; qs && len <= limit; qs = qs->next) { /* scan the list */len += sprintf(buf + len, "  item at %p, qset at %p\n",qs, qs->data);if (qs->data && !qs->next) /* dump only the last item */for (j = 0; j < d->qset; j++) {if (qs->data[j])len += sprintf(buf + len,"    % 4i: %8p\n",j, qs->data[j]);}}up(&scull_devices[i].sem);}*eof = 1;return len;}/* * For now, the seq_file implementation will exist in parallel.  The * older read_procmem function should maybe go away, though. *//* * Here are our sequence iteration methods.  Our "position" is * simply the device number. */static void *scull_seq_start(struct seq_file *s, loff_t *pos){if (*pos >= scull_nr_devs)return NULL;   /* No more to read */return scull_devices + *pos;}static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos){(*pos)++;if (*pos >= scull_nr_devs)return NULL;return scull_devices + *pos;}static void scull_seq_stop(struct seq_file *s, void *v){/* Actually, there's nothing to do here */}static int scull_seq_show(struct seq_file *s, void *v){struct scull_dev *dev = (struct scull_dev *) v;struct scull_qset *d;int i;if (down_interruptible(&dev->sem))return -ERESTARTSYS;seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n",(int) (dev - scull_devices), dev->qset,dev->quantum, dev->size);for (d = dev->data; d; d = d->next) { /* scan the list */seq_printf(s, "  item at %p, qset at %p\n", d, d->data);if (d->data && !d->next) /* dump only the last item */for (i = 0; i < dev->qset; i++) {if (d->data[i])seq_printf(s, "    % 4i: %8p\n",i, d->data[i]);}}up(&dev->sem);return 0;}/* * Tie the sequence operators up. */static struct seq_operations scull_seq_ops = {.start = scull_seq_start,.next  = scull_seq_next,.stop  = scull_seq_stop,.show  = scull_seq_show};/* * Now to implement the /proc file we need only make an open * method which sets up the sequence operators. */static int scull_proc_open(struct inode *inode, struct file *file){return seq_open(file, &scull_seq_ops);}/* * Create a set of file operations for our proc file. */static struct file_operations scull_proc_ops = {.owner   = THIS_MODULE,.open    = scull_proc_open,.read    = seq_read,.llseek  = seq_lseek,.release = seq_release};/* * Actually create (and remove) the /proc file(s). */static void scull_create_proc(void){struct proc_dir_entry *entry;create_proc_read_entry("scullmem", 0 /* default mode */,NULL /* parent dir */, scull_read_procmem,NULL /* client data */);entry = create_proc_entry("scullseq", 0, NULL);//create a read-only file in /procif (entry)entry->proc_fops = &scull_proc_ops;//关联文件和对应的文件操作函数集}static void scull_remove_proc(void){/* no problem if it was not registered */remove_proc_entry("scullmem", NULL /* parent dir */);remove_proc_entry("scullseq", NULL);}#endif /* SCULL_DEBUG *//* * Open and close */int scull_open(struct inode *inode, struct file *filp){struct scull_dev *dev; /* device information */dev = container_of(inode->i_cdev, struct scull_dev, cdev);//经典的一个宏,cdev是scull_dev中的一个成员,现在有cdev的指针inode->i_cdev得到scull_dev的指针filp->private_data = dev; /* for other methods *//* now trim to 0 the length of the device if open was write-only */if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {if (down_interruptible(&dev->sem))//清空前先得到信号量return -ERESTARTSYS;scull_trim(dev); /* ignore errors */up(&dev->sem);//清空后释放信号量}return 0;          /* success */}int scull_release(struct inode *inode, struct file *filp){return 0;}/* * Follow the list *///得到第n个scull_qset的结构体指针(从0开始)struct scull_qset *scull_follow(struct scull_dev *dev, int n){struct scull_qset *qs = dev->data;        /* Allocate first qset explicitly if need be */if (! qs) {//为第一个scull_qset分配空间qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);if (qs == NULL)return NULL;  /* Never mind */memset(qs, 0, sizeof(struct scull_qset));}/* Then follow the list */while (n--) {if (!qs->next) {qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);if (qs->next == NULL)return NULL;  /* Never mind */memset(qs->next, 0, sizeof(struct scull_qset));}qs = qs->next;continue;}return qs;}/* * Data management: read and write */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;/* the first listitem */int quantum = dev->quantum, qset = dev->qset;int itemsize = quantum * qset; /* how many bytes in the listitem *///在一个scull_qset中总共有itemsize个字节可使用。int item, s_pos, q_pos, rest;ssize_t retval = 0;if (down_interruptible(&dev->sem))return -ERESTARTSYS;if (*f_pos >= dev->size)//*f_pos表示已读的字节数goto out;if (*f_pos + count > dev->size)count = dev->size - *f_pos;/* find listitem, qset index, and offset in the quantum *///这里举个例子比较好,比如*f_pos为4000*1000+4000*500+100item = (long)*f_pos / itemsize;//找到是第几个scull_qset结构(从第0个开始),例子里就是1了rest = (long)*f_pos % itemsize; //找到在1000个指针数组中指向的数据块中的偏移(从第0个开始),例子里就是4000*500+100s_pos = rest / quantum; q_pos = rest % quantum;//q_pos找到在这块4000B的空间里的偏移(0~3999)//s_pos找到是在第几个指针指向的数据块中,(从0开始)/* follow the list up to the right position (defined elsewhere) */dptr = scull_follow(dev, item);//找到第item个scull_qset结构,(从第0个开始)if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])goto out; /* don't fill holes *//* read only up to the end of this quantum */if (count > quantum - q_pos)//最多只读完这个数据块4000Bcount = 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, qset = dev->qset;int itemsize = quantum * qset;int item, s_pos, q_pos, rest;ssize_t retval = -ENOMEM; /* value used in "goto out" statements */if (down_interruptible(&dev->sem))return -ERESTARTSYS;/* find listitem, qset index and offset in the quantum *///写和读类似item = (long)*f_pos / itemsize;rest = (long)*f_pos % itemsize;s_pos = rest / quantum; q_pos = rest % quantum;/* follow the list up to the right position */dptr = scull_follow(dev, item);if (dptr == NULL)goto out;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;}/* write only up to the end of this quantum */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;        /* update the size */if (dev->size < *f_pos)//更新设备拥有的数据内存dev->size = *f_pos;  out:up(&dev->sem);return retval;}/* * The ioctl() implementation */int scull_ioctl(struct inode *inode, struct file *filp,                 unsigned int cmd, unsigned long arg){int err = 0, tmp;int retval = 0;    /* * extract the type and number bitfields, and don't decode * wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok() */if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;/* * the direction is a bitmask, and VERIFY_WRITE catches R/W * transfers. `Type' is user-oriented, while * access_ok is kernel-oriented, so the concept of "read" and * "write" is reversed */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: /* Set: arg points to the value */if (! capable (CAP_SYS_ADMIN))return -EPERM;retval = __get_user(scull_quantum, (int __user *)arg);break;  case SCULL_IOCTQUANTUM: /* Tell: arg is the value */if (! capable (CAP_SYS_ADMIN))return -EPERM;scull_quantum = arg;break;  case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */retval = __put_user(scull_quantum, (int __user *)arg);break;  case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */return scull_quantum;  case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */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: /* sHift: like Tell + Query */if (! capable (CAP_SYS_ADMIN))return -EPERM;tmp = scull_quantum;scull_quantum = arg;return tmp;          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;        /*         * The following two change the buffer size for scullpipe.         * The scullpipe device uses this same ioctl method, just to         * write less code. Actually, it's the same driver, isn't it?         */  case SCULL_P_IOCTSIZE:scull_p_buffer = arg;break;  case SCULL_P_IOCQSIZE:return scull_p_buffer;  default:  /* redundant, as cmd was checked against MAXNR */return -ENOTTY;}return retval;}/* * The "extended" operations -- only seek */loff_t scull_llseek(struct file *filp, loff_t off, int whence){struct scull_dev *dev = filp->private_data;loff_t newpos;switch(whence) {  case 0: /* SEEK_SET */newpos = off;break;  case 1: /* SEEK_CUR */newpos = filp->f_pos + off;break;  case 2: /* SEEK_END */newpos = dev->size + off;break;  default: /* can't happen */return -EINVAL;}if (newpos < 0) return -EINVAL;filp->f_pos = newpos;return newpos;}struct file_operations scull_fops = {.owner =    THIS_MODULE,.llseek =   scull_llseek,.read =     scull_read,.write =    scull_write,.ioctl =    scull_ioctl,.open =     scull_open,.release =  scull_release,};/* * Finally, the module stuff *//* * The cleanup function is used to handle initialization failures as well. * Thefore, it must be careful to work correctly even if some of the items * have not been initialized */void scull_cleanup_module(void){int i;dev_t devno = MKDEV(scull_major, scull_minor);/* Get rid of our char dev entries */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);}#ifdef SCULL_DEBUG /* use proc only if debugging */scull_remove_proc();#endif/* cleanup_module is never called if registering failed */unregister_chrdev_region(devno, scull_nr_devs);/* and call the cleanup functions for friend devices */scull_p_cleanup();scull_access_cleanup();}/* * Set up the char_dev structure for this device. */static void scull_setup_cdev(struct scull_dev *dev, int index){int err, devno = MKDEV(scull_major, scull_minor + index);    cdev_init(&dev->cdev, &scull_fops);dev->cdev.owner = THIS_MODULE;dev->cdev.ops = &scull_fops;err = cdev_add (&dev->cdev, devno, 1);/* Fail gracefully if need be */if (err)printk(KERN_NOTICE "Error %d adding scull%d", err, index);}int scull_init_module(void){int result, i;dev_t dev = 0;/* * Get a range of minor numbers to work with, asking for a dynamic * major unless directed otherwise at load time. */if (scull_major) {dev = MKDEV(scull_major, scull_minor);//静态分配,分配scull_nr_devs个设备号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;}        /*  * allocate the devices -- we can't have them static, as the number * can be specified at load time */scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);//为设备结构体分配空间if (!scull_devices) {result = -ENOMEM;goto fail;  /* Make this more graceful */}memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));        /* Initialize each device. */for (i = 0; i < scull_nr_devs; i++) {scull_devices[i].quantum = scull_quantum;scull_devices[i].qset = scull_qset;init_MUTEX(&scull_devices[i].sem);     //信号量在模块初始化时便已设置scull_setup_cdev(&scull_devices[i], i);//初始化字符设备cdev,次设备号为i}        /* At this point call the init function for any friend device */dev = MKDEV(scull_major, scull_minor + scull_nr_devs);//生成后续的总设备号dev += scull_p_init(dev);//scull_p_init()返回pipe设备数目dev += scull_access_init(dev);#ifdef SCULL_DEBUG /* only when debugging */scull_create_proc();#endifreturn 0; /* succeed */  fail:scull_cleanup_module();return result;}module_init(scull_init_module);module_exit(scull_cleanup_module);

关于对proc文件系统的操作没有深入探查,我会在后面的学习中逐步研究的。


原创粉丝点击