首页 > 代码库 > Introduction the naive“scull” 《linux设备驱动》 学习笔记

Introduction the naive“scull” 《linux设备驱动》 学习笔记

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);


有关设备号处理的宏定义分析



LDD和scull相关各种结构体的故事



                     上面的各种结构体是搞明白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. Thecount and name parameters work like those given toregister_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 andcount is the size of the requested data transfer. The buff argument points to the userbuffer 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 = http://www.mamicode.com/NULL;>
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: jasonleaster@gmail.com
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;}

root@ubuntu:/home/jasonleaster/Desktop/ldd3-examples-3.x-master/scull# gcc ./test.c
root@ubuntu:/home/jasonleaster/Desktop/ldd3-examples-3.x-master/scull# ./a.out
hello world!



它是hello world,但不仅仅是hello world!

                                                                —— jasonleaster