从事Linux开发也有几年时间了,期间也写了一些比较简单的驱动,但一直没有做系统的整理,今天终于想静下心来做一些整理,首先就从最基本的字符设备驱动开始。先贴出一个简单的字符设备驱动,在后面做简单说明:
char_driver.c
#include
#include
#include
#include
#include
#include
#include
static int char_driver_open(struct inode *inode, struct file *filp)
{
printk(KERN_EMERG"open dev:%d\n", iminor(inode));
return 0;
}
static int char_driver_release(struct inode *inode, struct file *filp)
{
printk(KERN_EMERG"close dev:%d\n", MINOR(inode->i_rdev));
return 0;
}
static struct file_operations char_driver_fops = {
owner: THIS_MODULE,
open: char_driver_open,
release: char_driver_release,
};
static struct miscdevice misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "char_driver",
.fops = &char_driver_fops,
};
static int __init char_driver_init(void)
{
int ret;
ret = misc_register(&misc);
printk(KERN_EMERG"hello driver dev_init!\n");
return ret;
}
static void __exit char_driver_exit(void)
{
misc_deregister(&misc);
printk(KERN_EMERG"hello driver dev_exit!\n");
}
MODULE_LICENSE("GPL");
module_init(char_driver_init);
module_exit(char_driver_exit);
本例子中的头文件是每个Linux设备驱动必须包含的,也没有太多可解释的,复制粘贴就行了。
struct file_operations结构体是Linux设备驱动中非常重要的一个结构体,主要用来存储驱动内核模块提供的对设备进行各种操作的函数的指针。该结构体的每个域都对应着驱动内核模块用来处理某个被请求的 事务的函数的地址。其中本例子中用到了三个成员,如下:
struct module *owner;
内核使用这个字段避免内核模块正在使用时被卸载,几乎所有的情况下该字段都会被初始化为THIS_MODULE。
int (*open) (struct inode *, struct file *);
open为设备驱动执行的第一个操作,通常做一些初始化工作。但open并不一定是必须要声明的,如果没有声明,设备打开的操作永远成功,但系统不会通知驱动。
int (*release) (struct inode *, struct file *);
release是在进程调用close时调用的,用于释放一些系统资源。但注意的是并不是每次调用close都会调用release,系统会在关闭所有的设备描述符之后才调用release。
struct file结构体是一个内核结构,不会出现在用户程序中,与用户控件的FILE没有任何关系。系统中每个打开的文件在内核控件都有一个对应的file结构,它由内核在open是创建,并传递给在该文件上进行操作的所有函数。在调用release的时候内核会释放这个数据结构。struct file比较常用的几个字段如下:
loff_t f_pos;
用于记录当前的读/写位置。loff_t 是一个64位的数。在驱动程序中只有方法llseek可以修改它,其他方法只可以读取它的值。read/write方法也会影响此值,但不是直接去修改filep->f_pos
,而是通过修改其参数中最后那个指针loff_t * ppos
来达到修改f_pos
的目的,如下所示:
static ssize_t mem_write(struct file *filp, const char __user *buffer, size_t count, loff_t * ppos)
{
struct mem_dev *my_dev;
int write_size;
my_dev=filp->private_data;
if(count > MEM_SIZE - *ppos){
write_size = MEM_SIZE - *ppos;
}else{
write_size = count;
}
copy_from_user(my_dev->data + filp->f_pos, buffer, write_size);
*ppos += count;
return write_size;
}
unsigned int f_flag;
文件标志,比如_O_RDONLY
、O_NONBLOCK
等。在驱动程序中经常使用O_NONBLOCK
来检查用户请求是否为非阻塞式调用。其他标志很少用到。
void *private_data;
驱动程序可以将这个字段用于任何目的或者忽略这个字段。private_data
是跨系统调用时保存状态信息非常有用的资源。
struct miscdevice
是注册混杂字符设备用到的结构体,用法比较简单,这里不多做说明,根据示例使用即可。个人觉得混杂字符设备比较方便,不需要自己手动指定设备号,系统会自动分配,减去了平时开发调试中很多麻烦。
驱动模块在加载和卸载时都需要调用相应的方法,需要通过下面的宏来指定相应的方法:
module_init(char_driver_init); // 指定加载模块时调用方法
module_exit(char_driver_exit); // 指定卸载模块时调用方法
当然函数char_driver_init
和char_driver_exit
也要区别于其他函数,要分别用__init
和__exit
来修饰,如下所示:
static int __init char_driver_init(void)
{
int ret;
ret = misc_register(&misc);
return ret;
}
static void __exit char_driver_exit(void)
{
misc_deregister(&misc);
}
至此,这个简单的字符设备模型用到的一些知识点大概都说到了,下面为Makefile文件:
Makefile
ifneq ($(KERNELRELEASE),)
obj-m := char_driver.o
else
KDIR := /lib/modules/$(shell uname -r)/build
all:
make -C $(KDIR) M=$(shell pwd) modules
clean:
rm -f *.ko *.o *.mod.o *.mod.c *.symvers modul*
endif
还有测试app:
app.c
#include
#include
#include
#include
#include
#include
#include
int main(int argc, char *argv[])
{
int fd;
fd = open("/dev/char_driver", O_RDWR);
if(0 > fd){
perror("can not open 6410_led:");
}
close(fd);
return 0;
}