




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、第一章Linux内核概述Linux 内核是一个庞大而复杂的操作系统的核心,不过尽管庞大,但是却采用子系统和分层的概念很好地进行了组织。在本文中,您将探索 Linux 内核的总体结构,并学习一些主要的子系统和核心接口。您还可以通过其他 IBM 文章的链接更深入地进行学习。由于本章的目标是对 Linux 内核进行介绍并探索其体系结构和主要组件,因此首先回顾一下 Linux 的简短历史,然后从较高的层次审视 Linux 内核的体系结构,最后介绍它的主要子系统。Linux 内核具有超过 600 万行的代码,因此本文不可能进行完整的介绍。请使用指向其他内容的链接进一步学习。1.1 linux简短历史尽管
2、 Linux 绝对是最流行的开源操作系统,但是相对于其他操作系统的漫长历史来说,Linux 的历史非常短暂。在计算机出现早期,程序员是使用硬件语言在裸硬件上进行开发的。缺少操作系统就意味着在某个时间只有一个应用程序(和一个用户)可以使用这些庞大而又昂贵的设备。早期的操作系统是在 20 世纪 50 年代开发的,用来提供简单的开发体验。二十年后,Andrew Tanenbaum 创建了一个微内核版本的 UNIX®,名为 MINIX(代表 minimal UNIX),它可以在小型的个人计算机上运行。这个开源操作系统在 20 世纪 90 年代激发了 Linus Torvalds 开发 Lin
3、ux 的灵感(请参看图 1 所示)。Linux 快速从一个个人项目进化成为一个全球数千人参与的开发项目。对于 Linux 来说,最为重要的决策之一是采用 GPL(GNU General Public License)。在 GPL 保护之下,Linux 内核可以防止商业使用,并且它还从 GNU 项目(Richard Stallman 开发,其源代码要比 Linux 内核大得多)的用户空间开发受益。这允许使用一些非常有用的应用程序,例如 GCC(GNU Compiler Collection)和各种 shell 支持。1.2linux内核组成1.2.1linux内核简介现在让我们从一个比较高的高度
4、来审视一下 GNU/Linux 操作系统的体系结构。您可以从两个层次上来考虑操作系统,如图 2 所示。最上面是用户(或应用程序)空间。这是用户应用程序执行的地方。用户空间之下是内核空间,Linux 内核正是位于这里。GNU C Library (glibc)也在这里。它提供了连接内核的系统调用接口,还提供了在用户空间应用程序和内核之间进行转换的机制。这点非常重要,因为内核和用户空间的应用程序使用的是不同的保护地址空间。每个用户空间的进程都使用自己的虚拟地址空间,而内核则占用单独的地址空间。Linux 内核可以进一步划分成 3 层。最上面是系统调用接口,它实现了一些基本的功能,例如 read 和
5、 write。系统调用接口之下是内核代码,可以更精确地定义为独立于体系结构的内核代码。这些代码是 Linux 所支持的所有处理器体系结构所通用的。在这些代码之下是依赖于体系结构的代码,构成了通常称为 BSP(Board Support Package)的部分。这些代码用作给定体系结构的处理器和特定于平台的代码。Linux内核属性在讨论大型而复杂的系统的体系结构时,可以从很多角度来审视系统。体系结构分析的一个目标是提供一种方法更好地理解源代码,这正是本文的目的。Linux 内核实现了很多重要的体系结构属性。在或高或低的层次上,内核被划分为多个子系统。Linux 也可以看作是一个整体,因为它会将所
6、有这些基本服务都集成到内核中。这与微内核的体系结构不同,后者会提供一些基本的服务,例如通信、I/O、内存和进程管理,更具体的服务都是插入到微内核层中的。每种内核都有自己的优点,不过这里并不对此进行讨论。随着时间的流逝,Linux 内核在内存和 CPU 使用方面具有较高的效率,并且非常稳定。但是对于 Linux 来说,最为有趣的是在这种大小和复杂性的前提下,依然具有良好的可移植性。Linux 编译后可在大量处理器和具有不同体系结构约束和需求的平台上运行。一个例子是 Linux 可以在一个具有内存管理单元(MMU)的处理器上运行,也可以在那些不提供 MMU 的处理器上运行。Linux 内核的 uC
7、linux 移植提供了对非 MMU 的支持。Linux内核子系统现在使用图 3 中的分类说明 Linux 内核的主要组件。系统调用接口SCI 层提供了某些机制执行从用户空间到内核的函数调用。正如前面讨论的一样,这个接口依赖于体系结构,甚至在相同的处理器家族内也是如此。SCI 实际上是一个非常有用的函数调用多路复用和多路分解服务。在 ./linux/kernel 中您可以找到 SCI 的实现,并在 ./linux/arch 中找到依赖于体系结构的部分。进程管理进程管理的重点是进程的执行。在内核中,这些进程称为线程,代表了单独的处理器虚拟化(线程代码、数据、堆栈和 CPU 寄存器)。在用户空间,通
8、常使用进程 这个术语,不过 Linux 实现并没有区分这两个概念(进程和线程)。内核通过 SCI 提供了一个应用程序编程接口(API)来创建一个新进程(fork、exec 或 Portable Operating System Interface POSIX 函数),停止进程(kill、exit),并在它们之间进行通信和同步(signal 或者 POSIX 机制)。进程管理还包括处理活动进程之间共享 CPU 的需求。内核实现了一种新型的调度算法,不管有多少个线程在竞争 CPU,这种算法都可以在固定时间内进行操作。这种算法就称为 O(1) 调度程序,这个名字就表示它调度多个线程所使用的时间和调度
9、一个线程所使用的时间是相同的。 O(1) 调度程序也可以支持多处理器(称为对称多处理器或 SMP)。您可以在 ./linux/kernel 中找到进程管理的源代码,在 ./linux/arch 中可以找到依赖于体系结构的源代码。内存管理内核所管理的另外一个重要资源是内存。为了提高效率,如果由硬件管理虚拟内存,内存是按照所谓的内存页 方式进行管理的(对于大部分体系结构来说都是 4KB)。Linux 包括了管理可用内存的方式,以及物理和虚拟映射所使用的硬件机制。不过内存管理要管理的可不止 4KB 缓冲区。Linux 提供了对 4KB 缓冲区的抽象,例如 slab 分配器。这种内存管理模式使用 4K
10、B 缓冲区为基数,然后从中分配结构,并跟踪内存页使用情况,比如哪些内存页是满的,哪些页面没有完全使用,哪些页面为空。这样就允许该模式根据系统需要来动态调整内存使用。为了支持多个用户使用内存,有时会出现可用内存被消耗光的情况。由于这个原因,页面可以移出内存并放入磁盘中。这个过程称为交换,因为页面会被从内存交换到硬盘上。内存管理的源代码可以在 ./linux/mm 中找到。虚拟文件系统虚拟文件系统(VFS)是 Linux 内核中非常有用的一个方面,因为它为文件系统提供了一个通用的接口抽象。VFS 在 SCI 和内核所支持的文件系统之间提供了一个交换层(请参看图 4)。在 VFS 上面,是对诸如 o
11、pen、close、read 和 write 之类的函数的一个通用 API 抽象。在 VFS 下面是文件系统抽象,它定义了上层函数的实现方式。它们是给定文件系统(超过 50 个)的插件。文件系统的源代码可以在 ./linux/fs 中找到。文件系统层之下是缓冲区缓存,它为文件系统层提供了一个通用函数集(与具体文件系统无关)。这个缓存层通过将数据保留一段时间(或者随即预先读取数据以便在需要是就可用)优化了对物理设备的访问。缓冲区缓存之下是设备驱动程序,它实现了特定物理设备的接口。网络堆栈网络堆栈在设计上遵循模拟协议本身的分层体系结构。回想一下,Internet Protocol (IP) 是传输
12、协议(通常称为传输控制协议或 TCP)下面的核心网络层协议。TCP 上面是 socket 层,它是通过 SCI 进行调用的。socket 层是网络子系统的标准 API,它为各种网络协议提供了一个用户接口。从原始帧访问到 IP 协议数据单元(PDU),再到 TCP 和 User Datagram Protocol (UDP),socket 层提供了一种标准化的方法来管理连接,并在各个终点之间移动数据。内核中网络源代码可以在 ./linux/net 中找到。设备驱动程序Linux 内核中有大量代码都在设备驱动程序中,它们能够运转特定的硬件设备。Linux 源码树提供了一个驱动程序子目录,这个目录又
13、进一步划分为各种支持设备,例如 Bluetooth、I2C、serial 等。设备驱动程序的代码可以在 ./linux/drivers 中找到。依赖体系结构的代码尽管 Linux 很大程度上独立于所运行的体系结构,但是有些元素则必须考虑体系结构才能正常操作并实现更高效率。./linux/arch 子目录定义了内核源代码中依赖于体系结构的部分,其中包含了各种特定于体系结构的子目录(共同组成了 BSP)。对于一个典型的桌面系统来说,使用的是 i386 目录。每个体系结构子目录都包含了很多其他子目录,每个子目录都关注内核中的一个特定方面,例如引导、内核、内存管理等。这些依赖体系结构的代码可以在 ./
14、linux/arch 中找到。1.3Linux内核的其他特性如果 Linux 内核的可移植性和效率还不够好,Linux 还提供了其他一些特性,它们无法划分到上面的分类中。作为一个生产操作系统和开源软件,Linux 是测试新协议及其增强的良好平台。Linux 支持大量网络协议,包括典型的 TCP/IP,以及高速网络的扩展(大于 1 Gigabit Ethernet GbE 和 10 GbE)。Linux 也可以支持诸如流控制传输协议(SCTP)之类的协议,它提供了很多比 TCP 更高级的特性(是传输层协议的接替者)。Linux 还是一个动态内核,支持动态添加或删除软件组件。被称为动态可加载内核模
15、块,它们可以在引导时根据需要(当前特定设备需要这个模块)或在任何时候由用户插入。Linux 最新的一个增强是可以用作其他操作系统的操作系统(称为系统管理程序)。最近,对内核进行了修改,称为基于内核的虚拟机(KVM)。这个修改为用户空间启用了一个新的接口,它可以允许其他操作系统在启用了 KVM 的内核之上运行。除了运行 Linux 的其他实例之外, Microsoft® Windows® 也可以进行虚拟化。惟一的限制是底层处理器必须支持新的虚拟化指令。第二章 linux内核模块开发Linux设备驱动会以内核模块的形式出现,因此学会编写Linux内核模块编程是学习linux设备
16、驱动的先决条件。1.1linux内核模块简介Linux内核的整体结构非常庞大,其包含的组件非常多。我们如何把需要的部分都包含在内核中呢?把需要的功能都编译到linux内核。.以模块方式扩展内核功能。为了使学生对模块建立初步的感性认识,我们先来看一个最简单的内核模块”hello world”,代码如下:#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static int hello_init(void) printk("hell
17、o worldn”); return 0; static void hello_exit(void) printk(<1> "hello module exitn "); module_init(hello_init); module_exit(hello_exit); MODULE_AUTHOR("zky");MODULE_DESCRIPTION("A simple hello Module ");MODULE_VERSION("V1.0");这个最简单的内核模块只包含内核加载函数、卸载函数和对Dua
18、l BSD/GPL许可权限的声明以及一些描述信息。编译会产生hello.ko目标文件,通过”insmod ./hello.ko”命令可以加载它,通过”rmmod hello”命令可以卸载它,加载时输出”hello world”, 卸载时输出”hello module exit”,查看输出信息可通过dmesg命令。内核模块中用于输出的函数式内核空间的printk()而非用户空间的printf(),printk()的用法和printf()相似,但前者可定义输出级别。Printk()可作为一种最基本的内核调试手段。printk有8个loglevel,定义在<linux/kernel.h>
19、中:#define KERN_EMERG "<0>" /* system is unusable */#define KERN_ALERT "<1>" /* ac
20、tion must be taken immediately*/#define KERN_CRIT "<2>" /* critical conditions */#define KERN_ERR "<3>" /* error
21、 conditions */#define KERN_WARNING "<4>" /* warning conditions */#define KERN_NOTICE &
22、quot;<5>" /* normal but significant condition */#define KERN_INFO "<6>" /* informational */#define
23、 KERN_DEBUG "<7>" /* debug-level messages */未指定优先级的默认级别定义在/kernel/printk.c中:#define DEFAULT_MESSAGE_LOGLEVEL 4 /* KERN_WARNING */当优先级的值小于console_loglevel这个整数变量的值,信息才能显示出来。而console_loglevel的初始值DEFAULT_CONSOLE_LOGLEVE
24、L也定义在/kernel/printk.c中: #define DEFAULT_CONSOLE_LOGLEVEL 7 /* anything MORE serious than KERN_DEBUG */在linux系统中,使用lsmod命令可以获得系统中加载了的所有模块以及模块间的依赖关系,例如:Lsmod命令实际上读取并分析/proc/modules文件,与上述lsmod命令结果对应的/proc/modules文件如下:内核中已加载模块的信息也存在于/sys/module目录下,加载hello.ko后,内核中将包含/sys/module/hello目录,该目录下又包含一个refcnt文件和
25、一个sections目录,在/sys/module/hello目录下运行”tree -a”得到如下目录树:Modprobe命令比insmod命令要强大,它在加载某模块时会同时加载该模块所依赖的其他模块。使用modprob命令加载的模块若以”modprobe r filename”的方式卸载将同时其他依赖的模块。使用modinfo <模块名>命令可以获得模块的信息,包括模块的作者、模块的说明、模块所支持的参数以及vermagic,如下所示:1.2模块的编译接下来我们来简单看看模块是如何构造的,模块的构造和用户空间应用程序的构造过程有很大的不同。实际上对本章先前给出的”hello wo
26、rld”示例来说,下面一行就足以了:Obj-m := hello.o如果大家熟悉make但对2.6内核构造系统还不熟悉的话,则可能会对此makefile的工作方式感到疑惑。毕竟上面这行并不是makefile文件的常见格式。问题的答案就是内核的构造系统处理了其余的问题。上面的赋值语句说明了又一个模块需要从目标文件hello.o中构造,而从目标文件中构造的模块名称为hello.ko。如果我们要构造的模块名称为module.ko,并有两个源文件生成(比如file1.c和file2.c),则正确的makefile可如下编写:Obj-m := module.oModule-objs :=file1.o
27、file2.o为了让上面这种类型的makefile文件正常工作,必须在大的内核构造系统环境中调用它们。如果内核源码保存在/kernel-2.6目录中,则用来构造模块的make命令应该为(在包含模块源代码和makefile的目录中键入):Make C /kernel-2.6 M=pwd modules上述命令首先改变目录到-C选项指定的位置(既内核源代码目录),其中保存有内核的顶层makefile文件,M=选项让该makefile在构造modules目标之前返回到模块源代码目录。然后modules目标指向obj-m变量中设定的模块;在上面的例子中,我们将该变量设置成了module.o。上面这样的
28、make命令还是有些烦人,因此内核开发者又开发了一种makefile方法,这种方法将使得内核树之外的模块构造变得更加容易,其技巧就是用下面的方法来编写makefile:#如果已定义KERNELRELEASE,则说明是从内核构造系统调用的,因此可利用其内建语句。Ifneq ($(KERNELRELEASE),)obj-m :=hello.o#否则,是直接从命令行调用的,这时要调用内核构造系统ElseKERNELDIR ?=/KERNEL-2.6PWD := $(shell pwd)Default:$(MAKE) C $(KERNELDIR) M=$(PWD) modulesendif需要注意的是
29、上面的makefile文件并不完整;一个真正的makefile文件应该包含通常用来清除无用文件的目标,安装模块的目标等等。1.3linux内核模块的结构一个linux内核模块主要由以下几个部分组成:l 模块加载函数(必须)当通过insmo或modprobe命令加载内核模块时,模块的加载函数会自动被内核执行,完成本模块相关初始化工作。l 模块卸载函数(必须)当通过rmmod命令卸载模块时,模块的卸载函数会自动被内核执行,完成与模块加载函数相反的功能。l 模块许可证声明(必须)模块许可证(LICENCE)声明描述内核模块的许可权限,如果不声明LICENCE,模块被加载时将收到内核被污染的警告。在2
30、.6内核中,可接受得LICENSE包括“GPL”、“GPL v2”、“GPL and additional right”、“Dual BSD/GPL”、“Dual MPL/GPL”和“Proprietary”。大多数情况下,内核模块应遵循GPL兼容许可权。Linux2.6内核模块最常见的是以MODULE_LICENSE(“Dual BSD/GPL”)语句声明模块采用BSD/GPL双LICENSE。l 模块参数(可选)模块参数是模块被加载的时候可以被传递给他的值,它本身对应模块内部的全局变量。l 模块导出符号(可选)内核模块可以导出符号(symbol,对应于函数或变量),这样其他模块可以使用本模
31、块中的变量或函数。l 模块作者等信息声明(可选)。1.4模块加载函数Linux内核模块加载函数一般以_init标识声明,典型的模块加载函数的形式如下:Static int _init initialization_function(void) /初始化代码Module_init(initialization_function);模块加载函数必须以“module_init(函数名)”的形式指定。它返回整形值,若初始化成功,应返回0。而在初始化失败时。应该返回错误编码。在linux内核里,错误编码是一个负值,在<linux/errno.h>中定义,包含-ENODEV、-ENOMEM之类
32、的符号值。返回相应的错误编码是种非常好的习惯,因为只有这样,用户程序才可以利用perror等方法把它们转换成有意义的错误信息字符串。在linux2.6内核中,所有标识为_init的函数在连接的时候都会放在.init.text(这是module_init宏在目标代码中增加的一个特殊区段,用于说明内核初始化函数的所在位置)这个区段中,此外,所有的_init函数在区段.initcall.init中还保存着一份函数指针,在初始化时内核会通过这些函数指针调用这些_init函数,并在初始化完成后释放init区段(包括.init.text和.initcall.init等)。所以大家应注意不要在结束初始化后仍
33、要使用的函数上使用这个标记。1.5模块卸载函数Linux内核卸载模块函数一般以_exit标识声明,典型的模块卸载函数的形式如下:Satic void _exit cleanup_function(void) /释放代码Module_exit(cleanup_function);模块卸载函数在模块卸载时被调用,不返回任何值,必须以”module_exit(函数名)”的形式来指定。一般来说,模块卸载函数完成与模块加载函数相反的功能:l 如果模块加载函数注册了 XXX模块,则模块卸载函数应注销XXX。l 若模块加载函数动体申请了内存,则模块卸载函数应释放该内存。l 若模块加载函数申请了硬件资源,则模
34、块卸载函数应释放这些硬件资源。l 若模块加载函数开启了硬件,则模块卸载函数应关闭硬件。和_init一样_exit也可以使对应函数在运行完成后自动回收内存。1.6模块参数我们可以用”module_param(参数名,参数类型,参数读/写权限)”为模块定义一个参数,例如下列代码定义了一个整形参数和一个字符指针参数:Static char *book_name = “linux 模块”;Satic int num = 4000;Module_param(num, int, S_IRUGO);Module_param(book_name, charp, S_IRUGO);在装载内核模块时,用户可以向模
35、块传递参数,形式为”insmod (或 modprobe) 模块名 参数名=参数值”,如果不传递,参数将使用模块内定义的默认值。参数类型可以是byte、short、ushort、int、uint、long、ulong、charp、bool、或invbool(布尔的反),在模块被编译时会将module_param中声明的类型与变量定义的类型进行比较,判断是否一致。模块被加载后,在/sys/module/目录下将出现以此模块命名的目录。当“参数读/写权限”为0时,表示此参数不存在sysfs文件系统下对应的文件节点,如果此模块存在“参数读/写权限”不为0的命令行参数,在此模块的目录下还将出现para
36、meters目录,包含一系列以参数名命名的文件节点,这些文件的权限值就是传入module_param()的“参数读/写权限”,而文件的内容为参数的值。现在我们定义一个包含两个参数的模块,并观察模块加载时被传递参数和不传递参数时的输出。#include <linux/init.h> #include <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); static char *book_name = "dissecting Linux Device Driver" static int
37、num = 4000; static int book_init(void) printk(KERN_INFO " book name:%sn",book_name); printk(KERN_INFO " book num:%dn",num); return 0; static void book_exit(void) printk(KERN_INFO " Book module exitn "); module_init(book_init); module_exit(book_exit); module_param(num, i
38、nt, S_IRUGO); module_param(book_name, charp, S_IRUGO|S_IWUGO); MODULE_AUTHOR("zky");MODULE_DESCRIPTION("A simple Module for testing module params");MODULE_VERSION("V1.0");对上述模块运新“insmod book.ko”命令加载,相应输出都为模块内的默认值,通过查看“/var/log/messages”日志文件可以看到内核的输出,如下所示:当用户运行“insmod bo
39、ok.ko book_name=mybook num=3000”命令时,输出的是用户传递的参数,如下所示:1.7导出符号Linux2.6的/proc/kallsyms文件对应着内核符号表,它记录了符号以及符号所在的内存地址。模块可使用如下宏导出符号到内核符号表:EXPORT_SYMBOL(符号名);EXPORT_SYMBOL_GPL(符号名);导出的符号将可以被其他模块使用,使用前声明一下即可。EXPORT_SYMBOL_GPL()只适用于包含GPL许可权的模块。如下代码给出了一个导出整数加、减运算函数符号的内核模块的例子。#include <linux/init.h> #incl
40、ude <linux/module.h> MODULE_LICENSE("Dual BSD/GPL"); int add_integar(int a,int b) return a+b; int sub_integar(int a,int b) return a-b; EXPORT_SYMBOL(add_integar);EXPORT_SYMBOL(sub_integar);从/proc/kallsyms文件中找出add_integar、sub_integar相关信息:1.8模块声明与描述在linux模块中,我们可以使用MODULE_AUTHOR、MODULE_
41、DESCRIPTION、MODULE_VERSION、MODULE_DEVICE_TABLE、MODULE_ALIAS分别声明模块的作者、描述、版本、设备表和别名,例如:MODULE_AUTHOR(author);MODULE_DESCRIPTION(description);MODULE_VERSION(version);MODULE_DEVICE_TABLE(device table);MODULE_ALIAS(alternate_name);第三章linux字符设备驱动开发Linux下的设备驱动程序被组织为一组完成不同任务的函数的集合,通过这些函数使得linux的设备操作犹如文件一般。在
42、应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作,如open()、close()、read()、write() 等。Linux主要将设备分为二类:字符设备和块设备。字符设备是指设备发送和接收数据以字符的形式进行;而块设备则以整个数据缓冲区的形式进行。字符设备的驱动相对比较简单。3.1 Linux字符设备驱动结构3.1.1 cdev结构体在Linux2.6内核中一个字符设备用cdev结构来描述,其定义如下:struct cdev struct kobject kobj;&
43、#160; struct module *owner; /所属模块 const struct file_operations *ops; /文件操作结构,在写驱动时,其结构体内的大部分函数要被实现
44、 struct list_head list; dev_t dev; /设备号,int 类型,高12位为主设备号,低20位为次设备号 unsigned int count;下面一组函数用来对cdev结构体进行操作:void cdev_init(struct cdev *, const s
45、truct file_operations *); /初始化,建立cdev和file_operation 之间的连接。 struct cdev *cdev_alloc(void); /动态申请一个cdev内存。void cdev_put(struct cdev *p); /释放。int cdev_add(struct cdev *, dev_t, unsigned); /注册设备,通常发生在驱动模块的加载函数中。 void cdev_del(struct cdev *);/注销设备,通常发生在驱动模块的卸载函数中。3.1.2
46、分配和释放设备号主设备号表示设备对应的驱动程序;次设备号由内核使用,用于正确确定设备文件所指的设备。内核用dev_t类型(<linux/types.h>)来保存设备编号,dev_t是一个32位的数,12位表示主设备号,20为表示次设备号。在实际使用中,是通过<linux/kdev_t.h>中定义的宏来转换格式。 (dev_t)->主设备号、次设备号 MAJOR(dev_t dev) MINOR(dev_t dev) 主设备号、次设备号->(dev_t) MKDEV(int major,int minor)
47、160;建立一个字符设备之前,驱动程序首先要做的事情就是获得设备编号。其这主要函数在<linux/fs.h>中声明:int register_chrdev_region(dev_t first, unsigned int count,char *name); /指定设备编号int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name); /动态生成设备编号void unregister_chrdev_region(d
48、ev_t first, unsigned int count); /释放设备编号分配之设备号的最佳方式是:默认采用动态分配,同时保留在加载甚至是编译时指定主设备号的余地。3.1.3 file_operations结构体file_operation就是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用。读取file_operation中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。 在系统内部,I/O设备的存取操作通过特定的入口点来进行,而这组特定的入口点恰恰是由设备驱动程序提供
49、的。通常这组设备驱动程序接口是由结构file_operations结构体向系统说明的,它定义在include/linux/fs.h中。struct file_operations struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char _user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, char _user *, size_t, loff_t);ssize_t (*wr
50、ite) (struct file *, const char _user *, size_t, loff_t *);ssize_t (*aio_write) (struct kiocb *, const char _user *, size_t, loff_t);int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);int (*ioctl) (struct inode *, struct file *, unsigne
51、d int, unsigned long);long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);long (*compat_ioctl) (struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *, fl_owner_t id);int
52、 (*release) (struct inode *, struct file *);int (*fsync) (struct file *, struct dentry *, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*readv) (struct file *, const struct iovec *,
53、unsigned long, loff_t *);ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(stru
54、ct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*dir_notify)(struct file *filp, unsigned long arg);int (*flock) (struct file *, int, struct file_lock *);ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int
55、);ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);传统上, 一个 file_operation 结构或者其一个指针称为 fops( 或者它的一些变体). 结构中的每个成员必须指向驱动中的函数, 这些函数实现一个特别的操作, 或者对于不支持的操作留置为 NULL.一般情况下,进行设备驱动程序的设计只是比较注重下面的几个方法:struct file_operations *_ops= .owner = THIS_MODULE,
56、60;.llseek = *_llseek, .read = *_read, .write = *_write, .ioctl = *_ioctl, .open = *_open, .release = *_release, ;3.2 虚拟字符设备驱动3.2.1 加载与卸载设备驱动注册一个独立的cdev设备的基本过程如下:1、为struct cdev 分配空间(如果已经将struct cdev 嵌入到自己的设备的特定结构体中,并分配了空间,这步略过!)struct cdev *my_c
57、dev = cdev_alloc();2、初始化struct cdev void cdev_init(struct cdev *cdev, const struct file_operations *fops) 3、初始化cdev.ownercdev.owner = THIS_MODULE;4、注册cdev,cdev_add(struct cdev *, dev_t ,unsigned)向内核注册一个字符设备,通知内核struct cdev的信息(在执行这步之前必须确定你对struct cdev的以上设置已经完成!)从系统中移除一个字符设备:void cdev_del(struct cdev
58、*p)以下是scull中的初始化代码(之前已经为struct scull_dev 分配了空间):/* * Set up the char_dev structure for this device. */static void scull_setup_cdev(struct scull_dev *dev, int index) int err, devno = MKDEV(scull_major, scull_minor + index);
59、60;cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; /这句可以省略,在cdev_init中已经做过 err = cdev_add (&dev->cdev, devno, 1); /* Fail gracefully if need be 这步值得注意*/ if (err) print
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 机场新冠疫情防控检查职责
- 供应链管理中的风险防控措施与预案
- 热力公司2025年设备维护管理计划
- 家电生产线工艺流程及管理
- 中班下学期环保教育活动计划
- IT行业销售工程师技术沟通心得体会范文
- 教育机构课程组织协调方案
- 2025-2030中国PA热熔胶颗粒行业市场发展趋势与前景展望战略研究报告
- 2025-2030中国CD播放机行业市场发展趋势与前景展望战略研究报告
- 2025-2030中国5G手机行业市场运行分析及发展趋势与投资研究报告
- 2025年辅警招聘考试试题库及答案(全优)
- (一模)2025年广东省高三高考模拟测试 (一) 英语试卷(含官方答案及详解)
- 退役军人无人机培训宣传
- 退役军人保密教育
- DB44∕T 370-2006 东风螺养殖技术规范繁殖与苗种培育技术
- 7.1我国法治建设的历程 课件高中政治统编版必修三政治与法治
- 2025年仲裁法考试试题及答案
- 2025年电梯修理作业证理论考试练习题(100题)含答案
- 交通运输部南海航海保障中心推迟公开招聘笔试高频重点模拟试卷提升(共500题附带答案详解)
- T-ZJWL 001-2024 大宗商品供应链金融动产质押监管仓储服务规范
- Unit 3 Faster,highter,stronger Understanding Ideas The road to success群文阅读说课稿 2024-2025学年高中英语人教版选择性必修第一册
评论
0/150
提交评论