一、 主设备号和次设备号
主设备号表示设备对应的驱动程序;
次设备号由内核使用,用于正确确定设备文件所指的设备。
内核用dev_t类型()来保存设备编号,dev_t是一个32位的数,12位表示主设备号,20为表示次设备号。在实际使用中,是通过中定义的宏来转换格式,如下:
(dev_t)à主设备号、次设备号 |
MAJOR(dev_t dev) MINOR(dev_t dev) |
主设备号、次设备号à(dev_t) |
MKDEV(int major,int minor) |
建立一个字符设备之前,驱动程序首先要做的事情就是获得设备编号。其这主要函数在中声明:
int register_chrdev_region(dev_t first, unsigned int count,char *name); //指定设备编号
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name); //动态生成设备编号
void unregister_chrdev_region(dev_t first, unsigned int count); //释放设备编号
分配之设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。
以下是在scull.c中用来获取主设备好的代码:
if
(
scull_major
)
{
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
;
}
在这部分中,比较重要的是在用函数获取设备编号后,其中的参数name是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中。
到此可以写一个分配设备号的简单驱动了,代码如下:
/* *File Name :scull.c *Function :test character device driver *Author :gufeiyang *From :<> *Time :2010-2-1 home yunnan */ #include <linux/init.h> #include <linux/module.h> #include <linux/version.h> #include <linux/types.h> #include <linux/kdev_t.h> #include <linux/fs.h> #define SCULL_MAJOR 0 #define SCULL_MINOR 0 #define SCULL_NR_DEVS 4 #define DEVICE_NAME “scnull” static dev_t dev; static int scull_major, result; static char __initdata info[] = “This is my first character device driver!Module initial!\n”; static int __init scull_init(void) { printk(info); if (SCULL_MAJOR) { dev = MKDEV(SCULL_MAJOR, SCULL_NR_DEVS); result = register_chrdev_region(dev, SCULL_NR_DEVS, DEVICE_NAME); } else { result = alloc_chrdev_region(&dev, SCULL_NR_DEVS,SCULL_NR_DEVS,DEVICE_NAME); scull_major = MAJOR(dev); } if (result < 0) { printk(KERN_WARNING “scull: can’t get major %d\n”, scull_major); return result; }else{ printk(KERN_ALERT”scull_major = %d\n”,scull_major); return 0; } } static void __exit scull_exit(void) { printk(KERN_ALERT”Module exit\n”); unregister_chrdev_region(dev, SCULL_NR_DEVS); } module_init(scull_init); module_exit(scull_exit); MODULE_LICENSE(“Dual BSD/GPL”); MODULE_AUTHOR(“gufeiyang”);
|
执行后得到以下结果:
[root@gfy-S3C2440 /tmp]# insmod scull.ko
This is my first character device driver!Module initial!
Scull_major = 252
可以见主设备号为252;
注意:申请了设备号一定要释放!
二、一些重要的数据结构
大部分基本的驱动程序操作涉及及到三个重要的内核数据结构,分别是file_operations、file和inode,它们的定义都在。
三、字符设备的注册【LDD3上新方法如下所示】
1、为struct cdev 分配空间(如果已经将struct cdev 嵌入到自己的设备的特定结构体中,并分配了空间,这步略过!)
struct cdev *my_cdev = cdev_alloc();
2、初始化struct cdev
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
3、初始化cdev.owner
cdev.owner = THIS_MODULE;
4、cdev设置完成,通知内核struct cdev的信息(在执行这步之前必须确定你对struct cdev的以上设置已经完成!)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
从系统中移除一个字符设备:void cdev_del(struct cdev *p)
以下是scull中的初始化代码(之前已经为struct scull_dev 分配了空间):
/*
* 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);
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops; //这句可以省略,在cdev_init中已经做过
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 register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
major 是设备的主设备号, name 是驱动的名子(出现在 /proc/devices), fops 是缺省的 file_operations 结构. 一个对 register_chrdev 的调用为给定的主编号注册 0 – 255 的次编号, 并且为每一个建立一个缺省的 cdev 结构. 使用这个接口的驱动必须准备好处理对所有 256 个次编号的 open 调用( 不管它们是否对应真实设备 ), 它们不能使用大于 255 的主或次编号。
如果你使用 register_chrdev, 从系统中去除你的设备的正确的函数是:
int unregister_chrdev(unsigned int major, const char *name);
major 和 name 必须和传递给 register_chrdev 的相同, 否则调用会失败.
u 到目前为止,该驱动程序的功能只是向内核申请了主设备号和在内核中注册了设备,还没有在文件系统中生成设备节点,未生成节点的驱动程序对于应用程序来说没有任何用处,下面我们就参照天嵌手册上的驱动程序来为LDD3的scull设备在文件系统中注册节点。LDD3上的节点生成是用脚本来写的,实际中应该在驱动中生成。
此时也就对设备驱动初始化有了明确的步骤:
1) 最好动态申请主设备号;
2) 在内核中注册设备;
3) 在文件系统中生成设备节点;
下面来说明如何生策划那个设备节点:
static struct class *scull_class;//先定义一个设备类,该结构在#include 中定义
/*注册一个类,使mdev可以在/dev下创建设备节点*/
scull_class = class_create(THIS_MODULE, DEVICE_NAME);
if(IS_ERR(scull_class))
{
printk(KERN_ALERT”Err:faile in scull_class!\n”);
return -1;
}
/*创建设备节点,名字为DEVICE_NAME ,主设备号用上面动态生成的dev*/
class_device_create(scull_class, NULL, dev, NULL, DEVICE_NAME);
到这一步就可以写出带有设备节点的驱动程序了,如下:
/* *File Name :scull.c *Function :test character device driver *Author :gufeiyang *From :<> *Time :2010-2-1 home yunnan */ #include <linux/init.h> #include <linux/module.h> #include <linux/version.h> #include <linux/types.h> #include <linux/kdev_t.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/cdev.h> #define SCULL_MAJOR 0 #define SCULL_MINOR 0 #define SCULL_NR_DEVS 2 #define DEVICE_NAME “scull” /* * 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 semaphore sem; /* mutual exclusion semaphore */
struct cdev cdev; /* Char device structure */ }; static struct file_operations scull_fops = { .owner = THIS_MODULE }; static dev_t dev; static int scull_major, result; static char __initdata info[] = “This is my first character device driver!\n”; static struct class *scull_class;
static int scull_setup_cdev(struct scull_dev *scull_dev, dev_t devt) { int err; cdev_init(&scull_dev->cdev, &scull_fops); scull_dev->cdev.owner = THIS_MODULE; scull_dev->cdev.ops = &scull_fops; err = cdev_add (&scull_dev->cdev, devt, 2); /* Fail gracefully if need be */ if (err) { printk(KERN_NOTICE “Error %d adding scull!\n”, err); return err; } return 0; } static int __init scull_init(void) { int ret; const struct cdev *sdev = cdev_alloc(); printk(info); /*动态申请主设备号*/ if (SCULL_MAJOR) { dev = MKDEV(SCULL_MAJOR, SCULL_NR_DEVS); result = register_chrdev_region(dev, SCULL_NR_DEVS, DEVICE_NAME); } else { result = alloc_chrdev_region(&dev, SCULL_NR_DEVS,SCULL_NR_DEVS,DEVICE_NAME); scull_major = MAJOR(dev); } if (result < 0) { printk(KERN_WARNING “scull: can’t get major %d\n”, scull_major); return result; } else { printk(DEVICE_NAME” major = %d\n”, scull_major); } /*注册设备*/ // ret = register_chrdev(scull_major, DEVICE_NAME, &scull_fops);//老方法
ret = scull_setup_cdev(sdev, dev);//新方法
if (ret < 0) { printk(DEVICE_NAME “ can’t register major number\n”); return ret; } /*注册一个类,使mdev可以在/dev下创建设备节点*/ scull_class = class_create(THIS_MODULE, DEVICE_NAME); if(IS_ERR(scull_class)) { printk(KERN_ALERT”Err:faile in scull_class!\n”); return -1; } /*创建设备节点,名字为DEVICE_NAME ,主设备号用上面动态生成的dev*/ class_device_create(scull_class, NULL, dev, NULL, DEVICE_NAME); printk(DEVICE_NAME “ initialized\n”); return 0; } static void __exit scull_exit(void) { printk(KERN_ALERT”Module exit!CHRACTER DEVICE DRVER END!\n”); unregister_chrdev_region(dev, SCULL_NR_DEVS); class_device_destroy(scull_class, dev); class_destroy(scull_class); } module_init(scull_init); module_exit(scull_exit); MODULE_LICENSE(“Dual BSD/GPL”); MODULE_DESCRIPTION(“CHARACTER DEVICE DRVER”); MODULE_AUTHOR(“gufeiyang@2010-02-02”);
|
其中注册设备节点用的是《linux设备驱动程序(第三版)》中提到的新方法
Insmod以后看到
[root@gfy-S3C2440 /tmp]# insmod scull.ko
This is my first character device driver!
Scull major = 252
scull initialized
[root@gfy-S3C2440 /tmp]# ls –al /dev/scull
crw-rw---- 1 root root 252, 2 Jan 1 00:00 /dev/scull
可见在/dev下面生成了scull的设备节点。
四、scull模型的内存使用
以下是scull模型的结构体:
/*
* 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 semaphore sem; /* mutual exclusion semaphore */
struct cdev cdev; /* Char device structure */
};
scull驱动程序引入了两个Linux内核中用于内存管理的核心函数,它们的定义都在:
void *kmalloc(size_t size, int flags);
void kfree(void *ptr);
以下是scull模块中的一个释放整个数据区的函数(类似清零),将在scull以写方式打开和scull_cleanup_module中被调用:
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next, *dptr;
int qset = dev->qset; /* 量子集中量子的个数*/
int i;
for (dptr = dev->data; dptr; dptr = next) { /* 循环scull_set个数次,直到dptr为NULL为止。*/
if (dptr->data) {
for (i = 0; i < qset; i++)/* 循环一个量子集中量子的个数次*/
kfree(dptr->data[i]);/* 释放其中一个量子的空间*/
kfree(dptr->data);/* 释放当前的scull_set的量子集的空间*/
dptr->data = NULL;/* 释放一个scull_set中的void **data指针*/
}
next = dptr->next; /* 准备下个scull_set的指针*/
kfree(dptr);/* 释放当前的scull_set*/
}
dev->size = 0; /* 当前的scull_device所存的数据为0字节*/
dev->quantum = scull_quantum;/* 初始化一个量子的大小*/
dev->qset = scull_qset;/* 初始化一个量子集中量子的个数*/
dev->data = NULL;/* 释放当前的scull_device的struct scull_qset *data指针*/
return 0;
}
以下是scull模块中的一个沿链表前行得到正确scull_set指针的函数,将在read和write方法中被调用:
/*Follow the list*/
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;
}
其实这个函数的实质是:如果已经存在这个scull_set,就返回这个scull_set的指针。如果不存在这个scull_set,一边沿链表为scull_set分配空间一边沿链表前行,直到所需要的scull_set被分配到空间并初始化为止,就返回这个scull_set的指针。
五、 open和release
open方法提供给驱动程序以初始化的能力,为以后的操作作准备。应完成的工作如下:
(1)检查设备特定的错误(如设备未就绪或硬件问题);
(2)如果设备是首次打开,则对其进行初始化;
(3)如有必要,更新f_op指针;
(4)分配并填写置于filp->private_data里的数据结构。
而根据scull的实际情况,他的open函数只要完成第四步(将初始化过的struct scull_dev dev的指针传到filp->private_data里,以备后用)就好了,所以open函数很简单。但是其中用到了定义在
中的container_of宏,源码如下:
#define container_of(ptr, type, member) ({ \
const typeof( ((type *)0)->member ) *__mptr = (ptr); \
(type *)( (char *)__mptr – offsetof(type,member) );})
其实从源码可以看出,其作用就是:通过指针ptr,获得包含ptr所指向数据(是member结构体)的type
结构体的指针。即是用指针得到另外一个指针。Release方法提供释放内存,关闭设备的功能。应完成的工作如下:
(1)释放由open分配的、保存在file->private_data中的所有内容;
(2)在最后一次关闭操作时关闭设备。由于前面定义了scull是一个全局且持久的内存区,所以他的release什么都不做。
六、 read和write
read和write方法的主要作用就是实现内核与用户空间之间的数据拷贝。因为Linux的内核空间和用户空间隔离的,所以要实现数据拷贝就必须使用在中定义的:
unsigned long copy_to_user(void __user *to,
const void *from,
unsigned long count);
unsigned long copy_from_user(void *to,
const void __user *from,
unsigned long count);
而值得一提的是以上两个函数和
#define __copy_from_user(to,from,n) (memcpy(to, (void __force *)from, n), 0)
#define __copy_to_user(to,from,n) (memcpy((void __force *)to, from, n), 0)
之间的关系:通过源码可知,前者调用后者,但前者在调用前对用户空间指针进行了检查。至于read和write 的具体函数比较简单,就在实验中验证好了。
到目前为止就可以写一个完整的scull 驱动程序了,本人按照LDD3修改的scull驱动如下:
/* *File Name :scull.c *Function :test character device driver *Author :gufeiyang *From :<> *Time :2010-2-1 home yunnan */ #include <linux/init.h> #include <linux/module.h> #include <linux/version.h> #include <linux/types.h> #include <linux/kdev_t.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/cdev.h>
#include <linux/uaccess.h> #define SCULL_MAJOR 0 #define SCULL_MINOR 0 #define SCULL_NR_DEVS 2 #define DEVICE_NAME “scull”
static int scull_quantum = 3; static int scull_qset = 5; module_param(scull_qset, int, S_IRUGO); module_param(scull_quantum, int, S_IRUGO); /* * 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 semaphore sem; /* mutual exclusion semaphore */ struct cdev cdev; /* Char device structure */ }; #if 0 static int scull_trim(struct scull_dev *dev) { struct scull_qset *next, *dptr; int qset = dev->qset; /* 量子集中量子的个数*/ int i; for (dptr = dev->data; dptr; dptr = next) { /* 循环scull_set个数次,直到dptr为NULL为止。*/ if (dptr->data) { for (i = 0; i < qset; i++)/* 循环一个量子集中量子的个数次*/ kfree(dptr->data[i]);/* 释放其中一个量子的空间*/
kfree(dptr->data);/* 释放当前的scull_set的量子集的空间*/ dptr->data = NULL;/* 释放一个scull_set中的void **data指针*/ } next = dptr->next; /* 准备下个scull_set的指针*/ kfree(dptr);/* 释放当前的scull_set*/ } dev->size = 0; /* 当前的scull_device所存的数据为0字节*/ dev->quantum = scull_quantum;/* 初始化一个量子的大小*/ dev->qset = scull_qset;/* 初始化一个量子集中量子的个数*/ dev->data = NULL;/* 释放当前的scull_device的struct scull_qset *data指针*/ return 0; } #endif /*Follow the list*/ static 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; } static 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; s_pos = rest / quantum; 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; 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; } static 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; } static struct file_operations scull_fops = { .owner = THIS_MODULE, .read = scull_read, .write = scull_write }; static dev_t dev; static int scull_major, result; static char __initdata info[] = “This is my first character device driver!\n”; static struct class *scull_class;
static int scull_setup_cdev(struct scull_dev *scull_dev, dev_t devt) { int err; cdev_init(&scull_dev->cdev, &scull_fops); scull_dev->cdev.owner = THIS_MODULE; scull_dev->cdev.ops = &scull_fops; err = cdev_add (&scull_dev->cdev, devt, 2); /* Fail gracefully if need be */ if (err) { printk(KERN_NOTICE “Error %d adding scull!\n”, err); return err; } return 0; } static int __init scull_init(void) { int ret; const struct cdev *sdev = cdev_alloc(); printk(info); /*动态申请主设备号*/ if (SCULL_MAJOR) { dev = MKDEV(SCULL_MAJOR, SCULL_NR_DEVS); result = register_chrdev_region(dev, SCULL_NR_DEVS, DEVICE_NAME); } else { result = alloc_chrdev_region(&dev, SCULL_NR_DEVS,SCULL_NR_DEVS,DEVICE_NAME); scull_major = MAJOR(dev); } if (result < 0) { printk(KERN_WARNING “scull: can’t get major %d\n”, scull_major); return result; } else { printk(DEVICE_NAME” major = %d\n”, scull_major); } /*注册设备*/ // ret = register_chrdev(scull_major, DEVICE_NAME, &scull_fops);//老方法
ret = scull_setup_cdev(sdev, dev);//新方法
if (ret < 0) { printk(DEVICE_NAME “ can’t register major number\n”); return ret; } /*注册一个类,使mdev可以在/dev下创建设备节点*/ scull_class = class_create(THIS_MODULE, DEVICE_NAME); if(IS_ERR(scull_class)) { printk(KERN_ALERT”Err:faile in scull_class!\n”); return -1; } /*创建设备节点,名字为DEVICE_NAME ,主设备号用上面动态生成的dev*/ class_device_create(scull_class, NULL, dev, NULL, DEVICE_NAME); printk(DEVICE_NAME “ initialized\n”); return 0; } static void __exit scull_exit(void) { printk(KERN_ALERT”Module exit!CHRACTER DEVICE DRVER END!\n”); unregister_chrdev_region(dev, SCULL_NR_DEVS); class_device_destroy(scull_class, dev); class_destroy(scull_class); } module_init(scull_init); module_exit(scull_exit); MODULE_LICENSE(“Dual BSD/GPL”); MODULE_DESCRIPTION(“CHARACTER DEVICE DRVER”); MODULE_AUTHOR(“gufeiyang@2010-02-02”);
|
程序测试:
[root@gfy-S3C2440 /tmp]# insmod scull.ko
This is my first character device driver!
Scull major = 252
scull initialized
[root@gfy-S3C2440 /tmp]# ls –l /dev/scull
crw-rw---- 1 root root 252, 2 Jan 1 00:00 /dev/scull
[root@gfy-S3C2440 /tmp]# cat test
abcd
[root@gfy-S3C2440 /tmp]# cp test /dev/scull
[root@gfy-S3C2440 /tmp]# cat /dev/scull
abcd
[root@gfy-S3C2440 /tmp]# du –h /dev/scull
4.0K /dev/scull 不明白这个4k是如何来的,可能是自己理解scull的内存分配还不够
[root@gfy-S3C2440 /tmp]# echo gufeiyang >> /dev/scull
[root@gfy-S3C2440 /tmp]# cat /dev/scull
abcd
gufeiyang
[root@gfy-S3C2440 /tmp]#
好,现在只差写一个应用程序来测试驱动了,为了更好地理解scull设备,写应用程序之前先把scull设备理解透彻。
说明:上述程序是本人未参考正确的scull的驱动程序写的,存在一定bug,如果拿去测试就会发现虽然能cp和echo数据进去,能cat正确数据出来,但是写应用程序读写的时候就出问题了,还有卸载模块的有时候也会出问题。完整版本请看我的另一篇《寻找差距》,是结合我的前辈的源码修改而成了,没有问题,好用,不需要如《LDD3》所述用脚本建立设备节点,此法参照天嵌手册而来,不足之处是只生成了一个设备节点。
【快速参考】--摘自《linux设备驱动程序(第三版)》
#include
dev_t
dev_t 是用来在内核里代表设备号的类型.
Int MAJOR(dev_t dev);
int MINOR(dev_t dev);
从设备编号中抽取主次编号的宏.
Dev_t MKDEV(unsigned int major, unsigned int minor);
从主次编号来建立 dev_t 数据项的宏定义.
#include
“文件系统”头文件是编写设备驱动需要的头文件. 许多重要的函数和数据结构在此定义.
Int register_chrdev_region(dev_t first, unsigned int count, char *name)
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name)
void unregister_chrdev_region(dev_t first, unsigned int count);
允许驱动分配和释放设备编号的范围的函数. Register_chrdev_region 应当用在事先知道需要的主编号时; 对于动态分配, 使用 alloc_chrdev_region 代替.
Int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
老的( 2.6 之前) 字符设备注册函数. 它在 2.6 内核中被模拟, 但是不应当给新代码使用. 如果主编号不是 0, 可以不变地用它; 否则一个动态编号被分配给这个设备.
Int unregister_chrdev(unsigned int major, const char *name);
恢复一个由 register_chrdev 所作的注册的函数. Major 和 name 字符串必须包含之前用来注册设备时同样的值.
Struct file_operations;
struct file;
struct inode;
大部分设备驱动使用的 3 个重要数据结构. File_operations 结构持有一个字符驱动的方法; struct file 代表一个打开的文件, struct inode 代表磁盘上的一个文件.
#include
struct cdev *cdev_alloc(void);
void cdev_init(struct cdev *dev, struct file_operations *fops);
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
void cdev_del(struct cdev *dev);
cdev 结构管理的函数, 它代表内核中的字符设备.
#include
container_of(pointer, type, field);
一个传统宏定义, 可用来获取一个结构指针, 从它里面包含的某个其他结构的指针.
这个包含文件声明内核代码使用的函数来移动数据到和从用户空间.
Unsigned long copy_from_user (void *to, const void *from, unsigned long count);
unsigned long copy_to_user (void *to, const void *from, unsigned long count);
在用户空间和内核空间拷贝数据.
程序存在的问题:不知道为什么,有时候在rmmod的时候会出现错误,看来开学了把Tekkaman Ninja大哥的源码下载下来对比一下才知道错误在哪里,也好比较差距!