字符驱动

本章介绍以scullc为例,如何写一个完整的字符驱动。详细说明请参考ldd3 chap3


the design of scullc

在设置一个驱动之前,首先得知道驱动相关的硬件有那些功能,我们的驱动要提供哪些能力。

scullc提供一段内存区域,用于模式字符设备的读写。


major and minor numbers

字符设备的访问 可以通过在相应的文件系统中查找其名字。其通常都位于 /dev 目录下。

可以通过 ls -l 命令看出字符设备 跟普通文件的一些差异:通常在 最后一次访问时间之前,普通文件显示的是文件的大小;而字符设备是主次设备号,之间以逗号分割。

主设备号 用于区分何种设备;次设备号 用于内核在“同种设备中”访问我们的设备。


the internal representation of device numbers

内核内部用dev_t 表示设备的设备号(定义在 linux/types.h 中,包括主次设备号。)在2.6.11内核中,dev_t 是一个32位的整形:其中高12位表示主设备号,低20位表示次设备号。

但为了代码的可移植性,我们不应做此假设。我们可以通过下面接口来获取其主次设备号。(定义在 linux/kdev_t.h)

MAJOR(dev_t dev);

MINOR(dev_t dev);

你也可以通过主次设备号 来获取设备号

MKDEV(int major, int minor);


allocating and freeing device numbers

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

成功返回0,失败返回负整数。

分配成功后,设备的主设备号,name可以 从/proc/devices 中查看。

void unregister_chrdev_region(dev_t first, unsigned int count);

上面的函数只是分配了设备的主次设备号,并没有实现应用层访问设备的具体操作。


some important data structures

大部分设备的具体操作都涉及到3个重要的内核数据结构:file_operations, file 和 inode。


file operations <linux/fs.h>

到目前为止,我们还未涉及 对我们预留分配的设备号的操作。而fops 正式如此。

struct module *owner; //这个域用于防止 设备在使用时 被卸载,通常初始化为 THIS_MODULE

loff_t (*llseek) (struct file*, loff_t, int);

ssize_t (*read)(struct file*, char __user *, size_t, loff_t *);

ssize_t (*write)(struct file*, const char __user*, size_t, loft_t*);

int (*readdir)(struct file*, void *, filldir_t); //只对具体的文件系统有用,设备文件通常赋为NULL;

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 *); //如果为NULL, mmap系统调用返回 -ENODEV。

int (*open)(struct inode*, struct file*);

int (*flush)(struct file*);

int (*release)(struct in ode*, struct file*);

scullc设备驱动只实现一些最重要的设备方法。初始化代码如下:

struct file_operations scull_fops = {
	.owner = THIS_MODULE,
	.llseek = scullc_llseek,
	.read = scullc_read,
	.write = scullc_write,
	.ioctl = scullc_ioctl,
	.open = scullc_open,
	.release = scullc_release,
};

the file structure <linux/fs.h>

文件结构代表着一个打开的文件。当文件“完全关闭时”释放该数据结构。

mode_t mode; //FMODE_READ, FMODE_WRITE;当涉及到open 或者 ioctl 时可能需要检测该标志位的读写权限,而read和write时,因为内核已经在调用相应的方法前检测过了,所以。。。

loff_t pos;

unsigned int f_flags; //O_RDONLY, O_NONBLOCK, and O_SYNC.

struct file_operations *f_op; //文件相关的操作。在open 设备时进行赋值。

void *private_data; //open系统调用会在 涉及具体设备的open方法之前 将该域设为NULL,你可以将该域另作它用或者不使用该域。当有一点:如果你将该域指向一块分配的区域,在相应的release方法中要记得释放该区域。

struct dentry *f_dentry; //文件所在的目录。设备文件通常不设置目录结构,除了通过filp->f_dentry->d_inode访问inode节点。


the inode structure

与file结构代表一个打开的文件描述符不同,inode 代表着一个文件。一个文件可以有很多打开的文件描述符,但只有一个inode节点。

dev_t i_rdev; //相应文件的设备号。

struct cdev *i_cdev; //当inode指向一个字符设备时,该域指向相应的字符设备。


char device registeration

在内核调用你的字符设备操作之前,你必须注册你的字符设备。首先,必须包含<linux/cdev.h>头文件。

有两种方式分配和初始化字符设备:

(1)如果你想在运行时分配一个cdev结构,你可以这样实现

struct cdev *my_cdev =  cdev_alloc();
my_cdev->ops = &my_fops;
(2)如果你想在相应设备中嵌入cdev结构,可以调用下面接口

void cdev_init(struct cdev *cdev, struct file_operations *fops);


一旦建立起 cdev,最终要通知内核,这里有一个字符设备需要加入。

int cdev_add(struct cddv *cdev, dev_t num, unsigned int count);

关于cdev_add 有两点需要注意的地方:(1)cdev_add 可能失败;(2)一旦cdev_add 返回成功,你的设备就被“激活”了,可能会立即受到访问。


卸载设备时需要将cdev从系统中移除:

void cdev_del(struct cdev *dev);

一旦调用该接口后就不应该再访问此设备了。


device registration in scullc

scullc 数据结构

struct scullc_dev {
	void **data;		/*pointer to the first quantum set*/
	int quantum;		/*the current quantum size*/
	int qset;		/*the current array size*/
	unsigned long size; 	/*amount of data stored here*/
	struct semaphore sem;	/*mutual exclusion semaphore*/
	struct cdev cdev;	/*char device struct*/
};

先忽视该数据结构中的其他元素,来看一下cdev如何初始化以及添加到内核的。代码如下:

static void scullc_setup_cdev(struct scullc_dev *dev, int index)
{
	int err, devno = MKDEV(scullc_major, scullc_minor + index);
	cdev_init(&dev->cdev, &scullc_fops);
	dev->cdev.owner = THIS_MODULE;
	err = cdev_add(&dev->cddv, devno, 1);
	if(err)
		printk(KERN_NOTICE "Error %d adding scull%d\n", err, index);
}
自我感觉这段代码写的并不好:原因在于如果部分 cdev cdev_add 失败,应该撤销之前添加成功的cdev。而此段代码明显不能。


the older way

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

void unregister_chrdev(unsigned int major, const char *name); 

这两个接口只是对 cdev_init, cdev_add 以及 cddv_del 的提取和封装。


下面涉及具体的文件操作。

open and release

the open method

open方法 的作用主要用于 对设备驱动进行初始化,为之后的其他方法调用提供准备工作。主要执行的任务如下:

(1)检测设备相关的错误(如设备是否已经激活 或相似的硬件问题)

(2)第一次打开设备需要进行的初始化

(3)必要时更新f_op指针

(4)初始化filp->private_data域

原型:

int (*open)(struct inode *inode, struct file *filp);

scullc_open 简单实现如下:

int scullc_open(struct inode *inode, struct file *filp)
{
	struct scullc_dev dev = container_of(&inode->i_cdev, struct scullc_dev, cdev);
	filp->private_data = dev;	/*for other method*/

	/*now trim to 0 the length of the device if open for write-only*/
	if( (fill->f_flags & O_ACCMODE) == O_RDONLY)	{
		scullc_trim(dev); 		/*ignore errors*/
	}

	return 0;
}

因为scullc不涉及到具体的硬件,所以没有上面列出的(1),(2)。


the release method

release方法跟open的角色相反。主要任务包括:

(1)释放在open中分配的 filp->private_data

(2)最后一次关闭设备时 “shut down the device”

scullc的release非常简单,仅仅是返回0.


因为scullc仅仅涉及对分配的内存区域的读写操作,不具有一般设备的代表性。所以scullc的读写及内存接口此处不作涉及。有兴趣的可以自己查看内核代码中字符设备的读写实现。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值