scull:Simple Character Utility for Loading Localities,区域装载的简单字符工具。
scull是一个操作内存区域的字符设备驱动程序,这片内存区就相当于一个设备。
主设备号和次设备号
主设备号标识设备对应的驱动程序,次设备号由内核使用,用于正确确定设备文件所指的设备。可以通过次设备号获得一个指向内核设备的直接指针,也可以将次设备号当做设备本地数组的索引。
设备编号的内部使用 dev_t类型()用来保存设备编号,包括主设备号和次设备号。在内核2.6.0版本中,dev_t是一个32为32位,其中12位表示主设备号,其余20位表示次设备号。
获得主设备号和次设备号
MAJOR(dev_t dev); MINOR(dev_t dev);
|
将主设备号和次设备号转换成dev_t类型
MKDEV(int major, int minor);
|
分配和释放设备编号建立一个字符设备之前,首先要获得一个或多个设备编号。
#include int register_chrdev_region(dev_t first, unsigned int count, char *name);
|
first是要分配的设备编号范围的起始值,常设置为0.
count是所请求的连续设备编号的个数.
name是和该编号范围关联的设备名称,将出现在/proc/devices和sysfs中.
动态分配设备编号
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
|
dev仅用于输出,成功完成调用后将保存已分配范围的第一个编号.
firstminor是要使用的被请求的第一个次设备号,通常是0.
设备编号释放(通常在清除函数中调用)
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; }
|
重要的数据结构
文件操作file_operations()
file_operations结构用来将驱动程序连接到上面获得的设备编号。每个打开的文件(在内部由一个file结构表示)和一组函数关联(通过包含指向一个file_operations结构的f_op字段)。
scull设备驱动程序所实现的方法
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, };
|
struct file结构()
struct file结构和用户空间的FILE没有任何关联,FILE在C库中定义不会出现在内核,struct file是一个内核结构,不会出现在用户空间。 file代表一个打开的文件,其重要的成员如下:
mode_t f_mode;
文件模式。通过FMODE_READ和FMODE_WRITE位来标识文件是否可读或可写。
loff_t f_pos;
当前读写位置。loff_t是64位,gcc术语来说是long long.
unsigned int f_flags;
文件标志,如O_RDONLY、O_NONBLOCK、O_SYNC。
struct file_operations *f_op;
与文件相关的操作。内核在open的时候对这个指针赋值,以后需要处理这些操作时就读取这个指针。
void *private_date;
open系统调用在调用驱动程序的open前将这个指针置为NULL。驱动程序可以将这个字段用于任何目的或者忽略这个字段。也可以用这个字段指向已分配的数据,但一定要在内核销毁file结构的前在release方法中释放内存。
struct dentry *f_dentry;
文件对应的目录项结构。
inode结构
内核用inode结构在内部表示文件,和file结构不同,后者表示打开的文件描述符。对单个文件,可能有多个打开的文件描述符的file结构,但他们都指向单个inode结构。
inode结构中与驱动程序代码有关的成员:
dev_t i_rdev;
对表示设备文件的inode结构,该字段包含了真正的设备标号。
struct cdev *i_cdev;
struct cdev表示字符设备的内核内部结构。当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针。
从inode结构中获得主设备号和次设备号的宏
unsigned int iminor(struct inode *inode); unsigned int imajor(struct inode *inode);
|
字符设备的注册
内核内部使用struct cdev结构表示字符设备。在内核调用设备操作前,必须分配一个或多个上述结构,因此代码要包含,其中定义了这个结构及其辅助函数。
字符设备的分配
struct cdev *my_cdev = cdev_alloc( ); my_cdev->ops = &my_fops;
|
字符设备的初始化
void cdev_init(struct cdev *cdev, struct file_operations *fops);
|
另外,struct cdev的所有者字段也应该设置为THIS_MODULES.
通知内核该结构的信息
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
|
dev是cdev结构,num是该设备对应的第一个设备编号,count是应该和该设备关联的设备编号的数量。
移除一个字符设备
void cdev_del(struct cdev *dev);
|
Scull设备中的设备注册
scull内部,struct scull_dev结构表示每个设备
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设备的注册
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; 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); int unregister_chrdev(unsigned int major, const char *name);
|
major是设备的主设备号,name是驱动程序名称,fops是默认的file_operations结构。
register_chrdev()将为给定的主设备号注册0~255作为次设备号,并为每个设备建立一个对应的默认cdev结构。不能使用大于255的主设备号和次设备号。
open和release方法
open方法提供给驱动程序初始化能力。
- 检查设备特定的错误
- 如果设备首次打开,则对其初始化
- 如有必要,更新f_op指针
- 分配并填写置于file->private_data里的数据结构
int (*open)(struct inode *inode, struct file *filp);
|
inode参数的i_cdev字段包含了cdev结构,可以从cdev结构得到scull_dev结构。
#include <linux/kernel.h>
struct scull_dev *dev; /* device information */ dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; /* for other methods */
|
将scull_dev结构指针保存到file结构的private_data字段,以备后用。
scull_open代码片段
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); 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) { scull_trim(dev); /* ignore errors */ } return 0; /* success */ }
|
release方法作用和open相反。
- 释放由open分配的、保存在filp->private_data中的所有内容
- 在最后一次关闭操作时关闭设备
scull_release代码片段
int scull_release(struct inode *inode, struct file *filp) { return 0; }
|
并不是每个close系统调用都会引起对release方法的调用。只有真正释放设备数据结构的close调用才会调用这个方法。内核对每个file结构维护其被使用次数的计数器。只有在file结构的计数归0时,close系统调用才会执行release方法。
scull设备的内存使用
scull使用的内存区域也成为设备,长度可变。
scull驱动程序引入Linux内核中用于内存管理的2个核心函数。
#include <linux/slab.h>
void *kmalloc(size_t size, int flags); void kfree(void *ptr);
|
前者试图分配size个字节的内存,其返回值指向该内存,失败返回NULL。flags表示分配内存的方法。
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_trim函数负责释放数据区,并且在文件以写入方式打开时由scull_open调用。
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; }
|
模块的清楚函数也调用scull_trim函数,以便将由scull所使用的内存返回给系统。
read和write
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);
|
filp是文件指针,count是请求传输的数据长度,buff是指向用户空间的缓冲区,offp是一个指向long offset type(长偏移量类型)对象的指针,这个对象指明用户在文件中进行存取操作的位置。返回值是signed size type类型。
用于在用户地址空间和内核地址空间进行整段数据拷贝的函数
#include <asm/uaccess.h>
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);
|
这2个函数在进行数据拷贝之前先检查用户空间指针是否有效,如果无效则不拷贝,如果在拷贝的过程中遇到无效指针,则拷贝部分数据。
调用程序对read的返回值解释如下
- 如果返回值等于传递给read系统调用的coun参数,说明请求的数据成功传输
- 如果返回值为正,但小于count,说明只有部分数据成功传输,程序可能会重新读取数据
- 如果返回值为0,表示到达了文件尾
- 负值意味着发生了错误,该值指明了发生了什么错误,错误码在中定义
scull设备的read代码片段
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; }
|
调用程序对write方法的返回值解释
- 返回值等于count,则完成了所请求数据的传输
- 返回值为正,但小于count,则只传输了部分数据,程序可能再次试图写入余下数据
- 返回值为0,意味着什么也没写入。这个结果不是错误。标准库会重复调用write
- 负值意味着发生错误,与read同解
scull设备的write代码
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; }
|
readv和writev
向量型函数,具有一个结构数组,每个结构包含一个指向缓冲区的指针和长度值。
readv调用可用于将指定数量的数据依次读入每个缓冲区。
writev调用是把每个缓冲区内容收集起来,并将他们在一次写入操作中进行输出。
如果驱动程序没有提供用于处理向量操作的方法,readv和writev会通过多次调用read和write来实现。
向量操作函数原型
ssize_t (*readv) (struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos); ssize_t (*writev) (struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
|
iovec结构的定义在
struct iovec { void _ _user *iov_base; __kernel_size_t iov_len; };
|
iovec结构描述了一个用于传输的数据块--这个数据块起始位置在iov_base,长度为iov_len个字节。
count参数指明要操作多少个iovec结构。
scull设备驱动程序源码及测试
源码:
|
文件: |
scull.zip |
大小: |
10KB |
下载: |
下载 | |
装载脚本:
|
文件: |
scull_load.zip |
大小: |
0KB |
下载: |
下载 | |
测试代码:
|
文件: |
scull-test.zip |
大小: |
0KB |
下载: |
下载 | |
阅读(1427) | 评论(0) | 转发(0) |