Linux驱动程序设计之入门篇

(转载请注明出处)


一. 明晰基础概念和工作方式

在说驱动之前要先明确一个概念,大家应该都知道,在linux中把一切东西都当作文件。

同样的,一个硬件也被当成一个文件,这些代表硬件的文件我们把它们叫做设备文件

设备文件被统一的放在/dev路径下面。
(用过mount命令的应该很清楚,mount就是将/dev下面的设备文件给挂载到我们的文件系统上)

写过程序的一般都很清楚,当我们打开普通文件之后可以读写,可以对文件作很多事情。

而现在我们面对的是一个设备文件,它代表的是一个硬件,那么读写之类各种操作是由什么东西来完成的呢?

答案是:驱动程序


其实驱动程序在做什么事情呢?答案很简单,就是实现c语言中那些对文件的操作函数。当用户程序调用这些操作函数如open(),read(),write()等等普通的glibc中的函数时,会自动将操作映射到驱动程序中对这些操作的实现。以此来实现对硬件的读写操作。

用户态程序对设备文件的操作的例子就像这样:

int fd;
char buff[10];
fd = open(“/dev/port”, O_RDWR);
write(fd, buff, 1);
close(fd);

大概代码就像这样。

二. 核心数据结构

现在要介绍一个在理解驱动程序设计架构时最重要的数据结构 – file_opeartions
(是我自己以为的,不同意见可以说…嘿嘿)

我不直接说这个数据结构,我用一段最简单的驱动程序的代码来说明。

int xxx_open(struct inode *inode, struct file *filp)
{
    //…
}

int xxx_release(struct inode *inode, struct file *filp)
{
    //…
}

ssize_t xxx_read(struct file *filp, char *buf, size_t count, loff_                   *f_pos)
{
    //…
}

struct file_operations xxx_fops =
{
    .owner = THIS_MODULE,
    .open = xxx_open,
    .release = xxx_release,
    .read = xxx_read;
};

一般聪明的人看了这个代码就会知道这个file_operations是个什么东西。

它定义了由驱动程序实现的一系列具体操作的函数指针。

我们写驱动程序就是去填这一个个的函数,并且把file_operations中的函数指针指向我们自己写的这些函数(我们把这种函数叫做回调函数(callback function))。

当然,file_operations中有很多很多的函数指针.

它的定义在linux的源代码的/include/linux/fs.h文件中,希望读者去看看,而且由于linux每一个版本间的变化,这个数据结构并不是不变的,很可能某一个函数指针在某一个版本就没有掉了。所以在实现自己的驱动程序之前,最好先去查看下这个代码。

三. 设备的注册与注销

代码很简单我写出来大家一看就知道

int xxx_init(void)
{
    register_chrdev(181, “temp_chr”, &xxx_fops);
    //…
}

void xxx_exit(void)
{
    unregister_chrdev(181, “temp_chr”);
    //…
}

moudle_init(xxx_init);
module_exit(xxx_exit);
//注,把上面的代码和这个代码合在一起就是一个完整的linux驱动程序。

register_chrdev这个函数的三个参数分别是:
1.指定的主设备号
2.我给设备的命名
3.file_operations结构

四. 如何编译驱动

Makefile文件就一行:
obj-m := temp_chr.o

编译命令make -C /usr/src/linux-2.6.31/ M=/temp_chr/ modules
(当然,你可以把命令直接写到Makefile文件中去,这样直接make一下就好了)

解释一下这个编译的参数.

第一个路径/usr/src/linux-2.6.31/这个是指定的linux源码存储的位置。
编译出来的驱动程序也只能适用于这个版本的linux内核。如果你要编译适用于其他版本内核的驱动程序,那么就先去下载一个那个版本的内核代码,然后把这个参数改成那个源码的路径就好了。

M=/temp_chr/这个路径是你的驱动程序的存储位置。
如果你就在该目录下面可以改成M=$(pwd)

编译好之后就会出现一个temp_chr.ko文件,这个就是我们编译好的驱动程序。

如果你的这个模块(驱动程序是以内核模块(kernel modules)存在于linux中的,那个.ko文件就是一个内核模块)包括了多个.c文件,那么Makefile文件就这样写:

obj-m := modulename.o
module-objs := file1.o file2.o

五. 如何加载驱动

命令很简单:insmod temp_chr.ko

由于我这个驱动用的是比较老的注册方式,所以不会自动创建设备文件,所以需要手动创建一个:

cd /dev
mknod temp_chr c 181 0

这样就好了,参数的意义
1.temp_chr是被创建设备文件的名字
2.c表示这个是字符设备
3.181是主设备号
4.0是次设备号

这里需要明晰一个概念:设备号

设备号由两个部分构成,主设备号次设备号

主设备号:主设备号标识了设备的类型,拥有相同主设备号的设备使用的驱动程序必定是相同的。(可以容易推出主设备号与驱动程序是一一对应的)

次设备号:次设备号区分了同一类型设备的不同实体,比如一台电脑可能有两块硬盘,一块硬盘的次设备号是1,而另一块硬盘的次设备号就绝对不是1。