快速参考:
#include <linux/types.h> dev_t dev_t is the type used to represent device numbers within the kernel.
int MAJOR(dev_t dev); int MINOR(dev_t dev); Macros that extract the major and minor numbers from a device number.
dev_t MKDEV(unsigned int major, unsigned int minor); Macro that builds a dev_t data item from the major and minor numbers.
#include <linux/fs.h> The “filesystem” header is the header required for writing device drivers. Many important functions and data structures are declared in here.
int register_chrdev_region(dev_t first, unsigned int count, char *name) int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name) void unregister_chrdev_region(dev_t first, unsigned int count);
Functions that allow a driver to allocate and free ranges of device numbers. register_chrdev_region should be used when the desired major number is known in advance; for dynamic allocation, use alloc_chrdev_region instead. int register_chrdev(unsigned int major, const char *name, struct file_operations *fops); The old (pre-2.6) char device registration routine. It is emulated in the 2.6 kernel but should not be used for new code. If the major number is not 0, it is used unchanged; otherwise a dynamic number is assigned for this device.
int unregister_chrdev(unsigned int major, const char *name); Function that undoes a registration made with register_chrdev. Both major and the name string must contain the same values that were used to register the driver.
struct file_operations; struct file; struct inode; Three important data structures used by most device drivers. The file_operations structure holds a char driver’s methods; struct file represents an open file, and struct inode represents a file on disk.
#include <linux/cdev.h> struct cdev *cdev_alloc(void); void cdev_init(struct cdev *dev, struct file_operations *fops); int cdev_add(struct cdev *dev, dev_t num, unsigned int count); void cdev_del(struct cdev *dev); Functions for the management of cdev structures, which represent char devices within the kernel. #include <linux/kernel.h> container_of(pointer, type, field); A convenience macro that may be used to obtain a pointer to a structure from a pointer to some other structure contained within it.
#include <asm/uaccess.h> This include file declares functions used by kernel code to move data to and from user space. unsigned long copy_from_user (void *to, const void *from, unsigned long count); unsigned long copy_to_user (void *to, const void *from, unsigned long count);
Copy data between user space and kernel space.
第一节的测试:
#include <linux/module.h> #include <linux/init.h> #include <linux/types.h> /* dev_t */ #include <linux/kdev_t.h> /* MAJOR(dev_t dev); MINOR(dev_t dev); */ #include <linux/fs.h> /* chrdev_region */ #include "my_scull.h" MODULE_AUTHOR("chen"); MODULE_LICENSE("Dual BSD/GPL"); int my_scull_major = MY_SCULL_MAJOR; int my_scull_minor = 0; int my_scull_nr_devs = MY_SCULL_NR_DEVS; static int my_scull_init(void) { int result; dev_t dev; printk(KERN_ALERT "my scull init\n"); /* * Get a range of minor numbers to work with, asking for a dynamic * major unless directed otherwise at load time. */ if(my_scull_major) { dev = MKDEV(my_scull_major, my_scull_minor); result = register_chrdev_region(dev, my_scull_nr_devs, "my_scull"); } else { result = alloc_chrdev_region(&dev, my_scull_minor, my_scull_nr_devs, "my_scull"); my_scull_major = MAJOR(dev); } if(result < 0) { printk(KERN_WARNING "my scull:can't get major %d\n", my_scull_major); return result; } printk(KERN_ALERT "myscull dev:%d, major:%d, minor:%d\n", dev, my_scull_major, my_scull_minor); return 0; } static void my_scull_cleanup(void) { dev_t devno = MKDEV(my_scull_major, my_scull_minor); /* my_scull_cleanup is never called if registering failed */ unregister_chrdev_region(devno, my_scull_nr_devs); printk(KERN_ALERT "my scull clean up\n"); } module_init(my_scull_init); module_exit(my_scull_cleanup);
my_scull_load:
#!/bin/sh module="my_scull" device="my_scull" mode="644" # invoke insmod with all arguments we got # and use a pathname, as newer modutils don't look in. by default /sbin/insmod ./$module.ko $* || exit 1 # remove stale nodes rm -f /dev/${device}[0-3] major=$(awk "\$2==\"$module\" {print \$1}" /proc/devices) echo $major #echo "/dev/${device}0 c $major 0" mknod /dev/${device}0 c $major 0 mknod /dev/${device}1 c $major 1 mknod /dev/${device}2 c $major 2 mknod /dev/${device}3 c $major 3 # give appropriate group/permissions, and change the group. # Not all distributions have staff, some have "wheel" instead. group="staff" grep -q '^staff:' /etc/group || group="wheel" chgrp $group /dev/${device}[0-3] chmod $mode /dev/${device}[0-3]
my_scull_unload:
#!/bin/sh module="my_scull" device="my_scull" # invoke rmmod with all arguments we got /sbin/rmmod $module $* || exit 1 # Remove stale nodes rm -f /dev/${device} /dev/${device}[0-3]
看完第三章后和参考例程后的数据
#include <linux/module.h> #include <linux/init.h> #include <linux/types.h> /* dev_t */ #include <linux/kdev_t.h> /* MAJOR(dev_t dev); MINOR(dev_t dev); */ #include <linux/fs.h> /* chrdev_region */ #include <asm/uaccess.h> #include "my_scull.h" MODULE_AUTHOR("chen"); MODULE_LICENSE("Dual BSD/GPL"); int my_scull_major = MY_SCULL_MAJOR; int my_scull_minor = 0; int my_scull_nr_devs = MY_SCULL_NR_DEVS; int my_scull_quantum = MY_SCULL_QUANTUM; int my_scull_qset = MY_SCULL_QSET; struct my_scull_dev *scull_devices; /* allocated in my_scull_init */ static struct file_operations my_scull_fops = { .owner = THIS_MODULE, .open = my_scull_open, .release = my_scull_release, .write = my_scull_write, .read = my_scull_read, // .llseek = my_scull_llseek, // .ioctl = my_scull_ioctl, }; /* * Empty out the scull device; must be called with the device * semaphore held. */ int my_scull_trim(struct my_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); } dev->size = 0; dev->quantum = my_scull_quantum; dev->qset = my_scull_qset; dev->data = NULL; return 0; } /* * Set up the char dev structure for this device. */ static void my_scull_setup_cdev(struct my_scull_dev *dev, int index) { int err, devno = MKDEV(my_scull_major, my_scull_minor+index); cdev_init(&dev->cdev, &my_scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &my_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); } /* * Open and close */ int my_scull_open(struct inode *inode, struct file *filp) { struct my_scull_dev *dev; /* device information */ dev = container_of(inode->i_cdev, struct my_scull_dev, cdev); /* from dev_t get my_scull_dev pointer */ 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) { my_scull_trim(dev); /* ignore errors */ } return 0; /* success */ } int my_scull_release(struct inode *inode, struct file *filp) { return 0; } /* * Follow the list */ struct scull_qset *my_scull_follow(struct my_scull_dev *dev, int n) { struct scull_qset *qs = dev->data; /* Allocate first qset explicitly if need be */ if(! qs) { qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL); if(qs == NULL) return NULL; /* Nerver 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; /* Nerver mind */ memset(qs->next, 0, sizeof(struct scull_qset)); } qs = qs->next; continue; } return qs; } /* * Data management: read and write */ ssize_t my_scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { struct my_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 */ 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; /* 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 (defined elsewhere) */ dptr = my_scull_follow(dev, item); 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) 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 my_scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { struct my_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 = my_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; } static int my_scull_init(void) { int result, i; dev_t dev; printk(KERN_ALERT "my scull init\n"); /* * Get a range of minor numbers to work with, asking for a dynamic * major unless directed otherwise at load time. */ if(my_scull_major) { dev = MKDEV(my_scull_major, my_scull_minor); result = register_chrdev_region(dev, my_scull_nr_devs, "my_scull"); } else { result = alloc_chrdev_region(&dev, my_scull_minor, my_scull_nr_devs, "my_scull"); my_scull_major = MAJOR(dev); } if(result < 0) { printk(KERN_WARNING "my scull:can't get major %d\n", my_scull_major); return result; } printk(KERN_ALERT "myscull dev:%d, major:%d, minor:%d\n", dev, my_scull_major, my_scull_minor); /* * allocate the device -- we can't have them static, as the number * can be specified at load time */ scull_devices = kmalloc(my_scull_nr_devs*sizeof(struct my_scull_dev), GFP_KERNEL); if(!scull_devices) { result = -ENOMEM; goto fail; /* Make this more graceful */ } memset(scull_devices, 0, my_scull_nr_devs * sizeof(struct my_scull_dev)); /* Initialize each device */ for(i=0;i<my_scull_nr_devs;i++) { scull_devices[i].quantum = my_scull_quantum; scull_devices[i].qset = my_scull_qset; init_MUTEX(&scull_devices[i].sem); my_scull_setup_cdev(&scull_devices[i], i); } dev = MKDEV(my_scull_major, my_scull_minor+my_scull_nr_devs); // dev += my_scull_p_init(dev); // dev += my_scull_access_init(dev); return 0; /* succees */ fail: my_scull_cleanup(); } static void my_scull_cleanup(void) { dev_t devno = MKDEV(my_scull_major, my_scull_minor); int i; if(scull_devices) { for(i=0;i<my_scull_nr_devs;i++) { my_scull_trim(scull_devices + i); cdev_del(&scull_devices[i].cdev); } kfree(scull_devices); } /* my_scull_cleanup is never called if registering failed */ unregister_chrdev_region(devno, my_scull_nr_devs); printk(KERN_ALERT "my scull clean up\n"); } module_init(my_scull_init); module_exit(my_scull_cleanup);
头文件
#ifndef __MY_SCULL_H_ #define __MY_SCULL_H_ #include <linux/ioctl.h> #include <linux/cdev.h> #ifndef MY_SCULL_MAJOR #define MY_SCULL_MAJOR 0 /* dynamic major by default */ #endif #ifndef MY_SCULL_NR_DEVS #define MY_SCULL_NR_DEVS 4 /* scull0 through scull3 */ #endif #ifndef MY_SCULL_QUANTUM #define MY_SCULL_QUANTUM 4000 #endif #ifndef MY_SCULL_QSET #define MY_SCULL_QSET 1000 #endif /* * Representation of scull quantum sets. */ struct scull_qset { void **data; struct scull_qset *next; }; struct my_scull_dev { struct scull_qset *data; /* Pointer to first quantum set */ int quantum; /* the current quantum size */ int qset; /* the current arry size */ unsigned long size; /* amount of data sotred here */ unsigned int access_key; /* used by sculluid and scullpriv */ struct semaphore sem; /* mutual exclusion semaphore */ struct cdev cdev; /* Char device structure */ }; int my_scull_trim(struct my_scull_dev *dev); static void my_scull_setup_cdev(struct my_scull_dev *dev, int index); int my_scull_open(struct inode *inode, struct file *filp); int my_scull_release(struct inode *inode, struct file *filp); struct scull_qset *my_scull_follow(struct my_scull_dev *dev, int n); ssize_t my_scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); ssize_t my_scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); static int my_scull_init(void); static void my_scull_cleanup(void); #endif