建设科技信息 网站建设,电子商务企业网站建设发展论文,在国外做电商网站,企业网站系统那个好目录
虚拟串口设备驱动
一个驱动支持多个设备
习题 虚拟串口设备驱动 字符设备驱动除了前面搭建好代码的框架外#xff0c;接下来最重要的就是要实现特定于设备的操作方法#xff0c;这是驱动的核心和关键所在#xff0c;是一个驱动区别于其他驱动的本质所在#xff0c;…目录
虚拟串口设备驱动
一个驱动支持多个设备
习题 虚拟串口设备驱动 字符设备驱动除了前面搭建好代码的框架外接下来最重要的就是要实现特定于设备的操作方法这是驱动的核心和关键所在是一个驱动区别于其他驱动的本质所在是整个驱动代码中最灵活的代码所在。了解了虚拟串口设备的工作方式后接下来就可以针对性的编写驱动程序代码如下
#include linux/init.h
#include linux/kernel.h
#include linux/module.h#include linux/fs.h
#include linux/cdev.h
#include linux/kfifo.h#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME vserstatic struct cdev vsdev;
DEFINE_KFIFO(vsfifo, char, 32);static int vser_open(struct inode *inode, struct file *filp)
{return 0;
}static int vser_release(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t vser_read(struct file *filp, char __user *buf, size_t count,loff_t *pos)
{unsigned int copied 0;kfifo_to_user(vsfifo, buf, count, copied);return copied;
}static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{unsigned int copied 0;kfifo_from_user(vsfifo, buf, count, copied);return copied;
}static struct file_operations vser_ops {.owner THIS_MODULE,.open vser_open,.release vser_release,.read vser_read,.write vser_write,
};static int __init vser_init(void)
{int ret;dev_t dev;dev MKDEV(VSER_MAJOR,VSER_MINOR);ret register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);if(ret)goto reg_err;cdev_init(vsdev, vser_ops);vsdev.owner THIS_MODULE;ret cdev_add(vsdev, dev, VSER_DEV_CNT);if (ret)goto add_err;return 0;add_err:unregister_chrdev_region(dev, VSER_DEV_CNT);reg_err:return ret;
}static void __exit vser_exit(void)
{dev_t dev;dev MKDEV(VSER_MAJOR,VSER_MINOR);cdev_del(vsdev);unregister_chrdev_region(dev, VSER_DEV_CNT);
}module_init(vser_init);
module_exit(vser_exit);MODULE_LICENSE(GPL);
MODULE_AUTHOR(name E-mail);
MODULE_DESCRIPTION(A simple module);
MODULE_ALIAS(virtual-serial); 新的驱动在代码第15行定义并初始化了一个名叫 vsfifo 的 struct kfifo 对象每个元素的数据类型为char共有32个元素的空间。代码第17行到第25行设备打开和关闭函数分别对应于file_operations 内的open和release 方法。因为是虚拟设备所以这里并没有需要特别处理的操作仅仅返回 0表示成功。这两个函数都有两个相同的形参第一个形参是要打开或关闭文件的inode第二个形参则是打开对应件后由内核构造并初始化好的file结构在前面我们已经较深入地分析了这两个对象的作用。这里之所以叫release而不叫close是因为一个文件可以被打开多次那么vser_open函数相应地会被调用多次但是关闭文件只有到最后一个close操作才会导致vser_release函数被调用所以用 release 更贴切。 代码第27第34行是read系统调用驱动实现这里主要是把FIFO中的数据返回给用户层使用了kfifo_to_user 这个宏。read系统调用要求用户返回实际读取的字节数而copied变量的值正好符合这一要求。代码36到第43对应的write系统调用的驱动实现同read系统调用一样只是数据流向相反而已。 读和写函数引入了3个新的形参分别是bufcount和pos根据上面的代码已经不难发现它们的含义。buf代表的是用户空间的内存起始地址;count表示用户想要读写多少个字节的数据:而pos是文件的位置指针在虚拟串口这个不支持随机访问的设备中该参数无用。_user是提醒驱动代码编写者这个内存空间属于用户空间。 代码第 47 行到第 50 行是将file_operations中的函数指针分别指向上面定义的函数这样在应用层发生相应的系统调用后在驱动里面的函数就会被相应地调用。上面这个示例实现了一个功能非常简单但是基本可用的虚拟串口驱动程序。按照下面的步骤可以进行验证。 通过实验结果可以看到对/dev/vser0写入什么数据就可以从这个设备读到什么数据和一个具备内环回功能的串口是一致的。 为了方便读者对照查阅特将file_operations结构类型的定义代码列出。从中我们可以看到还有很多接口函数还没有实现在后面的章节中我们会陆续再实现一些接口。显然一个驱动对下面的接口的实现越多它对用户提供的功能就越多但这也不是说我们必须要实现下面的所有函数接口。比如串口不支持随机访问那么llseek函数接口自然就不用实现。 1525 struct file_operations { 1526 struct module *owner; 1527 loff_t (*llseek) (struct file *, loff t, int); 1528 ssize_t (*read) (struct file *, char__user *, size t, loff t *); 1529 ssize_t (*write) (struct file *, const char _user *, size t, loff t*); 1530 ssize t (*alo read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 1531 ssize t (*aio write) (struct kiocb *, const struct iovec *, unsigned long, loff t) 1532 int (iterite) latruct tile , atruet dir_context *); 1533 unsigned int (*poll) (struct file *, strunt poll_table_struct *); 1534 long (unlocked ioctl) (struct file *, unsigned int, unsigned long); 1535 long (*compat_ioctl) (struct file *, unsigned int, unsigned long); 1536 int (*mmap) (struct file *, struct vm_area_struct *); 1537 int (*open) (struct inode *, struct file *); 1538 int (*flush) (struct file *, f1_owner_t id); 1539 int (*release) (struct inode *, struct file *); 1540 int (*fsync) (struct file *, loff_t, loff_t, int datasync); 1541 int (*aio_fsync) (struct kiocb *, int datasync); 1542 int (*fasync) (int, struct file *, int); 1543 int (*lock) (struct file *, int, struct file_lock *); 1544 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *,int); 1545 unsigned long (*get_unmapped_area) (struct file *, unsigned long, unsigned long, unsigned long, unsigned long); 1546 int (*check_flags)(int); 1547 int (*flock) (struct file *, int, struct file_lock *); 1548 ssize_t (*splice_write) (struct pipe_inode_info *, struct file *, lofft *, size_t, unsigned int); 1549 ssize_t (*splice_read) (struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); 1550 int (*setlease) (struct file *, long, struct file_lock **); 1551 long (*fallocate)(struct file *file, int mode, loff_t offset, 1552 loff_t len); 1553 int (*show_fdinfo)(struct seq_file *m, struct file *f); 1554 }; 一个驱动支持多个设备 如果一类设备有多个个体(比如系统上有两个串口)那么我们就应该写一个驱动来支持这几个设备而不是每一个设备都写一个驱动。对于多个设备所引入的变化是什么呢?首先我们应向内核注册多个设备号其次就是在添加 cdev对象时指明该cdev对象管理了多个设备;或者添加多个 cdev 对象每个cdev对象管理一个设备。接下来最麻烦的部分在于读写操作因为设备是多个那么设备对应的资源也应该是多个(比如虚拟串口驱动中的FIFO)。在读写操作时怎么来区分究竟应该对哪个设备进行操作呢(对于虚拟串口驱动而言就是要确定对哪个FIFO 进行操作)?观察读和写函数没有发现能够区别设备的形参。再观察open 接口我们会发现有一个inode形参通过前面的内容我们知道inode里面包含了对应设备的设备号以及所对应的cdev对象的地址。因此我们可以在open接口函数中取出这些信息并存放在file结构对象的某个成员中再在读写的接口函数中获取该 file 结构的成员从而可以区分出对哪个设备进行操作。 下面首先展示用一个 cdev实现对多个设备的支持
#include linux/init.h
#include linux/kernel.h
#include linux/module.h#include linux/fs.h
#include linux/cdev.h
#include linux/kfifo.h#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 2
#define VSER_DEV_NAME vserstatic struct cdev vsdev;
static DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);static int vser_open(struct inode *inode, struct file *filp)
{switch (MINOR(inode-i_rdev)) {default:case 0:filp-private_data vsfifo0;break;case 1:filp-private_data vsfifo1;break;}return 0;
}static int vser_release(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t vser_read(struct file *filp, char __user *buf, size_t count,loff_t *pos)
{unsigned int copied 0;struct kfifo *vsfifo filp-private_data;kfifo_to_user(vsfifo, buf, count, copied);return copied;
}static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{unsigned int copied 0;struct kfifo *vsfifo filp-private_data;kfifo_from_user(vsfifo, buf, count, copied);return copied;
}static struct file_operations vser_ops {.owner THIS_MODULE,.open vser_open,.release vser_release,.read vser_read,.write vser_write,
};static int __init vser_init(void)
{int ret;dev_t dev;dev MKDEV(VSER_MAJOR,VSER_MINOR);ret register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);if(ret)goto reg_err;cdev_init(vsdev, vser_ops);vsdev.owner THIS_MODULE;ret cdev_add(vsdev, dev, VSER_DEV_CNT);if (ret)goto add_err;return 0;add_err:unregister_chrdev_region(dev, VSER_DEV_CNT);reg_err:return ret;
}static void __exit vser_exit(void)
{dev_t dev;dev MKDEV(VSER_MAJOR,VSER_MINOR);cdev_del(vsdev);unregister_chrdev_region(dev, VSER_DEV_CNT);
}module_init(vser_init);
module_exit(vser_exit);MODULE_LICENSE(GPL);
MODULE_AUTHOR(name E-mail);
MODULE_DESCRIPTION(A simple module);
MODULE_ALIAS(virtual-serial); 上面的代码针对前一示例做的修改是:将VSER_DEV_CNT定义为2表示支持两个设备;用DEFINE_KFIFO分别是vsfifo0和vsfifo1(很显然这里动态分配FIFO要优于静态定义但是这会涉及后面章节中内核内存分配的相关知识故此使用静态的方法);在open接口函数中根据次设备号的值来确定保存哪个FIFO结构体的地址到file结构中的private_data成员中file结构中的private_data是一个void *类型的指针内核保证不会使用该指针所以正如其名一样是驱动私有的在读写接口函数中则是先从file结构中取出private_data的值即FIFO结构的地址,然后再进一步操作。 接下来演示如何将每一个edev对象对应到一个设备来实现一个驱动对多个设备的支持
#include linux/init.h
#include linux/kernel.h
#include linux/module.h#include linux/fs.h
#include linux/cdev.h
#include linux/kfifo.h#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 2
#define VSER_DEV_NAME vserstatic DEFINE_KFIFO(vsfifo0, char, 32);
static DEFINE_KFIFO(vsfifo1, char, 32);struct vser_dev {struct kfifo *fifo;struct cdev cdev;
};static struct vser_dev vsdev[2];static int vser_open(struct inode *inode, struct file *filp)
{ filp-private_data container_of(inode-i_cdev, struct vser_dev, cdev);return 0;
}static int vser_release(struct inode *inode, struct file *filp)
{return 0;
}static ssize_t vser_read(struct file *filp, char __user *buf, size_t count,loff_t *pos)
{unsigned int copied 0;struct vser_dev *dev filp-private_data;kfifo_to_user(dev-fifo, buf, count, copied);return copied;
}static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{unsigned int copied 0;struct vser_dev *dev filp-private_data;kfifo_from_user(dev-fifo, buf, count, copied);return copied;
}static struct file_operations vser_ops {.owner THIS_MODULE,.open vser_open,.release vser_release,.read vser_read,.write vser_write,
};static int __init vser_init(void)
{int i;int ret;dev_t dev;dev MKDEV(VSER_MAJOR,VSER_MINOR);ret register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);if(ret)goto reg_err;for( i 0; i VSER_DEV_CNT; i) {cdev_init(vsdev[i].cdev, vser_ops);vsdev[i].cdev.owner THIS_MODULE;vsdev[i].fifo i 0 ? (struct kfifo *) vsfifo0 : (struct kfifo*)vsfifo1; ret cdev_add(vsdev[i].cdev, dev i, 1);if (ret)goto add_err;}return 0;add_err:for(--i;i0;--i)cdev_del(vsdev[i].cdev);unregister_chrdev_region(dev, VSER_DEV_CNT);reg_err:return ret;
}static void __exit vser_exit(void)
{int i;dev_t dev;dev MKDEV(VSER_MAJOR,VSER_MINOR);for(i 0; i VSER_DEV_CNT; i)cdev_del(vsdev[i].cdev);unregister_chrdev_region(dev, VSER_DEV_CNT);
}module_init(vser_init);
module_exit(vser_exit);MODULE_LICENSE(GPL);
MODULE_AUTHOR(name E-mail);
MODULE_DESCRIPTION(A simple module);
MODULE_ALIAS(virtual-serial);代码第17行至第20行新定义了一个结构类型vser_dev代表一种具体的设备类,通常和设备相关的内容都应该和cdev定义在一个结构中。如果用面向对的思想理解这种做法将会变得很容易。cdev是所有字符设备的一个抽象是一个基类而一个具体类型的设备应该是由该基类派生出来的一个子类子类包含了特定设备所特有的强性比如vser_dev中的fifo这样子类就更能刻画好一类具体的设备。代码第22行创建了两个vser_dev类型的对象和C不同的是创建这两个对象仅仅是为其分配了内存并没有调用构造函数来初始化这两个对象但在代码的第 74行到第77行完成了这个作。查看内核源码会发现这种面向对象的思想处处可见只能说因为语言的特性并没有把这种形式体现得很明显而已。代码的第 74行到第82行通过两次循环完成了两个cdev对象的初始化和添加工作并且初始化了fifo成员的指向。这里需要说明的是用 DEFINE_KFIFO 定义的FIFO每定义一个FIFO就会新定义一种数据类型所以严格来说 vsfifo0和vsfifo1是两种不同类型的对象但好在这里能和struct kfifo类型兼容。 代码第26行用到了一个container_of宏这是在Linux内核中设计得非常巧妙的一个宏在整个Linux内核源码中几乎随处可见。它的作用就是根据结构成员的地址来反向得到结构的起始地址。在代码中inode-i_cdev给出了struct vser_dev结构类型中cdev成员的地址(见图3.2)通过container_of宏就得到了包含该 cdev的结构地址。 使用上面两种方式都可以实现一个驱动对多个同类型设备的支持。使用下面的命令可以测试这两个驱动程序。 make ARCHarm ./lazy 上面再ubuntu中下面再开发板中 mknod /dev/vser0 c 256 0 mknod /dev/vser1 c 256 1 depmod modprobe vser echo 11111 /dev/vser0 echo 22222 /dev/vser1 cat /dev/vser0 cat /dev/vser1 这俩就会分别打印出来。没带开发板但是现象绝对没问题。 习题
1.字符设备和块设备的区别不包括( B)。 [A]字符设备按字节流进行访问块设备按块大小进行访问
[B]字符设备只能处理可打印字符块设备可以处理二进制数据
[C]多数字符设备不能随机访问而块设备一定能随机访问
[D] 字符设备通常没有页高速缓存而块设备有
2.在3.14.25 版本的内核中主设备号占(C )位次设备号占(D )位。 [A]8 [B]16 [C] 12 [D] 20 3.用于分配主次设备号的函数是(C )。 [A]register_chrdev_region [B] MKDEV [C]alloc_chrdev_region [D] MAJOR 4.在字符设备驱动中struct file_operations 结构中的函数指针成员不包含( B)。 [A]open [B]close [C] read [D] show_fdinfo