本文共 5625 字,大约阅读时间需要 18 分钟。
该系列是我在学习宋宝华老师的《Linux设备驱动开发详解》以及结合其他网上教程所做记录。
1. Linux设备
在Linux操作系统下的设备通常分为三类:
字符设备、块设备和网络设备。
字符设备是以字节为单位逐个进行I/O操作的设备,在对字符设备发出读写请求时,实际的硬件I/O紧接着就发生了,一般来说字符设备中的缓存是可有可无的,而且也不支持随机访问。
块设备则是利用一块系统内存作为缓冲区,当用户进程对设备进行读写请求时,驱动程序先查看缓冲区中的内容,如果缓冲区中的数据能满足用户的要求就返回相应的数据,否则就调用相应的请求函数来进行实际的I/O操作。块设备主要是针对磁盘等慢速设备设计的,其目的是避免耗费过多的CPU时间来等待操作的完成。
网络设备不同于字符设备和块设备,它是面向报文的而不是面向流的,它不支持随机访问,也没有请求缓冲区。在Linux里一个网络设备也可以叫做一个网络接口,如eth0,应用程序是通过Socket而不是设备节点来访问网络设备,在系统里根本就不存在网络设备节点。
一般说来,PCI卡属于字符设备。
2. 字符设备驱动框架
2.1 设备驱动模块
在以模块方式编写驱动程序时,要实现两个必不可少的函数init_module()和cleanup_module(),而且至少要包含<linux/krernel.h>和<linux/module.h>两个头文件。
static int __init xxx_init_module (void){ ...}static void __exit xxx_cleanup_module (void){ ...}/* 驱动模块加载函数 */module_init(xxx_init_module);/* 驱动模块卸载函数 */module_exit(xxx_cleanup_module);
2.2 字符设备驱动的两个重要结构体
cdev:
在Linux内核中,使用cdev结构体描述一个字符设备,cdev结构体的定义如下
struct cdev { struct kobject kobj; /* 内嵌的kobject对象 */ struct module *owner; /* 所属模块*/ struct file_operations *ops; /* 文件操作结构体*/ struct list_head list; dev_t dev; /* 设备号*/ unsigned int count; }; |
cdev结构体的dev_t成员定义了设备号,为32位,其中12位为主设备号,20位为次设备号。使用下列宏可以从dev_t获得主设备号和次设备号:
MAJOR(dev_t dev) MINOR(dev_t dev) |
而使用下列宏则可以通过主设备号和次设备号生成dev_t:
MKDEV(int major, int minor) |
file_operations:
cdev结构体的另一个重要成员file_operations定义了字符设备驱动提供给虚拟文件系统的接口函数。
file_operations结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终被内核调用。file_operations结构体的定义如下
struct file_operations { struct 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); 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); }; |
2.3 字符设备驱动模型
以下为一个简单字符驱动框架示例代码,没有任何功能
#include #include #include #include #include //预定义主设备号 #define LS_MAJOR 150 static int ls_major = LS_MAJOR; module_param(ls_major, int, S_IRUGO); //自定义设备结构体 struct ls_dev { struct cdev cdev; //在Linux内核中,使用cdev结构体描述一个字符设备 }; struct ls_dev *ls_devp; /* 字符设备file_operations open 成员函数 */ static int xxx_open(struct inode *inode, struct file *filp) { return 0; } /* 字符设备file_operations ioctl 成员函数 */ static long xxx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { return 0; } /* 字符设备file_operations read 成员函数 */ static ssize_t xxx_read(struct file *filp, char __user * buf, size_t size, loff_t * ppos) { return 0; } /* 字符设备file_operations write成员函数 */ static ssize_t xxx_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos) { return 0; } /* 字符设备file_operations release成员函数 */ static int xxx_release(struct inode *inode, struct file *filp) { return 0; } /* 设备文件操作接口 */ static const struct file_operations xxx_fops = { .owner = THIS_MODULE, /* xxx_fops 所属的设备模块 */ .read = xxx_read, /* 读设备操作*/ .write = xxx_write, /* 写设备操作*/ .unlocked_ioctl = xxx_ioctl, /* 控制设备操作*/ .open = xxx_open, /* 打开设备操作*/ .release = xxx_release, /* 释放设备操作*/ }; static int __init xxx_init_module (void) { //申请字符设备号 dev_t xxx_dev_no = MKDEV(ls_major, 0); register_chrdev_region(xxx_dev_no, 1, "driver_test"); //为自定义设备结构体申请内存 ls_devp = kzalloc(sizeof(struct ls_dev), GFP_KERNEL); //将自定义设备结构体内的cdev成员与file_operations设备文件操作接口绑定 cdev_init(&ls_devp->cdev,&xxx_fops); //拥有该结构的模块的指针,一般为THIS_MODULES ls_devp->cdev.owner = THIS_MODULE; //注册设备 cdev_add(&ls_devp->cdev, xxx_dev_no, 1); return 0; } static void __exit xxx_cleanup_module (void) { /* 注销字符设备 */ cdev_del(&ls_devp->cdev); /* 释放占用的设备号 */ unregister_chrdev_region(MKDEV(ls_major, 0), 1); kfree(ls_devp); } /* 驱动模块加载函数 */ module_init(xxx_init_module); /* 驱动模块卸载函数 */ module_exit(xxx_cleanup_module); MODULE_AUTHOR("LuoSheng"); MODULE_LICENSE("GPL v2");
3. 驱动加载
可以使用命令insmod载入Linux内核,从而成为内核的一个组成部分,此时内核会调用模块中的函数init_module( )。当不需要该模块时,可以使用rmmod命令进行卸载,此进内核会调用模块中的函数cleanup_module()。任何时候都可以使用命令来lsmod查看目前已经加载的模块以及正在使用该模块的用户数。
所有已经注册(即已经加载了驱动程序)的硬件设备的主设备号可以从/proc/devices文件中得到。使用mknod命令可以创建指定类型的设备文件,同时为其分配相应的主设备号和次设备号。例如,下面的命令:
#insmod pcitest.ko //加载驱动 #mknod /dev/pcitest c 150 0 //创建设备文件,主设备号150,次设备号0 #rmmod pcitest //卸载驱动 |
将建立一个主设备号为150,次设备号为0的字符设备文件/dev/ pcitest。
当应用程序对某个设备文件进行系统调用时,Linux内核会根据该设备文件的设备类型和主设备号调用相应的驱动程序,并从用户态进入到核心态,再由驱动程序判断该设备的次设备号,最终完成对相应硬件的操作。
转载地址:https://blog.csdn.net/qq_22042587/article/details/78175014 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!