linux 将设备驱动分成几大类: 字符 设备、杂项设备、块设备、网络设备······
本篇文章介绍杂项设备驱动的编写,杂项设备与字符设备本质上没什么区别,但是写法和相关函数的使用上有区别。
除此之外杂项设备主设备号都为10,设备间通过次设备号来进行区分,与字符设备相比节约了主设备号。
杂项设备驱动编写模式一般如下:
在linux系统下一切皆文件,设备驱动同样秉承此“”大法“”。
对文件操作就少不了打开、读写、关闭等操作。
所以杂项设备驱动第一步就是进行文件操作函数的编写。
驱动模块最终需要加载到内核上运行,内核对文件的操作在内部有标准的系统调用,
那么怎样将自己写的文件操作函数挂接到系统标准的函数调用上呢?
这就是文件操作集合这个结构体所要做的工作了。
首先看一下该结构体的结构
[cpp] view pl ai n copy
struct file_opera ti ons {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*wri te ) (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);
int (*readdir) (struct file *, void *, filldir_t);
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 pi pe_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);
};
其实我们仔细观察,该结构体里除了第一个成员外,其余的成员都是函数指针,当我们在进一步观察。原来他们当中很多都很熟悉。
有许多跟原来经常调用的文件操作函数很类似,其实他们作用就是将自己书写的文件操作函数与系统标准文件操作函数调用衔接起来。
也可以把他们说成是文件操作的函数的声明,只有在这个结构体里声明过的函数,在对驱动操作时才能使用该函数。
什么意思呢?
如果我们没有在这个结构体中声明read,也就是读取文件的函数,我们在调用该驱动模块时就不能对该模块进行读操作。
该结构体成员很多,我们不必全部都要声明,我们在实际使用时,用到那个写哪个就可以了。
例如我们只写一个简单的LED灯的驱动,只涉及简单的IO控制,因此其余的函数对我们没有意义,我们就可以不写。
对于第一个成员owner,它的定义有很多,在早期简单的驱动编写中,我们统一将它赋值“”THIS_MODULE“”。
可以防止驱动在运行中被意外卸载。至于其他用法自己可以在以后深入学习中慢慢探索。
写完文件操作集合,还有另外一个重要结构体 mi scdevice,它记录了该驱动设备的相关信息,为设备注册提供必要信息。
[cpp] view plain copy
struct miscdevice {
int minor;
const char *name;
const struct file_opera TI ons *fops;
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
umode_t mode;
};
对于我们比较关注的就是前三项
minor 次设备号,若果让系统自动分配设备号 应赋值为MISC_DUNAMIC_MINOR
name 设备名称
fops 操作集合的声明 将文件操作集合结构体地址赋值给它即可
将以上内容完成后,就是驱动的初始化和卸载函数的书写。
模块初始化函数中主要完成的内容,主要是进行杂项设备的注册。
模块卸载函数,主要是对杂项设备的卸载。
最后声明模块的初始化、卸载函数的入口。声明遵循的开源协议。
最后可编写一个控制函数验证驱动的正确性
下面附上一段示例代码,此段并没有实质性操作,只简单演示杂项设备驱动编写流程
[cpp] view plain copy
#include
#include
#include
#include
#include
//打开函数
sta TI c int misc_open(struct inode *node, struct file *fp)
{
printk("this dev is open\r\n");
return 0;
}
//关闭函数
sta TI c int misc_close(struct inode *node, struct file *fp)
{
printk("this dev is close\r\n");
return 0;
}
//读函数
ssize_t misc_read(struct file *fp, char __user *buf, size_t size, loff_t *loff)
{
printk("this dev is read\r\n");
return 0;
}
//写函数
ssize_t misc_write(struct file *fp, const char __user *buf, size_t size, loff_t *loff)
{
printk("this dev is write\r\n");
return 0;
}
[cpp] view plain copy
//文件操作集合
struct file_opera TI ons fops={
.owner=THIS_MODULE,
.open=misc_open,
.read=misc_read,
.write=misc_write,
.release=misc_close,
};
//设备相关信息
struct miscdevice mymisc={
.minor=MISC_DYNAMIC_MINOR,
.name="mymisc",
.fops=fops,
};
//驱动初始化
static int __init misc_init(void)
{
if((misc_register(&mymisc))
{
printk("this module is insmod fail\r\n");
return -1;
}
printk("this module is success\r\n");
return 0;
}
//驱动卸载
static void __exit misc_exit(void)
{
printk("this module is exit\r\n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
驱动控制函数代码
[cpp] view plain copy
#include
#include
#include
#include
#include
#include
//主函数 参数为:传入设备名称(含有路径)
int main(int argc,char *argv[])
{
int fd = open(argv[1],O_RDWR);
if(fd == -1)
{
printf("打开失败\n");
return 0;
}
read(fd,buf,0);
write(fd,NULL,0);
close(fd);
return 0;
}
驱动控制代码的作用主要是将,设备节点打开对其进行读写关闭等操作(这里没有实质操作,只打印一句话),只是来验证驱动框架的正确性
makefile代码如下:
[cpp] view plain copy
KERN_DIR = /zhangchao/linux3.5/linux-3.5
all:
make -C $(KERN_DIR) M=`pwd` modules
clean:
make -C $(KERN_DIR) M=`pwd` modules clean
rm -rf modules.order
obj-m += misc.o
编译模块
[cpp] view plain copy
[root@CentOS zhangchao]# make
make -C /zhangchao/linux3.5/linux-3.5 M=`pwd` modules
make[1]: 进入目录“/zhangchao/linux3.5/linux-3.5”
Building modules, stage 2.
MODPOST 1 modules
make[1]: 离开目录“/zhangchao/linux3.5/linux-3.5”
[root@CentOS zhangchao]# ls
Makefile misc.c misc.mod.c misc.o Module.symve rs
misc_app.c misc.ko misc.mod.o modules.order
[root@CentOS zhangchao]#
可以看到.ko模块文件已经生成
安装模块
[cpp] view plain copy
[root@ZC/zhangchao]#insmod misc.ko
[ 857.560000] this module is success
打印相关信息模块安装成功
查看模块
[cpp] view plain copy
[root@ZC/zhangchao]#lsmod
Module Size Used by Tainted: G
misc 1440 0
模块信息存在,模块已经成功安装
编译驱动控制代码
[cpp] view plain copy
[root@CentOS zhangchao]# arm -linux-gcc misc_app.c -o run
[root@CentOS zhangchao]# ls
main.c man misc.c misc.mod.c misc.o Module.symvers
Makefile misc_app.c misc.ko misc.mod.o modules.order run
编译没有报错,生成了可执行文件 run
查看设备
[cpp] view plain copy
[root@ZC/zhangchao]#ls -al /dev/mymisc
crw-rw---- 1 root root 10, 47 Mar 16 17:00 /dev/mymisc
设备已经存在,证明杂项设备注册成功,在设备列表内部能够查看到设备。主设备号10 次设备号47
运行控制 程序,传入参数为注册的设备节点,控制程序可以通过设备节点对设备进行读写等操作。(在开发板上执行)
[cpp] view plain copy
[root@ZC/zhangchao]#./run /dev/mymisc
[ 1487.635000] this dev is open
[ 1487.635000] this dev is read
[ 1487.635000] this dev is write
[ 1487.635000] this dev is close
[root@ZC/zhangchao]#
驱动卸载
[cpp] view plain copy
[root@ZC/zhangchao]#rmmod misc.ko
[ 1577.995000] this module is exit
模块编译,控制程序的编译在宿主机上进行,开发板上只有内核,没有编译工具,模块的装载、查看、卸载在开发板上运行。
宿主机: VMware12版本下的CentOS7
开发板: 友善之臂Tiny4412开发板
交叉编译器: arm-linux-gcc 4.5.1
内核版本:linux-3.5