Chinaunix首页 | 论坛 | 博客
  • 博客访问: 286971
  • 博文数量: 74
  • 博客积分: 2811
  • 博客等级: 少校
  • 技术积分: 710
  • 用 户 组: 普通用户
  • 注册时间: 2009-06-02 21:14
文章分类

全部博文(74)

文章存档

2011年(1)

2010年(24)

2009年(49)

我的朋友

分类:

2009-06-07 10:15:16

第三章
scull( Simple Character Utility for Loading Localities)
scull为一个字符驱动,用于操作内存,由于其没有依赖具体的设备,因此,能够运行在不同的平台上,可移植性强。
 
可以用scull做成不同类型的字符设备
scull0~scull3
全局永久的内存区共享。全局说明设备能被多次打开,设备里的数据能够被打开它的文件描述符所共享;永久说明设备被关闭后再打开,里面的数据依然存在。
 
scullpipe0~scullpipe3
FIFO设备,一个进程读的数据来源于另外一个进程的写数据,如果多个进程读数据,则他们竞争数据;scullpipe的内部可以进行阻塞读写和非阻塞读写,而没有用到中断
 
scullsigle
一次一个进程使用驱动
scullpriv
每个虚拟终端都是私有的,因为每个终端和控制台都有对应的内存区
sculluid
可以多次打开,但每次只能一个用户使用。如果一个设备被“锁着”,则会返回“设备忙”
scullwuid
可以多次打开,但每次只能一个用户使用。实现阻塞打开
 
主次编号:
字符设备用“c”标识,块设备用“b”标识
 
设备编号的内部表示:
在linux/type.h里面的dev_t定义为32位无符号数,其中12位为主设备编号,20位为次设备编号
 
在linux/kdev_t.h里面的定义如何获得主设备号或次设备号
MAJOR(dev_t dev);
MINOR(dev_t dev);
如果你想要一个主次编号,则要调用
MKDEV(int major,int minor);
 
分配和释放注册编号:
在linux/fs.h
固定分配设备号:
int register_chrdev_region(dev_t first,unsigned int count,const char *name);
first:你要分配的起始设备编号 count:连续设备编号的总数 name:设备名
 
动态分配设备号:
int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,const char *name);
dev:是一个输出参数,为分配范围的第一个数 firstminor:第一个请求的次设备号,其余同上
 
如果成功,则返回0,如果失败,则返回负数
 
释放一个设备号:(动态和固定都使用)
unregister_chrdev_region(dev_t first,unsigned int count); 
 
主编号的动态分配:
除非只有自己写的内核才不会出现设备号冲突,因此,使用动态分配比较好,你也可以先用自定义的如果返回为负值,则调用动态分配也可以,因此多使用的是alloc_chrdev_region而不是register_chrdev_region
 
在调用insmod后,可以用lsmod来查看设备号,或者在/proc/devices目录下查看
 
应用动态分配设备号的一个缺点是不能预先设定你的设备节点,因此要使用下面的方法来填补这个缺点
利用awk可以查询主设备号
 

#!/bin/sh
module="scull"#模块的名字
device="scull"#设备的名字
mode="664"#建立设备的模式
# invoke insmod with all arguments we got
# and use a pathname, as newer modutils don't look in . by default
/sbin/insmod ./$module.ko $* || exit 1
# remove stale nodes
rm -f /dev/${device}[0-3] #删除之前创建的设备节点
major=$(awk "
" {print }" /proc/devices)#获取主设备号
mknod /dev/${device}0 c $major 0 #设置设备节点
mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2
mknod /dev/${device}3 c $major 3
# give appropriate group/permissions, and change the group.
# Not all distributions have staff, some have "wheel" instead.
group="staff" #设置group的权限
grep -q '^staff:' /etc/group || group="wheel"
chgrp $group /dev/${device}[0-3]
chmod $mode /dev/${device}[0-3] #修改设备模式

 
后面的几句权限说明会在后面的章节介绍
 
下面是在申请主设备号的模块比较好的方法

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;
}

其中scull_major为设备的主设备号,scull_nr_devs为设备的个数,dev用于保存设备的主次设备号,MKDEV函数用于获取dev的主次设备号,MAJOR函数用于获取动态分配后的主设备号
记住动态分配函数里面的是填写第一个想分配次设备号,而不是主设备号!!!
 
一些重要的数据结构
基本的驱动操作包括了3个基本的数据结构:file_operations,file和inode
下面具体一一说明:
文件操作:file_operations
其定义在linux/fs.h里面
这是一个函数指针的集合,定义了read,write等一些操作函数,大部分实现了系统的操作
以下是函数的部分介绍:(对第三版的电子书的翻译做了部分修改!!不知正确与否!!!)(蓝色为不太理解的,留待以后章节再研究!!!)
 
struct module *owner
作为第一个 file_operations 成员并不是用于操作的; 它是一个指向拥有这个结构的模块的指针. 这
个成员用在模块还在使用的情况下防止被卸载. 一般它被简单初始化为THIS_MODULE, 其定义在
 
loff_t (*llseek) (struct file *, loff_t, int);
llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一
个"long offset", 能用在位宽为32位和64位上. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会随机的方式修改 file 结构中的位置计数器( 在"file 结构" 一节中描述).
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
用来从设备中获取数据. 如果是一个空指针导致 read 系统返回 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).

ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t);
初始化一个异步读 -- 可能在函数返回前不结束的读操作. 如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
发送数据给设备. 如果 NULL, 返回-EINVAL . 如果非负, 则代表成功写的字节数.

ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *);
初始化设备上的一个异步写.

int (*readdir) (struct file *, void *, filldir_t);
对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.

unsigned int (*poll) (struct file *, struct poll_table_struct *);
poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的
读或写是否会阻塞. poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且,
可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 如果一个驱动的 poll 方法
为 NULL, 设备假定为不阻塞地可读可写.

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不
是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表. 如果设备不提供 ioctl 方法, 对于
任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.

int (*mmap) (struct file *, struct vm_area_struct *);
mmap函数将设备内存映射到进程的地址空间. 如果这个返回是 NULL, 则返回 -ENODEV.帧缓冲比较有用

int (*open) (struct inode *, struct file *);
尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项
是 NULL, 设备打开一直成功, 但是你的驱动不会得到通知.(翻译好像有点..,暂时看不明白)

int (*flush) (struct file *);
flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何
未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中
使用;
SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush
为 NULL, 内核简单地忽略用户应用程序的请求.(实在看不懂)

int (*release) (struct inode *, struct file *);
在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.

int (*fsync) (struct file *, struct dentry *, int);
这个方法是 fsync 系统调用的后端, 用户调用来刷新任何挂着的数据. 如果这个指针是
NULL, 系统调用返回 -EINVAL.

int (*aio_fsync)(struct kiocb *, int);
这是 fsync 方法的异步版本.

int (*fasync) (int, struct file *, int);
这个操作用来通知设备它的 FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述. 这个成员可以是NULL 如果驱动不支持异步通知.

int (*lock) (struct file *, int, struct file_lock *);
lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实
现它.

ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
这些方法实现分散/聚集读写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作; 这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 ).

ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *);
这个方法实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个.
例如, 它被一个需要发送文件内容到一个网络连接的 web 服务器使用. 设备驱动常常使 sendfile 为 NULL.

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动
实际上不实现 sendpage.sendpage 通常为 NULL.

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned ong);
这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中. 这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.

int (*check_flags)(int)
这个方法允许模块检查传递给 fnctl(F_SETFL...) 调用的标志.

int (*dir_notify)(struct file *, unsigned long);
这个方法在应用程序使用 fcntl 来请求目录改变通知时调用. 只对文件系统有用; 驱动不需要
实现 dir_notify.
 
scull只用到了部分比较重要的函数,其file_operations的初始化步骤为:
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, 
};
 
 
文件结构:file
struct file 代表了一个打开的文件,系统中每个打开的文件在内核空间都有一个关联的struct file 当关闭所有实例后,内核才会释放这个数据结构
 
struct file可以称为file或filp,其中file多指结构,filp多指结构指针
其中包括的主要函数:
 
mode_t f_mode;
文件模式确定文件是可读的或者是可写的(或者都是), 通过位 FMODE_READ 和
FMODE_WRITE. 你可能想在你的 open 或者 ioctl 函数中检查这个成员的读写许可, 但是你
不需要检查读写许可, 因为内核在调用你的方法之前检查. 当文件还没有为那种存取而打开
时读或写的企图被拒绝, 驱动甚至不知道这个情况.(文件的打开模式)
loff_t f_pos;
当前读写位置. loff_t 在所有平台都是 64 位( 在 gcc 术语里是 long long ). 驱动可以读这个值,
如果它需要知道文件中的当前位置, 但是正常地不应该改变它; 读和写应当使用它们作为最
后参数而收到的指针来更新一个位置, 代替直接作用于 filp->f_pos. 这个规则的一个例外是
在 llseek 方法中, 它的目的就是改变文件位置.当前读写位置,尽量不要改变当前位置)
unsigned int f_flags;
这些是文件标志, 例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驱动应当检查
O_NONBLOCK 标志来看是否是请求非阻塞操作( 我们在第一章的"阻塞和非阻塞操作"一节
中讨论非阻塞 I/O ); 其他标志很少使用. 特别地, 应当检查读/写许可, 使用 f_mode 而不是
f_flags
. 所有的标志在头文件 中定义.(文件的打开标志)
struct file_operations *f_op;
和文件关联的操作. 内核安排指针作为它的 open 实现的一部分, 接着读取它当它需要分派任
何的操作时. filp->f_op 中的值从不由内核保存为后面的引用; 这意味着你可改变你的文件关
联的文件操作, 在你返回调用者之后新方法会起作用. 例如, 关联到主编号 1 (/dev/null, /dev/
zero, 等等)的 open 代码根据打开的次编号来替代 filp->f_op 中的操作. 这个做法允许实现几
种行为, 在同一个主编号下而不必在每个系统调用中引入开销. 替换文件操作的能力是面向
对象编程的"方法重载"的内核对等体.
 
void *private_data;
open 系统调用设置这个指针为 NULL, 在为驱动调用 open 方法之前. 你可自由使用这个成员
或者忽略它; 你可以使用这个成员来指向分配的数据, 但是接着你必须记住在内核销毁文件
结构之前, 在 release 方法中释放那个内存. private_data 是一个有用的资源, 在系统调用间保留
状态信息, 我们大部分例子模块都使用它.
struct dentry *f_dentry;
关联到文件的目录入口( dentry )结构. 设备驱动编写者正常地不需要关心 dentry 结构, 除了
作为 filp->f_dentry->d_inode 存取 inode 结构.
 
inode结构:inode
inode 结构由内核在内部用来表示文件.
大部分的函数都不怎么使用,主要的有以下两个(好像还有一个块的函数,不知道为什么说不用,留待以后更进)
dev_t i_rdev;
对于代表设备文件的节点, 这个成员包含实际的设备编号.

struct cdev *i_cdev;
struct cdev 是内核的内部结构, 代表字符设备; 这个成员包含一个指针, 指向这个结构, 当节点指的是一个字符设备文件时.
 
为了移植性好,已经少采用i_rdev函数了,而采用以下的函数在inode中提取主次设备号
unsigned int major(struct inode *inode);
unsigned int minor(struct inode *inode);
 
字符设备的注册:
应用struct cdev来注册字符设备,因此必须引用linux/cdev.h
 
注册字符设备方法:(步骤)
struct cdev *my_cdev = cdev_alloc();
 
void cdev_init(struct cdev *cdev, struct fileoperations *fops);
 
cdev.owner = THIS_MODULE;
 
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
 
dev为cdev结构,num 设备相应的第一个设备号,count 关联到设备的设备数
其中cdev_add有可能会返回失败,但一经调用,你的设备就被认为是“可使用”因此你要确保在调用cdev_add前的操作成功,并且驱动已经准备好。。。
 
卸载字符设备的方法:
void cdev_del(struct cdev *dev);
 
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);
}
 
 
open和release:
open:
大部分open进行以下操作:
1.检查设备特定的错误(例如设备没准备好, 或者类似的硬件错误)
2.如果它第一次打开, 初始化设备
3.如果需要, 更新 f_op 指针.
4.分配并填充要放进 filp->private_data 的任何数据结构
 
open方法的原型是:
int (*open)(struct inode *inode,struct file *filp);
我们不想要包含cdev的结构,而是要包含scull_cdev的结构,因此采用container_of(定义在linux\kernel.h)
container_of(pointer, container_type, container_filer);
例如:
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
 
release:
release做的动作刚刚跟open相反,他要做的东西有以下的2种
1.释放 open 分配在 filp->private_data 中的任何东西
2.在最后的 close 关闭设备
 
因为scull没有涉及相关的硬件,因此他的release是最简单的,如下:
int release(struct inode *inode,struct file filp)
{
  return 0;
}
 
 设备不会多次调用release,因为只有close等于0的时候,才会调用release,因此每个设备open和release都是一一对应的
 
scull内存使用
scull是由一个包括1000个指针的数组指向4000或8000个字节区域(32位或64位的系统)
其中数组为量子集即量子的数量=1000,4000为量子,总的空间=量子*量子集=(4000或8000)*1000
量子=SCULL_QUANTUM 量子集=SCULL_QSET
 
scull应用2个函数来管理内存:
void *kmalloc(size_t size, int flags);
void kfree(void *ptr);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
阅读(761) | 评论(0) | 转发(0) |
0

上一篇:关于TS流的解析

下一篇:VM硬盘扩容

给主人留下些什么吧!~~