Introduction the naive “scull”
首先,什么是scull?
scull (Simple Character Utility for Loading Localities). scull is a char driver that acts on a memory area as though it were a device.
和第一个C程序Hello world一样,他什么都不能干,却能很好的阐释怎么一步步进阶的去写驱动
blog的最后,我会给出这对于scull设备的测试(如果对操作系统有一定的了解,看过MOS,APUE,CSAPP,就会有一种“什么都链接起来了”的感觉,从driver 内核,到用户空间程序,会知道到底是怎么实现的)
看scull之前确保你有足够的耐心和阅读代码的技巧(逻辑顺序)去看完书附带的scull源代码,不然一切都是空谈。非常喜欢@nero说的那句话
“看代码你还嫌长?”
一个常用的API —— module_param()
#include <linux/moduleparam.h> module_param(variable, type, perm);
Macro that creates a module parameter that can be adjusted by the user when the module is loaded (or at boot time for built-in code). The type can be one of bool, charp, int, invbool, long, short, ushort, uint, ulong, or in tarray.
实例:
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);
然后常用的文档相关信息的宏定义:
LINUX_VERSION_CODE Integer macro, useful to #ifdef version dependencies. EXPORT_SYMBOL (symbol); EXPORT_SYMBOL_GPL (symbol); Macro used to export a symbol to the kernel. The second form exports without using versioning information, and the third limits the export to GPL-licensed modules. MODULE_AUTHOR(author); MODULE_DESCRIPTION(description); MODULE_VERSION(version_string); MODULE_DEVICE_TABLE(table_info); MODULE_ALIAS(alternate_name); Place documentation on the module in the object file
Modern Linux kernels allow multiple drivers to share major numbers, but most devices that you will see are still organized on the one-major-one-driver principle.
To obtain the major or minor parts of a dev_t, use:
MAJOR(dev_t dev); MINOR(dev_t dev);
If, instead, you have the major and minor numbers and need to turn them into a dev_t, use:
MKDEV(int major, int minor);
上面的各种结构体是搞明白scull的基础,把握了有哪些结构体,代码也就是对这些结构体进行操作而已,熟悉了结构体,看代码也不会很晕了
Allocating and Freeing Device Numbers
One of the first things your driver will need to do when setting up a char device is to obtain one or more device numbers to work with.
The necessary function for this task is
register_chrdev_region, which is declared in <linux/fs.h>:
int register_chrdev_region(dev_t first, unsigned int count,char *name);
参数说明:
Here,first is the beginning device number of the range you would like to allocate. The minor number portion of first is often 0, but there is no requirement to that effect.
count is the total number of contiguous device numbers you are requesting.
Note that, if count is large, the range you request could spill over to the next major number; but everything will still work properly as long as the number range you request is available. Finally,
nameis the name of the device that should be associated with this number range; it will appear in /proc/devices and sysfs.
As with most kernel functions, the return value fromregister_chrdev_region will be 0 if the allocation was successfully performed. In case of error, a negative error code will be returned, and you will not have access
to the requested region.
更提倡使用动态分配设备号:
register_chrdev_region works well if you know ahead of time exactly which device numbers you want. Often, however, you will not know which major numbers your device will use;
there is a constant effort within the Linux kernel development com-munity to move over to the use of dynamicly-allocated device numbers. The kernel will happily allocate a major number for you on the fly, but you must
request this allocation by using a different function:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
With this function,dev is an output-only parameter that will, on successful completion, hold the first number in your allocated range. first minor should be the requested first minor number to use; it is usually 0. The
count and name parameters work like those given to
register_chrdev_region.
Regardless of how you allocate your device numbers, you should free them when they are no longer in use. Device numbers are freed with:
void unregister_chrdev_region(dev_t first, unsigned int count);
The usual place to call unregister_chrdev_region would be in your module’s cleanup function.
强烈建议使用动态分配设备号
For new drivers, we strongly suggest that you use dynamic allocation to obtain your major device number, rather than choosing a number randomly from the ones that are currently free. In other words, your drivers
should almost certainly be using alloc_chrdev_region rather than register_chrdev_region.
对于一个设备或文件(反正linux的精神就是Everything is a file)所有的操作都类似,都是通过open write close 等等system call interface,在这统一接口的背后,有个很棒的抽象!——struct file_operations
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, };
通过指针函数,把对某一设备的操作函数都封装到一个结构体里面去,多么伟大而又漂亮的思想啊!封装,抽象!
对于字符设备结构体的动态申请,加入内核,以及删除的操作接口
If you wish to obtain a standalone cdev structure at runtime, you may do so with code such as:
struct cdev *my_cdev = cdev_alloc(); my_cdev->ops = &my_fops;
Chances are, however, that you will want to embed the cdev structure within a device-specific structure of your own; that is what scull does. In that case, you should initialize the structure that you have already allocated with:
void cdev_init(struct cdev *cdev, struct file_operations *fops);
Either way, there is one other struct cdev field that you need to initialize. Like the file_operations structure, struct cdev has an owner field that should be set to THIS_MODULE .
Once the cdev structure is set up, the final step is to tell the kernel about it with a call to:
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
一旦调用了这个接口,设备就会被“激活”。随时可能被调用,此处使用一定小心!
Here,dev is the cdev structure,num is the first device number to which this device responds, and count is the number of device numbers that should be associated with the device. Often count is one, but there are situations
where it makes sense to have more than one device number correspond to a specific device. Consider, for example, the SCSI tape driver, which allows user space to select operating modes (such as density) by assigning multiple minor numbers to each physical
device.
There are a couple of important things to keep in mind when using cdev_add . The first is that this call can fail. If it returns a negative error code, your device has not been added to the system. It almost always
succeeds, however, and that brings up the other point: as soon as cdev_add returns, your device is “live” and its operations
can be called by the kernel. You should not call cdev_add until your driver is completely ready to handle operations on the device.
对于cdev结构体所有的操作API都在这里了
http://blog.csdn.net/cinmyheart/article/details/38238557
最后移除设备用cdev_del这个接口
To remove a char device from the system, call:
void cdev_del(struct cdev *dev);
Clearly, you should not access the cdev structure after passing it to cdev_del .
对于scull这个设备,利用这样自己构造的scull_dev结构体来把scull关键的要素抽象出来,封装在一起。写其他设备驱动的时候也一样,把设备的关键描述性特征抽象出来封装在一起。这是很棒的抽象思想!
struct scull_dev { struct scull_qset *data; /* Pointer to first quantum set */ int quantum; /* the current quantum size */ int qset; /* the current array size */ unsigned long size; /* amount of data stored here */ unsigned int access_key; /* used by sculluid and scullpriv */ struct semaphore sem; /* mutual exclusion semaphore */ struct cdev cdev; /* Char device structure */ };
The open Method
The open method is provided for a driver to do any initialization in preparation for later operations. In most drivers, open should perform the following tasks:
? Check for device-specific errors (such as device-not-ready or similar hardware problems)? Initialize the device if it is being opened for the first time
? Update the f_op pointer, if necessary
? Allocate and fill any data structure to be put in filp->private_data
The first order of business, however, is usually to identify which device is being opened. Remember that the prototype for the open method is:
int (*open)(struct inode *inode, struct file *filp);
The inode argument has the information we need in the form of its i_cdev field, which contains the cdev structure we set up before.
The release Method
The role of therelease method is the reverse ofopen . Sometimes you’ll find that the method implementation is called device_close instead of device_release. Either way, the device method should perform the following
tasks:
? Deallocate anything thatopen allocated in filp->private_data? Shut down the device on last close
The basic form of scull has no hardware to shut down, so the code required is minimal:
int scull_release(struct inode *inode, struct file *filp) { return 0; }
scull的使用
In scull , each device is a linked list of pointers, each of which points to a scull_dev structure. Each such structure can refer, by default, to at most four million bytes, through an array of intermediate pointers.
The released source uses an array of 1000 pointers to areas of 4000 bytes. We call each memory area a quantum and the array
(or its length) a quantum set.A scull device and its memory areas are shown in Figure 3-1.
read and write
The read and writemethods both perform a similar task, that is, copying data from and to application code. Therefore, their prototypes are pretty similar, and it’s worth introducing them at the same time:
ssize_t read(struct file *filp, char __user *buff,size_t count, loff_t *offp); ssize_t write(struct file *filp, const char __user *buff,size_t count, loff_t *offp);
参数说明:
For both methods, filp is the file pointer and
count is the size of the requested data transfer. The buff argument points to the user
buffer holding the data to be written or the empty buffer where the newly read data should be placed. Finally,
offp is a pointer to a “long offset type” object that indicates the file position the user is accessing. The return value is a “signed size type”; its use is discussed later.
Let us repeat that the buff argument to the read and write methods is a user-space pointer. Therefore, it cannot be directly dereferenced by kernel code. There are a few reasons for this restriction:
? Depending on which architecture your driver is running on, and how the kernel was configured, the user-space pointer may not be valid while running in kernel
mode at all. There may be no mapping for that address, or it could point to some other, random data.? Even if the pointer does mean the same thing in kernel space, user-space memory is paged, and the memory in question might not be resident in RAM when the system call is made. Attempting to
reference the user-space memory directly could generate a page fault, which is something that kernel code is not allowed to do. The result would be an “oops,” which would result in the
death of the process that made the system call.? The pointer in question has been supplied by a user program, which could be buggy or malicious. If your driver ever blindly dereferences a user-supplied pointer,
it provides an open doorway allowing a user-space program to access or overwrite memory anywhere in the system. If you do not wish to be responsible for compromising the security of your
users’ systems, you cannot ever derefer-ence a user-space pointer directly.
最重要的实现代码:main.c
/* * 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/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/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; //4000 int scull_qset = SCULL_QSET; //1000 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("Alessandro Rubini, Jonathan Corbet"); MODULE_LICENSE("Dual BSD/GPL"); struct scull_dev *scull_devices; /* allocated in scull_init_module */ //全局变量,scull_devices的值由kmalloc赋予 //指向SCULL_NR_DEVS(4)个连续内存的struct scull_dev 结构体 /* * Empty out the scull device; must be called with the device * semaphore held. */ /*清空设备,被调用时必须使用信号锁 semaphore*/ int scull_trim(struct scull_dev *dev) { struct scull_qset *next, *dptr; int qset = dev->qset; /* "dev" is not-null */ /*虽然这里注释说了dev不能是空,但是还是应该加上NULL检查*/ int i; /*通过两层for循环,第一层遍历每个directory,第二层遍历每个子集元素,这里可以利用那个figure3-1来理解*/ 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 = scull_quantum; dev->qset = scull_qset; dev->data = NULL; //将设备内存入口(dev->data指向设备内存入口)设置为NULL return 0; } /*以下是scull调试用的代码 利用proc 文件系统 会打印相关设备相关信息便于debug*/ #ifdef SCULL_DEBUG /* use proc only if debugging */ /* * The proc filesystem: function to read and entry */ static int scull_read_mem_proc_show(struct seq_file *m, void *v) { int i, j; int limit = m->size - 80; /* Don't print more than this */ for (i = 0; i < scull_nr_devs && m->count <= limit; i++) { struct scull_dev *d = &scull_devices[i]; struct scull_qset *qs = d->data; if (mutex_lock_interruptible(&d->mutex)) return -ERESTARTSYS; seq_printf(m,"\nDevice %i: qset %i, q %i, sz %li\n", i, d->qset, d->quantum, d->size); for (; qs && m->count <= limit; qs = qs->next) { /* scan the list */ seq_printf(m, " 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]) seq_printf(m, " % 4i: %8p\n", j, qs->data[j]); } } mutex_unlock(&scull_devices[i].mutex); } return 0; } /* * 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 (mutex_lock_interruptible(&dev->mutex)) 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]); } } mutex_unlock(&dev->mutex); 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 }; #define DEFINE_PROC_SEQ_FILE(_name) static int _name##_proc_open(struct inode *inode, struct file *file) { return single_open(file, _name##_proc_show, NULL); } static const struct file_operations _name##_proc_fops = { .open = _name##_proc_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, }; DEFINE_PROC_SEQ_FILE(scull_read_mem) /* * Actually create (and remove) the /proc file(s). */ static void scull_create_proc(void) { struct proc_dir_entry *entry; proc_create("scullmem", 0 /* default mode */, NULL /* parent dir */, &scull_read_mem_proc_fops); entry = proc_create("scullseq", 0, NULL, &scull_proc_ops); if (!entry) { printk(KERN_WARNING "proc_create scullseq failed\n"); } } 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 */ /* scull_open scull_read 等等函数都是对应的相应的对于该设备的系统调用的具体实现函数 很有意思*/ 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); filp->private_data = dev; /* for other methods */ /*所谓的open就是把这个设备的file结构体指针的private_data指针指向dev指针指向的结构体...貌似有点绕啊。。。哈哈*/ /* now trim to 0 the length of the device if open was write-only */ if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) { if (mutex_lock_interruptible(&dev->mutex)) return -ERESTARTSYS; scull_trim(dev); /* ignore errors */ mutex_unlock(&dev->mutex); } return 0; /* success */ } int scull_release(struct inode *inode, struct file *filp) { return 0; } /* * Follow the list */ /*找到dev指向设备的第n个set 会被scull_read调用*/ 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) { //如果设备是第一次使用,为描述第一个节点的结构体申请内存 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 */ int item, s_pos, q_pos, rest; ssize_t retval = 0; if (mutex_lock_interruptible(&dev->mutex)) return -ERESTARTSYS; if (*f_pos >= dev->size) //如果期望的文件偏置大于文件大小(跑到文件外头去咯),这时候结束read goto out; if (*f_pos + count > dev->size) //超出dev设备的大小,不能读count那么多,于是只能独到设备末端,将count 重置为dev->size - *f_pos 为当前最多能读的字节数 count = dev->size - *f_pos; /* find listitem, qset index, and offset in the quantum */ item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize;//这里很细节,*f_pos 被强制类型转换成long 避免数据的截断 s_pos = rest / quantum; q_pos = rest % quantum; //这里我是真没看懂s_pos 和 q_pos ,高手路过求指教。。。暂且放一放了 /* follow the list up to the right position (defined elsewhere) */ dptr = 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: mutex_unlock(&dev->mutex); 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 (mutex_lock_interruptible(&dev->mutex)) 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: mutex_unlock(&dev->mutex); return retval; } /* * The ioctl() implementation */ long scull_ioctl(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; /*应该加上指针是否为NULL的安全检查!*/ 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, .unlocked_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);//释放之前申请的scull_devices指向的结构体struct scull_dev } #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(); //暂且不考虑pipe管道模型和access模型,忽略以下两个接口,以后再研究 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); //把主次设备号写入到devno中 cdev_init(&dev->cdev, &scull_fops);//scull_fops == scull file operations. dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; //scull_fops是全局变量,指针。指向结构体struct file_operations 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) {//不会进来这里,int scull_major = SCULL_MAJOR; SCULL_MAJOR是0,于是进入else,动态分配设备号 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; } /* * 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); /*scull_devices 是全局变量 ,指向连续的scull_nr_devs(4) 个struct scull_dev结构体*/ if (!scull_devices) { /*如果kmalloc失败,那么退出,设备模块载入失败,清除模块*/ result = -ENOMEM; goto fail; /* Make this more graceful */ } memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev)); /*将scull_devices指向的内存区域置0*/ /* Initialize each device. */ for (i = 0; i < scull_nr_devs; i++) { scull_devices[i].quantum = scull_quantum; scull_devices[i].qset = scull_qset; mutex_init(&scull_devices[i].mutex);//对锁初始化 scull_setup_cdev(&scull_devices[i], 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); dev += scull_access_init(dev); #ifdef SCULL_DEBUG /* only when debugging */ scull_create_proc(); #endif return 0; /* succeed */ fail: scull_cleanup_module(); return result; } module_init(scull_init_module); module_exit(scull_cleanup_module);
scull.h
/* * scull.h -- definitions for the 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. * * $Id: scull.h,v 1.15 2004/11/04 17:51:18 rubini Exp $ */ #ifndef _SCULL_H_ #define _SCULL_H_ #include <linux/ioctl.h> /* needed for the _IOW etc stuff used later */ /* * Macros to help debugging */ /* 下面的宏定义是帮助debug的。 kernel的debug是不能交互式像user space的程序那样单步调试的 (其实可以,但是linus拒绝这样做,他觉得不好) */ #undef PDEBUG /* undef it, just in case */ #ifdef SCULL_DEBUG # ifdef __KERNEL__ /* This one if debugging is on, and kernel space */ # define PDEBUG(fmt, args...) printk( KERN_DEBUG "scull: " fmt, ## args) /* 这里这个宏定义比较好玩,是新的C99标准引入的参数变长的宏定义 */ # else /* This one for user space */ # define PDEBUG(fmt, args...) fprintf(stderr, fmt, ## args) # endif #else # define PDEBUG(fmt, args...) /* not debugging: nothing */ #endif #undef PDEBUGG //不得不吐槽。。。这个宏定义好隐蔽啊。。命名。。两个G。。GG #define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */ #ifndef SCULL_MAJOR //置0,让内核动态分配主设备号 #define SCULL_MAJOR 0 /* dynamic major by default */ #endif #ifndef SCULL_NR_DEVS //scull设备的数目 #define SCULL_NR_DEVS 4 /* scull0 through scull3 */ #endif #ifndef SCULL_P_NR_DEVS // #define SCULL_P_NR_DEVS 4 /* scullpipe0 through scullpipe3 */ #endif /* * The bare device is a variable-length region of memory. * Use a linked list of indirect blocks. * * "scull_dev->data" points to an array of pointers, each * pointer refers to a memory area of SCULL_QUANTUM bytes. * * The array (quantum-set) is SCULL_QSET long. */ #ifndef SCULL_QUANTUM #define SCULL_QUANTUM 4000 #endif #ifndef SCULL_QSET #define SCULL_QSET 1000 #endif /* * The pipe device is a simple circular buffer. Here its default size */ #ifndef SCULL_P_BUFFER #define SCULL_P_BUFFER 4000 #endif /* * Representation of scull quantum sets. */ struct scull_qset { void **data; struct scull_qset *next; }; struct scull_dev { struct scull_qset *data; /* Pointer to first quantum set */ int quantum; /* the current quantum size */ int qset; /* the current array size */ unsigned long size; /* amount of data stored here */ unsigned int access_key; /* used by sculluid and scullpriv */ struct mutex mutex; /* mutual exclusion semaphore */ struct cdev cdev; /* Char device structure */ }; /* * Split minors in two parts */ #define TYPE(minor) (((minor) >> 4) & 0xf) /* high nibble */ #define NUM(minor) ((minor) & 0xf) /* low nibble */ /* * The different configurable parameters */ extern int scull_major; /* main.c */ extern int scull_nr_devs; extern int scull_quantum; extern int scull_qset; extern int scull_p_buffer; /* pipe.c */ /* * Prototypes for shared functions */ int scull_p_init(dev_t dev); void scull_p_cleanup(void); int scull_access_init(dev_t dev); void scull_access_cleanup(void); int scull_trim(struct scull_dev *dev); ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos); ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos); loff_t scull_llseek(struct file *filp, loff_t off, int whence); long scull_ioctl(struct file *filp, unsigned int cmd, unsigned long arg); /* * Ioctl definitions */ /* Use 'k' as magic number */ #define SCULL_IOC_MAGIC 'k' /* Please use a different 8-bit number in your code */ #define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0) /* * S means "Set" through a ptr, * T means "Tell" directly with the argument value * G means "Get": reply by setting through a pointer * Q means "Query": response is on the return value * X means "eXchange": switch G and S atomically * H means "sHift": switch T and Q atomically */ #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) /* * The other entities only have "Tell" and "Query", because they're * not printed in the book, and there's no need to have all six. * (The previous stuff was only there to show different ways to do it. */ #define SCULL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC, 13) #define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC, 14) /* ... more to come */ #define SCULL_IOC_MAXNR 14 #endif /* _SCULL_H_ */
设备安装好之后,测试代码(用户层的API可以用了。。。就当是复习APUE的接口)
test.c
/**************************************************************** code writer :EOF code file : test.c code date : 2014.07.31 e-mail: [email protected] code purpose: just a demo for how to use "scull". Have a good time. *****************************************************************/ #include <stdio.h> #include <fcntl.h> #include <unistd.h> int main() { char dev_dir[] = "/dev/scull"; char buf[1024]; int fp; fp = open("/dev/scull",O_RDWR); if(fp < 0) { printf("open device failed!\n"); goto end; } write(fp,"hello world!\n",1024); lseek(fp,0,SEEK_SET); read(fp,buf,1024); printf("%s",buf); end: close(fp); return 0; }
[email protected]:/home/jasonleaster/Desktop/ldd3-examples-3.x-master/scull# gcc ./test.c
[email protected]:/home/jasonleaster/Desktop/ldd3-examples-3.x-master/scull# ./a.out
hello world!
它是hello world,但不仅仅是hello world!
—— jasonleaster
Introduction the naive“scull” 《linux设备驱动》 学习笔记