SCULL device driver
note
Learner: 山涛
Date: 2007-6-29
Reference:
ldd3_example/scull/main.c
设备类型:
字符设备
驱动介绍:
SCULL (Simple Character Utility for Loading Localities).
SCULL is a char driver that acts on a memory area
as though it were a device.
scull0 to scull3:
Four devices, each consisting of a
memory area that is both global and persistent. Global means that if the device
is opened multiple times, the data contained within the device is shared by all
the file descriptors that opened it. Persistent means that if the device is
closed and reopened, data isn't lost. This device can be fun to work with,
because it can be accessed and tested using conventional commands, such as cp,
cat, and shell I/O redirection.
设备模型:
SCULL是对你的PC上的一片内存进行操作,而且没有限制使用内存的大小(它完全可以用光你的PC上的所有内存)。SCULL是通过kmalloc()和kfree()来进行内存的获取和释放。
In scull, each
device is a linked list of pointers, each of which points to a scull_dev structure. Each such
structure can refer, by default, to at most four million bytes, through an array
of intermediate pointers. The released source uses an array of 1000 pointers to
areas of 4000 bytes. We call each memory area a quantum and the array (or
its length) a quantum set.
The chosen numbers are such that writing a single
byte in scull consumes 8000 or 12,000 thousand bytes of memory: 4000 for the
quantum and 4000 or 8000 for the quantum set (according to whether a pointer is
represented in 32 bits or 64 bits on the target platform). If, instead, you
write a huge amount of data, the overhead of the linked list is not too bad.
There is only one list element for every four megabytes of data, and the maximum
size of the device is limited by the computer's memory size.
表示设备的数据结构:
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
*/
};
驱动框架:
①模块初始:module_init(scull_init_module);
②模块注销:module_exit(scull_cleanup_module);
③字符设备相关方法的实现:scull_llseek, scull_read, scull_write,
scull_ioctl, scull_open, scull_release。
(1)
模块初始化
1)使用alloc_chrdev_region()函数动态获取主设备号,并注册4个SCULL设备为字符设备(scull0~scull3);
Name
alloc_chrdev_region —
register a range of char device numbers
Synopsis
Int alloc_chrdev_region
( |
dev_t *
|
dev,
|
|
unsigned
|
baseminor,
|
|
unsigned
|
count,
|
|
const char *
|
name); |
Arguments
dev
output parameter for first
assigned number
baseminor
first of the requested range
of minor numbers
count
the number of minor numbers
required
name
the name of the associated
device or driver
Description
Allocates a range of char
device numbers. The major number will be chosen dynamically, and returned (along
with the first minor number) in dev. Returns zero or a negative error code.
|
2)使用kmalloc()函数为注册的4个字符设备分配相应的数据结构scull_dev;
3)初始化scull_dev结构的相关域:quantum, qset, sem;
4)为scull设备在内核里建立char_dev结构(调用cdev_init()、cdev_add()函数);
As we mentioned, the kernel
uses structures of
type struct cdev to represent char devices internally. Before the kernel invokes
your device's operations, you must allocate and register one or more of these
structures.[6] To do
so, your code should include , where the structure and its
associated helper functions are defined.
Name
cdev_init — initialize a
cdev structure
Synopsis
void cdev_init
( |
struct cdev *
|
cdev,
|
|
const struct file_operations
* |
fops); |
Arguments
cdev
the structure to initialize
fops
the file_operations for this
device
Description
Initializes cdev,
remembering fops, making it ready to add to the system with cdev_add.
Name
cdev_add — add a char device
to the system
Synopsis
int cdev_add
( |
struct cdev *
|
p,
|
|
dev_t
|
dev,
|
|
unsigned
|
count); |
Arguments
p
the cdev structure for the
device
dev
the first device number for
which this device is responsible
count
the number of consecutive
minor numbers corresponding to this device
Description
cdev_add adds the device
represented by p to the system, making it live immediately. A negative error
code is returned on failure. |
(2)
模块注销
1)
释放设备数据结构scull_dev以及其上链接的所有内存块;调用cdev_del(),从内核里删除scull设备相应的cdev数据结构。
Name
cdev_del — remove a cdev
from the system
Synopsis
void cdev_del
( |
struct cdev *
|
p); |
Arguments
p
the cdev structure to be
removed
Description
cdev_del removes
p from the system, possibly freeing the structure itself. |
2)
调用unregister_chrdev_region()函数释放分配的一系列设备号。
Name
unregister_chrdev_region —
return a range of device numbers
Synopsis
void
unregister_chrdev_region ( |
dev_t
|
from,
|
|
unsigned
|
count); |
Arguments
from
the first in the range of
numbers to unregister
count
the number of device numbers
to unregister
Description
This function
will unregister a range of count device numbers, starting with from. The caller should normally be the
one who allocated those numbers in the first place... |
(3)
方法实现
1) open()
a.
获取表示scull设备的数据结构scull_dev;(调用container_of())
b.
将scull_dev结构保存在filp->private_data;
c.
如果打开设备文件的模式为O_WRONLY,调用scull_trim()函数对数据结构进行重置。(
The device is truncating it to a length of 0 when
the device is opened for writing. This is performed because, by design,
overwriting a scull device with a shorter file
results in a shorter device data area. This is similar to the way opening a
regular file for writing truncates it to zero length. The operation does nothing
if the device is opened for reading.)
2) release()
N/A
3) read()
a.
根据参数f_pos获取读数据的buffer起始地址指针;
b.
计算读数据的长度(scull设备的策略是,读取的数据长度最大不超过quantum值,这也就意味着,读取的数据在一个quantum内存块之内);
c.
调用copy_to_user()函数将数据从scull设备读取到用户空间;
d.
调整参数f_pos的值,返回实际读取数据的长度。
4) write()
a.
根据参数f_pos获取写数据的buffer地址指针;(如果buffer不存在,应当使用kmalloc()分配)
b.
计算写数据的长度(scull设备的策略是,写入的数据长度最大不超过quantum值,这也就意味着,写入的数据在一个quantum内存块之内);
c.
调用copy_from_user()函数将数据从用户空间写入到scull设备;
d.
调整参数f_pos的值;调整scull_dev结构size域的值,该值表示scull设备文件当前的实际长度。
e.
返回实际写入数据的长度。
5) ioctl()
The ioctl driver method has a prototype that
differs somewhat from the user-space version:
int (*ioctl)
(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long
arg);
The inode
and filp pointers are the values
corresponding to the file descriptor fd passed on by the application and are the
same parameters passed to the open method. The cmd argument is passed from the user unchanged,
and the optional arg argument is passed in
the form of an unsigned long, regardless of whether it was given by the user as
an integer or a pointer. If the invoking program doesn't pass a third argument,
the arg value received by the driver
operation is undefined. Because type checking is disabled on the extra argument,
the compiler can't warn you if an invalid argument is passed to ioctl, and any
associated bug would be difficult to spot.
cmd由四部分组成:type, number, direction,
size。Type 8位,称为幻数;number 8位,cmd的序列数;direction 2位,表示数据传输的方向;size 14位,表示用户参数的长度。
Scull设定了12个cmd命令,用于用户可以获取或者设置quantum和qset的值。参数arg使用两种方式,一种指针,一种直接赋值。
/*
* S means "Set" through a
ptr,
* T means "Tell" directly with the argument
value
* G means "Get": reply by setting through a
pointer
* Q means "Query": response is on the return
value
* X means "eXchange": switch G and S
atomically
* H means "sHift": switch T and Q
atomically
*/
#define SCULL_IOCSQUANTUM
_IOW(SCULL_IOC_MAGIC, 1,
int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
#define SCULL_IOCTQUANTUM
_IO(SCULL_IOC_MAGIC,
3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)
#define SCULL_IOCGQUANTUM
_IOR(SCULL_IOC_MAGIC, 5,
int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)
#define SCULL_IOCQQUANTUM
_IO(SCULL_IOC_MAGIC,
7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)
#define SCULL_IOCXQUANTUM
_IOWR(SCULL_IOC_MAGIC, 9, int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10,
int)
#define SCULL_IOCHQUANTUM
_IO(SCULL_IOC_MAGIC,
11)
#define
SCULL_IOCHQSET
_IO(SCULL_IOC_MAGIC,
12) |
6) llseek()
修改文件的偏移量。
每个打开文件都有一个与其相关联的“当前文件位移量”。它是一个非负整数,
用以度量从文件开始处计算的字节数。通常,读、写操作都从当前文件位移量
处开始,并使位移量增加所读或写的字节数。按系统默认,当打开一个文件时,
除非指定O_APPEND选项,否则该位移量被设置为0。
可以调用seek()显示的定位一个文件。
对参数off的解释,取决于另一个参数whence。
若whence=SEEK_SET,则将该文件的位移量设置为距文件开始处offset个字节;
若whence=SEEK_CUR,则将该文件的位移量设置为其当前值加offset,offset可正可负;
若whence=SEEK_END,则将该文件的位移量设置为文件长度加offset,offset可正可负。