收藏 分销(赏)

android驱动开发和移植详解.docx

上传人:仙人****88 文档编号:8921729 上传时间:2025-03-08 格式:DOCX 页数:27 大小:76.82KB 下载积分:10 金币
下载 相关 举报
android驱动开发和移植详解.docx_第1页
第1页 / 共27页
android驱动开发和移植详解.docx_第2页
第2页 / 共27页


点击查看更多>>
资源描述
android驱动开发和移植详解 本文出处:《Android 底层开发技术实战详解——内核、移植和驱动》 我们开发的 Android 驱动程序是基于 Linux 内核的,本文首先介绍移植 Android 系统的基本知识和基本原理, 这也是为驱动开发打下坚实基础,知其然也知其所以然;最后我们将通过深入浅出的案例学习驱动开发过程。 1.1 驱动开发需要做的工作 Android 作为当前最流行的手机操作系统之一,受到了广大开发人员和商家的青睐。Android 正在逐渐形成一个蓬勃发展的产业,带来了无限商机。既然 Android 这么火爆,我们程序员可以 学习它的哪一方面的内容呢?本书的驱动开发又属于哪一领域呢?接下来将为读者奉上这两个 问题的答案。 Android 是一个开放的系统,这个系统的体积非常庞大,开发人员无须掌握整个 Android 体 系中的开发知识,只需熟悉其中某一个部分即可收获自己的未来。 从具体功能上划分,Android 开发主要分为如下三个领域。 1. 移植开发移动电话系统 移植开发的目的是构建硬件系统,并且移植 Android 的软件系统,最终形成手机产品。 2. Android 应用程序开发 应用程序开发的目的是开发出各种 Android 应用程序,然后将这些应用程序投入 Android 市 场,进行交易。 Android 的应用程序开发是 Android 开发的另一个方面。从开发的角度来看,这种形式的开 发可以基于某个硬件系统,在没有硬件系统的情况下也可以基于 Linux 或者 Windows 下的 Android 模拟器来开发。这种类型的开发工作在 Android 系统的上层。 事实上,在 Android 软件系统中,第 3 个层次(Java 框架)和第 4 个层次(Java 应用)之间 的接口也就是 Android 的系统接口(系统 API)。这个层次是标准的接口,所有的 Android 应用 程序都是基于这个层次的接口开发出来的。Android 系统的第 4 个层次就是一组内置的 Android 应用程序。 Android 应用程序开发者开发的应用程序和 Android 系统的第 4 个层次的应用程序其实是一 个层次的内容。例如,Android 系统提供了基本的桌面程序,开发者可以根据 Android 的系统接 口,实现另外一个桌面程序,提供给用户安装使用。根据 Android 系统的接口开发游戏,也是 Android 底层开发技术实战详解——内核、移植和驱动 Android 应用程序开发的一个重要方向。 上述两种类型的开发结构如图 3-1 所示。 主屏幕 联系人 电话 浏览器 „„ 应用程序 活动管理器 窗口管理器 内容提供器 视图系统 通知管理器 应用程序 框架 应用 程序 开发 包管理器 电话管理器 资源管理器 本地管理器 XMPP服务 接口管理器 包管理器 包管理器 持久层库 Android 运行 环境 Dalvik虚拟 机器 包管理器 位图及矢量 浏览器引擎 库 2D图形引 擎 中间协议 libc函数库 显示驱动 相机驱动 蓝牙驱动 Flash内存驱 动 Binder IPC 驱动 Linux 内核层 能源管理 USB驱动 键盘驱动 WiFi驱动 音频驱动 移植 开发 各种硬件 图 3-1 Android 开发的领域 3. Android 系统开发 系统开发的目的是升级或改造 Android 中已经存在的应用和架构,开发出有自己特色的手机 系统。例如联想手机乐 Phone 就是在 Android 基础上打造的一款适合国人使用习惯的手机系统, 如图 3-2 所示。 58 图 3-2 乐 Phone Android 系统开发的一个比较典型的示例就是当系统需要某种功能时,为了给 Java 层次的应 用程序提供调用的接口,需要从底层到上层的整体开发,具体步骤如下所示。 增加 C 或者 C++和本地库。 定义 Java 层所需要的类(系统 API)。 将所需要的代码封装成 JNI。 结合 Java 类和 JNI。 应用程序调用 Java 类。 一定要慎重对待对 Android 系统 API 的改动工作,因为系统 API 的稍微变动就可能会涉及 Android 应用程序的兼容问题。 Android 系统本身的功能也处于增加和完善的过程中,因此 Android 系统的开发也是一个重 要的方面。这种类型的开发涉及 Android 软件系统的各个层次。在更多的时候,Android 系统开 发只是在不改变系统 API 的情况下修正系统的缺陷,增加系统的稳定性。 从商业模式的角度来看,第一种类型的开发和第二种类型的开发是 Android 开发的主流。事 实上,移动电话的制造者主要进行第一种类型的开发,产品是 Android 实体手机;公司、个人和 团体都可以进行第二种类型的开发,其产品是不同的 Android 应用程序。 在 Android 的开发过程中,每一种类型的开发都只涉及整个 Android 系统的一个子集。在 Android 系统中有着众多开发点,这些开发点相互独立,又有内在联系。在开发的过程中,需要 重点掌握目前开发点涉及的部分。 背景说明:Android API 的接口是用 Java 语言编写的,通常更改接口函数的格式(参数、返回值)、 常量的值等内容就相当于更改系统 API。 Android 是一个开放的系统,适用于从最低端直到最高端的智能手机。核心的 Android API 在每部手机上都可使用,但仍然有一些 API 接口有一些特别的适用范围:这就是 所谓的“可选 API”。 在为某手机编写 Android 应用程序时,需要多少地对 Android API 进行修改,然后实现 我们需要的功能。例如使用 Android API 添加蓝牙程序和 Wi-Fi 程序。在更改 Android API 时,通常更改其接口函数的格式(参数、返回值)和常量值等内容。但是 Android API 毕竟是谷歌推出的一系列标准,为了方便以后系统的升级,建议大家不改变 Android API 的格式,而是只改变 Android API 的具体行为,也就是说为这些固定的 Android API 编写各种各样的应用程序。 1.2 Android 移植 本书讲解的是 Android 驱动方面的开发知识,由图 3-1 可知,驱动开发是底层的应用,属于 Linux 内核层的工作。因为驱动是系统和硬件之间的载体,涉及不同硬件的应用问题,所以需要 做系统移植工作。本节将简要介绍系统移植方面的有关问题。 59 Android 底层开发技术实战详解——内核、移植和驱动 1.2.1 移植的任务 Android 移植开发的最终目的是开发手机产品。从开发者的角度来看,这种类型的开发以具 有硬件系统为前提,在硬件系统的基础上构建 Android 软件系统。这种类型的开发工作在 Android 系统的底层。在软件系统方面,主要的工作集中在以下两个方面。 (1)Linux 中的相关设备驱动程序 驱动程序是硬件和上层软件的接口。在 Android 手机系统中,需要基本的屏幕、触摸屏、键 盘等驱动程序,以及音频、摄像头、电话的 Modem、Wi-Fi、蓝牙等多种设备驱动程序。 (2)Android 本地框架中的硬件抽象层 在 Android 中硬件抽象层工作在用户空间,介于驱动程序和 Android 系统之间。Android 系统 对硬件抽象层通常都有标准的接口定义,在开发过程中,实现这些接口也就给 Android 系统提供 了硬件抽象层。 上述两个部分综合起来相互结合,共同完成了 Android 系统的软件移植。移植成功与否取决 于驱动程序的品质和对 Android 硬件抽象层接口的理解程度。Android 移植开发的工作由核心库、 Dalvik 虚拟机、硬件抽象层、Linux 内核层和硬件系统协同完成,具体结构如图 3-3 所示。 核心库 Dalvik虚拟机 硬件抽象层 Linux内核层(各种驱动) 硬件系统 移植 图 3-3 Android 移植结构 1.2.2 移植的内容 在 Android 系统中,在移植过程中主要移植驱动方面的内容。Android 移植主要分为如下几 个类型。 Ÿ 基本图形用户界面(GUI)部分:包括显示部分、用户输入部分和硬件相关的加速部分,还 包括媒体编解码和 OpenGL 等。 Ÿ 音视频输入输出部分:包括音频、视频输出和摄像头等。 Ÿ 连接部分:包括无线局域网、蓝牙、GPS 等。 Ÿ 电话部分:包括通话、GSM 等。 Ÿ 附属部件:包括传感器、背光、振动器等。 具体来说主要移植下面的内容。 Ÿ Display 显示部分:包括 FrameBuffer 驱动和 Gralloc 模块。 Ÿ Input 用户输入部分:包括 Event 驱动和 EventHub。 Ÿ Codec 多媒体编解码:包括硬件 Codec 驱动和 Codec 插件,例如 OpenMax。 Ÿ 3D Accelerator(3D 加速器)部分:包括硬件 OpenGL 驱动和 OpenGL 插件。 60 Ÿ Audio 音频部分:包括 Audio 驱动和 Audio 硬件抽象层。 Ÿ Video Out 视频输出部分:包括视频显示驱动和 Overlay 硬件抽象层。 Ÿ Camera 摄像头部分:包括 Camera 驱动(通常是 v4l2)和 Camera 硬件抽象层。 Ÿ Phone 电话部分:包括 Modem 驱动程序和 RIL 库。 Ÿ GPS 全球定位系统部分:包括 GPS 驱动(例如串口)和 GPS 硬件抽象层。 Ÿ Wi-Fi 无线局域网部分:包括 Wlan 驱动和协议和 Wi-Fi 的适配层。 Ÿ Blue Tooth 蓝牙部分:包括 BT 驱动和协议及 BT 的适配层。 Ÿ Sensor 传感器部分:包括 Sensor 驱动和 Sensor 硬件抽象层。 Ÿ Vibrator 振动器部分:包括 Vibrator 驱动和 Vibrator 硬件抽象层。 Ÿ Light 背光部分:包括 Light 驱动和 Light 硬件抽象层。 Ÿ Alarm 警告器部分:包括 Alarm 驱动和 RTC 系统和用户空间调用。 Ÿ Battery 电池部分:包括电池部分驱动和电池的硬件抽象层。 注意:在 Android 系统中有很多组件,但并不是每一个组件都需要移植,例如那些纯软的组件就 不需要移植。像浏览器引擎虽然需要下层的网络支持,但是实际上并不需要直接为其移植 网络接口,而是通过无线局域网或者电话系统数据连接来完成标准的网络接口。 1.2.3 驱动开发的任务 前面介绍了 Android 系统的基本知识和移植内容,那么究竟在驱动开发领域需要做什么工作 呢?我们的任务就是为某一个将要在 Android 系统上使用的硬件开发一个驱动程序。因为 Android 是基于 Linux 的,所以开发 Android 驱动其实就是开发 Linux 驱动。 对于大部分子系统来说,硬件抽象层和驱动程序都需要根据实际系统的情况来实现,例如传 感器部分、音频部分、视频部分、摄像头部分和电话部分。另外也有一些子系统的硬件抽象层是 标准的,只需实现 Linux 内核中的驱动程序即可,例如输入部分、振动器部分、无线局域网部分 和蓝牙部分等。对于有标准的硬件抽象层的系统,有的时候通常也需要做一些配置工作。 随着 Android 系统的更新和发展,它已经不仅仅是一个移动设备的平台,也可以用于消费类 电子和智能家电,例如 3.0 以后的版本主要是针对平板电脑的,另外电子书、数字电视、机顶盒、 固定电话等都逐渐使用 Android 系统。在这些平台上,通常需要实现比移动设备更少的部件。一 般来说,包括显示和用户输入的基本用户界面部分是需要移植的,其他部分是可选的。例如电话 系统、振动器、背光、传感器等一般不需要在非移动设备系统上实现,一些固定位置设备通常不 需要实现 GPS 系统。 1.3 Android 对 Linux 的改造 Android 内核是基于 Linux 2.6 内核的,这是一个增强内核版本,除了修改部分 Bug 外,还提 供了用于支持 Android 平台的设备驱动。Android 不但使用了 Linux 内核的基本功能,而且对 Linux 进行了改造,目的是实现更为强大的通信功能。 61 1.3.1 Android 对 Linux 内核文件的改动 在本书第 1 章的内容中已经讲解过 Linux 内核的基本知识,其实 Android 对 Linux 内核也进 行了改动,这些改动保存在下面的文件中。 drivers/misc/kernel_debugger.c drivers/misc/pmem.c drivers/misc/qemutrace/qemu_trace_sysfs.c drivers/misc/qemutrace/qemu_trace.c drivers/misc/qemutrace/qemu_trace.h drivers/misc/uid_stat.c drivers/staging/android/lowmemorykiller.c drivers/staging/android/logger.c drivers/staging/android/timed_output.h drivers/staging/android/ram_console.c drivers/staging/android/timed_gpio.c drivers/staging/android/logger.h drivers/staging/android/binder.h drivers/staging/android/binder.c drivers/staging/android/timed_output.c drivers/staging/android/timed_gpio.h drivers/rtc/alarm.c drivers/rtc/rtc-goldfish.c drivers/net/pppolac.c drivers/net/ppp_mppe.c drivers/net/pppopns.c drivers/video/goldfishfb.c drivers/switch/switch_class.c drivers/switch/switch_gpio.c drivers/char/dcc_tty.c drivers/char/goldfish_tty.c drivers/watchdog/i6300esb.c drivers/input/misc/gpio_event.c drivers/input/misc/gpio_input.c drivers/input/misc/gpio_output.c drivers/input/misc/keychord.c drivers/input/misc/gpio_axis.c drivers/input/misc/gpio_matrix.c drivers/input/keyreset.c drivers/input/keyboard/goldfish_events.c drivers/input/touchscreen/synaptics_i2c_rmi.c drivers/usb/gadget/android.c drivers/usb/gadget/f_adb.h drivers/usb/gadget/f_mass_storage.h drivers/usb/gadget/f_adb.c drivers/usb/gadget/f_mass_storage.c 62 drivers/mmc/host/goldfish.c drivers/power/goldfish_battery.c drivers/leds/ledtrig-sleep.c drivers/mtd/devices/goldfish_nand_reg.h drivers/mtd/devices/goldfish_nand.c kernel/power/earlysuspend.c kernel/power/consoleearlysuspend.c kernel/power/fbearlysuspend.c kernel/power/wakelock.c kernel/power/userwakelock.c kernel/cpuset.c kernel/cgroup_debug.c kernel/cgroup.c mm/ashmem.c include/linux/ashmem.h include/linux/switch.h include/linux/keychord.h include/linux/earlysuspend.h include/linux/android_aid.h include/linux/uid_stat.h include/linux/if_pppolac.h include/linux/usb/android.h include/linux/wifi_tiwlan.h include/linux/android_alarm.h include/linux/keyreset.h include/linux/synaptics_i2c_rmi.h include/linux/android_pmem.h include/linux/kernel_debugger.h include/linux/gpio_event.h include/linux/wakelock.h include/linux/if_pppopns.h net/ipv4/sysfs_net_ipv4.c net/ipv4/af_inet.c net/ipv6/af_inet6.c net/bluetooth/af_bluetooth.c security/commoncap.c fs/proc/base.c 1.3.2 为 Android 构建 Linux 的操作系统 如果我们以一个原始的 Linux 操作系统为基础,改造成一个适合于 Android 的系统,所要做 的工作其实非常简单,仅仅是增加适用于 Android 的驱动程序。在 Android 中有很多 Linux 系统 的驱动程序,将这些驱动程序移植到新系统的步骤非常简单,具体来说有以下三个步骤。 编写新的源代码。 在 KConfig 配置文件中增加新内容。 63 在 Makefile 中增加新内容。 在 Android 系统中,通常会使用 FrameBuffer 驱动、Event 驱动、Flash MTD 驱动、Wi-Fi 驱 动、蓝牙驱动和串口等驱动程序。并且还需要音频、视频、传感器等驱动和 sysfs 接口。移植的 过程就是移植上述驱动的过程,我们的工作是在 Linux 下开发适用于 Android 的驱动程序,并移 植到 Android 系统。 在 Android 中添加扩展驱动程序的基本步骤如下所示。 在 Linux 内核中移植硬件驱动程序,实现系统调用接口。 在 HAL 中把硬件驱动程序的调用封装成 Stub。 为上层应用的服务实现本地库, Dalvik 虚拟机调用本地库来完成上层 Java 代码的实现。由 编写 Android 应用程序,提供 Android 应用服务和用户操作界面。 1.4 内核空间和用户空间接口是一个媒介 驱动程序是供系统使用硬件的,也就是说,驱动程序是介于系统和硬件之间的桥梁。 Linux在 下开发这些中间桥梁的驱动程序时,需要用到内核空间和用户空间之间的接口。 1.4.1 内核空间和用户空间的相互作用 现在,越来越多的应用程序需要编写内核级和用户级的程序来一起完成具体的任务。通常采 用以下模式:首先,编写内核服务程序利用内核空间提供的权限和服务来接收、处理和缓存数据; 然后,编写用户程序和先前完成的内核服务程序进行交互。具体来说,可以利用用户程序来配置 内核服务程序的参数,提取内核服务程序提供的数据,当然也可以向内核服务程序输入待处理数 据。 比较典型的应用包括 Netfilter(内核服务程序:防火墙);Iptable(用户级程序:规则设置 程序);IPSEC(内核服务程序:VPN 协议部分);IKE(用户级程序:VPN 密钥协商处理); 当然还包括大量的设备驱动程序及相应的应用软件。这些应用都是由内核级和用户级程序通过相 互交换信息来一起完成特定任务的。 1.4.2 系统和硬件之间的交互 实现硬件和系统的交互是底层开发的主要任务之一。在 Linux 平台下有如下 5 种实现此功能 的方式。 1. 编写自己的系统调用 系统调用是用户级程序访问内核最基本的方法。目前 Linux 大致提供了二百多个标准的系统 调用(具体请参考内核代码树中的“include/asm-i386/unistd.h” “arch/i386/kernel/entry.S”和文件), 并且允许我们添加自己的系统调用来实现和内核的信息交换。假如我们想建立一个系统调用日志 系统,将所有的系统调用动作记录下来,以便进行入侵检测,此时可以编写一个内核服务程序, 该程序负责收集所有的系统调用请求,并将这些调用信息记录到在内核中自建的缓冲里。我们无 64 法在内核里实现复杂的入侵检测程序,因此必须将该缓冲里的记录提取到用户空间。最直截了当 的方法是自己编写一个新系统调用实现这种提取缓冲数据的功能。当内核服务程序和新系统调用 都实现后,就可以在用户空间里编写用户程序执行入侵检测任务了,入侵检测程序可以定时、轮 询或在需要的时候调用新系统调用从内核提取数据,然后进行入侵检测。 2. 编写驱动程序 Linux/UNIX 的一个特点就是把所有的东西都看做文件(every thing is a file)。系统定义了简 洁完善的驱动程序界面,客户程序可以用统一的方法通过这个界面和内核驱动程序交互。而大部 分系统的使用者和开发者已经非常熟悉这种界面及相应的开发流程了。 驱动程序运行于内核空间,用户空间的应用程序通过文件系统中“/dev/”目录下的一个文件 来和它交互。这就是我们熟悉的文件操作流程:open()→read()→write()→ioctl()→close()。 注意:并不是所有的内核驱动程序都是这个界面,网络驱动程序和各种协议栈的使用就不大一致, 比如套接口编程虽然也有 open(),close()等概念,但它的内核实现及外部使用方式都和普 通驱动程序有很大差异。 这里先不谈设备驱动程序在内核中要做的中断响应、设备管理、数据处理等工作,在此先把 注意力集中在它与用户级程序交互这一部分。操作系统为此定义了一种统一的交互界面,就是前 面所说的 open()、read()、write()、ioctl()和 close()等。每个驱动程序按照自己的需要做独立实现, 把自己提供的功能和服务隐藏在这个统一界面下。客户级程序选择需要的驱动程序或服务(其实 就是选择“/dev/”目录下的文件),按照上述界面和文件操作流程,就可以跟内核中的驱动交互 了。用面向对象的概念更容易解释,系统定义了一个抽象的界面(Abstract Interface),每个具体 的驱动程序都是这个界面的实现(Implementation)。 由此可见,驱动程序也是用户空间和内核信息交互的重要方式之一。从本质上来说,ioctl、 read、和 write 也是通过系统调用去完成的,只是这些调用已被内核进行了标准封装和统一定义。 因此用户不必像填加新系统调用那样必须修改内核代码,重新编译新内核,使用虚拟设备只需要 通过模块方法将新的虚拟设备安装到内核中(insmod 上)就能方便使用。 大致可以将 Linux 中的设备分为如下三类。 Ÿ 字符设备:包括那些必须以顺序方式,像字节流一样被访问的设备。 Ÿ 块设备:指那些可以用随机方式,以整块数据为单位来访问的设备,如硬盘等。 Ÿ 网络接口:通常指网卡和协议栈等复杂的网络输入输出服务。 如果将我们的系统调用日志系统用字符型驱动程序的方式实现,整个过程就非常简单了。可 以将内核中收集和记录信息的那一部分编写成一个字符设备驱动程序。虽然没有实际对应的物理 设备,但是 Linux 的设备驱动程序本来就是一个软件抽象,它可以结合硬件提供服务,也完全可 以作为纯软件提供服务。在驱动程序中,可以使用 open()来启动服务,用 read()返回处理好的记 录,用 ioctl()设置记录格式等,用 close()停止服务,write()没有用到,那么我们可以不去实现它。 然后在“/dev/”目录下建立一个设备文件,对应我们新加入内核的系统调用日志系统驱动程序。 3. 使用 proc 文件系统 proc 是 Linux 提供的一种特殊的文件系统,使用它的目的就是提供一种便捷的用户和内核间 65 的交互方式。proc 以文件系统作为使用界面,使应用程序可以以文件操作的方式安全、方便地获 取系统当前运行的状态和其他一些内核数据信息。 proc 文件系统多用于监视、管理和调试系统,平常使用的 ps 和 top 等管理工具就是利用 proc 来读取内核信息的。除了读取内核信息外,proc 文件系统还提供了写入功能,所以我们可以利用 它来向内核输入信息。比如通过修改 proc 文件系统下的系统参数配置文件“/proc/sys”,可以直 接在运行时动态更改内核参数。 除了系统已经提供的文件条目,通过 proc 为我们留的接口,允许在内核中创建新的条目从而 与用户程序共享信息数据。比如可以为系统调用日志程序(无论是作为驱动程序还是作为单纯的 内核模块)在 proc 文件系统中创建新的文件条目,在此条目中显示系统调用的使用次数,每个单 独系统调用的使用频率等。也可以增加另外的条目用于设置日志记录规则。 4. 使用虚拟文件系统(VFS) 很多内核开发者认为利用 ioctl()系统调用往往会使得系统调用意义不明确,而且难控制。而 将信息放入到 proc 文件系统中会使信息组织混乱,所以不赞成过多地使用此系统。他们的建议是 实现一种孤立的虚拟文件系统来代替 ioctl()和 proc。这是因为文件系统接口清楚,而且便于用户 空间访问,同时利用虚拟文件系统使得利用脚本执行系统管理任务更加方便、有效。 下面举例来说如何通过虚拟文件系统修改内核信息。假设我们可以实现一个名为“sagafs” 的虚拟文件系统,其中文件 log 对应内核存储的系统调用日志。此时就可以通过文件访问的普遍 方法获得日志信息,命令如下所示。 # cat /sagafs/log 使用虚拟文件系统可以更加方便、清晰地实现信息交互。但是很多程序员认为 VFS 的 API 接口十分复杂。其实读者无须担心,因为从 Linux 2.5 内核开始就提供了一种叫做 libfs 的例子程 序,它可以帮助不熟悉文件系统的用户封装实现了 VFS 的通用操作。 5. 使用内存映像 Linux 通过内存映像机制来提供用户程序对内存直接访问的能力。内存映像的意思是把内核 中特定部分的内存空间映射到用户级程序的内存空间去。也就是说,用户空间和内核空间共享一 块相同的内存。这样做有如下影响。 内核在这块地址内存储变更的任何数据,用户可以立即发现和使用,根本无须进行数据复制。 在使用系统调用交互信息时,在整个操作过程中必须有一步数据复制的工作,或者是把内核 数据复制到用户缓冲区,或者是把用户数据复制到内核缓冲区。这样对于许多数据传输量大、时 间要求高的应用来说很不科学,因为许多应用根本就无法忍受数据复制所耗费的时间和资源。 1.4.3 使用 Relay 实现内核到用户空间的数据传输 Relay 是一种从 Linux 内核到用户空间的高效数据传输技术。通过用户定义的 Relay 通道,内 核空间的程序能够高效、可靠、便捷地将数据传输到用户空间。Relay 特别适用于内核空间有大 量数据需要传输到用户空间的情形,目前已经广泛应用在内核调试工具如 SystemTap 中。 66 1. Relay 的原理 Relay 提供了一种机制,使得内核空间的程序能够通过用户定义的 Relay 通道(channel)将 大量数据高效地传输到用户空间。一个 Relay 通道由一组和 CPU 一一对应的内核缓冲区组成。这 些缓冲区又被称为 Relay 缓冲区(buffer),其中的每一个在用户空间都用一个常规文件来表示, 叫做 Relay 文件(file)。内核空间的用户可以利用 Relay 提供的 API 接口来写入数据,这些数据 会被自动写入当前的 CPU ID 对应的那个 Relay 缓冲区;同时,这些缓冲区从用户空间看来,是 一组普通文件,可以直接使用 read()进行读取,也可以使用 mmap()进行映射。Relay 并不关心数 据的格式和内容,这些完全依赖于使用 Relay 的用户程序。Relay 的目的是提供一个足够简单的 接口,从而使得基本操作尽可能高效。 Relay 实现了对数据读和写的分离,使得大量突发性数据写入的时候,不需要受限于用户空 间相对较慢的读取速度,从而大大提高了效率。Relay 作为写入和读取的桥梁,也就是将内核用 户写入的数据缓存并转发给用户空间的程序。这种转发机制正是 Relay 这个名称的由来。 2. Relay 的 API 在 Relay 中提供了许多 API 来支持用户程序完整地使用 Relay。这些 API 主要分为两大类, 分别是面向用户空间和面向内核空间的,具体说明如下所示。 (1)面向用户空间的 API 此类 API 编程接口向用户空间程序提供了访问 Relay 通道缓冲区数据的基本操作的入口,主 要包括如下方法。 Ÿ open():允许用户打开一个已经存在的通道缓冲区。 Ÿ mmap():使通道缓冲区被映射到位于用户空间的调用者的地址空间。要特别注意的是, 我们不能仅对局部区域进行映射。也就是说,必须映射整个缓冲区文件,其大小是 CPU 的个数和单个 CPU 缓冲区大小的乘积。 Ÿ read():读取通道缓冲区的内容。这些数据一旦被读出,就意味着它们被用户空间的程序 消费掉了,不能被之后的读操作看到。 Ÿ sendfile():将数据从通道缓冲区传输到一个输出文件描述符。其中可能的填充字符会被自 动去掉,不会被用户看到。 Ÿ poll():支持 POLLIN/POLLRDNORM/POLLERR 信号。每次子缓冲区的边界被越过时, 等待着的用户空间程序会得到通知。 Ÿ close():将通道缓冲区的引用数减 1。当引用数减为 0 时,表明没有进程或者内核用户需 要打开它,从而这个通道缓冲区被释放。 (2)面向内核空间的 API 此类 API 接口向位于内核空间的用户提供了管理 Relay 通道、数据写入等功能,其中最为常 用的如下所示。 Ÿ relay_open():创建一个 Relay 通道,包括创建每个 CPU 对应的 Relay 缓冲区。 Ÿ relay_close():关闭一个 Relay 通道,包括释放所有的 Relay 缓冲区,在此之前会调用 relay_switch()来处理这些 Relay 缓冲区以保证已读取但是未满的数据不会丢失。 Ÿ relay_write():将数据写入到当前 CPU 对应的 Relay 缓冲区内。由于它使用了 local_irqsave() 67 保护,因此也可以在中断上下文中使用。 Ÿ relay_reserve(): Relay 通道中保留一块连续的区域来留给未来的写入操作。在通常用于那 些希望直接写入到 Relay 缓冲区的用户。考虑到性能或者其他因素,这些用户不希望先把 数据写到一个临时缓冲区中,然后再通过 relay_write()进行写入。 3. 使用 Relay 在下面的内容中,将通过一个最简单的例子来介绍使用 Relay 的方法。本实例由如下两部分 组成。 Ÿ 位于内核空间将数据写入 Relay 文件的程序,使用时需要作为一个内核模块被加载。 Ÿ 位于用户空间从 Relay 文件中读取数据的程序,使用时作为普通用户态程序运行。 (1)实现内核空间 内核空间程序的主要操作如下所示。 Ÿ 当加载模块时,打开一个 Relay 通道,并且往打开的 Relay 通道中写入消息。 Ÿ 当卸载模块时,关闭 Relay 通道。 文件 hello-mod.c 的具体实现代码如下所示。 #include <linux/module.h> #include <linux/relayfs_fs.h> static struct rchan *hello_rchan; int init_module(void) { const char *msg="Hello world\n"; hello_rchan = relay_open("cpu", NULL, 8192, 2, NULL); if(!hello_rchan){ printk("relay_open() failed.\n"); return -ENOMEM; } relay_write(hello_rchan, msg, strlen(msg)); return 0; } void cleanup_module(void) { if(hello_rchan) { relay_close(hello_rchan); hello_rchan = NULL; } return; } MODULE_LICENSE ("GPL"); MODULE_DESCRIPTION ("Simple example of Relay"); (2)实现用户空间 用户空间的函数主要操作过程如下所示。 68 如果 relayfs 文件系统还没有被 umount(是一个命令)处理,则将其 umount 到“/mnt/relay” 目录上。首先遍历每一个 CPU 对应的缓冲文件,然后打开文件,接着读取所有文件内容,然后 关闭文件,最后,umount 掉 Relay 文件系统。 实现文件 audience.c 的具体实现代码如下所示。 #inc
展开阅读全文

开通  VIP会员、SVIP会员  优惠大
下载10份以上建议开通VIP会员
下载20份以上建议开通SVIP会员


开通VIP      成为共赢上传

当前位置:首页 > 包罗万象 > 大杂烩

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        抽奖活动

©2010-2025 宁波自信网络信息技术有限公司  版权所有

客服电话:4009-655-100  投诉/维权电话:18658249818

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :微信公众号    抖音    微博    LOFTER 

客服