ldd3代碼分析(高級字符驅動)

/*
 * main.c -- the bare scull char module
 *
 * 此代碼爲ldd3例子,自己加了些註釋;希望可以和更多和我同樣有興趣的鳥兒們一塊學習討論。

 * 哪有註釋的不對的地方請發mail給我,或留言;
 *
 * author : [email protected] 
 *
 * date: 2007-2-7
 * 
 * Note:註釋的每一個關鍵的段都以[tag00]作了標籤,大家可以按照tag的順序閱讀;
 * e.g: 搜索 "Tag000"
 
*/


#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;
int scull_qset =    SCULL_QSET;

/*
 * 模塊參數,可在模塊轉載時賦值,很靈活方便;
 * e.g:
 *         insmod scull.ko scull_major=111 scull_nr_devs=3 scull_quantum=1000
 *
 *[形參說明]
 * 1 -- 變量名;
 * 2 -- 變量類型;
 * 3 -- sysfs入口項的訪問許可掩碼(一般用S_IRUGO就成);
*/

module_param(scull_major, 
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 */
/* Note: 不要把它理解成一個指向scull_dev結構的指針, 它其實是一個scull_dev結構數組,等待下面kmalloc分配多個我們scull設備空間 */


/*
 * Empty out the scull device; 就像銷燬鏈表,和理解如何編寫一個字符驅動沒有關係,可以不看;
 *
 * must be called with the device semaphore held. 要注意一下了,肯定是要同步的;
 *
 
*/

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

    dev
->size = 0;
    dev
->quantum = scull_quantum;
    dev
->qset = scull_qset;
    dev
->data = NULL;
    
return 0;
}


//Start: [Tag003] proc的實現,可以先不看;
#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 *= &scull_devices[i];
        
struct scull_qset *qs = d->data;
        
if (down_interruptible(&d->sem))
            
return -ERESTARTSYS;
        len 
+= sprintf(buf+len," Device %i: qset %i, q %i, sz %li ",
                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 ",
                    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 ",
                                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, 
" Device %i: qset %i, q %i, sz %li ",
            (
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 ", 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 ",
                            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);
    
if (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 */
//End



/* 開始實現對設備操作的方法集了,關鍵!!! */
/*
 * Open and close
 
*/

//[Tag004]
/*
open應完成的工作有:
    1.檢查設備特定的錯誤(諸如設備未就緒或類似的硬件問題)
    2.如果設備是首次打開,則對其進行初始化;
    3.如有必要,更新f_op指針;
    4.分配並填寫filp->private_data;(在這裏我們只實現這項即可)
*/


/*
[形參說明]
    struct inode *inode -- 用它的i_cdev成員得到dev;
    struct file *filp -- 將得到的dev存放到他的成員private_data中;
*/

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);
    
/*
    [說明]
        1.我們要填充的應該是我們自己的特殊設備,而不是鉗在他裏面的字符設備結構;
        2.inode結構的i_cdev成員這能提供基本字符設備結構;
        3.這裏利用了定義在<linux/kernel.h>中的宏來實現通過cdev得到dev;
    
*/

    
    
/*
    以後read , write ,等操作的實現中就靠他來得到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 */
}


/* close device file, in here we do nothing */
/* 
 * [Tag005]
 * close應完成的工作有:
 *    1.釋放由open分配的,保存在filp->private_data中的所有內容;
 *  2.在最後一次關閉操作時關閉設備;
 * [注意:]並不是每次的close系統調用都會去調用到release. 在open時,也僅在open時纔會創建
 * 一個新的數據結構;在fork, dup時只是增加了這個結構中維護的一個引用計數;
 * 所以當這個引用計數爲0時,調用的close才意味着要釋放設備數據結構,此時release纔會被調用;
 
*/

int scull_release(struct inode *inode, struct file *filp)
{
    
return 0;
}



/*
 * Follow the list
 * 
 * 第一次調用時用於創建鏈表;
 * 然後就是找到第n個節點;
 * 對編寫驅動程序關係不大;
 
*/

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, 
0sizeof(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, 0sizeof(struct scull_qset));
        }

        qs 
= qs->next;
        
continue;
    }

    
return qs;
}


/*[Tag006]
 * Data management: read and write
 * [read和write的參數]
 *        1] filp -- 文件指針;用它的成員filp->private_data得到dev;
 *         2] buf -- 都是來自用戶空間的指針;
 *      3] count -- 緩衝區大小;(希望傳輸的字節數目)
 *        4] f_pos -- 指向一個長偏移量對象的指針,這個對象指明瞭用戶在文件中進行存取
 *            操作的位置;
 *
 *[返回值]
 *         1]如果返回值等於count,則完成了所請求數目的字節傳輸;
 *        2]如果返回值是正,但小於count,則繼續讀或寫餘下的數據;
 *        3]如果爲0,則證明已經到了文件尾;
 *        4]如果爲負,則發生了錯誤。會返回一個錯誤碼,該值指明瞭發生了什麼錯誤。
 *             錯誤碼在<linux/errno.h>中定義;
 *            例如:-EINTR (系統調用被中斷)
 *                  -EFAULT (無效地址)
 
*/



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 (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; //在這個鏈表項的什麼位置(偏移量),用於下面找qset索引和偏移量;
    s_pos = rest / quantum;        //在這個節點裏**data這個指針數組的第幾行;
     q_pos = rest % quantum; //在這行,即這個量子裏的偏移量;

    
/* 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;

/*
 * 上面爲這步準備了具體在哪個鏈表項的指針數組的第幾行的第幾列(即dptr->data[s_pos] + q_pos)
 * 從這個位置的內核態的buf中拷給用戶態    
*/
    

//關鍵一步,將數據拷給用戶空間
    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;
}


//與read的實現類似
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 < 0return -EINVAL;
    filp
->f_pos = newpos;
    
return newpos;
}



//[Tag007]將這組操作打包爲一個對象;
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
 
*/



//[Tag008]模塊卸載或goto fail時;
/*
 * 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();

}



/* [Tag002] 
    這裏主要乾了2件事;
    在內核內部使用struct cdev結構來表示字符設備;
    [1]在這裏因爲我們將cdev結構嵌入到自己的scull_dev設備下了,所以我們用下面這個方法來
    初始化已分配的結構;
    cdev_init(&dev->cdev, &scull_fops);
    
    [2]告訴內核我們新結構的信息;
*/

/*
 * 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);
    
   
// [1]
    cdev_init(&dev->cdev, &scull_fops);    /* 初始化, 字符設備和給它一組在它上面操作的方法集 */
    
    
/* 填充基本字符設備的成員 */
    dev
->cdev.owner = THIS_MODULE;        //模塊計數
    dev->cdev.ops = &scull_fops;        //附上一組操作自己的方法集
    
//    [2]
    err = cdev_add (&dev->cdev, devno, 1);
    
/*
    函數說明:
        cdev -- 字符設備的結構指針,我們就是要把他告訴給內核;
        devno -- 設備編號,用MKDEV利用全局的主設備號和次設備號生成的;
        1    -- 是應該和該設備關聯的設備編號的數量, 一般情況下都爲1;
            一般我們都是一個設備編號對應一個設備;        
    
*/

    
/*
    注意:
        在調用cdev_add後,我們的設備就被添加到系統了,他"活"了. 附加的操作集也就可以被內核調用了
        ,因此,在驅動程序還沒有完全準備好處理設備上的操作時,就不能調用cdev_add!
    
*/

    
/* Fail gracefully if need be */
    
if (err)
        printk(KERN_NOTICE 
"Error %d adding scull%d", err, index);
}


/*[Tag000]
 * 當模塊加載時,調用;但是爲什麼要放在最後來實現他呢,看到Tag002時,你應該就明白了;
*/

int scull_init_module(void)
{
    
int result, i;
    dev_t dev 
= 0;

/* [Tag001] */
/* [1]分配設備編號 */
/*
 * 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); /* 利用主設備號,找到設備編號給方法1用 */
        result 
= register_chrdev_region(dev, scull_nr_devs, "scull");
    }
 else {        /* 動態自己生成設備編號,然後再利用設備編號得到主設備號;
                        記住如果用這個方法那麼就要後建設備文件了,因爲不能提前知道主號
                        當然也可以利用ldd3書中提供的腳本,巨方便&&通用 
*/

        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]設備對象實例化*/ 
        
/* 
     * 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));

/* [3]在這裏初始化設備用了2.6的新方法,在scull_setup_cdev裏完成 */
        
/* Initialize each device. */
    
for (i = 0; i < scull_nr_devs; i++{
        scull_devices[i].quantum 
= scull_quantum;    /* 可以根據自己insmod時傳參
                                                        來自己改變量子和量子集(指針數組)的大小 
*/

        scull_devices[i].qset 
= scull_qset;
        init_MUTEX(
&scull_devices[i].sem);
        scull_setup_cdev(
&scull_devices[i], i);    /* 在分別完主設備編號後goto Tag002 設備註冊 */
    }


        
/* 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);        
//insmod    
module_exit(scull_cleanup_module);    //rmmod

 

/*
 * 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
 
*/


#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)
#  
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
#define PDEBUGG(fmt, args...) /* nothing: it's a placeholder */

#ifndef SCULL_MAJOR
#define SCULL_MAJOR 0   /* dynamic major by default */
#endif

#ifndef SCULL_NR_DEVS
#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        /* 每個指針(量子)指向一個4000字節的區域 */
#endif

#ifndef SCULL_QSET
#define SCULL_QSET    1000        /* 一個有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;    /* 下一個鏈表節點(鏈表項) */
}
;

/* 我們自己的設備(包含了基本的cdev字符設備結構) */
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        */
}
;

/*
 * 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);
int     scull_ioctl(struct inode *inode, 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_ */

 

linux設備驅動(第三版)的例子裏還提供了幾個非常通用和靈活的腳本,還有一個標準的Makefile.

大家可以利用下面的文件,修改一下運行試試效果。如果想了解讀寫的具體過程可以試試用strace命令來追蹤;

e.g : strace ls -l >/dev/scull0

 

# Comment/uncomment the following line to disable/enable debugging
#非常標準的Makefile,稍加修改就可以用在很多驅動上
#將這個開關打開,看proc的輸出。在這個例子分別有二個用於輸出的proc文件。一個是用老方法實現的
#
/proc/scullmem
#新方法 
/proc/scullseq
DEBUG 
= y    


# Add your debugging flag (or not) to CFLAGS
ifeq ($(DEBUG),y)
  DEBFLAGS 
= ---DSCULL_DEBUG # "-O" is needed to expand inlines
else
  DEBFLAGS 
= -O2
endif

CFLAGS 
+= $(DEBFLAGS)
CFLAGS 
+= -I$(LDDINC)

ifneq ($(KERNELRELEASE),)
# call from kernel build system

scull
-objs := main.o pipe.o access.o

obj
-m    := scull.o

else

KERNELDIR 
?= /lib/modules/$(shell uname -r)/build
PWD       :
= $(shell pwd)

modules:
    $(MAKE) 
-C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD)/../include modules

endif



clean:
    rm 
-rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

depend .depend dep:
    $(CC) $(CFLAGS) 
-*.c > .depend


ifeq (.depend,$(wildcard .depend))
include .depend
endif

 

下面這個是load,因爲在2.6的新方法中我們是先動態分配主設備號,然後再根據/proc/modules來建立設備文件的

看下面這個腳本的實現就知道了。

 

#!/bin/sh
# $Id: scull_load,v 
1.4 2004/11/03 06:19:49 rubini Exp $
module
="scull"
device
="scull"
mode
="664"

# Group: since distributions 
do it differently, look for wheel or use staff
if grep -'^staff:' /etc/group; then
    group
="staff"
else
    group
="wheel"
fi

# invoke insmod with all arguments we got
# and use a pathname, 
as insmod doesn't look in . by default
/sbin/insmod ./$module.ko $* || exit 1

# retrieve major number
major
=$(awk "/$2=="$module" {print /$1}" /proc/devices)

# Remove stale nodes and replace them, then give gid and perms
# Usually the script 
is shorter, it's scull that has several devices in it.

rm 
-/dev/${device}[0-3]
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
ln 
-sf ${device}0 /dev/${device}
chgrp $group 
/dev/${device}[0-3
chmod $mode  
/dev/${device}[0-3]

rm 
-/dev/${device}pipe[0-3]
mknod 
/dev/${device}pipe0 c $major 4
mknod 
/dev/${device}pipe1 c $major 5
mknod 
/dev/${device}pipe2 c $major 6
mknod 
/dev/${device}pipe3 c $major 7
ln 
-sf ${device}pipe0 /dev/${device}pipe
chgrp $group 
/dev/${device}pipe[0-3
chmod $mode  
/dev/${device}pipe[0-3]

rm 
-/dev/${device}single
mknod 
/dev/${device}single  c $major 8
chgrp $group 
/dev/${device}single
chmod $mode  
/dev/${device}single

rm 
-/dev/${device}uid
mknod 
/dev/${device}uid   c $major 9
chgrp $group 
/dev/${device}uid
chmod $mode  
/dev/${device}uid

rm 
-/dev/${device}wuid
mknod 
/dev/${device}wuid  c $major 10
chgrp $group 
/dev/${device}wuid
chmod $mode  
/dev/${device}wuid

rm 
-/dev/${device}priv
mknod 
/dev/${device}priv  c $major 11
chgrp $group 
/dev/${device}priv
chmod $mode  
/dev/${device}priv






 

下面這個用於卸載

#!/bin/sh
module
="scull"
device
="scull"

# invoke rmmod with all arguments we got
/sbin/rmmod $module $* || exit 1

# Remove stale nodes

rm 
-/dev/${device} /dev/${device}[0-3
rm 
-/dev/${device}priv
rm 
-/dev/${device}pipe /dev/${device}pipe[0-3]
rm 
-/dev/${device}single
rm 
-/dev/${device}uid
rm 
-/dev/${device}wuid





 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章