第3章 字符驱动
本书示例scull 不依任何赖硬件设备,只是操作一些从内核分配的内存,任何人都可以编译和运行scull;注意,本章节源码摘自NanoPi-linux3.4.y
内核,与阅读的本书源码略有差别
文章目录
3.1 scull设计需求
第一步定义驱动将要提供个用户程序的能力(机制)
- scull0~scull3:4 个设备,每个由一个全局永久的内存区组成(全局意味着如果设备被多次打开,设备中含有的数据由所有打开它的文件描述符共享)
- scullpipe0~scullpipe3:4个FIFO(先入先出) 设备,行为像管道(一个进程读的内容来自另一个进程所写,如果多个进程读同一个设备,它们竞争数据)
- scullsingle(只允许一次一个进程使用驱动)、scullpriv(对每个虚拟终端私有,每个控制台/终端上的进程有不同的内存区)、sculluid(一次只能是一个用户,返回设备忙)、scullwuid
3.2 设备编号
- 字符设备通过文件系统中的名字来存取(设备文件、文件系统结点),位于
/dev/
目录 ls -l
,字符驱动的第一列以c
标识,块设备的第一列以b
标识;,
左侧的数字为主编号,右侧为次编号- 主编号标识设备相连的驱动,次编号被内核用来决定引用哪个设备
#root@OpenWrt:~# ls -l /dev
drwxr-xr-x 3 root root 60 Jan 1 1970 bus
crw------- 1 root root 5, 1 Jan 1 1970 console
crw------- 1 root root 10, 63 Jan 1 1970 cpu_dma_latency
crw-rw-rw- 1 root root 1, 7 Jan 1 1970 full
crw------- 1 root root 254, 0 Jan 1 1970 gpiochip0
3.2.1 设备编号的内部表示
dev_t
类型(<linux/types.h>
)用来持有设备编号,可使用<linux/kdev_t.h>
中宏定义获取设备主次编号(MAJOR(dev_t dev)
、MINOR(dev_t dev)
);主次编号可以使用MKDEV(int major, int minor)
转换为dev_t
//linux/types.h
typedef __kernel_dev_t dev_t;
//linux/kdev_t.h
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
3.2.2 分配和释放设备编号
- 获取一个或多个设备编号;
register_chrdev_region
中from
为要分配的起始设备编号,count
为请求的连续设备编号总数(不宜过大,会溢出占用下一个次编号),name
为连接到这个编号范围的设备名字(会出现在/proc/devices
和sysfs
中)
//linux/fs.h 成功返回0,失败返回错误码
extern int register_chrdev_region(dev_t, unsigned, const char *);
//fs/char_dev.c
int register_chrdev_region(dev_t from, unsigned count, const char *name)
- 请求内核动态分配主编号;
alloc_chrdev_region
中dev
为函数成功完成时分配范围的第一个数,baseminor
为请求的第一个要用的次编号
//linux/fs.h
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
//fs/char_dev.c
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
- 释放设备编号;调用
unregister_chrdev_region
的地方常常是模块的cleanup
函数
//linux/fs.h
extern void unregister_chrdev_region(dev_t, unsigned);
//fs/char_dev.c
void unregister_chrdev_region(dev_t from, unsigned count)
3.2.3 主编号的动态分配
- 一些主编号是静态分配给最普通的设备,可从
Documentation/devices.txt
中查看,可随机选取空闲的编号作为主编号,但可能引发冲突
#Documentation/devices.txt截取片段
0 Unnamed devices (e.g. non-device mounts)
0 = reserved as null device number
See block major 144, 145, 146 for expansion areas.
1 char Memory devices
1 = /dev/mem Physical memory access
2 = /dev/kmem Kernel virtual memory access
3 = /dev/null Null device
1 block RAM disk
0 = /dev/ram0 First RAM disk
1 = /dev/ram1 Second RAM disk
- 建议动态获取主设备编号,动态分配的缺点为无法提前创建设备节点,但可以在编号分配之后读取
/proc/devices
获取设备节点
root@OpenWrt:~# cat /proc/devices
Character devices:
1 mem
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
Block devices:
259 blkext
31 mtdblock
- 为使动态主编号来加载一个驱动,可以使用以下脚本
snull_load
来代替调用insmod
,在调用insmod
后,读取/proc/devices
创建特殊文件;该脚本可以通过重定义变量和调整mknod
行来适用于另外的驱动,该脚本仅仅展示了创建4个设备;该脚本必须root
用户执行,因此在脚本的最后几行改变了设备的组模式
#!/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 "\\$2==\"$module\" {print \\$1}" /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"
grep -q '^staff:' /etc/group || group="wheel"
chgrp $group /dev/${device}[0-3]
chmod $mode /dev/${device}[0-3]
- 主编号分配的最好方式是缺省使用动态分配,而预留在加载时指定主编号的选项权;
3.3 3种重要的数据结构
大部分驱动操作包括3 个重要的内核数据结构,file_operations
,file
和inode
3.3.1 file_operations
file_operations
将字符驱动与设备操作相联系,f_op
是一个指向file_operations
的指针,而file_operations
是一个函数指针的集合,每打开一个文件都会使用一个f_op
成员与自身函数集合相关联(这也是内核中面向对象式编程)
//linux/fs.h
const struct file_operations *f_op;
struct file_operations {
struct module *owner;//指向拥有这个结构的模块的指针,它被简单初始化为 THIS_MODULE(<linux/module.h>)
loff_t (*llseek) (struct file *, loff_t, int);//改变文件中的当前读/写位置,并且新位置作为(正的)返回值,loff_t 参数是一个long offset,并且就算在32位平台上也至少64位宽,错误由一个返回负值,如果这个函数指针是NULL,seek调用会以潜在地无法预知的方式修改file结构中的位置计数器
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//用来从设备中获取数据,一个非负返回值代表了成功读取的字节数(返回值是一个signed size 类型,常常是目标平台本地的整数类型)
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//发送数据给设备,非负返回值代表成功写的字节数
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);//初始化一个异步读(可能在函数返回前不结束的读操作),如果这个方法是NULL,所有的操作会由read代替进行(同步)
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, 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,设备假定为不阻塞地可读可写
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);//ioctl系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道,这不是读也不是写),另外, 几个ioctl命令被内核识别而不必引用fops表,如果设备不提供ioctl方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的ioctl"), 系统调用返回一个错误
int (*mmap) (struct file *, struct vm_area_struct *);//mmap用来请求将设备内存映射到进程的地址空间,如果这个方法是NULL,mmap系统调用返回-ENODEV
int (*open) (struct inode *, struct file *);//尽管这常常是对设备文件进行的第一个操作,不要求驱动声明一个对应的方法,如果这个项是NULL,设备打开一直成功,但是你的驱动不会得到通知
int (*flush) (struct file *, fl_owner_t id);//flush操作在进程关闭它的设备文件描述符的拷贝时调用,它应当执行(并且等待)设备的任何未完成的操作,这个必须不要和用户查询请求的fsync操作混淆,当前,flush在很少驱动中使用;例如,SCSI磁带驱动使用它,为确保所有写的数据在设备关闭前写到磁带上;如果flush为NULL,内核简单地忽略用户应用程序的请求
int (*release) (struct inode *, struct file *);//在文件结构被释放时引用这个操作;如同open,release可以为NULL
int (*fsync) (struct file *, loff_t, loff_t, int datasync);//这个方法是fsync 系统调用的后端,用户调用来刷新任何挂着的数据,如果这个指针是 NULL,系统调用返回 -EINVAL
int (*aio_fsync) (struct kiocb *, int datasync);//这是fsync方法的异步版本
int (*fasync) (int, struct file *, int);//这个操作用来通知设备它的FASYNC标志的改变;如果驱动不支持异步通知,这个成员可以是NULL
int (*lock) (struct file *, int, struct file_lock *);//lock方法用来实现文件加锁,加锁对常规文件是必不可少的特性,但是设备驱动几乎从不实现它
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);//由内核调用来发送数据,一次一页,到对应的文件;设备驱动实际上不实现sendpage
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);//允许模块检查传递给fnctl(F_SETFL...)调用的标志
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
file_operations
结构或其变体的每个成员必须指向驱动中的函数,这些函数实现一个特别的操作,对于不支持的操作留置为NULL
__user
是一种注释,对于正常编译没有任何效果,但它可以被外部检查软件使用,用来找出对用户空间地址的错误使用(一个指针是一个不能被直接解引用的用户空间地址)- scull设备中只需要如下几个重要方法,这个声明使用标准的C标记式结构初始化语法;这个语法是首选的,标记式初始化允许结构成员重新排序
3.3.2 file
struct file
与用户空间程序的FILE
指针无任何关系,它构代表一个打开的文件(它不特定给设备驱动,系统中每个打开的文件有一个关联的struct file
在内核空间),它由内核在open
时创建,并传递给在文件上操作的任何函数,直到最后的关闭;在文件的所有实例都关闭后,内核释放这个数据结构
//linux/fs.h
struct file {
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op;//和文件关联的操作
/*
* Protects f_ep_links, f_flags, f_pos vs i_size in lseek SEEK_CUR.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
#ifdef CONFIG_SMP
int f_sb_list_cpu;
#endif
atomic_long_t f_count;
unsigned int f_flags;//文件标志,如O_RDONLY,O_NONBLOCK,和O_SYNC;驱动应当检查O_NONBLOCK标志来看是否是请求非阻塞操作,所有的标志在头文件<linux/fcntl.h>中定义
fmode_t f_mode;//通过位FMODE_READ和FMODE_WRITE确定文件是可读的或者是可写的(或者都是)
loff_t f_pos;//当前读写位置
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;//使用这个成员来指向分配的数据, 必须记住在内核销毁文件结构之前, 在release方法中释放那个内存;一般用于在系统调用间保留状态信息
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
3.3.3 inode
inode
结构用于内核在内部用表示文件,它和代表打开文件描述符的文件结构是不同的;可能有代表单个文件的多个打开描述符的许多文件结构,但是它们都指向一个单个inode
结构
//linux/fs.h
struct inode {
umode_t i_mode;
unsigned short i_opflags;
uid_t i_uid;
gid_t i_gid;
unsigned int i_flags;
const struct inode_operations *i_op;
struct super_block *i_sb;
struct address_space *i_mapping;
/* Stat data, not accessed from path walking */
unsigned long i_ino;
/*
* Filesystems may only read i_nlink directly. They shall use the
* following functions for modification:
*
* (set|clear|inc|drop)_nlink
* inode_(inc|dec)_link_count
*/
union {
const unsigned int i_nlink;
unsigned int __i_nlink;
};
dev_t i_rdev;//代表设备文件的节点,包含实际的设备编号
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes;
blkcnt_t i_blocks;
loff_t i_size;
/* Misc */
unsigned long i_state;
struct mutex i_mutex;
unsigned long dirtied_when; /* jiffies of first dirtying */
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct file_lock *i_flock;
struct address_space i_data;
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;//代表字符设备
};
void *i_private; /* fs or device private pointer */
};
3.4 字符设备注册
- 内核在内部使用类型
struct cdev
(<linux/cdev.h>
)的结构来代表字符设备;在内核调用设备操作前,需要分配并注册一个或几个这些结构 - 2中方法分配和初始化化这些结构
//获得一个独立的cdev结构
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
//将cdev结构嵌入自己的设备特定的结构
void cdev_init(struct cdev *cdev, struct file_operations *fops);
//告诉内核,num是这个设备响应的第一个设备号,count是应当关联到设备的设备号的数目
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
//为系统去除字符设备
void cdev_del(struct cdev *dev);
3.4.2 老方法
- 2.6之前内核版本使用
int register_chrdev(unsigned int major, const char *name, struct
file_operations *fops);
int unregister_chrdev(unsigned int major, const char *name);
3.5 open和release
3.5.1 open方法
- 检查设备特定的错误(例如,设备没准备好, 或者类似的硬件错误)
- 如果它第一次打开,初始化设备
- 如果需要,更新
f_op
指针 - 分配并填充要放进
filp->private_data
的任何数据结构
int (*open)(struct inode *inode, struct file *filp);
3.5.2 release方法
- 释放
open
分配在filp->private_data
中的任何东西 - 在最后的
close
关闭设备 - 不是每个
close
系统调用都会引起调用release
方法,只有真正释放设备数据结构的调用会调用这个方法,因此当一个设备文件关闭次数超过它被打开的次数时,并不会出现任何问题(内核会维持一个文件结构被使用多少次的计数) flush
方法在每次应用程序调用close
时都被调用
3.6 scull的内存使用
- 使用
kmalloc
和kfree
管理
void *kmalloc(size_t size, int flags);
void kfree(void *ptr);
- scull中每个设备是一个指针列表,指向一个
scull_dev
设备
3.7 读和写
filp
是文件指针,count
是请求的传输数据大小,buff
参数指向持有被写入数据的缓存,或者放入新数据的空缓存,offp
是一个指向long offset type
对象的指针,它指出用户正在存取的文件位置,返回值是一个signed size type
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);
read
和write
方法的buff
参数是用户空间指针,因此,它不能被内核代码直接解引用(内核空间与用户空间并不在一个RAM区)- 使用内核中拷贝函数进行拷贝
long copy_from_user(void *to,const void __user * from, unsigned long n);
long copy_to_user(void __user *to,const void *from, unsigned long n);
- 内核函数返回一个负数指示一个错误,这个数的值指出所发生的错误类型,用户空间运行的程序使用
errno
变量来查找问题,用户空间的行为由POSIX
标准来规定,但是这个标准没有规定内核内部如何操作
3.7.1 read方法
read
返回值由调用的应用程序说明:
- 如果这个值等于传递给
read
系统调用的count
参数,请求的字节数已经被传送 - 如果是正数,但是小于 count,只有部分数据被传送
- 如果值为0,到达了文件末尾(没有读取数据)
- 一个负值表示有一个错误,这个值指出了什么错误(
<linux/errno.h>
)
3.7.2 write方法
返回值说明
:
- 如果值等于
count
,要求的字节数已被传送 - 如果正值,但是小于 count,只有部分数据被传送,程序最可能重试写入剩下的数据
- 如果值为 0,什么没有写,这个结果不是一个错误
- 一个负值表示发生一个错误(
<linux/errno.h>
)
3.7.7 readv和writev方法
read
和write
的矢量版本,它们使用一个结构数组,每个包含一个缓存的指针和一个长度值;readv
调用被期望来轮流读取指示的数量到每个缓存,writev
要收集每个缓存的内容到一起并且作为单个写操作送出它们- 3.6.y源码中并未找到这两个函数,暂时跳过
3.8 scull编译测试
scull驱动保留了写给它的任何数据,直到新数据覆盖它,可以使用cp
、dd
测试该驱动
3.9 本章总结
dev_t
(#include <linux/types.h>
)在内核里代表设备号的类型#include <linux/fs.h>
是编写设备驱动最重要的头文件- 3个重要的数据结构
struct file_operations
、struct file
、struct inode
cdev
(#include <linux/cdev.h>
)代表内核中的字符设备#include <asm/uaccess.h>
内核代码使用的函数来移动数据到从用户空间- 本章主要介绍了设备编号以及编号的分配和释放,介绍了3种在驱动编写中非常重要的数据结构,以及设备如何注册,如何编写
open
、read
、write
、release
方法
3.10 scull示例
- 模块初始化
/*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 cdev结构体在内核中代表一个字符设备*/
};
/*初始化模块,申请分配设备编号,可通过insmod命令行指定一个值给scull_major(或修改代码中值),以动态或静态分配设备编号,scull_major为主设备编号*/
int scull_init_module(void)
{
int result, i;
dev_t dev = 0;//包含主次设备编号
/*注册设备(分配设备编号)*/
/*
* Get a range of minor numbers to work with, asking for a dynamic
* major unless directed otherwise at load time.
*/
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");//静态分配设备编号(from,count,name)
} 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_nr_devs个scull设备分配内存空间,并将分配得到的内存清0,内存分布图见3-6*/
/*
* allocate the devices -- we can't have them static, as the number
* can be specified at load time
*/
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_devices) {
result = -ENOMEM;
goto fail; /* Make this more graceful */
}
memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
/* Initialize each device. */
for (i = 0; i < scull_nr_devs; i++) {
scull_devices[i].quantum = scull_quantum;//scull设备当前“量子”大小
scull_devices[i].qset = scull_qset;//当前数组大小
init_MUTEX(&scull_devices[i].sem);//对每个scull设备的sem成员进行初始化
scull_setup_cdev(&scull_devices[i], i);//对scull设备的cdev成员变量初始化和注册
}
/* At this point call the init function for any friend device */
dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
dev += scull_p_init(dev);
dev += scull_access_init(dev);
#ifdef SCULL_DEBUG /* only when debugging */
scull_create_proc();
#endif
return 0; /* succeed */
fail:
scull_cleanup_module();
return result;
}
/*
* 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);//调用MKDEV宏得到设备编号(主设备编号一样,次设备编号分别为0~3)
cdev_init(&dev->cdev, &scull_fops);//调用cdev_init函数对cdev结构体进行初始化,指定对应的文件操作函数集是scull_fops(file_operations型变量)
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add (&dev->cdev, devno, 1);//将cdev结构体注册到内核
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}
- 指定设备文件操作函数集
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,
};
- 设备文件操作函数
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);//调用container_of宏,通过cdev成员得到包含该cdev的scull_dev结构
filp->private_data = dev; /* for other methods;将得到的scull_dev结构保存在filp->private_data中,因为open结束后,后面的read,write等操作使用同一个filp变量,它们即可以从filp->private_data中直接取出scull_dev结构体来使用*/
/* now trim to 0 the length of the device if open was write-only;如果scull设备文件是以只写的方式打开,则要调用scull_trim将scull设备清空*/
if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
if (down_interruptible(&dev->sem))//进行加锁解锁操作,进行互斥
return -ERESTARTSYS;
scull_trim(dev); /* ignore errors */
up(&dev->sem);
}
return 0; /* success */
}
/*
* Empty out the scull device; must be called with the device
* semaphore held.
*/
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);//释放scull_qset占用的内存空间
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->data = NULL;
return 0;
}
int scull_release(struct inode *inode, struct file *filp)
{
return 0;
}
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;//item代表要读的数据起始点在哪个scull_qset中
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);//返回item指定的scull_qset
if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
goto out; /* don't fill holes;如果指定的scull_qset不存在,或者量子指针数组不存在,或者量子不存在,都退出*/
/* 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;//读取完成后,新的文件指针位置向前移动count个字节
retval = count;//返回读取到的字节数
out:
up(&dev->sem);
return retval;
}
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;
}
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;//item代表要写入的位置在哪个scull_qset中
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;//代表要写入的位置在哪个量子中,q_pos代表要写入的位置在量子的具体位置
/* 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;//将文件指针后移count字节
retval = count;
/* update the size */
if (dev->size < *f_pos)
dev->size = *f_pos;
out:
up(&dev->sem);
return retval;
}
-
源码分析参考链接
-
备注
使用3.6的源码树编译是编译不过的,毕竟原代码是2.6版本