Linux设备驱动程序学习(3)-字符设备驱动程序

来源:互联网 发布:淘宝网站是怎么赚钱的 编辑:程序博客网 时间:2024/05/17 05:08

开始学习《Linux设备驱动程序(第三版)》第三章,本章主要是学习字符设备的基本操作,以scull为研究对象,即“simple character utility for loading localities”(区域装载的简单字符工具),scull是一个操作内存区域的字符设备驱动程序,这片内存区域就相当于一个设备。scull可以为真实的设备驱动程序提供一个样板。


一、主设备号和次设备号

主设备号标识设备对应的驱动程序。次设备号由内核使用,用于正确确定设备文件对应的设备。内核允许多个驱动程序共享一个主设备号。

1、 设备编号的内部表达

内核中,用dev_t类型<linux/types.h>来保存设备编号,dev_t是个32位的数,12位用来表示主设备号,20位表示次设备号。

实际使用中,应该使用<linux/kdev_t.h>中的宏来变换格式:

获得dev_t的主设备号或

次设备号

MAJOR(dev_t dev)

MINOR(dev_t dev)

将主设备号和次设备号转化成dev_t类型

MKDEV(int major, int minor)


2、分配和释放设备编号

在建立一个字符设备之前,驱动程序首先要做的就是获得一个或者多个设备编号,完成该工作的函数声明在<lnux/fs.h>中。

静态分配设备编号:

1 int register_chrdev_region(dev_t from, unsigned count, const char *name)

适用于已知设备号的情况

成功执行返回0

1 dev_t from        要分配的设备编号范围的起始值2 unsigned count      所请求的连续设备编号的个数3 const char *name       与该编号范围关联的设备名称

 

动态分配设备编号

1 int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

成功执行返回0

1 dev_t *dev            用于保存已分配范围的第一个设备编号2 unsigned baseminor    第一个次设备号,通常是03 unsigned count        所请求的连续设备编号的个数4 const char *name      与该编号范围关联的设备名称

 

释放设备编号

不管是采用什么方法分配设备号,释放设备号需使用下面函数:

1 void unregister_chrdev_region(dev_t from, unsigned count)
1 dev_t from              设备注册的第一个设备号2 unsigned count      已注册的连续设备编号的个数

 

分配设备编号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定设备号的余地。

 

下面是scull.c中用来获取设备号的代码:

复制代码
 1 if (scull_major) { 2         dev = MKDEV(scull_major, scull_minor); 3         result = register_chrdev_region(dev, scull_nr_devs, "scull"); 4     } else { 5         result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, 6                 "scull"); 7         scull_major = MAJOR(dev); 8     } 9     if (result < 0) {10         printk(KERN_WARNING "scull: can't get major %d\n", scull_major);11         return result;12     }
复制代码

这部分中,函数中参数name是和该编号范围关联的设备名称,获取设备编号后,它将出现在/proc/devices和sysfs中。

 

二、一些重要的数据结构

大部分的驱动程序都会涉及到三个内核数据结构,分别是file_operations、files和inode。它们定义在<lnux/fs.h>中。

1、file结构

系统中,每一个打开的文件在内核空间都有一个对应的file结构。由内核在open时创建并传递给在该文件上进行操作的所有函数,直到最后的close函数。在文件的所有实例都被关闭后,内核会释放这个结构。指向struct file的指针通常被称为file或者filp(文件指针),书中一致取filp。File是结构本身,filp则是指向该结构的指针。

struct file比较重要的结构成员如下:

复制代码
1 struct file {2     struct dentry   *f_dentry;      // 文件对应的目录项(dentry)结构3     struct file_operations  *f_op;      // 与文件相关的操作4     unsigned int    f_flags;        // 文件标志5     mode_t  f_mode;     // 文件模式,可读或可写6     loff_t  f_pos;      // 当前读写/位置7     void    *private_data;      // 跨系统调用时保存信息8 };
复制代码

 

1、inode结构

内核用inode表示磁盘上的文件。区别file结构:

file表示打开的文件描述符,对于单个文件,可能会有许多个表示打开的文件描述符的file结构,但是他们都指向单个inode结构。

inode结构中包含了大量的有关文件信息:

1 struct inode {2     dev_t   i_rdev;     // 包含了真正的设备编号       3     struct cdev *i_cdev;    // 当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针4 };

 

内核开发者增加了两个新的宏,可用来从一个inode中获得主设备号和次设备号:

获得主设备号

unsigned imajor(struct inode *inode)

获得次设备号

unsigned iminor(struct inode *inode)

如果我们想从inode结构中获得主次设备号,我们应该使用上述宏,而不是直接操作i_rdev。

 

1、 文件操作

struct file_operations结构用来建立设备驱动程序和设备编号之间的连接。  结构中包含了一组函数指针,每个打开的文件(在内部用一个file结构表示)和一组函数关联。这些操作主要是用来实现系统调用,我们可以认为文件是一个“对象”,而操作它的函数是“方法”,即对象声明的动作将作用于其本身

file_operations结构或者指向这类结构的指针称为fops。该结构中的每一个字段都必须指向驱动程序中实现特定操作的函数,对于支持的操作,对应的字段可置为NULL值。

复制代码
 1 struct file_operations { 2     struct module *owner; 3     loff_t (*llseek) (struct file *, loff_t, int); 4     ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 5     ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t); 6     ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 7     ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t); 8     int (*readdir) (struct file *, void *, filldir_t); 9     unsigned int (*poll) (struct file *, struct poll_table_struct *);10     int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);11     int (*mmap) (struct file *, struct vm_area_struct *);12     int (*open) (struct inode *, struct file *);13     int (*flush) (struct file *);14     int (*release) (struct inode *, struct file *);15     int (*fsync) (struct file *, struct dentry *, int datasync);16     int (*aio_fsync) (struct kiocb *, int datasync);17     int (*fasync) (int, struct file *, int);18     int (*lock) (struct file *, int, struct file_lock *);19     ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);20     ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);21     ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);22     ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);23     unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);24     int (*check_flags)(int);25     int (*dir_notify)(struct file *filp, unsigned long arg);26     int (*flock) (struct file *, int, struct file_lock *);27 };
复制代码

 

 scull设备的file_operations结构初始化如下:

复制代码
1 struct file_operations scull_fops = {2     .owner =    THIS_MODULE,3     .read =     scull_read,4     .write =    scull_write,5     .open =     scull_open,     /* 函数名即函数入口地址 */6     .release =  scull_release,7 };/* 注意这里标记化结构体初始化的语法 */
复制代码

 标记化结构初始化语法允许结构成员进行重新排列。

 

三、字符设备的注册

内核内部使用struct cdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或者多个上述结构。代码应包含<linux/cdev.h>,其中定义了这个结构以及与其相关的一些辅助函数。

注册一个独立的cdev设备的过程如下:

1、为struct cdev分配空间(如果已经将struct cdev嵌入到自己设备的特定结构中,并分配的内存空间,则该步骤可省)

1 struct cdev *my_cdev = cdev_alloc();

 2、初始化struct cdev

1 void cdev_init(struct cdev *cdev, struct file_operations *fops)

 3、初始化cdev.owner

1 cdev.owner = THIS_MODULE;

 4、在cdev结构都设置好之后,最后的步骤是告诉内核该结构的信息(在驱动程序还没有完全准备好处理设备上的操作时,就不能调用下面函数)。

1 int cdev_add(struct cdev *p, dev_t dev, unsigned count)

 附:从系统中移除一个字符设备

1 void cdev_del(struct cdev *p)

 

scull完成设备注册的代码如下(之前已经为struct scull_dev 分配了空间):

复制代码
 1 /* 2  * Set up the char_dev structure for this device. 3  */ 4  /* 设备注册函数 */ 5 static void scull_setup_cdev(struct scull_dev *dev, int index) 6 { 7     /* 由主、次设备号得到完整具体的设备号 */ 8     /* 主设备号:scull_major */ 9     /* 次设备号:scull_minor + index */10     int err, devno = MKDEV(scull_major, scull_minor + index);       11     12     /* 初始化cdev结构,且指定其ops函数指针 */13     cdev_init(&dev->cdev, &scull_fops);     14     15     /* 指定cdev结构所有者 */16     dev->cdev.owner = THIS_MODULE;      17     18     /* 这一步可以省略,因为调用cdev_init时已实现 */19     //dev->cdev.ops = &scull_fops;      20     21     /* 向内核注册设备,立即生效 */22     err = cdev_add (&dev->cdev, /* 设备对应的cdev结构 */ 23                 devno,  /* 设备对应的第一个设备号 */24                 1); /* 和该设备关联的连续设备编号的数目,常取1 */25 26     /* Fail gracefully if need be */27     if (err)    /* 向内核注册设备失败 */28         printk(KERN_NOTICE "Error %d adding scull%d", err, index);29 }
复制代码

 

早期的注册方法

新的代码不应该再使用这些老的接口,因为这种机制会在将来的内核中消失,这些函数声明在<lnux/fs.h>中。

注册一个字符设备驱动程序的经典方式:

1 int register_chrdev(unsigned int major, const char *name, struct file_operations *fops)

如果使用register_chrdev注册设备,则将自己的设备从系统中移除的正确方法是:

1 int unregister_chrdev(unsigned int major, const char *name)

 

四、scull模型的内存使用

 

scull使用的内存区域这里也称为设备,其长度是可变的。写的越多,它就变得越长。用更短的文件以覆盖方式写设备时则会变短。

下面是描述scull设备的结构体:

复制代码
1 /*2  * Representation of scull quantum sets.3  */4  /* 量子集链表,每一个链表项内嵌一个量子集 */5 struct scull_qset {6     void **data;    /* 指明量子集(指针数组)起始位置 */7     struct scull_qset *next;    /* 指向下一个量子集链表项 */8 };
复制代码

 

复制代码
 1 /* 定义scull_dev结构体用来描述scull设备 */ 2 struct scull_dev { 3     struct scull_qset *data; /* 指向第一个scull_qset结构体 */ 4     int quantum; /* 量子大小,量子也是指针,指向的内存区域大小即为quantum */ 5     int qset;/* 量子集大小(指针数组元素个数),量子集即指针数组,其元素即量子 */    6     unsigned long size; /* 数据总量 */ 7     unsigned int access_key;  8     struct semaphore sem;  9     struct cdev cdev; /* 字符设备结构 */10 };
复制代码

 

对scull设备量子集、量子的理解

量子集实际上是一个指针数组,其成员即量子,量子是一个指针,指向某一个内存块,该内存块的大小为quantum字节,量子集中有多少个量子,即该指针数组元素的个数,用qset衡量。在scull设备中,可以存在多个这样的量子集(指针数组),每个量子集内嵌在量子集链表struct scull_qset中,并且所有的量子集大小都相等(即每个量子集中量子个数都一样),每个量子的大小也相等(量子指针所指向的内存块大小相等)。

 

scull驱动程序引入了Linux内核用于内存管理的两个核心函数。这两个函数定义在<linux/slab.h>中:

1 void *kmalloc(size_t size, int flags);2 void kfree(const void *ptr);

 

 

scull驱动代码中直接操作量子集、量子的函数:

scull_trim()

负责释放整个数据区(类似清零),并且在文件以只写方式打开时由scull_open调用,以及在模块退出函数scull_cleanup_module()中被调用:

复制代码
 1 /* 2  * Empty out the scull device; must be called with the device 3  * semaphore held. 4  */ 5  /* 设备文件清除函数 */ 6  /* 释放整个数据区:量子集链表项->量子集->量子。简单遍历链表并且释放它发现的任何量子集和量子 */ 7  /* 在scull_open在文件为写而打开时调用 */ 8  /* 调用该函数时必须要有信号量-后面再理解 */ 9 int scull_trim(struct scull_dev *dev)10 {11     struct scull_qset *next, *dptr;12     int qset = dev->qset;/* dev非空,量子集大小,即量子集中量子个数,指针数组中元素个数 */ 13     int i;14 15     /* 遍历设备所有量子集链表项,循环次数为设备的量子集个数次 */16     for (dptr = dev->data;/* 第一个量子集链表项 */17         dptr;/* dptr是否为NULL */18         dptr = next)/* 下一个量子集链表项 */19     { 20         if (dptr->data) {/* 量子集(指针数组)中是否有数据 */21             for (i = 0; i < qset; i++)/* 遍历释放当前量子集中的每个量子,量子集大小为qset */22                 kfree(dptr->data[i]);/* 释放一个量子(其指向的内存块),量子(其指向的内存块)大小为quantum字节 */23             kfree(dptr->data);/* 释放一个量子集(指针数组),存储qset个量子(指针)时占据的内存 */24             dptr->data = NULL;25         }26         next = dptr->next;/* 获取下一个量子集链表项 */27         kfree(dptr);/* 释放当前量子集链表项 */28     }29     /* 清理struct scull_dev *dev中变量的值 */30     dev->size = 0;31     dev->quantum = scull_quantum;32     dev->qset = scull_qset;33     dev->data = NULL;34     return 0;35 }
复制代码

 

 

scull_follow():

以下是scull模块中的一个沿链表前行得到正确scull_set指针的函数,将在read和write方法中被调用:

复制代码
 1 /* 2  * Follow the list 3  */ 4  /* 返回dev设备的第n个量子集链表项指针,量子集不够n个就申请新的 */ 5 struct scull_qset *scull_follow(struct scull_dev *dev, int n) 6 { 7     struct scull_qset *qs = dev->data;/* 当前设备的第一个量子集 */ 8  9         /* Allocate first qset explicitly if need be */10         /* 如果当前设备还没有量子集,则显示地分配第一个量子集 */11     if (! qs) {12         /* kmalloc动态分配连续的物理地址、虚拟地址连续的内存空间,用于小内存分配 */13         qs = dev->data = kmalloc(sizeof(struct scull_qset),/* 要分配的块大小 */14                                 GFP_KERNEL);/* 内存管理器的行为标志 */15         if (qs == NULL)16             return NULL;/* 分配失败 */17         memset(qs, 0, sizeof(struct scull_qset));/* 清空所分配的内存块 */18     }19 20     /* Then follow the list */21     /* 遍历当前设备的量子集链表n步,确保有n个量子集,量子集不够就申请新的 */22     while (n--) {23         if (!qs->next) {/* 量子集不够n个,申请新的 */24             qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);25             if (qs->next == NULL)/* 分配失败 */26                 return NULL;  /* Never mind */27             memset(qs->next, 0, sizeof(struct scull_qset));28         }29         qs = qs->next;30         continue;/* 结束本次循环 */31     }32     return qs;/* 返回dev设备的第n个量子集入口指针 */33 }
复制代码

 

 

五、open和release

open方法提供给驱动程序以初始化的能力,从而为以后的操作完成初始化做准备。

在设备驱动程序中,open应完成如下工作:

1、检查设备特定的错误(诸如设备未就绪或类似的硬件问题)

2、如果设备是首次打开,则对其进行初始化

3、如有必要,更新f_op指针

4、分配并填写置于filp->private_data里的数据结构

在实际应用中,cdev结构一般嵌套在特定的设备结构中。如scull设备中,cdev结构体嵌套在scull_cdev结构中,我们通常不需要cdev结构本身,而是希望得到包含cdev结构的scull_cdev结构,在这种情况下,需要使用内核中的一个宏,它定义在<linux/kernel.h>中:

复制代码
 1 /** 2  * container_of - cast a member of a structure out to the containing structure 3  * 4  * @ptr:    the pointer to the member. 5  * @type:   the type of the container struct this is embedded in. 6  * @member: the name of the member within the struct. 7  * 8  */ 9 #define container_of(ptr, type, member) ({          \10         const typeof( ((type *)0)->member ) *__mptr = (ptr);    \11         (type *)( (char *)__mptr - offsetof(type,member) );})
复制代码

 其作用为:通过指针ptr,获得包含ptr所指向数据(是member结构体)的type结构体的指针。即是用指针得到另外一个指针。

在scull中应用该宏的代码如下:

1 /* 识别需要被打开的设备(得到设备对应的设备结构体) */2 /* 宏container_of利用父结构体struct scull_dev的成员struct cdev cdev得到指向该父结构体的指针 */3 dev = container_of(inode->i_cdev,/* 指向该成员的指针,struct inode中定义,struct cdev *i_cdev */4                      struct scull_dev,/* 父结构体类型 */5                                   cdev);/* 该成员的名称,其包含在父结构体struct scull_dev中,struct cdev cdev */

 

 

release方法和open作用相反,其应完成的工作如下:

1、释放由open分配的、保存在filp->private_data中的所有内容

2、在最后一次关闭操作时关闭设备

但是,并不是每个close系统调用都会引起对release方法的调用,只有那些真正释放设备数据结构的close系统调用才会调用这个方法。内核对每个file 结构维护其被使用多少次的计数器,无论是fork还是dup,都不会创建新的file 数据结构(仅由open 创建),他们只是增加已有结构中的使用计数。只有在file 结构的计数归为0时,close 系统调用才会执行release 方法,这只在删除这个结构时参才会发生。release方法与close 系统调用间的关系保证了对于每次open驱动程序只会看到对应的一次release 调用。

因为scull被定义为一个全局持久的内存区,所以它的release什么都不要去做。

注意:flush方法在应用程序每次调用close 时都会被调用,但是很少有驱动程序去实现flush,因为在close时并没有什么事情需要去做,除非release被调用。

 

六、read和write

read和write的作用主要是实现用户空间和内核空间之间整段数据的拷贝。这种能力由下面的内核函数提供,它们在<asm/uaccess.h>中定义,它们用于拷贝任意一段字节序列:

 

1 unsigned long copy_to_user(void __user *to, const void *from, 2                 unsigned long n)3 unsigned long copy_from_user(void *to, const void __user *from, 4                  unsigned long n)

 

这两个函数在调用时会检查用户空间的指针是否有效。如果不需要检查用户空间的指针,则可以调用下面两个函数:

1 unsigned long __copy_from_user(void *to, const void __user *from, unsigned long n)2 unsigned long __copy_to_user(void __user *to, const void *from, unsigned long n)

 

由内核源码可知,copy_to_user和copy_from_user分别是对__copy_from_user和__copy_to_user的进一步封装调用。

 

七、开发板上实验

实验平台:mini2440(256M NAND)       

内核版本:友善的内核(Linux 2.6.32.2)及文件系统

模块程序:http://files.cnblogs.com/ycz9999/scull.zip

模块测试程序:http://files.cnblogs.com/ycz9999/scull_test.zip

1、量子集、量子大小使用默认值

scull_quantum = 4000

scull_qset = 1000

插入驱动模块,建立设备节点

复制代码
[root@FriendlyARM 3]# lsscull.ko    scull_test[root@FriendlyARM 3]# insmod scull.ko[root@FriendlyARM 3]# lsmodscull 3157 0 - Live 0xbf000000[root@FriendlyARM 3]# cat /proc/devicesCharacter devices:  1 mem  4 /dev/vc/0  4 tty  5 /dev/tty  5 /dev/console  5 /dev/ptmx  7 vcs 10 misc 13 input 14 sound 21 sg 29 fb 81 video4linux 89 i2c 90 mtd116 alsa128 ptm136 pts180 usb188 ttyUSB189 usb_device204 s3c2410_serial253 scull254 rtcBlock devices:259 blkext  7 loop  8 sd 31 mtdblock 65 sd 66 sd 67 sd 68 sd 69 sd 70 sd 71 sd128 sd129 sd130 sd131 sd132 sd133 sd134 sd135 sd179 mmc[root@FriendlyARM 3]# ls /sys/module/aircable          hid_apple         omninet           tcp_cubicark3116           io_edgeport       opticon           tda8290belkin_sa         io_ti             option            tda9887ch341             ipaq              oti6858           tea5761cp210x            ipw               pl2303            tea5767cyberjack         ir_usb            printk            ti_usb_3410_5052cypress_m8        iuu_phoenix       qcserial          tuner_simpledigi_acceleport   kernel            safe_serial       tuner_xc2028dm9000            keyboard          scsi_mod          usb_storageempeg             keyspan           scull             usbcoreftdi_sio          keyspan_pda       sg                usbhidfunsoft           kl5kusb105        sierra            usbserialgarmin_gps        kobil_sct         snd               uvcvideogspca_gl860       lockd             snd_pcm           v4l1_compatgspca_m5602       mct_u232          snd_pcm_oss       visorgspca_main        mos7720           snd_timer         vtgspca_mr97310a    mos7840           soundcore         whiteheatgspca_ov519       mousedev          spcp8x5           xc5000gspca_stv06xx     mt20xx            spurious          yaffsgspca_zc3xx       navman            sunrpchid               nfs               symbolserial[root@FriendlyARM 3]# mknod -m 666 /dev/scull0 c 253 0[root@FriendlyARM 3]# mknod -m 666 /dev/scull1 c 253 1[root@FriendlyARM 3]# mknod -m 666 /dev/scull2 c 253 2[root@FriendlyARM 3]# mknod -m 666 /dev/scull3 c 253 3[root@FriendlyARM 3]# ls /dev/scull*/dev/scull0  /dev/scull1  /dev/scull2  /dev/scull3[root@FriendlyARM 3]#
复制代码

在创建设备节点时,驱动程序是动态分配的设备号,所以需要从/proc/devices中获得设备号。在申请设备号时,已经指定了起始的次设备号和注册的设备号的个数,因此在指定次设备号时,不要超出了次设备号的范围。以scull为例,驱动程序动态申请了scull_nr_devs 个(4个)设备号,且起始次设备号为0,如果要创建4个设备号连续的设备节点,则最大次设备号不能超过3。否则,执行应用程序时,代码运行失败!

 

2>启动测试程序

复制代码
[root@FriendlyARM 3]# ./scull_testwrite ok! code=20read ok! code=20[0]=0 [1]=1 [2]=2 [3]=3 [4]=4[5]=5 [6]=6 [7]=7 [8]=8 [9]=9[10]=10 [11]=11 [12]=12 [13]=13 [14]=14[15]=15 [16]=16 [17]=17 [18]=18 [19]=19[root@FriendlyARM 3]#
复制代码

 

2、设置量子大小为6

scull_quantum=6

scull_qset = 1000

1>插入驱动模块

[root@FriendlyARM 3]# insmod scull.ko scull_quantum=6[root@FriendlyARM 3]# lsmodscull 3157 0 - Live 0xbf006000[root@FriendlyARM 3]#

 

2>启动测试程序

复制代码
[root@FriendlyARM 3]# ./scull_testwrite error! code=6write error! code=6write error! code=6write ok! code=2read error! code=6read error! code=6read error! code=6read ok! code=2[0]=0 [1]=1 [2]=2 [3]=3 [4]=4[5]=5 [6]=6 [7]=7 [8]=8 [9]=9[10]=10 [11]=11 [12]=12 [13]=13 [14]=14[15]=15 [16]=16 [17]=17 [18]=18 [19]=19[root@FriendlyARM 3]#
复制代码

 

3、设置量子大小为6,量子集大小为2

scull_quantum=6

scull_qset = 2

1>插入驱动模块

[root@FriendlyARM 3]# insmod scull.ko scull_quantum=6 scull_qset=2[root@FriendlyARM 3]# lsmodscull 3157 0 - Live 0xbf00c000[root@FriendlyARM 3]#

 

2>启动测试程序

复制代码
[root@FriendlyARM 3]# ./scull_testwrite error! code=6write error! code=6write error! code=6write ok! code=2read error! code=6read error! code=6read error! code=6read ok! code=2[0]=0 [1]=1 [2]=2 [3]=3 [4]=4[5]=5 [6]=6 [7]=7 [8]=8 [9]=9[10]=10 [11]=11 [12]=12 [13]=13 [14]=14[15]=15 [16]=16 [17]=17 [18]=18 [19]=19[root@FriendlyARM 3]#
复制代码

本实验测试了模块的读写能力,还测试了量子读写是否有效。但是,由于自己在内核知识上的欠缺,关于应用程序和对应驱动程序间是如何进行参数传递的,还是有很多疑问。

 

参考:

《Linux设备驱动程序(第三版)》

Tekkaman Ninja: http://blog.chinaunix.net/uid/20543672.html

分类: Linux设备驱动