1 / 49

Linux 系统驱动概述

Linux 系统驱动概述. 博创科技. Linux 系统驱动概述. 操作系统功能的划分 linux下的设备驱动的特点 Linux 设备的分类 典型的 Linux 字符型设备驱动结构 Linux 驱动中的数据结构 Linux 驱动中常用的操作函数 Linux 下的设备管理的问题. 操作系统功能的划分. 进程管理 内存管理 文件系统 设备控制 网络功能. linux 下的设备驱动的特点. linux 下的设备控制由驱动程序完成: Linux 下对外设的访问只能通过驱动程序 Linux 对于驱动程序有统一的接口,以文件的形式定义系统的驱动程序:

stefan
Télécharger la présentation

Linux 系统驱动概述

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Linux系统驱动概述 博创科技

  2. Linux系统驱动概述 • 操作系统功能的划分 • linux下的设备驱动的特点 • Linux设备的分类 • 典型的Linux字符型设备驱动结构 • Linux驱动中的数据结构 • Linux驱动中常用的操作函数 • Linux下的设备管理的问题

  3. 操作系统功能的划分 • 进程管理 • 内存管理 • 文件系统 • 设备控制 • 网络功能

  4. linux下的设备驱动的特点 • linux下的设备控制由驱动程序完成: • Linux下对外设的访问只能通过驱动程序 • Linux对于驱动程序有统一的接口,以文件的形式定义系统的驱动程序: Open、Release、read、write、ioctl… • 驱动程序属于内核的一部分,可以使用中断、DMA等操作 • 驱动程序需要在用户态和内核态之间传递数据,是应用程序与内核及外设通讯的桥梁 • uClinux下可以在应用层直接访问外设,操作寄存器,但是无法处理中断——不推荐使用

  5. Linux设备的分类 按照上述系统内核的功能,Linux中把系统的设备定义成如下三类: • 字符设备 • 支持面向字符的I/O操作 • 使用独立的缓冲区结构 • 顺序存取数据 • 块设备 • 仅支持面向块的I/O操作 • I/O操作都通过在内核地址空间中的I/O缓冲区进行 • 随机存取:支持几乎任意长度和任意位置上的I/O请求 • 网络设备

  6. 典型的Linux字符型设备驱动所包含的内容 • 初始化函数:xxx_init,向操作系统注册及硬件初始化(包括中断的request_irq和使能) • 主体代码 :file_operations里面注册的操作函数 • 中断处理函数 • 示例

  7. Linux驱动中的数据结构- file_operations file_operations数据结构,定义在include/linux/fs.h中,驱动程序很大一部分工作就是要“填写”结构体中定义的函数。 lseek:移动文件指针的位置,只能用于随机存取设备 read:读操作 write:写操作,与read类似 readdir:取得下一目录节点,文件系统相关的设备驱动程序使用 select:选择操作,如果没有提供将会认为设备已经准备好 ioctl:读、写以外的其它操作,参数为自定义的的命令数 mmap:内核空间到用户空间的映射 open:打开设备,为I/O操作做准备 release:即close操作

  8. Linux驱动中的数据结构- file_operations • Open函数 Open方法提供给驱动程序初始化设备的能力,从而为以后的设备操作做好准备,此外open操作一般还会递增使用计数,用以防止文件关闭前模块被卸载出内核。在大多数驱动程序中Open方法应完成如下工作: 递增使用计数 1.检查特定设备错误。 2.如果设备是首次打开,则对其进行初始化。 3.识别次设备号,如有必要修改f_op指针。 4.分配并填写filp->private_data中的数据。

  9. Linux驱动中的数据结构- file_operations • Release函数 与open方法相反,release 方法应完成如下功能: 1.释放由open分配的filp->private_data中的所有内容 2.在最后一次关闭操作时关闭设备 3.使用计数减一

  10. Linux驱动中的数据结构- file_operations • Read和Write函数 • read 方法完成将数据从内核拷贝到应用程序空间,write方法相反,将数据从应用程序空间拷贝到内核。对于者两个方法,参数filp是文件指针,count是请求传输数据的长度,buffer是用户空间的数据缓冲区,ppos是文件中进行操作的偏移量,类型为64位数。由于用户空间和内核空间的内存映射方式完全不同,所以不能使用象memcpy之类的函数,必须使用如下函数: • unsigned long copy_to_user (void *to,const void *from,unsigned long count); • unsigned long copy_from_user(void *to,const void *from,unsigned long count);

  11. Linux驱动中的数据结构- file_operations • ioctl函数 • ioctl方法主要用于对设备进行读写之外的其他控制,比如配置设备、进入或退出某种操作模式,这些操作一般都无法通过read/write文件操作来完成,比如在UP-NETARM2410-S中的SPI设备通道的选择操作,无法通过write操作控制,这就是ioctl操作的功能。 • 驱动程序中定义的ioctl 方法原型为: int (*ioctl) (struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg) inode 和 filp两个指针对应应用程序传递的文件描述符fd,cmd不会被修改地传递给驱动程序,可选的参数arg则无论用户应用程序使用的是指针还是其他类型值,都以unsigned long的形式传递给驱动。

  12. Linux驱动中的数据结构 • inode结构: • 提供关于设备文件的/dev/xxx的信息 • file结构: • 提供关于被打开的文件的信息

  13. Linux驱动中常用的操作函数 • 申请中断: • int request_irq(unsigned int irq, void (*handler)(int, void *, struct pt_regs *), unsigned long irq_flags, const char * devname, void *dev_id) • 释放中断: • void free_irq(unsigned int irq, void *dev_id); • 申请内存:void * kmalloc(unsigned int len, int priority); • 释放内存:void kfree(void * obj); • 访问I/O端口:inb,outb,inw,outw;inl,outl • 时钟、定时器有关的系统函数:add_timer,del_timer,init_timer • 打开和关闭中断允许:cli(),sti() • 内核打印:printk

  14. 设备管理的问题 如今,Linux 支持很多不同种类的硬件。这意味着/dev中都有数百个特殊文件来表示所有这些设备。而且,这些特殊文件中大多数甚至不会映射到系统中存在的设备上

  15. Linux设备驱动的路径 • Linux的设备以文件的形式存在于/dev目录下 • 设备文件是特殊文件,使用ls /dev -l命令可以看到: • c-字符设备 • b-块设备 • l-符号连接 crw------- 1 root root 10, 7 Aug 31 2002 amigamouse1 crw------- 1 root root 10, 134 Aug 31 2002 apm_bios brw-rw---- 1 root disk 29, 0 Aug 31 2002 aztcd lr-xr-xr-x 1 root root 9 Dec 26 05:52 tty0 -> /dev/vc/0

  16. 主设备号和次设备号 • 主设备号标识设备对应的驱动程序 • 一个驱动程序可以控制若干个设备,次设备号提供了一种区分它们的方法 • 系统增加一个驱动程序就要赋予它一个主设备号。这一赋值过程在驱动程序的初始化过程中进行: 对于字符设备: int register_chrdev(unsigned int major, const char*name,struct file_operations *fops); • 对于查看/dev目录下的设备的主次设备号可以使用如下命令: [/mnt/yaffs]ls /dev -l crw------- 1 root root 5, 1 Jan 1 00:00 console crw------- 1 root root 5, 64 Jan 1 00:00 cua0

  17. 动态分配设备号 • 在Documentation/device.txt文件中可以找到已经静态分配给大部分设备的列表 • 由于许多数字已经分配了,为新设备选择一个唯一的号码是很困难的 • 如果调用register_chrdev时的major为零,函数就会选择一个空闲号码并做为返回值返回

  18. 动态分配的问题 动态分配的主设备号不能保证总是一样的,无法事先创建设备节点 • 可以从/proc/devices读取 cat /proc/devices • 利用脚本动态创建设备文件节点

  19. 使用设备文件系统--devfs • 在Linux 2.4的内核里引入了devfs来解决linux下设备文件管理的问题 • 在驱动程序中通过devfs_register()函数创建设备文件系统的节点 其原型为: devfs_register(devfs_handle_t dir, const char *name, unsigned int flags,unsigned int major, unsigned int minor, umode_t mode, void *ops, void *info) • 系统启动的时候mount设备文件系统 • 所有需要的设备节点都由内核自动管理。/dev目录下只有挂载的设备 • 注:在linux-2.6.12之后的内核的解决办法:udev文件系统 • http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html

  20. 使用设备文件系统-- udev • udev 完全在用户态 (userspace) 工作,利用设备加入或移除时内核所发送的hotplug 事件 (event) 来工作。 • 关于设备的详细信息是由内核输出 (export) 到位于 /sys 的 sysfs 文件系统的。所有的设备命名策略、权限控制和事件处理都是在用户态下完成的。 • 与此相反,devfs 是作为内核的一部分工作的。

  21. 创建设备节点--mknod • mknod /dev/xxx c major minor • 关于主次设备号: major + minor • 上层应用对驱动的调用: • fd=open(“/dev/xxx”,...); file_operations xxx_open • read(fd,buffer,count);  file_operations xxx_ read

  22. Linux驱动程序原理 • fd=open(“/dev/xxx”,...) file_operations xxx_open • write(fd,buffer,count) file_operations xxx_ write • read(fd,buffer,count) file_operations xxx_ read

  23. Linux下的驱动加载方式 • 一种是直接编译到内核,当内核启动之后,新的驱动程序随之运行; • 二是编译为模块,动态加载运行 • 对模块操作需要使用module-utiles: • insmod将编译的模块直接插入内核 • rmmod从内核中卸载模块 • lsmod显示已安装的模块 • gcc编译参数: -D__KERNEL__ -DMODULE –I$(KERNELDIR_INCLUDE) • 在调试的过程中一般使用模块动态加载的方式,它的调试效率较高。当驱动调试完成后,在发行的过程就集成进内核。但编译进内核是某些驱动运行的唯一方法。例如:console驱动,flash驱动和对至少一种文件系统的支持等等。

  24. Linux下的驱动加载方式 • module_init:insmod时自动调用,负责模块初始化工作。 • 对应的操作是module_init • module_exit:卸载时调用,负责清除工作。 • 对应的操作是module_exit • 参见:kernel/include/linux/init.h

  25. module_init(1) include/linux/init.h中 #define module_init(x) __initcall(x); #define __initcall(fn) \ static initcall_t __initcall_##fn __init_call = fn static initcall_t __initcall_name_of_initialization_routine __init_call = name_of_initialization_routine

  26. module_init(2) • include/linux/init.h中定义: #define __init_call __attribute__ ((unused,__section__ (".initcall.init"))) typedef int (*initcall_t)(void); • arch/arm/vmlinux-armv.lds.in文件中: __initcall_start = .; *(.initcall.init) __initcall_end = .; . = ALIGN(4096); __init_end = .; • init/main.c文件中定义了do_initcalls函数

  27. __init宏 在include/linux/init.h中 对于非模块加载的驱动程序: #define __init __attribute__ \ ((__section__ (".text.init"))) • 通过__init,会把函数中的代码放到.text.init段。这个段在系统启动以后会被释放。在系统内核启动以后,会看到: Freeing init memory: 68K

  28. Linux下驱动的调试 1、 使用printk函数 printk函数中可以使用附加不同的日志级别或消息优先级 2 、使用/proc文件系统 内核利用它向外输出信息 3、使用ioctl方法 ioctl系统调用会调用驱动的ioctl方法,我们可以通过设置不同的命名号来编写一些测试函数,使用ioctl系统调用在用户级调用这些函数进行调试。 4、使用strace命令进行调试 strace命令是一个功能强大的工具,它可以显示用户空间的程序发出的全部系统调用,不仅可以显示调用,还可以显示调用的参数和用符号方式表示的返回值。

  29. Linux下的中断驱动 • 中断概念 • 基本定义:中断,异常,中断源,中断嵌套,中断优先级等 • 中断向量:标识中断的无符号数,组成中断向量表。ARM有7个: • 复位:当复位电平有效时产生。程序跳转到复位异常处理程序处 • 未定义指令:遇到不能处理的指令时产生 • 软件中断:执行SWI指令产生。用于实现系统调用功能 • 指令预取中止:预取指令的地址不存在,或该地址不允许当前指令访问,发出中止信号,但指令被执行时产生异常 • 数据中止:数据访问指令的地址不存在,或该地址不允许当前指令访问时,产生数据中止异常 • IRQ:标准中断请求 • FIQ:快速中断请求

  30. Linux下的中断驱动

  31. ARM的中断过程 • 中断的进入 • 将下一条指令的地址存入相应连接寄存器LR • 将CPSR复制到相应的SPSR中 • 根据中断/异常类型,强制设置CPSR的运行模式位 • 强制PC从相关的异常向量地址取下一条指令执行,从而跳转到相应的异常处理程序 • 从中断返回 • 将连接寄存器LR的值减去相应的偏移量后送到PC中 • 将SPSR复制回CPSR中 • 如果进入时设置了中断禁止位,那么清除该标志 • 中断返回 • 注意:给IRQ脚中断信号前,必须先打开该中断的使能寄存器和正确设置对应的屏蔽寄存器。当这两个寄存器都设置正确了,中断产生了,CPU保存当前程序运行环境,跳到中断入口,ARM芯片一般是0x18地址处。设置好中断向量,中断向量一般是个跳转语句,跳到正式的中断处理过程,在这里可以关闭所有中断,清中断,处理等等,然后退出。某些处理器一定要清中断,否则下次再给中断信号时就没有反应了。

  32. 中断相关函数说明 • int set_external_irq(int irq, int edge, int pullup); • 功能:设置中断线相应的属性,完成注册中断前的设置 • 参数: • irq:需要设置的中断号。可以自定义数值,也可以直接使用kernel/arch/arm/kernel/irqs.h中预定义的值。例如: IRQ_EINT0, IRQ_EINT4_7,IRQ_DMA0等 • edge:中断触发方式,可以是EXT_LOWLEVEL,EXT_HIGHLEVEL,EXT_FALLING_EDGE,EXT_RISING_EDGE,EXT_BOTH_EDGES • pullup:是否拉高。可以设为GPIO_PULLUP_EN和 GPIO_PULLUP_DIS方式。当设为GPIO_PULLUP_EN时,在GPIO没有输入输出时,线路保持高电平。否则始终保持低电平。一般需要设置成拉高状态 • void enable_irq(unsigned int irq); • 参数: • irq:set_external_irq中设置的中断线号 • 功能:使能所选的中断线 • void disable_irq(unsigned int irq); • 参数: • 功能:使得所选择的中断线无效 • 和enable_irq配对使用,实现相反功能

  33. 中断相关函数说明 • int request_irq(unsigned int irq, void (*handler)(int,void *,struct pt_regs *), unsigned long irq_flags,const char * devname, void * dev_id); • 功能:定位中断源,使能中断线和IRQ处理函数 • irq:外设所使用的中断号 • handler函数指针:设备驱动程序所实现的ISR函数 • irqflags:中断请求类型标志,可以是下列值的“或”: • SA_SHIRQ:中断是共享的。此时要求参数dev_id必须有效,不能为NULL; IRQ号参数irq不能大于NR_IRQS;且handler指针不能为NULL。否则将出现错误号为-22的参数无效错,无法注册中断服务程序 • SA_INTERRUPT:当处理中断时,其他局部中断不可用 • SA_SAMPLE_RANDOM:中断可以作为随机计量单位熵使用 • devname指针:设备名字字符串 • dev_id:指向全局唯一的设备标识ID,void类型的指针,供驱动程序自行解释

  34. 中断相关函数说明 • void free_irq(unsigned int irq, void *dev_id); • 功能:释放和中断号绑定的中断处理函数 • 注意:必须和register_irq()配对使用,它的参数必须和register_irq()里注册的参数一致。dev_id不能是中断服务程序地址,否则执行rmmod删除该将出现错误,必须重启系统才能再次insmod该模块 • 示例

  35. 功能描述 • 编写简单的虚拟硬件驱动程序并进行调试 • 参考驱动代码框架如下,其中的demo_read,demo_write函数完成驱动的读写接口功能,do_write函数实现将用户写入的数据逆序排列,通过读取函数读取转换后的数据。这里只是演示接口的实现过程和内核驱动对用户的数据的处理。demo_ioctl函数演示ioctl调用接口的实现过程。

  36. static int demo_open(struct inode *inode, struct file *filp) { /* 初始化一些资源,如将缓存区清零等。有些资源在设备未打开即未被 * 使用之前最好不要打开,如中断、时钟等。 */ memset(wbuff, 0, BUFFSIZE); memset(rbuff, 0, BUFFSIZE); count = 0; return 0; } static int demo_close(struct inode *inode, struct file *filp) { return 0; }

  37. static ssize_t demo_read(struct file *filp, char *buf, size_t cnt, loff_t *off) { if(count < cnt) cnt = count; if(cnt) copy_to_user(buf, rbuff, cnt); /*向应用程序传输数据*/ return cnt; } static ssize_t demo_write(struct file *filp, const char *buf, size_t cnt, loff_t *off) { if(cnt > BUFFSIZE) cnt = BUFFSIZE; copy_from_user(wbuff, buf, cnt); /*接受来自应用程序的数据*/ count = cnt; return cnt; }

  38. static int demo_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) { switch(cmd) { default: break; } return 0; }

  39. static struct file_operations demo_fops = { owner: THIS_MODULE, write: demo_write, read: demo_read, ioctl: demo_ioctl, open: demo_open, release: demo_close, };

  40. static int __init demo_init(void) { int result; /* 为设备驱动程序分配资源,比如分配一些内存 *我们分配两小片的缓存区给驱动程序。当向设备写数据时,数据被保存在 *写缓存区(wbuff)中,当从设备读数据时, 数据从读缓存区(rbuff)中读出。驱动程序负责将数据从wbuff写入rbuff。*/ wbuff = kmalloc(BUFFSIZE, GFP_KERNEL); rbuff = kmalloc(BUFFSIZE, GFP_KERNEL); /* 注册设备 */ result = register_chrdev(DEMO_MAJOR,, “demo”, &demo_fops); if(result < 0) { /* 如果设备注册出错,打印错误提示,返回错误代码 */ printk(KERN_ERR “Cannot register demo device.”); return -EIO; } return 0; /* 一般返回0表示没有错误 */ }

  41. static void __exit demo_exit(void) { int result; /*移除设备*/ result = unregister_chrdev(DEMO_MAJOR, "demo"); if(result < 0) { printk(KERN_ERR "Cannot remove demo device.\n"); return; } /*释放存储区*/ if(wbuff) kfree(wbuff); if(rbuff) kfree(rbuff); return; } MODULE_LICENSE("GPL"); module_init(demo_init); /*标记模块初始化函数*/ module_exit(demo_exit); /*标记模块清除函数*/

  42. 用户测试程序test_demo.c • 编写test_demo.c测试驱动程序 • 参见实验指导书

  43. 实验步骤 • 参见实验指导书

  44. 设备文件系统devfs • 2.4版本Linux内核中引入了设备文件系统Device Filesystem(devfs),所有的设备文件作为一个可以挂装的文件系统,这样就可以被文件系统进行统一管理,从而设备文件就可以挂装到任何需要的地方。命名规则也发生了变化,一般将主设备建立一个目录,再将具体的子设备文件建立在此目录下。比如在UP-NETARM2410-S中的MTD 设备为:/dev/mtdblock/0。使用devfs要求内核编译时定义了符号CONFIG_DEVFS_FS

  45. 与devfs有关的函数 devfs_register函数用于向内核注册设备文件,其原型为: devfs_handle_t devfs_register(devfs_handle_t dir, const char *name, unsigned int flags,unsigned int major, unsigned int minor, umode_t mode, void *ops, void *info) devfs_mk_dir函数用于将设备文件创建在一个特定的目录内,其原型: devfs_handle_t devfs_mk_dir(devfs_handle_t dir, const char *name, void *info) devfs_unregister函数用于移除设备文件,其原型为: void devfs_unregister(devfs_handle_t deventry);

  46. 参数说明 • dir新创建的设备文件的父目录,一般设为null, 表示父目录为/dev • name设备名称,如想包含子目录,可以直接在名字中包含’/’ • flagsdevfs标志的位掩码。 • major主设备号如果在flags参数中指定为DEVFS_FL_AUTO_DEVNUM,则主次设备号就无用了。 • minor次设备号mode设备的访问模式 • ops设备的文件操作数据结构指针 • infofilp->private_data的默认值。 • deventry指向devfs_register调用后获得的devfs入口

  47. 下面在驱动程序里使用devfs,请各位老师根据已给出的程序完成使用devfs的demo程序下面在驱动程序里使用devfs,请各位老师根据已给出的程序完成使用devfs的demo程序

  48. ……………………………………………………………………………………………………………… #ifdef CONFIG_DEVFS_FS static devfs_handle_t devfs_demo_dir, devfs_demoraw; #endif ……………………………………………………… static int __init demo_init(void) { /*other code here*/ #ifdef CONFIG_DEVFS_FS devfs_demo_dir = devfs_mk_dir(NULL, "demo", NULL); devfs_demoraw = devfs_register(devfs_demo_dir, "0", DEVFS_FL_DEFAULT, DEMO_MAJOR, DEMO_MINOR, S_IFCHR | S_IRUSR | S_IWUSR, &demo_fops, NULL); #endif /*other code here*/ } static void __exit demo_exit(void) { /*other code here*/ #ifdef CONFIG_DEVFS_FS devfs_unregister(devfs_demoraw); devfs_unregister(devfs_demo_dir); #endif /*other code here*/

  49. 在驱动模块成功插入后,会在/dev下面建立一个叫做demo的设备文件,若使用devfs,则无需建立设备节点,插入模块后系统会自动建立/dev/demo/0文件节点。在驱动模块成功插入后,会在/dev下面建立一个叫做demo的设备文件,若使用devfs,则无需建立设备节点,插入模块后系统会自动建立/dev/demo/0文件节点。

More Related