天天看点

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

开始学习《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    第一个次设备号,通常是0
3 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中用来获取设备号的代码:

Linux设备驱动程序学习(3)-字符设备驱动程序
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     }      
Linux设备驱动程序学习(3)-字符设备驱动程序

这部分中,函数中参数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比较重要的结构成员如下:

Linux设备驱动程序学习(3)-字符设备驱动程序
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 };      
Linux设备驱动程序学习(3)-字符设备驱动程序

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值。

Linux设备驱动程序学习(3)-字符设备驱动程序
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 };      
Linux设备驱动程序学习(3)-字符设备驱动程序

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

Linux设备驱动程序学习(3)-字符设备驱动程序
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 };/* 注意这里标记化结构体初始化的语法 */      
Linux设备驱动程序学习(3)-字符设备驱动程序

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

三、字符设备的注册

内核内部使用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 分配了空间):

Linux设备驱动程序学习(3)-字符设备驱动程序
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 }      
Linux设备驱动程序学习(3)-字符设备驱动程序

早期的注册方法

新的代码不应该再使用这些老的接口,因为这种机制会在将来的内核中消失,这些函数声明在<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模型的内存使用

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

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

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

Linux设备驱动程序学习(3)-字符设备驱动程序
1 /*
2  * Representation of scull quantum sets.
3  */
4  /* 量子集链表,每一个链表项内嵌一个量子集 */
5 struct scull_qset {
6     void **data;    /* 指明量子集(指针数组)起始位置 */
7     struct scull_qset *next;    /* 指向下一个量子集链表项 */
8 };      
Linux设备驱动程序学习(3)-字符设备驱动程序
Linux设备驱动程序学习(3)-字符设备驱动程序
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 };      
Linux设备驱动程序学习(3)-字符设备驱动程序

对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()中被调用:

Linux设备驱动程序学习(3)-字符设备驱动程序
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 }      
Linux设备驱动程序学习(3)-字符设备驱动程序

scull_follow():

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

Linux设备驱动程序学习(3)-字符设备驱动程序
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 }      
Linux设备驱动程序学习(3)-字符设备驱动程序

五、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>中:

Linux设备驱动程序学习(3)-字符设备驱动程序
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) );})      
Linux设备驱动程序学习(3)-字符设备驱动程序

 其作用为:通过指针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

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

Linux设备驱动程序学习(3)-字符设备驱动程序
[[email protected] 3]# ls
scull.ko    scull_test
[[email protected] 3]# insmod scull.ko
[[email protected] 3]# lsmod
scull 3157 0 - Live 0xbf000000
[[email protected] 3]# cat /proc/devices
Character 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 mtd
116 alsa
128 ptm
136 pts
180 usb
188 ttyUSB
189 usb_device
204 s3c2410_serial
253 scull
254 rtc

Block devices:
259 blkext
  7 loop
  8 sd
 31 mtdblock
 65 sd
 66 sd
 67 sd
 68 sd
 69 sd
 70 sd
 71 sd
128 sd
129 sd
130 sd
131 sd
132 sd
133 sd
134 sd
135 sd
179 mmc
[[email protected] 3]# ls /sys/module/
aircable          hid_apple         omninet           tcp_cubic
ark3116           io_edgeport       opticon           tda8290
belkin_sa         io_ti             option            tda9887
ch341             ipaq              oti6858           tea5761
cp210x            ipw               pl2303            tea5767
cyberjack         ir_usb            printk            ti_usb_3410_5052
cypress_m8        iuu_phoenix       qcserial          tuner_simple
digi_acceleport   kernel            safe_serial       tuner_xc2028
dm9000            keyboard          scsi_mod          usb_storage
empeg             keyspan           scull             usbcore
ftdi_sio          keyspan_pda       sg                usbhid
funsoft           kl5kusb105        sierra            usbserial
garmin_gps        kobil_sct         snd               uvcvideo
gspca_gl860       lockd             snd_pcm           v4l1_compat
gspca_m5602       mct_u232          snd_pcm_oss       visor
gspca_main        mos7720           snd_timer         vt
gspca_mr97310a    mos7840           soundcore         whiteheat
gspca_ov519       mousedev          spcp8x5           xc5000
gspca_stv06xx     mt20xx            spurious          yaffs
gspca_zc3xx       navman            sunrpc
hid               nfs               symbolserial
[[email protected] 3]# mknod -m 666 /dev/scull0 c 253 0
[[email protected] 3]# mknod -m 666 /dev/scull1 c 253 1
[[email protected] 3]# mknod -m 666 /dev/scull2 c 253 2
[[email protected] 3]# mknod -m 666 /dev/scull3 c 253 3
[[email protected] 3]# ls /dev/scull*
/dev/scull0  /dev/scull1  /dev/scull2  /dev/scull3
[[email protected] 3]#      
Linux设备驱动程序学习(3)-字符设备驱动程序

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

2>启动测试程序

Linux设备驱动程序学习(3)-字符设备驱动程序
[[email protected] 3]# ./scull_test
write ok! code=20
read 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

[[email protected] 3]#      
Linux设备驱动程序学习(3)-字符设备驱动程序

2、设置量子大小为6

scull_quantum=6

scull_qset = 1000

1>插入驱动模块

[[email protected] 3]# insmod scull.ko scull_quantum=6
[[email protected] 3]# lsmod
scull 3157 0 - Live 0xbf006000
[[email protected] 3]#      

2>启动测试程序

Linux设备驱动程序学习(3)-字符设备驱动程序
[[email protected] 3]# ./scull_test
write error! code=6
write error! code=6
write error! code=6
write ok! code=2
read error! code=6
read error! code=6
read error! code=6
read 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

[[email protected] 3]#      
Linux设备驱动程序学习(3)-字符设备驱动程序

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

scull_quantum=6

scull_qset = 2

1>插入驱动模块

[[email protected] 3]# insmod scull.ko scull_quantum=6 scull_qset=2
[[email protected] 3]# lsmod
scull 3157 0 - Live 0xbf00c000
[[email protected] 3]#      

2>启动测试程序

Linux设备驱动程序学习(3)-字符设备驱动程序
[[email protected] 3]# ./scull_test
write error! code=6
write error! code=6
write error! code=6
write ok! code=2
read error! code=6
read error! code=6
read error! code=6
read 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

[[email protected] 3]#      
Linux设备驱动程序学习(3)-字符设备驱动程序

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

参考:

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

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

分类: Linux设备驱动

继续阅读