【原创】《Linux设备驱动程序》学习之循序渐进 --- 字符设备驱动


【原创】《Linux设备驱动程序》学习之循序渐进 --- 字符设备驱动


第三章 --- 字符设备驱动程序

本章介绍的一些代码段,取自一个真正的设备驱动程序:scull,即“Simple Character Utility for Loading Locality,区域装载的简单字符工具”的缩写。scull是一个操作内存区域的字符设备驱动程序,这片内存区域就相当于一个设备。scull的优点在于它和硬件无关,只是操作从内核中分配的一些内存。

对字符设备的访问是通过文件系统内的设备名称进行的。那些名称被称为特殊文件、设备文件,或者简单称之为文件系统树的节点,它们通常位于/dev目录。字符设备驱动程序的设备文件可通过ls -l命令输出的第一列中的“c”来识别。块设备也出现在/dev下,但他们由字符“b”标识。

通常而言,主设备号标识设备对应的驱动程序。次设备号由内核使用,用于正确确定设备文件所指的设备。

如果使用驱动程序的人只有我们自己,则选定一个编号的方法永远行得通;然而,一旦驱动程序被广泛使用,随机选定的主设备号可能造成冲突和麻烦。因此,对于一个新的驱动程序,我们强烈建议读者不要随便选一个当前未使用的设备好作为主设备号,二应该使用动态分配机制获得主设备号。换句话说,驱动程序应该始终使用alloc_chrdev_region而不是register_chrdev_region函数。

因此,为了加载一个使用动态主设备号的设备驱动程序,对insmod的调用可替换为一个简单的脚本,该脚本在调用insmod之后读取/proc/devices以获取新分配的主设备号,然后创建对应的设备文件。

scull_load脚本文件来自于书中的示例,用来装载模块,稍加改变即可用在其他驱动程序,具体含义可参考书中51页,代码如下:

<span style="font-size:18px;">#!/bin/sh
# $Id: scull_load,v 1.4 2004/11/03 06:19:49 rubini Exp $
module="scull"
device="scull"
mode="664"

# Group: since distributions do it differently, look for wheel or use staff
if grep -q '^staff:' /etc/group; then
    group="staff"
else
    group="wheel"
fi

# invoke insmod with all arguments we got
# and use a pathname, as insmod doesn't look in . by default
/sbin/insmod ./$module.ko $* || exit 1

# retrieve major number
major=$(awk "\\$2==\"$module\" {print \\$1}" /proc/devices)

# Remove stale nodes and replace them, then give gid and perms
# Usually the script is shorter, it's scull that has several devices in it.

rm -f /dev/${device}[0-3]
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
ln -sf ${device}0 /dev/${device}
chgrp $group /dev/${device}[0-3] 
chmod $mode  /dev/${device}[0-3]

rm -f /dev/${device}pipe[0-3]
mknod /dev/${device}pipe0 c $major 4
mknod /dev/${device}pipe1 c $major 5
mknod /dev/${device}pipe2 c $major 6
mknod /dev/${device}pipe3 c $major 7
ln -sf ${device}pipe0 /dev/${device}pipe
chgrp $group /dev/${device}pipe[0-3] 
chmod $mode  /dev/${device}pipe[0-3]

rm -f /dev/${device}single
mknod /dev/${device}single  c $major 8
chgrp $group /dev/${device}single
chmod $mode  /dev/${device}single

rm -f /dev/${device}uid
mknod /dev/${device}uid   c $major 9
chgrp $group /dev/${device}uid
chmod $mode  /dev/${device}uid

rm -f /dev/${device}wuid
mknod /dev/${device}wuid  c $major 10
chgrp $group /dev/${device}wuid
chmod $mode  /dev/${device}wuid

rm -f /dev/${device}priv
mknod /dev/${device}priv  c $major 11
chgrp $group /dev/${device}priv
chmod $mode  /dev/${device}priv
</span>


scull_unload脚本文件来自于书中的示例,用来卸载模块,稍加改变即可用在其他驱动程序,代码如下:

<span style="font-size:18px;">#!/bin/sh
module="scull"
device="scull"

# invoke rmmod with all arguments we got
/sbin/rmmod $module $* || exit 1

# Remove stale nodes

rm -f /dev/${device} /dev/${device}[0-3] 
rm -f /dev/${device}priv
rm -f /dev/${device}pipe /dev/${device}pipe[0-3]
rm -f /dev/${device}single
rm -f /dev/${device}uid
rm -f /dev/${device}wuid
</span>

file_operations结构,定义在<linux/fs.h>,其实是在linux-2.6.10/include/linux/fs.h中。其中各个成员的含义和使用参考书中54页。

<span style="font-size:18px;">/*
 * NOTE:
 * read, write, poll, fsync, readv, writev can be called
 *   without the big kernel lock held in all filesystems.
 */
struct file_operations {
	struct module *owner;
	loff_t (*llseek) (struct file *, loff_t, int);
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
	ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
	ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);
	int (*readdir) (struct file *, void *, filldir_t);
	unsigned int (*poll) (struct file *, struct poll_table_struct *);
	int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
	int (*mmap) (struct file *, struct vm_area_struct *);
	int (*open) (struct inode *, struct file *);
	int (*flush) (struct file *);
	int (*release) (struct inode *, struct file *);
	int (*fsync) (struct file *, struct dentry *, int datasync);
	int (*aio_fsync) (struct kiocb *, int datasync);
	int (*fasync) (int, struct file *, int);
	int (*lock) (struct file *, int, struct file_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 *);
	ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
	int (*check_flags)(int);
	int (*dir_notify)(struct file *filp, unsigned long arg);
	int (*flock) (struct file *, int, struct file_lock *);
};</span>

我们会注意到有许多参数会含有__user字符串,它其实是一种形式的文档而已,表明指针是一个用户空间地址,因此不能被直接引用。对通常的编译来说,__user并没有任何效果,但是可由外部检查软件使用,用来寻找对用户空间地址的错误使用。

scull设备驱动程序所实现的只是最重要的设备的方法,它的file_operations被初始化为如下形式:

<span style="font-size:18px;">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,
};</span>

file结构,定义在<linux/fs.h>,其实是在linux-2.6.10/include/linux/fs.h中。file结构代表一个打开的文件,与用户空间程序中的FILE没有任何关联。其中重要成员的含义和使用参考书中57页。

<span style="font-size:18px;">struct file {
	struct list_head	f_list;
	struct dentry		*f_dentry;
	struct vfsmount         *f_vfsmnt;
	struct file_operations	*f_op;
	atomic_t		f_count;
	unsigned int 		f_flags;
	mode_t			f_mode;
	int			f_error;
	loff_t			f_pos;
	struct fown_struct	f_owner;
	unsigned int		f_uid, f_gid;
	struct file_ra_state	f_ra;

	unsigned long		f_version;
	void			*f_security;

	/* needed for tty driver, and maybe others */
	void			*private_data;

#ifdef CONFIG_EPOLL
	/* Used by fs/eventpoll.c to link all the hooks to this file */
	struct list_head	f_ep_links;
	spinlock_t		f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
	struct address_space	*f_mapping;
};</span>

inode结构,定义在<linux/fs.h>,其实是在linux-2.6.10/include/linux/fs.h中。

<span style="font-size:18px;">struct inode {
	struct hlist_node	i_hash;
	struct list_head	i_list;
	struct list_head	i_dentry;
	unsigned long		i_ino;
	atomic_t		i_count;
	umode_t			i_mode;
	unsigned int		i_nlink;
	uid_t			i_uid;
	gid_t			i_gid;
	dev_t			i_rdev;
	loff_t			i_size;
	struct timespec		i_atime;
	struct timespec		i_mtime;
	struct timespec		i_ctime;
	unsigned int		i_blkbits;
	unsigned long		i_blksize;
	unsigned long		i_version;
	unsigned long		i_blocks;
	unsigned short          i_bytes;
	unsigned char		i_sock;
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	struct semaphore	i_sem;
	struct rw_semaphore	i_alloc_sem;
	struct inode_operations	*i_op;
	struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
	struct super_block	*i_sb;
	struct file_lock	*i_flock;
	struct address_space	*i_mapping;
	struct address_space	i_data;
#ifdef CONFIG_QUOTA
	struct dquot		*i_dquot[MAXQUOTAS];
#endif
	/* These three should probably be a union */
	struct list_head	i_devices;
	struct pipe_inode_info	*i_pipe;
	struct block_device	*i_bdev;
	struct cdev		*i_cdev;
	int			i_cindex;

	__u32			i_generation;

#ifdef CONFIG_DNOTIFY
	unsigned long		i_dnotify_mask; /* Directory notify events */
	struct dnotify_struct	*i_dnotify; /* for directory notifications */
#endif

	unsigned long		i_state;
	unsigned long		dirtied_when;	/* jiffies of first dirtying */

	unsigned int		i_flags;

	atomic_t		i_writecount;
	void			*i_security;
	union {
		void		*generic_ip;
	} u;
#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t		i_size_seqcount;
#endif
};</span>

内核内部使用struct cdev表示字符设备。

scull中的设备结构定义

<span style="font-size:18px;">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		*/
};</span>

scull中的设备结构注册

<span style="font-size:18px;">/*
 * 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);
    
	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);
}
</span>
实际的设备方法,read方法的任务是从设备拷贝数据到用户空间(使用copy_to_user),而write方法则是从用户空间拷贝数据到设备上(使用copy_from_user)。
copy_to_user和copy_from_user这两个函数的作用并不限于在内核空间和用户空间之间拷贝数据,它们还检查用户空间的指针是否有效。如果指针无效,就不会进行拷贝。另一方面,如果在拷贝的过程中遇到无效地址,则仅仅会复制部分数据。如果不需要检查用户空间指针,那么建议调用__copy_to_user和__copy_from_user。

scull_read和scull_write方法

<span style="font-size:18px;">/*
 * Data management: read and write
 */

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

	if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
		goto out; /* don't fill holes */

	/* 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;
	retval = count;

  out:
	up(&dev->sem);
	return retval;
}


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;
	rest = (long)*f_pos % itemsize;
	s_pos = rest / quantum; q_pos = rest % quantum;

	/* 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;
	retval = count;

        /* update the size */
	if (dev->size < *f_pos)
		dev->size = *f_pos;

  out:
	up(&dev->sem);
	return retval;
}</span>

快速参考

#include <linux/types.h> 
dev_t  
dev_t 是用来在内核里代表设备号的类型. 包括主设备号和次设备号。在内核2.6.10版本中,dev_t 是一个32位的数,其中12位用来表示主设备号,其余20位用来表示次设备号。我们应该使用使用<linux/kdev_t.h>中定义的下面两个宏来获得dev_t的主次设备号。
int MAJOR(dev_t dev); 
int MINOR(dev_t dev); 
从设备编号中抽取主次编号的宏. 即将主设备号和次设备号转换成dev_t类型,则使用:
dev_t MKDEV(unsigned int major, unsigned int minor); 从主次编号来建立 dev_t 数据项的宏定义. 
#include <linux/fs.h> 
"文件系统"头文件是编写设备驱动需要的头文件. 许多重要的函数和数据
结构在此定义. 
int register_chrdev_region(dev_t first, unsigned int count, char *name) 
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name) 

void unregister_chrdev_region(dev_t first, unsigned int count); 
允许驱动分配和释放设备编号的范围的函数. register_chrdev_region 应
当用在事先知道需要的主编号时; 对于动态分配, 使用 
alloc_chrdev_region 代替. 
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops); 
老的( 2.6 之前) 字符设备注册函数. 它在 2.6 内核中被模拟, 但是不应
当给新代码使用. 如果主编号不是 0, 可以不变地用它; 否则一个动态编
号被分配给这个设备. 
int unregister_chrdev(unsigned int major, const char *name); 
恢复一个由 register_chrdev 所作的注册的函数. major 和 name 字符串
必须包含之前用来注册设备时同样的值. 
struct file_operations; 
struct file; 
struct inode; 
大部分设备驱动使用的 3 个重要数据结构. file_operations 结构持有一
个字符驱动的方法; struct file 代表一个打开的文件, struct inode 代
表磁盘上的一个文件. 
#include <linux/cdev.h> 
struct cdev *cdev_alloc(void); 
void cdev_init(struct cdev *dev, struct file_operations *fops); 
int cdev_add(struct cdev *dev, dev_t num, unsigned int count); 
void cdev_del(struct cdev *dev); 
cdev 结构管理的函数, 它代表内核中的字符设备. 
#include <linux/kernel.h> 
container_of(pointer, type, field); 
一个传统宏定义, 可用来获取一个结构指针, 从它里面包含的某个其他结
构的指针. 
#include <asm/uaccess.h> 
这个包含文件声明内核代码使用的函数来移动数据到和从用户空间. 
unsigned long copy_from_user (void *to, const void *from, unsigned long count); 
unsigned long copy_to_user (void *to, const void *from, unsigned long count); 

在用户空间和内核空间拷贝数据. 


原文链接http://blog.csdn.net/geng823/article/details/37331389

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux设备驱动程序是用于控制和管理硬件设备的软件模块。学习Linux设备驱动程序可以帮助开发人员理解和掌握Linux内核的工作原理,以及如何编写和调试设备驱动程序。 以下是一些学习Linux设备驱动程序的笔记和建议: 1. 理解Linux设备模型:Linux设备模型是一种用于管理设备的框架,它提供了一种统一的方式来表示和操作设备学习Linux设备模型可以帮助你理解设备的注册、初始化和销毁过程。 2. 学习字符设备驱动程序字符设备是一种以字节为单位进行读写的设备,如串口、终端等。学习字符设备驱动程序可以帮助你了解字符设备的打开、关闭、读写等操作,并学习如何实现设备文件的注册和操作。 3. 学习设备驱动程序:块设备是一种以块为单位进行读写的设备,如硬盘、闪存等。学习设备驱动程序可以帮助你了解块设备的分区、缓存、IO调度等操作,并学习如何实现块设备的注册和操作。 4. 学习中断处理:中断是设备向处理器发送信号的一种机制,用于通知处理器设备的状态变化。学习中断处理可以帮助你了解中断的注册、处理和释放过程,并学习如何编写中断处理程序。 5. 学习设备驱动程序的调试技巧:设备驱动程序的调试是一个重要的技能,可以帮助你快速定位和解决问题。学习设备驱动程序的调试技巧可以帮助你理解和使用调试工具,如 printk、kprobe等。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值