字符设备驱动程序的完整模板
- 格式:pdf
- 大小:87.88 KB
- 文档页数:12
字符设备驱动程序及数据结构简介-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应用开发菜鸟进阶》第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结构体。
字符设备驱动开发字符设备驱动: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作为主设备号。
linux设备驱动第三篇:写⼀个简单的字符设备驱动在linux设备驱动第⼀篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写⼀个简单的字符设备驱动。
本篇借鉴LDD中的源码,实现⼀个与硬件设备⽆关的字符设备驱动,仅仅操作从内核中分配的⼀些内存。
下⾯就开始学习如何写⼀个简单的字符设备驱动。
⾸先我们来分解⼀下字符设备驱动都有那些结构或者⽅法组成,也就是说实现⼀个可以使⽤的字符设备驱动我们必须做些什么⼯作。
1、主设备号和次设备号对于字符设备的访问是通过⽂件系统中的设备名称进⾏的。
他们通常位于/dev⽬录下。
如下:xxx@ubuntu:~$ ls -l /dev/total 0brw-rw---- 1 root disk 7, 0 3⽉ 25 10:34 loop0brw-rw---- 1 root disk 7, 1 3⽉ 25 10:34 loop1brw-rw---- 1 root disk 7, 2 3⽉ 25 10:34 loop2crw-rw-rw- 1 root tty 5, 0 3⽉ 25 12:48 ttycrw--w---- 1 root tty 4, 0 3⽉ 25 10:34 tty0crw-rw---- 1 root tty 4, 1 3⽉ 25 10:34 tty1crw--w---- 1 root tty 4, 10 3⽉ 25 10:34 tty10其中b代表块设备,c代表字符设备。
对于普通⽂件来说,ls -l会列出⽂件的长度,⽽对于设备⽂件来说,上⾯的7,5,4等代表的是对应设备的主设备号,⽽后⾯的0,1,2,10等则是对应设备的次设备号。
那么主设备号和次设备号分别代表什么意义呢?⼀般情况下,可以这样理解,主设备号标识设备对应的驱动程序,也就是说1个主设备号对应⼀个驱动程序。
当然,现在也有多个驱动程序共享主设备号的情况。
⽽次设备号有内核使⽤,⽤于确定/dev下的设备⽂件对应的具体设备。
简单字符设备驱动程序的设计简单字符设备驱动程序的设计摘要本文介绍了简单字符设备驱动程序的设计,包括驱动程序的层次结构、关键功能模块的实现以及编写驱动程序的一般步骤。
通过阅读本文,读者可以了解字符设备驱动程序的基本原理和设计方法,为开发自己的驱动程序奠定基础。
1. 引言字符设备驱动程序是操作系统中的一个重要组成部分,它负责处理和管理字符设备的读写请求。
字符设备包括键盘、显示器等基本输入输出设备,也包括串口、并口等其他字符设备。
本文将以一个简单的字符设备驱动程序为例,介绍其设计和实现过程。
2. 设计思路2.1 驱动程序的层次结构字符设备驱动程序通常具有以下三个层次结构:- 应用层:负责与用户空间进行交互,接收和处理用户的读写请求。
- 中间层:负责驱动程序的逻辑控制,将用户请求传递给底层驱动程序。
- 底层层:与硬件设备进行交互,负责真正的数据读写操作。
2.2 关键功能模块的实现字符设备驱动程序的关键功能模块包括初始化、打开、关闭、读取和写入等。
下面分别介绍这些功能模块的实现。
- 初始化:在驱动程序加载时进行初始化操作,包括申请资源、注册字符设备等。
- 打开:当用户打开字符设备时,内核会调用驱动程序的打开函数,可以在该函数中进行一些必要的初始化操作。
- 关闭:当用户关闭字符设备时,内核会调用驱动程序的关闭函数,可以在该函数中进行一些必要的资源释放操作。
- 读取:当用户从字符设备中读取数据时,内核会调用驱动程序的读取函数,可以在该函数中实现数据的读取操作。
- 写入:当用户向字符设备中写入数据时,内核会调用驱动程序的写入函数,可以在该函数中实现数据的写入操作。
3. 编写驱动程序的步骤编写字符设备驱动程序的一般步骤包括以下几个方面:1. 定义并注册字符设备:在代码中定义一个字符设备结构体,并在初始化函数中调用 `register_chrdev` 函数进行注册。
2. 实现初始化函数:在初始化函数中进行资源的申请、字符设备结构体的初始化和注册等操作。
实现如下的功能:--字符设备驱动程序的结构及驱动程序需要实现的系统调用--可以使用cat命令或者自编的readtest命令读出"设备"里的内容--以8139网卡为例,演示了I/O端口和I/O内存的使用本文中的大部分内容在Linux Device Driver这本书中都可以找到,这本书是Linux驱动开发者的唯一圣经。
================================================== ===== 先来看看整个驱动程序的入口,是char8139_init(这个函数如果不指定MODULE_LICENSE("GPL", 在模块插入内核的时候会出错,因为将非"GPL"的模块插入内核就沾污了内核的"GPL"属性。
module_init(char8139_init;module_exit(char8139_exit;MODULE_LICENSE("GPL";MODULE_AUTHOR("ypixunil";MODULE_DESCRIPTION("Wierd char device driver for Realtek 8139 NIC"; 接着往下看char8139_init(static int __init char8139_init(void{int result;PDBG("hello. init.\n";/* register our char device */result=register_chrdev(char8139_major, "char8139", &char8139_fops;if(result<0{PDBG("Cannot allocate major device number!\n";return result;}/* register_chrdev( will assign a major device number and return if it called * with "major" parameter set to 0 */if(char8139_major == 0char8139_major=result;/* allocate some kernel memory we need */buffer=(unsigned char*(kmalloc(CHAR8139_BUFFER_SIZE, GFP_KERNEL;if(!buffer{PDBG("Cannot allocate memory!\n";result= -ENOMEM;可以通过"cat /proc/devices"命令来查看系统中已经使用的主设备号。
本文由我司收集整编,推荐下载,如有疑问,请与我司联系【Linux内核驱动】字符设备驱动框架模板2017/09/05 502 #include linux/init.h #include linux/module.h #include linux/fs.h #include linux/cdev.h #include linux/kdev_t.h #include linux/slab.h #include linux/device.h #define DEVICE_NAME “chrdev_test”#define DEVICE_MAJOR 0#define MINOR_BASE 0#define MINOR_NUM 1MODULE_LICENSE(“Dual BSD/GPL”);MODULE_AUTHOR(“colorfulshark@hotmail”);static int major = DEVICE_MAJOR;static int minor_base = MINOR_BASE;static int minor_num = MINOR_NUM;static dev_t devno;static struct cdev *pcdev;static struct class *test_class;static struct device *test_device;static struct file_operations fops = { .owner = THIS_MODULE,};#if 0module_param(major, int, S_IRWXU);module_param(minor_base, int, S_IRWXU);module_param(minor_num, int, S_IRWXU);#endifstatic void request_devno(void) //申请设备号{ int ret; if(major) { printk(KERN_ALERT “alloc device number static\n”); devno = MKDEV(major, minor_base); ret = register_chrdev_region(devno, minor_num, DEVICE_NAME); } else { printk(KERN_ALERT “alloc device number dynamic\n”); ret = alloc_chrdev_region( devno, minor_base, minor_num, DEVICE_NAME); printk(KERN_ALERT “alloc major : %d\n”, MAJOR(devno)); } if(ret 0) { printk(KERN_ALERT “register chrdev region failed\n”); }}static void free_devno(void) //释放设备号{ unregister_chrdev_region(devno, minor_num);}static int add_cdev(void) //添加字符设备{ int error; pcdev = cdev_alloc(); if(pcdev == NULL) { goto err_no_mem; } cdev_init(pcdev, fops); pcdev- owner = THIS_MODULE; error = cdev_add(pcdev, devno, minor_num); if(error) goto err_cdev_add; printk(KERN_ALERT “success to add cdev\n”); return 0;err_no_mem: printk(KERN_ALERT “no enough memory\n”); free_devno(); return -ENOMEM;err_cdev_add: printk(KERN_ALERT “fail to add cdev\n”); free_devno(); return error;}static void del_cdev(void) //删除字符设备{ cdev_del(pcdev);}static void。