字符设备驱动程序的完整模板

合集下载

字符设备驱动程序及数据结构简介-vincent_zou的专栏-CSDN博客

字符设备驱动程序及数据结构简介-vincent_zou的专栏-CSDN博客

字符设备驱动程序及数据结构简介-vincent_zou的专栏-CSDN博客展开全文1.设备号分为主次设备号,看上去像是两个号码,但在内核中用dev_t(<linux/types.h>)一种结构表示,同时不应该自己去假设赋值设备号,而是使用宏(<linux/kdev_t.h>)来取得.MAJOR(dev_t dev);MINOR(dev_t dev);即使你有确定的主,次设备号也要用dev=MKDEV(int major, int minor);1.1分配设备号<linux/fs.h>静态分配 //first就是上面的devint register_chrdev_region(dev_t first, unsigned int count, char *name);first 是你要分配的起始设备编号. first 的次编号部分常常是 0, 但是没有要求是那个效果.count 是你请求的连续设备编号的总数. (一般为1)注意, 如果count 太大, 你要求的范围可能溢出到下一个次编号; 但是只要你要求的编号范围可用, 一切都仍然会正确工作.name 是应当连接到这个编号范围的设备的名子(DEVICE_NAME); 它会出现在 /proc/devices 和 sysfs 中动态分配int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);dev 是一个只输出的参数, 它在函数成功完成时持有你的分配范围的第一个数.fisetminor 应当是请求的第一个要用的次编号; 它常常是 0.count 和 name 参数如同给 request_chrdev_region 的一样>>>应该始终使用动态分配,但最好为定制设备号留有接口,以参数形式,以name_major=0做为默认值,可能的操作如下:if (scull_major) {dev = MKDEV(scull_major, scull_minor);result = register_chrdev_region(dev, scull_nr_devs, "scull");} else {result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");scull_major = MAJOR(dev);}if (result < 0) {printk(KERN_WARNING "scull: can't get major %d\n", scull_major);return result;}1.2释放设备号 //设备号void unregister_chrdev_region(dev_t first, unsigned int count);2.重要的数据结构2.1文件操作<linux/fs.h>中的file_operation结构,其成员struct module *owner 第一个 file_operations 成员根本不是一个操作; 几乎所有时间中, 它被简单初始化为THIS_MODULE, 一个在<linux/module.h> 中定义的宏.loff_t (*llseek) (struct file *, loff_t, int); llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个"long offset", 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, seek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 用来从设备中获取数据. 在这个位置的一个空指针导致read 系统调用以-EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型).ssize_t (*aio_read)(struct kiocb *, char __user *, size_t, loff_t); 初始化一个异步读-- 可能在函数返回前不结束的读操作. 如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地).ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数.ssize_t (*aio_write)(struct kiocb *, const char __user *, size_t, loff_t *); 初始化设备上的一个异步写.int (*readdir) (struct file *, void *, filldir_t); 对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对文件系统有用.unsigned int (*poll) (struct file *, struct poll_table_struct *); poll 方法是 3 个系统调用的后端: poll, epoll, 和 select, 都用作查询对一个或多个文件描述符的读或写是否会阻塞. poll 方法应当返回一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到I/O 变为可能. 如果一个驱动的poll 方法为NULL, 设备假定为不阻塞地可读可写.int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用 fops 表. 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, "设备无这样的 ioctl"), 系统调用返回一个错误.int (*mmap) (struct file *, struct vm_area_struct *); mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是NULL, mmap 系统调用返回 -ENODEV.int (*open) (struct inode *, struct file *); 尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是NULL, 设备打开一直成功, 但是你的驱动不会得到通知.int (*flush) (struct file *); flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求.int (*release) (struct inode *, struct file *); 在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL.int (*fsync) (struct file *, struct dentry *, int); 这个方法是 fsync 系统调用的后端, 用户调用来刷新任何挂着的数据. 如果这个指针是NULL, 系统调用返回 -EINVAL.int (*aio_fsync)(struct kiocb *, int); 这是 fsync 方法的异步版本.int (*fasync) (int, struct file *, int); 这个操作用来通知设备它的FASYNC 标志的改变. 异步通知是一个高级的主题, 在第 6 章中描述. 这个成员可以是NULL 如果驱动不支持异步通知.int (*lock) (struct file *, int, struct file_lock *); lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它.ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); 这些方法实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作; 这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 ).ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *); 这个方法实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个. 例如, 它被一个需要发送文件内容到一个网络连接的 web 服务器使用. 设备驱动常常使 sendfile 为 NULL.ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动实际上不实现 sendpage.unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); 这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段中. 这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.[10]int (*check_flags)(int) 这个方法允许模块检查传递给fnctl(F_SETFL...) 调用的标志.int (*dir_notify)(struct file *, unsigned long); 这个方法在应用程序使用 fcntl 来请求目录改变通知时调用. 只对文件系统有用; 驱动不需要实现 dir_notify.>>>下面是一个实现的可能例子,重要的函数被实现struct file_operations scull_fops = {.owner = THIS_MODULE,.llseek = scull_llseek,.read = scull_read,.write = scull_write,.ioctl = scull_ioctl,.open = scull_open,.release = scull_release,};这个声明使用标准的 C 标记式结构初始化语法.2.2文件结构struct file, 定义于 <linux/fs.h>其成员:mode_t f_mode; 文件模式确定文件是可读的或者是可写的(或者都是), 通过位FMODE_READ 和FMODE_WRITE. 你可能想在你的open 或者 ioctl 函数中检查这个成员的读写许可, 但是你不需要检查读写许可, 因为内核在调用你的方法之前检查. 当文件还没有为那种存取而打开时读或写的企图被拒绝, 驱动甚至不知道这个情况.loff_t f_pos; 当前读写位置. loff_t 在所有平台都是 64 位( 在 gcc 术语里是 long long ). 驱动可以读这个值, 如果它需要知道文件中的当前位置, 但是正常地不应该改变它; 读和写应当使用它们作为最后参数而收到的指针来更新一个位置, 代替直接作用于 filp->f_pos. 这个规则的一个例外是在 llseek 方法中, 它的目的就是改变文件位置.unsigned int f_flags; 这些是文件标志, 例如O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驱动应当检查 O_NONBLOCK 标志来看是否是请求非阻塞操作( 我们在第一章的"阻塞和非阻塞操作"一节中讨论非阻塞 I/O ); 其他标志很少使用. 特别地, 应当检查读/写许可, 使用f_mode 而不是 f_flags. 所有的标志在头文件 <linux/fcntl.h> 中定义.struct file_operations *f_op; 和文件关联的操作. 内核安排指针作为它的open 实现的一部分, 接着读取它当它需要分派任何的操作时. filp->f_op 中的值从不由内核保存为后面的引用; 这意味着你可改变你的文件关联的文件操作, 在你返回调用者之后新方法会起作用. 例如, 关联到主编号 1 (/dev/null, /dev/zero, 等等)的 open 代码根据打开的次编号来替代 filp->f_op 中的操作. 这个做法允许实现几种行为, 在同一个主编号下而不必在每个系统调用中引入开销. 替换文件操作的能力是面向对象编程的"方法重载"的内核对等体.void *private_data; open 系统调用设置这个指针为 NULL, 在为驱动调用open 方法之前. 你可自由使用这个成员或者忽略它; 你可以使用这个成员来指向分配的数据, 但是接着你必须记住在内核销毁文件结构之前, 在 release 方法中释放那个内存. private_data 是一个有用的资源, 在系统调用间保留状态信息, 我们大部分例子模块都使用它.struct dentry *f_dentry; 关联到文件的目录入口( dentry )结构. 设备驱动编写者正常地不需要关心dentry 结构, 除了作为filp->f_dentry->d_inode 存取 inode 结构.2.3inode结构其成员:dev_t i_rdev; 对于代表设备文件的节点, 这个成员包含实际的设备编号.struct cdev *i_cdev; struct cdev 是内核的内部结构, 代表字符设备; 这个成员包含一个指针, 指向这个结构, 当节点指的是一个字符设备文件时.i_rdev 类型在 2.5 开发系列中改变了, 破坏了大量的驱动. 作为一个鼓励更可移植编程的方法, 内核开发者已经增加了 2 个宏, 可用来从一个 inode 中获取主次编号:unsigned int iminor(struct inode *inode);unsigned int imajor(struct inode *inode);//不太了解为了不要被下一次改动抓住, 应当使用这些宏代替直接操作 i_rdev3.字符设备的注册3.1添加struct cdev *my_cdev = cdev_alloc();my_cdev->ops = &my_fops;//有了cdev_init函数这个多余了但是, 偶尔你会想将 cdev 结构嵌入一个你自己的设备特定的结构; scull 这样做了. 在这种情况下, 你应当初始化你已经分配的结构, 使用: void cdev_init(struct cdev *cdev, struct file_operations *fops);任一方法, 有一个其他的struct cdev 成员你需要初始化. 象file_operations 结构, struct cdev 有一个拥有者成员, 应当设置为THIS_MODULE. 一旦 cdev 结构建立, 最后的步骤是把它告诉内核, 调用:int cdev_add(struct cdev *dev, dev_t num, unsigned int count);这里, dev 是cdev 结构, num 是这个设备响应的第一个设备号, count 是应当关联到设备的设备号的数目. 常常 count 是 1, 但是有多个设备号对应于一个特定的设备的情形.>>>在使用 cdev_add 是有几个重要事情要记住. 第一个是这个调用可能失败. 如果它返回一个负的错误码, 你的设备没有增加到系统中. 它几乎会一直成功, 但是, 并且带起了其他的点: cdev_add 一返回, 你的设备就是"活的"并且内核可以调用它的操作. 除非你的驱动完全准备好处理设备上的操作, 你不应当调用 cdev_add3.2去除一个字符设备, 调用:void cdev_del(struct cdev *dev);>>>实例,将cdev放入一个自定义的结构中:struct scull_dev {struct scull_qset *data; /* Pointer to first quantum set */int quantum; /* the current quantum size */int qset; /* the current array size */unsigned long size; /* amount of data stored here */unsigned int access_key; /* used by sculluid and scullpriv */ struct semaphore sem; /* mutual exclusion semaphore */struct cdev cdev; /* Char device structure */};以下代码是对其初始化:static void scull_setup_cdev(struct scull_dev *dev, int index){int err, devno = MKDEV(scull_major, scull_minor + index);cdev_init(&dev->cdev, &scull_fops);dev->cdev.owner = THIS_MODULE;dev->cdev.ops = &scull_fops;err = cdev_add (&dev->cdev, devno, 1);/* Fail gracefully if need be */if (err)//it is importent!printk(KERN_NOTICE "Error %d adding scull%d", err, index);}五、open和releaseopen方法提供给驱动程序以初始化的能力,为以后的操作作准备。

【嵌入式LINUX操作系统】字符设备驱动程序编写举例

【嵌入式LINUX操作系统】字符设备驱动程序编写举例

《嵌入式Linux应用开发菜鸟进阶》第11章对字符设备驱动的模块框架有一个宏观的认识:#include <linux/module.h>#include <linux/types.h>#include <linux/fs.h>#include <linux/errno.h>︙#include<XXX>这里根据实际的驱动需要添加头文件static int mem_major = 251;︙/*这里定义驱动需要的一些静态数据或者指针,当然作为全局变量,一般不要轻易使用这些静态变量,它们很占内存,并且浪费资源*/︙实现file_operation中挂接的函数static const struct file_operations mem_operation={.owner = THIS_MODULE,︙};根据驱动需要实现相应的系统调用函数static int mymem_init(void)1{︙}模块驱动的注册函数static void mymem_exit(void){︙}模块的释放函数MODULE_AUTHOR("Lin Hui");MODULE_LICENSE("GPL");定义模块编写的作者以及遵循的协议module_init(mymem_init);module_exit(mymem_exit);定义模块初始化入口函数以上就是一个驱动基本不变的部分,针对字符变化的部分进行详细的讲解。

首先是字符设备的注册。

字符设备的注册主要分为4 步:设备号、分配设备号、定义并初始化file_operation结构体和字符设备的注册。

其中,设备号与分配设备号在11.1节中已经详述,这里不再重复。

下面介绍字符设备注册的详细步骤。

(1)设备号。

(2)分配设备号。

(3)定义并初始化file_operations结构体。

字符设备驱动程序共44页文档

字符设备驱动程序共44页文档

56、书不仅是生活,而且是现在、过 去和未 来文化 生活的 源泉。 ——库 法耶夫 57、生命不可能有两次,但许多人连一 次也不 善于度 过。— —吕凯 特 58、问渠哪得清如许,为有源头活水来 。—— 朱熹 59、我的努力求学没有得到别的好处, 只不过 是愈来 愈发觉 自己的 无知。 ——笛 卡儿

60、生活的道路一旦选定,就要勇敢地 走到底 ,决不 回头。 ——左
1、不要轻言放弃,否则对不起自己。
2、要冒一次险!整个生命就是一场冒险。走得最远的人,常是愿意 去做,并愿意去冒险的人。“稳妥”之船,从未能从岸边走远。-戴尔.卡耐基。
梦的,回味起来却有 久久不会退去的余香。
字符设备驱动程序4、守业的最好办法就是不断的发展。 5、当爱不能完美,我宁愿选择无悔,不管来生多么美丽,我不愿失 去今生对你的记忆,我不求天长地久的美景,我只要生生世世的轮 回里有你。

字符设备驱动开发

字符设备驱动开发

字符设备驱动开发字符设备驱动:1:驱动模块的加载与卸载 1.1:module_init(xxx_init); //注册模块加载函数,通过insmod或modprobe命令加载驱动的时候,xxx_init 这个函数就会被调⽤。

例如:insmod chrdevtest.ko ; modprobe chrdevtest.ko 1.2:module_exit(xxx_exit); //注册模块卸载函数,通过rmmod或modprobe -r命令加载驱动的时候,xxx_init 这个函数就会被调⽤。

例如:rmmod chrdevtest.ko ; modprobe -r chrdevtest.ko2:字符设备注册与注销 2.1:static inline int register_chrdev(unsigned int major, const char*name,const struct file_operations*fops); 参数1为主设备号;参数2为设备名字;参数3为操作函数集合;⼀般在xxx_init⾥调⽤ 2.2:static inline void unregister_chrdev(unsigned int major, const char*name); 参数1为主设备号;参数2为设备名字;⼀般在xxx_exit⾥调⽤3:实现设备的具体操作函数 3.1:取决与具体实现4:设备号分配 4.1:设备号(就是unsigned int型)分为主设备号(⾼12位),次设备号(低20位) 4.2:静态分配:在注册设备时,指定主设备号;注销设备时调⽤unregister_chrdev注销这个设备号及设备 4.3:动态分配:在注册设备之前,调⽤alloc_chrdev_region,系统就会分配⼀个没有使⽤得设备号;注销设备时调⽤unregister_chrdev_region回收设备号5:创建设备节点⽂件 5.1:驱动加载成功需要在/dev ⽬录下创建⼀个与之对应的设备节点⽂件,应⽤程序就是通过操作这个设备节点⽂件来完成对具体设备的操作 例如:mknod /dev/chrdevtest c 200 0新字符设备驱动:1:在上⾯注册设备⽤的是register_chrdev只需给定⼀个主设备号就OK了,但是会有俩个问题:(1)需要事先确定哪些设备号是可⽤得(2)会将主设备下得所有次设备号都占⽤了;为解决这两个问题,引出如下解决⽅案 1.1:如果没有指定设备号的话就使⽤此函数来申请设备号:int alloc_chrdev_region(dev_t*dev, unsigned baseminor, unsigned count, const char*name) 1.2:如果给定了设备的主设备号和次设备号就使⽤如下所⽰函数来注册设备号:int register_chrdev_region(dev_t from, unsigned count, const char*name) 1.3:统⼀使⽤如下释放函数void unregister_chrdev_region(dev_t from, unsigned count)2:新的设备注册⽅法 2.1:字符设备结构(编写字符设备驱动之前需要定义⼀个 cdev 结构体变量,这个变量就表⽰⼀个字符设备)struct cdev {struct kobject kobj;struct module *owner;const struct file_operations *ops; //操作函数集合struct list_head list;dev_t dev; //设备号unsigned int count;}; 2.2:cdev_init函数:定义好 cdev 变量以后就要使⽤ cdev_init 函数对其进⾏初始化,cdev_init 函数原型如下void cdev_init(struct cdev *cdev, const struct file_operations *fops) 2.3:cdev_add函数:cdev_add 函数⽤于向 Linux 系统添加字符设备(cdev 结构体变量),也就是注册,原型如下int cdev_add(struct cdev *p, dev_t dev, unsigned count) 2.4:cdev_del函数:卸载驱动的时候⼀定要使⽤ cdev_del 函数从 Linux 内核中删除相应的字符设备,原型如下void cdev_del(struct cdev *p)3:⾃动创建设备节点⽂件 3.1:mdev机制:udev 是⼀个⽤户程序,在 Linux 下通过 udev 来实现设备⽂件的创建与删除,udev 可以检测系统中硬件设备状态,可以根据系统中硬件设备状态来创建或者删除设备⽂件。

一个简单字符设备驱动实例

一个简单字符设备驱动实例

如何编写Linux设备驱动程序Linux是Unix操作系统的一种变种,在Linux下编写驱动程序的原理和思想完全类似于其他的Unix系统,但它dos或window环境下的驱动程序有很大的区别。

在Linux环境下设计驱动程序,思想简洁,操作方便,功能也很强大,但是支持函数少,只能依赖kernel中的函数,有些常用的操作要自己来编写,而且调试也不方便。

本文是在编写一块多媒体卡编制的驱动程序后的总结,获得了一些经验,愿与Linux fans共享,有不当之处,请予指正。

以下的一些文字主要来源于khg,johnsonm的Write linux device driver,Brennan's Guide to Inline Assembly,The Linux A-Z,还有清华BBS上的有关device driver的一些资料. 这些资料有的已经过时,有的还有一些错误,我依据自己的试验结果进行了修正.一、Linux device driver 的概念系统调用是操作系统内核和应用程序之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口。

设备驱动程序为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。

设备驱动程序是内核的一部分,它完成以下的功能:1)对设备初始化和释放;2)把数据从内核传送到硬件和从硬件读取数据;3)读取应用程序传送给设备文件的数据和回送应用程序请求的数据;4)检测和处理设备出现的错误。

在Linux操作系统下有两类主要的设备文件类型,一种是字符设备,另一种是块设备。

字符设备和块设备的主要区别是:在对字符设备发出读/写请求时,实际的硬件I/O一般就紧接着发生了,块设备则不然,它利用一块系统内存作缓冲区,当用户进程对设备请求能满足用户的要求,就返回请求的数据,如果不能,就调用请求函数来进行实际的I/O操作。

块设备是主要针对磁盘等慢速设备设计的,以免耗费过多的CPU时间来等待.已经提到,用户进程是通过设备文件来与实际的硬件打交道。

驱动之路-简单字符设备驱动程序

驱动之路-简单字符设备驱动程序

驱动之路-简单字符设备驱动程序一、重要知识点1. 主次设备号dev_tdev_t是内核中用来表示设备编号的数据类型;int MAJOR(dev_t dev)int MINOR(dev_t dev)这两个宏抽取主次设备号。

dev­_t MKDEV(unsigned int major, unsignedint minor)这个宏由主/次设备号构造一个dev_t结构。

2. 分配和释放设备号int register_chardev_region(dev_t first,unsigned int count, char *name)静态申请设备号。

Int alloc_chardev_region(dev_t *dev,unsigned int firstminor, unsigned int count, char *name) 动态申请设备号,注意第一个参数是传地址,而静态则是传值。

3. 几种重要的数据结构struct filefile结构代表一个打开的文件,它由内核在open时创建,并传递给该文件上进行操作的所有函数,直到最后的close函数。

file结构private_data是跨系统调用时保存状态信息非常有用的资源。

file结构的f_ops 保存了文件的当前读写位置。

struct inode内核用inode代表一个磁盘上的文件,它和file结构不同,后者表示打开的文件描述符。

对于单个文件,可能会有许多个表示打开文件的文件描述符file结构,但他们都指单个inode结构。

inode的dev_t i_rdev成员包含了真正的设备编号,struct cdev *i_cdev包含了指向struct cdev结构的指针。

struct file_operations。

字符设备驱动编写

字符设备驱动编写

字符设备驱动编写字符设备驱动编写流程1.流程说明在上一节中已经提到,设备驱动程序可以使用模块的方式动态加载到内核中去。

加载模块的方式与以往的应用程序开发有很大的不同。

以往在开发应用程序时都有一个main函数作为程序的入口点,而在驱动开发时却没有main 函数,模块在调用insmod命令时被加载,此时的入口点是init_module函数,通常在该函数中完成设备的注册。

同样,模块在调用rmmod 函数时被卸载,此时的入口点是cleanup_module函数,在该函数中完成设备的卸载。

在设备完成注册加载之后,用户的应用程序就可以对该设备进行一定的操作,如read、write等,而驱动程序就是用于实现这些操作,在用户应用程序调用相应入口函数时执行相关的操作,init_module入口点函数则不需要完成其他如read、write之类功能。

上述函数之间的关系如图11.3 所示。

内核设备注册设备卸载设备功能用户调用模块init_module()cleanup_modulermmodinsmod图11.3 设备驱动程序流程图2.重要数据结构用户应用程序调用设备的一些功能是在设备驱动程序中定义的,也就是设备驱动程序的入口点,它是一个在<linux/fs.h>中定义的struct file结构,这是一个内核结构,不会出现在用户空间的程序中,它定义了常见文件I/O 函数的入口。

如下所示:struct file_operations {loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *filp, char *buff, size_t count, loff_t *offp); ssize_t (*write) (struct file *filp, const char *buff, size_t count, loff_t *offp); int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsignedlong);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, struct dentry *);int (*fasync) (int, struct file *, int);int (*check_media_change) (kdev_t dev);int (*revalidate) (kdev_t dev);int (*lock) (struct file *, int, struct file_lock *);};这里定义的很多函数读者在第6 章中已经见到过了,当时是调用这些函数,而在这里我们将学习如何实现这些函数。

字符设备驱动框架

字符设备驱动框架

Linux中设备分类:按照对设备的访问方式可分为以下三类:1.字符设备(char device)(1)例如:键盘、鼠标、串口、帧缓存等;(2)通过/dev/下的设备节点访问;以字节为单位访问;(3)一般只支持顺序访问;(特例:帧缓存framebuffer)(4)无缓冲。

2.块设备(block device)(1)例如:磁盘、光驱、flash等;(2)以固定大小为单位访问:磁盘以扇区(512B)为单位;flash以页为单位。

(3)支持随机访问;(4)有缓冲(减少磁盘IO,提高效率)。

3.网络设备(network device)(1)无设备文件(节点);(2)应用层通过socket接口访问网络设备(报文发送和接收的媒介)。

设备驱动在内核中的结构:1.VFS虚拟文件系统作用:向应用层提供一致的文件访问接口,正是由于VFS的存在,才可以将设备以文件的方式访问。

2.虚拟文件系统,存在于内存中,不在磁盘上,掉电丢失。

例如:/proc、/sys、/tmp。

设备号:1.作用:唯一地标识一个设备;2.类型:dev_t devno;即32位无符号整型;3.组成:(1)主设备号:用于区分不同类型(按功能划分)的设备;(2)此设备号:用于区分相同类型的不同设备。

注意:相同类型的设备(主设备号相同)可以使用同一个驱动。

4.构建设备号:int major = 250; int minor = 0;(1)dev_t devno = (major << 20) | minor;不建议使用;(2)利用宏来构建:dev_t devno = MKDEV (major, minor);注意:我们可以通过文件$(srctree)/documentation/device.txt来查看内核对设备号的分配情况。

(1)该文本中的有对应设备文件的设备号是已经被申请过的,我们不可以重复使用(申请);(2)从中可以看出,我们在编写驱动程序时可以使用的主设备号范围为240~254,为了方便记忆,通常使用250作为主设备号。

  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

4.打开函数
int xxOpen(DEV_HDR *pxxDevHdr, int option, int flags) { xx_DEV * pxxDev=(xx_DEV *) pxxDevHdr; if(pxxDevHdr==NULL) { errnoSet(S_xx_NODEV); return(ERROR); } /*判断设备是否打开*/ if(pxxDev->Opened) { errnoSet(S_xx_DEVOPENED); return(ERROR); } pxxDev->Opened=TRUE; … } /*在这里作必要的初始化*/ return((int)pxxDevHdr);
6.写函数
int xxWrite(int xxDevId, char * pBuf, int nBytes) { xx_DEV * pxxDev=(xx_DEV *) pxxDevId; int WriteLength=0; BOOL FoundError; /*设备描述符是否为空*/ if(pxxDev==(xx_DEV *)NULL) { errnoSet(S_xx_NODEV); return(ERROR); } /*设备写就绪*/ if(pxxDev->ReadyToWrite) { readLength=0; pxxDev->ReadyToWrite=FALSE; … … } pxxDev->ReadyToWrite=TRUE; if(FoundError) return(ERROR); return(WriteLength);/*返回发送字符长度*/ } /*向设备发送处理*/ /*判断相关状态*/
3.设备创建函数
STATUS xxDevCreate(char *devName) { xx_DEV *pxxDev; /*检查驱动程序是否安装*/ if(xxDrvNum<1) { errno=S_ioLib_NO_DRIVER; return(ERROR); } /*分配内存并初始化*/ if((pxxDev=(xx_DEV *)malloc(sizeof(xx_DEV)))==NULL) return(ERROR);
二、串行设备驱动程序模板
1.串行通道数据结构
typedef struct { SIO_DRV_FUNCS * pDrvFuncs; STATUS STATUS void * void * UINT16 UINT16 UCHAR void … } (*getTxChar)(); (*putRcvChar)(); getTxArg; putRcvArg; int_vec; channelMode; (*inByte)(int); (*outByte)(int,char); /**/ /**/ /*中断向量*/ /*模式中断或轮询*/ /*从硬件读取一个字节函数指针*/ /*向硬件读取一个字节函数指针*/ /*驱动程序所需的函数*/ /*传送函数指针*/ /*接受函数指针*/ /*xx_CHAN*/
bzero(pxxDev, sizeof(xx_DEV)); selWakeupListInit(&pxxDev->selWakeupList); … … /*初始化必要的描述结构 pxxDev*/ /*执行设备硬件初始化,例如 I/O 地址、设备本地内存地址等*/
/*将设备添加到设备列表*/ if(iosDevAdd(&pxxDev->devHdr,devName, xxDrvNum)==ERROR { free((char *)pxxDev); return(ERROR); } return(OK); }
free(pxxDev);
9.卸载设备函数
STATUS xxDelete(char * devName) { DEV_HDR *pDevTail; char * pNameTail; /*查找设备*/ pDevHdr=iosDevFind(devName, &pNameTail); if(pDevHdr==NULL || *pNameTail !=’\0’) return(ERROR); … /*释放设备所占用的资源,例如:信号量、唤醒等待该设备的任务等*/ /*卸载设备*/ iosDevDelete(pDevHdr);
/*必要的寄存器描述及结构*/
2.串行设备驱动程序结构初始化函数
void xxDevInit(xx_CHAN *pChan/*指向设备的指针*/) { SIO_DRV_FUNCS xxSioDrvFuncs; /*驱动程序结构指针*/ if(xxSioDrvFuncs.ioctl == NULL) { xxSioDrvDFuncs.ioctl = (int (*)())xxIoctl; xxSioDrvDFuncs.txStartup = (int (*)())xxStartup; xxSioDrvDFuncs.calbackInstall = xxCallbackInstall; xxSioDrvDFuncs.pollInput = (int (*)())xxPollInput; xxSioDrvDFuncs.pollOutput = (int (*)(SIO_CHAN *, char))xxPollOutput; } pChan->pDevFuncs = &xxSioDrvFuncs;
8.设备关闭函数
int xxClose(int xxDevId) { xx_DEV * pxxDev=(xx_DEV *)xxDevId; if(pxxDev==(xx_DEV *)NULL) { errnoSet(S_xx_NOMEM); return(ERROR); } … … } /*设备相关操作*/ /*释放相关资源*/ /*释放资源*/
VxWorks 字符设备驱动程序的完整模板
一、字符设备驱动程序的模板
以下是一个完整的摸板
1.设备描述符结构
LOCAL int xxDrvNum=0; //驱动程序索引号 typedef struct { DEV_HRDdevHdr; BOOL BOOL UINT32 UINT32 ….. SEL_WAKEUP_LIST selWakeupList; //select()功能 BOOL BOOL }xx_DEV; ReadyToRead; //设备读就绪 ReadyToWrite; //设备写就绪 isCreate; isOpen; ioAddr; //设备头数据结构 //设备创建标志 //设备打开标志 //设备 I/O 基地址
… }ห้องสมุดไป่ตู้
/*必要的硬件初始化*/
3.回调安装函数
static int xxCallbackInstall(SIO_CHAN * pSioChan, int callbackType, STATUS (*callback)(), void * callbackArg) { xx_CHAN * pChan = (xx_CHAN *)pSioChan; switch(callbacktype) { case SIO_CALLBACK_GET_TX_CHAR: pChan->getTxChar pChan->getTxArg return(OK); case SIO_CALLBACK_PUT_RCV_CHAR: pChan->getRcvChar pChan->getRcvArg return(OK); default: return(ERROR); } } = callback; = callbackArg; = callback; = callbackArg;
4.启动一次发送周期函数
LOCAL int xxTxStartup(SIO_CHAN * pChan) { xx_CHAN * pxxChan = (xx_CHAN *)pChan; char ier = xx_IER_RXRDY; char status; if(pxxChan->cahnnelMode == SIO_MODE_INT) { /*根据硬件寄存器的值判断状态*/ status = ((*pxxChan->inByte)(pxxChan->sta)); /*如果状态正确,打开中断*/ if(status == xx_OK) (*pxxChan->outByte)(pxxChan->ier); /*ier:打开中断寄存器*/ } return(OK); }
7. I/O 控制函数
int xxIoctl(int xxDevId, int cmd, int arg) { int status; xx_DEV *pxxDev=(xx_DEV *)xxDevId; switch(cmd) { case FIOSELECT: selNodeAdd(&pxxDev->selWakeupList, (SEL_WAKEUP_NODE *)arg);
RegMEMBase; //设备存储器基地址
2.设备驱动程序装载函数
STATUS xxDrv() { //首先判断驱动程序是否已经安装 if(xxDrvNum>0) return(OK); … /*在这里添加驱动程序的初始化部分*/ /*将驱动程序添加到驱动程序描述表中*/ if((xxDrvNum=iosDrvInstall(xxOpen, NULL, xxOpen, xxClose, xxRead, xxWrite, xxIoctl)==ERROR) { return(ERROR); } return(OK); }
return(OK); }
10.设备中断管理函数
STATUS ULONG xxIntHandler(int xxDevId) { xx_DEV * pxxDev=(xx_DEV *) xxDevId; … /*读取中断状态*/
/*如果可以接受*/ pxxDev->ReadyToRead=TRUE; /*如果可以发送*/ pxxDev->ReadyToWrite=FALSE; … } /*清除相应中断*/
相关文档
最新文档