Linux设备驱动程序读书笔记(3)

第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_regionfrom为要分配的起始设备编号,count为请求的连续设备编号总数(不宜过大,会溢出占用下一个次编号),name为连接到这个编号范围的设备名字(会出现在/proc/devicessysfs中)
//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_regiondev为函数成功完成时分配范围的第一个数,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_operationsfileinode

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的内存使用

  • 使用kmallockfree管理
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);
  • readwrite方法的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方法

  • readwrite的矢量版本,它们使用一个结构数组,每个包含一个缓存的指针和一个长度值;readv调用被期望来轮流读取指示的数量到每个缓存,writev要收集每个缓存的内容到一起并且作为单个写操作送出它们
  • 3.6.y源码中并未找到这两个函数,暂时跳过

3.8 scull编译测试

scull驱动保留了写给它的任何数据,直到新数据覆盖它,可以使用cpdd测试该驱动

3.9 本章总结

  • dev_t#include <linux/types.h>)在内核里代表设备号的类型
  • #include <linux/fs.h>是编写设备驱动最重要的头文件
  • 3个重要的数据结构struct file_operationsstruct filestruct inode
  • cdev#include <linux/cdev.h>)代表内核中的字符设备
  • #include <asm/uaccess.h>内核代码使用的函数来移动数据到从用户空间
  • 本章主要介绍了设备编号以及编号的分配和释放,介绍了3种在驱动编写中非常重要的数据结构,以及设备如何注册,如何编写openreadwriterelease方法

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值