linux字符设备驱动.ppt

上传人:博****1 文档编号:568452810 上传时间:2024-07-24 格式:PPT 页数:43 大小:312.51KB
返回 下载 相关 举报
linux字符设备驱动.ppt_第1页
第1页 / 共43页
linux字符设备驱动.ppt_第2页
第2页 / 共43页
linux字符设备驱动.ppt_第3页
第3页 / 共43页
linux字符设备驱动.ppt_第4页
第4页 / 共43页
linux字符设备驱动.ppt_第5页
第5页 / 共43页
点击查看更多>>
资源描述

《linux字符设备驱动.ppt》由会员分享,可在线阅读,更多相关《linux字符设备驱动.ppt(43页珍藏版)》请在金锄头文库上搜索。

1、Linux 2.6字符设备驱动程序设计字符设备驱动程序设计黄松青设备驱动概述设备驱动概述v设备由两部分组成,一个是被称为控制器的电器部分,另一设备由两部分组成,一个是被称为控制器的电器部分,另一个是机械部分。个是机械部分。 v一组寄存器组被赋予到各个控制器。一组寄存器组被赋予到各个控制器。I/O端口包含端口包含4组寄存器,组寄存器,即状态寄存器,控制寄存器,数据输入寄存器,数据输出寄即状态寄存器,控制寄存器,数据输入寄存器,数据输出寄存器。存器。状态寄存器拥有可以被CPU读取的(状态)位,用来 指示当前命令是否执行完毕,或者字节是否可以被读出或写入,以及任何错误提示。控制寄存器则用于启动一条命

2、令(指令)或者改变设备的(工作)模式。数据输入寄存器用于获取输入的数据。数据输出寄存器则向CPU发送结果。设备驱动概述设备驱动概述v操作系统是通过各种驱动程序来驾驭硬件设备,它为操作系统是通过各种驱动程序来驾驭硬件设备,它为用户屏蔽了各种各样的设备。用户屏蔽了各种各样的设备。v设备驱动程序是操作系统内核和机器硬件之间的接口,设备驱动程序是操作系统内核和机器硬件之间的接口,系统调用是操作系统内核和应用程序之间的接口。系统调用是操作系统内核和应用程序之间的接口。v在应用程序看来,硬件设备只是一个设备文件,在应用程序看来,硬件设备只是一个设备文件, 应用应用程序可以象操作普通文件一样对硬件设备进行操

3、作程序可以象操作普通文件一样对硬件设备进行操作. 设备驱动概述设备驱动概述v驱动完成以下的功能驱动完成以下的功能: 对设备初始化和释放. 把数据从内核传送到硬件和从硬件读取数据. 读取应用用程序传送给设备文件的数据和回送应用用程序请求的数据. 检测和处理设备出现的错误. 设备驱动概述设备驱动概述v无操作系统的设备驱动无操作系统的设备驱动v有操作系统的设备驱动有操作系统的设备驱动ApplicationDriverHardwareApplicationLib APISystem callEmbedded OSHardwareDriver不带操作系统软件结构 带操作系统软件结构Linux系统驱动概述

4、系统驱动概述 v驱动程序在驱动程序在Linux系统中以内核模块的形式存在系统中以内核模块的形式存在,它的它的编写方式遵循模块的方式编写方式遵循模块的方式v驱动程序在系统的程序中介于驱动程序在系统的程序中介于HAL层与硬件之间层与硬件之间.向向上上,与系统各部分与系统各部分(主要是文件系统主要是文件系统)衔接衔接,向下向下,处理硬处理硬件的各种操作请求件的各种操作请求v系统与驱动之间有标准的接口定义系统与驱动之间有标准的接口定义,驱动就是要实现系驱动就是要实现系统的接口统的接口vLinux提供提供3种基本的设备驱动接口种基本的设备驱动接口,包括字符包括字符,块设块设备和网络设备驱动备和网络设备驱

5、动,其中字符设备是应用最广其中字符设备是应用最广,和用户和用户应用程序联系最直接的驱动应用程序联系最直接的驱动Linux系统驱动概述系统驱动概述vLinuxLinux操作系统把设备纳入文件系统的范畴来管理。操作系统把设备纳入文件系统的范畴来管理。v每个设备在每个设备在LinuxLinux系统上看起来都像一个文件,它们存放系统上看起来都像一个文件,它们存放在在/dev/dev目录中目录中,称为称为 设备节点设备节点 。v对文件操作的系统调用大都适用于设备文件。对文件操作的系统调用大都适用于设备文件。 字符设备驱动字符设备驱动 v应用程序如何使用字符设备应用程序如何使用字符设备,一个例子一个例子

6、static char buf1024; int main(void) int fd, size = 1024; fd = open(“/dev/fb0”, O_RDWR); if(fd 0) read(fd, buf, size); write(fd, buf, size); return 0; 字符设备驱动字符设备驱动v字符设备通过文件系统中的名子来存取字符设备通过文件系统中的名子来存取. 那些名子称为文件系统的特殊那些名子称为文件系统的特殊v文件文件,或者设备文件或者设备文件,一般放在一般放在/dev目录下目录下 #ls l /dev/ crw-rw-rw- 1 root root 1,

7、 5 Apr 11 2002 zero crw-rw-rw- 1 root root 1, 3 Apr 11 2002 nullcrw- 1 root root 4, 1 Oct 28 03:04 tty1 crw-rw-rw- 1 root tty 4, 64 Apr 11 2002 ttys0 crw-rw- 1 root uucp 4, 65 Apr 11 2002 ttyS1 crw-w- 1 vcsa tty 7, 1 Apr 11 2002 vcs1 crw-w- 1 vcsa tty 7,129 Apr 11 2002 vcsa1 其中其中,类似类似 4, 1这样的数字叫设备的主

8、这样的数字叫设备的主,次编号次编号字符设备驱动字符设备驱动v主次编号主次编号 - 传统上传统上, 主设备号标识设备相连的驱动例如主设备号标识设备相连的驱动例如, /dev/null 和和 /dev/zero 都由驱动都由驱动 1 来管理来管理,次设备号是用来区分同一驱动程序管理次设备号是用来区分同一驱动程序管理的不同设备的不同设备 - 在内核中在内核中,系统是根据主次编号来确定调用的驱动程序的系统是根据主次编号来确定调用的驱动程序的,对于对于 2.6.0 内核内核,主次编号是主次编号是 32 位的数位的数,叫叫dev_t类型类型, 12 位用作主编号位用作主编号, 20 位用作次编号位用作次编

9、号,为了保障代码的移植为了保障代码的移植,应该用系统提供的宏应该用系统提供的宏 来操作来操作 MAJOR(dev_t dev) MINOR(dev_t dev) MKDEV(int major, int minor) - 可以根据可以根据主次编号在文件系统中建立对应的设备名主次编号在文件系统中建立对应的设备名 #mknod /dev/my_name c 254 0 字符设备驱动字符设备驱动v分配和释放设备编号分配和释放设备编号 #include int register_chrdev_region(dev_t first, unsigned int count, char *name); 您事

10、先知道你需要哪个设备编号您事先知道你需要哪个设备编号,但不知道是否被使用但不知道是否被使用,调用该函数调用该函数 #include int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name); 您由系统动态分配设备编号您由系统动态分配设备编号,用该函数用该函数 在设备注册后在设备注册后,您的设备号将出现在您的设备号将出现在/proc/devices中和中和/sys中中 释放以上两个函数分配的设备编号释放以上两个函数分配的设备编号 void unregister_chrd

11、ev_region(dev_t first, unsigned int count); 字符设备驱动字符设备驱动v字符设备注册字符设备注册 老方法老方法: - 注册注册 include int register_chrdev(unsigned int major, const char *name, struct file_operations *fops); - 注销注销 int unregister_chrdev(unsigned int major, const char *name); 一旦你的驱动注册成功一旦你的驱动注册成功, 系统就可以对你的设备进行操作系统就可以对你的设备进行操作

12、,所以所以 在注册前在注册前,你需要把你的你需要把你的fops结构准备好结构准备好. 字符设备驱动字符设备驱动v字符设备注册字符设备注册 新方法新方法: - 注册注册 include struct cdev *cdev_alloc(void); void cdev_init(struct cdev *dev, struct file_operations *fops); int cdev_add(struct cdev *dev, dev_t num, unsigned int count); - 注销注销 void cdev_del(struct cdev *dev); cdev 结构管理的

13、函数结构管理的函数, 它代表内核中的字符设备它代表内核中的字符设备 在注册前在注册前,你需要把你的你需要把你的fops结构准备好结构准备好 字符设备驱动字符设备驱动v大部分驱动程序涉及三个重要的内核数据结构:大部分驱动程序涉及三个重要的内核数据结构:文件操作file_operations结构体文件对象file结构体索引节点inode结构体字符设备驱动字符设备驱动v重要数据结构重要数据结构struct file_operation 1) struct file_operation, 此结构定义了维系在此结构定义了维系在设备驱动的操作的函数设备驱动的操作的函数. 2) 在进程在进程PCB中中,每个

14、打开文件每个打开文件,包括设备文件包括设备文件 (内内部用一个部用一个 struct file 结构来代表结构来代表, 稍后我们会看到稍后我们会看到)与它自身的函数集合相关连与它自身的函数集合相关连, 它通过包含一个称为它通过包含一个称为 f_op 的指向一个的指向一个 file_operations 结构成员指针结构成员指针建立关系建立关系 3) 用户通过系统调用用户通过系统调用open,read,write等在这个结等在这个结构中有相应的方法对应构中有相应的方法对应 字符设备驱动字符设备驱动v重要数据结构重要数据结构,struct file_operation的成员的成员 struct m

15、odule *owner 第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有时间中, 它被简单初始化为 THIS_MODULE, 一个在 中定义的宏. loff_t (*llseek) (struct file *, loff_t, int); llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. loff_t 参数是一个long offset, 并且就算在 32位平台上也至少 64 位宽. 错误由一个负返回值指示. 如果这个函数指针是 NULL, s

16、eek 调用会以潜在地无法预知的方式修改 file 结构中的位置计数器( 在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

17、_t, loff_t); 初始化一个异步读 - 可能在函数返回前不结束的读操作. 如果这个方法是 NULL, 所有的操作会由 read 代替进行(同步地)字符设备驱动字符设备驱动v重要数据结构重要数据结构,struct file_operation的成员的成员 ssize_t (*write) (struct file *, const char _user *, size_t, loff_t *); 发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数. ssize_t (*aio_write)(struct ki

18、ocb *, 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 方法应当返回

19、一个位掩码指示是否非阻塞的读或写是可能的, 并且, 可能地, 提供给内核信息用来使调用进程睡眠直到 I/O 变为可能. 如果一个驱动的 poll 方法为 NULL, 设备假定为不阻塞地可读可写字符设备驱动字符设备驱动v重要数据结构重要数据结构,struct file_operation的成员的成员int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); ioctl 系统调用提供了发出设备特定命令的方法(例如格式化软盘的一个磁道, 这不是读也不是写). 另外, 几个 ioctl 命令被内核识别而不必引用

20、fops 表. 如果设备不提供 ioctl 方法, 对于任何未事先定义的请求(-ENOTTY, 设备无这样的 ioctl), 系统调用返回一个错误. int (*mmap) (struct file *, struct vm_area_struct *); mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV.int (*open) (struct inode *, struct file *); 尽管这常常是对设备文件进行的第一个操作, 不要求驱动声明一个对应的方法. 如果这个项是 NULL, 设备打开一直成功, 但是你的驱动

21、不会得到通知.int (*flush) (struct file *); flush 操作在进程关闭它的设备文件描述符的拷贝时调用; 它应当执行(并且等待)设备的任何未完成的操作. 这个必须不要和用户查询请求的 fsync 操作混淆了. 当前, flush 在很少驱动中使用; SCSI 磁带驱动使用它, 例如, 为确保所有写的数据在设备关闭前写到磁带上. 如果 flush 为 NULL, 内核简单地忽略用户应用程序的请求字符设备驱动字符设备驱动v重要数据结构重要数据结构,struct file_operation的成员的成员int (*release) (struct inode *, str

22、uct 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 标志的改变. 异步

23、通知是一个高级的主题, 如果驱动不支持异步通知这个成员可以是NULL.int (*lock) (struct file *, int, struct file_lock *); lock 方法用来实现文件加锁; 加锁对常规文件是必不可少的特性, 但是设备驱动几乎从不实现它字符设备驱动字符设备驱动v重要数据结构重要数据结构,struct file_operation的成员的成员 ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t (*writev) (struct fil

24、e *, const struct iovec *, unsigned long, loff_t *); 这些方法实现发散/汇聚读和写操作. 应用程序偶尔需要做一个包含多个内存区的单个读或写操作; 这些系统调用允许它们这样做而不必对数据进行额外拷贝. 如果这些函数指针为 NULL, read 和 write 方法被调用( 可能多于一次 ). ssize_t (*sendfile)(struct file *, loff_t *, size_t, read_actor_t, void *); 这个方法实现 sendfile 系统调用的读, 使用最少的拷贝从一个文件描述符搬移数据到另一个. 例如,

25、 它被一个需要发送文件内容到一个网络连接的 web 服务器使用. 设备驱动常常使 sendfile 为 NULL. ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); sendpage 是 sendfile 的另一半; 它由内核调用来发送数据, 一次一页, 到对应的文件. 设备驱动上不实现 字符设备驱动字符设备驱动v重要数据结构重要数据结构,struct file_operation的成员的成员 unsigned long (*get_unmapped_area)(struct file

26、 *, unsigned long, unsigned long, unsigned long, unsigned long); 这个方法的目的是在进程的地址空间找一个合适的位置来映射在底层设备上的内存段. 这个任务通常由内存管理代码进行; 这个方法存在为了使驱动能强制特殊设备可能有的任何的对齐请求. 大部分驱动可以置这个方法为 NULL.vint (*check_flags)(int) 这个方法允许模块检查传递给 fnctl(F_SETFL.) 调用的标志.vint (*dir_notify)(struct file *, unsigned long); 这个方法在应用程序使用 fcntl

27、来请求目录改变通知时调用. 只对文件系统有用; 驱动不需要实现 dir_字符设备驱动字符设备驱动v重要数据结构重要数据结构,struct file的成员的成员 mode_t f_mode; 文件模式确定文件是可读的或者是可写的(或者都是), 通过位 FMODE_READ 和 FMODE_WRITE. 你可能想在你的 open 或者 ioctl 函数中检查这个成员的读写许可, 但是你不需要检查读写许可, 因为内核在调用你的方法之前检查. 当文件还没有为那种存取而打开时读或写的企图被拒绝, 驱动甚至不知道这个情况. loff_t f_pos; 当前读写位置. loff_t 在所有平台都是 64 位

28、( 在 gcc 术语里是 long long ). 驱动可以读这个值, 如果它需要知道文件中的当前位置, 但是正常地不应该改变它; 读和写应当使用它们作为最后参数而收到的指针来更新一个位置, 代替直接作用于 filp-f_pos. 这个规则的一个例外是在 llseek 方法中, 它的目的就是改变文件位置. unsigned int f_flags; 这些是文件标志, 例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驱动应当检查 O_NONBLOCK 标志来看是否是请求非阻塞操作; 其他标志很少使用. 特别地, 应当检查读/写许可, 使用 f_mode 而不是 f_flag

29、s. 所有的标志在头文件 中定义字符设备驱动字符设备驱动v重要数据结构重要数据结构,struct file的成员的成员 struct file_operations *f_op; 和文件关联的操作. 内核安排指针作为它的 open 实现的一部分, 接着读取它当它需要分派任何的操作时. filp-f_op 中的值从不由内核保存为后面的引用; 这意味着你可改变你的文件关联的文件操作, 在你返回调用者之后新方法会起作用. 例如, 关联到主编号 1 (/dev/null, /dev/zero, 等等)的 open 代码根据打开的次编号来替代 filp-f_op 中的操作. 这个做法允许实现几种行为,

30、在同一个主编号下而不必在每个系统调用中引入开销. 替换文件操作的能力是面向对象编程的方法重载的内核对等体.void *private_data; open 系统调用设置这个指针为 NULL, 在为驱动调用 open 方法之前. 你可自由使用这个成员或者忽略它; 你可以使用这个成员来指向分配的数据, 但是接着你必须记住在内核销毁文件结构之前, 在 release 方法中释放那个内存. private_data 是一个有用的资源, 在系统调用间保留状态信息, 我们大部分例子模块都使用它.struct dentry *f_dentry; 关联到文件的目录入口( dentry )结构. 设备驱动编写者

31、正常地不需要关心 dentry 结构, 除了作为 filp-f_dentry-d_inode 存取 inode 结构字符设备驱动字符设备驱动v重要数据结构重要数据结构,struct inode的成员的成员 inode 结构由内核在内部用来表示文件结构由内核在内部用来表示文件. 因此因此, 它和代表打开文件描述它和代表打开文件描述符的文件结构是不同的符的文件结构是不同的. 可能有代表单个文件的多个打开描述符的许多可能有代表单个文件的多个打开描述符的许多文件结构文件结构, 但是它们都指向一个单个但是它们都指向一个单个 inode 结构结构. inode 结构包含大量关于文件的信息结构包含大量关于文

32、件的信息. 作为一个通用的规则作为一个通用的规则, 这个结构这个结构只有只有 2 个成员对于编写驱动代码有用个成员对于编写驱动代码有用: dev_t i_rdev; 对于代表设备文件的节点, 这个成员包含实际的设备编号. struct cdev *i_cdev; struct cdev 是内核的内部结构, 代表字符设备;当节点指的是一个字符设备文件时,这个成员包含一个指针, 指向这个结构 i_rdev 类型在类型在 2.5 开发系列中改变了开发系列中改变了, 破坏了大量的驱动破坏了大量的驱动. 作为一个作为一个鼓励更可移植编程的方法鼓励更可移植编程的方法, 内核开发者已经增加了内核开发者已经增

33、加了 2 个宏个宏, 可用来从一可用来从一个个 inode 中获取主次编号中获取主次编号: unsigned int iminor(struct inode *inode); unsigned int imajor(struct inode *inode); 为了有良好的移植性为了有良好的移植性, 应当使用这些宏代替直接操作应当使用这些宏代替直接操作 i_字符设备驱动字符设备驱动v设备打开与关闭设备打开与关闭 open 方法在应用程序调用方法在应用程序调用open()系统调用时被调用系统调用时被调用,提供给驱动提供给驱动来做任何的初始化来准备后续的操作来做任何的初始化来准备后续的操作. 在大部

34、分驱动中在大部分驱动中, open 应当应当进行下面的工作进行下面的工作: 1) 检查设备特定的错误检查设备特定的错误(例如设备没准备好例如设备没准备好, 或者类似或者类似 的硬件错误的硬件错误 2) 如果它第一次打开如果它第一次打开, 初始化设备初始化设备 3) 如果需要如果需要, 更新更新 f_op 指针指针. 4) 分配并填充要放进分配并填充要放进 filp-private_data 的任何数据结构的任何数据结构 release 方法在应用程序调用方法在应用程序调用close()系统调用时被调用系统调用时被调用,它的角色它的角色是是 open 的反面的反面. 1) 释放释放 open 分

35、配在分配在 filp-private_data 中的任何东西中的任何东西 2) 在最后的在最后的 close 关闭设备关闭设备字符设备驱动字符设备驱动v与应用程序交换数据与应用程序交换数据 交换的方式有很多交换的方式有很多, 最直接的方法是在最直接的方法是在struct file_operation中的中的read/write方法中与用户空间的方法中与用户空间的buffer进行数据的交换进行数据的交换,但它们不能进行类似于但它们不能进行类似于memcpy这样这样的拷贝的拷贝,而是使用内核提供的接口来进行而是使用内核提供的接口来进行: #include unsigned long copy_to

36、_user(void _user *to, const void *from,unsigned long count); unsigned long copy_from_user( void *to,const void _user *from, unsigned long count); 实验实验:虚拟字符设备驱动虚拟字符设备驱动v目的目的: 实现一个简单的字符设备驱动实现一个简单的字符设备驱动,该设备驱动不该设备驱动不进行任何的硬件操作进行任何的硬件操作,只实现只实现open,release,read,write方法并测试数方法并测试数据与应用的交换据与应用的交换,通过实验通过实验,达到能

37、实现字达到能实现字 符设备的框架和对之有增强认识符设备的框架和对之有增强认识实验实验:虚拟字符设备驱动虚拟字符设备驱动v实验从实验从Hello world内核模块修改过来内核模块修改过来 增加头文件和宏定义增加头文件和宏定义 #include #include #define TEST_MAJOR 实验实验:虚拟字符设备驱动虚拟字符设备驱动v增加静态变量增加静态变量 static char drv_name = “test”; static char tmpbuf1024; static struct file_operations chardev_fops = .owner = THIS_M

38、ODULE, .read= test_chardev_read, .write= test_chardev_write, .open= test_chardev_open, .release = test_chardev_release, ; 注意要让函的声明在结构前可见注意要让函的声明在结构前可见实验实验:虚拟字符设备驱动虚拟字符设备驱动v增加增加open/release函数函数 static int test_chardev_open(struct inode *inode, struct file *file) printk(test_chardev_open.n);return 0;

39、static int test_chardev_release(struct inode *inode, struct file *file)printk(test_chardev_release.n);return 0;实验实验:虚拟字符设备驱动虚拟字符设备驱动v增加增加read函数函数 static ssize_t test_chardev_read (struct file *file, char _user *buf, size_t count, loff_t *offset) if(count 1024n”); return EINVAL; return count; 实验实验:虚拟

40、字符设备驱动虚拟字符设备驱动v增加增加write函数函数 static ssize_t test_chardev_write(struct file *file, const char _user *buf, size_t count, loff_t *offset) if(count 1024n”); return EINVAL; return count; 实验实验:虚拟字符设备驱动虚拟字符设备驱动v在模块的入口函数增加在模块的入口函数增加 if(register_chrdev(TEST_MAJOR, drv_name, &chardev_fops) printk(“fail to reg

41、ister devicen”); return 1; 实验实验:虚拟字符设备驱动虚拟字符设备驱动v在模块的出口函数增加在模块的出口函数增加 unregister_chrdev(TEST_MAJOR, drv_name);v编译加载该模块编译加载该模块v在文件系统中建立设备节点在文件系统中建立设备节点 #mknod /dev/my_dev c 233 实验实验:虚拟字符设备驱动虚拟字符设备驱动v测试应用程序测试应用程序test.c #include #include #include #include static char sz = “this is a test stringn”; stai

42、tc char readback1024; int main(void) int fd; fd = open(“/dev/my_dev”, O_RDWR); if(fd 0) printf(“I am writing my devicen”); write(fd, sz, strlen(sz) + 1); read(fd, readback, strlen(sz) + 1); printf(“the string I read back is : %sn”, readback); return 0; 编译编译: arm-linux-gcc o test test.c, 运行运行,观察结果观察结

43、果v设备控制设备控制ioctl用户应用程序调用接口用户应用程序调用接口int ioctl(int fd, unsigned long cmd, ); 设备驱动的相应接口设备驱动的相应接口int (*ioctl) ( struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg); 实验实验:虚拟字符设备驱动虚拟字符设备驱动实验实验:虚拟字符设备驱动虚拟字符设备驱动v选择选择 ioctl 命令命令cmd- 根据根据 Linux 内核惯例来为你的驱动选择内核惯例来为你的驱动选择 ioctl 号号, 你应当首你

44、应当首先检查先检查 include/asm/ioctl.h 和和 Documentation/ioctl-number.txt 有些有些cmd值是系统用值是系统用的的,驱动应当避开驱动应当避开 - cmd在在linux2.6中被分为中被分为4个位端个位端 类型类型, 命令值命令值, 传输方向传输方向, 和参数大小和参数大小 - 使用内核的宏来定义使用内核的宏来定义 #include 或或 _IOC(dir, type, nr, size) - 使用内核宏来提取使用内核宏来提取 _IO_DIR(nr), _IO_TYPE(nr), _IO_NR(nr), _IO_SIZE(nr) 实验实验:虚拟

45、字符设备驱动虚拟字符设备驱动v阻塞阻塞 I/O - 当我们的驱动在操作中某种条件没有满足当我们的驱动在操作中某种条件没有满足,为了提为了提高系统效率高系统效率,我们需要先放弃我们需要先放弃cpu时间时间(睡眠睡眠 ),直到条直到条件得到满足件得到满足,在应用程序中就表现为阻塞在应用程序中就表现为阻塞- 对于一个进程对于一个进程睡眠睡眠意味着它被标识为处于一个特意味着它被标识为处于一个特殊的状态并且从调度器的运行队列中去除殊的状态并且从调度器的运行队列中去除, 这个进程这个进程将不被在任何将不被在任何 CPU 上调度上调度,直到发生某些事情改变了直到发生某些事情改变了那个状态那个状态实验实验:虚

46、拟字符设备驱动虚拟字符设备驱动v睡眠规则睡眠规则1) 当你运行在原子上下文或中断处理时不能当你运行在原子上下文或中断处理时不能睡眠睡眠 2) 当你醒来当你醒来,结果是你不能关于你醒后的系统结果是你不能关于你醒后的系统状态做任何的假设状态做任何的假设, 并且你必须检查来确保你并且你必须检查来确保你在等待的条件是否得当满足在等待的条件是否得当满足 3) 是你的进程不能睡眠是你的进程不能睡眠,除非确信其他人除非确信其他人, 在在某处的某处的, 将唤醒它将唤醒它 实验实验:虚拟字符设备驱动虚拟字符设备驱动v进入睡眠进入睡眠- 驱动需要检查驱动需要检查filp-f_flags 中的中的 O_NONBLO

47、CK 标志标志 - 进程进入睡眠需要一个等待队列进程进入睡眠需要一个等待队列#include DECLARE_WAIT_QUEUE_HEAD(name); 或者动态地初始化或者动态地初始化wait_queue_head_t my_queue; init_waitqueue_head(&my_queue); 实验实验:虚拟字符设备驱动虚拟字符设备驱动v进入睡眠进入睡眠wait_event(queue, condition) wait_event_interruptible(queue, condition) wait_event_timeout(queue, condition, timeout) wait_event_interruptible_timeout(queue,condition, timeout) 实验实验:虚拟字符设备驱动虚拟字符设备驱动v唤醒进程唤醒进程void wake_up(wait_queue_head_t *queue); void wake_up_interruptible( wait_queue_head_t *queue); - 唤醒可以在任何的程序中进行唤醒可以在任何的程序中进行,包括中断处理包括中断处理

展开阅读全文
相关资源
正为您匹配相似的精品文档
相关搜索

最新文档


当前位置:首页 > 高等教育 > 研究生课件

电脑版 |金锄头文库版权所有
经营许可证:蜀ICP备13022795号 | 川公网安备 51140202000112号