scull的阅读笔记
---------scull------------
Simple Character Utility for LoadingLocalities,区域装载的简单字符工具。
scull设备:scull0,scull1,scull2,scull3.
这四个设备分别由一个全局且持久的内存区域组成。
可以使用常用命令来访问和测试这个设备,如cp,cat以及shell的I/O重定向等。
-------主设备号和次设备号--------------
设备,位于/dev目录,字符设备的标志是c,块设备的标志是b。
主设备号标识设备对应的驱动程序,次设备号由内核使用,用于正确确定设备文件所指的设备。
dev_t类型(<linux/types.h>中定义),用来保存设备编号,主设备号12位,次设备号20位。
由dev_t类型获得主设备号和次设备号:
MAJOR(dev_t dev);/*<linux/kdev_t.h>*/
MINOR(dev_t dev);
将主设备号和次设备号转换成dev_t类型:
MKDEV(int major,int minor); /*<linux/kdev_t.h>*/
-------分配和释放设备编号---------
获得设备编号:
int register_chrdev_region(dev_tfirst,unsigned int count,char *name); /*<linux/fs.h>*/
first:设备编号范围的起始值
count:连续设备编号的个数
name:和该设备编号范围关联的设备名称,将出现在/proc/devices和sysfs中。
设备编号的动态分配
int alloc_chrdev_region(dev_t *dev,unsignedint firstminor,unsigned int count,char *name);
dev:保存申请到的第一个设备号
firstminor:第一个次设备号
count,name:同register_chrdev_region().
释放设备编号:
void unregister_chrdev_region(dev_tfirst,unsigned int count);
在用户空间程序可以访问上述设备编号之前,驱动程序需要将设备编号和内部函数连接起来,这些内部函数用来实现设备的操作。
动态分配主设备号
驱动程序应该始终使用动态分配机制获取主设备号,即使用alloc_chrdev_region,而不是register_chrdev_region.
分配过设备号以后,可以从/proc/devices中读取到。
用来获取主设备号的代码:
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 getmajor %d\n",scull_major);
return result;
}
----------一些重要的数据结构--------------
file_operatons,file,inode
file_operations结构:
就是用来建立设备编号与驱动程序操作的链接的。
file_operations结构在<linux/fs.h>中定义。
这个结构的每一个字段指向实现特定操作的函数。
scull设备驱动的file_operations结构:
struct file_operations scull_fops =
{
.ownner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioctl,
.open = scull_open,
.release= scull_release,
};
file结构:在<linux/fs.h>中定义。
file结构代表一个打开的文件,系统中每个打开的文件在内核空间都有一个对应的file结构。
它由内核在open时创建,并传递给在该文件上进行操作的所有函数,知道close。
inode结构:
内核用inode结构在内部表示文件。
其中的两个字段如下
dev_t i_rdev 设备编号
struct cdev *icdev 字符设备的内部结构
------------字符设备的注册----------------
内核用struct cdev结构表示字符设备,头文件<linux/cdev.h>
在内核调用设备的操作之前,必须分配并注册一个或多个上述结构。
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
这时,可以将cdev结构嵌入到自己的设备特定结构中。
初始已分配到的机构:
void cdev_init(struct cdev *cdev,structfile_operations *fops);
在cdev结构设置好之后,最后的步骤是通过下面的调用告诉内核该结构的信息:
int cdev_add(struct cdev *dev,dev_tnum,unsigned int count);
num:第一个设备编号
count:设备编号的数量
移出一个字符设备:
void cdev_del(struct cdev *dev);
----------scull中的设备注册-----------
在scull内部,通过structscull_dev的结构来表示每个设备。
struct scull_dev
{
struct scull_qset *data; /*指向第一个量子集的指针*/
int quantum; /*当前量子的大小*/
int qset; /*当前数组的大小*/
unsigned long size; /*保存在其中的数据总量*/
unsigned int access_key; /*由sculluid和scullpriv使用*/
struct semaphore sem; /*互斥信号量*/
struct cdev cdev; /*字符设备结构*/
};
cdev的代码:
static void scull_setup_cdev(structscull_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);
if(err)
printk(KERN_NOTICE "Error%d adding scull %d",err,index);
}
----------早期的办法---------------
早期不使用cdev接口。
注册一个字符设备驱动程序的经典方式是:
int register_chrdev(unsigned intmajor,const char *name,struct file_operations *fops);
移出的函数:
int unregister_chrdev(unsigned intmajor,const char *name);
----------open和release------------------
open方法
open方法做初始化的工作。
open的原型:
int (*open)(struct inode *inode,struct file*filp);
inode参数的i_cdev字段包含了所需要的信息,唯一的问题是,我们通常不需要cdev
本身,而是希望得到包含cdev结构的scull_dev结构,这可以通过<linux/kernel.h>
中的container_of宏来实现:
container_of(pointer,container_type,container_field);
struct scull_dev *dev; /*deviceinformation*/
dev = container_of(inode->i_cdev,structscull_dev,cdev);
filp->private_data = dev;
一旦代码找到scull_dev结构之后,scull将一个指针保存到file结构的private_data字段,方便今后对该指针的访问!!!
scull_open代码如下:
int scull_open(struct inode *inode,structfile *filp)
{
struct scull_dev *dev; /*deviceinformation*/
dev = container_of(inode->i_cdev,structscull_dev,cdev);
filp->private_data = dev;
if((filp->f_flags & O_ACCMODE) ==O_WRONLY)
{
scull_trim(dev);
}
return 0;
}
release方法
释放由open分配的、保存在filp->private_data中的所有内容,
在最后一次关闭操作时关闭设备。
----------scull的内存使用-------------
scull驱动程序引入了linux内核中用于内存管理的两个核心函数。
定义在<linux/slab.h>中,它们是
void *kmalloc(size_t size,int flags);flags:GFP_KERNEL
void kfree(void *ptr);
在scull中,每个设备都是一个scull_qset结构体组成的链表,
每个scull_qset结构体里有一个指针数组(称量子集),
指针数组里的每个指针指向一个4000字节的内存区(称量子)。
scull_trim简单地遍历链表,释放所有找到的量子和量子集。
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)
{
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;
}
---------------read和write--------------
read完成的任务是从内核空间拷贝数据到应用程序空间;
write则是从应用程序空间拷贝数据到内核空间。
ssize_t read(struct file *filp,char __user*buff,size_t count,loff_t *offp);
ssize_t write(struct file *filp,const__user *buff,size_t count,loff_t *offp);
offp:指明文件中存取操作的位置。
buff:是用户空间的指针。
很显然,驱动程序必须访问用户空间的缓冲区以便完成自己的工作。
为了确保安全,这种访问应始终通过内核提供的专用函数完成。<asm/uaccess.h>
unsigned long copy_to_user(void __user*to,const void *from,unsigned long count);
unsigned long copy_from_user(void *to,constvoid __user *from,unsigned long count);
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;
int quantum = dev->quantum,qset =dev->qset;
int itemsize = quantum * qset;
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;
/*在量子集中寻找链表项、qset索引以及偏移量*/
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;q_pos = rest %quantum;
dptr = scull_follow(dev,item);
...........省略............
}
在内核中加载scull模块后 可以通过如下方式测试scull的用途
#echo “hello”>/dev/scull0
#cat /dev/scull0
hello解压、修改Android的ramdisk.img的手动方法:
将ramdisk.img复制一份到任何其他目录下,将其名称改为ramdisk.img.gz,并使用命令
gunzip ramdisk.img.gz
然后新建一个文件夹,叫ramdisk吧,进入,输入命令
cpio -i -F../ramdisk.img
这下,你就能看见并操作ramdisk里面的内容了。
#