linux设备驱动第三篇:如何实现简单的字符设备驱动

在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动。本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存。

下面就开始学习如何写一个简单的字符设备驱动。首先我们来分解一下字符设备驱动都有那些结构或者方法组成,也就是说实现一个可以使用的字符设备驱动我们必须做些什么工作。

1、主设备号和次设备号

对于字符设备的访问是通过文件系统中的设备名称进行的。他们通常位于/dev目录下。如下:

xxx@ubuntu:~$ ls -l /dev/total 0brw-rw—- 1 root disk7, 0 3月 25 10:34 loop0brw-rw—- 1 root disk7, 1 3月 25 10:34 loop1brw-rw—- 1 root disk7, 2 3月 25 10:34 loop2crw-rw-rw- 1 root tty5, 0 3月 25 12:48 ttycrw–w—- 1 root tty4, 0 3月 25 10:34 tty0crw-rw—- 1 root tty4, 1 3月 25 10:34 tty1crw–w—- 1 root tty4, 10 3月 25 10:34 tty10

其中b代表块设备,c代表字符设备。对于普通文件来说,ls -l会列出文件的长度,而对于设备文件来说,上面的7,5,4等代表的是对应设备的主设备号,而后面的0,1,2,10等则是对应设备的次设备号。那么主设备号和次设备号分别代表什么意义呢?一般情况下,可以这样理解,主设备号标识设备对应的驱动程序,也就是说1个主设备号对应一个驱动程序。当然,现在也有多个驱动程序共享主设备号的情况。而次设备号有内核使用,用于确定/dev下的设备文件对应的具体设备。举一个例子,虚拟控制台和串口终端有驱动程序4管理,而不同的终端分别有不同的次设备号。

1.1、设备编号的表达

在内核中,dev_t用来保存设备编号,包括主设备号和次设备号。在2.6的内核版本种,dev_t是一个32位的数,其中12位用来表示主设备号,其余20位用来标识次设备号。

通过dev_t获取主设备号和次设备号使用下面的宏:

MAJOR(dev_t dev);

MINOR(dev_t dev);

相反,通过主设备号和次设备号转换为dev_t类型使用:

MKDEV(int major, int minor);

1.2、分配和释放设备编号

在构建一个字符设备之前,驱动程序首先要获得一个或者多个设备编号,这类似一个营业执照,有了营业执照才在内核中正常工作营业。完成此工作的函数是:

int register_chrdev_region(dev_t first, unsigned int count, const char *name);first是要分配的设备编号范围的起始值。count是连续设备的编号的个数。name是和该设备编号范围关联的设备名称,他将出现在/proc/devices和sysfs中。此函数成功返回0,失败返回负的错误码。此函数是在已知主设备号的情况下使用,在未知主设备号的情况下,我们使用下面的函数:int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, const char *name);

dev用于输出申请到的设备编号,firstminor要使用的第一个此设备编号。

在不使用时需要释放这些设备编号,已提供其他设备程序使用:

void unregister_chrdev_region(dev_t dev, unsigned int count);

函数多在模块的清除函数中调用。

分配到设备编号之后,我们只是拿到了营业执照,虽说现在已经准备的差不多了,但是我们只是从内核中申请到了设备号,应用程序还是不能对此设备作任何事情,我们需要一个简单的函数来把设备编号和此设备能实现的功能连接起来,这样我们的模块才能提供具体的功能.这个操作很简单,稍后就会提到,在此之前先介绍几个重要的数据结构。

2、重要的数据结构

注册设备编号仅仅是完成一个字符设备驱动的第一步。下面介绍大部分驱动都会包含的三个重要的内核的数据结构。

2.1、文件操作file_operations

file_operations是第一个重要的结构,定义在 <linux/fs.h>, 是一个函数指针的集合,设备所能提供的功能大部分都由此结构提供。这些操作也是设备相关的系统调用的具体实现。此结构的具体实现如下所示:

struct file_operations {//它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULEstruct module *owner;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, loff_t *);ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);int (*iterate) (struct file *, struct dir_context *);unsigned int (*poll) (struct file *, struct poll_table_struct *);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, loff_t, loff_t, 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 (*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 (*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);int (*show_fdinfo)(struct seq_file *m, struct file *f);};

需要说明的是这里面的函数在驱动中不用全部实现,不支持的操作留置为NULL。

2.2、文件结构struct file用开怀的笑容去迎接每一个黎明,

linux设备驱动第三篇:如何实现简单的字符设备驱动

相关文章:

你感兴趣的文章:

标签云: