Microsoft Word - 第1章 Linux快速入门
|
|
|
- 锋禹位 焦
- 9 years ago
- Views:
Transcription
1 嵌 入 式 LINUX 应 用 程 序 开 发 标 准 教 程 作 者 : 华 清 远 见 第 1 章 Linux 快 速 入 门 Linux Linux Linux Linux 能 够 独 立 安 装 Linux 操 作 系 统 能 够 熟 练 使 用 Linux 系 统 的 基 本 命 令 认 识 Linux 系 统 启 动 过 程 能 够 独 立 在 Linux 系 统 中 安 装 软 件 能 够 独 立 设 置 Linux 环 境 变 量 能 够 独 立 定 制 Linux 服 务
2 1.1 嵌 入 式 Linux 基 础 自 由 开 源 软 件 在 嵌 入 式 应 用 上, 受 到 青 睐,Linux 日 益 成 为 主 流 的 嵌 入 式 操 作 系 统 之 一 随 着 摩 托 罗 拉 手 机 A760 IBM 智 能 型 手 表 WatchPad 夏 普 PDA Zaurus 等 一 款 款 高 性 能 智 能 数 码 产 品 的 出 现, 以 及 Motolola 三 星 MontaVista 飞 利 浦 Nokia IBM SUN 等 众 多 国 际 顶 级 巨 头 的 加 入, 嵌 入 式 Linux 的 队 伍 越 来 越 庞 大 了 目 前, 国 外 不 少 大 学 研 究 机 构 和 知 名 公 司 都 加 入 了 嵌 入 式 Linux 的 开 发 工 作, 成 熟 的 嵌 入 式 Linux 产 品 不 断 涌 现 2004 年 全 球 嵌 入 式 Linux 市 场 规 模 已 达 9150 万 美 元,2005 年 有 亿 美 元,2006 年 有 亿 美 元, 2007 年 达 到 亿 美 元, 每 年 平 均 增 长 30% 究 竟 是 什 么 原 因 让 嵌 入 式 Linux 系 统 发 展 如 此 迅 速 业 界 归 纳 为 三 大 原 因.. 第 一,Linux 在 嵌 入 式 系 统 所 需 的 实 时 性 电 源 管 理 等 核 心 技 术 不 断 发 展 ; 第 二, 国 际 标 准 组 织 ( 如 OSDL CELF 等 ) 持 续 建 立 嵌 入 式 Linux 相 关 标 准, 有 效 解 决 版 本 分 歧 与 兼 容 性 问 题 ; 第 三, 业 界 主 导 组 织 开 发 厂 商 等 不 断 推 出 嵌 入 式 Linux 相 关 开 发 工 具 维 护 系 统 嵌 入 式 Linux 以 年 费 订 阅 方 式 为 主, 与 其 他 的 以 产 品 利 润 为 收 入 方 式 的 嵌 入 式 系 统 不 同, 弹 性 的 捆 绑 销 售 策 略, 助 其 成 功 地 逐 年 提 高 市 场 占 有 率, 从 2004 年 的 46.8% 扩 大 到 2007 年 的 56.4% 国 际 有 名 的 嵌 入 式 Linux 操 作 系 统 提 供 商 Montavista, 收 购 了 PalmSource 的 爱 可 信 和 奇 趣 科 技 等, 加 强 了 对 中 国 市 场 的 投 入, 并 在 整 个 嵌 入 式 操 作 系 统 市 场 中, 占 据 了 重 要 地 位 而 嵌 入 式 操 作 系 统 的 领 先 厂 商, 也 改 变 了 原 来 的 单 一 产 品 路 线, 开 始 推 出 自 己 的 Linux 软 件 产 品, 实 现 两 条 腿 走 路 国 内 的 嵌 入 式 软 件 厂 商 也 以 Linux 为 突 破 口, 纷 纷 开 发 各 种 基 于 Linux 的 操 作 系 统 产 品 这 些 嵌 入 式 Linux 厂 商 已 经 形 成 了 一 个 不 容 忽 视 的 群 体 以 下 就 从 Linux 开 始, 一 层 层 揭 开 嵌 入 式 Linux 的 面 纱 Linux 发 展 概 述 简 单 地 说,Linux 是 指 一 套 免 费 使 用 和 自 由 传 播 的 类 UNIX 操 作 系 统 人 们 通 常 所 说 的 Linux 是 Linus Torvalds 所 写 的 Linux 操 作 系 统 内 核 当 时 的 Linus 还 是 芬 兰 赫 尔 辛 基 大 学 的 一 名 学 生, 他 主 修 的 课 程 中 有 一 门 课 是 操 作 系 统, 而 且 这 门 课 是 专 门 研 究 程 序 的 设 计 和 执 行 最 后 这 门 课 程 提 供 了 一 种 称 为 Minix 的 初 期 UNIX 系 统 Minix 是 一 款 仅 为 教 学 而 设 计 的 操 作 系 统, 而 且 功 能 有 限 因 此, 和 Minix 的 众 多 使 用 者 一 样,Linus 也 希 望 能 给 它 添 加 一 些 功 能 在 之 后 的 几 个 月 里,Linus 根 据 实 际 的 需 要 编 写 了 磁 盘 驱 动 程 序 以 便 下 载 访 问 新 闻 组 的 文 件, 又 编 写 了 个 文 件 系 统 以 便 能 够 阅 读 Minix 文 件 系 统 中 的 文 件 这 样, 当 你 有 了 任 务 切 换, 有 了 文 件 系 统 和 设 备 驱 动 程 序 后, 这 就 是 UNIX, 或 者 至 少 是 其 内 核 于 是,0.0.1 版 本 的 Linux 就 诞 生 了 Linus 从 一 开 始 就 决 定 自 由 传 播 Linux, 他 把 源 代 码 发 布 在 网 上, 于 是, 众 多 的 爱 好 者 和 程 序 员 也 都 通 过 互 联 网 加 入 到 Linux 的 内 核 开 发 工 作 中 这 个 思 想 与 FSF(Free Software Foundation) 资 助 发 起 的 GNU(GNU s Not UNIX) 的 自 由 软 件 精 神 不 谋 而 合 GNU 是 为 了 推 广 自 由 软 件 的 精 神 以 实 现 一 个 自 由 的 操 作 系 统, 然 后 从 应 用 程 序 开 始, 实 现 其 内 核 而 当 时 Linux 的 优 良 性 能 备 受 GNU 的 赏 识, 于 是 GNU 就 决 定 采 用 Linus 及 其 开 发 者 的 内 核 在 他 们 的 共 同 努 力 下,Linux 这 个 完 整 的 操 作 系 统 诞 生 了 其 中 的 程 序 开 发 共 同 遵 守 General Public License (GPL) 协 议, 这 是 最 开 放 也 是 最 严 格 的 许 可 协 议 方 式, 这 个 协 议 规 定 了 源 码 必 须 可 以 无 偿 的 获 取 并 且 修 改 因 此, 从 严 格 意 义 上 说,Linux 应 该 叫 做 GNU/Linux, 其 中 许 多 重 要 的 工 具 如 gcc gdb make emacs 等 都 是 GNU 贡 献 的 这 个 婴 儿 版 的 操 作 系 统 以 平 均 两 星 期 更 新 一 次 的 速 度 迅 速 成 长, 如 今 的 Linux 已 经 有 超 过 250 种 发 行 版 本, 且 可 以 支 持 所 有 体 系 结 构 的 处 理 器, 如 X86 PowerPC ARM Xscale 等, 也 可 以 支 持 带 MMU 或 不 带 MMU 的 处 理 器 到 目 前 为 止, 它 的 内 核 版 本 也 已 经 从 原 先 的 发 展 到 现 在 的 2.6.xx 2
3 自 由 软 件 (free software) 中 的 free 并 不 是 指 免 费, 而 是 指 自 由 它 赋 予 使 用 者 4 种 自 由 自 由 之 1: 有 使 用 软 件 的 自 由 自 由 之 2: 有 研 究 该 软 件 如 何 运 作 的 自 由, 并 且 得 以 改 写 该 软 件 来 满 足 使 用 者 自 身 的 需 求 取 得 该 软 件 的 源 码 是 达 成 此 目 的 前 提 自 由 之 3: 有 重 新 散 布 该 软 件 的 自 由, 所 以 每 个 人 都 可 以 藉 由 散 布 自 由 软 件 来 敦 亲 睦 邻 自 由 之 4: 有 改 善 再 利 用 该 软 件 的 自 由, 并 且 可 以 发 表 改 写 版 供 公 众 使 用, 如 此 一 来, 整 个 社 群 都 可 以 受 惠 取 得 该 软 件 的 源 码 是 达 成 此 目 的 前 提 GPL:GPL 协 议 是 GNU 组 织 维 护 的 一 种 版 权 协 议, 遵 守 这 个 协 议 的 软 件 可 以 自 由 地 获 取 查 看 使 用 其 源 代 码 GPL 协 议 是 整 个 开 源 世 界 的 精 神 基 础 Linux 的 内 核 版 本 号 : Linux 内 核 版 本 号 格 式 是 x.y.zz-www, 数 字 x 代 表 版 本 类 型, 数 字 y 为 偶 数 时 是 稳 定 版 本, 为 奇 数 时 是 开 发 版 本, 如 为 稳 定 版 本, 为 开 发 版 本, 测 试 版 本 为 3 个 数 字 加 上 测 试 号, 如 rc1 最 新 的 Linux 内 核 版 本 可 从 上 获 得 Linux 作 为 嵌 入 式 操 作 系 统 的 优 势 从 Linux 系 统 的 发 展 过 程 可 以 看 出,Linux 从 最 开 始 就 是 一 个 开 放 的 系 统, 并 且 它 始 终 遵 循 着 源 代 码 开 放 的 原 则, 它 是 一 个 成 熟 而 稳 定 的 网 络 操 作 系 统, 作 为 嵌 入 式 操 作 系 统 有 如 下 优 势 1. 低 成 本 开 发 系 统 Linux 的 源 码 开 放 性 允 许 任 何 人 获 取 并 修 改 Linux 的 源 码 这 样 一 方 面 大 大 降 低 了 开 发 的 成 本, 另 一 方 面 又 可 以 提 高 开 发 产 品 的 效 率 并 且 还 可 以 在 Linux 社 区 中 获 得 支 持, 用 户 只 需 向 邮 件 列 表 发 一 封 邮 件, 即 可 获 得 作 者 的 支 持 2. 可 应 用 于 多 种 硬 件 平 台 Linux 可 支 持 X86 PowerPC ARM Xscale MIPS SH 68K Alpha Sparc 等 多 种 体 系 结 构, 并 且 已 经 被 移 植 到 多 种 硬 件 平 台 这 对 于 经 费 时 间 受 限 制 的 研 究 与 开 发 项 目 是 很 有 吸 引 力 的 Linux 采 用 一 个 统 一 的 框 架 对 硬 件 进 行 管 理, 同 时 从 一 个 硬 件 平 台 到 另 一 个 硬 件 平 台 的 改 动 与 上 层 应 用 无 关 3. 可 定 制 的 内 核 Linux 具 有 独 特 的 内 核 模 块 机 制, 它 可 以 根 据 用 户 的 需 要, 实 时 地 将 某 些 模 块 插 入 到 内 核 中 或 者 从 内 核 中 移 走, 并 能 根 据 嵌 入 式 设 备 的 个 性 需 要 量 体 裁 衣 经 裁 减 的 Linux 内 核 最 小 可 达 到 150KB 以 下, 尤 其 适 合 嵌 入 式 领 域 中 资 源 受 限 的 实 际 情 况 当 前 的 2.6 内 核 加 入 了 许 多 嵌 入 式 友 好 特 性 3
4 4. 性 能 优 异 Linux 系 统 内 核 精 简 高 效 并 且 稳 定, 能 够 充 分 发 挥 硬 件 的 功 能, 因 此 它 比 其 他 操 作 系 统 的 运 行 效 率 更 高 在 个 人 计 算 机 上 使 用 Linux, 可 以 将 它 作 为 工 作 站 它 也 非 常 适 合 在 嵌 入 式 领 域 中 应 用, 对 比 其 他 操 作 系 统, 它 占 用 的 资 源 更 少, 运 行 更 稳 定, 速 度 更 快 5. 良 好 的 网 络 支 持 Linux 是 首 先 实 现 TCP/IP 协 议 栈 的 操 作 系 统, 它 的 内 核 结 构 在 网 络 方 面 是 非 常 完 整 的, 并 提 供 了 对 包 括 十 兆 位 百 兆 位 及 千 兆 位 的 以 太 网, 还 有 无 线 网 络 Token ring( 令 牌 环 ) 和 光 纤 甚 至 卫 星 的 支 持, 这 对 现 在 依 赖 于 网 络 的 嵌 入 式 设 备 来 说 无 疑 是 很 好 的 选 择 Linux 发 行 版 本 由 于 Linux 属 于 GNU 系 统, 而 这 个 系 统 采 用 GPL 协 议, 并 保 证 了 源 代 码 的 公 开, 于 是 众 多 组 织 或 公 司 在 Linux 内 核 源 代 码 的 基 础 上 进 行 了 一 些 必 要 的 修 改 加 工, 然 后 再 开 发 一 些 配 套 的 软 件, 并 把 它 整 合 成 一 个 自 己 的 发 布 版 Linux 除 去 非 商 业 组 织 Debian 开 发 的 Debian GNU/Linux 外, 美 国 的 Red Hat 公 司 发 行 了 Red Hat Linux, 法 国 的 Mandrake 公 司 发 行 了 Mandrake Linux, 德 国 的 SUSE 公 司 发 行 了 SUSE Linux, 国 内 众 多 公 司 也 发 行 了 中 文 版 的 Linux, 如 著 名 的 红 旗 Linux Linux 目 前 已 经 有 超 过 250 个 发 行 版 本 下 面 仅 对 Red Hat Debian Mandrake 等 具 有 代 表 性 的 Linux 发 行 版 本 进 行 介 绍 1.Red Hat 国 内, 乃 至 是 全 世 界 的 Linux 用 户 最 熟 悉 的 发 行 版 想 必 就 是 Red Hat 了 Red Hat 最 早 是 由 Bob Young 和 Marc Ewing 在 1995 年 创 建 的 目 前 Red Hat 分 为 两 个 系 列 : 由 Red Hat 公 司 提 供 收 费 技 术 支 持 和 更 新 的 Red Hat Enterprise Linux(RHEL,Red Hat 的 企 业 版 ), 以 及 由 社 区 开 发 的 免 费 的 桌 面 版 Fedora Core Red Hat 企 业 版 有 3 个 版 本 AS ES 和 WS AS 是 其 中 功 能 最 为 强 大 和 完 善 的 版 本 而 正 统 的 桌 面 版 Red Hat 版 本 更 新 早 已 停 止, 最 后 一 版 是 Red Hat 9.0 本 书 就 以 稳 定 性 高 的 RHEL AS 作 为 安 装 实 例 进 行 讲 解 官 方 主 页 : 2.Debian 之 所 以 把 Debian 单 独 列 出, 是 因 为 Debian GNU/Linux 是 一 个 非 常 特 殊 的 版 本 在 1993 年, 伊 恩 默 多 克 (Ian Murdock) 发 起 Debian 计 划, 它 的 开 发 模 式 和 Linux 及 其 他 开 放 性 源 代 码 操 作 系 统 的 精 神 一 样, 都 是 由 超 过 800 位 志 愿 者 通 过 互 联 网 合 作 开 发 而 成 的 一 直 以 来,Debian GNU/Linux 被 认 为 是 最 正 宗 的 Linux 发 行 版 本, 而 且 它 是 一 个 完 全 免 费 高 质 量 的 且 与 UNIX 兼 容 的 操 作 系 统 Debian 系 统 分 为 3 个 版 本, 分 别 为 稳 定 版 (Stable) 测 试 版 (Testing) 和 不 稳 定 版 (Unstable) 每 次 发 布 的 版 本 都 是 稳 定 版, 而 测 试 版 在 经 过 一 段 时 间 的 测 试 证 明 没 有 问 题 后 会 成 为 新 的 稳 定 版 Debian 拥 有 超 过 8710 种 不 同 的 软 件, 每 一 种 软 件 都 是 自 由 的, 而 且 有 非 常 方 便 的 升 级 安 装 指 令, 基 本 囊 括 了 用 户 的 所 有 需 要 Debian 也 是 最 受 欢 迎 的 嵌 入 式 Linux 之 一 官 方 主 页 : 4
5 3. 国 内 的 发 行 版 本 及 其 他 目 前 国 内 的 红 旗 新 华 等 都 发 行 了 自 己 的 Linux 版 本 除 了 前 面 所 提 到 的 这 些 版 本 外, 业 界 还 存 在 着 诸 如 gentoo LFS 等 适 合 专 业 人 士 使 用 的 版 本 在 此 不 做 介 绍, 有 兴 趣 的 读 者 可 以 自 行 查 找 相 关 的 资 料 做 进 一 步 的 了 解 如 何 学 习 Linux 正 如 人 们 常 说 的 实 践 出 真 知, 学 习 Linux 的 过 程 也 一 样 只 有 通 过 大 量 的 动 手 实 践 才 能 真 正 地 领 会 Linux 的 精 髓, 才 能 迅 速 掌 握 在 Linux 上 的 应 用 开 发, 相 信 有 编 程 语 言 经 验 的 读 者 一 定 会 认 同 这 一 点 因 此, 在 本 书 中 笔 者 安 排 了 大 量 的 实 验 环 节 和 课 后 实 践 环 节, 希 望 读 者 尽 可 能 多 参 与 另 外 要 指 出 的 是, 互 联 网 也 是 一 个 很 好 的 学 习 工 具, 一 定 要 充 分 地 加 以 利 用 正 如 编 程 一 样, 实 践 的 过 程 中 总 会 出 现 多 种 多 样 的 问 题, 笔 者 在 写 作 的 过 程 当 中 会 尽 可 能 地 考 虑 可 能 出 现 的 问 题, 但 限 于 篇 幅 和 读 者 的 实 际 情 况, 不 可 能 考 虑 到 所 有 可 能 出 现 的 问 题, 所 以 希 望 读 者 能 充 分 利 用 互 联 网 这 一 共 享 的 天 空, 在 其 中 寻 找 答 案 以 下 列 出 了 国 内 的 一 些 Linux 论 坛 : Linux 安 装 有 了 一 个 初 步 的 了 解 后, 读 者 是 否 想 亲 自 试 一 下? 其 实 安 装 Linux 是 一 件 很 容 易 的 事 情, 不 过 在 开 始 安 装 之 前, 还 需 要 了 解 一 下 在 Linux 安 装 过 程 中 可 能 遇 到 的 一 些 基 本 知 识 以 及 它 与 Windows 的 区 别 基 础 概 念 1. 文 件 系 统 分 区 和 挂 载 文 件 系 统 是 指 操 作 系 统 中 与 管 理 文 件 有 关 的 软 件 和 数 据 Linux 的 文 件 系 统 和 Windows 中 的 文 件 系 统 有 很 大 的 区 别,Windows 文 件 系 统 是 以 驱 动 器 的 盘 符 为 基 础 的, 而 且 每 一 个 目 录 与 相 应 的 分 区 对 应, 例 如 E:\workplace 是 指 此 文 件 在 E 盘 这 个 分 区 下 而 Linux 恰 好 相 反, 文 件 系 统 是 一 棵 文 件 树, 且 它 的 所 有 文 件 和 外 部 设 备 ( 如 硬 盘 光 驱 等 ) 都 是 以 文 件 的 形 式 挂 在 这 个 文 件 树 上, 例 如 /usr/local 对 于 Windows 而 言, 就 是 指 所 有 分 区 都 是 在 一 些 目 录 下 总 之, 在 Windows 下, 目 录 结 构 属 于 分 区 ;Linux 下, 分 区 属 于 目 录 结 构 其 关 系 如 图 1.1 和 图 1.2 所 示 图 1.1 Linux 下 目 录 与 分 区 关 系 图 1.2 Windows 下 目 录 与 分 区 关 系 图 因 此, 在 Linux 中 把 每 一 个 分 区 和 某 一 个 目 录 对 应, 以 后 再 对 这 个 目 录 的 操 作 就 是 对 这 个 分 区 的 操 作, 这 样 就 实 现 了 硬 件 管 理 手 段 和 软 件 目 录 管 理 手 段 的 统 一 这 个 把 分 区 和 目 录 对 应 的 过 程 叫 做 挂 载 (Mount), 而 这 个 挂 载 在 文 件 树 中 的 位 置 就 是 挂 载 点 这 种 对 应 关 系 可 以 由 用 户 随 时 中 断 和 改 变 5
6 Linux 文 件 系 统 的 挂 载 特 性 给 用 户 能 带 来 怎 样 的 好 处 呢? 2. 主 分 区 扩 展 分 区 和 逻 辑 分 区 硬 盘 分 区 是 针 对 一 个 硬 盘 进 行 操 作 的, 它 可 以 分 为 : 主 分 区 扩 展 分 区 逻 辑 分 区 其 中 主 分 区 就 是 包 含 操 作 系 统 启 动 所 必 需 的 文 件 和 数 据 的 硬 盘 分 区, 要 在 硬 盘 上 安 装 操 作 系 统, 则 该 硬 盘 必 须 要 有 一 个 主 分 区, 而 且 其 主 分 区 的 数 量 可 以 是 1~3 个 ; 扩 展 分 区 也 就 是 除 主 分 区 外 的 分 区, 但 它 不 能 直 接 使 用, 必 须 再 将 它 划 分 为 若 干 个 逻 辑 分 区 才 可 使 用, 其 数 量 可 以 有 0 或 1 个 ; 而 逻 辑 分 区 则 在 数 量 上 没 有 什 么 限 制 它 们 的 关 系 如 图 1.3 所 示 一 般 而 言, 对 于 先 装 了 Windows 的 用 户,Windows 的 C 盘 是 装 在 主 分 区 上 的, 可 以 把 Linux 安 装 在 另 一 个 主 分 区 或 者 扩 展 分 区 上 为 了 安 装 方 便 安 全 起 见, 一 般 采 用 把 Linux 装 在 多 余 的 逻 辑 分 区 上, 如 图 1.4 所 示 图 1.3 Linux 下 主 分 区 扩 展 分 区 逻 辑 分 区 示 意 图 图 1.4 Linux 安 装 的 分 区 示 意 图 通 常, 在 Windows 下 的 盘 符 和 Linux 设 备 文 件 的 对 应 关 系 如 下 : C 盘 /dev/hda1( 主 分 区 ) D 盘 /dev/hda5( 逻 辑 分 区 ) E 盘 /dev/hda6( 逻 辑 分 区 ) 3.SWAP 交 换 分 区 在 硬 件 条 件 有 限 的 情 况 下, 为 了 运 行 大 型 的 程 序,Linux 在 硬 盘 上 划 出 一 个 区 域 来 当 作 临 时 的 内 存, 而 Windows 操 作 系 统 把 这 个 区 域 叫 做 虚 拟 内 存,Linux 把 它 叫 做 交 换 分 区 swap 在 安 装 Linux 建 立 交 换 分 区 时, 一 般 将 其 设 为 内 存 大 小 的 2 倍, 当 然 也 可 以 设 为 更 大 4. 分 区 格 式 不 同 的 操 作 系 统 选 择 了 不 同 的 格 式, 同 一 种 操 作 系 统 也 可 能 支 持 多 种 格 式 微 软 公 司 的 Windows 就 选 择 了 FAT32 NTFS 两 种 格 式, 但 是 Windows 不 支 持 Linux 上 常 见 的 分 区 格 式 Linux 是 一 个 开 放 的 操 作 系 统, 它 最 初 使 用 EXT2 格 式, 后 来 使 用 EXT3 格 式, 但 是 它 同 时 支 持 非 常 多 的 分 区 格 式, 包 括 很 多 大 型 机 上 UNIX 使 用 的 XFS 格 式, 也 包 括 微 软 公 司 的 FAT 以 及 NTFS 格 式 5.GRUB GRUB 是 一 种 引 导 装 入 器 ( 类 似 在 嵌 入 式 中 非 常 重 要 的 bootloader), 它 负 责 装 入 内 核 并 引 导 Linux 系 统, 6
7 位 于 硬 盘 的 起 始 部 分 由 于 GRUB 多 方 面 的 优 越 性, 如 今 的 Linux 一 般 都 默 认 采 用 GRUB 来 引 导 Linux 操 作 系 统 但 事 实 上 它 还 可 以 引 导 Windows 等 多 种 操 作 系 统 在 安 装 了 Windows 和 Linux 双 系 统 后, 系 统 是 以 Linux 的 GRUB 作 为 引 导 装 入 器 来 选 择 启 动 Windows 或 Linux 的, 因 此, 若 此 时 直 接 在 Windows 下 把 Linux 的 分 区 删 除, 会 导 致 系 统 因 没 有 引 导 装 入 器 而 无 法 启 动 Windows, 这 点 要 格 外 小 心 6.root 权 限 Linux 也 是 一 个 多 用 户 的 系 统 ( 在 这 一 点 上 类 似 Windows XP), 不 同 的 用 户 和 用 户 组 会 有 不 同 的 权 限, 其 中 把 具 有 超 级 权 限 的 用 户 称 为 root 用 户 root 的 默 认 主 目 录 在 /root 下, 而 其 他 普 通 用 户 的 目 录 则 在 /home 下 root 的 权 限 极 高, 它 甚 至 可 以 修 改 Linux 的 内 核, 因 此 建 议 初 学 者 要 慎 用 root 权 限, 不 然 一 个 小 小 的 参 数 设 置 错 误 很 有 可 能 导 致 系 统 的 严 重 问 题 硬 件 需 求 Linux 对 硬 件 的 需 求 非 常 低 如 果 要 是 只 想 在 字 符 方 式 下 运 行, 那 么 一 台 386 的 计 算 机 已 经 可 以 用 来 安 装 Linux 了 ; 如 果 想 运 行 X-Windows, 那 也 只 需 要 一 台 16MB 内 存 600MB 硬 盘 的 486 计 算 机 即 可 这 听 起 来 比 那 些 需 要 256MB 内 存 2.0GBHz 的 操 作 系 统 要 好 得 多, 事 实 上 也 正 是 如 此 现 在 软 件 和 硬 件 行 业 的 趋 势 是 让 用 户 购 买 更 快 的 计 算 机, 不 断 扩 充 内 存 和 硬 盘, 而 Linux 却 不 受 这 个 趋 势 的 影 响 随 着 Linux 的 发 展, 由 于 在 其 上 运 行 的 软 件 越 来 越 多, 因 此 它 所 需 要 的 配 置 越 来 越 高, 但 是 用 户 可 以 有 选 择 地 安 装 软 件, 从 而 节 省 资 源 既 可 以 运 行 在 Pentium 4 处 理 器 上, 也 可 以 运 行 在 400MHz 的 Pentium II 上, 甚 至 如 果 用 户 需 要, 也 可 以 在 只 有 文 本 界 面 的 更 低 配 置 的 机 器 上 运 行 由 此 可 见 Linux 非 常 适 合 需 求 各 异 的 嵌 入 式 硬 件 平 台 而 且 Linux 可 以 很 好 地 支 持 标 准 配 件 如 果 用 户 的 计 算 机 是 采 用 标 准 配 件, 那 么 运 行 Linux 应 该 没 有 任 何 问 题 安 装 准 备 在 开 始 安 装 之 前, 首 先 需 要 了 解 一 下 机 器 的 硬 件 配 置, 包 括 以 下 几 个 问 题 (1) 有 几 个 硬 盘, 每 个 硬 盘 的 大 小, 如 果 有 两 个 以 上 的 硬 盘 哪 个 是 主 盘 (2) 内 存 有 多 大 (3) 显 卡 的 厂 家 和 型 号, 有 多 大 的 显 存 (4) 显 示 器 的 厂 家 和 型 号 (5) 鼠 标 的 类 型 如 果 用 户 的 计 算 机 需 要 联 网, 那 么 还 需 要 注 意 以 下 问 题 (1) 计 算 机 的 IP 地 址 子 网 掩 码 网 关 DNS 的 地 址 主 机 名 (2) 有 的 时 候 还 需 要 搞 清 楚 网 卡 的 型 号 和 厂 商 如 果 不 确 定 系 统 对 硬 件 的 兼 容 性, 或 者 想 了 解 Linux 是 否 支 持 一 些 比 较 新 或 不 常 见 的 硬 件, 用 户 可 以 到 和 进 行 查 询 其 次, 用 户 可 以 选 择 从 网 络 安 装 ( 如 果 带 宽 够 大, 笔 者 推 荐 从 商 家 手 中 购 买 Linux 的 安 装 盘, 一 般 会 获 得 相 应 的 产 品 手 册 售 后 服 务 和 众 多 附 赠 的 商 业 软 件 ), 也 可 以 从 他 人 那 里 复 制, 放 心, 这 是 合 法 的, 因 为 Linux 是 免 费 的 如 果 用 户 需 要 获 得 最 新 的, 或 需 要 一 个 不 易 于 购 买 到 的 版 本, 那 么 用 户 可 以 从 下 载 一 个 需 要 的 Linux 版 本 最 后, 应 在 安 装 前 确 认 磁 盘 上 是 否 有 足 够 的 空 间, 一 般 的 发 行 版 本 全 部 安 装 需 要 3GB 左 右, 最 小 安 装 可 以 到 数 十 兆 字 节, 当 然 还 需 要 给 未 来 的 使 用 留 下 足 够 的 空 间 如 果 用 户 拥 有 的 是 一 个 已 经 分 区 的 空 闲 空 间, 那 么 可 以 选 择 在 安 装 前 在 Windows 下 删 除 相 应 分 区, 也 可 以 选 择 在 安 装 时 删 除 7
8 1.3 Linux 文 件 及 文 件 系 统 在 安 装 完 Linux 之 后, 下 面 先 对 Linux 中 一 些 非 常 重 要 的 概 念 做 一 些 介 绍, 以 便 进 一 步 学 习 使 用 Linux 文 件 类 型 及 文 件 属 性 1. 文 件 类 型 Linux 中 的 文 件 类 型 与 Windows 有 显 著 的 区 别, 其 中 最 显 著 的 区 别 在 于 Linux 对 目 录 和 设 备 都 当 作 文 件 来 进 行 处 理, 这 样 就 简 化 了 对 各 种 不 同 类 型 设 备 的 处 理, 提 高 了 效 率 Linux 中 主 要 的 文 件 类 型 分 为 4 种 : 普 通 文 件 目 录 文 件 链 接 文 件 和 设 备 文 件 (1) 普 通 文 件 普 通 文 件 同 Windows 中 的 文 件 一 样, 是 用 户 日 常 使 用 最 多 的 文 件 它 包 括 文 本 文 件 shell 脚 本 (shell 的 概 念 在 第 2 章 会 进 行 讲 解 ) 二 进 制 的 可 执 行 程 序 和 各 种 类 型 的 数 据 (2) 目 录 文 件 在 Linux 中, 目 录 也 是 文 件, 它 们 包 含 文 件 名 和 子 目 录 名 以 及 指 向 那 些 文 件 和 子 目 录 的 指 针 目 录 文 件 是 Linux 中 存 储 文 件 名 的 惟 一 地 方, 当 把 文 件 和 目 录 相 对 应 起 来 时, 也 就 是 用 指 针 将 其 链 接 起 来 之 后, 就 构 成 了 目 录 文 件 因 此, 在 对 目 录 文 件 进 行 操 作 时, 一 般 不 涉 及 对 文 件 内 容 的 操 作, 而 只 是 对 目 录 名 和 文 件 名 的 对 应 关 系 进 行 操 作 另 外,Linux 系 统 中 的 每 个 文 件 都 被 赋 予 惟 一 的 数 值, 而 这 个 数 值 被 称 作 索 引 节 点 索 引 节 点 存 储 在 一 个 称 作 索 引 节 点 表 (Inode Table) 中, 该 表 在 磁 盘 格 式 化 时 被 分 配 每 个 实 际 的 磁 盘 或 分 区 都 有 自 己 的 索 引 节 点 表 一 个 索 引 节 点 包 含 文 件 的 所 有 信 息, 包 括 磁 盘 上 数 据 的 地 址 和 文 件 类 型 Linux 文 件 系 统 把 索 引 节 点 号 1 赋 予 根 目 录, 这 也 就 是 Linux 的 根 目 录 文 件 在 磁 盘 上 的 地 址 根 目 录 文 件 包 括 文 件 名 目 录 名 及 它 们 各 自 的 索 引 节 点 号 的 列 表,Linux 可 以 通 过 查 找 从 根 目 录 开 始 的 一 个 目 录 链 来 找 到 系 统 中 的 任 何 文 件 Linux 通 过 目 录 链 接 来 实 现 对 整 个 文 件 系 统 的 操 作 比 如, 把 文 件 从 一 个 磁 盘 目 录 移 到 另 一 实 际 磁 盘 的 目 录 时 ( 实 际 上 是 通 过 读 取 索 引 节 点 表 来 检 测 这 种 动 作 的 ), 这 时, 原 先 文 件 的 磁 盘 索 引 号 被 删 除, 在 新 磁 盘 上 建 立 相 应 的 索 引 节 点 它 们 之 间 的 相 应 关 系 如 图 1.5 所 示 图 1.5 目 录 文 件 与 索 引 节 点 关 系 (3) 链 接 文 件 链 接 文 件 有 些 类 似 于 Windows 中 的 快 捷 方 式, 但 是 它 的 功 能 更 为 强 大 它 可 以 实 现 对 不 同 的 目 录 文 件 系 统 甚 至 是 不 同 的 机 器 上 的 文 件 直 接 访 问, 并 且 不 必 重 新 占 用 磁 盘 空 间 (4) 设 备 文 件 Linux 把 设 备 都 当 作 文 件 一 样 来 进 行 操 作, 这 样 就 大 大 方 便 了 用 户 的 使 用 ( 在 后 面 的 Linux 编 程 中 可 以 更 为 明 显 地 看 出 ) 在 Linux 下 与 设 备 相 关 的 文 件 一 般 都 在 /dev 目 录 下, 它 包 括 两 种, 一 种 是 块 设 备 文 件, 另 一 种 是 字 符 设 备 文 件 块 设 备 文 件 是 指 数 据 的 读 写, 它 们 是 以 块 ( 如 由 柱 面 和 扇 区 编 址 的 块 ) 为 单 位 的 设 备, 最 简 单 的 如 硬 盘 (/dev/hda1) 等 字 符 设 备 主 要 是 指 串 行 端 口 的 接 口 设 备 8
9 2. 文 件 属 性 Linux 中 的 文 件 属 性 如 图 1.6 如 示 图 1.6 Linux 文 件 属 性 表 示 方 法 首 先,Linux 中 文 件 的 拥 有 者 可 以 把 文 件 的 访 问 属 性 设 成 3 种 不 同 的 访 问 权 限 : 可 读 (r) 可 写 (w) 和 可 执 行 (x) 文 件 又 有 3 个 不 同 的 用 户 级 别 : 文 件 拥 有 者 (u) 所 属 的 用 户 组 (g) 和 系 统 里 的 其 他 用 户 (o) 第 一 个 字 符 显 示 文 件 的 类 型 - 表 示 普 通 文 件 d 表 示 目 录 文 件 l 表 示 链 接 文 件 c 表 示 字 符 设 备 b 表 示 块 设 备 p 表 示 命 名 管 道, 比 如 FIFO 文 件 (First In First Out, 先 进 先 出 ) f 表 示 堆 栈 文 件, 比 如 LIFO 文 件 (Last In First Out, 后 进 先 出 ) s 表 示 套 接 字 第 一 个 字 符 之 后 有 三 个 三 位 字 符 组 : 第 一 个 三 位 字 符 组 表 示 文 件 拥 有 者 (u) 对 该 文 件 的 权 限 第 二 个 三 位 字 符 组 表 示 文 件 用 户 组 (g) 对 该 文 件 的 权 限 第 三 个 三 位 字 符 组 表 示 系 统 其 他 用 户 (o) 对 该 文 件 的 权 限 若 该 用 户 组 对 此 没 有 权 限, 一 般 显 示 - 字 符 目 录 权 限 和 文 件 权 限 有 一 定 的 区 别 对 于 目 录 而 言,r 代 表 允 许 列 出 该 目 录 下 的 文 件 和 子 目 录,w 代 表 允 许 生 成 和 删 除 该 目 录 下 的 文 件,x 代 表 允 许 访 问 该 目 录 文 件 系 统 类 型 介 绍 1.ext2 和 ext3 ext3 是 现 在 Linux( 包 括 Red Hat,Mandrake 下 ) 常 见 的 默 认 的 文 件 系 统, 它 是 ext2 的 升 级 版 本 正 如 Red Hat 公 司 的 首 席 核 心 开 发 人 员 Michael K.Johnson 所 说, 从 ext2 转 换 到 ext3 主 要 有 以 下 4 个 理 由 : 可 用 性 数 据 完 整 性 速 度 以 及 易 于 转 化 ext3 中 采 用 了 日 志 式 的 管 理 机 制, 它 使 文 件 系 统 具 有 很 强 的 快 速 恢 复 能 力, 并 且 由 于 从 ext2 转 换 到 ext3 无 须 进 行 格 式 化, 因 此, 更 加 推 进 了 ext3 文 件 系 统 的 推 广 2.swap 文 件 系 统 该 文 件 系 统 是 Linux 中 作 为 交 换 分 区 使 用 的 在 安 装 Linux 的 时 候, 交 换 分 区 是 必 须 建 立 的, 并 且 它 所 采 用 的 文 件 系 统 类 型 必 须 是 swap 而 没 有 其 他 选 择 9
10 3.vfat 文 件 系 统 Linux 中 把 DOS 中 采 用 的 FAT 文 件 系 统 ( 包 括 FAT12 FAT16 和 FAT32) 都 称 为 vfat 文 件 系 统 4.NFS 文 件 系 统 NFS 文 件 系 统 是 指 网 络 文 件 系 统, 这 种 文 件 系 统 也 是 Linux 的 独 到 之 处 它 可 以 很 方 便 地 在 局 域 网 内 实 现 文 件 共 享, 并 且 使 多 台 主 机 共 享 同 一 主 机 上 的 文 件 系 统 而 且 NFS 文 件 系 统 访 问 速 度 快 稳 定 性 高, 已 经 得 到 了 广 泛 的 应 用, 尤 其 在 嵌 入 式 领 域, 使 用 NFS 文 件 系 统 可 以 很 方 便 地 实 现 文 件 本 地 修 改, 而 免 去 了 一 次 次 读 写 Flash 的 忧 虑 5.ISO9660 文 件 系 统 这 是 光 盘 所 使 用 的 文 件 系 统, 在 Linux 中 对 光 盘 已 有 了 很 好 的 支 持, 它 不 仅 可 以 提 供 对 光 盘 的 读 写, 还 可 以 实 现 对 光 盘 的 刻 录 Linux 目 录 结 构 下 面 以 Red Hat Enterprise 4 AS 为 例, 详 细 列 出 了 Linux 文 件 系 统 中 各 主 要 目 录 的 存 放 内 容, 如 表 1.1 所 示 表 1.1 Linux 文 件 系 统 目 录 结 构 目 录 目 录 内 容 /bin /boot /dev /etc /etc/rc.d /etc/rc.d/init /home /lib /lost+found /media /misc /mnt /proc bin 就 是 二 进 制 (binary) 的 英 文 缩 写 在 这 里 存 放 Linux 常 用 操 作 命 令 的 执 行 文 件, 如 mv ls mkdir 等 有 时, 这 个 目 录 的 内 容 和 /usr/bin 里 面 的 内 容 一 样, 它 们 都 是 放 置 一 般 用 户 使 用 的 执 行 文 件 这 个 目 录 下 存 放 操 作 系 统 启 动 时 所 要 用 到 的 程 序 如 启 动 grub 就 会 用 到 其 下 的 /boot/grub 子 目 录 该 目 录 中 包 含 了 所 有 Linux 系 统 中 使 用 的 外 部 设 备 要 注 意 的 是, 这 里 并 不 是 存 放 外 部 设 备 的 驱 动 程 序, 它 实 际 上 是 一 个 访 问 这 些 外 部 设 备 的 端 口 由 于 在 Linux 中, 所 有 的 设 备 被 当 作 文 件 进 行 操 作, 比 如 :/dev/cdrom 代 表 光 驱, 用 户 可 以 非 常 方 便 地 像 访 问 文 件 目 录 一 样 对 其 进 行 访 问 该 目 录 下 存 放 了 系 统 管 理 时 要 用 到 的 各 种 配 置 文 件 和 子 目 录 如 网 络 配 置 文 件 文 件 系 统 x 系 统 配 置 文 件 设 备 配 置 信 息 设 置 用 户 信 息 等 都 在 这 个 目 录 下 系 统 在 启 动 过 程 中 需 要 读 取 其 参 数 并 进 行 相 应 的 配 置 该 目 录 主 要 存 放 Linux 启 动 和 关 闭 时 要 用 到 的 脚 本 文 件, 在 后 面 的 启 动 详 解 中 还 会 进 一 步 地 讲 解 该 目 录 存 放 所 有 Linux 服 务 默 认 的 启 动 脚 本 ( 在 新 版 本 的 Linux 中 还 用 到 /etc/xinetd.d 目 录 下 的 内 容 ) 该 目 录 是 Linux 系 统 中 默 认 的 用 户 工 作 根 目 录 如 前 面 在 节 中 所 述, 执 行 adduser 命 令 后 系 统 会 在 /home 目 录 下 为 对 应 账 号 建 立 一 个 同 名 的 主 目 录 该 目 录 是 用 来 存 放 系 统 动 态 链 接 共 享 库 的 几 乎 所 有 的 应 用 程 序 都 会 用 到 这 个 目 录 下 的 共 享 库 因 此, 千 万 不 要 轻 易 对 这 个 目 录 进 行 操 作 该 目 录 在 大 多 数 情 况 下 都 是 空 的 只 有 当 系 统 产 生 异 常 时, 会 将 一 些 遗 失 的 片 段 放 在 此 目 录 下 该 目 录 下 是 光 驱 和 软 驱 的 挂 载 点,Fedora Core 4 已 经 可 以 自 动 挂 载 光 驱 和 软 驱 该 目 录 下 存 放 从 DOS 下 进 行 安 装 的 实 用 工 具, 一 般 为 空 该 目 录 是 软 驱 光 驱 硬 盘 的 挂 载 点, 也 可 以 临 时 将 别 的 文 件 系 统 挂 载 到 此 目 录 下 该 目 录 是 用 于 放 置 系 统 核 心 与 执 行 程 序 所 需 的 一 些 信 息 而 这 些 信 息 是 在 内 存 中 由 系 统 产 生 的, 故 不 占 用 硬 盘 空 间 10
11 /root /sbin /tmp /usr /usr/bin /usr/sbin /usr/src /srv /sys /var 该 目 录 是 超 级 用 户 登 录 时 的 主 目 录 该 目 录 用 来 存 放 系 统 管 理 员 的 常 用 的 系 统 管 理 程 序 该 目 录 用 来 存 放 不 同 程 序 执 行 时 产 生 的 临 时 文 件 一 般 Linux 安 装 软 件 的 默 认 安 装 路 径 就 是 这 里 这 是 一 个 非 常 重 要 的 目 录, 用 户 的 很 多 应 用 程 序 和 文 件 都 存 放 在 这 个 目 录 下, 类 似 于 Windows 下 的 Program Files 的 目 录 系 统 用 户 使 用 的 应 用 程 序 超 级 用 户 使 用 的 比 较 高 级 的 管 理 程 序 和 系 统 守 护 程 序 内 核 源 代 码 默 认 的 放 置 目 录 该 目 录 存 放 一 些 服 务 启 动 之 后 需 要 提 取 的 数 据 这 是 Linux 2.6 内 核 的 一 个 很 大 的 变 化 该 目 录 下 安 装 了 2.6 内 核 中 新 出 现 的 一 个 文 件 系 统 sysfs sysfs 文 件 系 统 集 成 了 下 面 3 种 文 件 系 统 的 信 息 : 针 对 进 程 信 息 的 proc 文 件 系 统 针 对 设 备 的 devfs 文 件 系 统 以 及 针 对 伪 终 端 的 devpts 文 件 系 统 该 文 件 系 统 是 内 核 设 备 树 的 一 个 直 观 反 映 当 一 个 内 核 对 象 被 创 建 的 时 候, 对 应 的 文 件 和 目 录 也 在 内 核 对 象 子 系 统 中 被 创 建 这 也 是 一 个 非 常 重 要 的 目 录, 很 多 服 务 的 日 志 信 息 都 存 放 在 这 里 1.4 实 验 内 容 安 装 Linux 操 作 系 统 1. 实 验 目 的 读 者 通 过 亲 自 动 手 安 装 Linux 操 作 系 统, 对 Linux 有 个 初 步 的 认 识, 并 且 加 深 对 Linux 中 的 基 本 概 念 的 理 解, 熟 悉 Linux 文 件 系 统 目 录 结 构 2. 实 验 内 容 安 装 Linux(Red Hat Enterprise 4 AS 版 本 ) 操 作 系 统, 查 看 Linux 的 目 录 结 构 3. 实 验 步 骤 (1) 磁 盘 规 划 在 这 一 步 骤 中, 需 要 留 出 最 好 有 5GB 以 上 的 空 间 来 安 装 Linux 系 统 (2) 下 载 Linux 版 本 可 以 从 Linux 的 映 像 网 站 上 下 载 各 版 本 的 Linux (3) 搜 集 主 机 硬 件 信 息 查 看 相 应 版 本 的 Linux 是 否 已 有 了 对 相 应 各 硬 件 的 驱 动 支 持 较 新 版 本 的 Linux 一 般 对 硬 件 的 支 持 都 比 较 好 (4) 确 认 用 户 网 络 信 息 包 括 IP 子 网 掩 码 DNS 地 址 等 (5) 按 照 本 书 1.2 小 节 讲 述 的 步 骤 安 装 Linux, 对 关 键 的 步 骤 要 加 倍 小 心, 如 配 置 文 件 系 统 及 硬 盘 分 区 (6) 选 择 安 装 套 件, 建 议 新 手 可 以 使 用 全 部 安 装 来 减 少 以 后 学 习 的 难 度 (7) 配 置 用 户 信 息 网 络 信 息 等 (8) 安 装 完 成, 用 普 通 用 户 登 录 到 Linux 下 (9) 使 用 文 件 浏 览 器 熟 悉 文 件 的 目 录 结 构 11
12 4. 实 验 结 果 能 够 成 功 安 装 Linux 操 作 系 统, 并 且 对 Linux 文 件 系 统 的 目 录 结 构 能 有 一 个 整 体 的 了 解 1.5 本 章 小 结 本 章 首 先 介 绍 了 Linux 的 历 史 嵌 入 式 Linux 操 作 系 统 的 优 势 Linux 不 同 发 行 版 本 的 区 别 以 及 如 何 学 习 Linux 在 这 里 要 着 重 掌 握 的 是 Linux 内 核 与 GNU 的 关 系, 了 解 Linux 版 本 号 的 规 律, 同 时 还 要 了 解 Linux 多 硬 件 平 台 支 持 低 开 发 成 本 等 优 越 性 本 章 接 着 介 绍 了 如 何 安 装 Linux, 这 里 最 关 键 的 一 步 是 分 区 希 望 读 者 能 很 好 地 掌 握 主 分 区 扩 展 分 区 的 概 念 Linux 文 件 系 统 与 Windows 文 件 系 统 的 区 别 以 及 Linux 中 挂 载 与 挂 载 点 的 含 义, 这 几 个 都 是 Linux 中 的 重 要 概 念, 希 望 读 者 能 够 切 实 理 解 其 含 义 在 安 装 完 Linux 之 后, 本 章 讲 解 了 Linux 中 文 件 和 文 件 系 统 的 概 念 这 些 是 Linux 中 最 基 础 最 常 见 的 概 念, 只 有 真 正 理 解 之 后 才 能 为 进 一 步 学 习 Linux 打 下 很 好 的 基 础 读 者 要 着 重 掌 握 Linux 的 文 件 分 类 文 件 属 性 的 表 示 方 法, 并 且 能 够 通 过 实 际 查 看 Linux 目 录 结 构 来 熟 悉 Linux 中 重 要 目 录 的 作 用 最 后 本 章 还 设 计 了 本 书 中 的 第 一 个 实 验 安 装 Linux, 这 也 是 读 者 必 须 要 完 成 的 最 基 础 的 实 验 1.6 思 考 与 练 习 1. 请 查 找 相 关 资 料, 查 看 GNU 所 规 定 的 自 由 软 件 的 具 体 协 议 是 什 么 2. 请 问 Linux 下 的 文 件 系 统 和 Windows 下 的 文 件 系 统 有 什 么 区 别? 3. 试 指 出 读 者 Linux 系 统 中 的 磁 盘 划 分 情 况 ( 如 主 分 区 扩 展 分 区 的 对 应 情 况 ) 4. 如 何 安 装 Linux? 5.Linux 中 的 文 件 有 哪 些 类, 这 样 分 类 有 什 么 好 处? 6. 若 有 一 个 文 件, 其 属 性 为 -rwxr rw-, 说 出 这 代 表 什 么? 7. 请 说 出 下 列 目 录 中 放 置 的 是 哪 些 文 件 /etc/ /etc/rc.d/init.d/ /usr/bin /bin /usr/sbin /sbin /var/log 联 系 方 式 集 团 官 网 : 嵌 入 式 学 院 : 移 动 互 联 网 学 院 : 企 业 学 院 : 物 联 网 学 院 : 研 发 中 心 :dev.hqyj.com 集 团 总 部 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 内 华 清 远 见 教 育 集 团 北 京 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 区, 电 话 : /5 12
13 上 海 地 址 : 上 海 市 徐 汇 区 漕 溪 路 银 海 大 厦 A 座 8 层, 电 话 : 深 圳 地 址 : 深 圳 市 龙 华 新 区 人 民 北 路 美 丽 AAA 大 厦 15 层, 电 话 : 成 都 地 址 : 成 都 市 武 侯 区 科 华 北 路 99 号 科 华 大 厦 6 层, 电 话 : 南 京 地 址 : 南 京 市 白 下 区 汉 中 路 185 号 鸿 运 大 厦 10 层, 电 话 : 武 汉 地 址 : 武 汉 市 工 程 大 学 卓 刀 泉 校 区 科 技 孵 化 器 大 楼 8 层, 电 话 : 西 安 地 址 : 西 安 市 高 新 区 高 新 一 路 12 号 创 业 大 厦 D3 楼 5 层, 电 话 :
14 嵌 入 式 LINUX 应 用 程 序 开 发 标 准 教 程 作 者 : 华 清 远 见 第 2 章 Linux 基 础 命 令 Linux Linux shell 掌 握 shell 基 本 概 念 熟 练 使 用 Linux 中 用 户 管 理 命 令 熟 练 使 用 Linux 中 系 统 相 关 命 令 熟 练 使 用 Linux 中 文 件 目 录 相 关 命 令 熟 练 使 用 Linux 中 打 包 压 缩 相 关 命 令 熟 练 使 用 Linux 中 文 件 比 较 合 并 相 关 命 令 熟 练 使 用 Linux 中 网 络 相 关 命 令 了 解 Linux 的 启 动 过 程 深 入 了 解 init 进 程 及 其 配 置 文 件 能 够 独 立 完 成 在 Linux 中 解 压 缩 软 件 学 会 添 加 环 境 变 量 能 够 独 立 定 制 Linux 中 的 系 统 服 务
15 2.1 Linux 常 用 命 令 在 安 装 完 Linux 再 次 启 动 之 后, 就 可 以 进 入 到 与 Windows 类 似 的 图 形 化 界 面 了 这 个 界 面 就 是 Linux 图 形 化 界 面 X 窗 口 系 统 ( 简 称 X) 的 一 部 分 要 注 意 的 是,X 窗 口 系 统 仅 仅 是 Linux 上 面 的 一 个 软 件 ( 或 者 也 可 称 为 服 务 ), 它 不 是 Linux 自 身 的 一 部 分 虽 然 现 在 的 X 窗 口 系 统 已 经 与 Linux 整 合 得 相 当 好 了, 但 毕 竟 还 不 能 保 证 绝 对 的 可 靠 性 另 外,X 窗 口 系 统 是 一 个 相 当 耗 费 系 统 资 源 的 软 件, 它 会 大 大 地 降 低 Linux 的 系 统 性 能 因 此, 若 是 希 望 更 好 地 享 受 Linux 所 带 来 的 高 效 及 高 稳 定 性, 建 议 读 者 尽 可 能 地 使 用 Linux 的 命 令 行 界 面, 也 就 是 shell 环 境 当 用 户 在 命 令 行 下 工 作 时, 不 是 直 接 同 操 作 系 统 内 核 交 互 信 息 的, 而 是 由 命 令 解 释 器 接 受 命 令, 分 析 后 再 传 给 相 关 的 程 序 shell 是 一 种 Linux 中 的 命 令 行 解 释 程 序, 就 如 同 command.com 是 DOS 下 的 命 令 解 释 程 序 一 样, 为 用 户 提 供 使 用 操 作 系 统 的 接 口 它 们 之 间 的 关 系 如 图 2.1 所 示 用 户 在 提 示 符 下 输 入 的 命 令 都 由 shell 先 解 释 然 后 传 给 Linux 内 核 shell 是 命 令 语 言 命 令 解 释 程 序 及 程 序 设 计 语 言 的 统 称 它 不 仅 拥 有 自 己 内 建 的 shell 命 令 集, 同 时 也 能 被 系 统 中 其 他 应 用 程 序 所 调 用 shell 的 一 个 重 要 特 性 是 它 自 身 就 是 一 个 解 释 型 的 程 序 设 计 语 言,shell 程 序 设 计 语 言 支 持 绝 大 多 数 在 高 级 语 言 中 能 见 到 的 程 序 元 素, 如 函 数 变 量 数 组 和 程 序 控 制 结 构 shell 编 程 语 言 简 单 易 学, 任 何 在 提 示 符 中 能 键 入 的 命 令 都 能 放 到 一 个 可 执 行 的 shell 程 序 中 关 于 shell 编 程 的 详 细 讲 解, 感 兴 趣 的 读 者 可 以 参 见 其 他 相 关 书 籍 Linux 中 运 行 shell 的 环 境 是 系 统 工 具 下 的 终 端, 读 者 可 以 单 击 终 端 以 启 动 shell 环 境 这 时 屏 幕 上 显 示 类 似 [david@localhost home]$ 的 信 息, 其 中,david 是 指 系 统 用 户, localhost 是 计 算 机 名, 而 home 是 指 当 前 所 在 的 目 录 由 于 Linux 中 的 命 令 非 常 多, 要 全 部 介 绍 几 乎 是 不 可 能 的 因 此, 在 本 书 按 照 命 令 的 用 途 进 行 分 类 讲 解, 并 且 对 每 一 类 中 最 常 用 的 命 令 详 细 讲 解, 同 时 列 出 同 一 类 中 的 其 他 命 令 由 于 同 一 类 的 命 令 都 有 很 大 的 相 似 性, 因 此, 读 者 通 过 学 习 本 书 中 所 列 命 令, 可 以 很 快 地 掌 握 其 他 命 令 图 2.1 内 核 shell 和 用 户 的 关 系 命 令 格 式 说 明 格 式 中 带 [] 的 表 明 为 可 选 项, 其 他 为 必 选 项 选 项 可 以 多 个 连 带 写 入 本 章 后 面 选 项 参 数 列 表 中 加 粗 的 含 义 是 : 该 选 项 是 非 常 常 用 的 选 项 用 户 系 统 相 关 命 令 Linux 是 一 个 多 用 户 的 操 作 系 统, 每 个 用 户 又 可 以 属 于 不 同 的 用 户 组, 下 面, 首 先 来 熟 悉 一 下 Linux 中 的 用 户 切 换 和 用 户 管 理 的 相 关 命 令 1. 用 户 切 换 (su) (1) 作 用 变 更 为 其 他 使 用 者 的 身 份, 主 要 用 于 将 普 通 用 户 身 份 转 变 为 超 级 用 户, 而 且 需 输 入 相 应 用 户 密 码 (2) 格 式 su [ 选 项 ] [ 使 用 者 ] 2
16 其 中 的 使 用 者 为 要 变 更 的 对 应 使 用 者 (3) 常 见 参 数 主 要 选 项 参 数 如 表 2.1 所 示 表 2.1 su 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -,-l,--login -m,-p -c,--command 为 该 使 用 者 重 新 登 录, 大 部 分 环 境 变 量 ( 如 HOME SHELL 和 USER 等 ) 和 工 作 目 录 都 是 以 该 使 用 者 (USER) 为 主 若 没 有 指 定 USER, 缺 省 情 况 是 root 执 行 su 时 不 改 变 环 境 变 量 变 更 账 号 为 USER 的 使 用 者, 执 行 指 令 (command) 后 再 变 回 原 来 使 用 者 (4) 使 用 示 例 [david@localhost ~]$ su - root Password: [root@localhost ~]# 示 例 通 过 su 命 令 将 普 通 用 户 变 更 为 root 用 户, 并 使 用 选 项 - 携 带 root 环 境 变 量 (5) 使 用 说 明 在 将 普 通 用 户 变 更 为 root 用 户 时 建 议 使 用 - 选 项, 这 样 可 以 将 root 的 环 境 变 量 和 工 作 目 录 同 时 带 入, 否 则 在 以 后 的 使 用 中 可 能 会 由 于 环 境 变 量 的 原 因 而 出 错 在 转 变 为 root 权 限 后, 提 示 符 变 为 # 环 境 变 量 实 际 上 就 是 用 户 运 行 环 境 的 参 数 集 合 Linux 是 一 个 多 用 户 的 操 作 系 统 而 且 在 每 个 用 户 登 录 系 统 后, 都 会 有 一 个 专 有 的 运 行 环 境 通 常 每 个 用 户 默 认 的 环 境 都 是 相 同 的, 而 这 个 默 认 环 境 实 际 上 就 是 一 组 环 境 变 量 的 定 义 用 户 可 以 对 自 己 的 运 行 环 境 进 行 定 制, 其 方 法 就 是 修 改 相 应 的 系 统 环 境 变 量 常 见 的 环 境 变 量 如 下 PATH 是 系 统 路 径 HOME 是 系 统 根 目 录 HISTSIZE 是 指 保 存 历 史 命 令 记 录 的 条 数 LOGNAME 是 指 当 前 用 户 的 登 录 名 HOSTNAME 是 指 主 机 的 名 称, 若 应 用 程 序 要 用 到 主 机 名, 通 常 是 从 这 个 环 境 变 量 中 来 取 得 的 SHELL 是 指 当 前 用 户 用 的 是 哪 种 shell LANG/LANGUGE 是 和 语 言 相 关 的 环 境 变 量, 使 用 多 种 语 言 的 用 户 可 以 修 改 此 环 境 变 量 MAIL 是 指 当 前 用 户 的 邮 件 存 放 目 录 设 置 环 境 变 量 方 法 如 下 通 过 echo 显 示 字 符 串 ( 指 定 环 境 变 量 ) 通 过 export 设 置 新 的 环 境 变 量 通 过 env 显 示 所 有 环 境 变 量 通 过 set 命 令 显 示 所 有 本 地 定 义 的 shell 变 量 通 过 unset 命 令 来 清 除 环 境 变 量 读 者 可 以 试 着 用 env 命 令 查 看 su - root ( 或 su ) 和 su root 的 区 别 2. 用 户 管 理 (useradd 和 passwd) 3
17 Linux 中 常 见 用 户 管 理 命 令 如 表 2.2 所 示, 本 书 仅 以 useradd 和 passwd 为 例 进 行 详 细 讲 解, 其 他 命 令 类 似, 请 读 者 自 行 学 习 使 用 表 2.2 Linux 常 见 用 户 管 理 命 令 命 令 命 令 含 义 格 式 useradd 添 加 用 户 账 号 useradd [ 选 项 ] 用 户 名 usermod 设 置 用 户 账 号 属 性 usermod [ 选 项 ] 属 性 值 userdel 删 除 对 应 用 户 账 号 userdel [ 选 项 ] 用 户 名 groupadd 添 加 组 账 号 groupadd [ 选 项 ] 组 账 号 groupmod 设 置 组 账 号 属 性 groupmod [ 选 项 ] 属 性 值 groupdel 删 除 对 应 组 账 号 groupdel [ 选 项 ] 组 账 号 passwd 设 置 账 号 密 码 passwd [ 对 应 账 号 ] id 显 示 用 户 ID 组 ID 和 用 户 所 属 的 组 列 表 id [ 用 户 名 ] groups 显 示 用 户 所 属 的 组 groups [ 组 账 号 ] who 显 示 登 录 到 系 统 的 所 有 用 户 who (1) 作 用 1 useradd: 添 加 用 户 账 号 2 passwd: 更 改 对 应 用 户 的 账 号 密 码 (2) 格 式 1 useradd:useradd [ 选 项 ] 用 户 名 2 passwd:passwd [ 选 项 ] [ 用 户 名 ] 其 中 的 用 户 名 为 修 改 账 号 密 码 的 用 户, 若 不 带 用 户 名, 缺 省 为 更 改 当 前 使 用 者 的 密 码 (3) 常 用 参 数 1 useradd 主 要 选 项 参 数 如 表 2.3 所 示 表 2.3 useradd 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -g 指 定 用 户 所 属 的 群 组 -m 自 动 建 立 用 户 的 登 入 目 录 -n 取 消 建 立 以 用 户 名 称 为 名 的 群 组 2 passwd: 一 般 很 少 使 用 选 项 参 数 (4) 使 用 实 例 [root@localhost ~]# useradd david [root@localhost ~]# passwd david New password: ( 输 入 密 码 ) Retype new password: ( 再 输 入 一 次 密 码, 以 确 认 输 入 的 正 确 性 ) passwd: all authentication tokens updated successfully [root@localhost ~]# su david [david@localhost ~]$ [david@localhost ~]$ pwd( 查 看 当 前 目 录 ) /home/david ( 该 用 户 的 工 作 目 录 ) 实 例 中 先 添 加 了 用 户 名 为 david 的 用 户, 接 着 又 为 该 用 户 设 置 了 账 号 密 码 从 su 的 命 令 可 以 看 出, 该 用 户 添 加 成 功, 其 工 作 目 录 为 /home/david (5) 使 用 说 明 4
18 在 添 加 用 户 时, 这 两 个 命 令 是 一 起 使 用 的, 其 中,useradd 必 须 用 root 的 权 限 而 且 useradd 指 令 所 建 立 的 账 号, 实 际 上 是 保 存 在 /etc/passwd 文 本 文 件 中, 文 件 中 每 一 行 包 含 一 个 账 号 信 息 在 缺 省 情 况 下,useradd 所 做 的 初 始 化 操 作 包 括 在 /home 目 录 下 为 对 应 账 号 建 立 一 个 同 名 的 主 目 录, 并 且 还 为 该 用 户 单 独 建 立 一 个 与 用 户 名 同 名 的 组 adduser 只 是 useradd 的 符 号 链 接 ( 关 于 符 号 链 接 的 概 念 在 本 节 后 面 会 有 介 绍 ), 两 者 是 相 同 的 passwd 还 可 用 于 普 通 用 户 修 改 账 号 密 码,Linux 并 不 采 用 类 似 Windows 的 密 码 回 显 ( 显 示 为 * 号 ), 所 以 输 入 的 这 些 字 符 用 户 是 看 不 见 的 密 码 最 好 包 括 字 母 数 字 和 特 殊 符 号, 并 且 设 成 6 位 以 上 3. 系 统 管 理 命 令 (ps 和 kill) Linux 中 常 见 的 系 统 管 理 命 令 如 表 2.4 所 示, 本 书 以 ps 和 kill 为 例 进 行 讲 解 表 2.4 Linux 常 见 系 统 管 理 命 令 命 令 命 令 含 义 格 式 ps 显 示 当 前 系 统 中 由 该 用 户 运 行 的 进 程 列 表 ps [ 选 项 ] top 动 态 显 示 系 统 中 运 行 的 程 序 ( 一 般 为 每 隔 5s) top kill 输 出 特 定 的 信 号 给 指 定 PID( 进 程 号 ) 的 进 程 kill [ 选 项 ] 进 程 号 (PID) uname 显 示 系 统 的 信 息 ( 可 加 选 项 -a) uname [ 选 项 ] setup 系 统 图 形 化 界 面 配 置 setup crontab 循 环 执 行 例 行 性 命 令 crontab [ 选 项 ] shutdown 关 闭 或 重 启 Linux 系 统 shutdown [ 选 项 ] [ 时 间 ] uptime 显 示 系 统 已 经 运 行 了 多 长 时 间 uptime clear 清 除 屏 幕 上 的 信 息 clear (1) 作 用 1 ps: 显 示 当 前 系 统 中 由 该 用 户 运 行 的 进 程 列 表 2 kill: 输 出 特 定 的 信 号 给 指 定 PID( 进 程 号 ) 的 进 程, 并 根 据 该 信 号 完 成 指 定 的 行 为 其 中 可 能 的 信 号 有 进 程 挂 起 进 程 等 待 进 程 终 止 等 (2) 格 式 1 ps:ps [ 选 项 ] 2 kill:kill [ 选 项 ] 进 程 号 (PID) kill 命 令 中 的 进 程 号 为 信 号 输 出 的 指 定 进 程 的 进 程 号, 当 选 项 是 缺 省 时 为 输 出 终 止 信 号 给 该 进 程 (3) 常 见 参 数 1 ps 主 要 选 项 参 数 如 表 2.5 所 示 表 2.5 ps 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -ef -aux 查 看 所 有 进 程 及 其 PID( 进 程 号 ) 系 统 时 间 命 令 详 细 目 录 执 行 者 等 除 可 显 示 -ef 所 有 内 容 外, 还 可 显 示 CPU 及 内 存 占 用 率 进 程 状 态 -w 显 示 加 宽 并 且 可 以 显 示 较 多 的 信 息 2 kill 主 要 选 项 参 数 如 表 2.6 所 示 表 2.6 kill 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -s 将 指 定 信 号 发 送 给 进 程 -p 打 印 出 进 程 号 (PID), 但 并 不 送 出 信 号 5
19 -l 列 出 所 有 可 用 的 信 号 名 称 (4) 使 用 实 例 root]# ps ef UID PID PPID C STIME TTY TIME CMD root ? 00:00:05 init root ? 00:00:00 [keventd] root ? 00:00:00 [ksoftirqd_cpu0] root ? 00:00:00 [ksoftirqd_cpu1] root ? 00:00:00 /usr/local/bin/ntpd -c /etc/ntp. root :16 pts/1 00:00:00 grep ntp [root@localhost root]# kill ( 杀 死 进 程 ) [root@localhost root]# ps -ef grep ntp root :16 pts/1 00:00:00 grep ntp 该 实 例 中 首 先 查 看 所 有 进 程, 并 终 止 进 程 号 为 7421 的 ntp 进 程, 之 后 再 次 查 看 时 已 经 没 有 该 进 程 号 的 进 程 (5) 使 用 说 明 ps 在 使 用 中 通 常 可 以 与 其 他 一 些 命 令 结 合 起 来 使 用, 主 要 作 用 是 提 高 效 率 ps 选 项 中 的 参 数 w 可 以 写 多 次, 通 常 最 多 写 3 次, 它 的 含 义 为 加 宽 3 次, 这 足 以 显 示 很 长 的 命 令 行 了 例 如 :ps auxwww 管 道 是 Linux 中 信 息 通 信 的 重 要 方 式 它 是 把 一 个 程 序 的 输 出 直 接 连 接 到 另 一 个 程 序 的 输 入, 而 不 经 过 任 何 中 间 文 件 管 道 线 是 指 连 接 两 个 或 更 多 程 序 管 道 的 通 路 在 shell 中 字 符 表 示 管 道 线 如 前 例 子 中 的 ps ef grep ntp 所 示, ps ef 的 结 果 直 接 输 入 到 grep ntp 的 程 序 中 ( 关 于 grep 命 令 在 后 面 会 有 详 细 的 介 绍 ) grep pr sort 和 wc 都 可 以 在 上 述 管 道 线 上 工 作 读 者 可 以 灵 活 地 运 用 管 道 机 制 4. 磁 盘 相 关 命 令 (fdisk) Linux 中 与 磁 盘 相 关 的 命 令 如 表 2.7 所 示, 本 书 仅 以 fdisk 为 例 进 行 讲 解 表 2.7 Linux 常 见 系 统 管 理 命 令 选 项 参 数 含 义 格 式 free 查 看 当 前 系 统 内 存 的 使 用 情 况 free [ 选 项 ] df 查 看 文 件 系 统 的 磁 盘 空 间 占 用 情 况 df [ 选 项 ] du 统 计 目 录 ( 或 文 件 ) 所 占 磁 盘 空 间 的 大 小 du [ 选 项 ] fdisk 查 看 硬 盘 分 区 情 况 及 对 硬 盘 进 行 分 区 管 理 fdisk [-l] (1) 作 用 fdisk 可 以 查 看 硬 盘 分 区 情 况, 并 可 对 硬 盘 进 行 分 区 管 理, 这 里 主 要 介 绍 如 何 查 看 硬 盘 分 区 情 况, 另 外,fdisk 也 是 一 个 非 常 好 的 硬 盘 分 区 工 具, 感 兴 趣 的 读 者 可 以 另 外 查 找 资 料 学 习 如 何 使 用 fdisk 进 行 硬 盘 分 区 (2) 格 式 fdisk [-l] (3) 使 用 实 例 [root@localhost ~]# fdisk -l Disk /dev/hda: 40.0 GB, bytes 240 heads, 63 sectors/track, 5168 cylinders Units = cylinders of * 512 = bytes Device Boot Start End Blocks Id System 6
20 /dev/hda1 * c W95 FAT32 (LBA) /dev/hda f W95 Ext'd (LBA) /dev/hda b W95 FAT32 /dev/hda b W95 FAT32 /dev/hda Linux /dev/hda Linux swap Disk /dev/sda: 999 MB, bytes 4 heads, 8 sectors/track, cylinders Units = cylinders of 32 * 512 = bytes Disk identifier: 0x 专 业 始 于 专 注 卓 识 源 于 远 见 Device Boot Start End Blocks Id System /dev/sda1 * b W95 FAT32 可 以 看 出, 使 用 fdisk l 列 出 了 文 件 系 统 的 分 区 情 况 (4) 使 用 说 明 使 用 fdisk 必 须 拥 有 root 权 限 IDE 硬 盘 对 应 的 设 备 名 称 分 别 为 hda hdb hdc 和 hdd,scsi 硬 盘 对 应 的 设 备 名 称 则 为 sda sdb 此 外,hda1 代 表 hda 的 第 一 个 硬 盘 分 区,hda2 代 表 hda 的 第 二 个 分 区, 依 此 类 推 通 过 查 看 /var/log/messages 文 件, 可 以 找 到 Linux 系 统 已 辨 认 出 来 的 设 备 代 号 5. 文 件 系 统 挂 载 命 令 (mount) (1) 作 用 挂 载 文 件 系 统, 它 的 使 用 权 限 是 超 级 用 户 或 /etc/fstab 中 允 许 的 使 用 者 正 如 节 中 所 述, 挂 载 是 指 在 分 区 和 目 录 之 间 建 立 映 射 关 系 的 过 程, 而 挂 载 点 是 指 挂 载 在 文 件 树 中 的 位 置 使 用 mount 命 令 可 以 把 文 件 系 统 挂 载 到 相 应 的 目 录 下, 并 且 由 于 Linux 中 把 设 备 都 当 成 文 件 一 样 使 用, 因 此,mount 命 令 也 可 以 挂 载 不 同 的 设 备 通 常, 在 Linux 下 /mnt 目 录 是 专 门 用 于 挂 载 不 同 的 文 件 系 统 的, 它 可 以 在 该 目 录 下 新 建 不 同 的 子 目 录 来 挂 载 不 同 的 设 备 文 件 系 统 (2) 格 式 mount [ 选 项 ] [ 类 型 ] 设 备 文 件 名 挂 载 点 目 录 其 中 的 类 型 是 指 设 备 文 件 的 类 型 (3) 常 见 参 数 mount 常 见 参 数 如 表 2.8 所 示 表 2.8 mount 命 令 选 项 常 见 参 数 列 表 选 项 参 数 含 义 -a 依 照 /etc/fstab 的 内 容 装 载 所 有 相 关 的 硬 盘 -l 列 出 当 前 已 挂 载 的 设 备 文 件 系 统 名 称 和 挂 载 点 -t 类 型 -f 将 后 面 的 设 备 以 指 定 类 型 的 文 件 格 式 装 载 到 挂 载 点 上 常 见 的 类 型 有 前 面 介 绍 过 的 几 种 :vfat ext3 ext2 iso9660 nfs 等 通 常 用 于 除 错 它 会 使 mount 不 执 行 实 际 挂 上 的 动 作, 而 是 模 拟 整 个 挂 上 的 过 程, 通 常 会 和 -v 一 起 使 用 (4) 使 用 实 例 使 用 mount 命 令 主 要 通 过 以 下 几 个 步 骤 1 确 认 是 否 为 Linux 可 以 识 别 的 文 件 系 统,Linux 可 识 别 的 文 件 系 统 只 要 是 以 下 几 种 7
21 Windows 95/98 常 用 的 FAT32 文 件 系 统 :vfat WindowsNT/2000 的 文 件 系 统 :ntfs OS/2 用 的 文 件 系 统 :hpfs Linux 用 的 文 件 系 统 :ext2 ext3 nfs CD-ROM 光 盘 用 的 文 件 系 统 :iso 确 定 设 备 的 名 称, 可 通 过 使 用 命 令 fdisk -l 查 看 3 查 找 挂 载 点 必 须 确 定 挂 载 点 已 经 存 在, 也 就 是 在 /mnt 下 的 相 应 子 目 录 已 经 存 在, 一 般 建 议 在 /mnt 下 新 建 几 个 如 /mnt/windows, /mnt/usb 的 子 目 录, 现 在 有 些 新 版 本 的 Linux( 如 Fedora Ubuntu 红 旗 Linux 中 软 Linux MandrakeLinux) 都 可 自 动 挂 载 文 件 系 统,Red Hat Linux 仅 可 自 动 挂 载 光 驱 4 挂 载 文 件 系 统 如 下 所 示 [root@locaohost ~]# mkdir -p /mnt/win/c [root@locaohost ~]# mount -t vfat /dev/hda1 /mnt/win/c [root@localhost ~]# cd /mnt/win/c 24.s03e01.pdtv.xvid-sfm.rmvb Documents and Settings Program Files 24.s03e02.pdtv.xvid-sfm.rmvb Downloads Recycled C 盘 是 原 先 笔 者 Windows 系 统 的 启 动 盘 可 见, 在 挂 载 了 C 盘 之 后, 可 直 接 访 问 Windows 下 的 C 盘 的 内 容 5 在 使 用 完 该 设 备 文 件 后 可 使 用 命 令 umount 将 其 卸 载 [root@localhost ~]# umount /mnt/win/c [root@localhost ~]# cd /mnt/win/c [root@localhost ~]# ls /mnt/win/c 可 见, 此 时 目 录 /mnt/win/c 下 为 空 Windows 下 的 C 盘 已 被 成 功 卸 载 在 Linux 下 如 何 使 用 U 盘 呢? 一 般 U 盘 为 SCSI 格 式 的 硬 盘, 其 格 式 为 vfat 格 式, 其 设 备 号 可 通 过 fdisk l 进 行 查 看, 假 若 设 备 名 为 /dev/sda1, 则 可 用 如 下 命 令 将 其 挂 载 : mount -t vfat /dev/sda1 /mnt/usb 若 想 设 置 在 开 机 时 自 动 挂 载, 可 在 文 件 /etc/fstab 中 加 入 相 应 的 设 置 行 即 可 文 件 相 关 命 令 Linux 中 有 关 文 件 的 操 作 非 常 重 要, 也 非 常 常 用, 本 节 将 对 Linux 系 统 的 文 件 操 作 命 令 进 行 详 细 讲 解 1.cd (1) 作 用 改 变 当 前 工 作 目 录 (2) 格 式 cd [ 路 径 ] 其 中 的 路 径 为 要 改 变 的 工 作 目 录, 可 为 相 对 路 径 或 绝 对 路 径 (3) 使 用 实 例 [root@localhost ~]# cd /home/david/ [root@localhost david]# pwd 8
22 david]# /home/david/ 该 实 例 中 变 更 工 作 目 录 为 /home/david/, 在 后 面 的 pwd ( 显 示 当 前 目 录 ) 的 结 果 中 可 以 看 出 (4) 使 用 说 明 该 命 令 将 当 前 目 录 改 变 至 指 定 路 径 的 目 录 若 没 有 指 定 路 径, 则 回 到 用 户 的 主 目 录 ( 例 如 : /home/david 为 用 户 david 的 主 目 录 ) 为 了 改 变 到 指 定 目 录, 用 户 必 须 拥 有 对 指 定 目 录 的 执 行 和 读 权 限 该 命 令 可 以 使 用 通 配 符 使 用 cd 可 以 回 到 前 次 工 作 目 录./ 代 表 当 前 目 录,../ 代 表 上 级 目 录 2.ls (1) 作 用 列 出 目 录 和 文 件 的 信 息 (2) 格 式 ls [ 选 项 ] [ 文 件 ] 其 中 文 件 选 项 为 指 定 查 看 指 定 文 件 的 相 关 内 容, 若 未 指 定 文 件, 默 认 查 看 当 前 目 录 下 的 所 有 文 件 (3) 常 见 参 数 ls 主 要 选 项 参 数 见 表 2.9 所 示 表 2.9 ls 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -1,--format=single-column 一 行 输 出 一 个 文 件 ( 单 列 输 出 ) -a,-all 列 出 目 录 中 所 有 文 件, 包 括 以. 开 头 的 隐 藏 文 件 -d 将 目 录 名 和 其 他 文 件 一 样 列 出, 而 不 是 列 出 目 录 的 内 容 -l,--format=long, --format=verbose 除 每 个 文 件 名 外, 增 加 显 示 文 件 类 型 权 限 硬 链 接 数 所 有 者 名 组 名 大 小 (Byte) 及 时 间 信 息 ( 如 未 指 明 是 其 他 时 间 即 指 修 改 时 间 ) -f 不 排 序 目 录 内 容, 按 它 们 在 磁 盘 上 存 储 的 顺 序 列 出 (4) 使 用 实 例 [david@localhost test]$ ls -l total 220 drwxr-xr-x 2 root root 4096 Mar bin drwxr-xr-x 3 root root 4096 Apr boot -rw-r--r-- 1 root root 0 Apr test.run 该 实 例 查 看 当 前 目 录 下 的 所 有 文 件, 并 通 过 选 项 -l 显 示 出 详 细 信 息 显 示 格 式 说 明 如 下 文 件 类 型 与 权 限 链 接 数 文 件 属 主 文 件 属 组 文 件 大 小 修 改 的 时 间 名 字 (5) 使 用 说 明 在 ls 的 常 见 参 数 中,-l( 长 文 件 名 显 示 格 式 ) 的 选 项 是 最 为 常 见 的 可 以 详 细 显 示 出 各 种 信 息 若 想 显 示 出 所 有. 开 头 的 隐 藏 文 件, 可 以 使 用 -a, 这 在 嵌 入 式 开 发 中 很 常 用 Linux 中 的 可 执 行 文 件 不 是 与 Windows 一 样 通 过 文 件 扩 展 名 来 标 识 的, 而 是 通 过 设 置 文 件 相 应 的 可 执 行 属 性 来 实 现 的 9
23 3.mkdir (1) 作 用 创 建 一 个 目 录 (2) 格 式 mkdir [ 选 项 ] 路 径 (3) 常 见 参 数 mkdir 主 要 选 项 参 数 如 表 2.10 所 示 表 2.10 mkdir 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -m 对 新 建 目 录 设 置 存 取 权 限, 也 可 以 用 chmod 命 令 ( 在 本 节 后 会 有 详 细 说 明 ) 设 置 -p 可 以 是 一 个 路 径 名 称 此 时 若 此 路 径 中 的 某 些 目 录 尚 不 存 在, 在 加 上 此 选 项 后, 系 统 将 自 动 建 立 好 那 些 尚 不 存 在 的 目 录, 即 一 次 可 以 建 立 多 个 目 录 (4) 使 用 实 例 [david@localhost ~]$ mkdir -p./hello/my [david@localhost ~]$ cd hello/my [david@localhost my]$ pwd( 查 看 当 前 目 录 命 令 ) /home/david/hello/my 该 实 例 使 用 选 项 -p 一 次 创 建 了./hello/my 多 级 目 录 [david@localhost my]$ mkdir -m 777./why [david@localhost my]$ ls -l total 4 drwxrwxrwx 2 root root 4096 Jan 14 09:24 why 该 实 例 使 用 改 选 项 -m 创 建 了 相 应 权 限 的 目 录 对 于 777 的 权 限 在 本 节 后 面 会 有 详 细 的 说 明 (5) 使 用 说 明 该 命 令 要 求 创 建 目 录 的 用 户 在 创 建 路 径 的 上 级 目 录 中 具 有 写 权 限, 并 且 路 径 名 不 能 是 当 前 目 录 中 已 有 的 目 录 或 文 件 名 称 4.cat (1) 作 用 连 接 并 显 示 指 定 的 一 个 或 多 个 文 件 的 有 关 信 息 (2) 格 式 cat[ 选 项 ] 文 件 1 文 件 2 其 中 的 文 件 1 文 件 2 为 要 显 示 的 多 个 文 件 (3) 常 见 参 数 cat 命 令 的 常 见 参 数 如 表 2.11 所 示 表 2.11 cat 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -n 由 第 一 行 开 始 对 所 有 输 出 的 行 数 编 号 -b 和 -n 相 似, 只 不 过 对 于 空 白 行 不 编 号 (4) 使 用 实 例 10
24 ~]$ cat -n hello1.c hello2.c 1 #include <stdio.h> 2 void main() 3 4 printf("hello!this is my home!\n"); 5 6 #include <stdio.h> 7 void main() 8 9 printf("hello!this is your home!\n"); 10 在 该 实 例 中, 指 定 对 hello1.c 和 hello2.c 进 行 输 出, 并 指 定 行 号 5.cp mv 和 rm (1) 作 用 1 cp: 将 给 出 的 文 件 或 目 录 复 制 到 另 一 文 件 或 目 录 中 2 mv: 为 文 件 或 目 录 改 名 或 将 文 件 由 一 个 目 录 移 入 另 一 个 目 录 中 3 rm: 删 除 一 个 目 录 中 的 一 个 或 多 个 文 件 或 目 录 (2) 格 式 1 cp:cp [ 选 项 ] 源 文 件 或 目 录 目 标 文 件 或 目 录 2 mv:mv [ 选 项 ] 源 文 件 或 目 录 目 标 文 件 或 目 录 3 rm:rm [ 选 项 ] 文 件 或 目 录 (3) 常 见 参 数 1 cp 主 要 选 项 参 数 如 表 2.12 所 示 表 2.12 cp 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -a 保 留 链 接 文 件 属 性, 并 复 制 其 子 目 录, 其 作 用 等 于 dpr 选 项 的 组 合 -d 复 制 时 保 留 链 接 -f 删 除 已 经 存 在 的 目 标 文 件 而 不 提 示 -i 在 覆 盖 目 标 文 件 之 前 将 给 出 提 示 要 求 用 户 确 认 回 答 y 时 目 标 文 件 将 被 覆 盖, 而 且 是 交 互 式 复 制 -p 此 时 cp 除 复 制 源 文 件 的 内 容 外, 还 将 把 其 修 改 时 间 和 访 问 权 限 也 复 制 到 新 文 件 中 -r 若 给 出 的 源 文 件 是 一 个 目 录 文 件, 此 时 cp 将 递 归 复 制 该 目 录 下 所 有 的 子 目 录 和 文 件 此 时 目 标 文 件 必 须 为 一 个 目 录 名 2 mv 主 要 选 项 参 数 如 表 2.13 所 示 表 2.13 mv 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -i -f 若 mv 操 作 将 导 致 对 已 存 在 的 目 标 文 件 的 覆 盖, 此 时 系 统 询 问 是 否 重 写, 并 要 求 用 户 回 答 y 或 n, 这 样 可 以 避 免 误 覆 盖 文 件 禁 止 交 互 操 作 在 mv 操 作 要 覆 盖 某 已 有 的 目 标 文 件 时 不 给 任 何 指 示, 在 指 定 此 选 项 后,i 选 项 将 不 再 起 作 用 3 rm 主 要 选 项 参 数 如 表 2.14 所 示 表 2.14 rm 命 令 常 见 参 数 列 表 11
25 选 项 参 数 含 义 -i 进 行 交 互 式 删 除 -f 忽 略 不 存 在 的 文 件, 但 从 不 给 出 提 示 -r 指 示 rm 将 参 数 中 列 出 的 全 部 目 录 和 子 目 录 均 递 归 地 删 除 (4) 使 用 实 例 1 cp [root@www hello]# cp -a./my/why/./ [root@www hello]# ls my why 该 实 例 使 用 -a 选 项 将 /my/why 目 录 下 的 所 有 文 件 复 制 到 当 前 目 录 下 而 此 时 在 原 先 目 录 下 还 有 原 有 的 文 件 2 mv [root@www hello]# mv -i./my/why/./ [root@www hello]# ls my why 该 实 例 中 把 /my/why 目 录 下 的 所 有 文 件 移 至 当 前 目 录, 则 原 目 录 下 文 件 被 自 动 删 除 3 rm [root@www hello]# rm r -i./why rm: descend into directory './why'? y rm: remove './why/my.c'? y rm: remove directory './why'? y 该 实 例 使 用 -r 选 项 删 除./why 目 录 下 所 有 内 容, 系 统 会 进 行 确 认 是 否 删 除 (5) 使 用 说 明 1 cp: 该 命 令 把 指 定 的 源 文 件 复 制 到 目 标 文 件, 或 把 多 个 源 文 件 复 制 到 目 标 目 录 中 2 mv 该 命 令 根 据 命 令 中 第 二 个 参 数 类 型 的 不 同 ( 是 目 标 文 件 还 是 目 标 目 录 ) 来 判 断 是 重 命 名 还 是 移 动 文 件, 当 第 二 个 参 数 类 型 是 文 件 时,mv 命 令 完 成 文 件 重 命 名, 此 时, 它 将 所 给 的 源 文 件 或 目 录 重 命 名 为 给 定 的 目 标 文 件 名 ; 当 第 二 个 参 数 是 已 存 在 的 目 录 名 称 时,mv 命 令 将 各 参 数 指 定 的 源 文 件 均 移 至 目 标 目 录 中 ; 在 跨 文 件 系 统 移 动 文 件 时,mv 先 复 制, 再 将 原 有 文 件 删 除, 而 连 至 该 文 件 的 链 接 也 将 丢 失 3 rm 如 果 没 有 使 用 - r 选 项, 则 rm 不 会 删 除 目 录 ; 使 用 该 命 令 时 一 旦 文 件 被 删 除, 它 是 不 能 被 恢 复 的, 所 以 最 好 使 用 -i 参 数 6.chown 和 chgrp (1) 作 用 1 chown: 修 改 文 件 所 有 者 和 组 别 2 chgrp: 改 变 文 件 的 组 所 有 权 (2) 格 式 1 chown:chown [ 选 项 ]... 文 件 所 有 者 [ 所 有 者 组 名 ] 文 件 其 中 的 文 件 所 有 者 为 修 改 后 的 文 件 所 有 者 2 chgrp:chgrp [ 选 项 ]... 文 件 所 有 组 文 件 12
26 其 中 的 文 件 所 有 组 为 改 变 后 的 文 件 组 拥 有 者 (3) 常 见 参 数 chown 和 chgrp 的 常 见 参 数 意 义 相 同, 其 主 要 选 项 参 数 如 表 2.15 所 示 表 2.15 chown 和 chgrp 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -c,-changes -f,--silent,--quiet 详 尽 地 描 述 每 个 file 实 际 改 变 了 哪 些 所 有 权 不 打 印 文 件 所 有 权 就 不 能 修 改 的 报 错 信 息 (4) 使 用 实 例 在 笔 者 的 系 统 中 一 个 文 件 的 所 有 者 原 先 是 这 样 的 test]#$ ls -l -rwxr-xr-x 15 apectel david 月 4 200X uclinux-dist.tar 可 以 看 出, 这 是 一 个 文 件, 文 件 拥 有 者 是 apectel, 具 有 可 读 写 和 执 行 的 权 限, 它 所 属 的 用 户 组 是 david, 具 有 可 读 和 执 行 的 权 限, 但 没 有 可 写 的 权 限, 同 样, 系 统 其 他 用 户 对 其 也 只 有 可 读 和 执 行 的 权 限 首 先 使 用 chown 将 文 件 所 有 者 改 为 root [root@localhost test]# chown root uclinux-dist.tar [root@localhost test]# ls l -rwxr-xr-x 15 root david 月 4 200X uclinux-dist.tar 可 以 看 出, 此 时, 该 文 件 拥 有 者 变 为 了 root, 它 所 属 文 件 用 户 组 不 变 接 着 使 用 chgrp 将 文 件 用 户 组 变 为 root [root@localhost test]# chgrp root uclinux-dist.tar [root@localhost test]# ls l -rwxr-xr-x 15 root root 月 4 200X uclinux-dist.tar (5) 使 用 说 明 使 用 chown 和 chgrp 必 须 拥 有 root 权 限 在 进 行 有 关 文 件 的 操 作 时, 若 想 避 免 输 入 冗 长 的 文 件, 在 文 件 名 没 有 重 复 的 情 况 下 可 以 使 用 输 入 文 件 前 几 个 字 母 +<Tab> 键 的 方 式, 即 :cd /uc<tab> 会 显 示 cd /uclinux-list 7.chmod (1) 作 用 改 变 文 件 的 访 问 权 限 (2) 格 式 chmod 可 使 用 符 号 标 记 进 行 更 改 和 八 进 制 数 指 定 更 改 两 种 方 式, 因 此 它 的 格 式 也 有 两 种 不 同 的 形 式 1 符 号 标 记 :chmod [ 选 项 ] 符 号 权 限 [ 符 号 权 限 ] 文 件 其 中 的 符 号 权 限 可 以 指 定 为 多 个, 也 就 是 说, 可 以 指 定 多 个 用 户 级 别 的 权 限, 但 它 们 中 间 要 用 逗 号 分 开 表 示, 若 没 有 显 式 指 出 则 表 示 不 作 更 改 2 八 进 制 数 :chmod [ 选 项 ] 八 进 制 权 限 文 件 其 中 的 八 进 制 权 限 是 指 要 更 改 后 的 文 件 权 限 (3) 选 项 参 数 chmod 主 要 选 项 参 数 如 表 2.16 所 示 表 2.16 chmod 命 令 常 见 参 数 列 表 13
27 选 项 参 数 含 义 -c 若 该 文 件 权 限 确 实 已 经 更 改, 才 显 示 其 更 改 动 作 -f 若 该 文 件 权 限 无 法 被 更 改 也 不 要 显 示 错 误 信 息 -v 显 示 权 限 变 更 的 详 细 资 料 (4) 使 用 实 例 chmod 涉 及 文 件 的 访 问 权 限, 在 此 对 相 关 的 概 念 进 行 简 单 的 回 顾 在 节 中 已 经 提 到, 文 件 的 访 问 权 限 可 表 示 成 :- rwx rwx rwx 在 此 设 有 3 种 不 同 的 访 问 权 限 : 读 (r) 写 (w) 和 运 行 (x) 3 个 不 同 的 用 户 级 别 : 文 件 拥 有 者 (u) 所 属 的 用 户 组 (g) 和 系 统 里 的 其 他 用 户 (o) 在 此, 可 增 加 一 个 用 户 级 别 a(all) 来 表 示 所 有 这 3 个 不 同 的 用 户 级 别 1 第 一 种 符 号 连 接 方 式 的 chmod 命 令 中, 用 加 号 + 代 表 增 加 权 限, 用 减 号 代 表 删 除 权 限, 等 于 号 = 代 表 设 置 权 限 例 如, 原 先 笔 者 系 统 中 有 文 件 uclinux tgz, 其 权 限 如 下 所 示 [root@localhost test]# ls l -rw-r--r-- 1 root root Mar uclinux tgz [root@localhost test]# chmod a+rx,u+w uclinux tgz [root@localhost test]# ls l -rwxr-xr-x 1 root root Mar uclinux tgz 可 见, 在 执 行 了 chmod 之 后, 文 件 拥 有 者 除 拥 有 所 有 用 户 都 有 的 可 读 和 执 行 的 权 限 外, 还 有 可 写 的 权 限 2 对 于 第 二 种 八 进 制 数 指 定 的 方 式, 将 文 件 权 限 字 符 代 表 的 有 效 位 设 为 1, 即 rw- rw- 和 r-- 的 八 进 制 表 示 为 , 把 这 个 二 进 制 串 转 换 成 对 应 的 八 进 制 数 就 是 6 6 4, 也 就 是 说 该 文 件 的 权 限 为 664( 三 位 八 进 制 数 ) 这 样 对 于 转 化 后 八 进 制 数 二 进 制 及 对 应 权 限 的 关 系 如 表 2.17 所 示 表 2.17 转 化 后 八 进 制 数 二 进 制 及 对 应 权 限 的 关 系 转 换 后 八 进 制 数 二 进 制 对 应 权 限 转 换 后 八 进 制 数 二 进 制 对 应 权 限 没 有 任 何 权 限 只 能 执 行 只 写 只 写 和 执 行 只 读 只 读 和 执 行 读 和 写 读 写 和 执 行 同 上 例, 原 先 笔 者 系 统 中 有 文 件 genromfs tar.gz, 其 权 限 如 下 所 示 [root@localhost test]# ls l -rw-rw-r-- 1 david david Dec genromfs tar.gz [root@localhost test]# chmod 765 genromfs tar.gz [root@localhost test]# ls l -rwxrw-r-x 1 david david Dec genromfs tar.gz 可 见, 在 执 行 了 chmod 765 之 后, 该 文 件 的 拥 有 者 权 限 文 件 组 权 限 和 其 他 用 户 权 限 都 恰 当 地 对 应 了 (5) 使 用 说 明 使 用 chmod 必 须 具 有 root 权 限 chmod o+x uclinux tgz 是 什 么 意 思? 它 所 对 应 的 八 进 制 数 指 定 更 改 应 如 何 表 示? 8.grep 14
28 (1) 作 用 在 指 定 文 件 中 搜 索 特 定 的 内 容, 并 将 含 有 这 些 内 容 的 行 标 准 输 出 (2) 格 式 grep [ 选 项 ] 格 式 [ 文 件 及 路 径 ] 其 中 的 格 式 是 指 要 搜 索 的 内 容 格 式, 若 缺 省 文 件 及 路 径 则 默 认 表 示 在 当 前 目 录 下 搜 索 (3) 常 见 参 数 grep 主 要 选 项 参 数 如 表 2.18 所 示 表 2.18 grep 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -c 只 输 出 匹 配 行 的 计 数 -I 不 区 分 大 小 写 ( 只 适 用 于 单 字 符 ) -h 查 询 多 文 件 时 不 显 示 文 件 名 -l 查 询 多 文 件 时 只 输 出 包 含 匹 配 字 符 的 文 件 名 -n 显 示 匹 配 行 及 行 号 -s 不 显 示 不 存 在 或 无 匹 配 文 本 的 错 误 信 息 -v 显 示 不 包 含 匹 配 文 本 的 所 有 行 (4) 使 用 实 例 [root@localhost test]# grep "hello" / -r Binary file./iscit2005/ 备 份 /iscit2004.sql matches./arm_tools/uclinux-samsung/linux-2.4.x/documentation/s390/debugging390.txt:hello world$2 = 0 在 本 例 中, hello 是 要 搜 索 的 内 容, / -r 是 指 定 文 件, 表 示 搜 索 根 目 录 下 的 所 有 文 件 (5) 使 用 说 明 在 缺 省 情 况 下, grep 只 搜 索 当 前 目 录 如 果 此 目 录 下 有 许 多 子 目 录, grep 会 以 如 下 形 式 列 出 : grep:sound:is a directory 这 会 使 grep 的 输 出 难 以 阅 读 但 有 以 下 两 种 解 决 的 方 法 1 明 确 要 求 搜 索 子 目 录 :grep r( 正 如 上 例 中 所 示 ); 2 忽 略 子 目 录 :grep -d skip 当 预 料 到 有 许 多 输 出 时, 可 以 通 过 管 道 将 其 转 到 less ( 分 页 器 ) 上 阅 读 : 如 grep "h"./ -r less 分 页 阅 读 grep 特 殊 用 法 grep pattern1 pattern2 files: 显 示 匹 配 pattern1 或 pattern2 的 行 ; grep pattern1 files grep pattern2: 显 示 既 匹 配 pattern1 又 匹 配 pattern2 的 行 ; 在 文 件 命 令 中 经 常 会 使 用 pattern 正 则 表 达 式, 它 是 可 以 描 述 一 类 字 符 串 的 模 式 (Pattern), 如 果 一 个 字 符 串 可 以 用 某 个 正 则 表 达 式 来 描 述, 就 称 这 个 字 符 和 该 正 则 表 达 式 匹 配 这 和 DOS 中 用 户 可 以 使 用 通 配 符 * 代 表 任 意 字 符 类 似 在 Linux 系 统 上, 正 则 表 达 式 通 常 被 用 来 查 找 文 本 的 模 式, 以 及 对 文 本 执 行 搜 索 - 替 换 操 作 等 正 则 表 达 式 的 主 要 参 数 有 如 下 \: 忽 略 正 则 表 达 式 中 特 殊 字 符 的 原 有 含 义 ; ^: 匹 配 正 则 表 达 式 的 开 始 行 ; $: 匹 配 正 则 表 达 式 的 结 束 行 ; <: 从 匹 配 正 则 表 达 式 的 行 开 始 ; >: 到 匹 配 正 则 表 达 式 的 行 结 束 ; [ ]: 单 个 字 符, 如 [A] 即 A 符 合 要 求 ; 15
29 [-]: 范 围, 如 [A-Z], 即 A B C 一 直 到 Z 都 符 合 要 求 ; : 所 有 的 单 个 字 符 ; *: 所 有 字 符, 长 度 可 以 为 0 9.find (1) 作 用 在 指 定 目 录 中 搜 索 文 件, 它 的 使 用 权 限 是 所 有 用 户 (2) 格 式 find [ 路 径 ][ 选 项 ][ 描 述 ] 其 中 的 路 径 为 文 件 搜 索 路 径, 系 统 开 始 沿 着 此 目 录 树 向 下 查 找 文 件 它 是 一 个 路 径 列 表, 相 互 用 空 格 分 离 若 缺 省 路 径, 那 么 默 认 为 当 前 目 录 其 中 的 描 述 是 匹 配 表 达 式, 是 find 命 令 接 受 的 表 达 式 (3) 常 见 参 数 [ 选 项 ] 主 要 参 数 如 表 2.19 所 示 表 2.19 find 选 项 常 见 参 数 列 表 选 项 参 数 含 义 -depth -mount 使 用 深 度 级 别 的 查 找 过 程 方 式, 在 某 层 指 定 目 录 中 优 先 查 找 文 件 内 容 不 在 其 他 文 件 系 统 ( 如 Msdos Vfat 等 ) 的 目 录 和 文 件 中 查 找 [ 描 述 ] 主 要 参 数 如 表 2.20 所 示 表 2.20 find 描 述 常 见 参 数 列 表 选 项 参 数 含 义 -name 支 持 通 配 符 * 和? -user -print 用 户 名 : 搜 索 文 件 属 主 为 用 户 名 (ID 或 名 称 ) 的 文 件 输 出 搜 索 结 果, 并 且 打 印 (4) 使 用 实 例 [root@localhost test]# find./ -name hello*.c./hello1.c./iscit2005/hello2.c 在 该 实 例 中 使 用 了 -name 的 选 项 支 持 通 配 符 (5) 使 用 说 明 若 使 用 目 录 路 径 为 /, 通 常 需 要 查 找 较 多 的 时 间, 可 以 指 定 更 为 确 切 的 路 径 以 减 少 查 找 时 间 find 命 令 可 以 使 用 混 合 查 找 的 方 法, 例 如, 想 在 /etc 目 录 中 查 找 大 于 字 节, 并 且 在 24 小 时 内 修 改 的 某 个 文 件, 则 可 以 使 用 -and( 与 ) 把 两 个 查 找 参 数 链 接 起 来 组 合 成 一 个 混 合 的 查 找 方 式, 如 find /etc -size c -and -mtime locate (1) 作 用 16
30 用 于 查 找 文 件 其 方 法 是 先 建 立 一 个 包 括 系 统 内 所 有 文 件 名 称 及 路 径 的 数 据 库, 之 后 当 寻 找 时 就 只 需 查 询 这 个 数 据 库, 而 不 必 实 际 深 入 档 案 系 统 之 中 了 因 此 其 速 度 比 find 快 很 多 (2) 格 式 locate [ 选 项 ] (3)locate 主 要 选 项 参 数 如 表 2.21 所 示 表 2.21 locate 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -u 从 根 目 录 开 始 建 立 数 据 库 -U 在 指 定 的 位 置 开 始 建 立 数 据 库 -f 将 特 定 的 文 件 系 统 排 除 在 数 据 库 外, 例 如 proc 文 件 系 统 中 的 文 件 -r 使 用 正 则 运 算 式 做 寻 找 的 条 件 -o 指 定 数 据 库 的 名 称 (4) 使 用 实 例 [root@localhost test]# locate issue -U./ [root@localhost test]# updatedb [root@localhost test]# locate -r issue*./arm_tools/uclinux-samsung/lib/libpam/doc/modules/pam_issue.sgml./arm_tools/uclinux-samsung/lib/libpam/modules/pam_issue./arm_tools/uclinux-samsung/lib/libpam/modules/pam_issue/makefile./arm_tools/uclinux-samsung/lib/libpam/modules/pam_issue/pam_issue.c 实 例 中 首 先 在 当 前 目 录 下 建 立 了 一 个 数 据 库, 并 且 在 更 新 了 数 据 库 之 后 进 行 正 则 匹 配 查 找 通 过 运 行 可 以 发 现 locate 的 运 行 速 度 非 常 快 (5) 使 用 说 明 locate 命 令 所 查 询 的 数 据 库 由 updatedb 程 序 来 更 新, 而 updatedb 是 由 cron daemon 周 期 性 建 立 的, 但 若 所 找 到 的 档 案 是 最 近 才 建 立 或 刚 改 名 的, 可 能 会 找 不 到, 因 为 updatedb 默 认 每 天 运 行 一 次, 用 户 可 以 由 修 改 crontab 配 置 (etc/crontab) 来 更 新 周 期 值 11.ln (1) 作 用 为 某 一 个 文 件 在 另 外 一 个 位 置 建 立 一 个 符 号 链 接 当 需 要 在 不 同 的 目 录 用 到 相 同 的 文 件 时,Linux 允 许 用 户 不 用 在 每 一 个 需 要 的 目 录 下 都 存 放 一 个 相 同 的 文 件, 而 只 需 将 其 他 目 录 下 的 文 件 用 ln 命 令 链 接 即 可, 这 样 就 不 必 重 复 地 占 用 磁 盘 空 间 (2) 格 式 ln[ 选 项 ] 目 标 目 录 (3) 常 见 参 数 s 建 立 符 号 链 接 ( 这 也 是 通 常 惟 一 使 用 的 参 数 ) (4) 使 用 实 例 [root@localhost test]# ln -s../genromfs tar.gz./hello [root@localhost test]# ls -l total lrwxrwxrwx 1 root root 24 Jan 14 00:25 hello ->../genromfs tar.gz 17
31 该 实 例 建 立 了 当 前 目 录 的 hello 文 件 与 上 级 目 录 之 间 的 符 号 链 接, 可 以 看 见, 在 hello 的 ls l 中 的 第 一 位 为 l, 表 示 符 号 链 接, 同 时 还 显 示 了 链 接 的 源 文 件 (5) 使 用 说 明 ln 命 令 会 保 持 每 一 处 链 接 文 件 的 同 步 性, 也 就 是 说, 不 论 改 动 了 哪 一 处, 其 他 的 文 件 都 会 发 生 相 同 的 变 化 ln 的 链 接 分 软 链 接 和 硬 链 接 两 种 软 链 接 就 是 上 面 所 说 的 ln -s ** **, 它 只 会 在 用 户 选 定 的 位 置 上 生 成 一 个 文 件 的 镜 像, 不 会 重 复 占 用 磁 盘 空 间, 平 时 使 用 较 多 的 都 是 软 链 接 硬 链 接 是 不 带 参 数 的 ln ** **, 它 会 在 用 户 选 定 的 位 置 上 生 成 一 个 和 源 文 件 大 小 相 同 的 文 件, 无 论 是 软 链 接 还 是 硬 链 接, 文 件 都 保 持 同 步 变 化 压 缩 打 包 相 关 命 令 Linux 中 打 包 压 缩 的 相 关 命 令 如 表 2.22 所 示, 本 书 以 gzip 和 tar 为 例 进 行 讲 解 表 2.22 Linux 常 见 系 统 管 理 命 令 命 令 命 令 含 义 格 式 bzip2.bz2 文 件 的 压 缩 ( 或 解 压 缩 ) 程 序 bzip2[ 选 项 ] 压 缩 ( 解 压 缩 ) 的 文 件 名 bunzip2.bz2 文 件 的 解 压 缩 程 序 bunzip2[ 选 项 ].bz2 压 缩 文 件 bzip2recover 修 复 损 坏 的.bz2 文 件 bzip2recover.bz2 压 缩 文 件 gzip.gz 文 件 的 压 缩 程 序 gzip [ 选 项 ] 压 缩 ( 解 压 缩 ) 的 文 件 名 gunzip 解 压 缩 被 gzip 压 缩 过 的 文 件 gunzip [ 选 项 ].gz 文 件 名 unzip 解 压 缩 winzip 压 缩 的.zip 文 件 unzip [ 选 项 ].zip 压 缩 文 件 compress 早 期 的 压 缩 或 解 压 缩 程 序 ( 压 缩 后 文 件 名 为.Z) compress [ 选 项 ] 文 件 tar 对 文 件 目 录 进 行 打 包 或 解 压 缩 tar [ 选 项 ] [ 打 包 后 文 件 名 ] 文 件 目 录 列 表 1.gzip (1) 作 用 对 文 件 进 行 压 缩 和 解 压 缩, 而 且 gzip 根 据 文 件 类 型 可 自 动 识 别 压 缩 或 解 压 (2) 格 式 gzip [ 选 项 ] 压 缩 ( 解 压 缩 ) 的 文 件 名 (3) 常 见 参 数 gzip 主 要 选 项 参 数 如 表 2.23 所 示 表 2.23 gzip 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -c 将 输 出 信 息 写 到 标 准 输 出 上, 并 保 留 原 有 文 件 -d 将 压 缩 文 件 解 压 -l 对 每 个 压 缩 文 件, 显 示 下 列 字 段 : 压 缩 文 件 的 大 小 未 压 缩 时 文 件 的 大 小 压 缩 比 未 压 缩 时 文 件 的 名 字 -r 查 找 指 定 目 录 并 压 缩 或 解 压 缩 其 中 的 所 有 文 件 -t 测 试, 检 查 压 缩 文 件 是 否 完 整 -v 对 每 一 个 压 缩 和 解 压 的 文 件, 显 示 文 件 名 和 压 缩 比 18
32 (4) 使 用 实 例 test]# gzip portmap i386.rpm test]# ls portmap i386.rpm.gz test]# gzip -l portmap i386.rpm compressed uncompressed ratio uncompressed_name % portmap i386.rpm 该 实 例 将 目 录 下 的 hello.c 文 件 进 行 压 缩, 选 项 -l 列 出 了 压 缩 比 (5) 使 用 说 明 使 用 gzip 压 缩 只 能 压 缩 单 个 文 件, 而 不 能 压 缩 目 录, 其 选 项 -d 是 将 该 目 录 下 的 所 有 文 件 逐 个 进 行 压 缩, 而 不 是 压 缩 成 一 个 文 件 2.tar (1) 作 用 对 文 件 目 录 进 行 打 包 或 解 包 在 此 需 要 对 打 包 和 压 缩 这 两 个 概 念 进 行 区 分 打 包 是 指 将 一 些 文 件 或 目 录 变 成 一 个 总 的 文 件, 而 压 缩 则 是 将 一 个 大 的 文 件 通 过 一 些 压 缩 算 法 变 成 一 个 小 文 件 为 什 么 要 区 分 这 两 个 概 念 呢? 这 是 由 于 在 Linux 中 的 很 多 压 缩 程 序 ( 如 前 面 介 绍 的 gzip) 只 能 针 对 一 个 文 件 进 行 压 缩, 这 样 当 想 要 压 缩 较 多 文 件 时, 就 要 借 助 它 的 工 具 将 这 些 堆 文 件 先 打 成 一 个 包, 然 后 再 用 原 来 的 压 缩 程 序 进 行 压 缩 (2) 格 式 tar [ 选 项 ] [ 打 包 后 文 件 名 ] 文 件 目 录 列 表 tar 可 自 动 根 据 文 件 名 识 别 打 包 或 解 包 动 作, 其 中 打 包 后 文 件 名 为 用 户 自 定 义 的 打 包 后 文 件 名 称, 文 件 目 录 列 表 可 以 是 要 进 行 打 包 备 份 的 文 件 目 录 列 表, 也 可 以 是 进 行 解 包 的 文 件 目 录 列 表 (3) 主 要 参 数 tar 主 要 选 项 参 数 如 表 2.24 所 示 表 2.24 tar 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -c 建 立 新 的 打 包 文 件 -r 向 打 包 文 件 末 尾 追 加 文 件 -x 从 打 包 文 件 中 解 出 文 件 -o 将 文 件 解 开 到 标 准 输 出 -v 处 理 过 程 中 输 出 相 关 信 息 -f 对 普 通 文 件 操 作 -z 调 用 gzip 来 压 缩 打 包 文 件, 与 -x 联 用 时 调 用 gzip 完 成 解 压 缩 -j 调 用 bzip2 来 压 缩 打 包 文 件, 与 -x 联 用 时 调 用 bzip2 完 成 解 压 缩 -Z 调 用 compress 来 压 缩 打 包 文 件, 与 -x 联 用 时 调 用 compress 完 成 解 压 缩 (4) 使 用 实 例 [root@localhost home]# tar -cvf david.tar david./david/./david/.bash_logout./david/.bash_profile./david/.bashrc 19
33 ./david/.bash_history./david/my/./david/my/1.c.gz./david/my/my.c.gz./david/my/hello.c.gz./david/my/why.c.gz home]# ls -l david.tar -rw-r--r-- 1 root root Jan 14 15:01 david.tar 该 实 例 将 david 目 录 下 的 文 件 加 以 打 包, 其 中 选 项 -v 在 屏 幕 上 输 出 了 打 包 的 具 体 过 程 [david@localhost david]# tar -zxvf linux tar.gz linux / linux /drivers/ linux /drivers/video/ linux /drivers/video/aty/ 该 实 例 用 选 项 -z 调 用 gzip, 与 -x 联 用 时 完 成 解 压 缩 (5) 使 用 说 明 tar 命 令 除 了 用 于 常 规 的 打 包 之 外, 使 用 更 为 频 繁 的 是 用 选 项 -z 或 -j 调 用 gzip 或 bzip2(linux 中 另 一 种 解 压 工 具 ) 完 成 对 各 种 不 同 文 件 的 解 压 表 2.25 对 Linux 中 常 见 类 型 的 文 件 解 压 命 令 做 一 个 总 结 表 2.25 Linux 常 见 类 型 的 文 件 解 压 命 令 一 览 表 文 件 后 缀 解 压 命 令 示 例.a tar xv tar xv hello.a.z Uncompress uncompress hello.z.gz Gunzip gunzip hello.gz.tar.z tar xvzf tar xvzf hello.tar.z.tar.gz/.tgz tar xvzf tar xvzf hello.tar.gz tar.bz2 tar jxvf tar jxvf hello.tar.bz2.rpm.deb(debain 中 的 文 件 格 式 ) 安 装 :rpm i 解 压 缩 :rpm2cpio 安 装 :dpkg i 解 压 缩 :dpkg-deb --fsys-tarfile 安 装 :rpm -i hello.rpm 解 压 缩 :rpm2cpio hello.rpm 安 装 :dpkg -i hello.deb 解 压 缩 :dpkg-deb --fsys-tarhello hello.deb.zip Unzip unzip hello.zip 文 件 比 较 合 并 相 关 命 令 1.diff (1) 作 用 比 较 两 个 不 同 的 文 件 或 不 同 目 录 下 的 两 个 同 名 文 件 功 能, 并 生 成 补 丁 文 件 (2) 格 式 diff[ 选 项 ] 文 件 1 文 件 2 20
34 diff 比 较 文 件 1 和 文 件 2 的 不 同 之 处, 并 按 照 选 项 所 指 定 的 格 式 加 以 输 出 diff 的 格 式 分 为 命 令 格 式 和 上 下 文 格 式, 其 中 上 下 文 格 式 又 包 括 了 旧 版 上 下 文 格 式 和 新 版 上 下 文 格 式, 命 令 格 式 分 为 标 准 命 令 格 式 简 单 命 令 格 式 及 混 合 命 令 格 式, 它 们 之 间 的 区 别 会 在 使 用 实 例 中 进 行 详 细 讲 解 当 选 项 缺 省 时,diff 默 认 使 用 混 合 命 令 格 式 (3) 主 要 参 数 diff 主 要 选 项 参 数 如 表 2.26 所 示 表 2.26 diff 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -r 对 目 录 进 行 递 归 处 理 -q 只 报 告 文 件 是 否 有 不 同, 不 输 出 结 果 -e,-ed 命 令 格 式 -f RCS( 修 订 控 制 系 统 ) 命 令 简 单 格 式 -c,--context -u,--unified 旧 版 上 下 文 格 式 新 版 上 下 文 格 式 -Z 调 用 compress 来 压 缩 归 档 文 件, 与 -x 联 用 时 调 用 compress 完 成 解 压 缩 (4) 使 用 实 例 以 下 有 两 个 文 件 hello1.c 和 hello2.c /* hello1.c */ #include <stdio.h> void main() printf("hello!this is my home!\n"); /* hello2.c */ #include <stdio.h> void main() printf("hello!this is your home!\n"); 以 下 实 例 主 要 讲 解 了 各 种 不 同 格 式 的 比 较 和 补 丁 文 件 的 创 建 方 法 1 主 要 格 式 比 较 首 先 使 用 旧 版 上 下 文 格 式 进 行 比 较 [root@localhost david]# diff -c hello1.c hello2.c *** hello1.c Sat Jan 14 16:24: hello2.c Sat Jan 14 16:54: *************** *** 1,5 **** #include <stdio.h> void main()! printf("hello!this is my home!\n"); --- 1, #include <stdio.h> 21
35 void main()! printf("hello!this is your home!\n"); 可 以 看 出, 用 旧 版 上 下 文 格 式 进 行 输 出 时, 在 显 示 每 个 有 差 别 行 的 同 时 还 显 示 该 行 的 上 下 3 行, 区 别 的 地 方 用! 加 以 标 出, 由 于 示 例 程 序 较 短, 上 下 3 行 已 经 包 含 了 全 部 代 码 接 着 使 用 新 版 的 上 下 文 格 式 进 行 比 较 [root@localhost david]# diff -u hello1.c hello2.c --- hello1.c Sat Jan 14 16:24: hello2.c Sat Jan 14 16:54:41 -1,5 #include <stdio.h> void main() - printf("hello!this is my home!\n"); + printf("hello!this is your home!\n"); 可 以 看 出, 在 新 版 上 下 文 格 式 输 出 时, 仅 把 两 个 文 件 的 不 同 之 处 分 别 列 出, 而 相 同 之 处 没 有 重 复 列 出, 这 样 大 大 方 便 了 用 户 的 阅 读 接 下 来 使 用 命 令 格 式 进 行 比 较 [root@localhost david]# diff -e hello1.c hello2.c 4c printf("hello!this is your home!\n"); 可 以 看 出, 命 令 符 格 式 输 出 时 仅 输 出 了 不 同 的 行, 其 中 命 令 符 4c 中 的 数 字 表 示 行 编 号, 字 母 的 含 义 为 :a 表 示 添 加,b 表 示 删 除,c 表 示 更 改 因 此,-e 选 项 的 命 令 符 表 示 : 若 要 把 hello1.c 变 为 hello2.c, 就 需 要 把 hello1.c 的 第 4 行 改 为 显 示 出 的 printf( Hello!This is your home!\n ); 选 项 -f 和 选 项 -e 显 示 的 内 容 基 本 相 同, 就 是 数 字 和 字 母 的 顺 序 相 交 换 了, 从 以 下 的 输 出 结 果 可 以 看 出 [root@localhost david]# diff -f hello1.c hello2.c c4 printf("hello!this is your home!\n"); 在 diff 选 项 缺 省 的 情 况 下, 输 出 结 果 如 下 所 示 [root@localhost david]# diff hello1.c hello2.c 4c4 < printf("hello!this is my home!\n"); --- > printf("hello!this is your home!\n"); 可 以 看 出,diff 缺 省 情 况 下 的 输 出 格 式 充 分 显 示 了 如 何 将 hello1.c 转 化 为 hello2.c, 即 通 过 4c4 实 现 2 创 建 补 丁 文 件 ( 也 就 是 差 异 文 件 ) 是 diff 的 功 能 之 一, 不 同 的 选 项 格 式 可 以 生 成 与 之 相 对 应 的 补 丁 文 件, 如 下 面 扔 例 子 所 示 [root@localhost david]# diff hello1.c hello2.c >hello.patch [root@localhost david]# vi hello.patch 4c4 < printf("hello!this is my home!\n"); 22
36 --- > printf("hello!this is your home!\n"); 可 以 看 出, 使 用 缺 省 选 项 创 建 补 丁 文 件 的 内 容 和 前 面 使 用 缺 省 选 项 的 输 出 内 容 是 一 样 的 上 例 中 所 使 用 的 > 是 输 出 重 定 向 通 常 在 Linux 上 执 行 一 个 shell 命 令 行 时, 会 自 动 打 开 3 个 标 准 文 件 : 标 准 输 入 文 件 (stdin), 即 通 常 对 应 终 端 的 键 盘 ; 标 准 输 出 文 件 (stdout) 和 标 准 错 误 输 出 文 件 (stderr), 前 两 个 文 件 都 对 应 终 端 的 屏 幕 进 程 将 从 标 准 输 入 文 件 中 得 到 输 入 数 据, 并 且 将 正 常 输 出 数 据 输 出 到 标 准 输 出 文 件, 而 将 错 误 信 息 送 到 标 准 错 误 文 件 中 这 就 是 通 常 使 用 的 标 准 输 入 / 输 出 方 式 直 接 使 用 标 准 输 入 / 输 出 文 件 存 在 以 下 问 题 : 首 先, 用 户 输 入 的 数 据 只 能 使 用 一 次 当 下 次 希 望 再 次 使 用 这 些 数 据 时 就 不 得 不 重 新 输 入 同 样, 用 户 对 输 出 信 息 不 能 做 更 多 的 处 理, 只 能 等 待 程 序 的 结 束 为 了 解 决 上 述 问 题,Linux 系 统 为 输 入 输 出 的 信 息 传 送 引 入 了 两 种 方 式 : 输 入 / 输 出 重 定 向 机 制 和 管 道 ( 在 的 小 知 识 中 已 有 介 绍 ) 其 中, 输 入 重 定 向 是 指 把 命 令 ( 或 可 执 行 程 序 ) 的 标 准 输 入 重 定 向 到 指 定 的 文 件 中 也 就 是 说, 输 入 可 以 不 来 自 键 盘, 而 来 自 一 个 指 定 的 文 件 同 样, 输 出 重 定 向 是 指 把 命 令 ( 或 可 执 行 程 序 ) 的 标 准 输 出 或 标 准 错 误 输 出 重 新 定 向 到 指 定 文 件 中 这 样, 该 命 令 的 输 出 就 可 以 不 显 示 在 屏 幕 上, 而 是 写 入 到 指 定 文 件 中 就 如 上 述 例 子 中 所 用 到 的 把 diff hello1.c hello2.c 的 结 果 重 定 向 到 hello.patch 文 件 中 这 就 大 大 增 加 了 输 入 / 输 出 的 灵 活 性 2.patch (1) 作 用 命 令 跟 diff 配 合 使 用, 把 生 成 的 补 丁 文 件 应 用 到 现 有 代 码 上 (2) 格 式 patch [ 选 项 ] [ 待 patch 的 文 件 [patch 文 件 ]] 常 用 的 格 式 为 :patch -pnum [patch 文 件 ], 其 中 的 -pnum 是 选 项 参 数, 在 后 面 会 详 细 介 绍 (3) 常 见 参 数 patch 主 要 选 项 参 数 如 表 2.27 所 示 表 2.27 patch 命 令 常 见 参 数 列 表 选 项 参 数 含 义 -b 生 成 备 份 文 件 -d 把 dir 设 置 为 解 释 补 丁 文 件 名 的 当 前 目 录 -e 把 输 入 的 补 丁 文 件 看 作 是 ed 脚 本 -pnum 剥 离 文 件 名 中 的 前 NUM 个 目 录 部 分 -t 在 执 行 过 程 中 不 要 求 任 何 输 入 -v 显 示 patch 的 版 本 号 以 下 对 -punm 选 项 进 行 说 明 首 先 查 看 以 下 示 例 ( 对 分 别 位 于 xc.orig/config/cf/makefile 和 xc.bsd/config/cf/makefile 的 文 件 使 用 patch 命 令 ) diff -runa xc.orig/config/cf/makefile xc.bsd/config/cf/makefile 以 下 是 patch 文 件 的 头 标 记 23
37 --- xc.orig/config/cf/imake.cf Fri Jul 30 12:45: xc.new/config/cf/imake.cf Fri Jan 21 13:48: 这 个 patch 如 果 直 接 应 用, 那 么 它 会 去 找 xc.orig/config/cf 目 录 下 的 Makefile 文 件, 假 如 用 户 源 码 树 的 根 目 录 是 缺 省 的 xc 而 不 是 xc.orig, 则 除 了 可 以 把 xc.orig 移 到 xc 处 之 外, 还 有 什 么 简 单 的 方 法 应 用 此 patch 吗?NUM 就 是 为 此 而 设 的 :patch 会 把 目 标 路 径 名 剥 去 NUM 个 /, 也 就 是 说, 在 此 例 中,-p1 的 结 果 是 config/cf/makefile,-p2 的 结 果 是 cf/makefile 因 此, 在 此 例 中 就 可 以 用 命 令 cd xc;patch _p1 < /pathname/xxx.patch 完 成 操 作 (4) 使 用 实 例 david]# diff hello1.c hello2.c >hello1.patch david]# patch./hello1.c < hello1.patch patching file./hello1.c david]# vi hello1.c #include <stdio.h> void main() printf("hello!this is your home!\n"); 在 该 实 例 中, 由 于 patch 文 件 和 源 文 件 在 同 一 目 录 下, 因 此 直 接 给 出 了 目 标 文 件 的 目 录, 在 应 用 了 patch 之 后,hello1.c 的 内 容 变 为 了 hello2.c 的 内 容 (5) 使 用 说 明 如 果 patch 失 败,patch 命 令 会 把 成 功 的 patch 行 补 上 其 差 异, 同 时 ( 无 条 件 ) 生 成 备 份 文 件 和 一 个.rej 文 件.rej 文 件 里 没 有 成 功 提 交 的 patch 行, 需 要 手 工 打 上 补 丁 这 种 情 况 在 源 码 升 级 的 时 候 有 可 能 会 发 生 在 多 数 情 况 下,patch 程 序 可 以 确 定 补 丁 文 件 的 格 式, 当 它 不 能 识 别 时, 可 以 使 用 -c -e -n 或 者 -u 选 项 来 指 定 输 入 的 补 丁 文 件 的 格 式 由 于 只 有 GNU patch 可 以 创 建 和 读 取 新 版 上 下 文 格 式 的 patch 文 件, 因 此, 除 非 能 够 确 定 补 丁 所 面 向 的 只 是 那 些 使 用 GNU 工 具 的 用 户, 否 则 应 该 使 用 旧 版 上 下 文 格 式 来 生 成 补 丁 文 件 为 了 使 patch 程 序 能 够 正 常 工 作, 需 要 上 下 文 的 行 数 至 少 是 2 行 ( 即 至 少 是 有 一 处 差 别 的 文 件 ) 网 络 相 关 命 令 Linux 下 网 络 相 关 的 常 见 命 令 如 表 2.28 所 示, 本 书 仅 以 ifconfig 和 ftp 为 例 进 行 说 明 表 2.28 Linux 下 网 络 相 关 命 令 选 项 参 数 含 义 常 见 选 项 格 式 netstat 显 示 网 络 连 接 路 由 表 和 网 络 接 口 信 息 netstat [-an] nslookup 查 询 一 台 机 器 的 IP 地 址 和 其 对 应 的 域 名 nslookup [IP 地 址 / 域 名 ] finger 查 询 用 户 的 信 息 finger [ 选 项 ] [ 使 用 者 ] [ 用 主 机 ] ping 用 于 查 看 网 络 上 的 主 机 是 否 在 工 作 ping [ 选 项 ] 主 机 名 /IP 地 址 ifconfig 查 看 和 配 置 网 络 接 口 的 参 数 ifconfig [ 选 项 ] [ 网 络 接 口 ] ftp 利 用 ftp 协 议 上 传 和 下 载 文 件 在 本 节 中 会 详 细 讲 述 telnet 利 用 telnet 协 议 访 问 主 机 telent [ 选 项 ] [IP 地 址 / 域 名 ] ssh 利 用 ssh 登 录 对 方 主 机 ssh [ 选 项 ] [IP 地 址 ] 24
38 1.ifconfig (1) 作 用 用 于 查 看 和 配 置 网 络 接 口 的 地 址 和 参 数, 包 括 IP 地 址 网 络 掩 码 广 播 地 址, 它 的 使 用 权 限 是 超 级 用 户 (2) 格 式 ifconfig 有 两 种 使 用 格 式, 分 别 用 于 查 看 和 更 改 网 络 接 口 1 ifconfig [ 选 项 ] [ 网 络 接 口 ]: 用 来 查 看 当 前 系 统 的 网 络 配 置 情 况 2 ifconfig 网 络 接 口 [ 选 项 ] 地 址 : 用 来 配 置 指 定 接 口 ( 如 eth0 eth1) 的 IP 地 址 网 络 掩 码 广 播 地 址 等 (3) 常 见 参 数 ifconfig 第 二 种 格 式 的 常 见 选 项 参 数 如 表 2.29 所 示 表 2.29 ftp 命 令 选 项 的 常 见 参 数 列 表 选 项 参 数 含 义 -interface up down broadcast address poin to point address netmask address 指 定 的 网 络 接 口 名, 如 eth0 和 eth1 激 活 指 定 的 网 络 接 口 卡 关 闭 指 定 的 网 络 接 口 设 置 接 口 的 广 播 地 址 启 用 点 对 点 方 式 设 置 指 定 接 口 设 备 的 IP 地 址 设 置 接 口 的 子 网 掩 码 (4) 使 用 实 例 首 先, 在 本 例 中 使 用 ifconfig 的 第 一 种 格 式 来 查 看 网 络 接 口 配 置 情 况 [root@localhost ~]# ifconfig eth0 Link encap:ethernet HWaddr 00:08:02:E0:C1:8A Mask: inet addr: Bcast: inet6 addr: fe80::208:2ff:fee0:c18a/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:26931 errors:0 dropped:0 overruns:0 frame:0 TX packets:3209 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes: (6.3 MiB) TX bytes: (313.7 KiB) Interrupt:11 lo Link encap:local Loopback inet addr: Mask: inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:16436 Metric:1 RX packets:2537 errors:0 dropped:0 overruns:0 frame:0 TX packets:2537 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes: (1.9 MiB) TX bytes: (1.9 MiB) 可 以 看 出, 使 用 ifconfig 的 显 示 结 果 中 详 细 列 出 了 所 有 活 跃 接 口 的 IP 地 址 硬 件 地 址 广 播 地 址 子 网 掩 码 回 环 地 址 等 [root@localhost workplace]# ifconfig eth0 eth0 Link encap:ethernet HWaddr 00:08:02:E0:C1:8A 25
39 inet addr: Bcast: Mask: inet6 addr: fe80::208:2ff:fee0:c18a/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:27269 errors:0 dropped:0 overruns:0 frame:0 TX packets:3212 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes: (6.3 MiB) TX bytes: (314.9 KiB) Interrupt:11 在 此 例 中, 通 过 指 定 接 口 显 示 出 对 应 接 口 的 详 细 信 息 另 外, 用 户 还 可 以 通 过 指 定 参 数 -a 来 查 看 所 有 接 口 ( 包 括 非 活 跃 接 口 ) 的 信 息 接 下 来 的 示 例 指 出 了 如 何 使 用 ifconfig 的 第 二 种 格 式 来 改 变 指 定 接 口 的 网 络 参 数 配 置 [root@localhost ~]# ifconfig eth0 down [root@localhost ~]# ifconfig lo Link encap:local Loopback inet addr: Mask: inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:16436 Metric:1 RX packets:1931 errors:0 dropped:0 overruns:0 frame:0 TX packets:1931 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:0 RX bytes: (2.4 MiB) TX bytes: (2.4 MiB) 在 此 例 中, 通 过 将 指 定 接 口 的 状 态 设 置 为 DOWN, 暂 时 停 止 该 接 口 的 工 作 [root@localhost ~]# ifconfig eth netmask [root@localhost ~]# ifconfig eth0 Link encap:ethernet HWaddr 00:08:02:E0:C1:8A inet addr: Bcast: Mask: inet6 addr: fe80::208:2ff:fee0:c18a/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:1722 errors:0 dropped:0 overruns:0 frame:0 TX packets:5 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes: (143.9 KiB) TX bytes:398 (398.0 b) Interrupt:11 从 上 例 可 以 看 出,ifconfig 改 变 了 接 口 eth0 的 IP 地 址 子 网 掩 码 等, 在 之 后 的 ifconfig 查 看 中 可 以 看 出 确 实 发 生 了 变 化 (5) 使 用 说 明 用 ifconfig 命 令 配 置 的 网 络 设 备 参 数 不 重 启 就 可 生 效, 但 在 机 器 重 新 启 动 以 后 将 会 失 效, 除 非 在 网 络 接 口 配 置 文 件 中 进 行 修 改 2.ftp (1) 作 用 该 命 令 允 许 用 户 利 用 ftp 协 议 上 传 和 下 载 文 件 26
40 (2) 格 式 ftp [ 选 项 ] [ 主 机 名 /IP] ftp 相 关 命 令 包 括 使 用 命 令 和 内 部 命 令, 其 中 使 用 命 令 的 格 式 如 上 所 列, 主 要 用 于 登 录 到 ftp 服 务 器 内 部 命 令 是 指 成 功 登 录 后 进 行 的 一 系 列 操 作, 下 面 会 详 细 列 出 若 用 户 缺 省 主 机 名 /IP, 则 可 在 转 入 到 ftp 内 部 命 令 后 继 续 选 择 登 录 (3) 常 见 参 数 ftp 常 见 选 项 参 数 如 表 2.30 所 示 表 2.30 ftp 命 令 选 项 常 见 参 数 列 表 选 项 参 数 含 义 -v 显 示 远 程 服 务 器 的 所 有 响 应 信 息 -n 限 制 ftp 的 自 动 登 录 -d 使 用 调 试 方 式 -g 取 消 全 局 文 件 名 ftp 常 见 内 部 命 令 如 表 2.31 所 示 表 2.31 ftp 命 令 常 见 内 部 命 令 命 令 命 令 含 义 account[password] ascii 提 供 登 录 远 程 系 统 成 功 后 访 问 系 统 资 源 所 需 的 补 充 口 令 使 用 ASCII 类 型 传 输 方 式, 为 缺 省 传 输 模 式 bin/ type binary 使 用 二 进 制 文 件 传 输 方 式 ( 嵌 入 式 开 发 中 的 常 见 方 式 ) bye cd remote-dir cdup chmod mode file-name 退 出 ftp 会 话 过 程 进 入 远 程 主 机 目 录 进 入 远 程 主 机 目 录 的 父 目 录 将 远 程 主 机 文 件 file-name 的 存 取 方 式 设 置 为 mode close 中 断 与 远 程 服 务 器 的 ftp 会 话 ( 与 open 对 应 ) delete remote-file debug[debug-value] dir/ls[remote-dir][local-file] disconnection get remote-file[local-file] lcd[dir] mdelete[remote-file] mget remote-files mkdir dir-name mput local-file open host[port] 删 除 远 程 主 机 文 件 设 置 调 试 方 式, 显 示 发 送 至 远 程 主 机 的 每 条 命 令 显 示 远 程 主 机 目 录, 并 将 结 果 存 入 本 地 文 件 local-file 同 close 将 远 程 主 机 的 文 件 remote-file 传 至 本 地 硬 盘 的 local-file 将 本 地 工 作 目 录 切 换 至 dir 删 除 远 程 主 机 文 件 传 输 多 个 远 程 文 件 在 远 程 主 机 中 建 立 一 个 目 录 将 多 个 文 件 传 输 至 远 程 主 机 建 立 与 指 定 ftp 服 务 器 的 连 接, 可 指 定 连 接 端 口 passive 进 入 被 动 传 输 方 式 ( 在 这 种 模 式 下, 数 据 连 接 是 由 客 户 程 序 发 起 的 ) put local-file[remote-file] reget remote-file[local-file] size file-name system 将 本 地 文 件 local-file 传 送 至 远 程 主 机 类 似 于 get, 但 若 local-file 存 在, 则 从 上 次 传 输 中 断 处 继 续 传 输 显 示 远 程 主 机 文 件 大 小 显 示 远 程 主 机 的 操 作 系 统 类 型 (4) 使 用 实 例 27
41 首 先, 在 本 例 中 使 用 ftp 命 令 访 问 ftp://study.byr.edu.cn 站 点 ~]# ftp study.byr.edu.cn Connected to study.byr.edu.cn. 220 Microsoft FTP Service 500 'AUTH GSSAPI': command not understood 500 'AUTH KERBEROS_V4': command not understood KERBEROS_V4 rejected as an authentication type Name (study.byr.edu.cn:root): anonymous 331 Anonymous access allowed, send identity ( name) as password. Password: 230 Anonymous user logged in. Remote system type is Windows_NT. 由 于 该 站 点 可 以 匿 名 访 问, 因 此, 在 用 户 名 处 输 入 anonymous, 在 Password 处 输 入 任 意 一 个 地 址 即 可 登 录 成 功 ftp> dir 227 Entering Passive Mode (211,68,71,83,11,94). 125 Data connection already open; Transfer starting :00PM <DIR> Audio :41PM <DIR> BUPT_NET_Material :38PM <DIR> Document :47PM <DIR> Incoming :09AM <DIR> Material 226 Transfer complete. 以 上 使 用 ftp 内 部 命 令 dir 列 出 了 在 该 目 录 下 文 件 及 目 录 的 信 息 ftp> cd /Document/Wrox/Wrox.Beginning.SQL.Feb.2005.eBook-DDU 250 CWD command successful. ftp> pwd 257 "/Document/Wrox/Wrox.Beginning.SQL.Feb.2005.eBook-DDU" is current directory. 以 上 实 例 通 过 cd 命 令 进 入 相 应 的 目 录, 可 通 过 pwd 命 令 进 行 验 证 ftp> lcd /root/workplace Local directory now /root/workplace ftp> get d-wbsq01.zip local: d-wbsq01.zip remote: d-wbsq01.zip 200 PORT command successful. 150 Opening ASCII mode data connection for d-wbsq01.zip( bytes). WARNING! 5350 bare linefeeds received in ASCII mode File may not have transferred correctly. 226 Transfer complete bytes received in 1.7 seconds (8.6e+02 Kbytes/s) 接 下 来 通 过 lcd 命 令 首 先 改 变 用 户 的 本 地 工 作 目 录, 也 就 是 希 望 下 载 或 上 传 的 工 作 目 录, 接 着 通 过 get 命 令 进 行 下 载 文 件 由 于 ftp 默 认 使 用 ASCII 模 式, 因 此, 若 希 望 改 为 其 他 模 式 如 bin, 直 接 输 入 bin 即 可, 如 下 所 示 : ftp> bin 200 Type set to I. 28
42 ftp> bye 221 最 后 用 bye 命 令 退 出 ftp 程 序 (5) 使 用 说 明 若 是 需 要 匿 名 登 录, 则 在 Name (**.**.**.**): 处 键 入 anonymous, 在 Password: 处 键 入 自 己 的 地 址 即 可 若 要 传 送 二 进 制 文 件, 务 必 要 把 模 式 改 为 bin 2.2 Linux 启 动 过 程 详 解 在 了 解 了 Linux 的 常 见 命 令 之 后, 下 面 详 细 讲 解 Linux 的 启 动 过 程 Linux 的 启 动 过 程 包 含 了 Linux 工 作 原 理 的 精 髓, 而 且 在 嵌 入 式 开 发 过 程 中 非 常 需 要 这 方 面 的 知 识 概 述 用 户 开 机 启 动 Linux 过 程 如 下 : (1) 当 用 户 打 开 PC(intel CPU) 的 电 源 时,CPU 将 自 动 进 入 实 模 式, 并 从 地 址 0xFFFF0000 开 始 自 动 执 行 程 序 代 码, 这 个 地 址 通 常 是 ROM-BIOS 中 的 地 址 这 时 BIOS 进 行 开 机 自 检, 并 按 BIOS 中 设 置 的 启 动 设 备 ( 通 常 是 硬 盘 ) 进 行 启 动, 接 着 启 动 设 备 上 安 装 的 引 导 程 序 lilo 或 grub 开 始 引 导 Linux ( 也 就 是 启 动 设 备 的 第 一 个 扇 区 ), 这 时,Linux 才 获 得 了 启 动 权 (2) 第 二 阶 段,Linux 首 先 进 行 内 核 的 引 导, 主 要 完 成 磁 盘 引 导 读 取 机 器 系 统 数 据 实 模 式 和 保 护 模 式 的 切 换 加 载 数 据 段 寄 存 器 以 及 重 置 中 断 描 述 符 表 等 (3) 第 三 阶 段 执 行 init 程 序 ( 也 就 是 系 统 初 始 化 工 作 ),init 程 序 调 用 了 rc.sysinit 和 rc 等 程 序, 而 rc.sysinit 和 rc 在 完 成 系 统 初 始 化 和 运 行 服 务 的 任 务 后, 返 回 init (4) 第 四 阶 段,init 启 动 mingetty, 打 开 终 端 供 用 户 登 录 系 统, 用 户 登 录 成 功 后 进 入 了 shell, 这 样 就 完 成 了 从 开 机 到 登 录 的 整 个 启 动 过 程 Linux 启 动 总 体 流 程 如 图 2.2 所 示, 其 中 的 4 个 阶 段 分 别 由 同 步 棒 隔 开 第 一 阶 段 不 涉 及 Linux 自 身 的 启 动 过 程, 下 面 分 别 对 第 二 和 第 三 阶 段 进 行 详 细 讲 解 内 核 引 导 阶 段 图 2.2 Linux 启 动 总 体 流 程 图 在 grub 或 lilo 等 引 导 程 序 成 功 完 成 引 导 Linux 系 统 的 任 务 后,Linux 就 从 它 们 手 中 接 管 了 CPU 的 控 制 权 用 户 可 以 从 上 下 载 最 新 版 本 的 源 码 进 行 阅 读, 其 目 录 为 :linux-2.6.*.*/arch/i386/boot 在 启 动 过 程 中 主 要 用 到 该 目 录 下 的 几 个 文 件 :bootsect.s setup.s 以 及 compressed 子 目 录 下 的 head.s 等 29
43 Linux 的 内 核 通 常 是 压 缩 过 的, 包 括 上 述 提 到 的 那 几 个 重 要 的 汇 编 程 序, 它 们 都 是 在 压 缩 内 核 vmlinuz 中 的 Linux 中 提 供 的 内 核 包 含 了 众 多 驱 动 和 功 能, 容 量 较 大, 压 缩 内 核 可 以 节 省 大 量 的 空 间, 压 缩 的 内 核 在 启 动 时 可 以 对 自 身 进 行 解 包 (1)bootsect 阶 段 当 grub 读 入 vmlinuz 后, 会 根 据 bootsect(512 字 节 ) 把 它 自 身 和 setup 程 序 段 读 到 以 不 大 于 0x90000 开 始 的 的 内 存 里 ( 注 意 : 在 以 往 的 引 导 协 议 里 是 放 在 0x90000, 但 现 在 有 所 变 化 ), 然 后 grub 会 跳 过 bootsect 那 512 字 节 的 程 序 段, 直 接 运 行 setup 里 的 第 一 跳 指 令 就 是 说 bzimage 里 bootsect 的 程 序 没 有 再 被 执 行 了, 而 bootsect.s 在 完 成 了 指 令 搬 移 以 后 就 退 出 了 之 后 执 行 权 就 转 到 了 setup.s 的 程 序 中 (2)setup 阶 段 setup.s 的 主 要 功 能 是 利 用 ROM BIOS 中 断 读 取 机 器 系 统 数 据, 并 将 系 统 参 数 ( 包 括 内 存 磁 盘 等 ) 保 存 到 以 0x90000~0x901FF 开 始 的 内 存 中 此 外,setup.S 还 将 video.s 中 的 代 码 包 含 进 来, 检 测 和 设 置 显 示 器 和 显 示 模 式 最 后, 它 还 会 设 置 CPU 的 控 制 寄 存 器 CR0( 也 称 机 器 状 态 字 ), 从 而 进 入 32 位 保 护 模 式 运 行, 并 跳 转 到 绝 对 地 址 为 0x100000( 虚 拟 地 址 0xC x100000) 的 位 置 当 CPU 跳 到 0x 时, 将 执 行 arch/i386/kernel/head.s 中 的 startup_32 (3)head.S 阶 段 当 运 行 到 head.s 时, 系 统 已 经 运 行 在 保 护 模 式, 而 head.s 完 成 的 一 个 重 要 任 务 就 是 将 内 核 解 压 内 核 是 通 过 压 缩 的 方 式 放 在 内 存 中 的,head.S 通 过 调 用 misc.c 中 定 义 的 decompress_kernel() 函 数, 将 内 核 vmlinuz 解 压 到 0x 接 下 来 head.s 程 序 完 成 寄 存 器 分 页 表 的 初 始 化 工 作, 但 要 注 意 的 是, 这 个 head.s 程 序 与 完 成 解 压 缩 工 作 的 head.s 程 序 是 不 同 的, 它 在 源 代 码 中 的 位 置 是 arch/i386/kernel/head.s 在 完 成 了 初 始 化 之 后,head.S 就 跳 转 到 start_kernel() 函 数 中 去 了 (4)main.c 阶 段 start_kernel() 是 init/main.c 中 定 义 的 函 数,start_kernel() 调 用 了 一 系 列 初 始 化 函 数, 进 行 内 核 的 初 始 化 工 作 要 注 意 的 是, 在 初 始 化 之 前 系 统 中 断 仍 然 是 被 屏 蔽 的, 另 外 内 核 也 处 于 被 锁 定 状 态, 以 保 证 只 有 一 个 CPU 用 于 Linux 系 统 的 启 动 在 start_kernel() 的 最 后, 调 用 了 init() 函 数, 也 就 是 下 面 要 讲 述 的 init 阶 段 init 阶 段 在 加 载 了 内 核 之 后, 由 内 核 执 行 引 导 的 第 一 个 进 程 是 init 进 程, 该 进 程 号 始 终 是 1 init 进 程 根 据 其 配 置 文 件 /etc/inittab 主 要 完 成 系 统 的 一 系 列 初 始 化 的 任 务 由 于 该 配 置 文 件 是 init 进 程 执 行 的 惟 一 依 据, 因 此 先 对 它 的 格 式 进 行 统 一 讲 解 inittab 文 件 中 除 了 注 释 行 外, 每 一 行 都 有 如 下 格 式 : id:runlevels:action:process (1)id id 是 配 置 记 录 标 识 符, 由 1~4 个 字 符 组 成, 对 于 getty 或 mingetty 等 其 他 login 程 序 项, 要 求 id 与 tty 的 编 号 相 同, 否 则 getty 程 序 将 不 能 正 常 工 作 (2)runlevels runlevels 是 运 行 级 别 记 录 符, 一 般 使 用 0~6 以 及 S 和 s 其 中,0 1 6 运 行 级 别 为 系 统 保 留 :0 作 为 shutdown 动 作,1 作 为 重 启 至 单 用 户 模 式,6 为 重 启 ;S 和 s 意 义 相 同, 表 示 单 用 户 模 式, 且 无 需 inittab 文 件, 因 此 也 不 在 inittab 中 出 现 7~9 级 别 也 是 可 以 使 用 的, 传 统 的 UNIX 系 统 没 有 定 义 这 几 个 级 别 runlevel 可 以 是 并 列 的 多 个 值, 对 大 多 数 action 来 说, 仅 当 runlevel 与 当 前 运 行 级 别 匹 配 成 功 才 会 执 行 (3)action action 字 段 用 于 描 述 系 统 执 行 的 特 定 操 作, 它 的 常 见 设 置 有 :initdefault sysinit boot bootwait respawn 等 30
44 initdefault 用 于 标 识 系 统 缺 省 的 启 动 级 别 当 init 由 内 核 激 活 以 后, 它 将 读 取 inittab 中 的 initdefault 项, 取 得 其 中 的 runlevel, 并 作 为 当 前 的 运 行 级 别 如 果 没 有 inittab 文 件, 或 者 其 中 没 有 initdefault 项,init 将 在 控 制 台 上 请 求 输 入 runlevel sysinit boot bootwait 等 action 将 在 系 统 启 动 时 无 条 件 运 行, 忽 略 其 中 的 runlevel respawn 字 段 表 示 该 类 进 程 在 结 束 后 会 重 新 启 动 运 行 (4)process process 字 段 设 置 启 动 进 程 所 执 行 的 命 令 以 下 结 合 笔 者 系 统 中 的 inittab 配 置 文 件 详 细 讲 解 该 配 置 文 件 完 成 的 功 能 1. 确 定 用 户 登 录 模 式 在 /etc/inittab 中 列 出 了 如 下 所 示 的 登 录 模 式, 主 要 有 单 人 维 护 模 式 多 用 户 无 网 络 模 式 文 字 界 面 多 用 户 模 式 X-Windows 多 用 户 模 式 等 其 中 的 单 人 维 护 模 式 (run level 为 1) 类 似 于 Windows 中 的 安 全 模 式, 在 这 种 情 况 下, 系 统 不 加 载 复 杂 的 模 式 从 而 使 系 统 能 够 正 常 启 动 在 这 些 模 式 中 最 为 常 见 的 是 3 或 5, 其 中 本 系 统 中 默 认 的 为 5, 也 就 是 X-Windows 多 用 户 模 式 以 下 是 在 /etc/inittab 文 件 中 设 置 系 统 启 动 模 式 的 部 分 # Default runlevel. The runlevels used by RHS are: # 0 - halt (Do NOT set initdefault to this) # 1 - Single user mode # 2 - Multiuser, without NFS (The same as 3, if you do not have networking) # 3 - Full multiuser mode ( 文 本 界 面 启 动 模 式 ) # 4 - unused # 5 - X11 ( 图 形 界 面 启 动 模 式 ) # 6 - reboot (Do NOT set initdefault to this) # id:5:initdefault: 2. 执 行 /etc/rc.d/rc.sysinit 在 确 定 了 登 录 模 式 之 后, 就 要 开 始 将 Linux 的 主 机 信 息 读 入 系 统, 其 过 程 是 通 过 运 行 /etc/rc.d/rc.sysinit 脚 本 而 完 成 的 查 看 此 文 件 可 以 看 出, 在 这 里 确 定 了 默 认 路 径 主 机 名 称 /etc/sysconfig/network 中 所 记 录 的 网 络 信 息 等 以 下 是 在 /etc/inittab 文 件 中 运 行 该 脚 本 的 部 分 # System initialization. si::sysinit:/etc/rc.d/rc.sysinit 3. 加 载 内 核 的 外 挂 模 块, 执 行 各 运 行 级 别 的 脚 本 以 及 进 入 用 户 登 录 界 面 31
45 在 此, 主 要 是 读 取 模 块 加 载 配 置 文 件 (/etc/modules.conf), 以 确 认 需 要 加 载 哪 些 模 块 接 下 来 会 根 据 不 同 的 运 行 级 (run level), 通 过 带 参 数 ( 运 行 级 ) 运 行 /etc/rc.d/rc 脚 本, 加 载 不 同 的 模 块, 启 动 系 统 服 务 init 进 程 会 等 待 (wait) /etc/rc.d/rc 脚 本 的 返 回 系 统 还 需 要 配 置 一 些 异 常 关 机 的 处 理 部 分, 最 后 通 过 /sbin/mingetty 打 开 几 个 虚 拟 终 端 (tty1~tty6), 用 于 用 户 登 录 如 果 运 行 级 为 5( 图 形 界 面 启 动 ), 则 运 行 xdm 程 序, 给 用 户 提 供 xdm 图 形 界 面 的 登 录 方 式 如 果 在 本 地 打 开 一 个 虚 拟 终 端, 当 这 个 终 端 超 时 没 有 用 户 登 录 或 者 太 久 没 有 用 户 击 键 时, 该 终 端 会 退 出 执 行, 脚 本 中 的 respawn 即 告 诉 init 进 程 重 新 打 开 该 终 端, 否 则 在 经 过 一 段 时 间 之 后, 我 们 会 发 现 这 个 终 端 消 失 了, 无 法 利 用 ALT+Fn 切 换 以 下 是 /etc/inittab 文 件 中 的 相 应 部 分 l0:0:wait:/etc/rc.d/rc 0 l1:1:wait:/etc/rc.d/rc 1 l2:2:wait:/etc/rc.d/rc 2 l3:3:wait:/etc/rc.d/rc 3 l4:4:wait:/etc/rc.d/rc 4 l5:5:wait:/etc/rc.d/rc 5 l6:6:wait:/etc/rc.d/rc 6 # Trap CTRL-ALT-DELETE ca::ctrlaltdel:/sbin/shutdown -t3 -r now # When our UPS tells us power has failed, assume we have a few minutes # of power left. Schedule a shutdown for 2 minutes from now. # This does, of course, assume you have powerd installed and your # UPS connected and working correctly. pf::powerfail:/sbin/shutdown -f -h +2 "Power Failure; System Shutting Down" # If power was restored before the shutdown kicked in, cancel it. pr:12345:powerokwait:/sbin/shutdown -c "Power Restored; Shutdown Cancelled" # Run gettys in standard runlevels 1:2345:respawn:/sbin/mingetty tty1 2:2345:respawn:/sbin/mingetty tty2 3:2345:respawn:/sbin/mingetty tty3 4:2345:respawn:/sbin/mingetty tty4 5:2345:respawn:/sbin/mingetty tty5 6:2345:respawn:/sbin/mingetty tty6 # Run xdm in runlevel 5 x:5:respawn:/etc/x11/prefdm -nodaemon 2.3 Linux 系 统 服 务 init 进 程 的 作 用 是 启 动 Linux 系 统 服 务 ( 也 就 是 运 行 在 后 台 的 守 护 进 程 ) Linux 的 系 统 服 务 包 括 两 种, 第 一 种 是 独 立 运 行 的 系 统 服 务, 它 们 常 驻 内 存 中, 自 开 机 后 一 直 运 行 着 ( 如 httpd), 具 有 很 快 的 响 应 速 度 ; 第 二 种 是 由 xinet 设 定 的 服 务 xinet 能 够 同 时 监 听 多 个 指 定 的 端 口, 在 接 受 用 户 请 求 时, 它 能 够 根 据 用 户 请 求 的 端 口 不 同, 启 动 不 同 的 网 络 服 务 进 程 来 处 理 这 些 用 户 请 求 因 此, 可 以 把 xinetd 看 作 一 个 启 动 服 务 的 管 理 服 务 器, 它 决 定 把 一 个 客 户 请 求 交 给 哪 个 程 序 处 理, 然 后 启 动 相 应 的 守 护 进 程 下 面 分 别 介 绍 这 两 种 系 统 服 务 32
46 2.3.1 独 立 运 行 的 服 务 独 立 运 行 的 系 统 服 务 的 启 动 脚 本 都 放 在 目 录 /etc/rc.d/init.d/ 中 如 笔 者 系 统 中 的 系 统 服 务 的 启 动 脚 本 有 : [root@localhost init.d]# ls /etc/rc.d/init.d acpid dc_client iptables named pand rpcsvcgssd tux anacron dc_server irda netdump pcmcia saslauthd vncserver apmd diskdump irqbalance netfs portmap sendmail vsftpd arptables_jf dovecot isdn netplugd psacct single watchquagga atd dund killall network rawdevices smartd winbind autofs firstboot kudzu NetworkManager readahead smb xfs 为 了 指 定 特 定 运 行 级 别 服 务 的 开 启 或 关 闭, 系 统 的 各 个 不 同 运 行 级 别 都 有 不 同 的 脚 本 文 件, 其 目 录 为 /etc/rc.d/rcn.d, 其 中 的 N 分 别 对 应 不 用 的 运 行 级 别 读 者 可 以 进 入 各 个 不 同 的 运 行 级 别 目 录, 查 看 相 应 服 务 是 在 开 启 还 是 关 闭 状 态, 如 进 入 /rc3.d 目 录 中 的 文 件 如 下 所 示 : [root@localhost rc3.d]# ls /etc/rc.d/rc3.d K02NetworkManager K35winbind K89netplugd S10networ S28autofs S95anacron K05saslauthd K36lisa K90bluetooth S12syslog S40smartd S95atd K10dc_server K45named K94diskdump S13irqbalance S44acpid S97messagebus K10psacct K50netdump K99microcode_ctl S13portmap S55cups S97rhnsd 可 以 看 到, 每 个 对 应 的 服 务 都 以 K 或 S 开 头, 其 中 的 K 代 表 关 闭 (kill), 其 中 的 S 代 表 启 动 (start), 用 户 可 以 使 用 命 令 +start stop status restart 来 对 相 应 的 服 务 进 行 操 作 在 执 行 完 相 应 的 rcn.d 目 录 下 的 脚 本 文 件 后,init 最 后 会 执 行 rc.local 来 启 动 本 地 服 务, 因 此, 用 户 若 想 把 某 些 非 系 统 服 务 设 置 为 自 启 动, 可 以 编 辑 rc.local 脚 本 文 件, 加 上 相 应 的 执 行 语 句 即 可 另 外, 读 者 还 可 以 使 用 命 令 service+ 系 统 服 务 + 操 作 来 方 便 地 实 现 相 应 服 务 的 操 作, 如 下 所 示 : [root@localhost xinetd.d]# service xinetd restart 停 止 xinetd: [ 确 定 ] 开 启 xinetd: [ 确 定 ] xinetd 设 定 的 服 务 xinetd 管 理 系 统 中 不 经 常 使 用 的 服 务, 这 些 服 务 程 序 只 有 在 有 请 求 时 才 由 xinetd 服 务 负 责 启 动, 一 旦 运 行 完 毕 服 务 自 动 结 束 xinetd 的 配 置 文 件 为 /etc/xinetd.conf, 它 对 xinet 的 默 认 参 数 进 行 了 配 置 : # # Simple configuration file for xinetd # # Some defaults, and include /etc/xinetd.d/ defaults instances = 60 log_type = SYSLOG authpriv log_on_success = HOST PID log_on_failure = HOST cps =
47 includedir /etc/xinetd.d 从 该 配 置 文 件 的 最 后 一 行 可 以 看 出,xinetd 启 动 /etc/xinetd.d 为 其 配 置 文 件 目 录 在 对 应 的 配 置 文 件 目 录 中 可 以 看 到 每 一 个 服 务 的 基 本 配 置, 如 tftp 服 务 的 配 置 脚 本 文 件 如 下 : service tftp socket_type = dgram ( 数 据 报 格 式 ) protocol = udp ( 使 用 UDP 传 输 ) wait user server server_args = yes = root = /usr/sbin/in.tftpd = -s /tftpboot disable = yes ( 不 启 动 ) per_source = 11 cps = flags = IPv 系 统 服 务 的 其 他 相 关 命 令 除 了 在 本 节 中 提 到 的 service 命 令 之 外, 与 系 统 服 务 相 关 的 命 令 还 有 chkconfig, 它 也 是 一 个 很 好 的 工 具, 能 够 为 不 同 的 系 统 级 别 设 置 不 同 的 服 务 (1)chkconfig --list( 注 意 在 list 前 有 两 个 小 连 线 ): 查 看 系 统 服 务 设 定 示 例 : [root@localhost xinetd.d]# chkconfig --list sendmail 0: 关 闭 1: 关 闭 2: 打 开 3: 打 开 4: 打 开 5: 打 开 6: 关 闭 snmptrapd 0: 关 闭 1: 关 闭 2: 关 闭 3: 关 闭 4: 关 闭 5: 关 闭 6: 关 闭 gpm 0: 关 闭 1: 关 闭 2: 打 开 3: 打 开 4: 打 开 5: 打 开 6: 关 闭 syslog 0: 关 闭 1: 关 闭 2: 打 开 3: 打 开 4: 打 开 5: 打 开 6: 关 闭 (2)chkconfig --level N [ 服 务 名 称 ] 指 定 状 态 : 将 指 定 级 别 的 某 个 系 统 服 务 配 置 为 指 定 状 态 ( 打 开 / 关 闭 ) [root@localhost xinetd.d]# chkconfig --list grep ntpd ntpd 0: 关 闭 1: 关 闭 2 关 闭 3: 关 闭 4: 关 闭 5: 关 闭 6: 关 闭 [root@localhost ~]# chkconfig --level 3 ntpd on [root@localhost ~]# chkconfig --list grep ntpd ntpd 0: 关 闭 1: 关 闭 2: 关 闭 3: 打 开 4: 关 闭 5: 关 闭 6: 关 闭 另 外, 在 节 系 统 命 令 列 表 中 指 出 的 setup 程 序 中 也 可 以 设 定, 而 且 是 图 形 界 面, 操 作 较 为 方 便, 读 者 可 以 自 行 尝 试 2.4 实 验 内 容 在 Linux 下 解 压 常 见 软 件 1. 实 验 目 的 34
48 在 Linux 下 安 装 一 个 完 整 的 软 件 ( 嵌 入 式 Linux 的 必 备 工 具 交 叉 编 译 工 具 ), 掌 握 Linux 常 见 命 令, 学 会 设 置 环 境 变 量, 同 时 搭 建 起 嵌 入 式 Linux 的 交 叉 编 译 环 境 ( 关 于 交 叉 编 译 的 具 体 概 念 在 本 书 后 面 会 详 细 讲 解 ), 为 今 后 的 实 验 打 下 良 好 的 基 础 2. 实 验 内 容 在 Linux 中 解 压 cross tar.bz2, 并 添 加 到 系 统 环 境 变 量 中 去 3. 实 验 步 骤 (1) 将 光 盘 中 的 cross tar.bz2 复 制 到 Windows 下 的 任 意 盘 中 (2) 重 启 机 器 转 到 Linux 下, 并 用 普 通 用 户 身 份 登 录 (3) 打 开 终 端, 并 切 换 到 超 级 用 户 模 式 下 命 令 为 :su - root (4) 查 看 cross tar.bz2 所 在 的 Windows 下 对 应 分 区 的 格 式, 并 记 下 其 文 件 设 备 名 称, 如 /dev/hda1 等 命 令 为 :fdisk -l (5) 使 用 mkdir 命 令 在 /mnt 新 建 子 目 录 作 为 挂 载 点 命 令 为 :mkdir /mnt/win (6) 挂 载 Windows 相 应 分 区 若 是 vfat 格 式, 则 命 令 为 :mount t vfat /dev/hda* /mnt/win 由 于 ntfs 格 式 在 Linux 的 早 期 版 本 中 是 不 安 全 的, 只 能 读, 不 能 写, 因 此 最 好 把 文 件 放 到 fat32 格 式 的 文 件 系 统 中 (7) 进 入 挂 载 目 录, 查 看 是 否 确 实 挂 载 上 命 令 为 :cd /mnt/win;ls (8) 在 /usr/local 下 建 一 个 名 为 arm 的 目 录 命 令 为 :mkdir /usr/local/arm (9) 将 cross tar.bz2 复 制 到 刚 刚 创 建 的 目 录 中 命 令 为 :cp /mnt/win/cross tar.bz2 /usr/local/arm 若 cross tar.bz2 在 当 前 目 录 中, 则 可 将 命 令 简 写 为 :cp./cross tar.bz2 /usr/local/arm (10) 将 当 前 工 作 目 录 转 到 /usr/local/arm 下 命 令 为 :cd /usr/local/arm 为 什 么 要 将 此 目 录 创 建 在 /usr/local 下? (11) 解 压 缩 该 软 件 包 命 令 为 :tar jxvf cross tar.bz2 (12) 将 此 目 录 下 的 /bin 目 录 添 加 到 环 境 变 量 中 去 命 令 为 :export PATH=/usr/local/arm/3.3.2/bin :$PATH 用 此 方 法 添 加 的 环 境 变 量 在 掉 电 后 会 丢 失, 因 此, 可 以 在 /etc/bashrc 的 最 后 一 行 添 加 以 上 命 令 (13) 查 看 该 路 径 是 否 已 添 加 到 环 境 变 量 中 命 令 为 :echo $PATH 35
49 4. 实 验 结 果 成 功 搭 建 了 嵌 入 式 Linux 的 交 叉 编 译 环 境, 熟 悉 Linux 下 常 用 命 令, 如 su mkdir mount cp tar 等, 并 学 会 添 加 环 境 变 量, 同 时 对 Linux 的 目 录 结 构 有 了 更 进 一 步 的 理 解 定 制 Linux 系 统 服 务 1. 实 验 目 的 通 过 定 制 Linux 系 统 服 务, 进 一 步 理 解 Linux 的 守 护 进 程, 能 够 更 加 熟 练 运 用 Linux 操 作 基 本 命 令, 同 时 也 加 深 对 init 进 程 的 了 解 和 掌 握 2. 实 验 内 容 查 看 Linux 系 统 服 务, 并 定 制 其 系 统 服 务 3. 实 验 步 骤 (1) 查 看 系 统 的 默 认 运 行 级 别 命 令 为 :cat /etc/inittab( 假 设 当 前 运 行 级 别 为 N) (2) 进 入 相 应 级 别 的 服 务 脚 本 目 录, 查 看 哪 些 服 务 是 系 统 启 动 的 独 立 运 行 的 服 务, 并 做 下 记 录 命 令 为 :cd /etc/rc.d/rcn.d (3) 利 用 命 令 查 看 系 统 开 机 自 启 动 服 务, 与 上 次 查 看 结 果 进 行 比 较, 找 出 其 中 的 区 别, 并 思 考 其 中 的 原 因 命 令 为 :chkconfig list (4) 记 录 chkconfig list 命 令 中 由 xinet 管 理 的 服 务, 并 将 其 中 启 动 的 服 务 做 下 记 录 (5) 进 入 xinet 配 置 管 理 的 相 应 目 录, 查 看 是 否 与 chkconfig list 所 得 的 结 果 相 吻 合, 并 查 看 相 应 脚 本 文 件 命 令 为 :cd /etc/xinetd.d (6) 将 sshd 服 务 停 止 命 令 为 :service sshd stop (7) 将 sshd 服 务 设 置 为 开 机 不 启 动 命 令 为 :chkconfig level N sshd stop (8) 查 看 该 设 置 是 否 生 效 命 令 为 :chkconfig list (9) 查 看 系 统 中 所 有 服 务 及 其 端 口 号 列 表 命 令 为 :cat /etc/services (10) 将 sshd 服 务 端 口 号 改 为 4022 命 令 为 :vi /etc/services (11) 重 启 sshd 服 务, 验 证 所 改 的 端 口 号 是 否 生 效 命 令 为 :service sshd start (12) 重 启 Linux 系 统, 验 证 所 改 的 服 务 开 机 启 动 是 否 生 效 36
50 4. 实 验 结 果 分 析 本 实 验 通 过 验 证 Linux 系 统 服 务 的 启 动 状 态, 进 一 步 明 确 Linux 系 统 服 务 启 动 的 流 程, 更 深 入 地 理 解 了 Linux 系 统 操 作 本 实 验 还 通 过 定 制 Linux 系 统 服 务 sshd 的 开 机 启 动 状 态 和 端 口 号, 熟 悉 了 Linux 的 系 统 定 制 步 骤 2.5 本 章 小 结 本 章 首 先 讲 解 了 Linux 操 作 的 基 本 命 令, 这 些 命 令 是 使 用 Linux 的 基 础 Linux 基 本 命 令 包 括 用 户 系 统 相 关 命 令 文 件 目 录 相 关 命 令 压 缩 打 包 相 关 命 令 比 较 合 并 相 关 命 令 以 及 网 络 相 关 命 令 着 重 介 绍 了 每 一 类 命 令 中 有 代 表 性 的 重 要 命 令 及 其 用 法, 并 给 出 了 具 体 实 例, 对 其 他 命 令 列 出 了 其 使 用 方 法 希 望 读 者 能 举 一 反 三 灵 活 应 用 接 下 来, 本 章 讲 解 了 Linux 启 动 过 程, 这 部 分 的 内 容 比 较 难, 但 对 深 入 理 解 Linux 系 统 是 非 常 有 帮 助 的, 希 望 读 者 能 反 复 阅 读 最 后, 本 章 还 讲 解 了 Linux 系 统 服 务, 包 括 独 立 运 行 的 服 务 和 xinetd 设 定 的 服 务, 并 且 讲 解 了 Linux 系 统 中 设 定 服 务 的 常 用 方 法 本 章 安 排 了 两 个 实 验, 实 验 一 通 过 一 个 完 整 的 操 作 使 读 者 能 够 熟 练 使 用 Linux 的 基 本 命 令, 实 验 二 讲 解 了 如 何 定 制 Linux 系 统 服 务, 希 望 读 者 能 够 认 真 动 手 实 践 2.6 思 考 与 练 习 1. 更 改 目 录 的 名 称, 如 把 /home/david 变 为 /home/john 2. 如 何 将 文 件 属 性 变 为 -rwxrw-r--? 3. 下 载 最 新 Linux 源 码, 并 解 压 缩 至 /usr/src 目 录 下 4. 修 改 Telnet FTP 服 务 的 端 口 号 联 系 方 式 集 团 官 网 : 嵌 入 式 学 院 : 移 动 互 联 网 学 院 : 企 业 学 院 : 物 联 网 学 院 : 研 发 中 心 :dev.hqyj.com 集 团 总 部 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 内 华 清 远 见 教 育 集 团 北 京 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 区, 电 话 : /5 上 海 地 址 : 上 海 市 徐 汇 区 漕 溪 路 银 海 大 厦 A 座 8 层, 电 话 : 深 圳 地 址 : 深 圳 市 龙 华 新 区 人 民 北 路 美 丽 AAA 大 厦 15 层, 电 话 : 成 都 地 址 : 成 都 市 武 侯 区 科 华 北 路 99 号 科 华 大 厦 6 层, 电 话 : 南 京 地 址 : 南 京 市 白 下 区 汉 中 路 185 号 鸿 运 大 厦 10 层, 电 话 : 武 汉 地 址 : 武 汉 市 工 程 大 学 卓 刀 泉 校 区 科 技 孵 化 器 大 楼 8 层, 电 话 : 西 安 地 址 : 西 安 市 高 新 区 高 新 一 路 12 号 创 业 大 厦 D3 楼 5 层, 电 话 :
51 嵌 入 式 LINUX 应 用 程 序 开 发 标 准 教 程 作 者 : 华 清 远 见 第 3 章 Linux 下 C 编 程 基 础 Linux C 熟 悉 Linux 系 统 下 的 开 发 环 境 熟 悉 vi 的 基 本 操 作 熟 练 emacs 的 基 本 操 作 熟 悉 gcc 编 译 器 的 基 本 原 理 熟 练 使 用 gcc 编 译 器 的 常 用 选 项 熟 练 使 用 gdb 的 调 试 技 术 熟 悉 makefile 基 本 原 理 及 语 法 规 范 熟 练 使 用 autoconf 和 automake 生 成 makefile
52 3.1 Linux 下 C 语 言 编 程 概 述 C 语 言 简 单 回 顾 C 语 言 最 早 是 由 贝 尔 实 验 室 的 Dennis Ritchie 为 了 UNIX 的 辅 助 开 发 而 编 写 的, 它 是 在 B 语 言 的 基 础 上 开 发 出 来 的 尽 管 C 语 言 不 是 专 门 针 对 UNIX 操 作 系 统 或 机 器 编 写 的, 但 它 与 UNIX 系 统 的 关 系 十 分 紧 密 由 于 它 的 硬 件 无 关 性 和 可 移 植 性, 使 C 语 言 逐 渐 成 为 世 界 上 使 用 最 广 泛 的 计 算 机 语 言 为 了 进 一 步 规 范 C 语 言 的 硬 件 无 关 性,1987 年, 美 国 国 家 标 准 协 会 (ANSI) 根 据 C 语 言 问 世 以 来 各 种 版 本 对 C 语 言 的 发 展 和 扩 充, 制 定 了 新 的 标 准, 称 为 ANSI C ANSI C 语 言 比 原 来 的 标 准 C 语 言 有 了 很 大 的 发 展 目 前 流 行 的 C 语 言 编 译 系 统 都 是 以 它 为 基 础 的 C 语 言 的 成 功 并 不 是 偶 然 的, 它 强 大 的 功 能 和 它 的 可 移 植 性 让 它 能 在 各 种 硬 件 平 台 上 游 刃 自 如 总 体 而 言, C 语 言 有 如 下 特 点 (1)C 语 言 是 中 级 语 言 它 把 高 级 语 言 的 基 本 结 构 和 语 句 与 低 级 语 言 的 实 用 性 结 合 起 来 C 语 言 可 以 像 汇 编 语 言 一 样 对 位 字 节 和 地 址 进 行 操 作, 而 这 三 者 是 计 算 机 最 基 本 的 工 作 单 元 (2)C 语 言 是 结 构 化 的 语 言 C 语 言 采 用 代 码 及 数 据 分 隔, 使 程 序 的 各 个 部 分 除 了 必 要 的 信 息 交 流 外 彼 此 独 立 这 种 结 构 化 方 式 可 使 程 序 层 次 清 晰, 便 于 使 用 维 护 以 及 调 试 C 语 言 是 以 函 数 形 式 提 供 给 用 户 的, 这 些 函 数 可 方 便 地 调 用, 并 具 有 多 种 循 环 条 件 语 句 控 制 程 序 流 向, 从 而 使 程 序 完 全 结 构 化 (3)C 语 言 功 能 齐 全 C 语 言 具 有 各 种 各 样 的 数 据 类 型, 并 引 入 了 指 针 的 概 念, 可 使 程 序 效 率 更 高 另 外,C 语 言 也 具 有 强 大 的 图 形 功 能, 支 持 多 种 显 示 器 和 驱 动 器, 而 且 计 算 功 能 逻 辑 判 断 功 能 也 比 较 强 大, 可 以 实 现 决 策 目 的 (4)C 语 言 可 移 植 性 强 C 语 言 适 合 多 种 操 作 系 统, 如 DOS Windows Linux, 也 适 合 多 种 体 系 结 构, 因 此 尤 其 适 合 在 嵌 入 式 领 域 的 开 发 Linux 下 C 语 言 编 程 环 境 概 述 Linux 下 的 C 语 言 程 序 设 计 与 在 其 他 环 境 中 的 C 程 序 设 计 一 样, 主 要 涉 及 编 辑 器 编 译 链 接 器 调 试 器 及 项 目 管 理 工 具 现 在 我 们 先 对 这 4 种 工 具 进 行 简 单 介 绍, 后 面 会 对 其 一 一 进 行 讲 解 (1) 编 辑 器. Linux 下 的 编 辑 器 就 如 Windows 下 的 记 事 本 写 字 板 等 一 样, 完 成 对 所 录 入 文 字 的 编 辑 功 能 Linux 中 最 常 用 的 编 辑 器 有 vi(vim) 和 emacs, 它 们 功 能 强 大 使 用 方 便, 深 受 编 程 爱 好 者 的 喜 爱 在 本 书 中, 着 重 介 绍 vi 和 emacs (2) 编 译 链 接 器 编 译 是 指 源 代 码 转 化 生 成 可 执 行 代 码 的 过 程, 它 所 完 成 的 主 要 工 作 如 图 3.1 所 示 2
53 图 3.1 编 译 过 程 可 见, 编 译 过 程 是 非 常 复 杂 的, 它 包 括 词 法 语 法 和 语 义 的 分 析 中 间 代 码 的 生 成 和 优 化 符 号 表 的 管 理 和 出 错 处 理 等 在 Linux 中, 最 常 用 的 编 译 器 是 gcc 编 译 器 它 是 GNU 推 出 的 功 能 强 大 性 能 优 越 的 多 平 台 编 译 器, 其 执 行 效 率 与 一 般 的 编 译 器 相 比 平 均 效 率 要 高 20%~30% (3) 调 试 器 调 试 器 并 不 是 代 码 执 行 的 必 备 工 具, 而 是 专 为 方 便 程 序 员 调 试 程 序 而 用 的 有 编 程 经 验 的 读 者 都 知 道, 在 编 程 的 过 程 当 中, 往 往 调 试 所 消 耗 的 时 间 远 远 大 于 编 写 代 码 的 时 间 因 此, 有 一 个 功 能 强 大 使 用 方 便 的 调 试 器 是 必 不 可 少 的 gdb 是 绝 大 多 数 Linux 开 发 人 员 所 使 用 的 调 试 器, 它 可 以 方 便 地 设 置 断 点 单 步 跟 踪 等, 足 以 满 足 开 发 人 员 的 需 要 (4) 项 目 管 理 器 Linux 中 的 项 目 管 理 器 make 有 些 类 似 于 Windows 中 Visual c++ 里 的 工 程, 它 是 一 种 控 制 编 译 或 者 重 复 编 译 软 件 的 工 具, 另 外, 它 还 能 自 动 管 理 软 件 编 译 的 内 容 方 式 和 时 机, 使 程 序 员 能 够 把 精 力 集 中 在 代 码 的 编 写 上 而 不 是 在 源 代 码 的 组 织 上 3.2 常 用 编 辑 器 进 入 vi Linux 系 统 提 供 了 一 个 完 整 的 编 辑 器 家 族 系 列, 如 Ed Ex vi 和 emacs 等 按 功 能 它 们 可 以 分 为 两 大 类 : 行 编 辑 器 (Ed Ex) 和 全 屏 幕 编 辑 器 (vi emacs) 行 编 辑 器 每 次 只 能 对 一 行 进 行 操 作, 使 用 起 来 很 不 方 便 而 全 屏 幕 编 辑 器 可 以 对 整 个 屏 幕 进 行 编 辑, 用 户 编 辑 的 文 件 直 接 显 示 在 屏 幕 上, 从 而 克 服 了 行 编 辑 那 种 不 直 观 的 操 作 方 式, 便 于 用 户 学 习 和 使 用, 具 有 强 大 的 功 能 vi 是 Linux 系 统 的 第 一 个 全 屏 幕 交 互 式 编 辑 程 序, 它 从 诞 生 至 今 一 直 得 到 广 大 用 户 的 青 睐, 历 经 数 十 年 仍 然 是 人 们 主 要 使 用 的 文 本 编 辑 工 具, 足 以 见 其 生 命 力 之 强, 而 强 大 的 生 命 力 是 其 强 大 的 功 能 带 来 的 由 于 大 多 数 读 者 在 此 之 前 都 已 经 用 惯 了 Windows 平 台 上 的 编 辑 器, 因 此, 在 刚 刚 接 触 时 总 会 或 多 或 少 不 适 应, 但 只 要 习 惯 之 后, 就 能 感 受 到 它 的 方 便 与 快 捷 3
54 1.vi 的 模 式 vi 有 3 种 模 式, 分 别 为 命 令 行 模 式 插 入 模 式 及 命 令 行 模 式 下 面 具 体 介 绍 各 模 式 的 功 能 (1) 命 令 行 模 式 用 户 在 用 vi 编 辑 文 件 时, 最 初 进 入 的 为 一 般 模 式 在 该 模 式 中 用 户 可 以 通 过 上 下 移 动 光 标 进 行 删 除 字 符 或 整 行 删 除 等 操 作, 也 可 以 进 行 复 制 粘 贴 等 操 作, 但 无 法 编 辑 文 字 (2) 插 入 模 式 只 有 在 该 模 式 下, 用 户 才 能 进 行 文 字 编 辑 输 入, 用 户 按 [ESC] 可 键 回 到 命 令 行 模 式 (3) 底 行 模 式 在 该 模 式 下, 光 标 位 于 屏 幕 的 底 行 用 户 可 以 进 行 文 件 保 存 或 退 出 操 作, 也 可 以 设 置 编 辑 环 境, 如 寻 找 字 符 串 列 出 行 号 等 2.vi 的 基 本 流 程 (1) 进 入 vi, 即 在 命 令 行 下 键 入 vi hello ( 文 件 名 ) 此 时 进 入 的 是 命 令 行 模 式, 光 标 位 于 屏 幕 的 上 方, 如 图 3.2 所 示 图 3.2 进 入 vi 命 令 行 模 式 (2) 在 命 令 行 模 式 下 键 入 i 进 入 插 入 模 式, 如 图 3.3 所 示 可 以 看 出, 在 屏 幕 底 部 显 示 有 插 入 表 示 插 入 模 式 中 的 输 入 状 态, 在 该 模 式 下 可 以 输 入 文 字 信 息 4
55 图 3.3 进 入 vi 插 入 模 式 (3) 最 后, 在 插 入 模 式 中, 按 Esc 键, 则 当 前 模 式 转 入 命 令 行 模 式, 并 在 底 行 行 中 输 入 :wq ( 存 盘 退 出 ) 进 入 底 行 模 式, 如 图 3.4 所 示 这 样, 就 完 成 了 一 个 简 单 的 vi 操 作 流 程 : 命 令 行 模 式 插 入 模 式 底 行 模 式 由 于 vi 在 不 同 的 模 式 下 有 不 同 的 操 作 功 能, 因 此, 读 者 一 定 要 时 刻 注 意 屏 幕 最 下 方 的 提 示, 分 清 所 在 的 模 式 图 3.4 进 入 vi 底 行 模 式 3.vi 的 各 模 式 功 能 键 (1) 命 令 行 模 式 常 见 功 能 键 如 表 3.1 所 示 表 3.1 vi 命 令 行 模 式 功 能 键 功 能 键 功 能 i a 切 换 到 插 入 模 式, 在 目 前 的 光 标 所 在 处 插 入 输 入 的 文 字, 已 存 在 的 文 字 会 向 后 退 切 换 到 插 入 模 式, 并 从 目 前 光 标 所 在 位 置 的 下 一 个 位 置 开 始 输 入 文 字 5
56 o [ctrl]+[b] [ctrl]+[f] [ctrl]+[u] [ctrl]+[d] 切 换 到 插 入 模 式, 且 从 行 首 开 始 插 入 新 的 一 行 屏 幕 往 后 翻 动 一 页 屏 幕 往 前 翻 动 一 页 屏 幕 往 后 翻 动 半 页 屏 幕 往 前 翻 动 半 页 0( 数 字 0) 光 标 移 到 本 行 的 开 头 G ng 光 标 移 动 到 文 件 的 最 后 光 标 移 动 到 第 n 行 $ 移 动 到 光 标 所 在 行 的 行 尾 n<enter> /name?name x X dd ndd yy nyy 光 标 向 下 移 动 n 行 在 光 标 之 后 查 找 一 个 名 为 name 的 字 符 串 在 光 标 之 前 查 找 一 个 名 为 name 的 字 符 串 删 除 光 标 所 在 位 置 的 一 个 字 符 删 除 光 标 所 在 位 置 的 前 一 个 字 符 删 除 光 标 所 在 行 从 光 标 所 在 行 开 始 向 下 删 除 n 行 复 制 光 标 所 在 行 复 制 光 标 所 在 行 开 始 的 向 下 n 行 p 将 缓 冲 区 内 的 字 符 粘 贴 到 光 标 所 在 位 置 ( 与 yy 搭 配 ) u 恢 复 前 一 个 动 作 (2) 插 入 模 式 的 功 能 键 只 有 一 个, 即 按 Esc 键 可 回 到 命 令 行 模 式 (3) 底 行 模 式 常 见 功 能 键 如 表 3.2 所 示 表 3.2 vi 底 行 模 式 功 能 键 功 能 键 功 能 :w 将 编 辑 的 文 件 保 存 到 磁 盘 中 :q 退 出 vi( 系 统 对 做 过 修 改 的 文 件 会 给 出 提 示 ) :q! 强 制 退 出 vi( 对 修 改 过 的 文 件 不 作 保 存 ) :wq 存 盘 后 退 出 :w [filename] 另 存 一 个 名 为 filename 的 文 件 :set nu :set nonu 显 示 行 号, 设 定 之 后, 会 在 每 一 行 的 前 面 显 示 对 应 行 号 取 消 行 号 显 示 vim 是 vi 的 升 级 版, 与 vi 相 比 扩 展 了 很 多 功 能 且 保 持 与 vi 的 90% 相 兼 容, 感 兴 趣 的 读 者 可 以 查 看 相 关 资 料 进 行 学 习 初 探 emacs 正 如 前 面 所 述,vi 是 一 款 功 能 非 常 强 大 的 编 辑 器, 它 能 够 方 便 快 捷 高 效 地 完 成 用 户 的 任 务, 那 么, 在 此 再 次 向 读 者 介 绍 另 一 款 编 辑 器 是 否 多 此 一 举 呢? 答 案 是 否 定 的 因 为 emacs 不 仅 仅 是 一 款 功 能 强 大 的 编 译 器, 而 且 是 一 款 融 合 编 辑 编 译 调 试 于 一 体 的 开 发 环 境 虽 然, 它 没 有 Visual Studio 一 样 绚 丽 的 界 面, 但 是 它 可 以 在 没 有 图 形 显 示 的 终 端 环 境 下 出 色 的 工 作, 相 信 追 求 强 大 功 能 和 工 作 6
57 效 率 的 用 户 不 会 介 意 它 朴 素 的 界 面 的 emacs 的 使 用 和 vi 截 然 不 同 在 emacs 里, 没 有 类 似 于 vi 的 3 种 模 式 emacs 只 有 一 种 模 式, 也 就 是 编 辑 模 式, 而 且 它 的 命 令 全 靠 功 能 键 完 成 因 此, 功 能 键 也 就 相 当 重 要 了 但 emacs 却 还 使 用 一 个 不 同 vi 的 模 式, 它 的 模 式 是 指 各 种 辅 助 环 境 比 如, 当 编 辑 普 通 文 本 时, 使 用 的 是 文 本 模 式 (Text Mode), 而 当 写 程 序 时, 使 用 的 则 是 如 c 模 式 shell 模 式 等 下 面, 首 先 介 绍 一 下 emacs 作 为 编 辑 器 的 使 用 方 法, 以 帮 助 读 者 熟 悉 emacs 的 环 境 emacs 缩 写 注 释 : C+<chr> 表 示 按 住 Ctrl 键 的 同 时 键 入 字 符 <chr> 因 此,C+f 就 表 示 按 住 Ctrl 键 同 时 键 入 f M+<chr> 表 示 当 键 入 字 符 <chr> 时 同 时 按 住 Meta 或 Edit 或 Alt 键 ( 通 常 为 Alt 键 ) 1.emacs 安 装 现 在 较 新 版 本 的 Linux( 如 本 书 中 所 用 的 Red Hat Enterprise 4 AS) 的 安 装 光 盘 中 一 般 都 自 带 有 emacs 的 安 装 包, 用 户 可 以 通 过 安 装 光 盘 进 行 安 装 ( 一 般 在 第 2 张 光 盘 中 ) 2. 启 动 emacs 安 装 完 emacs 之 后, 只 需 在 命 令 行 键 入 emacs [ 文 件 名 ] ( 若 缺 省 文 件 名, 也 可 在 emacs 编 辑 文 件 后 另 存 时 指 定 ), 也 可 从 编 程 emacs 打 开, 如 3.5 图 所 示 的 就 是 从 编 程 emacs 打 开 的 emacs 欢 迎 界 面 图 3.5 emacs 欢 迎 界 面 接 着 可 单 击 任 意 键 进 入 emacs 的 工 作 窗 口, 如 图 3.6 所 示 从 图 中 可 见,emacs 的 工 作 窗 口 分 为 上 下 两 个 部 分, 上 部 为 编 辑 窗 口, 底 部 为 命 令 显 示 窗 口, 用 户 执 行 功 能 键 的 功 能 都 会 在 底 部 有 相 应 的 显 示, 有 时 也 需 要 用 户 在 底 部 窗 口 输 入 相 应 的 命 令, 如 查 找 字 符 串 等 7
58 图 3.6 emacs 的 工 作 窗 口 3. 进 入 emacs 在 进 入 emacs 后, 即 可 进 行 文 件 的 编 辑 由 于 emacs 只 有 一 种 编 辑 模 式, 因 此 用 户 无 需 进 行 模 式 间 的 切 换 下 面 介 绍 emacs 中 基 本 编 辑 功 能 键 (1) 移 动 光 标 虽 然 在 emacs 中 可 以 使 用 上 下 左 右 方 向 键 来 移 动 单 个 字 符, 但 笔 者 还 是 建 议 读 者 学 习 其 对 应 功 能 键, 因 为 它 们 不 仅 能 在 所 有 类 型 的 终 端 上 工 作, 而 且 读 者 将 会 发 现 在 熟 练 使 用 之 后, 输 入 这 些 Ctrl 加 字 符 会 比 按 方 向 键 快 很 多 表 3.3 列 举 了 emacs 中 光 标 移 动 的 常 见 功 能 键 表 3.3 emacs 光 标 移 动 功 能 键 功 能 键 功 能 功 能 键 功 能 C-f 向 前 移 动 一 个 字 符 M-b 向 后 移 动 一 个 单 词 C-b 向 后 移 动 一 个 字 符 C-a 移 动 到 行 首 C-p 移 动 到 上 一 行 C-e 移 动 到 行 尾 C-n 移 动 到 下 一 行 M-<(M 加 小 于 号 ) 移 动 光 标 到 整 个 文 本 的 开 头 M-f 向 前 移 动 一 个 单 词 M->(M 加 大 于 号 ) 移 动 光 标 到 整 个 文 本 的 末 尾 (2) 剪 切 和 粘 贴 在 emacs 中 可 以 使 用 Delete 和 BackSpace 删 除 光 标 前 后 的 字 符, 这 和 用 户 之 前 的 习 惯 一 致, 在 此 就 不 赘 述 以 词 和 行 为 单 位 的 剪 切 和 粘 贴 功 能 键 如 表 3.4 所 示 表 3.4 emacs 剪 切 和 粘 贴 功 能 键 功 能 功 能 键 功 能 M-Delete 剪 切 光 标 前 面 的 单 词 M-k 剪 切 从 光 标 位 置 到 句 尾 的 内 容 M-d 剪 切 光 标 前 面 的 单 词 C-y 将 缓 冲 区 中 的 内 容 粘 贴 到 光 标 所 在 的 位 置 C-k 剪 切 从 光 标 位 置 到 行 尾 的 内 容 C-x u 撤 销 操 作 ( 先 操 作 C+x, 接 着 再 单 击 u) 8
59 在 emacs 中 对 单 个 字 符 的 操 作 是 删 除, 而 对 词 和 句 的 操 作 是 剪 切, 即 保 存 在 缓 冲 区 中, 以 备 后 面 的 粘 贴 所 用 (3) 复 制 文 本 在 emacs 中 的 复 制 文 本 包 括 两 步 : 选 择 复 制 区 域 和 粘 贴 文 本 选 择 复 制 区 域 的 方 法 是 : 首 先 在 复 制 起 始 点 (A) 按 下 C-Space 或 C-@(C-Shift-2) 使 它 成 为 一 个 标 识 点, 再 将 光 标 移 至 复 制 结 束 点 (B), 再 按 下 M-w, 就 可 将 A 与 B 之 间 的 文 本 复 制 到 系 统 的 缓 冲 区 中 再 使 用 功 能 键 C-y 将 其 粘 贴 到 指 定 位 置 (4) 查 找 文 本 查 找 文 本 的 功 能 键 如 表 3.5 所 示 表 3.5 emacs 查 找 文 本 功 能 键 功 能 键 功 能 C-s C-r 查 找 光 标 以 后 的 内 容, 并 在 对 话 框 的 I-search: 后 输 入 要 查 找 的 字 符 串 查 找 光 标 以 前 的 内 容, 并 在 对 话 框 的 I-search backward: 后 输 入 要 查 找 的 字 符 串 (5) 保 存 文 档 在 emacs 中 保 存 文 档 的 功 能 键 为 C+x C+s ( 即 先 操 作 C+x, 接 着 再 操 作 C+s), 这 时, 屏 幕 底 下 的 对 话 框 会 出 现 如 Wrote /root/workplace/editor/why 的 字 样, 如 图 3.7 所 示 图 3.7 emacs 中 保 存 文 档 另 外,emacs 在 编 辑 时 会 为 每 个 文 件 提 供 自 动 保 存 (auto save) 的 机 制, 而 且 自 动 保 存 的 文 件 的 文 件 名 前 后 都 有 一 个 #, 例 如, 编 辑 名 为 hello.c 的 文 件, 其 自 动 保 存 的 文 件 的 文 件 名 就 叫 #hello.c# 当 用 户 正 常 地 保 存 了 文 件 后,emacs 就 会 删 除 这 个 自 动 保 存 的 文 件 这 个 机 制 当 系 统 发 生 异 常 时 非 常 有 用 (6) 退 出 文 档 在 emacs 中 退 出 文 档 的 功 能 键 为 C-x C-c 4.emacs 中 的 模 式 emacs 不 仅 仅 是 个 强 大 的 编 译 器, 它 还 是 一 个 集 编 译 调 试 等 于 一 体 的 工 作 环 境 在 这 里, 读 者 将 会 了 解 到 emacs 作 为 编 译 器 的 最 基 本 的 概 念, 感 兴 趣 的 读 者 可 以 参 考 Learning GNU Emacs,Second Edition 一 书 进 一 步 学 习 emacs 在 emacs 中 并 没 有 像 vi 中 那 样 的 命 令 行 编 辑 模 式, 只 有 一 种 编 辑 模 式 这 里 所 说 的 模 式 是 指 emacs 9
60 里 的 各 种 辅 助 环 境 下 面 着 重 讲 解 C 模 式 当 我 们 启 动 某 一 文 件 时,emacs 会 判 断 文 件 的 类 型, 从 而 自 动 选 择 相 应 的 模 式 当 然, 用 户 也 可 以 手 动 启 动 各 种 模 式, 用 功 能 键 M-x, 然 后 再 输 入 模 式 的 名 称, 如 图 3.8 所 示, 这 样 就 启 动 了 C 模 式 图 3.8 emacs 中 选 择 模 式 在 强 大 的 C 模 式 下, 用 户 拥 有 自 动 缩 进 注 释 预 处 理 扩 展 自 动 状 态 等 强 大 功 能 在 C 模 式 下 编 辑 代 码 时, 可 以 用 Tab 键 自 动 地 将 当 前 行 的 代 码 产 生 适 当 的 缩 进, 使 代 码 结 构 清 晰 美 观, 它 也 可 以 指 定 缩 进 的 规 则 源 代 码 要 有 良 好 的 可 读 性, 必 须 要 有 良 好 的 注 释 在 emacs 中, 用 M- 可 以 产 生 一 条 右 缩 进 的 注 释 C 模 式 下 是 /* comments */ 形 式 的 注 释,C++ 模 式 下 是 // comments 形 式 的 注 释 当 用 户 高 亮 选 定 某 段 文 本, 然 后 操 作 C-c C-c, 就 可 以 注 释 该 段 文 字 emacs 还 可 以 使 用 C 预 处 理 其 运 行 代 码 的 一 部 分, 以 便 让 程 序 员 检 测 宏 条 件 编 译 以 及 include 语 句 的 效 果 5.emacs 编 译 调 试 程 序 emacs 可 以 让 程 序 员 在 emacs 环 境 里 编 译 自 己 的 软 件 此 时, 编 辑 器 把 编 译 器 的 输 出 和 程 序 代 码 连 接 起 来 程 序 员 可 以 像 使 用 Windows 的 其 他 开 发 工 具 一 样, 将 出 错 位 置 和 代 码 定 位 联 系 起 来 emacs 默 认 的 编 辑 命 令 是 对 一 个 make( 在 本 章 3.6 节 中 会 详 细 介 绍 ) 的 调 用 用 户 可 以 打 开 tool 下 的 Compile 进 行 查 看 emacs 可 以 支 持 大 量 的 工 程 项 目, 以 方 便 程 序 员 的 开 发 另 外,emacs 为 gdb 调 试 器 提 供 了 一 个 功 能 齐 全 的 接 口 在 emacs 中 使 用 gdb 的 时 候, 程 序 员 不 仅 能 够 获 得 gdb 的 全 部 标 准 特 性, 还 可 以 获 得 通 过 接 口 增 强 而 产 生 的 其 他 性 能 3.3 gcc 编 译 器 GNU CC( 简 称 为 gcc) 是 GNU 项 目 中 符 合 ANSI C 标 准 的 编 译 系 统, 能 够 编 译 用 C C++ 和 Object C 等 语 言 编 写 的 程 序 gcc 不 仅 功 能 强 大, 而 且 可 以 编 译 如 C C++ Object C Java Fortran Pascal Modula-3 和 Ada 等 多 种 语 言, 而 且 gcc 是 一 个 交 叉 平 台 编 译 器, 它 能 够 在 当 前 CPU 平 台 上 为 多 种 不 同 体 系 结 构 的 硬 件 平 台 开 发 软 件, 因 此 尤 其 适 合 在 嵌 入 式 领 域 的 开 发 编 译 本 章 中 的 示 例, 除 非 特 别 注 明, 否 则 均 采 用 4.x.x 的 gcc 版 本 表 3.6 所 示 为 gcc 支 持 编 译 源 文 件 的 后 缀 及 其 解 释 表 3.6 gcc 所 支 持 后 缀 名 解 释 后 缀 名 所 对 应 的 语 言 后 缀 名 所 对 应 的 语 言 10
61 .c C 原 始 程 序.s/.S 汇 编 语 言 原 始 程 序.C/.cc/.cxx C++ 原 始 程 序.h 预 处 理 文 件 ( 头 文 件 ).m Objective-C 原 始 程 序.o 目 标 文 件.i 已 经 过 预 处 理 的 C 原 始 程 序.a/.so 编 译 后 的 库 文 件.ii 已 经 过 预 处 理 的 C++ 原 始 程 序 专 业 始 于 专 注 卓 识 源 于 远 见 gcc 编 译 流 程 解 析 如 本 章 开 头 提 到 的,gcc 的 编 译 流 程 分 为 了 4 个 步 骤, 分 别 为 : 预 处 理 (Pre-Processing); 编 译 (Compiling); 汇 编 (Assembling); 链 接 (Linking) 下 面 就 具 体 来 查 看 一 下 gcc 是 如 何 完 成 以 上 4 个 步 骤 的 首 先 看 一 下 hello.c 的 源 代 码 : #include <stdio.h> int main() printf("hello! This is our embedded world!\n"); return 0; (1) 预 处 理 阶 段 在 该 阶 段, 对 包 含 的 头 文 件 (#include) 和 宏 定 义 (#define #ifdef 等 ) 进 行 处 理 在 上 述 代 码 的 预 处 理 过 程 中, 编 译 器 将 包 含 的 头 文 件 stdio.h 编 译 进 来, 并 且 用 户 可 以 使 用 gcc 的 选 项 -E 进 行 查 看, 该 选 项 的 作 用 是 让 gcc 在 预 处 理 结 束 后 停 止 编 译 过 程 gcc 指 令 的 一 般 格 式 为 :gcc [ 选 项 ] 要 编 译 的 文 件 [ 选 项 ] [ 目 标 文 件 ] 其 中, 目 标 文 件 可 缺 省,gcc 默 认 生 成 可 执 行 的 文 件, 名 为 : 编 译 文 件.out [root@localhost gcc]# gcc E hello.c o hello.i 在 此 处, 选 项 -o 是 指 目 标 文 件, 由 表 3.6 可 知,.i 文 件 为 已 经 过 预 处 理 的 C 程 序 以 下 列 出 了 hello.i 文 件 的 部 分 内 容 : typedef int (* gconv_trans_fct) (struct gconv_step *, struct gconv_step_data *, void *, const unsigned char *, const unsigned char **, const unsigned char *, unsigned char **, size_t *); # 2 "hello.c" 2 int main() printf("hello! This is our embedded world!\n"); return 0; 由 此 可 见,gcc 确 实 进 行 了 预 处 理, 它 把 stdio.h 的 内 容 插 入 hello.i 文 件 中 11
62 (2) 编 译 阶 段 接 下 来 进 行 的 是 编 译 阶 段, 在 这 个 阶 段 中,gcc 首 先 要 检 查 代 码 的 规 范 性 是 否 有 语 法 错 误 等, 以 确 定 代 码 实 际 要 做 的 工 作, 在 检 查 无 误 后,gcc 把 代 码 翻 译 成 汇 编 语 言 用 户 可 以 使 用 -S 选 项 来 进 行 查 看, 该 选 项 只 进 行 编 译 而 不 进 行 汇 编, 结 果 生 成 汇 编 代 码 gcc]# gcc S hello.i o hello.s 以 下 列 出 了 hello.s 的 内 容, 可 见 gcc 已 经 将 其 转 化 为 汇 编 代 码 了, 感 兴 趣 的 读 者 可 以 分 析 一 下 这 一 个 简 单 的 C 语 言 小 程 序 是 如 何 用 汇 编 代 码 实 现 的.LC0:.file "hello.c".section.rodata.align 4.string.text.globl main.type main: pushl %ebp movl %esp, %ebp subl $8, %esp andl $-16, %esp movl $0, %eax addl $15, %eax addl $15, %eax shrl $4, %eax sall $4, %eax subl %eax, %esp subl $12, %esp pushl $.LC0 call puts addl $16, %esp movl $0, %eax leave ret.size main,.-main "Hello! This is our embedded world!".ident "GCC: (GNU) XYZ19 (Red Hat )".section.note.GNU-stack,"",@progbits (3) 汇 编 阶 段 汇 编 阶 段 是 把 编 译 阶 段 生 成 的.s 文 件 转 成 目 标 文 件, 读 者 在 此 使 用 选 项 -c 就 可 看 到 汇 编 代 码 已 转 化 为.o 的 二 进 制 目 标 代 码 了, 如 下 所 示 : [root@localhost gcc]# gcc c hello.s o hello.o (4) 链 接 阶 段 在 成 功 编 译 之 后, 就 进 入 了 链 接 阶 段 这 里 涉 及 一 个 重 要 的 概 念 : 函 数 库 读 者 可 以 重 新 查 看 这 个 小 程 序, 在 这 个 程 序 中 并 没 有 定 义 printf 的 函 数 实 现, 且 在 预 编 译 中 包 含 进 的 stdio.h 中 也 只 有 该 函 数 的 声 明, 而 没 有 定 义 函 数 的 实 现, 那 么, 是 在 哪 里 实 现 printf 函 数 的 呢? 最 后 的 答 案 是 : 系 统 把 这 些 函 数 的 实 现 都 放 到 名 为 libc.so.6 的 库 文 件 中 去 了, 在 没 有 特 别 指 定 时, gcc 会 到 系 统 默 认 的 搜 索 路 径 /usr/lib 下 进 行 查 找, 也 就 是 链 接 到 libc.so.6 函 数 库 中 去, 这 样 就 能 调 12
63 用 函 数 printf 了, 而 这 也 正 是 链 接 的 作 用 函 数 库 有 静 态 库 和 动 态 库 两 种 静 态 库 是 指 编 译 链 接 时, 将 库 文 件 的 代 码 全 部 加 入 可 执 行 文 件 中, 因 此 生 成 的 文 件 比 较 大, 但 在 运 行 时 也 就 不 再 需 要 库 文 件 了 其 后 缀 名 通 常 为.a 动 态 库 与 之 相 反, 在 编 译 链 接 时 并 没 有 将 库 文 件 的 代 码 加 入 可 执 行 文 件 中, 而 是 在 程 序 执 行 时 加 载 库, 这 样 可 以 节 省 系 统 的 开 销 一 般 动 态 库 的 后 缀 名 为.so, 如 前 面 所 述 的 libc.so.6 就 是 动 态 库 gcc 在 编 译 时 默 认 使 用 动 态 库 完 成 了 链 接 之 后,gcc 就 可 以 生 成 可 执 行 文 件, 如 下 所 示 gcc]# gcc hello.o o hello 运 行 该 可 执 行 文 件, 出 现 的 正 确 结 果 如 下 [root@localhost gcc]#./hello Hello! This is our embedded world! gcc 编 译 选 项 分 析 gcc 有 超 过 100 个 可 用 选 项, 主 要 包 括 总 体 选 项 告 警 和 出 错 选 项 优 化 选 项 和 体 系 结 构 相 关 选 项 以 下 对 每 一 类 中 最 常 用 的 选 项 进 行 讲 解 (1) 常 用 选 项 gcc 的 常 用 选 项 如 表 3.7 所 示, 很 多 在 前 面 的 示 例 中 已 经 有 所 涉 及 表 3.7 gcc 常 用 选 项 列 表 选 项 含 义 -c 只 编 译 不 链 接, 生 成 目 标 文 件.o -S 只 编 译 不 汇 编, 生 成 汇 编 代 码 -E 只 进 行 预 编 译, 不 做 其 他 处 理 -g 在 可 执 行 程 序 中 包 含 标 准 调 试 信 息 -o file 将 file 文 件 指 定 为 输 出 文 件 -v 打 印 出 编 译 器 内 部 编 译 各 过 程 的 命 令 行 信 息 和 编 译 器 的 版 本 -I dir 在 头 文 件 的 搜 索 路 径 列 表 中 添 加 dir 目 录 前 一 小 节 已 经 讲 解 了 -c -E -o -S 选 项 的 使 用 方 法, 在 此 主 要 讲 解 另 外 2 个 非 常 常 用 的 库 依 赖 选 项 -I dir -I dir 正 如 上 表 中 所 述, -I dir 选 项 可 以 在 头 文 件 的 搜 索 路 径 列 表 中 添 加 dir 目 录 由 于 Linux 中 头 文 件 都 默 认 放 到 了 /usr/include/ 目 录 下, 因 此, 当 用 户 希 望 添 加 放 置 在 其 他 位 置 的 头 文 件 时, 就 可 以 通 过 -I dir 选 项 来 指 定, 这 样,gcc 就 会 到 相 应 的 位 置 查 找 对 应 的 目 录 比 如 在 /root/workplace/gcc 下 有 两 个 文 件 : /* hello1.c */ #include<my.h> int main() printf("hello!!\n"); return 0; /* my.h */ #include<stdio.h> 这 样, 就 可 在 gcc 命 令 行 中 加 入 -I 选 项 : 13
64 gcc] gcc hello1.c I /root/workplace/gcc/ -o hello1 这 样,gcc 就 能 够 执 行 出 正 确 结 果 专 业 始 于 专 注 卓 识 源 于 远 见 在 include 语 句 中, <> 表 示 在 标 准 路 径 中 搜 索 头 文 件, 表 示 在 本 目 录 中 搜 索 故 在 上 例 中, 可 把 hello1.c 的 #include<my.h> 改 为 #include my.h, 就 不 需 要 加 上 -I 选 项 了 (2) 库 选 项 gcc 库 选 项 如 表 3.8 所 示 表 3.8 gcc 库 选 项 列 表 选 项 含 义 -static -shared 进 行 静 态 编 译, 即 链 接 静 态 库, 禁 止 使 用 动 态 库 1. 可 以 生 成 动 态 库 文 件 2. 进 行 动 态 编 译, 尽 可 能 地 链 接 动 态 库, 只 有 当 没 有 动 态 库 时 才 会 链 接 同 名 的 静 态 库 ( 默 认 选 项, 即 可 省 略 ) -L dir 在 库 文 件 的 搜 索 路 径 列 表 中 添 加 dir 目 录 -lname -fpic( 或 -fpic) 链 接 称 为 libname.a( 静 态 库 ) 或 者 libname.so( 动 态 库 ) 的 库 文 件 若 两 个 库 都 存 在, 则 根 据 编 译 方 式 (-static 还 是 -shared) 而 进 行 链 接 生 成 使 用 相 对 地 址 的 位 置 无 关 的 目 标 代 码 (Position Independent Code) 然 后 通 常 使 用 gcc 的 -static 选 项 从 该 PIC 目 标 文 件 生 成 动 态 库 文 件 我 们 通 常 需 要 将 一 些 常 用 的 公 共 函 数 编 译 并 集 成 到 二 进 制 文 件 (Linux 的 ELF 格 式 文 件 ), 以 便 其 他 程 序 可 重 复 地 使 用 该 文 件 中 的 函 数, 此 时 将 这 种 文 件 叫 做 函 数 库, 使 用 函 数 库 不 仅 能 够 节 省 很 多 内 存 和 存 储 器 的 空 间 资 源, 而 且 更 重 要 的 是 大 大 降 低 开 发 难 度 和 开 销, 提 高 开 发 效 率 并 增 强 程 序 的 结 构 性 实 际 上, 在 Linux 中 的 每 个 程 序 都 会 链 接 到 一 个 或 者 多 个 库 比 如 使 用 C 函 数 的 程 序 会 链 接 到 C 运 行 时 库,Qt 应 用 程 序 会 链 接 到 Qt 支 持 的 相 关 图 形 库 等 函 数 库 有 静 态 库 和 动 态 库 两 种, 静 态 库 是 一 系 列 的 目 标 文 件 (.o 文 件 ) 的 归 档 文 件 ( 文 件 名 格 式 为 libname.a), 如 果 在 编 译 某 个 程 序 时 链 接 静 态 库, 则 链 接 器 将 会 搜 索 静 态 库, 从 中 提 取 出 它 所 需 要 的 目 标 文 件 并 直 接 复 制 到 该 程 序 的 可 执 行 二 进 制 文 件 (ELF 格 式 文 件 ) 之 中 ; 动 态 库 ( 文 件 名 格 式 为 libname.so[. 主 版 本 号. 次 版 本 号. 发 行 号 ]) 在 程 序 编 译 时 并 不 会 被 链 接 到 目 标 代 码 中, 而 是 在 程 序 运 行 时 才 被 载 入 下 面 举 一 个 简 单 的 例 子, 讲 解 如 何 怎 么 创 建 和 使 用 这 两 种 函 数 库 首 先 创 建 unsgn_pow.c 文 件, 它 包 含 unsgn_pow() 函 数 的 定 义, 具 体 代 码 如 下 所 示 /* unsgn_pow.c: 库 程 序 */ unsigned long long unsgn_pow(unsigned int x, unsigned int y) unsigned long long res = 1; if (y == 0) res = 1; else if (y == 1) res = x; else res = x * unsgn_pow(x, y - 1); 14
65 return res; 然 后 创 建 pow_test.c 文 件, 它 会 调 用 unsgn_pow() 函 数 /* pow_test.c */ #include <stdio.h> #include <stdlib.h> int main(int argc, char *argv[]) unsigned int x, y; unsigned long long res; if ((argc < 3) (sscanf(argv[1], "%u", &x)!= 1) (sscanf(argv[2], "%u", &y))!= 1) printf("usage: pow base exponent\n"); exit(1); res = unsgn_pow(x, y); printf("%u ^ %u = %u\n", x, y, res); exit(0); 我 们 用 unsgn_pow.c 文 件 可 以 制 作 一 个 函 数 库 下 面 分 别 讲 解 怎 么 生 成 静 态 库 和 动 态 库 静 态 库 的 创 建 和 使 用 创 建 静 态 库 比 较 简 单, 使 用 归 档 工 具 ar 将 一 些 目 标 文 件 集 成 在 一 起 [root@localhost lib]# gcc -c unsgn_pow.c [root@localhost lib]# ar rcsv libpow.a unsgn_pow.o a - unsgn_pow.o 下 面 编 译 主 程 序, 它 将 会 链 接 到 刚 生 成 的 静 态 库 libpow.a 具 体 运 行 结 果 如 下 所 示 [root@localhost lib]# gcc -o pow_test pow_test.c -L. lpow [root@localhost lib]#./pow_test ^ 10 = 1024 其 中, 选 项 -L dir 的 功 能 与 -I dir 类 似, 能 够 在 库 文 件 的 搜 索 路 径 列 表 中 添 加 dir 目 录, 而 -lname 选 项 指 示 编 译 时 链 接 到 库 文 件 libname.a 或 者 libname.so 本 实 例 中, 程 序 pow_test.c 需 要 使 用 当 前 目 录 下 的 一 个 静 态 库 libpow.a 动 态 库 的 创 建 和 使 用 首 先 使 用 gcc 的 -fpic 选 项 为 动 态 库 构 造 一 个 目 标 文 件 [root@localhost lib]# gcc -fpic -Wall -c unsgn_pow.c 接 下 来, 使 用 -shared 选 项 和 已 创 建 的 位 置 无 关 目 标 代 码, 生 成 一 个 动 态 库 libpow.so [root@localhost lib]# gcc -shared -o libpow.so unsgn_pow.o 下 面 编 译 主 程 序, 它 将 会 链 接 到 刚 生 成 的 动 态 库 libpow.so [root@localhost lib]# gcc -o pow_test pow_test.c -L. lpow 在 运 行 可 执 行 程 序 之 前, 需 要 注 册 动 态 库 的 路 径 名 其 方 法 有 几 种 : 修 改 /etc/ld.so.conf 文 件, 或 者 修 改 LD_LIBRARY_PATH 环 境 变 量, 或 者 将 库 文 件 直 接 复 制 到 /lib 或 者 /usr/lib 目 录 下 ( 这 两 个 目 录 为 系 统 的 默 认 的 库 路 径 名 ) 15
66 lib]# cp libpow.so /lib lib]#./pow_test ^ 10 = 1024 动 态 库 只 有 当 使 用 它 的 程 序 执 行 时 才 被 链 接 使 用, 而 不 是 将 需 要 的 部 分 直 接 编 译 入 可 执 行 文 件 中, 并 且 一 个 动 态 库 可 以 被 多 个 程 序 使 用 故 可 称 为 共 享 库, 而 静 态 库 将 会 整 合 到 程 序 中, 因 此 在 程 序 执 行 时 不 用 加 载 静 态 库 从 而 可 知, 链 接 到 静 态 库 会 使 用 户 的 程 序 臃 肿, 并 且 难 以 升 级, 但 是 可 能 会 比 较 容 易 部 署 而 链 接 到 动 态 库 会 使 用 户 的 程 序 轻 便, 并 且 易 于 升 级, 但 是 会 难 以 部 署 (3) 告 警 和 出 错 选 项 gcc 的 告 警 和 出 错 选 项 如 表 3.9 所 示 表 3.9 gcc 警 告 和 出 错 选 项 选 项 列 表 选 项 含 义 -ansi -pedantic -pedantic-error 支 持 符 合 ANSI 标 准 的 C 程 序 允 许 发 出 ANSI C 标 准 所 列 的 全 部 警 告 信 息 允 许 发 出 ANSI C 标 准 所 列 的 全 部 错 误 信 息 -w 关 闭 所 有 告 警 -Wall -werror 允 许 发 出 gcc 提 供 的 所 有 有 用 的 报 警 信 息 把 所 有 的 告 警 信 息 转 化 为 错 误 信 息, 并 在 告 警 发 生 时 终 止 编 译 过 程 下 面 结 合 实 例 对 这 几 个 告 警 和 出 错 选 项 进 行 简 单 的 讲 解 有 以 下 程 序 段 : #include<stdio.h> void main() long long tmp = 1; printf("this is a bad code!\n"); return 0; 这 是 一 个 很 糟 糕 的 程 序, 读 者 可 以 考 虑 一 下 有 哪 些 问 题 -ansi 该 选 项 强 制 gcc 生 成 标 准 语 法 所 要 求 的 告 警 信 息, 尽 管 这 还 并 不 能 保 证 所 有 没 有 警 告 的 程 序 都 是 符 合 ANSI C 标 准 的 运 行 结 果 如 下 所 示 : [root@localhost gcc]# gcc ansi warning.c o warning warning.c: 在 函 数 main 中 : warning.c:7 警 告 : 在 无 返 回 值 的 函 数 中, return 带 返 回 值 warning.c:4 警 告 : main 的 返 回 类 型 不 是 int 可 以 看 出, 该 选 项 并 没 有 发 现 long long 这 个 无 效 数 据 类 型 的 错 误 -pedantic 打 印 ANSI C 标 准 所 列 出 的 全 部 警 告 信 息, 同 样 也 保 证 所 有 没 有 警 告 的 程 序 都 是 符 合 ANSI C 标 准 的 其 运 行 结 果 如 下 所 示 : [root@localhost gcc]# gcc pedantic warning.c o warning warning.c: 在 函 数 main 中 : warning.c:5 警 告 :ISO C90 不 支 持 long long warning.c:7 警 告 : 在 无 返 回 值 的 函 数 中, return 带 返 回 值 warning.c:4 警 告 : main 的 返 回 类 型 不 是 int 16
67 可 以 看 出, 使 用 该 选 项 查 出 了 long long 这 个 无 效 数 据 类 型 的 错 误 -Wall 打 印 gcc 能 够 提 供 的 所 有 有 用 的 报 警 信 息 该 选 项 的 运 行 结 果 如 下 所 示 : [root@localhost gcc]# gcc Wall warning.c o warning warning.c:4 警 告 : main 的 返 回 类 型 不 是 int warning.c: 在 函 数 main 中 : warning.c:7 警 告 : 在 无 返 回 值 的 函 数 中, return 带 返 回 值 warning.c:5 警 告 : 未 使 用 的 变 量 tmp 使 用 -Wall 选 项 找 出 了 未 使 用 的 变 量 tmp, 但 它 并 没 有 找 出 无 效 数 据 类 型 的 错 误 另 外,gcc 还 可 以 利 用 选 项 对 单 独 的 常 见 错 误 分 别 指 定 警 告 (4) 优 化 选 项 gcc 可 以 对 代 码 进 行 优 化, 它 通 过 编 译 选 项 -On 来 控 制 优 化 代 码 的 生 成, 其 中 n 是 一 个 代 表 优 化 级 别 的 整 数 对 于 不 同 版 本 的 gcc 来 讲,n 的 取 值 范 围 及 其 对 应 的 优 化 效 果 可 能 并 不 完 全 相 同, 比 较 典 型 的 范 围 是 从 0 变 化 到 2 或 3 不 同 的 优 化 级 别 对 应 不 同 的 优 化 处 理 工 作 如 使 用 优 化 选 项 -O 主 要 进 行 线 程 跳 转 (Thread Jump) 和 延 迟 退 栈 (Deferred Stack Pops) 两 种 优 化 使 用 优 化 选 项 -O2 除 了 完 成 所 有 -O1 级 别 的 优 化 之 外, 同 时 还 要 进 行 一 些 额 外 的 调 整 工 作, 如 处 理 器 指 令 调 度 等 选 项 -O3 则 还 包 括 循 环 展 开 和 其 他 一 些 与 处 理 器 特 性 相 关 的 优 化 工 作 虽 然 优 化 选 项 可 以 加 速 代 码 的 运 行 速 度, 但 对 于 调 试 而 言 将 是 一 个 很 大 的 挑 战 因 为 代 码 在 经 过 优 化 之 后, 原 先 在 源 程 序 中 声 明 和 使 用 的 变 量 很 可 能 不 再 使 用, 控 制 流 也 可 能 会 突 然 跳 转 到 意 外 的 地 方, 循 环 语 句 也 有 可 能 因 为 循 环 展 开 而 变 得 到 处 都 有, 所 有 这 些 对 调 试 来 讲 都 将 是 一 场 噩 梦 所 以 笔 者 建 议 在 调 试 的 时 候 最 好 不 使 用 任 何 优 化 选 项, 只 有 当 程 序 在 最 终 发 行 的 时 候 才 考 虑 对 其 进 行 优 化 (5) 体 系 结 构 相 关 选 项 gcc 的 体 系 结 构 相 关 选 项 如 表 3.10 所 示 表 3.10 gcc 体 系 结 构 相 关 选 项 列 表 选 项 含 义 -mcpu=type -mieee-fp -mno-ieee-fp -msoft-float -mshort -mrtd 针 对 不 同 的 CPU 使 用 相 应 的 CPU 指 令 可 选 择 的 type 有 i386 i486 pentium 及 i686 等 使 用 IEEE 标 准 进 行 浮 点 数 的 比 较 不 使 用 IEEE 标 准 进 行 浮 点 数 的 比 较 输 出 包 含 浮 点 库 调 用 的 目 标 代 码 把 int 类 型 作 为 16 位 处 理, 相 当 于 short int 强 行 将 函 数 参 数 个 数 固 定 的 函 数 用 ret NUM 返 回, 节 省 调 用 函 数 的 一 条 指 令 这 些 体 系 结 构 相 关 选 项 在 嵌 入 式 的 设 计 中 会 有 较 多 的 应 用, 读 者 需 根 据 不 同 体 系 结 构 将 对 应 的 选 项 进 行 组 合 处 理 在 本 书 后 面 涉 及 具 体 实 例 时 将 会 有 针 对 性 的 讲 解 3.4 gdb 调 试 器 调 试 是 所 有 程 序 员 都 会 面 临 的 问 题 如 何 提 高 程 序 员 的 调 试 效 率, 更 好 更 快 地 定 位 程 序 中 的 问 题 从 而 加 快 程 序 开 发 的 进 度, 是 大 家 都 很 关 注 的 问 题 就 如 读 者 熟 知 的 Windows 下 的 一 些 调 试 工 具, 如 Visual Studio 自 带 的 设 置 断 点 单 步 跟 踪 等, 都 受 到 了 广 大 用 户 的 赞 赏 那 么, 在 Linux 下 有 什 么 很 好 的 调 试 工 具 呢? gdb 调 试 器 是 一 款 GNU 开 发 组 织 并 发 布 的 UNIX/Linux 下 的 程 序 调 试 工 具 虽 然, 它 没 有 图 形 化 的 友 好 界 面, 但 是 它 强 大 的 功 能 也 足 以 与 微 软 的 Visual Studio 等 工 具 媲 美 下 面 就 请 跟 随 笔 者 一 步 步 学 习 gdb 调 试 器 17
68 3.4.1 gdb 使 用 流 程 这 里 给 出 了 一 个 短 小 的 程 序, 由 此 带 领 读 者 熟 悉 gdb 的 使 用 流 程 建 议 读 者 能 够 动 手 实 际 操 作 一 下 首 先, 打 开 Linux 下 的 编 辑 器 vi 或 者 emacs, 编 辑 如 下 代 码 ( 由 于 为 了 更 好 地 熟 悉 gdb 的 操 作, 笔 者 在 此 使 用 vi 编 辑, 希 望 读 者 能 够 参 见 3.3 节 中 对 vi 的 介 绍, 并 熟 练 使 用 vi) /*test.c*/ #include <stdio.h> int sum(int m); int main() int i, n = 0; sum(50); for(i = 1; i<= 50; i++) n += i; printf("the sum of 1-50 is %d \n", n ); int sum(int m) int i, n = 0; for (i = 1; i <= m; i++) n += i; printf("the sum of 1-m is %d\n", n); 在 保 存 退 出 后 首 先 使 用 gcc 对 test.c 进 行 编 译, 注 意 一 定 要 加 上 选 项 -g, 这 样 编 译 出 的 可 执 行 代 码 中 才 包 含 调 试 信 息, 否 则 之 后 gdb 无 法 载 入 该 可 执 行 文 件 [root@localhost gdb]# gcc -g test.c -o test 虽 然 这 段 程 序 没 有 错 误, 但 调 试 完 全 正 确 的 程 序 可 以 更 加 了 解 gdb 的 使 用 流 程 接 下 来 就 启 动 gdb 进 行 调 试 注 意,gdb 进 行 调 试 的 是 可 执 行 文 件, 而 不 是 如.c 的 源 代 码, 因 此, 需 要 先 通 过 gcc 编 译 生 成 可 执 行 文 件 才 能 用 gdb 进 行 调 试 [root@localhost gdb]# gdb test GNU gdb Red Hat Linux ( rh) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux-gnu"...using host libthread_db library "/lib/libthread_db.so.1". (gdb) 可 以 看 出, 在 gdb 的 启 动 画 面 中 指 出 了 gdb 的 版 本 号 使 用 的 库 文 件 等 信 息, 接 下 来 就 进 入 了 由 (gdb) 18
69 开 头 的 命 令 行 界 面 了 (1) 查 看 文 件 在 gdb 中 键 入 l (list) 就 可 以 查 看 所 载 入 的 文 件, 如 下 所 示 在 gdb 的 命 令 中 都 可 使 用 缩 略 形 式 的 命 令, 如 l 代 表 list, b 代 表 breakpoint, p 代 表 print 等, 读 者 也 可 使 用 help 命 令 查 看 帮 助 信 息 (gdb) l 1 #include <stdio.h> 2 int sum(int m); 3 int main() 4 5 int i,n = 0; 6 sum(50); 7 for(i = 1; i <= 50; i++) 8 9 n += i; 10 (gdb) l 11 printf("the sum of 1~50 is %d \n", n ); int sum(int m) int i, n = 0; 17 for(i = 1; i <= m; i++) n += i; printf("the sum of 1~m is = %d\n", n); 20 可 以 看 出,gdb 列 出 的 源 代 码 中 明 确 地 给 出 了 对 应 的 行 号, 这 样 就 可 以 大 大 地 方 便 代 码 的 定 位 (2) 设 置 断 点 设 置 断 点 是 调 试 程 序 中 一 个 非 常 重 要 的 手 段, 它 可 以 使 程 序 运 行 到 一 定 位 置 时 暂 停 因 此, 程 序 员 在 该 位 置 处 可 以 方 便 地 查 看 变 量 的 值 堆 栈 情 况 等, 从 而 找 出 代 码 的 症 结 所 在 在 gdb 中 设 置 断 点 非 常 简 单, 只 需 在 b 后 加 入 对 应 的 行 号 即 可 ( 这 是 最 常 用 的 方 式, 另 外 还 有 其 他 方 式 设 置 断 点 ), 如 下 所 示 : (gdb) b 6 Breakpoint 1 at 0x804846d: file test.c, line 6. 要 注 意 的 是, 在 gdb 中 利 用 行 号 设 置 断 点 是 指 代 码 运 行 到 对 应 行 之 前 将 其 停 止, 如 上 例 中, 代 码 运 行 到 第 6 行 之 前 暂 停 ( 并 没 有 运 行 第 6 行 ) (3) 查 看 断 点 情 况 在 设 置 完 断 点 之 后, 用 户 可 以 键 入 info b 来 查 看 设 置 断 点 情 况, 在 gdb 中 可 以 设 置 多 个 断 点 (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x d in main at test.c:6 用 户 在 断 点 键 入 backrace ( 只 输 入 bt 即 可 ) 可 以 查 到 调 用 函 数 ( 堆 栈 ) 的 情 况, 这 个 功 能 在 程 序 调 试 之 中 使 用 非 常 广 泛, 经 常 用 于 排 除 错 误 或 者 监 视 调 用 堆 栈 的 情 况 (gdb) b 19 (gdb) c 19
70 Breakpoin 2, sum(m=50) at test.c:19 19 printf( The sum of 1-m is %d\n, n); (gdb) bt #0 sum(m=50) at test.c:19 /* 停 在 test.c 的 sum() 函 数, 第 19 行 */ #1 0x080483e8 in main() at test.c:6 /* test.c 的 第 6 行 调 用 sum 函 数 */ (4) 运 行 代 码 接 下 来 就 可 运 行 代 码 了,gdb 默 认 从 首 行 开 始 运 行 代 码, 键 入 r (run) 即 可 ( 若 想 从 程 序 中 指 定 行 开 始 运 行, 可 在 r 后 面 加 上 行 号 ) (gdb) r Starting program: /root/workplace/gdb/test Reading symbols from shared object read from target memory...done. Loaded system supplied DSO at 0x5fb000 Breakpoint 1, main () at test.c:6 6 sum(50); 可 以 看 到, 程 序 运 行 到 断 点 处 就 停 止 了 (5) 查 看 变 量 值 在 程 序 停 止 运 行 之 后, 程 序 员 所 要 做 的 工 作 是 查 看 断 点 处 的 相 关 变 量 值 在 gdb 中 键 入 p + 变 量 值 即 可, 如 下 所 示 : (gdb) p n $1 = 0 (gdb) p i $2 = 在 此 处, 为 什 么 变 量 i 的 值 为 如 此 奇 怪 的 一 个 数 字 呢? 原 因 就 在 于 程 序 是 在 断 点 设 置 的 对 应 行 之 前 停 止 的, 那 么 在 此 时, 并 没 有 把 i 的 数 值 赋 为 零, 而 只 是 一 个 随 机 的 数 字 但 变 量 n 是 在 第 4 行 赋 值 的, 故 在 此 时 已 经 为 零 gdb 在 显 示 变 量 值 时 都 会 在 对 应 值 之 前 加 上 $N 标 记, 它 是 当 前 变 量 值 的 引 用 标 记, 所 以 以 后 若 想 再 次 引 用 此 变 量 就 可 以 直 接 写 作 $N, 而 无 需 写 冗 长 的 变 量 名 (6) 单 步 运 行 单 步 运 行 可 以 使 用 命 令 n (next) 或 s (step), 它 们 之 间 的 区 别 在 于 : 若 有 函 数 调 用 的 时 候, s 会 进 入 该 函 数 而 n 不 会 进 入 该 函 数 因 此, s 就 类 似 于 Uisual 等 工 具 中 的 step in, n 类 似 与 Uisual 等 工 具 中 的 step over 它 们 的 使 用 如 下 所 示 : (gdb) n The sum of 1-m is for (i = 1; i <= 50; i++) (gdb) s sum (m=50) at test.c:16 16 int i, n = 0; 可 见, 使 用 n 后, 程 序 显 示 函 数 sum() 的 运 行 结 果 并 向 下 执 行, 而 使 用 s 后 则 进 入 sum() 函 数 之 中 单 步 运 行 (7) 恢 复 程 序 运 行 在 查 看 完 所 需 变 量 及 堆 栈 情 况 后, 就 可 以 使 用 命 令 c (continue) 恢 复 程 序 的 正 常 运 行 了 这 时, 它 会 把 剩 余 还 未 执 行 的 程 序 执 行 完, 并 显 示 剩 余 程 序 中 的 执 行 结 果 以 下 是 之 前 使 用 n 命 令 恢 复 后 的 执 行 结 20
71 果 : (gdb) c Continuing. The sum of 1-50 is :1275 Program exited with code 031. 可 以 看 出, 程 序 在 运 行 完 后 退 出, 之 后 程 序 处 于 停 止 状 态 在 gdb 中, 程 序 的 运 行 状 态 有 运 行 暂 停 和 停 止 3 种, 其 中 暂 停 状 态 为 程 序 遇 到 了 断 点 或 观 察 点 之 类 的, 程 序 暂 时 停 止 运 行, 而 此 时 函 数 的 地 址 函 数 参 数 函 数 内 的 局 部 变 量 都 会 被 压 入 栈 (Stack) 中 故 在 这 种 状 态 下 可 以 查 看 函 数 的 变 量 值 等 各 种 属 性 但 在 函 数 处 于 停 止 状 态 之 后, 栈 就 会 自 动 撤 消, 它 也 就 无 法 查 看 各 种 信 息 了 gdb 基 本 命 令 gdb 的 命 令 可 以 通 过 查 看 help 进 行 查 找, 由 于 gdb 的 命 令 很 多, 因 此 gdb 的 help 将 其 分 成 了 很 多 种 类 (class), 用 户 可 以 通 过 进 一 步 查 看 相 关 class 找 到 相 应 命 令, 如 下 所 示 : (gdb) help List of classes of commands: aliases -- Aliases of other commands breakpoints -- Making program stop at certain points data -- Examining data files -- Specifying and examining files internals -- Maintenance commands Type "help" followed by a class name for a list of commands in that class. Type "help" followed by command name for full documentation. Command name abbreviations are allowed if unambiguous. 上 述 列 出 了 gdb 各 个 分 类 的 命 令, 注 意 底 部 的 加 粗 部 分 说 明 其 为 分 类 命 令 接 下 来 可 以 具 体 查 找 各 分 类 的 命 令, 如 下 所 示 : (gdb) help data Examining data. List of commands: call -- Call a function in the program delete display -- Cancel some expressions to be displayed when program stops delete mem -- Delete memory region disable display -- Disable some expressions to be displayed when program stops Type "help" followed by command name for full documentation. Command name abbreviations are allowed if unambiguous. 若 用 户 想 要 查 找 call 命 令, 就 可 键 入 help call (gdb) help call 21
72 Call a function in the program. The argument is the function name and arguments, in the notation of the current working language. The result is printed and saved in the value history, if it is not void. 当 然, 若 用 户 已 知 命 令 名, 直 接 键 入 help [command] 也 是 可 以 的 gdb 中 的 命 令 主 要 分 为 以 下 几 类 : 工 作 环 境 相 关 命 令 设 置 断 点 与 恢 复 命 令 源 代 码 查 看 命 令 查 看 运 行 数 据 相 关 命 令 及 修 改 运 行 参 数 命 令 以 下 就 分 别 对 这 几 类 命 令 进 行 讲 解 1. 工 作 环 境 相 关 命 令 gdb 中 不 仅 可 以 调 试 所 运 行 的 程 序, 而 且 还 可 以 对 程 序 相 关 的 工 作 环 境 进 行 相 应 的 设 定, 甚 至 还 可 以 使 用 shell 中 的 命 令 进 行 相 关 的 操 作, 其 功 能 极 其 强 大 gdb 常 见 工 作 环 境 相 关 命 令 如 表 3.11 所 示 表 3.11 gdb 工 作 环 境 相 关 命 令 命 令 格 式 含 义 set args 运 行 时 的 参 数 指 定 运 行 时 参 数, 如 set args 2 show args 查 看 设 置 好 的 运 行 参 数 Path dir 设 定 程 序 的 运 行 路 径 show paths 查 看 程 序 的 运 行 路 径 set environment var [=value] 设 置 环 境 变 量 show environment [var] 查 看 环 境 变 量 cd dir 进 入 dir 目 录, 相 当 于 shell 中 的 cd 命 令 Pwd 显 示 当 前 工 作 目 录 shell command 运 行 shell 的 command 命 令 2. 设 置 断 点 与 恢 复 命 令 gdb 中 设 置 断 点 与 恢 复 的 常 见 命 令 如 表 3.12 所 示 表 3.12 gdb 设 置 断 点 与 恢 复 相 关 命 令 命 令 格 式 含 义 Info b break [ 文 件 名 :] 行 号 或 函 数 名 < 条 件 表 达 式 > tbreak [ 文 件 名 :] 行 号 或 函 数 名 < 条 件 表 达 式 > delete [ 断 点 号 ] disable [ 断 点 号 ] enable [ 断 点 号 ] condition [ 断 点 号 ] < 条 件 表 达 式 > ignore [ 断 点 号 ]<num> Step Next Finish C 查 看 所 设 断 点 设 置 断 点 设 置 临 时 断 点, 到 达 后 被 自 动 删 除 删 除 指 定 断 点, 其 断 点 号 为 info b 中 的 第 一 栏 若 缺 省 断 点 号 则 删 除 所 有 断 点 停 止 指 定 断 点, 使 用 info b 仍 能 查 看 此 断 点 同 delete 一 样, 若 缺 省 断 点 号 则 停 止 所 有 断 点 激 活 指 定 断 点, 即 激 活 被 disable 停 止 的 断 点 修 改 对 应 断 点 的 条 件 在 程 序 执 行 中, 忽 略 对 应 断 点 num 次 单 步 恢 复 程 序 运 行, 且 进 入 函 数 调 用 单 步 恢 复 程 序 运 行, 但 不 进 入 函 数 调 用 运 行 程 序, 直 到 当 前 函 数 完 成 返 回 继 续 执 行 函 数, 直 到 函 数 结 束 或 遇 到 新 的 断 点 22
73 设 置 断 点 在 gdb 的 调 试 中 非 常 重 要, 下 面 着 重 讲 解 gdb 中 设 置 断 点 的 方 法 gdb 中 设 置 断 点 有 多 种 方 式 : 其 一 是 按 行 设 置 断 点 ; 另 外 还 可 以 设 置 函 数 断 点 和 条 件 断 点 下 面 具 体 介 绍 后 两 种 设 置 断 点 的 方 法 1 函 数 断 点 gdb 中 按 函 数 设 置 断 点 只 需 把 函 数 名 列 在 命 令 b 之 后, 如 下 所 示 : (gdb) b test.c:sum ( 可 以 简 化 为 b sum) Breakpoint 1 at 0x80484ba: file test.c, line 16. (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x080484ba in sum at test.c:16 要 注 意 的 是, 此 时 的 断 点 实 际 是 在 函 数 的 定 义 处, 也 就 是 在 16 行 处 ( 注 意 第 16 行 还 未 执 行 ) 2 条 件 断 点 gdb 中 设 置 条 件 断 点 的 格 式 为 :b 行 数 或 函 数 名 if 表 达 式 具 体 实 例 如 下 所 示 : (gdb) b 8 if i==10 Breakpoint 1 at 0x804848c: file test.c, line 8. (gdb) info b Num Type Disp Enb Address What 1 breakpoint keep y 0x c in main at test.c:8 stop only if i == 10 (gdb) r Starting program: /home/yul/test The sum of 1-m is 1275 Breakpoint 1, main () at test.c:9 9 n += i; (gdb) p i $1 = 10 可 以 看 到, 该 例 中 在 第 8 行 ( 也 就 是 运 行 完 第 7 行 的 for 循 环 ) 设 置 了 一 个 i==0 的 条 件 断 点, 在 程 序 运 行 之 后 可 以 看 出, 程 序 确 实 在 i 为 10 时 暂 停 运 行 3.gdb 中 源 码 查 看 相 关 命 令 在 gdb 中 可 以 查 看 源 码 以 方 便 其 他 操 作, 它 的 常 见 相 关 命 令 如 表 3.13 所 示 表 3.13 gdb 源 码 查 看 相 关 相 关 命 令 命 令 格 式 含 义 list < 行 号 > < 函 数 名 > file [ 文 件 名 ] forward-search 正 则 表 达 式 reverse-search 正 则 表 达 式 dir DIR show directories info line 查 看 指 定 位 置 代 码 加 载 指 定 文 件 源 代 码 的 前 向 搜 索 源 代 码 的 后 向 搜 索 将 路 径 DIR 添 加 到 源 文 件 搜 索 的 路 径 的 开 头 显 示 源 文 件 的 当 前 搜 索 路 径 显 示 加 载 到 gdb 内 存 中 的 代 码 23
74 4.gdb 中 查 看 运 行 数 据 相 关 命 令 gdb 中 查 看 运 行 数 据 是 指 当 程 序 处 于 运 行 或 暂 停 状 态 时, 可 以 查 看 的 变 量 及 表 达 式 的 信 息, 其 常 见 命 令 如 表 3.14 所 示 表 3.14 gdb 查 看 运 行 数 据 相 关 命 令 命 令 格 式 含 义 print 表 达 式 变 量 x <n/f/u> display 表 达 式 backtrace 查 看 程 序 运 行 时 对 应 表 达 式 和 变 量 的 值 查 看 内 存 变 量 内 容 其 中 n 为 整 数 表 示 显 示 内 存 的 长 度,f 表 示 显 示 的 格 式,u 表 示 从 当 前 地 址 往 后 请 求 显 示 的 字 节 数 设 定 在 单 步 运 行 或 其 他 情 况 中, 自 动 显 示 的 对 应 表 达 式 的 内 容 查 看 当 前 栈 的 情 况, 即 可 以 查 到 哪 些 被 调 用 的 函 数 尚 未 返 回 5.gdb 中 修 改 运 行 参 数 相 关 命 令 gdb 还 可 以 修 改 运 行 时 的 参 数, 并 使 该 变 量 按 照 用 户 当 前 输 入 的 值 继 续 运 行 它 的 设 置 方 法 为 : 在 单 步 执 行 的 过 程 中, 键 入 命 令 set 变 量 = 设 定 值 这 样, 在 此 之 后, 程 序 就 会 按 照 该 设 定 的 值 运 行 了 下 面, 笔 者 结 合 上 一 节 的 代 码 将 n 的 初 始 值 设 为 4, 其 代 码 如 下 所 示 : (gdb) b 7 Breakpoint 5 at 0x804847a: file test.c, line 7. (gdb) r Starting program: /home/yul/test The sum of 1-m is 1275 Breakpoint 5, main () at test.c:7 7 for(i=1; i <= 50; i++) (gdb) set n=4 (gdb) c Continuing. The sum of 1-50 is 1279 Program exited with code 031. 可 以 看 到, 最 后 的 运 行 结 果 确 实 比 之 前 的 值 大 了 4 gdb 使 用 时 的 注 意 点 : 在 gcc 编 译 选 项 中 一 定 要 加 入 -g 只 有 在 代 码 处 于 运 行 或 暂 停 状 态 时 才 能 查 看 变 量 值 设 置 断 点 后 程 序 在 指 定 行 之 前 停 止 3.5 make 工 程 管 理 器 到 此 为 止, 读 者 已 经 了 解 了 如 何 在 Linux 下 使 用 编 辑 器 编 写 代 码, 如 何 使 用 gcc 把 代 码 编 译 成 可 执 行 文 件, 还 学 习 了 如 何 使 用 gdb 来 调 试 程 序, 那 么, 所 有 的 工 作 看 似 已 经 完 成 了, 为 什 么 还 需 要 make 这 个 工 程 管 理 器 呢? 24
75 所 谓 工 程 管 理 器, 顾 名 思 义, 是 用 于 管 理 较 多 的 文 件 读 者 可 以 试 想 一 下, 由 成 百 上 千 个 文 件 构 成 的 项 目, 如 果 其 中 只 有 一 个 或 少 数 几 个 文 件 进 行 了 修 改, 按 照 之 前 所 学 的 gcc 编 译 工 具, 就 不 得 不 把 这 所 有 的 文 件 重 新 编 译 一 遍, 因 为 编 译 器 并 不 知 道 哪 些 文 件 是 最 近 更 新 的, 而 只 知 道 需 要 包 含 这 些 文 件 才 能 把 源 代 码 编 译 成 可 执 行 文 件, 于 是, 程 序 员 就 不 得 不 重 新 输 入 数 目 如 此 庞 大 的 文 件 名 以 完 成 最 后 的 编 译 工 作 编 译 过 程 分 为 编 译 汇 编 链 接 阶 段, 其 中 编 译 阶 段 仅 检 查 语 法 错 误 以 及 函 数 与 变 量 是 否 被 正 确 地 声 明 了, 在 链 接 阶 段 则 主 要 完 成 函 数 链 接 和 全 局 变 量 的 链 接 因 此, 那 些 没 有 改 动 的 源 代 码 根 本 不 需 要 重 新 编 译, 而 只 要 把 它 们 重 新 链 接 进 去 就 可 以 了 所 以, 人 们 就 希 望 有 一 个 工 程 管 理 器 能 够 自 动 识 别 更 新 了 的 文 件 代 码, 而 不 需 要 重 复 输 入 冗 长 的 命 令 行, 这 样,make 工 程 管 理 器 就 应 运 而 生 了 实 际 上,make 工 程 管 理 器 也 就 是 个 自 动 编 译 管 理 器, 这 里 的 自 动 是 指 它 能 够 根 据 文 件 时 间 戳 自 动 发 现 更 新 过 的 文 件 而 减 少 编 译 的 工 作 量, 同 时, 它 通 过 读 入 makefile 文 件 的 内 容 来 执 行 大 量 的 编 译 工 作 用 户 只 需 编 写 一 次 简 单 的 编 译 语 句 就 可 以 了 它 大 大 提 高 了 实 际 项 目 的 工 作 效 率, 而 且 几 乎 所 有 Linux 下 的 项 目 编 程 均 会 涉 及 它, 希 望 读 者 能 够 认 真 学 习 本 节 内 容 makefile 基 本 结 构 makefile 是 make 读 入 的 惟 一 配 置 文 件, 因 此 本 节 的 内 容 实 际 就 是 讲 述 makefile 的 编 写 规 则 在 一 个 makefile 中 通 常 包 含 如 下 内 容 : 需 要 由 make 工 具 创 建 的 目 标 体 (target), 通 常 是 目 标 文 件 或 可 执 行 文 件 ; 要 创 建 的 目 标 体 所 依 赖 的 文 件 (dependency_file); 创 建 每 个 目 标 体 时 需 要 运 行 的 命 令 (command), 这 一 行 必 须 以 制 表 符 (tab 键 ) 开 头 它 的 格 式 为 : target: dependency_files command /* 该 行 必 须 以 tab 键 开 头 */ 例 如, 有 两 个 文 件 分 别 为 hello.c 和 hello.h, 创 建 的 目 标 体 为 hello.o, 执 行 的 命 令 为 gcc 编 译 指 令 :gcc c hello.c, 那 么, 对 应 的 makefile 就 可 以 写 为 : #The simplest example hello.o: hello.c hello.h gcc c hello.c o hello.o 接 着 就 可 以 使 用 make 了 使 用 make 的 格 式 为 :make target, 这 样 make 就 会 自 动 读 入 makefile( 也 可 以 是 首 字 母 大 写 的 Makefile) 并 执 行 对 应 target 的 command 语 句, 并 会 找 到 相 应 的 依 赖 文 件 如 下 所 示 : [root@localhost makefile]# make hello.o gcc c hello.c o hello.o [root@localhost makefile]# ls hello.c hello.h hello.o makefile 可 以 看 到,makefile 执 行 了 hello.o 对 应 的 命 令 语 句, 并 生 成 了 hello.o 目 标 体 在 makefile 中 的 每 一 个 command 前 必 须 有 Tab 符, 否 则 在 运 行 make 命 令 时 会 出 错 makefile 变 量 上 面 示 例 的 makefile 在 实 际 中 是 几 乎 不 存 在 的, 因 为 它 过 于 简 单, 仅 包 含 两 个 文 件 和 一 个 命 令, 在 这 种 情 况 下 完 全 不 必 要 编 写 makefile 而 只 需 在 shell 中 直 接 输 入 即 可, 在 实 际 中 使 用 的 makefile 往 往 是 包 含 很 多 的 25
76 文 件 和 命 令 的, 这 也 是 makefile 产 生 的 原 因 下 面 就 可 给 出 稍 微 复 杂 一 些 的 makefile 进 行 讲 解 david:kang.o yul.o gcc kang.o bar.o -o myprog kang.o : kang.c kang.h head.h gcc Wall O -g c kang.c -o kang.o yul.o : bar.c head.h gcc - Wall O -g c yul.c -o yul.o 在 这 个 makefile 中 有 3 个 目 标 体 (target), 分 别 为 david kang.o 和 yul.o, 其 中 第 一 个 目 标 体 的 依 赖 文 件 就 是 后 两 个 目 标 体 如 果 用 户 使 用 命 令 make david, 则 make 管 理 器 就 是 找 到 david 目 标 体 开 始 执 行 这 时,make 会 自 动 检 查 相 关 文 件 的 时 间 戳 首 先, 在 检 查 kang.o yul.o 和 david 3 个 文 件 的 时 间 戳 之 前, 它 会 向 下 查 找 那 些 把 kang.o 或 yul.o 作 为 目 标 文 件 的 时 间 戳 比 如, kang.o 的 依 赖 文 件 为 kang.c kang.h head.h 如 果 这 些 文 件 中 任 何 一 个 的 时 间 戳 比 kang.o 新, 则 命 令 gcc Wall O -g c kang.c -o kang.o 将 会 执 行, 从 而 更 新 文 件 kang.o 在 更 新 完 kang.o 或 yul.o 之 后,make 会 检 查 最 初 的 kang.o yul.o 和 david 3 个 文 件, 只 要 文 件 kang.o 或 yul.o 中 的 至 少 有 一 个 文 件 的 时 间 戳 比 david 新, 则 第 二 行 命 令 就 会 被 执 行 这 样,make 就 完 成 了 自 动 检 查 时 间 戳 的 工 作, 开 始 执 行 编 译 工 作 这 也 就 是 make 工 作 的 基 本 流 程 接 下 来, 为 了 进 一 步 简 化 编 辑 和 维 护 makefile,make 允 许 在 makefile 中 创 建 和 使 用 变 量 变 量 是 在 makefile 中 定 义 的 名 字, 用 来 代 替 一 个 文 本 字 符 串, 该 文 本 字 符 串 称 为 该 变 量 的 值 在 具 体 要 求 下, 这 些 值 可 以 代 替 目 标 体 依 赖 文 件 命 令 以 及 makefile 文 件 中 其 他 部 分 在 makefile 中 的 变 量 定 义 有 两 种 方 式 : 一 种 是 递 归 展 开 方 式, 另 一 种 是 简 单 方 式 递 归 展 开 方 式 定 义 的 变 量 是 在 引 用 该 变 量 时 进 行 替 换 的, 即 如 果 该 变 量 包 含 了 对 其 他 变 量 的 引 用, 则 在 引 用 该 变 量 时 一 次 性 将 内 嵌 的 变 量 全 部 展 开, 虽 然 这 种 类 型 的 变 量 能 够 很 好 地 完 成 用 户 的 指 令, 但 是 它 也 有 严 重 的 缺 点, 如 不 能 在 变 量 后 追 加 内 容 ( 因 为 语 句 :CFLAGS = $(CFLAGS) -O 在 变 量 扩 展 过 程 中 可 能 导 致 无 穷 循 环 ) 为 了 避 免 上 述 问 题, 简 单 扩 展 型 变 量 的 值 在 定 义 处 展 开, 并 且 只 展 开 一 次, 因 此 它 不 包 含 任 何 对 其 他 变 量 的 引 用, 从 而 消 除 变 量 的 嵌 套 引 用 递 归 展 开 方 式 的 定 义 格 式 为 :VAR=var 简 单 扩 展 方 式 的 定 义 格 式 为 :VAR:=var make 中 的 变 量 使 用 均 使 用 的 格 式 为 :$(VAR) 变 量 名 是 不 包 括 : # = 以 及 结 尾 空 格 的 任 何 字 符 串 同 时, 变 量 名 中 包 含 字 母 数 字 以 及 下 划 线 以 外 的 情 况 应 尽 量 避 免, 因 为 它 们 可 能 在 将 来 被 赋 予 特 别 的 含 义 变 量 名 是 大 小 写 敏 感 的, 例 如 变 量 名 foo FOO 和 Foo 代 表 不 同 的 变 量 推 荐 在 makefile 内 部 使 用 小 写 字 母 作 为 变 量 名, 预 留 大 写 字 母 作 为 控 制 隐 含 规 则 参 数 或 用 户 重 载 命 令 选 项 参 数 的 变 量 名 下 面 给 出 了 上 例 中 用 变 量 替 换 修 改 后 的 makefile, 这 里 用 OBJS 代 替 kang.o 和 yul.o, 用 CC 代 替 gcc, 用 CFLAGS 代 替 -Wall -O g 这 样 在 以 后 修 改 时, 就 可 以 只 修 改 变 量 定 义, 而 不 需 要 修 改 下 面 的 定 义 实 体, 从 而 大 大 简 化 了 makefile 维 护 的 工 作 量 经 变 量 替 换 后 的 makefile 如 下 所 示 : OBJS = kang.o yul.o CC = gcc CFLAGS = -Wall -O -g david : $(OBJS) $(CC) $(OBJS) -o david 26
77 kang.o : kang.c kang.h $(CC) $(CFLAGS) -c kang.c -o kang.o yul.o : yul.c yul.h $(CC) $(CFLAGS) -c yul.c -o yul.o 可 以 看 到, 此 处 变 量 是 以 递 归 展 开 方 式 定 义 的 makefile 中 的 变 量 分 为 用 户 自 定 义 变 量 预 定 义 变 量 自 动 变 量 及 环 境 变 量 如 上 例 中 的 OBJS 就 是 用 户 自 定 义 变 量, 自 定 义 变 量 的 值 由 用 户 自 行 设 定, 而 预 定 义 变 量 和 自 动 变 量 为 通 常 在 makefile 都 会 出 现 的 变 量, 它 们 的 一 部 分 有 默 认 值, 也 就 是 常 见 的 设 定 值, 当 然 用 户 可 以 对 其 进 行 修 改 预 定 义 变 量 包 含 了 常 见 编 译 器 汇 编 器 的 名 称 及 其 编 译 选 项 表 3.15 列 出 了 makefile 中 常 见 预 定 义 变 量 及 其 部 分 默 认 值 表 3.15 makefile 中 常 见 的 预 定 义 变 量 预 定 义 变 量 含 义 AR AS CC 库 文 件 维 护 程 序 的 名 称, 默 认 值 为 ar 汇 编 程 序 的 名 称, 默 认 值 为 as C 编 译 器 的 名 称, 默 认 值 为 cc CPP C 预 编 译 器 的 名 称, 默 认 值 为 $(CC) E CXX FC C++ 编 译 器 的 名 称, 默 认 值 为 g++ Fortran 编 译 器 的 名 称, 默 认 值 为 f77 RM 文 件 删 除 程 序 的 名 称, 默 认 值 为 rm f ARFLAGS ASFLAGS CFLAGS CPPFLAGS CXXFLAGS FFLAGS 库 文 件 维 护 程 序 的 选 项, 无 默 认 值 汇 编 程 序 的 选 项, 无 默 认 值 C 编 译 器 的 选 项, 无 默 认 值 C 预 编 译 的 选 项, 无 默 认 值 C++ 编 译 器 的 选 项, 无 默 认 值 Fortran 编 译 器 的 选 项, 无 默 认 值 可 以 看 出, 上 例 中 的 CC 和 CFLAGS 是 预 定 义 变 量, 其 中 由 于 CC 没 有 采 用 默 认 值, 因 此, 需 要 把 CC=gcc 明 确 列 出 来 由 于 常 见 的 gcc 编 译 语 句 中 通 常 包 含 了 目 标 文 件 和 依 赖 文 件, 而 这 些 文 件 在 makefile 文 件 中 目 标 体 所 在 行 已 经 有 所 体 现, 因 此, 为 了 进 一 步 简 化 makefile 的 编 写, 就 引 入 了 自 动 变 量 自 动 变 量 通 常 可 以 代 表 编 译 语 句 中 出 现 目 标 文 件 和 依 赖 文 件 等, 并 且 具 有 本 地 含 义 ( 即 下 一 语 句 中 出 现 的 相 同 变 量 代 表 的 是 下 一 语 句 的 目 标 文 件 和 依 赖 文 件 ) 表 3.16 列 出 了 makefile 中 常 见 的 自 动 变 量 表 3.16 makefile 中 常 见 的 自 动 变 量 自 动 变 量 含 义 $* 不 包 含 扩 展 名 的 目 标 文 件 名 称 $+ 所 有 的 依 赖 文 件, 以 空 格 分 开, 并 以 出 现 的 先 后 为 序, 可 能 包 含 重 复 的 依 赖 文 件 $< 第 一 个 依 赖 文 件 的 名 称 $? 所 有 时 间 戳 比 目 标 文 件 晚 的 依 赖 文 件, 并 以 空 格 分 开 $@ 目 标 文 件 的 完 整 名 称 $^ 所 有 不 重 复 的 依 赖 文 件, 以 空 格 分 开 $% 如 果 目 标 是 归 档 成 员, 则 该 变 量 表 示 目 标 的 归 档 成 员 名 称 自 动 变 量 的 书 写 比 较 难 记, 但 是 在 熟 练 了 之 后 使 用 会 非 常 方 便, 请 读 者 结 合 下 例 中 的 自 动 变 量 改 写 的 27
78 makefile 进 行 记 忆 OBJS = kang.o yul.o CC = gcc CFLAGS = -Wall -O -g david : $(OBJS) $(CC) $^ -o $@ kang.o : kang.c kang.h $(CC) $(CFLAGS) -c $< -o $@ yul.o : yul.c yul.h $(CC) $(CFLAGS) -c $< -o $@ 另 外, 在 makefile 中 还 可 以 使 用 环 境 变 量 使 用 环 境 变 量 的 方 法 相 对 比 较 简 单,make 在 启 动 时 会 自 动 读 取 系 统 当 前 已 经 定 义 了 的 环 境 变 量, 并 且 会 创 建 与 之 具 有 相 同 名 称 和 数 值 的 变 量 但 是, 如 果 用 户 在 makefile 中 定 义 了 相 同 名 称 的 变 量, 那 么 用 户 自 定 义 变 量 将 会 覆 盖 同 名 的 环 境 变 量 makefile 规 则 makefile 的 规 则 是 make 进 行 处 理 的 依 据, 它 包 括 了 目 标 体 依 赖 文 件 及 其 之 间 的 命 令 语 句 在 上 面 的 例 子 中, 都 显 式 地 指 出 了 makefile 中 的 规 则 关 系, 如 $(CC) $(CFLAGS) -c $< -o $@, 但 为 了 简 化 makefile 的 编 写, make 还 定 义 了 隐 式 规 则 和 模 式 规 则, 下 面 就 分 别 对 其 进 行 讲 解 1. 隐 式 规 则 隐 含 规 则 能 够 告 诉 make 怎 样 使 用 传 统 的 规 则 完 成 任 务, 这 样, 当 用 户 使 用 它 们 时 就 不 必 详 细 指 定 编 译 的 具 体 细 节, 而 只 需 把 目 标 文 件 列 出 即 可 make 会 自 动 搜 索 隐 式 规 则 目 录 来 确 定 如 何 生 成 目 标 文 件 如 上 例 就 可 以 写 成 : OBJS = kang.o yul.o CC = gcc CFLAGS = -Wall -O -g david : $(OBJS) $(CC) $^ -o $@ 为 什 么 可 以 省 略 后 两 句 呢? 因 为 make 的 隐 式 规 则 指 出 : 所 有.o 文 件 都 可 自 动 由.c 文 件 使 用 命 令 $(CC) $(CPPFLAGS) $(CFLAGS) -c file.c o file.o 来 生 成 这 样 kang.o 和 yul.o 就 会 分 别 通 过 调 用 $(CC) $(CFLAGS) -c kang.c -o kang.o 和 $(CC) $(CFLAGS) -c yul.c -o yul.o 来 生 成 在 隐 式 规 则 只 能 查 找 到 相 同 文 件 名 的 不 同 后 缀 名 文 件, 如 kang.o 文 件 必 须 由 kang.c 文 件 生 成 表 3.17 给 出 了 常 见 的 隐 式 规 则 目 录 表 3.17 对 应 语 言 后 缀 名 C 编 译 :.c 变 为.o C++ 编 译 :.cc 或.C 变 为.o Pascal 编 译 :.p 变 为.o Fortran 编 译 :.r 变 为 -o makefile 中 常 见 隐 式 规 则 目 录 隐 式 规 则 $(CC) c $(CPPFLAGS) $(CFLAGS) $(CXX) -c $(CPPFLAGS) $(CXXFLAGS) $(PC) -c $(PFLAGS) $(FC) -c $(FFLAGS) 28
79 2. 模 式 规 则 模 式 规 则 是 用 来 定 义 相 同 处 理 规 则 的 多 个 文 件 的 它 不 同 于 隐 式 规 则, 隐 式 规 则 仅 仅 能 够 用 make 默 认 的 变 量 来 进 行 操 作, 而 模 式 规 则 还 能 引 入 用 户 自 定 义 变 量, 为 多 个 文 件 建 立 相 同 的 规 则, 从 而 简 化 makefile 的 编 写 模 式 规 则 的 格 式 类 似 于 普 通 规 则, 这 个 规 则 中 的 相 关 文 件 前 必 须 用 % 标 明 使 用 模 式 规 则 修 改 后 的 makefile 的 编 写 如 下 : OBJS = kang.o yul.o CC = gcc CFLAGS = -Wall -O -g david : $(OBJS) $(CC) $^ -o $@ %.o : %.c $(CC) $(CFLAGS) -c $< -o $@ make 管 理 器 的 使 用 使 用 make 管 理 器 非 常 简 单, 只 需 在 make 命 令 的 后 面 键 入 目 标 名 即 可 建 立 指 定 的 目 标, 如 果 直 接 运 行 make, 则 建 立 makefile 中 的 第 一 个 目 标 此 外 make 还 有 丰 富 的 命 令 行 选 项, 可 以 完 成 各 种 不 同 的 功 能 表 3.18 列 出 了 常 用 的 make 命 令 行 选 项 表 3.18 make 的 命 令 行 选 项 命 令 格 式 含 义 -C dir 读 入 指 定 目 录 下 的 makefile -f file 读 入 当 前 目 录 下 的 file 文 件 作 为 makefile -I 忽 略 所 有 的 命 令 执 行 错 误 -I dir 指 定 被 包 含 的 makefile 所 在 目 录 -n 只 打 印 要 执 行 的 命 令, 但 不 执 行 这 些 命 令 -p 显 示 make 变 量 数 据 库 和 隐 含 规 则 -s 在 执 行 命 令 时 不 显 示 命 令 -w 如 果 make 在 执 行 过 程 中 改 变 目 录, 则 打 印 当 前 目 录 名 3.6 使 用 autotools 在 上 一 小 节, 读 者 已 经 了 解 到 了 make 项 目 管 理 器 的 强 大 功 能 的 确,makefile 可 以 帮 助 make 完 成 它 的 使 命, 但 要 承 认 的 是, 编 写 makefile 确 实 不 是 一 件 轻 松 的 事, 尤 其 对 于 一 个 较 大 的 项 目 而 言 更 是 如 此 那 么, 有 没 有 一 种 轻 松 的 手 段 生 成 makefile 而 同 时 又 能 让 用 户 享 受 make 的 优 越 性 呢? 本 节 要 讲 的 autotools 系 列 工 具 正 是 为 此 而 设 的, 它 只 需 用 户 输 入 简 单 的 目 标 文 件 依 赖 文 件 文 件 目 录 等 就 可 以 轻 松 地 生 成 makefile 了, 这 无 疑 是 广 大 用 户 所 希 望 的 另 外, 这 些 工 具 还 可 以 完 成 系 统 配 置 信 息 的 收 集, 从 而 可 以 方 便 地 处 理 各 种 移 植 性 的 问 题 也 正 是 基 于 此, 现 在 Linux 上 的 软 件 开 发 一 般 都 用 autotools 来 制 作 makefile, 读 者 在 后 面 的 讲 述 中 就 会 了 解 到 29
80 3.6.1 autotools 使 用 流 程 正 如 前 面 所 言,autotools 是 系 列 工 具, 读 者 首 先 要 确 认 系 统 是 否 装 了 以 下 工 具 ( 可 以 用 which 命 令 进 行 查 看 ) aclocal autoscan autoconf autoheader automake 使 用 autotools 主 要 就 是 利 用 各 个 工 具 的 脚 本 文 件 以 生 成 最 后 的 makefile 其 总 体 流 程 是 这 样 的 使 用 aclocal 生 成 一 个 aclocal.m4 文 件, 该 文 件 主 要 处 理 本 地 的 宏 定 义 ; 改 写 configure.scan 文 件, 并 将 其 重 命 名 为 configure.in, 并 使 用 autoconf 文 件 生 成 configure 文 件 接 下 来, 笔 者 将 通 过 一 个 简 单 的 hello.c 例 子 带 领 读 者 熟 悉 autotools 生 成 makefile 的 过 程, 由 于 在 这 过 程 中 会 涉 及 较 多 的 脚 本 文 件, 为 了 更 清 楚 地 了 解 相 互 之 间 的 关 系, 强 烈 建 议 读 者 实 际 动 手 操 作 以 体 会 其 整 个 过 程 1.autoscan 它 会 在 给 定 目 录 及 其 子 目 录 树 中 检 查 源 文 件, 若 没 有 给 出 目 录, 就 在 当 前 目 录 及 其 子 目 录 树 中 进 行 检 查 它 会 搜 索 源 文 件 以 寻 找 一 般 的 移 植 性 问 题 并 创 建 一 个 文 件 configure.scan, 该 文 件 就 是 接 下 来 autoconf 要 用 到 的 configure.in 原 型 如 下 所 示 : [root@localhost automake]# autoscan autom4te: configure.ac: no such file or directory autoscan: /usr/bin/autom4te failed with exit status: 1 [root@localhost automake]# ls autoscan.log configure.scan hello.c 由 上 述 代 码 可 知 autoscan 首 先 会 尝 试 去 读 入 configure.ac ( 同 configure.in 的 配 置 文 件 ) 文 件, 此 时 还 没 有 创 建 该 配 置 文 件, 于 是 它 会 自 动 生 成 一 个 configure.in 的 原 型 文 件 configure.scan 2.autoconf configure.in 是 autoconf 的 脚 本 配 置 文 件, 它 的 原 型 文 件 configure.scan 如 下 所 示 : # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) #The next one is modified by david #AC_INIT(FULL-PACKAGE-NAME,VERSION,BUG-REPORT-ADDRESS) AC_INIT(hello,1.0) # The next one is added by david AM_INIT_AUTOMAKE(hello,1.0) AC_CONFIG_SRCDIR([hello.c]) AC_CONFIG_HEADER([config.h]) # Checks for programs. 30
81 AC_PROG_CC # Checks for libraries. # Checks for header files. # Checks for typedefs, structures, and compiler characteristics. # Checks for library functions. AC_CONFIG_FILES([makefile]) AC_OUTPUT 下 面 对 这 个 脚 本 文 件 进 行 解 释 以 # 号 开 始 的 行 是 注 释 AC_PREREQ 宏 声 明 本 文 件 要 求 的 autoconf 版 本, 如 本 例 使 用 的 版 本 2.59 AC_INIT 宏 用 来 定 义 软 件 的 名 称 和 版 本 等 信 息, 在 本 例 中 省 略 了 BUG-REPORT-ADDRESS, 一 般 为 作 者 的 AM_INIT_AUTOMAKE 是 笔 者 另 加 的, 它 是 automake 所 必 备 的 宏, 使 automake 自 动 生 成 makefile.in, 也 同 前 面 一 样,PACKAGE 是 所 要 产 生 软 件 套 件 的 名 称,VERSION 是 版 本 编 号 AC_CONFIG_SRCDIR 宏 用 来 检 查 所 指 定 的 源 码 文 件 是 否 存 在, 以 及 确 定 源 码 目 录 的 有 效 性 在 此 处 源 码 文 件 为 当 前 目 录 下 的 hello.c AC_CONFIG_HEADER 宏 用 于 生 成 config.h 文 件, 以 便 autoheader 使 用 AC_CONFIG_FILES 宏 用 于 生 成 相 应 的 makefile 文 件 中 间 的 注 释 之 间 可 以 分 别 添 加 用 户 测 试 程 序 测 试 函 数 库 测 试 头 文 件 等 宏 定 义 接 下 来 首 先 运 行 aclocal, 生 成 一 个 aclocal.m4 文 件, 该 文 件 主 要 处 理 本 地 的 宏 定 义 如 下 所 示 : [root@localhost automake]# aclocal 再 接 着 运 行 autoconf, 生 成 configure 可 执 行 文 件 如 下 所 示 : [root@localhost automake]# autoconf [root@localhost automake]# ls aclocal.m4 autom4te.cache autoscan.log configure configure.in hello.c 3.autoheader 接 着 使 用 autoheader 命 令, 它 负 责 生 成 config.h.in 文 件 该 工 具 通 常 会 从 acconfig.h 文 件 中 复 制 用 户 附 加 的 符 号 定 义, 因 为 这 里 没 有 附 加 符 号 定 义, 所 以 不 需 要 创 建 acconfig.h 文 件 如 下 所 示 : [root@localhost automake]# autoheader 4.automake 这 一 步 是 创 建 makefile 很 重 要 的 一 步,automake 要 用 的 脚 本 配 置 文 件 是 makefile.am, 用 户 需 要 自 己 创 建 相 应 的 文 件 之 后,automake 工 具 转 换 成 makefile.in 在 该 例 中, 笔 者 创 建 的 文 件 为 makefile.am, 如 下 所 示 : AUTOMAKE_OPTIONS=foreign bin_programs= hello hello_sources= hello.c 下 面 对 该 脚 本 文 件 的 对 应 项 进 行 解 释 其 中 的 AUTOMAKE_OPTIONS 为 设 置 automake 的 选 项 GNU 对 自 己 发 布 的 软 件 有 严 格 的 规 范, 比 如 必 须 附 带 许 可 证 声 明 文 件 COPYING 等, 否 则 automake 执 行 时 会 报 错 automake 提 供 了 3 31
82 种 软 件 等 级 :foreign gnu 和 gnits, 让 用 户 选 择 采 用, 默 认 等 级 为 gnu 在 本 示 例 中 采 用 foreign 等 级, 它 只 检 测 必 须 的 文 件 bin_programs 定 义 要 产 生 的 执 行 文 件 名 如 果 要 产 生 多 个 执 行 文 件, 每 个 文 件 名 用 空 格 隔 开 hello_sources 定 义 hello 这 个 执 行 程 序 所 需 要 的 原 始 文 件 如 果 hello 这 个 程 序 是 由 多 个 原 始 文 件 所 产 生 的, 则 必 须 把 它 所 用 到 的 所 有 原 始 文 件 都 列 出 来, 并 用 空 格 隔 开 例 如 : 若 目 标 体 hello 需 要 hello.c david.c hello.h 三 个 依 赖 文 件, 则 定 义 hello_sources=hello.c david.c hello.h 要 注 意 的 是, 如 果 要 定 义 多 个 执 行 文 件, 则 对 每 个 执 行 程 序 都 要 定 义 相 应 的 file_sources 接 下 来 可 以 使 用 automake 命 令 来 生 成 configure.in 文 件, 在 这 里 使 用 选 项 -a ( 或 者 adding-missing ) 可 以 让 automake 自 动 添 加 一 些 必 需 的 脚 本 文 件 如 下 所 示 : [root@localhost automake]# automake a( 或 者 automake --add-missing) configure.in: installing './install-sh' configure.in: installing './missing' makefile.am: installing 'depcomp' [root@localhost automake]# ls aclocal.m4 autoscan.log configure.in hello.c makefile.am missing autom4te.cache configure depcomp install-sh makefile.in config.h.in 可 以 看 到, 在 automake 之 后 就 可 以 生 成 configure.in 文 件 5. 运 行 configure 在 这 一 步 中, 通 过 运 行 自 动 配 置 设 置 文 件 configure, 把 makefile.in 变 成 了 最 终 的 makefile 如 下 所 示 : [root@localhost automake]#./configure checking for a BSD-compatible install... /usr/bin/install -c checking whether build environment is sane... yes checking for gawk... gawk checking whether make sets $(MAKE)... yes checking for gcc... gcc checking for C compiler default output file name... a.out checking whether the C compiler works... yes checking whether we are cross compiling... no checking for suffix of executables... checking for suffix of object files... o checking whether we are using the GNU C compiler... yes checking whether gcc accepts -g... yes checking for gcc option to accept ANSI C... none needed checking for style of include used by make... GNU checking dependency style of gcc... gcc3 configure: creating./config.status config.status: creating makefile config.status: executing depfiles commands 可 以 看 到, 在 运 行 configure 时 收 集 了 系 统 的 信 息, 用 户 可 以 在 configure 命 令 中 对 其 进 行 方 便 的 配 置 在./configure 的 自 定 义 参 数 有 两 种, 一 种 是 开 关 式 (--enable-xxx 或 --disable-xxx), 另 一 种 是 开 放 式, 即 后 面 要 填 入 一 串 字 符 (--with-xxx=yyyy) 参 数 读 者 可 以 自 行 尝 试 其 使 用 方 法 另 外, 读 者 可 以 查 看 同 一 目 录 下 的 config.log 文 件, 以 方 便 调 试 之 用 到 此 为 止,makefile 就 可 以 自 动 生 成 了 回 忆 整 个 步 骤, 用 户 不 再 需 要 定 制 不 同 的 规 则, 而 只 需 要 输 入 简 单 的 文 件 及 目 录 名 即 可, 这 样 就 大 大 方 便 了 用 户 的 使 用 autotools 生 成 makefile 的 流 程 如 图 3.9 所 示 32
83 autoscan aclocal configure.scan autoheader aclocal.m4 configure.in config.h.in makefile.am automake configure makefile.in./configure makefile 图 3.9 autotools 生 成 makefile 的 流 程 图 使 用 autotools 所 生 成 的 makefile autotools 生 成 的 makefile 除 具 有 普 通 的 编 译 功 能 外, 还 具 有 以 下 主 要 功 能 ( 感 兴 趣 的 读 者 可 以 查 看 这 个 简 单 的 hello.c 程 序 的 makefile) 1.make 键 入 make 默 认 执 行 make all 命 令, 即 目 标 体 为 all, 其 执 行 情 况 如 下 所 示 : [root@localhost automake]# make if gcc -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE=\"hello\" -DVERSION=\"1.0\" -I. -I. -g -O2 -MT hello.o -MD -MP -MF ".deps/hello.tpo" -c -o hello.o hello.c; \ then mv -f ".deps/hello.tpo" ".deps/hello.po"; else rm -f ".deps/hello.tpo"; exit 1; fi gcc -g -O2 -o hello hello.o 此 时 在 本 目 录 下 就 生 成 了 可 执 行 文 件 hello, 运 行./hello 能 出 现 正 常 结 果, 如 下 所 示 : [root@localhost automake]#./hello Hello!Autoconf! 2.make install 此 时, 会 把 该 程 序 安 装 到 系 统 目 录 中 去, 如 下 所 示 : [root@localhost automake]# make install if gcc -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE=\"hello\" -DVERSION=\"1.0\" -I. -I. -g -O2 -MT hello.o -MD -MP -MF ".deps/hello.tpo" -c -o hello.o hello.c; \ then mv -f ".deps/hello.tpo" ".deps/hello.po"; else rm -f ".deps/hello.tpo"; exit 1; fi gcc -g -O2 -o hello hello.o make[1]: Entering directory '/root/workplace/automake' test -z "/usr/local/bin" mkdir -p -- "/usr/local/bin" /usr/bin/install -c 'hello' '/usr/local/bin/hello' 33
84 make[1]: Nothing to be done for 'install-data-am'. make[1]: Leaving directory '/root/workplace/automake' 此 时, 若 直 接 运 行 hello, 也 能 出 现 正 确 结 果, 如 下 所 示 : [root@localhost automake]# hello Hello!Autoconf! 3.make clean 此 时,make 会 清 除 之 前 所 编 译 的 可 执 行 文 件 及 目 标 文 件 (object file, *.o), 如 下 所 示 : [root@localhost automake]# make clean test -z "hello" rm -f hello rm -f *.o 4.make dist 此 时,make 将 程 序 和 相 关 的 文 档 打 包 为 一 个 压 缩 文 档 以 供 发 布, 如 下 所 示 : [root@localhost automake]# make dist [root@localhost automake]# ls hello-1.0-tar.gz hello-1.0-tar.gz 可 见 该 命 令 生 成 了 一 个 hello-1.0-tar.gz 压 缩 文 件 由 上 面 的 讲 述 读 者 不 难 看 出,autotools 是 软 件 维 护 与 发 布 的 必 备 工 具, 鉴 于 此, 如 今 GUN 的 软 件 一 般 都 是 由 automake 来 制 作 的 对 于 automake 制 作 的 这 类 软 件, 应 如 何 安 装 呢? 3.7 实 验 内 容 vi 使 用 练 习 1. 实 验 目 的 通 过 指 定 指 令 的 vi 操 作 练 习, 使 读 者 能 够 熟 练 使 用 vi 中 的 常 见 操 作, 并 且 熟 悉 vi 的 3 种 模 式, 如 果 读 者 能 够 熟 练 掌 握 实 验 内 容 中 所 要 求 的 内 容, 则 表 明 对 vi 的 操 作 已 经 很 熟 练 了 2. 实 验 内 容 (1) 在 /root 目 录 下 建 一 个 名 为 vi 的 目 录 (2) 进 入 vi 目 录 (3) 将 文 件 /etc/inittab 复 制 到 vi 目 录 下 (4) 使 用 vi 打 开 vi 目 录 下 的 inittab 34
85 (5) 设 定 行 号, 指 出 设 定 initdefault( 类 似 于 id:5:initdefault ) 的 所 在 行 号 (6) 将 光 标 移 到 该 行 (7) 复 制 该 行 内 容 (8) 将 光 标 移 到 最 后 一 行 行 首 (9) 粘 贴 复 制 行 的 内 容 (10) 撤 消 第 9 步 的 动 作 (11) 将 光 标 移 动 到 最 后 一 行 的 行 尾 (12) 粘 贴 复 制 行 的 内 容 (13) 光 标 移 到 si::sysinit:/etc/rc.d/rc.sysinit (14) 删 除 该 行 (15) 存 盘 但 不 退 出 (16) 将 光 标 移 到 首 行 (17) 插 入 模 式 下 输 入 Hello,this is vi world! (18) 返 回 命 令 行 模 式 (19) 向 下 查 找 字 符 串 0:wait (20) 再 向 上 查 找 字 符 串 halt (21) 强 制 退 出 vi, 不 存 盘 分 别 指 出 每 个 命 令 处 于 何 种 模 式 下? 3. 实 验 步 骤 (1)mkdir /root/vi (2)cd /root/vi (3)cp /etc/inittab./ (4)vi./inittab (5):set nu( 底 行 模 式 ) (6)17<enter>( 命 令 行 模 式 ) (7)yy (8)G (9)p (10)u (11)$ (12)p (13)21G (14)dd (15):w( 底 行 模 式 ) (16)1G (17)i 并 输 入 Hello,this is vi world! ( 插 入 模 式 ) (18)Esc (19)/0:wait( 命 令 行 模 式 ) (20)?halt (21):q!( 底 行 模 式 ) 4. 实 验 结 果 该 实 验 的 最 终 结 果 是 对 /root/inittab 增 加 了 一 行 复 制 的 内 容 : id:5:initdefault 35
86 3.7.2 用 gdb 调 试 程 序 的 bug 1. 实 验 目 的 通 过 调 试 一 个 有 问 题 的 程 序, 使 读 者 进 一 步 熟 练 使 用 vi 操 作, 而 且 熟 练 掌 握 gcc 编 译 命 令 及 gdb 的 调 试 命 令, 通 过 对 有 问 题 程 序 的 跟 踪 调 试, 进 一 步 提 高 发 现 问 题 和 解 决 问 题 的 能 力 这 是 一 个 很 小 的 程 序, 只 有 35 行, 希 望 读 者 认 真 调 试 2. 实 验 内 容 (1) 使 用 vi 编 辑 器, 将 以 下 代 码 输 入 到 名 为 greet.c 的 文 件 中 此 代 码 的 原 意 为 输 出 倒 序 main 函 数 中 定 义 的 字 符 串, 但 结 果 显 示 没 有 输 出 代 码 如 下 所 示 : #include <stdio.h> int display1(char *string); int display2(char *string); int main () char string[] = "Embedded Linux"; display1 (string); display2 (string); int display1 (char *string) printf ("The original string is %s \n", string); int display2 (char *string1) char *string2; int size,i; size = strlen (string1); string2 = (char *) malloc (size + 1); for (i = 0; i < size; i++) string2[size - i] = string1[i]; string2[size+1] = ' '; printf("the string afterward is %s\n",string2); (2) 使 用 gcc 编 译 这 段 代 码, 注 意 要 加 上 -g 选 项 以 方 便 之 后 的 调 试 (3) 运 行 生 成 的 可 执 行 文 件, 观 察 运 行 结 果 (4) 使 用 gdb 调 试 程 序, 通 过 设 置 断 点 单 步 跟 踪, 一 步 步 找 出 错 误 所 在 (5) 纠 正 错 误, 更 改 源 程 序 并 得 到 正 确 的 结 果 36
87 3. 实 验 步 骤 (1) 在 工 作 目 录 上 新 建 文 件 greet.c, 并 用 vi 启 动 :vi greet.c (2) 在 vi 中 输 入 以 上 代 码 (3) 在 vi 中 保 存 并 退 出, 使 用 命 令 :wq (4) 用 gcc 编 译 :gcc -g greet.c -o greet (5) 运 行 greet, 使 用 命 令./greet, 输 出 为 : The original string is Embedded Linux The string afterward is 可 见, 该 程 序 没 有 能 够 倒 序 输 出 (6) 启 动 gdb 调 试 :gdb greet (7) 查 看 源 代 码, 使 用 命 令 l (8) 在 30 行 (for 循 环 处 ) 设 置 断 点, 使 用 命 令 b 30 (9) 在 33 行 (printf 函 数 处 ) 设 置 断 点, 使 用 命 令 b 33 (10) 查 看 断 点 设 置 情 况, 使 用 命 令 info b (11) 运 行 代 码, 使 用 命 令 r (12) 单 步 运 行 代 码, 使 用 命 令 n (13) 查 看 暂 停 点 变 量 值, 使 用 命 令 p string2[size - i] (14) 继 续 单 步 运 行 代 码 数 次, 并 检 查 string2[size-1] 的 值 是 否 正 确 (15) 继 续 程 序 的 运 行, 使 用 命 令 c (16) 程 序 在 printf 前 停 止 运 行, 此 时 依 次 查 看 string2[0] string2[1], 发 现 string[0] 没 有 被 正 确 赋 值, 而 后 面 的 赋 值 都 是 正 确 的, 这 时, 定 位 程 序 第 31 行, 发 现 程 序 运 行 结 果 错 误 的 原 因 在 于 size-1 由 于 i 只 能 增 到 size-1, 这 样 string2[0] 就 永 远 不 能 被 赋 值 而 保 持 NULL, 故 不 能 输 出 任 何 结 果 (17) 退 出 gdb, 使 用 命 令 q (18) 重 新 编 辑 greet.c, 把 其 中 的 string2[size - i] = string1[i] 改 为 string2[size i - 1] = string1[i]; 即 可 (19) 使 用 gcc 重 新 编 译 :gcc -g greet.c -o greet (20) 查 看 运 行 结 果 :./greet The original string is Embedded Linux The string afterward is xunil deddedbme 这 时, 输 出 结 果 正 确 4. 实 验 结 果 将 原 来 有 错 的 程 序 经 过 gdb 调 试, 找 出 问 题 所 在, 并 修 改 源 代 码, 输 出 正 确 的 倒 序 显 示 字 符 串 的 结 果 编 写 包 含 多 文 件 的 makefile 1. 实 验 目 的 通 过 对 包 含 多 文 件 的 makefile 的 编 写, 熟 悉 各 种 形 式 的 makefile, 并 且 进 一 步 加 深 对 makefile 中 用 户 自 定 义 变 量 自 动 变 量 及 预 定 义 变 量 的 理 解 37
88 2. 实 验 过 程 (1) 用 vi 在 同 一 目 录 下 编 辑 两 个 简 单 的 hello 程 序, 如 下 所 示 : #hello.c #include "hello.h" int main() printf("hello everyone!\n"); #hello.h #include <stdio.h> (2) 仍 在 同 一 目 录 下 用 vi 编 辑 makefile, 且 不 使 用 变 量 替 换, 用 一 个 目 标 体 实 现 ( 即 直 接 将 hello.c 和 hello.h 编 译 成 hello 目 标 体 ) 然 后 用 make 验 证 所 编 写 的 makefile 是 否 正 确 (3) 将 上 述 makefile 使 用 变 量 替 换 实 现 同 样 用 make 验 证 所 编 写 的 makefile 是 否 正 确 (4) 编 辑 另 一 个 makefile, 取 名 为 makefile1, 不 使 用 变 量 替 换, 但 用 两 个 目 标 体 实 现 ( 也 就 是 首 先 将 hello.c 和 hello.h 编 译 为 hello.o, 再 将 hello.o 编 译 为 hello), 再 用 make 的 -f 选 项 验 证 这 个 makefile1 的 正 确 性 (5) 将 上 述 makefile1 使 用 变 量 替 换 实 现 3. 实 验 步 骤 (1) 用 vi 打 开 上 述 两 个 代 码 文 件 hello.c 和 hello.h (2) 在 shell 命 令 行 中 用 gcc 尝 试 编 译, 使 用 命 令 : gcc hello.c o hello, 并 运 行 hello 可 执 行 文 件 查 看 结 果 (3) 删 除 此 次 编 译 的 可 执 行 文 件 :rm hello (4) 用 vi 编 辑 makefile, 如 下 所 示 : hello:hello.c hello.h gcc hello.c -o hello (5) 退 出 保 存, 在 shell 中 键 入 :make, 查 看 结 果 (6) 再 次 用 vi 打 开 makefile, 用 变 量 进 行 替 换, 如 下 所 示 : OBJS :=hello.o CC :=gcc hello:$(objs) $(CC) $^ -o $@ (7) 退 出 保 存, 在 shell 中 键 入 make, 查 看 结 果 (8) 用 vi 编 辑 makefile1, 如 下 所 示 : hello:hello.o gcc hello.o -o hello hello.o:hello.c hello.h gcc -c hello.c -o hello.o (9) 退 出 保 存, 在 shell 中 键 入 :make -f makefile1, 查 看 结 果 (10) 再 次 用 vi 编 辑 makefile1, 如 下 所 示 : OBJS1 :=hello.o 38
89 OBJS2 :=hello.c hello.h CC :=gcc hello:$(objs1) $(CC) $^ -o $(OBJS1):$(OBJS2) $(CC) -c $< -o 在 这 里 请 注 意 区 别 $^ 和 $< (11) 退 出 保 存, 在 shell 中 键 入 make -f makefile1, 查 看 结 果 4. 实 验 结 果 各 种 不 同 形 式 的 makefile 都 能 正 确 地 完 成 其 功 能 使 用 autotools 生 成 包 含 多 文 件 的 makefile 1. 实 验 目 的 通 过 使 用 autotools 生 成 包 含 多 文 件 的 makefile, 进 一 步 掌 握 autotools 的 使 用 方 法 同 时, 掌 握 Linux 下 安 装 软 件 的 常 用 方 法 2. 实 验 过 程 (1) 在 原 目 录 下 新 建 文 件 夹 auto (2) 将 上 例 的 两 个 代 码 文 件 hello.c 和 hello.h 复 制 到 该 目 录 下 (3) 使 用 autoscan 生 成 configure.scan (4) 编 辑 configure.scan, 修 改 相 关 内 容, 并 将 其 重 命 名 为 configure.in (5) 使 用 aclocal 生 成 aclocal.m4 (6) 使 用 autoconf 生 成 configure (7) 使 用 autoheader 生 成 config.h.in (8) 编 辑 makefile.am (9) 使 用 automake 生 成 makefile.in (10) 使 用 configure 生 成 makefile (11) 使 用 make 生 成 hello 可 执 行 文 件, 并 在 当 前 目 录 下 运 行 hello 查 看 结 果 (12) 使 用 make install 将 hello 安 装 到 系 统 目 录 下, 并 运 行, 查 看 结 果 (13) 使 用 make dist 生 成 hello 压 缩 包 (14) 解 压 hello 压 缩 包 (15) 进 入 解 压 目 录 (16) 在 该 目 录 下 安 装 hello 软 件 3. 实 验 步 骤 (1)mkdir./auto (2)cp hello.*./auto( 假 定 原 先 在 hello.c 文 件 目 录 下 ) 39
90 (3) 命 令 :autoscan (4) 使 用 vi 编 辑 configure.scan 为 : # -*- Autoconf -*- # Process this file with autoconf to produce a configure script. AC_PREREQ(2.59) AC_INIT(hello, 1.0) AM_INIT_AUTOMAKE(hello,1.0) AC_CONFIG_SRCDIR([hello.h]) AC_CONFIG_HEADER([config.h]) # Checks for programs. AC_PROG_CC # Checks for libraries. # Checks for header files. # Checks for typedefs, structures, and compiler characteristics. # Checks for library functions. AC_OUTPUT(makefile) (5) 保 存 退 出, 并 重 命 名 为 configure.in (6) 运 行 :aclocal (7) 运 行 :autoconf, 并 用 ls 查 看 是 否 生 成 了 configure 可 执 行 文 件 (8) 运 行 :autoheader (9) 用 vi 编 辑 makefile.am 文 件 为 : AUTOMAKE_OPTIONS=foreign bin_programs=hello hello_sources=hello.c hello.h (10) 运 行 :automake, 然 后 运 行 automake a (11) 运 行 :./configure (12) 运 行 :make (13) 运 行 :./hello, 查 看 结 果 是 否 正 确 (14) 运 行 :make install (15) 运 行 :hello, 查 看 结 果 是 否 正 确 (16) 运 行 :make dist (17) 在 当 前 目 录 下 解 压 hello-1.0.tar.gz:tar zxvf hello-1.0.tar.gz (18) 进 入 解 压 目 录 :cd./hello-1.0 (19) 下 面 开 始 Linux 下 常 见 的 安 装 软 件 步 骤 :./configure (20) 运 行 :make (21) 运 行 :./hello( 在 正 常 安 装 时 这 一 步 可 省 略 ) (22) 运 行 :make install (23) 运 行 :hello, 查 看 结 果 是 否 正 确 4. 实 验 结 果 能 够 正 确 使 用 autotools 生 成 makefile, 并 且 能 够 成 功 安 装 短 小 的 hello 软 件 40
91 3.8 本 章 小 结 本 章 是 Linux 中 进 行 C 语 言 编 程 的 基 础, 首 先 讲 解 了 C 语 言 编 程 的 关 键 点, 这 里 关 键 要 了 解 编 辑 器 编 译 链 接 器 调 试 器 及 项 目 管 理 工 具 等 概 念 接 下 来, 本 章 介 绍 了 两 个 Linux 中 常 见 的 编 辑 器 vi 和 emacs, 并 且 主 要 按 照 它 们 的 使 用 流 程 进 行 讲 解 再 接 下 来, 本 章 介 绍 了 gcc 编 译 器 的 使 用 函 数 库 的 创 建 与 使 用 以 及 gdb 调 试 器 的 使 用, 并 结 合 具 体 的 实 例 进 行 讲 解 虽 然 它 们 的 选 项 比 较 多, 但 是 常 用 的 并 不 多, 读 者 着 重 掌 握 笔 者 例 子 中 使 用 的 一 些 选 项 即 可 之 后, 本 章 又 介 绍 了 make 工 程 管 理 器 的 使 用, 这 里 包 括 makefile 的 基 本 结 构 makefile 的 变 量 定 义 及 其 规 则 和 make 的 使 用 最 后 介 绍 的 是 autotools 的 使 用, 这 是 非 常 有 用 的 工 具, 希 望 读 者 能 够 掌 握 本 章 的 实 验 安 排 比 较 多, 包 括 了 vi gdb makefile 和 autotool 的 使 用, 由 于 这 些 都 是 Linux 中 的 常 用 软 件, 因 此 希 望 读 者 切 实 掌 握 3.9 思 考 与 练 习 在 Linux 下 综 合 使 用 vi gcc 编 译 器 和 gdb 调 试 器 开 发 汉 诺 塔 游 戏 程 序 汉 诺 塔 游 戏 介 绍 如 下 约 19 世 纪 末, 在 欧 洲 的 商 店 中 出 售 一 种 智 力 玩 具, 在 一 块 铜 板 上 有 三 根 杆, 如 图 3.10 所 示 其 中, 最 左 边 的 杆 上 自 上 而 下 由 小 到 大 顺 序 串 着 由 64 个 圆 盘 构 成 的 塔 目 的 是 将 最 左 边 杆 上 的 盘 全 部 移 到 右 边 的 杆 上, 条 件 是 一 次 只 能 移 动 一 个 盘, 且 不 允 许 大 盘 放 在 小 盘 的 上 面 C B A 汉 诺 塔 图 3.10 汉 诺 塔 游 戏 示 意 图 联 系 方 式 集 团 官 网 : 嵌 入 式 学 院 : 移 动 互 联 网 学 院 : 企 业 学 院 : 物 联 网 学 院 : 研 发 中 心 :dev.hqyj.com 集 团 总 部 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 内 华 清 远 见 教 育 集 团 北 京 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 区, 电 话 : /5 上 海 地 址 : 上 海 市 徐 汇 区 漕 溪 路 银 海 大 厦 A 座 8 层, 电 话 : 深 圳 地 址 : 深 圳 市 龙 华 新 区 人 民 北 路 美 丽 AAA 大 厦 15 层, 电 话 : 成 都 地 址 : 成 都 市 武 侯 区 科 华 北 路 99 号 科 华 大 厦 6 层, 电 话 : 南 京 地 址 : 南 京 市 白 下 区 汉 中 路 185 号 鸿 运 大 厦 10 层, 电 话 :
92 武 汉 地 址 : 武 汉 市 工 程 大 学 卓 刀 泉 校 区 科 技 孵 化 器 大 楼 8 层, 电 话 : 专 业 始 于 专 注 卓 识 源 于 远 见 西 安 地 址 : 西 安 市 高 新 区 高 新 一 路 12 号 创 业 大 厦 D3 楼 5 层, 电 话 :
93 嵌 入 式 LINUX 应 用 程 序 开 发 标 准 教 程 作 者 : 华 清 远 见 第 4 章 嵌 入 式 系 统 基 础 了 解 嵌 入 式 系 统 的 含 义 及 其 发 展 情 况 了 解 嵌 入 式 系 统 的 体 系 结 构 了 解 ARM 处 理 器 及 ARM9 的 相 关 知 识 熟 悉 三 星 处 理 器 S3C2410 了 解 嵌 入 式 系 统 的 基 本 开 发 和 调 试 手 段
94 4.1 嵌 入 式 系 统 概 述 嵌 入 式 系 统 简 介 尼 葛 洛 庞 帝 2001 年 访 华 时 的 预 言 4~5 年 后, 嵌 入 式 智 能 电 脑 将 是 继 PC 和 Internet 后 的 最 伟 大 发 明! 如 今, 嵌 入 式 系 统 已 成 为 当 今 最 为 热 门 的 领 域 之 一, 它 迅 猛 的 发 展 势 头 引 起 了 社 会 各 界 人 士 的 关 注 如 家 用 电 器 手 持 通 信 设 备 信 息 终 端 仪 器 仪 表 汽 车 航 天 航 空 军 事 装 备 制 造 工 业 过 程 控 制 等 今 天, 嵌 入 式 系 统 带 来 的 工 业 年 产 值 已 超 过 1 万 亿 美 元 用 市 场 观 点 来 看,PC 已 经 从 高 速 增 长 期 进 入 到 平 稳 发 展 期, 其 年 增 长 率 由 20 世 纪 90 年 代 中 期 的 35% 逐 年 下 降, 使 单 纯 由 PC 机 带 领 电 子 产 业 蒸 蒸 日 上 的 时 代 成 为 历 史 根 据 PC 时 代 的 概 念, 美 国 Business Week 杂 志 提 出 了 后 PC 时 代 概 念, 即 计 算 机 通 信 和 消 费 产 品 的 技 术 将 结 合 起 来, 以 3C 产 品 的 形 式 通 过 Internet 进 入 家 庭 这 必 将 培 育 出 一 个 庞 大 的 嵌 入 式 应 用 市 场 那 么 究 竟 什 么 是 嵌 入 式 系 统 呢? 按 照 电 器 工 程 协 会 的 定 义, 嵌 入 式 系 统 是 用 来 控 制 或 者 监 视 机 器 装 置 工 厂 等 各 种 规 模 系 统 的 设 备 这 个 定 义 主 要 是 从 嵌 入 式 系 统 的 用 途 方 面 来 进 行 定 义 的 那 么, 下 面 再 来 看 一 个 在 多 数 书 籍 资 料 中 的 关 于 嵌 入 式 系 统 的 定 义 : 嵌 入 式 系 统 是 指 以 应 用 为 中 心, 以 计 算 机 技 术 为 基 础, 软 件 硬 件 可 剪 裁, 适 应 应 用 系 统 对 功 能 可 靠 性 成 本 体 积 功 耗 严 格 要 求 的 专 用 计 算 机 系 统 笔 者 认 为, 将 一 套 计 算 机 控 制 系 统 嵌 入 到 已 具 有 某 种 完 整 的 特 定 功 能 的 ( 或 者 将 会 具 备 完 整 功 能 的 ) 系 统 内 ( 例 如 : 各 种 机 械 设 备 ), 以 实 现 对 原 有 系 统 的 计 算 机 控 制, 此 时 将 这 个 新 系 统 叫 做 嵌 入 式 系 统 它 通 常 由 特 定 功 能 模 块 和 计 算 机 控 制 模 块 组 成, 主 要 由 嵌 入 式 微 处 理 器 外 围 硬 件 设 备 嵌 入 式 操 作 系 统 以 及 用 户 应 用 软 件 等 部 分 组 成 它 具 有 嵌 入 性 专 用 性 与 计 算 机 系 统 的 3 个 基 本 要 素 从 这 个 定 义 可 以 看 出, 人 们 平 常 所 广 泛 使 用 的 手 机 PDA MP3 机 顶 盒 都 属 于 嵌 入 式 系 统 设 备 ; 而 车 载 GPS 系 统 机 器 人 也 是 属 于 嵌 入 式 系 统 图 4.1 展 出 了 人 们 日 常 生 活 中 形 形 色 色 的 嵌 入 式 产 品 的 确, 嵌 入 式 系 统 已 经 进 入 了 人 们 生 活 的 方 方 面 面 手 机 PDA 车 载 GPS 接 收 机 网 际 频 宽 管 理 器 Internet 智 能 通 讯 服 务 器 指 纹 系 统 MP3 无 线 网 络 摄 像 机 图 4.1 生 活 中 的 嵌 入 式 设 备 嵌 入 式 系 统 发 展 历 史 嵌 入 式 系 统 经 过 30 年 的 发 展 历 程, 主 要 经 历 了 4 个 阶 段 第 1 阶 段 是 以 单 芯 片 为 核 心 的 可 编 程 控 制 器 形 式 的 系 统 这 类 系 统 大 部 分 应 用 于 一 些 专 业 性 强 的 工 业 控 制 系 统 中, 一 般 没 有 操 作 系 统 的 支 持, 通 过 汇 编 语 言 编 程 对 系 统 进 行 直 接 控 制 这 一 阶 段 系 统 的 主 要 特 点 是 : 系 统 结 构 和 功 能 相 对 单 一, 处 理 效 率 较 低, 存 储 容 量 较 小, 几 乎 没 有 用 户 接 口 由 于 这 种 嵌 入 式 系 统 使 用 简 单 价 格 低, 因 此 以 前 在 国 内 工 业 领 域 应 用 较 为 普 遍, 但 是 现 在 已 经 远 不 能 适 应 高 效 的 需 要 大 容 量 存 储 的 现 代 工 业 控 2
95 制 和 新 兴 信 息 家 电 等 领 域 的 需 求 第 2 阶 段 是 以 嵌 入 式 CPU 为 基 础 以 简 单 操 作 系 统 为 核 心 的 嵌 入 式 系 统 其 主 要 特 点 是 :CPU 种 类 繁 多, 通 用 性 比 较 弱 ; 系 统 开 销 小, 效 率 高 ; 操 作 系 统 达 到 一 定 的 兼 容 性 和 扩 展 性 ; 应 用 软 件 较 为 专 业 化, 用 户 界 面 不 够 友 好 第 3 阶 段 是 以 嵌 入 式 操 作 系 统 为 标 志 的 嵌 入 式 系 统 其 主 要 特 点 是 : 嵌 入 式 操 作 系 统 能 运 行 于 各 种 不 同 类 型 的 微 处 理 器 上, 兼 容 性 好 ; 操 作 系 统 内 核 小 效 率 高, 并 且 具 有 高 度 的 模 块 化 和 扩 展 性 ; 具 备 文 件 和 目 录 管 理 支 持 多 任 务 支 持 网 络 应 用 具 备 图 形 窗 口 和 用 户 界 面 ; 具 有 大 量 的 应 用 程 序 接 口 API, 开 发 应 用 程 序 较 简 单 ; 嵌 入 式 应 用 软 件 丰 富 第 4 阶 段 是 以 Internet 多 核 技 术 为 标 志 的 嵌 入 式 系 统 这 是 一 个 正 在 迅 速 发 展 的 阶 段 目 前 不 少 嵌 入 式 系 统 提 供 Internet 服 务, 而 且 多 种 多 核 嵌 入 式 处 理 器 以 及 支 持 多 核 的 软 件 产 品 陆 续 进 入 嵌 入 式 市 场 随 着 新 技 术 新 工 艺 的 发 展 以 及 它 们 与 信 息 家 电 工 业 控 制 技 术 结 合 日 益 紧 密, 嵌 入 式 设 备 的 全 能 化 将 代 表 嵌 入 式 系 统 的 未 来 嵌 入 式 系 统 的 特 点 (1) 面 向 特 定 应 用 的 特 点 从 前 面 图 4.1 中 也 可 以 看 出, 嵌 入 式 系 统 与 通 用 型 系 统 的 最 大 区 别 就 在 于 嵌 入 式 系 统 大 多 工 作 在 为 特 定 用 户 群 设 计 的 系 统 中, 因 此 它 通 常 都 具 有 低 功 耗 体 积 小 集 成 度 高 等 特 点, 并 且 可 以 满 足 不 用 应 用 的 特 定 需 求 (2) 嵌 入 式 系 统 的 硬 件 和 软 件 都 必 须 进 行 高 效 地 设 计, 量 体 裁 衣 去 除 冗 余, 力 争 在 同 样 的 硅 片 面 积 上 实 现 更 高 的 性 能, 这 样 才 能 在 具 体 应 用 中 对 处 理 器 的 选 择 更 具 有 竞 争 力 (3) 嵌 入 式 系 统 是 将 先 进 的 计 算 机 技 术 半 导 体 技 术 和 电 子 技 术 与 各 个 行 业 的 具 体 应 用 相 结 合 后 的 产 物 这 一 点 就 决 定 了 它 必 然 是 一 个 技 术 密 集 资 金 密 集 高 度 分 散 不 断 创 新 的 知 识 集 成 系 统, 从 事 嵌 入 式 系 统 开 发 的 人 才 也 必 须 是 复 合 型 人 才 (4) 为 了 提 高 执 行 速 度 和 系 统 可 靠 性, 嵌 入 式 系 统 中 的 软 件 一 般 都 固 化 在 存 储 器 芯 片 中 或 单 片 机 本 身, 而 不 是 存 储 于 磁 盘 中 (5) 嵌 入 式 开 发 的 软 件 代 码 尤 其 要 求 高 质 量 高 可 靠 性, 由 于 嵌 入 式 设 备 往 往 是 处 在 无 人 职 守 或 条 件 恶 劣 的 情 况 下, 因 此, 其 代 码 必 须 有 更 高 的 要 求 (6) 嵌 入 式 系 统 本 身 不 具 备 二 次 开 发 能 力, 即 设 计 完 成 后 用 户 通 常 不 能 在 该 平 台 上 直 接 对 程 序 功 能 进 行 修 改, 必 须 有 一 套 开 发 工 具 和 环 境 才 能 进 行 再 次 开 发 嵌 入 式 系 统 的 体 系 结 构 嵌 入 式 系 统 作 为 一 类 特 殊 的 计 算 机 系 统, 一 般 包 括 以 下 3 个 方 面 : 硬 件 设 备 嵌 入 式 操 作 系 统 和 应 用 软 件 它 们 之 间 的 关 系 如 图 4.2 所 示 应 用 软 件 嵌 入 式 操 作 系 统 硬 件 设 备 嵌 入 式 处 理 器 外 围 设 备 图 4.2 嵌 入 式 体 系 结 构 图 3
96 硬 件 设 备 包 括 嵌 入 式 处 理 器 和 外 围 设 备 其 中 的 嵌 入 式 处 理 器 (CPU) 是 嵌 入 式 系 统 的 核 心 部 分, 它 与 通 用 处 理 器 最 大 的 区 别 在 于, 嵌 入 式 处 理 器 大 多 工 作 在 为 特 定 用 户 群 所 专 门 设 计 的 系 统 中, 它 将 通 用 处 理 器 中 许 多 由 板 卡 完 成 的 任 务 集 成 到 芯 片 内 部, 从 而 有 利 于 嵌 入 式 系 统 在 设 计 时 趋 于 小 型 化, 同 时 还 具 有 很 高 的 效 率 和 可 靠 性 如 今, 全 世 界 嵌 入 式 处 理 器 已 经 超 过 1000 多 种, 流 行 的 体 系 结 构 有 30 多 个 系 列, 其 中 以 ARM PowerPC MC MIPS 等 使 用 得 最 为 广 泛 外 围 设 备 是 指 嵌 入 式 系 统 中 用 于 完 成 存 储 通 信 调 试 显 示 等 辅 助 功 能 的 其 他 部 件 目 前 常 用 的 嵌 入 式 外 围 设 备 按 功 能 可 以 分 为 存 储 设 备 ( 如 RAM SRAM Flash 等 ) 通 信 设 备 ( 如 RS-232 接 口 SPI 接 口 以 太 网 接 口 USB 接 口 无 线 通 信 等 ) 和 显 示 设 备 ( 如 显 示 屏 等 )3 类 常 见 存 储 器 概 念 辨 析 :RAM SRAM SDRAM ROM EPROM E 2 PROM Flash 存 储 器 可 以 分 为 很 多 种 类, 其 中 根 据 掉 电 后 数 据 是 否 丢 失 可 以 分 为 RAM( 随 机 存 取 存 储 器 ) 和 ROM( 只 读 存 储 器 ), 其 中 RAM 的 访 问 速 度 比 较 快, 但 掉 电 后 数 据 会 丢 失, 而 ROM 掉 电 后 数 据 不 会 丢 失 人 们 通 常 所 说 的 内 存 即 指 系 统 中 的 RAM RAM 又 可 分 为 SRAM( 静 态 存 储 器 ) 和 DRAM( 动 态 存 储 器 ) SRAM 是 利 用 双 稳 态 触 发 器 来 保 存 信 息 的, 只 要 不 掉 电, 信 息 是 不 会 丢 失 的 DRAM 是 利 用 MOS( 金 属 氧 化 物 半 导 体 ) 电 容 存 储 电 荷 来 储 存 信 息, 因 此 必 须 通 过 不 停 地 给 电 容 充 电 来 维 持 信 息, 所 以 DRAM 的 成 本 集 成 度 功 耗 等 明 显 优 于 SRAM 而 通 常 人 们 所 说 的 SDRAM 是 DRAM 的 一 种, 它 是 同 步 动 态 存 储 器, 利 用 一 个 单 一 的 系 统 时 钟 同 步 所 有 的 地 址 数 据 和 控 制 信 号 使 用 SDRAM 不 但 能 提 高 系 统 表 现, 还 能 简 化 设 计 提 供 高 速 的 数 据 传 输 在 嵌 入 式 系 统 中 经 常 使 用 EPROM E 2 PROM 都 是 ROM 的 一 种, 分 别 为 可 擦 除 可 编 程 ROM 和 电 可 擦 除 ROM, 但 使 用 不 是 很 方 便 Flash 也 是 一 种 非 易 失 性 存 储 器 ( 掉 电 不 会 丢 失 ), 它 擦 写 方 便, 访 问 速 度 快, 已 大 大 取 代 了 传 统 的 EPROM 的 地 位 由 于 它 具 有 和 ROM 一 样 掉 电 不 会 丢 失 的 特 性, 因 此 很 多 人 称 其 为 Flash ROM 嵌 入 式 操 作 系 统 从 嵌 入 式 发 展 的 第 3 阶 段 起 开 始 引 入 嵌 入 式 操 作 系 统 不 仅 具 有 通 用 操 作 系 统 的 一 般 功 能, 如 向 上 提 供 对 用 户 的 接 口 ( 如 图 形 界 面 库 函 数 API 等 ), 向 下 提 供 与 硬 件 设 备 交 互 的 接 口 ( 硬 件 驱 动 程 序 等 ), 管 理 复 杂 的 系 统 资 源, 同 时, 它 还 在 系 统 实 时 性 硬 件 依 赖 性 软 件 固 化 性 以 及 应 用 专 用 性 等 方 面, 具 有 更 加 鲜 明 的 特 点 应 用 软 件 是 针 对 特 定 应 用 领 域, 基 于 某 一 固 定 的 硬 件 平 台, 用 来 达 到 用 户 预 期 目 标 的 计 算 机 软 件 由 于 嵌 入 式 系 统 自 身 的 特 点, 决 定 了 嵌 入 式 应 用 软 件 不 仅 要 求 做 到 准 确 性 安 全 性 和 稳 定 性 等 方 面 需 要, 而 且 还 要 尽 可 能 地 进 行 代 码 优 化, 以 减 少 对 系 统 资 源 的 消 耗, 降 低 硬 件 成 本 几 种 主 流 嵌 入 式 操 作 系 统 分 析 1. 嵌 入 式 Linux 嵌 入 式 Linux(Embedded Linux) 是 指 对 标 准 Linux 经 过 小 型 化 裁 剪 处 理 之 后, 能 够 固 化 在 容 量 只 有 几 KB 或 者 几 MB 的 存 储 器 芯 片 或 者 单 片 机 中, 是 适 合 于 特 定 嵌 入 式 应 用 场 合 的 专 用 Linux 操 作 系 统 在 目 前 已 经 开 发 成 功 的 嵌 入 式 系 统 中, 大 约 有 一 半 使 用 的 是 Linux 这 与 它 自 身 的 优 良 特 性 是 分 不 开 的 嵌 入 式 Linux 同 Linux 一 样, 具 有 低 成 本 多 种 硬 件 平 台 支 持 优 异 的 性 能 和 良 好 的 网 络 支 持 等 优 点 另 外, 为 了 更 好 地 适 应 嵌 入 式 领 域 的 开 发, 嵌 入 式 Linux 还 在 Linux 基 础 上 做 了 部 分 改 进, 如 下 所 示 改 善 的 内 核 结 构 Linux 内 核 采 用 的 是 整 体 式 结 构 (Monolithic), 整 个 内 核 是 一 个 单 独 的 非 常 大 的 程 序, 这 样 虽 然 能 够 使 系 统 的 各 个 部 分 直 接 沟 通, 提 高 系 统 响 应 速 度, 但 与 嵌 入 式 系 统 存 储 容 量 小 资 源 有 限 的 特 点 不 相 符 合 4
97 因 此, 在 嵌 入 式 系 统 经 常 采 用 的 是 另 一 种 称 为 微 内 核 (Microkernel) 的 体 系 结 构, 即 内 核 本 身 只 提 供 一 些 最 基 本 的 操 作 系 统 功 能, 如 任 务 调 度 内 存 管 理 中 断 处 理 等, 而 类 似 于 设 备 驱 动 文 件 系 统 和 网 络 协 议 等 附 加 功 能 则 可 以 根 据 实 际 需 要 进 行 取 舍 这 样 就 大 大 减 小 了 内 核 的 体 积, 便 于 维 护 和 移 植 提 高 的 系 统 实 时 性 由 于 现 有 的 Linux 是 一 个 通 用 的 操 作 系 统, 虽 然 它 也 采 用 了 许 多 技 术 来 加 快 系 统 的 运 行 和 响 应 速 度, 但 从 本 质 上 来 说 并 不 是 一 个 嵌 入 式 实 时 操 作 系 统 因 此, 利 用 Linux 作 为 底 层 操 作 系 统, 在 其 上 进 行 实 时 化 改 造, 从 而 构 建 出 一 个 具 有 实 时 处 理 能 力 的 嵌 入 式 系 统, 如 RT-Linux 已 经 成 功 地 应 用 于 航 天 飞 机 的 空 间 数 据 采 集 科 学 仪 器 测 控 和 电 影 特 技 图 像 处 理 等 各 种 领 域 嵌 入 式 Linux 同 Linux 一 样, 也 有 众 多 的 版 本, 其 中 不 同 的 版 本 分 别 针 对 不 同 的 需 要 在 内 核 等 方 面 加 入 了 特 定 的 机 制 嵌 入 式 Linux 的 主 要 版 本 如 表 4.1 所 示 表 4.1 嵌 入 式 Linux 主 要 版 本 版 本 简 单 介 绍 CLinux RT-Linux Embedix XLinux PoketLinux 红 旗 嵌 入 式 Linux Montavista linux 风 河 linux 开 放 源 码 的 嵌 入 式 Linux 的 典 范 之 作 它 主 要 是 针 对 目 标 处 理 器 没 有 存 储 管 理 单 元 MMU, 它 运 行 稳 定, 具 有 良 好 的 移 植 性 和 优 秀 的 网 络 功 能, 对 各 种 文 件 系 统 有 完 备 的 支 持, 并 提 供 丰 富 的 API 由 美 国 墨 西 哥 理 工 学 院 开 发 的 嵌 入 式 Linux 硬 实 时 操 作 系 统 它 已 有 广 泛 的 应 用 根 据 嵌 入 式 应 用 系 统 的 特 点 重 新 设 计 的 Linux 发 行 版 本 它 提 供 了 超 过 25 种 的 Linux 系 统 服 务, 包 括 Web 服 务 器 等 此 外 还 推 出 了 Embedix 的 开 发 调 试 工 具 包 基 于 图 形 界 面 的 浏 览 器 等 可 以 说,Embedix 是 一 种 完 整 的 嵌 入 式 Linux 解 决 方 案 采 用 了 超 字 元 集 专 利 技 术, 使 Linux 内 核 不 仅 能 与 标 准 字 符 集 相 容, 还 涵 盖 了 12 个 国 家 和 地 区 的 字 符 集 因 此,XLinux 在 推 广 Linux 的 国 际 应 用 方 面 有 独 特 的 优 势 它 可 以 提 供 跨 操 作 系 统 并 且 构 造 统 一 的 标 准 化 的 和 开 放 的 信 息 通 信 基 础 结 构, 在 此 结 构 上 实 现 端 到 端 方 案 的 完 整 平 台 由 北 京 中 科 院 红 旗 软 件 公 司 推 出 的 嵌 入 式 Linux, 它 是 国 内 做 得 较 好 的 一 款 嵌 入 式 操 作 系 统 目 前, 中 科 院 计 算 机 研 究 所 自 行 开 发 的 开 放 源 码 的 嵌 入 式 操 作 系 统 Easy Embedded OS(EEOS) 也 已 经 开 始 进 入 实 用 阶 段 了 MontaVista Linux 是 MontaVista Software 于 1999 年 开 始 推 出 的, 专 门 面 向 嵌 入 式 系 统 的 商 业 级 操 作 系 统, 基 于 Linux 内 核 2.6, 采 用 可 抢 占 内 核 技 术, 集 合 了 MontaVista 硬 实 时 技 术, 性 能 远 远 高 于 标 准 2.6 内 核, 具 有 更 短 的 抢 占 延 迟, 反 应 速 度 是 标 准 内 核 的 200 倍 ; 采 用 优 先 级 线 程 实 现 中 断 服 务 程 序 的 调 度 与 Linux 家 族 兼 容 的 产 品 :VxWorks 和 LynxOS 已 经 有 一 些 嵌 入 式 操 作 系 统 产 品, 并 非 从 Linux 裁 剪 或 者 改 造 而 来, 但 是 已 经 基 本 实 现 POSIX 兼 容, 在 接 口 级 与 嵌 入 式 Linux 系 列 产 品 达 成 一 致 这 些 产 品 具 有 优 良 的 传 统 和 特 定 的 实 时 性 可 靠 性 实 现, 在 嵌 入 式 操 作 系 统 中 具 有 重 要 地 位 风 河 公 司 ( 著 名 的 实 时 操 作 系 统 VxWorks 的 厂 商 ) 一 直 致 力 于 嵌 入 式 Linux 方 面 的 研 究 和 开 发 首 个 满 足 由 Linux 基 金 会 (Linux Foundation) 制 定 的 电 信 级 Linux(CGL)4.0 规 范 要 求 的 商 用 化 Linux 厂 商 Wind River Platform for Network Equipment, Linux Edition 2.0 是 首 个 完 全 遵 循 最 新 CGL 规 范 的 网 络 通 信 与 电 信 行 业 Linux 平 台 产 品 为 了 不 失 一 般 性, 本 书 说 所 用 的 嵌 入 式 Linux 是 标 准 内 核 裁 减 的 Linux, 而 不 是 上 表 中 的 任 何 一 种 2.VxWorks VxWorks 操 作 系 统 是 美 国 WindRiver 公 司 于 1983 年 设 计 开 发 的 一 种 嵌 入 式 实 时 操 作 系 统 (RTOS), 它 是 在 当 前 市 场 占 有 率 很 高 的 嵌 入 式 操 作 系 统 之 一 VxWorks 的 实 时 性 做 得 非 常 好, 其 系 统 本 身 的 开 销 很 小, 进 程 调 度 进 程 间 通 信 中 断 处 理 等 系 统 公 用 程 序 精 练 而 有 效, 使 得 它 们 造 成 的 延 迟 很 短 另 外 VxWorks 提 供 的 多 任 务 机 制, 对 任 务 的 控 制 采 用 了 优 先 级 抢 占 (Linux 2.6 内 核 也 采 用 了 优 先 级 抢 占 的 机 制 ) 和 轮 转 调 度 机 制, 这 充 分 保 证 了 可 靠 的 实 时 性, 并 使 同 样 的 硬 件 配 置 能 满 足 更 强 的 实 时 性 要 求 另 外 VxWorks 具 有 高 度 的 可 靠 性, 从 而 保 证 了 用 户 工 作 环 境 的 稳 定 同 时,VxWorks 还 有 完 备 强 大 的 集 成 开 发 环 境, 这 也 大 大 方 便 了 用 户 的 使 用 5
98 但 是, 由 于 VxWorks 的 开 发 和 使 用 都 需 要 交 纳 高 额 的 专 利 费, 因 此 大 大 增 加 了 用 户 的 开 发 成 本 同 时, 由 于 VxWorks 的 源 码 不 公 开, 造 成 它 部 分 功 能 的 更 新 ( 如 网 络 功 能 模 块 ) 滞 后 3.QNX QNX 是 业 界 公 认 的 X86 平 台 上 最 好 的 嵌 入 式 实 时 操 作 系 统 之 一, 它 具 有 独 一 无 二 的 微 内 核 实 时 平 台, 是 建 立 在 微 内 核 和 完 全 地 址 空 间 保 护 基 础 之 上 的, 它 同 样 具 有 实 时 性 强 稳 定 可 靠 的 优 点 4.Windows CE Windows CE 是 微 软 公 司 开 发 的 一 个 开 放 的 可 升 级 的 32 位 嵌 入 式 操 作 系 统, 是 基 于 掌 上 型 电 脑 类 的 电 子 设 备 操 作 系 统 它 是 精 简 的 Windows 95 Windows CE 的 图 形 用 户 界 面 相 当 出 色 Windows CE 具 有 模 块 化 结 构 化 和 基 干 Win32 应 用 程 序 接 口 以 及 与 处 理 器 无 关 等 特 点 它 不 仅 继 承 了 传 统 的 Windows 图 形 界 面, 并 且 用 户 在 Windows CE 平 台 上 可 以 使 用 Windows 95/98 上 的 编 程 工 具 ( 如 Visual Studio 等 ) 也 可 以 使 用 同 样 的 函 数 使 用 同 样 的 界 面 风 格, 使 绝 大 多 数 Windows 上 的 应 用 软 件 只 需 简 单 地 修 改 和 移 植 就 可 以 在 Windows CE 平 台 上 继 续 使 用 但 与 VxWorks 相 同,Windows CE 也 是 比 较 昂 贵 的 5.Palm OS Paml OS 在 PDA 和 掌 上 电 脑 有 着 很 庞 大 的 用 户 群 Palm OS 最 明 显 的 特 点 在 精 简, 它 的 内 核 只 有 几 千 个 字 节, 同 时 用 户 也 可 以 方 便 地 开 发 定 制, 具 有 较 强 的 可 操 作 性 4.2 ARM 处 理 器 硬 件 开 发 平 台 ARM 处 理 器 简 介 ARM 是 一 类 嵌 入 式 微 处 理 器, 同 时 也 是 一 个 公 司 的 名 字 ARM 公 司 于 1990 年 11 月 成 立 于 英 国 剑 桥, 它 是 一 家 专 门 从 事 16/32 位 RISC 微 处 理 器 知 识 产 权 设 计 的 供 应 商 ARM 公 司 本 身 不 直 接 从 事 芯 片 生 产, 而 只 是 授 权 ARM 内 核, 再 给 生 产 和 销 售 半 导 体 的 合 作 伙 伴, 同 时 也 提 供 基 于 ARM 架 构 的 开 发 设 计 技 术 世 界 各 大 半 导 体 生 产 商 从 ARM 公 司 处 购 买 其 设 计 的 ARM 微 处 理 器 核, 根 据 各 自 不 同 的 应 用 领 域, 加 入 适 当 的 外 围 电 路, 从 而 形 成 自 己 的 ARM 微 处 理 器 芯 片 进 入 市 场 ARM 公 司 从 成 立 至 今, 在 短 短 几 十 年 的 时 间 就 占 据 了 75% 的 市 场 份 额, 如 今,ARM 微 处 理 器 及 技 术 的 应 用 几 乎 已 经 深 入 到 各 个 领 域 采 用 ARM 技 术 的 微 处 理 器 现 在 已 经 遍 及 各 类 电 子 产 品, 汽 车 消 费 娱 乐 影 像 工 业 控 制 海 量 存 储 网 络 安 保 和 无 线 等 市 场 到 2001 年 就 几 乎 已 经 垄 断 了 全 球 RISC 芯 片 市 场, 成 为 业 界 实 际 的 RISC 芯 片 标 准 图 4.3 列 举 了 使 用 ARM 微 处 理 器 的 公 司 名 称 6
99 图 4.3 ARM IP 核 用 户 ARM 的 成 功, 一 方 面 得 益 于 它 独 特 的 公 司 运 作 模 式, 另 一 方 面, 当 然 来 自 于 ARM 处 理 器 自 身 的 优 良 性 能 ARM 处 理 器 有 如 下 特 点 体 积 小 低 功 耗 低 成 本 高 性 能 支 持 ARM(32 位 )/ Thumb(16 位 )/ Thumb2(16/32 位 混 合 ) 指 令 集, 能 很 好 地 兼 容 8 位 /16 位 器 件 大 量 使 用 寄 存 器, 指 令 执 行 速 度 更 快 大 多 数 数 据 操 作 都 在 寄 存 器 中 完 成 寻 址 方 式 灵 活 简 单, 执 行 效 率 高 指 令 长 度 固 定 常 见 的 CPU 指 令 集 分 为 CISC 和 RISC 两 种 CISC(Complex Instruction Set Computer) 是 复 杂 指 令 集 自 PC 机 诞 生 以 来, 32 位 以 前 的 处 理 器 都 采 用 CISC 指 令 集 方 式 由 于 这 种 指 令 系 统 的 指 令 不 等 长, 因 此 指 令 的 数 目 非 常 多, 编 程 和 设 计 处 理 器 时 都 较 为 麻 烦 但 由 于 基 于 CISC 指 令 架 构 系 统 设 计 的 软 件 已 经 非 常 普 遍 了, 所 以 包 括 Intel AMD 等 众 多 厂 商 至 今 使 用 的 仍 为 CISC RISC(Reduced Instruction Set Computing) 是 精 简 指 令 集 研 究 人 员 在 对 CISC 指 令 集 进 行 测 试 时 发 现, 各 种 指 令 的 使 用 频 度 相 当 悬 殊, 其 中 最 常 使 用 的 是 一 些 比 较 简 单 的 指 令, 它 们 仅 占 指 令 总 数 的 20%, 但 在 程 序 中 出 现 的 频 度 却 占 80% RISC 正 是 基 于 这 种 思 想 提 出 的 采 用 RISC 指 令 集 的 微 处 理 器 处 理 能 力 强, 并 且 还 通 过 采 用 超 标 量 和 超 流 水 线 结 构, 大 大 增 强 并 行 处 理 能 力 ARM 体 系 结 构 简 介 1.ARM 微 处 理 器 工 作 状 态 ARM 微 处 理 器 的 工 作 状 态 一 般 有 三 种, 并 可 来 回 切 换 第 一 种 为 ARM 状 态, 此 时 处 理 器 执 行 32 位 的 字 对 齐 的 ARM 指 令 第 二 种 为 Thumb 状 态, 此 时 处 理 器 执 行 16 位 的 半 字 对 齐 的 Thumb 指 令 第 三 种 为 Thumb2 状 态, 此 时 处 理 执 行 16/32 位 混 合 的 多 类 型 对 齐 的 指 令 2.ARM 体 系 结 构 的 存 储 格 式 大 端 格 式 : 在 这 种 格 式 中, 字 数 据 的 高 字 节 存 储 在 低 地 址 中, 而 字 数 据 的 低 字 节 则 存 放 在 高 地 址 中 小 端 格 式 : 与 大 端 存 储 格 式 相 反, 在 小 端 存 储 格 式 中, 低 地 址 中 存 放 的 是 字 数 据 的 低 字 节, 高 地 7
100 址 存 放 的 是 字 数 据 的 高 字 节 3.ARM 处 理 器 模 式 ARM 微 处 理 器 支 持 7 种 运 行 模 式, 分 别 如 下 用 户 模 式 (usr): 应 用 程 序 执 行 状 态 快 速 中 断 模 式 (fiq): 用 于 高 速 数 据 传 输 或 通 道 处 理 等 快 速 中 断 处 理 外 部 中 断 模 式 (irq): 用 于 通 用 的 中 断 处 理 管 理 模 式 (svc): 特 权 模 式, 操 作 系 统 使 用 的 保 护 模 式 数 据 访 问 终 止 模 式 (abt): 当 数 据 或 指 令 预 取 终 止 时 进 入 该 模 式, 可 用 于 虚 拟 存 储 及 存 储 保 护 系 统 模 式 (sys): 运 行 具 有 特 权 的 操 作 系 统 任 务 ARM9 体 系 结 构 1.ARM 微 处 理 器 系 列 简 介 ARM 微 处 理 器 系 列 主 要 特 点 如 表 4.2 所 示 表 4.2 ARM 核 ARM7TDMI ARM 微 处 理 器 系 列 主 要 特 点 使 用 v4t 体 系 结 构 最 普 通 的 低 端 ARM 核 3 级 流 水 线 冯 诺 依 曼 体 系 结 构 CPI 约 为 1.9 T 表 示 支 持 Thumb 指 令 集 (ARM 指 令 是 32 位 的 ;Thumb 指 令 是 16 位 的 ) DI 表 示 Embedded ICE Logic, 支 持 JTAG 调 试 M 表 示 内 嵌 硬 件 乘 法 器 ARM720T 是 具 有 cache MMU( 内 存 管 理 单 元 ) 和 写 缓 冲 的 一 种 ARM7TDMI 使 用 v4t 体 系 结 构 5 级 流 水 线 :CPI 被 提 高 到 1.5, 提 高 了 最 高 主 频 ARM9TDMI ARM9E ARM11 系 列 哈 佛 体 系 结 构 : 增 加 了 存 储 器 有 效 带 宽 ( 指 令 存 储 器 接 口 和 数 据 存 储 器 接 口 ), 实 现 了 同 时 访 问 指 令 存 储 器 和 数 据 存 储 器 的 功 能 一 般 提 供 附 带 的 cache:arm922t 有 2 X 8KB 的 cache MMU 和 写 缓 冲 ; ARM920T 除 了 有 2 16KB 的 cache 之 外, 其 他 的 与 ARM922t 相 同 ; ARM940T 有 一 个 MPU( 内 存 保 护 单 元 ) ARM9E 是 在 ARM9TDMI 的 基 础 上, 增 加 了 一 些 功 能 : 支 持 V5TE 版 本 的 体 系 结 构, 实 现 了 单 周 期 乘 法 器 和 Embedded ICE Logic RT ARM926EJ-S / ARM946E-S: 有 可 配 置 的 指 令 和 数 据 cache 指 令 和 数 据 TCM 接 口 以 及 AHB 总 线 接 口 ARM926EJ-S 有 MMU,ARM946E-S 有 MPU ARM966E-S: 有 指 令 和 数 据 TCM 接 口, 没 有 cache MPU/MMU ARM1136JF-S: 使 用 ARM V6 体 系 结 构, 性 能 强 大 (8 级 流 水 线, 有 静 态 / 动 态 分 支 预 测 器 和 返 回 堆 栈 ), 有 低 延 迟 中 断 模 式, 有 MMU, 有 支 持 物 理 标 记 的 4-64k 指 令 和 数 据 cache, 有 一 些 内 嵌 的 可 配 置 的 TCM, 有 4 个 主 存 端 口 (64 位 存 储 器 接 口 ), 可 以 集 成 VFP 协 处 理 器 ( 可 选 ) ARM1156T2(F)-S: 有 MPU, 支 持 Thumb2 ISA ARM1176JZ(F)-S: 在 ARM1136JF-S 基 础 上 实 现 了 TrustZone 技 术 8
101 Cortex-A8: 使 用 v7a 体 系 结 构, 支 持 MMU AXI VFP 和 NEON 专 业 始 于 专 注 卓 识 源 于 远 见 Cortex 系 列 Cortex-R4: 使 用 v7r 体 系 结 构, 支 持 MPU( 可 选 ) AXI 和 Dual Issue 技 术 Cortex-M3: 使 用 v7m 体 系 结 构, 支 持 MPU ( 可 选 ) AHB Lite 和 APB 因 为 本 书 所 采 用 的 FS2410 开 发 板 的 S3C2410X 是 一 款 ARM9 核 处 理 器, 所 以 下 面 重 点 学 习 ARM9 核 处 理 器 2.ARM9 主 要 特 点 ARM 处 理 器 凭 借 它 的 低 功 耗 高 性 能 等 特 点, 被 广 泛 应 用 于 个 人 通 信 等 嵌 入 式 领 域, 而 ARM7 也 曾 在 中 低 端 手 持 设 备 中 占 据 了 一 席 之 地 然 而,ARM7 的 处 理 性 能 逐 渐 无 法 满 足 人 们 日 益 增 长 的 高 性 能 处 理 的 需 求, 它 开 始 退 出 主 流 应 用 领 域, 取 而 代 之 的 是 性 能 更 加 强 大 的 ARM9 系 列 处 理 器 新 一 代 的 ARM9 处 理 器, 通 过 全 新 的 设 计, 能 够 达 到 两 倍 以 上 于 ARM7 处 理 器 的 处 理 能 力 它 的 主 要 特 点 如 下 所 述 (1)5 级 流 水 线 ARM7 处 理 器 采 用 的 3 级 流 水 线 设 计, 而 ARM9 则 采 用 5 级 流 水 线 设 计, 如 图 4.4 所 示 通 过 使 用 5 级 流 水 线 机 制, 在 每 一 个 时 钟 周 期 内 可 以 同 时 执 行 5 条 指 令 这 样 就 大 大 提 高 了 处 理 性 能 在 同 样 的 加 工 工 艺 下,ARM9 处 理 器 的 时 钟 频 率 是 ARM7 的 1.8~2.2 倍 图 4.4 ARM7 与 ARM9 流 水 线 比 较 (2) 采 用 哈 佛 结 构 首 先 读 者 需 要 了 解 什 么 叫 哈 佛 结 构 在 计 算 机 中, 根 据 计 算 机 的 存 储 器 结 构 及 其 总 线 连 接 形 式, 计 算 机 系 统 可 以 被 分 为 冯 诺 依 曼 结 构 和 哈 佛 结 构, 其 中 冯 诺 依 曼 结 构 共 用 数 据 存 储 空 间 和 程 序 存 储 空 间, 它 们 共 享 存 储 器 总 线, 这 也 是 以 往 设 计 时 常 用 的 方 式 ; 而 哈 佛 结 构 则 具 有 分 离 的 数 据 和 程 序 空 间 及 分 离 的 访 问 总 线 所 以 哈 佛 结 构 在 指 令 执 行 时, 取 址 和 取 数 可 以 并 行, 因 此 具 有 更 高 的 执 行 效 率 ARM9 采 用 的 就 是 哈 佛 结 构, 而 ARM7 采 用 的 则 是 冯 诺 依 曼 结 构 如 图 4.5 和 图 4.6 分 别 体 现 了 冯 诺 依 曼 结 构 和 哈 佛 结 构 的 数 据 存 储 方 式 图 4.5 冯 诺 依 曼 结 构 图 4.6 哈 佛 结 构 由 于 在 RISC 架 构 的 处 理 器 中, 程 序 中 大 约 有 30% 的 指 令 是 Load-Store 指 令, 而 采 用 哈 佛 结 构 大 大 提 升 了 这 两 个 指 令 的 执 行 速 度, 因 此 对 提 高 系 统 效 率 的 贡 献 是 非 常 明 显 的 (3) 高 速 缓 存 和 写 缓 存 的 引 入 由 于 在 处 理 器 中, 一 般 处 理 器 速 度 远 远 高 于 存 储 器 访 问 速 度, 那 么, 如 果 存 储 器 访 问 成 为 系 统 性 能 的 瓶 颈, 则 处 理 器 再 快 都 毫 无 作 用 在 这 种 情 况 下, 高 速 缓 存 (Cache) 和 写 缓 存 (Write Buffer) 可 以 很 好 地 解 决 这 个 问 题, 它 们 存 储 了 最 近 常 用 的 代 码 和 数 据, 以 供 CPU 快 速 存 储, 如 图 4.7 所 示 (4) 支 持 MMU 9
102 图 4.7 ARM9 的 高 速 缓 存 和 读 缓 存 MMU 是 内 存 管 理 单 元, 它 把 内 存 以 页 (page) 为 单 位 来 进 行 处 理 一 页 内 存 是 指 一 个 具 有 一 定 大 小 的 连 续 的 内 存 块, 通 常 为 4096B 或 8192B 操 作 系 统 为 每 个 正 在 运 行 的 程 序 建 立 并 维 护 一 张 被 称 为 进 程 内 存 映 射 (Process Memory Map) 的 表, 表 中 记 录 了 程 序 可 以 存 取 的 所 有 内 存 页 以 及 它 们 的 实 际 位 置 每 当 程 序 存 取 一 块 内 存 时, 它 会 把 相 应 的 虚 拟 地 址 (virtual address) 传 送 给 MMU, 而 MMU 会 在 PMM 中 查 找 这 块 内 存 的 实 际 位 置, 也 就 是 物 理 地 址 (physical address), 物 理 地 址 可 以 在 内 存 中 或 磁 盘 上 的 任 何 位 置 如 果 程 序 要 存 取 的 位 置 在 磁 盘 上, 就 必 须 把 包 含 该 地 址 的 页 从 磁 盘 上 读 到 内 存 中, 并 且 必 须 更 新 PMM 以 反 映 这 个 变 化 ( 这 被 称 为 pagefault, 即 页 错 ) MMU 的 实 现 过 程 如 图 4.8 所 示 图 4.8 MMU 的 实 现 过 程 只 有 拥 有 了 MMU 才 能 真 正 实 现 内 存 保 护 例 如 当 A 进 程 的 程 序 试 图 直 接 访 问 属 于 B 进 程 的 虚 拟 地 址 中 的 数 据, 那 么 MMU 会 产 生 一 个 异 常 (Exception) 来 阻 止 A 的 越 界 操 作 这 样, 通 过 内 存 保 护, 一 个 进 程 的 失 败 并 不 会 影 响 其 他 进 程 的 运 行, 从 而 增 强 了 系 统 的 稳 定 性, 如 图 4.9 所 示 ARM9 也 正 是 因 为 拥 有 了 MMU, 所 以 比 ARM7 具 有 更 强 的 稳 定 性 和 可 靠 性 S3C2410 处 理 器 详 解 图 4.9 内 存 保 护 示 意 图 本 书 所 采 用 的 硬 件 平 台 是 深 圳 优 龙 科 技 有 限 公 司 的 开 发 板 FS2410( 如 图 4.10 所 示 ), 它 的 中 央 处 理 器 是 三 星 公 司 的 S3C2410X S3C2410X 是 使 用 ARM920T 核 采 用 0.18 m 工 艺 CMOS 标 准 宏 单 元 和 存 储 编 译 器 开 发 而 成 的 由 于 采 用 了 由 ARM 公 司 设 计 的 16/32 位 ARM920T RISC 处 理 器, 因 此 S3C2410X 实 现 了 MMU 和 独 立 的 16KB 指 令 和 16KB 数 据 哈 佛 结 构 的 缓 存, 且 每 个 缓 存 均 为 8 个 字 长 度 的 流 水 线 它 的 低 功 耗 精 简 而 出 色 的 全 静 态 设 计 特 别 适 用 于 对 成 本 和 功 耗 敏 感 的 领 域 S3C2410X 提 供 全 面 的 通 用 的 片 上 外 设, 大 大 降 低 系 统 的 成 本, 下 面 列 举 了 S3C2410X 的 主 要 片 上 功 能 1.8V ARM920T 内 核 供 电,1.8V/2.5V/3.3V 存 储 器 供 电 ; 16KB 指 令 和 16KB 数 据 缓 存 的 MMU 内 存 管 理 单 元 ; 外 部 存 储 器 控 制 (SDRAM 控 制 和 芯 片 选 择 逻 辑 ); 提 供 LCD 控 制 器 ( 最 大 支 持 4K 色 的 STN 或 256K 色 TFT 的 LCD), 并 带 有 1 个 通 道 的 LCD 专 用 DMA 控 制 器 ; 10
103 提 供 4 通 道 DMA, 具 有 外 部 请 求 引 脚 ; 提 供 3 通 道 UART( 支 持 IrDA1.0,16 字 节 发 送 FIFO 及 16 字 节 接 收 FIFO)/2 通 道 SPI 接 口 ; 提 供 1 个 通 道 多 主 IIC 总 线 控 制 器 /1 通 道 IIS 总 线 控 制 器 ; 兼 容 SD 主 机 接 口 1.0 版 及 MMC 卡 协 议 2.11 版 ; 提 供 2 个 主 机 接 口 的 USB 口 /1 个 设 备 USB 口 (1.1 版 本 ); 4 通 道 PWM 定 时 器 /1 通 道 内 部 计 时 器 ; 图 4.10 优 龙 FS2410 开 发 板 实 物 图 提 供 看 门 狗 定 时 器 ; 提 供 117 个 通 用 I/O 口 /24 通 道 外 部 中 断 源 ; 提 供 不 同 的 电 源 控 制 模 式 : 正 常 慢 速 空 闲 及 电 源 关 闭 模 式 ; 提 供 带 触 摸 屏 接 口 的 8 通 道 10 位 ADC; 提 供 带 日 历 功 能 的 实 时 时 钟 控 制 器 (RTC); 具 有 PLL 的 片 上 时 钟 发 生 器 S3C2410X 系 统 结 构 图 如 图 4.11 所 示 下 面 依 次 对 S3C2410X 的 系 统 管 理 器 Nand Flash 引 导 装 载 器 缓 冲 存 储 器 时 钟 和 电 源 管 理 及 中 断 控 制 进 行 简 要 讲 解, 要 注 意, 其 中 所 有 模 式 的 选 择 都 是 通 过 对 相 关 寄 存 器 特 定 值 的 设 定 来 实 现 的, 因 此, 当 读 者 需 要 对 此 进 行 修 改 时, 请 参 阅 三 星 公 司 提 供 S3C2410X 用 户 手 册 1. 系 统 管 理 器 S3C2410X 支 持 小 / 大 端 模 式, 它 将 系 统 的 存 储 空 间 分 为 8 个 组 (bank), 其 中 每 个 bank 有 128MB, 总 共 为 1GB 每 个 组 可 编 程 的 数 据 总 线 宽 度 为 8/16/32 位, 其 中 bank0~bank5 具 有 固 定 的 bank 起 始 地 址 和 结 束 地 址, 用 于 ROM 和 SRAM 而 bank6 和 bank7 是 大 小 可 变 的, 用 于 ROM SRAM 或 SDRAM 这 里, 所 有 的 存 储 器 bank 都 具 有 可 编 程 的 操 作 周 期, 并 且 支 持 掉 电 时 的 SDRAM 自 刷 新 模 式 和 多 种 类 型 的 引 导 ROM 2.nand flash 引 导 装 载 器 S3C2410X 支 持 从 nand flash 存 储 器 启 动, 其 中, 开 始 的 4KB 为 内 置 缓 冲 存 储 器, 它 在 启 动 时 将 被 转 载 ( 装 载 or 转 载 ) 到 SDRAM 中 并 执 行 引 导, 之 后 该 4KB 可 以 用 作 其 他 用 途 11
104 ARM920T Instruction MMU IV 2 A[31 0] C13 IPA[31 0] ID[31 0] Instruction CACHE (16KB) External Coproc Interface JTAG DV 2 A[31 0] ARM9TDMI Processor core (Internal Embedded ICE) Data MMU C13 DPA[31 0] DD[31 0] DVA[31 0] CP15 Data CACHE (16KB) Write Buffer WriteBack PA Tag RAM AMBA Bus I/F WBPA[31 0] Clock Generator (MPLL) LCD CONT. LCD DMA USB Host CONT ExtMaster NAND CONT. NAND Flash Boot Loader A H B B U S Bridge & DMA(4Ch) BUS CONT. Arbitor/Decode Interrupt CONT. Power Management Memory CONT. SRAM/NOR/SDRAM UART 0,1,2 I2C USB Devfce SDI/MMC Watchdog Timer BUS CONT. Arbitor/Decode SPI 0,1 A P B B U S I2S GPIO RTC ADC Timer/PWM 0~3,4(Internal) 图 4.11 S3C2410X 系 统 结 构 图 Flash 是 一 种 非 易 失 闪 存 技 术 Intel 于 1988 年 首 先 开 发 出 Nor Flash 技 术 之 后, 彻 底 改 变 了 原 先 由 EPROM 和 EEPROM 一 统 天 下 的 局 面 紧 接 着,1989 年 东 芝 公 司 发 布 了 Nand Flash 结 构, 强 调 降 低 每 比 特 的 成 本 更 高 的 性 能, 并 且 像 磁 盘 一 样 可 以 通 过 接 口 轻 松 升 级 Nor Flash 的 特 点 是 芯 片 内 执 行 (Execute In Place), 这 样 应 用 程 序 可 以 直 接 在 Flash 闪 存 内 运 行, 而 不 必 再 把 代 码 读 到 系 统 RAM 中 Nor Flash 的 传 输 效 率 很 高, 在 1~4MB 的 小 容 量 时 具 有 很 高 的 成 本 效 益, 但 是 很 低 的 写 入 和 擦 除 速 度 大 大 影 响 了 它 的 性 能 Nand flash 结 构 能 提 供 极 高 的 单 元 密 度, 可 以 达 到 高 存 储 密 度,NAND 读 和 写 操 作 采 用 512 字 节 的 块, 单 元 尺 寸 几 乎 是 nor 器 件 的 一 半, 同 时 由 于 生 产 过 程 更 为 简 单, 大 大 降 低 了 生 产 的 成 本 NAND 闪 存 中 每 个 块 的 最 大 擦 写 次 数 是 100 万 次, 是 Nor Flash 的 10 倍, 这 些 都 使 得 Nand Flash 越 来 越 受 到 人 们 的 欢 迎 同 时,S3C2410X 也 支 持 从 外 部 ngcs0 片 选 的 Nor flash 启 动, 如 在 优 龙 的 开 发 板 上 将 JP1 跳 线 去 掉 就 可 从 12
105 Nor Flash 启 动 ( 默 认 从 Nand Flash 启 动 ) 在 这 两 种 启 动 模 式 下, 各 片 选 的 存 储 空 间 分 配 是 不 同 的, 如 图 4.12 所 示 图 4.12 S3C2410 两 种 启 动 模 式 地 址 映 射 3. 缓 冲 存 储 器 S3C2410X 是 带 有 指 令 缓 存 (16KB) 和 数 据 缓 存 (16KB) 的 联 合 缓 存 装 置, 一 个 缓 冲 区 能 够 保 持 16 字 的 数 据 和 4 个 地 址 4. 时 钟 和 电 源 管 理 S3C2410X 采 用 独 特 的 时 钟 管 理 模 式, 它 具 有 PLL( 相 位 锁 定 环 路, 用 于 稳 定 频 率 ) 的 芯 片 时 钟 发 生 器, 而 在 此,PLL 又 分 为 UPLL 和 MPLL 其 中 UPLL 时 钟 发 生 器 用 于 主 / 从 USB 操 作,MPLL 时 钟 发 生 器 用 于 产 生 主 时 钟, 使 其 能 以 极 限 频 率 203MHz(1.8V) 运 行 S3C2410X 的 电 源 管 理 模 式 又 分 为 正 常 慢 速 空 闲 和 掉 电 4 种 模 式 其 中 慢 速 模 式 为 不 带 PLL 的 低 频 时 钟 模 式, 空 闲 模 式 始 终 为 CPU 停 止 模 式, 掉 电 模 式 为 所 有 外 围 设 备 全 部 掉 电 仅 内 核 电 源 供 电 的 模 式 另 外,S3C2410X 对 片 内 的 各 个 部 件 采 用 独 立 的 供 电 方 式 1.8V 的 内 核 供 电 3.3V 的 存 储 器 独 立 供 电 ( 通 常 对 SDRAM 采 用 3.3V, 对 移 动 SDRAM 采 用 1.8/2.5V) 3.3V 的 VDDQ 3.3V 的 I/O 独 立 供 电 由 于 在 嵌 入 式 系 统 中 电 源 管 理 非 常 关 键, 它 直 接 涉 及 功 耗 等 各 方 面 的 系 统 性 能, 而 S3C2410X 的 电 源 管 理 中 独 立 的 供 电 方 式 和 多 种 模 式 可 以 有 效 地 处 理 系 统 的 不 同 状 态, 从 而 达 到 最 优 的 配 置 5. 中 断 控 制 中 断 处 理 在 嵌 入 式 系 统 开 发 中 非 常 重 要, 尤 其 对 于 从 单 片 机 转 入 到 嵌 入 式 的 读 者 来 说, 与 单 片 机 中 简 单 的 中 断 模 式 相 比,ARM 中 的 中 断 处 理 要 复 杂 得 多 如 果 读 者 无 相 关 基 础, 建 议 先 熟 悉 相 关 的 基 础 概 念 再 进 行 下 一 步 学 习 首 先 给 出 了 一 般 的 中 断 处 理 流 程, 如 图 4.13 所 示 13
106 时 间 中 断 请 求 后 台 程 序 CPU 上 下 文 保 存 CPU 上 下 文 恢 复 中 断 服 务 程 序 用 户 中 断 服 务 子 程 序 代 码 中 断 延 迟 中 断 响 应 中 断 恢 复 图 4.13 一 般 中 断 处 理 流 程 S3C2410X 包 括 55 个 中 断 源, 其 中 有 1 个 看 门 狗 定 时 器 中 断 5 个 定 时 器 中 断 9 个 通 用 异 步 串 行 口 中 断 24 个 外 部 中 断 4 个 DMA 中 断 2 个 RTC( 实 时 时 钟 控 制 器 ) 中 断 2 个 USB 中 断 1 个 LCD 中 断 和 1 个 电 池 故 障 其 中, 对 外 部 中 断 源 具 有 电 平 / 边 沿 两 种 触 发 模 式 另 外, 对 于 非 常 紧 急 的 中 断 可 以 支 持 使 用 快 速 中 断 请 求 (FIQ) S3C2410X 的 中 断 处 理 流 程 ( 该 图 摘 自 S3C2410X 用 户 手 册 ) 如 图 4.14 所 示 Request sources (with sub-register) SUBSRCPND SUBMASK SRCPND MASK INTPND Request sources (without sub-register) MODE Priority IRQ FIQ 图 4.14 S3C2410X 中 断 处 理 流 程 图 中 的 SUBSRCPND SRCPND SUBMASK MASK 和 MODE 都 是 与 中 断 相 关 的 寄 存 器, 其 中 SUBSRCPND 和 SRCPND 寄 存 器 用 来 表 示 有 哪 些 中 断 被 触 发 了 和 是 否 正 在 等 待 处 理 (pending ); SUBMASK (INTSUBMSK 寄 存 器 ) 和 MASK(INTMSK 寄 存 器 ) 用 于 屏 蔽 某 些 中 断 图 中 的 Request sources(with sub register) 表 示 的 是 INT_RXD0 INT_TXD0 等 11 个 中 断 源, 它 们 不 同 于 Request sources(without sub register) 的 操 作 如 下 : (1) Request sources(without sub register) 中 的 中 断 源 被 触 发 之 后,SRCPND 寄 存 器 中 相 应 位 被 置 1, 如 果 此 中 断 没 有 被 INTMSK 寄 存 器 屏 蔽 或 者 是 快 中 断 (FIQ) 的 话, 它 将 被 进 一 步 处 理 (2) 对 于 Request sources(with sub register) 中 的 中 断 源 被 触 发 之 后,SUBSRCPND 寄 存 器 中 的 相 应 位 被 置 1, 如 果 此 中 断 没 有 被 SUBMSK 寄 存 器 屏 蔽 的 话, 它 在 SRCPND 寄 存 器 中 的 相 应 位 也 被 置 1 在 此 之 后 的 两 者 的 处 理 过 程 是 一 样 的 接 下 来, 在 SRCPND 寄 存 器 中, 被 触 发 的 中 断 的 相 应 位 被 置 1, 等 待 处 理 (1) 如 果 被 触 发 的 中 断 中 有 快 中 断 (FIQ) MODE(INTMOD 寄 存 器 ) 中 为 1 的 位 对 应 的 中 断, 则 CPU 的 FIQ 中 断 函 数 被 调 用 注 意 :FIQ 只 能 分 配 一 个, 即 INTMOD 中 只 能 有 一 位 被 设 为 1 (2) 对 于 一 般 中 断 IRQ, 可 能 同 时 有 几 个 中 断 被 触 发, 未 被 INTMSK 寄 存 器 屏 蔽 的 中 断 经 过 比 较 后, 选 出 优 先 级 最 高 的 中 断, 然 后 CPU 调 用 IRQ 中 断 处 理 函 数 中 断 处 理 函 数 可 以 通 过 读 取 INTPND( 标 识 最 高 优 先 级 的 寄 存 器 ) 寄 存 器 来 确 定 中 断 源 是 哪 个, 也 可 以 读 INTOFFSET 寄 存 器 来 确 定 中 断 源 4.3 嵌 入 式 软 件 开 发 流 程 嵌 入 式 系 统 开 发 概 述 由 嵌 入 式 系 统 本 身 的 特 性 所 影 响, 嵌 入 式 系 统 开 发 与 通 用 系 统 的 开 发 有 很 大 的 区 别 嵌 入 式 系 统 的 开 发 主 14
107 要 分 为 系 统 总 体 开 发 嵌 入 式 硬 件 开 发 和 嵌 入 式 软 件 开 发 3 大 部 分, 其 总 体 流 程 图 如 图 4.15 所 示 在 系 统 总 体 开 发 中, 由 于 嵌 入 式 系 统 与 硬 件 依 赖 非 常 紧 密, 往 往 某 些 需 求 只 能 通 过 特 定 的 硬 件 才 能 实 现, 因 此 需 要 进 行 处 理 器 选 型, 以 更 好 地 满 足 产 品 的 需 求 另 外, 对 于 有 些 硬 件 和 软 件 都 可 以 实 现 的 功 能, 就 需 要 在 成 本 和 性 能 上 做 出 抉 择 往 往 通 过 硬 件 实 现 会 增 加 产 品 的 成 品, 但 能 大 大 提 高 产 品 的 性 能 和 可 靠 性 再 次, 开 发 环 境 的 选 择 对 于 嵌 入 式 系 统 的 开 发 也 有 很 大 的 影 响 这 里 的 开 发 环 境 包 括 嵌 入 式 操 作 系 统 的 选 择 以 及 开 发 工 具 的 选 择 等 本 书 在 节 对 各 种 不 同 的 嵌 入 式 操 作 系 统 进 行 了 比 较, 读 者 可 以 以 此 为 依 据 进 行 相 关 的 选 择 比 如, 对 开 发 成 本 和 进 度 限 制 较 大 的 产 品 可 以 选 择 嵌 入 式 Linux, 对 实 时 性 要 求 非 常 高 的 产 品 可 以 选 择 Vxworks 等 由 于 本 书 主 要 讨 论 嵌 入 式 软 件 的 应 用 开 发, 因 此 对 硬 件 开 发 不 做 详 细 讲 解, 而 主 要 讨 论 嵌 入 式 软 件 开 发 的 流 程 嵌 入 式 软 件 开 发 概 述 嵌 入 式 软 件 开 发 总 体 流 程 为 图 4.15 中 软 件 设 计 实 现 部 分 所 示, 它 同 通 用 计 算 机 软 件 开 发 一 样, 分 为 需 求 分 析 软 件 概 要 设 计 软 件 详 细 设 计 软 件 实 现 和 软 件 测 试 其 中 嵌 入 式 软 件 需 求 分 析 与 硬 件 的 需 求 分 析 合 二 为 一, 故 没 有 分 开 画 出 由 于 在 嵌 入 式 软 件 开 发 的 工 具 非 常 多, 为 了 更 好 地 帮 助 读 者 选 择 开 发 工 具, 下 面 首 先 对 嵌 入 式 软 件 开 发 过 程 中 所 使 用 的 工 具 做 一 简 单 归 纳 嵌 入 式 软 件 的 开 发 工 具 根 据 不 同 的 开 发 过 程 而 划 分, 比 如 在 需 求 分 析 阶 段, 可 以 选 择 IBM 的 Rational Rose 等 软 件, 而 在 程 序 开 发 阶 段 可 以 采 用 CodeWarrior( 下 面 要 介 绍 的 ADS 的 一 个 工 具 ) 等, 在 调 试 阶 段 所 用 的 Multi-ICE 等 同 时, 不 同 的 嵌 入 式 操 作 系 统 往 往 会 有 配 套 的 开 发 工 具, 比 如 Vxworks 有 集 成 开 发 环 境 Tornado,WindowsCE 的 集 成 开 发 环 境 WindowsCE Platform 等 此 外, 不 同 的 处 理 器 可 能 还 有 对 应 的 开 发 工 具, 比 如 ARM 的 常 用 集 成 开 发 工 具 ADS IAR 和 RealView 等 在 这 里, 大 多 数 软 件 都 有 比 较 高 的 使 用 费 用, 但 也 可 以 大 大 加 快 产 品 的 开 发 进 度, 用 户 可 以 根 据 需 求 自 行 选 择 图 4.16 是 嵌 入 式 开 发 的 不 同 阶 段 的 常 用 软 件 15
108 系 统 定 义 可 行 性 研 究 需 求 分 析 系 统 总 体 框 架 软 硬 件 划 分 处 理 器 选 定 操 作 系 统 选 定 开 发 环 境 选 定 系 统 总 体 设 计 硬 件 设 计 制 作 硬 件 概 要 设 计 硬 件 详 细 设 计 硬 件 制 作 软 件 概 要 设 计 软 件 详 细 设 计 软 件 实 现 软 件 设 计 实 现 硬 件 测 试 软 件 测 试 软 硬 件 集 成 功 能 性 能 测 试 No 符 合 要 求 Yes 产 品 Products Rational Rose RealTime ObjectGeode Rhapsody TAU Tornado LambdaTOOL prism+ Spectra WinCE Platform Builder CodeWarrior Xray Debugger Logiscope Multi-ICE 图 4.15 嵌 入 式 系 统 开 发 流 程 图 Requirement Analysis Software Design Coding Test Phase 图 4.16 嵌 入 式 开 发 不 同 阶 段 的 常 用 软 件 嵌 入 式 系 统 的 软 件 开 发 与 通 常 软 件 开 发 的 区 别 主 要 在 于 软 件 实 现 部 分, 其 中 又 可 以 分 为 编 译 和 调 试 两 部 分, 下 面 分 别 对 这 两 部 分 进 行 讲 解 1. 交 叉 编 译 嵌 入 式 软 件 开 发 所 采 用 的 编 译 为 交 叉 编 译 所 谓 交 叉 编 译 就 是 在 一 个 平 台 上 生 成 可 以 在 另 一 个 平 台 上 执 行 的 代 码 在 第 3 章 中 已 经 提 到, 编 译 的 最 主 要 的 工 作 就 在 将 程 序 转 化 成 运 行 该 程 序 的 CPU 所 能 识 别 的 机 器 代 码, 由 于 不 同 的 体 系 结 构 有 不 同 的 指 令 系 统 因 此, 不 同 的 CPU 需 要 有 相 应 的 编 译 器, 而 交 叉 编 译 就 如 同 翻 译 一 样, 把 相 同 的 程 序 代 码 翻 译 成 不 同 CPU 的 对 应 可 执 行 二 进 制 文 件 要 注 意 的 是, 编 译 器 本 身 也 是 程 序, 也 要 在 与 之 对 应 的 某 一 个 CPU 平 台 上 运 行 嵌 入 式 系 统 交 叉 编 译 环 境 如 图 4.17 所 示 16
109 图 4.17 交 叉 编 译 环 境 与 交 叉 编 译 相 对 应, 平 时 常 用 的 编 译 称 为 本 地 编 译 这 里 一 般 将 进 行 交 叉 编 译 的 主 机 称 为 宿 主 机, 也 就 是 普 通 的 通 用 PC, 而 将 程 序 实 际 的 运 行 环 境 称 为 目 标 机, 也 就 是 嵌 入 式 系 统 环 境 由 于 一 般 通 用 计 算 机 拥 有 非 常 丰 富 的 系 统 资 源 使 用 方 便 的 集 成 开 发 环 境 和 调 试 工 具 等, 而 嵌 入 式 系 统 的 系 统 资 源 非 常 紧 缺, 无 法 在 其 上 运 行 相 关 的 编 译 工 具, 因 此, 嵌 入 式 系 统 的 开 发 需 要 借 助 宿 主 机 ( 通 用 计 算 机 ) 来 编 译 出 目 标 机 的 可 执 行 代 码 由 于 编 译 的 过 程 包 括 编 译 链 接 等 几 个 阶 段, 因 此, 嵌 入 式 的 交 叉 编 译 也 包 括 交 叉 编 译 交 叉 链 接 等 过 程, 通 常 ARM 的 交 叉 编 译 器 为 arm-elf-gcc arm-linux-gcc 等, 交 叉 链 接 器 为 arm-elf-ld arm-linux-ld 等, 交 叉 编 译 过 程 如 图 4.18 图 4.18 嵌 入 式 交 叉 编 译 过 程 所 示 2. 交 叉 调 试 嵌 入 式 软 件 经 过 编 译 和 链 接 后 即 进 入 调 试 阶 段, 调 试 是 软 件 开 发 过 程 中 必 不 可 少 的 一 个 环 节, 嵌 入 式 软 件 开 发 过 程 中 的 交 叉 调 试 与 通 用 软 件 开 发 过 程 中 的 调 试 方 式 有 很 大 的 差 别 在 常 见 软 件 开 发 中, 调 试 器 与 被 调 试 的 程 序 往 往 运 行 在 同 一 台 计 算 机 上, 调 试 器 是 一 个 单 独 运 行 着 的 进 程, 它 通 过 操 作 系 统 提 供 的 调 试 接 口 来 控 制 被 调 试 的 进 程 而 在 嵌 入 式 软 件 开 发 中, 调 试 时 采 用 的 是 在 宿 主 机 和 目 标 机 之 间 进 行 的 交 叉 调 试, 调 试 器 仍 然 运 行 在 宿 主 机 的 通 用 操 作 系 统 之 上, 但 被 调 试 的 进 程 却 是 运 行 在 基 于 特 定 硬 件 平 台 的 嵌 入 式 操 作 系 统 中, 调 试 器 和 被 调 试 进 程 通 过 串 口 或 者 网 络 进 行 通 信, 调 试 器 可 以 控 制 访 问 被 调 试 进 程, 读 取 被 调 试 进 程 的 当 前 状 态, 并 能 够 改 变 被 调 试 进 程 的 运 行 状 态 嵌 入 式 系 统 的 交 叉 调 试 有 多 种 方 法, 主 要 可 分 为 软 件 方 式 和 硬 件 方 式 两 种 它 们 一 般 都 具 有 如 下 一 些 典 型 特 点 调 试 器 和 被 调 试 进 程 运 行 在 不 同 的 机 器 上, 调 试 器 运 行 在 PC 机 ( 宿 主 机 ), 而 被 调 试 的 进 程 则 运 行 在 各 种 专 业 调 试 板 上 ( 目 标 板 ) 调 试 器 通 过 某 种 通 信 方 式 ( 串 口 并 口 网 络 JTAG 等 ) 控 制 被 调 试 进 程 在 目 标 机 上 一 般 会 具 备 某 种 形 式 的 调 试 代 理, 它 负 责 与 调 试 器 共 同 配 合 完 成 对 目 标 机 上 运 行 着 的 进 程 的 调 试 这 种 调 试 代 理 可 能 是 某 些 支 持 调 试 功 能 的 硬 件 设 备, 也 可 能 是 某 些 专 门 的 调 试 软 件 ( 如 gdbserver) 目 标 机 可 能 是 某 种 形 式 的 系 统 仿 真 器, 通 过 在 宿 主 机 上 运 行 目 标 机 的 仿 真 软 件, 整 个 调 试 过 程 可 以 在 一 台 计 算 机 上 运 行 此 时 物 理 上 虽 然 只 有 一 台 计 算 机, 但 逻 辑 上 仍 然 存 在 着 宿 主 机 和 目 标 机 的 区 别 下 面 分 别 就 软 件 调 试 桩 方 式 和 硬 件 片 上 调 试 两 种 方 式 进 行 详 细 介 绍 (1) 软 件 方 式 软 件 调 试 主 要 是 通 过 插 入 调 试 桩 的 方 式 来 进 行 的 调 试 桩 方 式 进 行 调 试 是 通 过 目 标 操 作 系 统 和 调 试 器 内 分 别 加 入 某 些 功 能 模 块, 二 者 互 通 信 息 来 进 行 调 试 该 方 式 的 典 型 调 试 器 有 gdb 调 试 器 gdb 的 交 叉 调 试 器 分 为 GdbServer 和 GdbClient, 其 中 的 GdbServer 就 作 为 调 试 桩 在 安 装 在 目 标 板 上, GdbClient 就 是 驻 于 本 地 的 gdb 调 试 器 它 们 的 调 试 原 理 图 如 图 图 4.19 gdb 远 程 调 试 原 理 图 4.19 所 示 gdb 调 试 的 工 作 流 程 首 先, 建 立 调 试 器 ( 本 地 gdb) 与 目 标 操 作 系 统 的 通 信 连 接, 可 通 过 串 口 网 卡 并 口 等 多 种 方 式 17
110 然 后, 在 目 标 机 上 开 启 GdbServer 进 程, 并 监 听 对 应 端 口 在 宿 主 机 上 运 行 调 试 器 gdb, 这 时,gdb 就 会 自 动 寻 找 远 端 的 通 信 进 程, 也 就 是 GdbServer 的 所 在 进 程 在 宿 主 机 上 的 gdb 通 过 GdbServer 请 求 对 目 标 机 上 的 程 序 发 出 控 制 命 令 这 时,GdbServer 将 请 求 转 化 为 程 序 的 地 址 空 间 或 目 标 平 台 的 某 些 寄 存 器 的 访 问, 这 对 于 没 有 虚 拟 存 储 器 的 简 单 的 嵌 入 式 操 作 系 统 而 言, 是 十 分 容 易 的 GdbServer 把 目 标 操 作 系 统 的 所 有 异 常 处 理 转 向 通 信 模 块, 并 告 知 宿 主 机 上 gdb 当 前 有 异 常 宿 主 机 上 的 gdb 向 用 户 显 示 被 调 试 程 序 产 生 了 哪 一 类 异 常 这 样 就 完 成 了 调 试 的 整 个 过 程 这 个 方 案 的 实 质 是 用 软 件 接 管 目 标 机 的 全 部 异 常 处 理 及 部 分 中 断 处 理, 并 在 其 中 插 入 调 试 端 口 通 信 模 块, 与 主 机 的 调 试 器 进 行 交 互 但 是 它 只 能 在 目 标 机 系 统 初 始 化 完 毕 调 试 通 信 端 口 初 始 化 完 成 后 才 能 起 作 用, 因 此, 一 般 只 能 用 于 调 试 运 行 于 目 标 操 作 系 统 之 上 的 应 用 程 序, 而 不 宜 用 来 调 试 目 标 操 作 系 统 的 内 核 代 码 及 启 动 代 码 而 且, 它 必 须 改 变 目 标 操 作 系 统, 因 此, 也 就 多 了 一 个 不 用 于 正 式 发 布 的 调 试 版 (2) 硬 件 调 试 相 对 于 软 件 调 试 而 言, 使 用 硬 件 调 试 器 可 以 获 得 更 强 大 的 调 试 功 能 和 更 优 秀 的 调 试 性 能 硬 件 调 试 器 的 基 本 原 理 是 通 过 仿 真 硬 件 的 执 行 过 程, 让 开 发 者 在 调 试 时 可 以 随 时 了 解 到 系 统 的 当 前 执 行 情 况 目 前 嵌 入 式 系 统 开 发 中 最 常 用 到 的 硬 件 调 试 器 是 ROMMonitor ROMEmulator In-CircuitEmulator 和 In-CircuitDebugger 采 用 ROMMonitor 方 式 进 行 交 叉 调 试 需 要 在 宿 主 机 上 运 行 调 试 器, 在 宿 主 机 上 运 行 ROM 监 视 器 (ROMMonitor) 和 被 调 试 程 序, 宿 主 机 通 过 调 试 器 与 目 标 机 上 的 ROM 监 视 器 遵 循 远 程 调 试 协 议 建 立 通 信 连 接 ROM 监 视 器 可 以 是 一 段 运 行 在 目 标 机 ROM 上 的 可 执 行 程 序, 也 可 以 是 一 个 专 门 的 硬 件 调 试 设 备, 它 负 责 监 控 目 标 机 上 被 调 试 程 序 的 运 行 情 况, 能 够 与 宿 主 机 端 的 调 试 器 一 同 完 成 对 应 用 程 序 的 调 试 在 使 用 这 种 调 试 方 式 时, 被 调 试 程 序 首 先 通 过 ROM 监 视 器 下 载 到 目 标 机, 然 后 在 ROM 监 视 器 的 监 控 下 完 成 调 试 优 点 :ROM 监 视 器 功 能 强 大, 能 够 完 成 设 置 断 点 单 步 执 行 查 看 寄 存 器 修 改 内 存 空 间 等 各 项 调 试 功 能 确 定 : 同 软 件 调 试 一 样, 使 用 ROM 监 视 器 目 标 机 和 宿 主 机 必 须 建 立 通 信 连 接 其 原 理 图 如 图 4.20 所 示 采 用 ROMEmulator 方 式 进 行 交 叉 调 试 时 需 图 4.20 ROMMonitor 调 试 方 式 要 使 用 ROM 仿 真 器, 并 且 它 通 常 被 插 入 到 目 标 机 上 的 ROM 插 槽 中, 专 门 用 于 仿 真 目 标 机 上 的 ROM 芯 片 在 使 用 这 种 调 试 方 式 时, 被 调 试 程 序 首 先 下 载 到 ROM 仿 真 器 中, 因 此 等 效 于 下 载 到 目 标 机 的 ROM 芯 片 上, 然 后 在 ROM 仿 真 器 中 完 成 对 目 标 程 序 的 调 试 优 点 : 避 免 了 每 次 修 改 程 序 后 都 必 须 重 新 烧 写 到 目 标 机 的 ROM 中 缺 点 :ROM 仿 真 器 本 身 比 较 昂 贵, 功 能 相 对 来 讲 又 比 较 单 一, 只 适 图 4.21 ROMEmulator 调 试 方 式 应 于 某 些 特 定 场 合 其 原 理 如 图 4.21 所 示 采 用 In-CircuitEmulator(ICE) 方 式 进 行 交 叉 调 试 时 需 要 使 用 在 线 仿 真 器, 它 是 目 前 最 为 有 效 的 嵌 入 式 系 统 的 调 试 手 段 它 是 仿 照 目 标 机 上 的 CPU 而 专 门 设 计 的 硬 件, 可 以 完 全 仿 真 处 理 器 芯 片 的 行 为 仿 真 器 与 目 标 板 可 以 通 过 仿 真 头 连 接, 与 宿 主 机 可 以 通 过 串 口 并 口 网 线 或 USB 口 等 连 接 方 式 由 于 仿 真 器 自 成 体 系, 所 以 调 试 时 既 可 以 连 接 目 标 板, 也 可 以 不 连 接 目 标 板 在 线 仿 真 器 提 供 了 非 常 丰 富 的 调 试 功 能 在 使 用 在 线 仿 真 器 进 行 调 试 的 过 程 中, 可 以 按 顺 序 单 步 执 行, 也 可 以 倒 退 执 行, 还 可 以 实 时 查 看 所 有 需 要 的 数 据, 从 而 给 调 试 过 程 带 来 了 很 多 的 便 利 嵌 入 式 系 统 应 用 的 一 个 显 著 特 点 是 与 现 实 世 界 中 的 硬 件 直 接 相 关, 并 存 在 各 种 异 变 和 事 先 未 知 的 变 化, 从 而 给 微 处 理 器 的 指 令 执 行 带 来 各 种 不 确 定 因 素, 这 种 不 确 定 性 在 目 前 情 况 下 只 有 通 过 在 线 仿 真 器 才 有 可 能 18
111 发 现 优 点 : 功 能 强 大, 软 硬 件 都 可 做 到 完 全 实 时 在 线 调 试 缺 点 : 价 格 昂 贵 其 原 理 如 图 4.22 所 示 采 用 In-CircuitDebugger(ICD) 方 式 进 行 交 叉 调 试 时 需 要 使 用 在 线 调 试 器 由 于 图 4.22 ICE 调 试 方 式 ICE 的 价 格 非 常 昂 贵, 并 且 每 种 CPU 都 需 要 一 种 与 之 对 应 的 ICE, 使 得 开 发 成 本 非 常 高 一 个 比 较 好 的 解 决 办 法 是 让 CPU 直 接 在 其 内 部 实 现 调 试 功 能, 并 通 过 在 开 发 板 上 引 出 的 调 试 端 口 发 送 调 试 命 令 和 接 收 调 试 信 息, 完 成 调 试 过 程 如 使 用 非 常 广 泛 的 ARM 处 理 器 的 JTAG 端 口 技 术 就 是 由 此 而 诞 生 的 JTAG 是 1985 年 指 定 的 检 测 PCB 和 IC 芯 片 的 一 个 标 准 1990 年 被 修 改 成 为 IEEE 的 一 个 标 准, 即 IEEE JTAG 标 准 所 采 用 的 主 要 技 术 为 边 界 扫 描 技 术, 它 的 基 本 思 想 就 是 在 靠 近 芯 片 的 输 入 输 出 管 脚 上 增 加 一 个 移 位 寄 存 器 单 元 因 为 这 些 移 位 寄 存 器 单 元 都 分 布 在 芯 片 的 边 界 上 ( 周 围 ), 所 以 被 称 为 边 界 扫 描 寄 存 器 (Boundary-Scan Register Cell) 当 芯 片 处 于 调 试 状 态 时 候, 这 些 边 界 扫 描 寄 存 器 可 以 将 芯 片 和 外 围 的 输 入 输 出 隔 离 开 来 通 过 这 些 边 界 扫 描 寄 存 器 单 元, 可 以 实 现 对 芯 片 输 入 输 出 信 号 的 观 察 和 控 制 对 于 芯 片 的 输 入 管 脚, 可 通 过 与 之 相 连 的 边 界 扫 描 寄 存 器 单 元 把 信 号 ( 数 据 ) 加 载 到 该 管 脚 中 去 ; 对 于 芯 片 的 输 出 管 脚, 可 以 通 过 与 之 相 连 的 边 界 扫 描 寄 存 器 单 元 捕 获 (CAPTURE) 该 管 脚 的 输 出 信 号 这 样, 边 界 扫 描 寄 存 器 提 供 了 一 个 便 捷 的 方 式 用 于 观 测 和 控 制 所 需 要 调 试 的 芯 片 现 在 较 为 高 档 的 微 处 理 器 都 带 有 JTAG 接 口, 包 括 ARM7 ARM9 StrongARM DSP 等, 通 过 JTAG 接 口 可 以 方 便 地 对 目 标 系 统 进 行 测 试, 同 时, 还 可 以 实 现 Flash 编 程, 这 是 非 常 受 欢 迎 的 图 4.23 JTAG 调 试 方 式 优 点 : 连 接 简 单, 成 本 低 缺 点 : 特 性 受 制 于 芯 片 厂 商 其 原 理 如 图 4.23 所 示 4.4 实 验 内 容 使 用 JTAG 烧 写 Nand Flash 1. 实 验 目 的 通 过 使 用 JTAG 烧 写 Flash 的 实 验, 了 解 嵌 入 式 硬 件 环 境, 熟 悉 JTAG 的 使 用, 为 今 后 的 进 一 步 学 习 打 下 良 好 的 基 础 本 书 以 优 龙 的 FS2410 及 Flash 烧 写 工 具 为 例 进 行 讲 解, 不 同 厂 商 的 开 发 板 都 会 提 供 相 应 的 Flash 烧 写 工 具, 并 有 相 应 的 说 明 文 档, 请 读 者 在 了 解 基 本 原 理 之 后 查 阅 相 关 手 册 2. 实 验 内 容 (1) 熟 悉 开 发 板 的 硬 件 布 局 (2) 连 接 JTAG 口 (2) 安 装 giveio( 用 于 烧 写 Flash) 驱 动 (3) 打 开 SJF2410_BIOS.BAT(Flash 烧 写 程 序 ) 进 行 烧 写 19
112 3. 实 验 步 骤 (1) 熟 悉 开 发 板 硬 件 设 备 (2) 用 20 针 的 排 线 将 20 针 的 JTAG 接 口 与 JTAG 小 板 的 JP3 接 口 相 连 (3) 安 装 giveio 驱 动, 如 图 4.24 所 示 图 4.24 安 装 givieo 单 击 Install 按 钮, 出 现 如 图 4.25 所 示 的 效 果, 这 就 表 明 驱 动 安 装 成 功 (4) 打 开 SJF2410_BIOS.BAT, 如 图 4.26 所 示 图 4.25 givieo 驱 动 安 装 完 成 图 4.26 打 开 SJF2410_BIOS.BAT (5) 在 Select the function to test: 输 入 0, 表 示 对 K9S1208(FS2410 的 Nand Flash 的 芯 片 型 号 ) 进 行 烧 写, 如 图 4.27 所 示 20
113 图 4.27 选 择 烧 写 对 应 芯 片 (6) 在 接 下 来 的 Select the function to test: 里 输 入 0, 表 示 烧 写 类 型 为 程 序 再 在 接 下 来 的 Input the target block 里 输 入 希 望 的 偏 移 地 址, 在 此 处 写 为 0, 如 图 4.28 所 示 图 4.28 选 择 烧 写 类 型 及 偏 移 地 址 (7) 接 下 来, 执 行 Flash 的 烧 写 过 程, 如 图 4.29 所 示 图 4.29 Flash 的 烧 写 过 程 4. 实 验 结 果 Flash 的 烧 写 完 成 之 后, 用 户 可 以 选 择 退 出 (Exit) 选 项, 这 样 整 个 过 程 就 完 成 了 4.6 本 章 小 结 本 章 讲 解 了 嵌 入 式 中 的 基 本 概 念, 包 括 嵌 入 式 系 统 的 含 义 发 展 历 史 特 点 以 及 其 体 系 结 构 在 这 里, 重 点 要 掌 握 嵌 入 式 系 统 和 通 用 计 算 机 的 区 别 以 加 深 对 嵌 入 式 系 统 的 理 解 接 下 来 对 ARM 体 系 进 行 了 概 括 性 讲 解, 希 望 读 者 能 重 点 掌 握 ARM9 的 特 性, 有 条 件 的 读 者 希 望 能 结 合 实 际 开 发 板 进 行 学 习, 没 有 开 发 板 的 读 者 也 可 参 看 书 中 的 实 物 图, 以 获 得 感 性 的 认 识 另 外, 不 同 的 硬 件 平 台 都 会 有 一 定 的 区 别, 但 其 主 要 原 理 是 一 样 的, 对 于 某 些 细 节 的 不 同 处 理 请 读 者 参 阅 对 应 厂 商 的 用 户 手 册 21
114 本 章 的 最 后 讲 解 了 嵌 入 式 软 件 开 发 的 流 程, 其 中 重 点 讲 解 了 交 叉 编 译 和 交 叉 调 试, 这 些 概 念 初 次 学 习 会 感 觉 比 较 枯 燥, 但 这 些 概 念 又 是 非 常 重 要 的, 在 后 面 的 具 体 开 发 中 会 经 常 涉 及, 希 望 读 者 对 这 些 内 容 能 够 认 真 消 化 最 后 安 排 的 一 个 实 验 希 望 有 条 件 的 读 者 能 动 手 做 做, 当 然 在 做 之 前 一 定 认 真 阅 读 不 同 厂 商 提 供 的 用 户 手 册 4.5 思 考 与 练 习 1. 从 各 方 面 比 较 嵌 入 式 系 统 与 通 用 计 算 器 的 区 别 2.ARM9 有 哪 些 优 于 ARM7 的 特 性? 3. 什 么 是 交 叉 编 译? 为 什 么 要 进 行 交 叉 编 译? 4. 嵌 入 式 开 发 的 常 用 调 试 手 段 有 哪 几 种? 说 出 它 们 各 自 的 优 缺 点 联 系 方 式 集 团 官 网 : 嵌 入 式 学 院 : 移 动 互 联 网 学 院 : 企 业 学 院 : 物 联 网 学 院 : 研 发 中 心 :dev.hqyj.com 集 团 总 部 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 内 华 清 远 见 教 育 集 团 北 京 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 区, 电 话 : /5 上 海 地 址 : 上 海 市 徐 汇 区 漕 溪 路 银 海 大 厦 A 座 8 层, 电 话 : 深 圳 地 址 : 深 圳 市 龙 华 新 区 人 民 北 路 美 丽 AAA 大 厦 15 层, 电 话 : 成 都 地 址 : 成 都 市 武 侯 区 科 华 北 路 99 号 科 华 大 厦 6 层, 电 话 : 南 京 地 址 : 南 京 市 白 下 区 汉 中 路 185 号 鸿 运 大 厦 10 层, 电 话 : 武 汉 地 址 : 武 汉 市 工 程 大 学 卓 刀 泉 校 区 科 技 孵 化 器 大 楼 8 层, 电 话 : 西 安 地 址 : 西 安 市 高 新 区 高 新 一 路 12 号 创 业 大 厦 D3 楼 5 层, 电 话 :
115 嵌 入 式 LINUX 应 用 程 序 开 发 标 准 教 程 作 者 : 华 清 远 见 第 5 章 嵌 入 式 Linux 开 发 环 境 的 搭 建 掌 握 嵌 入 式 交 叉 编 译 环 境 的 搭 建 掌 握 嵌 入 式 主 机 通 信 环 境 的 配 置 学 会 使 用 交 叉 编 译 工 具 链 学 会 配 置 Linux 下 的 minicom 和 Windows 下 的 超 级 终 端 学 会 在 Linux 下 和 Windows 下 配 置 TFTP 服 务 学 会 配 置 NFS 服 务 学 会 编 译 Linux 内 核 学 会 搭 建 Linux 的 根 文 件 系 统 熟 悉 嵌 入 式 Linux 的 内 核 相 关 代 码 的 分 布 情 况 掌 握 Bootloader 的 原 理 了 解 U-Boot 的 代 码 结 构 和 移 植
116 5.1 嵌 入 式 开 发 环 境 的 搭 建 嵌 入 式 交 叉 编 译 环 境 的 搭 建 交 叉 编 译 的 概 念 在 第 4 章 中 已 经 详 细 讲 述 过, 搭 建 交 叉 编 译 环 境 是 嵌 入 式 开 发 的 第 一 步, 也 是 必 备 的 一 步 搭 建 交 叉 编 译 环 境 的 方 法 很 多, 不 同 的 体 系 结 构 不 同 的 操 作 内 容 甚 至 是 不 同 版 本 的 内 核, 都 会 用 到 不 同 的 交 叉 编 译 器, 而 且, 有 些 交 叉 编 译 器 经 常 会 有 部 分 的 bug, 这 都 会 导 致 最 后 的 代 码 无 法 正 常 地 运 行 因 此, 选 择 合 适 的 交 叉 编 译 器 对 于 嵌 入 式 开 发 是 非 常 重 要 的 交 叉 编 译 器 完 整 的 安 装 一 般 涉 及 多 个 软 件 的 安 装 ( 读 者 可 以 从 ftp://gcc.gnu.org/pub/ 下 载 ), 包 括 binutils gcc glibc 等 软 件 其 中,binutils 主 要 用 于 生 成 一 些 辅 助 工 具, 如 objdump as ld 等 ;gcc 是 用 来 生 成 交 叉 编 译 器 的, 主 要 生 成 arm-linux-gcc 交 叉 编 译 工 具 ( 应 该 说, 生 成 此 工 具 后 已 经 搭 建 起 了 交 叉 编 译 环 境, 可 以 编 译 Linux 内 核 了, 但 由 于 没 有 提 供 标 准 用 户 函 数 库, 用 户 程 序 还 无 法 编 译 );glibc 主 要 是 提 供 用 户 程 序 所 使 用 的 一 些 基 本 的 函 数 库 这 样, 交 叉 编 译 环 境 就 完 全 搭 建 起 来 了 上 面 所 述 的 搭 建 交 叉 编 译 环 境 比 较 复 杂, 很 多 步 骤 都 涉 及 对 硬 件 平 台 的 选 择 因 此, 现 在 嵌 入 式 平 台 提 供 厂 商 一 般 会 提 供 在 该 平 台 上 测 试 通 过 的 交 叉 编 译 器, 而 且 很 多 公 司 把 以 上 安 装 步 骤 全 部 写 入 脚 本 文 件 或 者 以 发 行 包 的 形 式 提 供, 这 样 就 大 大 方 便 了 用 户 的 使 用 如 优 龙 的 FS2410 开 发 光 盘 里 就 附 带 了 和 两 个 版 本 的 交 叉 编 译 器, 其 中 前 一 个 版 本 是 用 于 编 译 Linux 2.4 内 核 的, 而 后 一 个 版 本 是 用 于 编 译 Linux 2.6 版 本 内 核 的 由 于 这 是 厂 商 测 试 通 过 的 编 译 器, 因 此 可 靠 性 会 比 较 高, 而 且 与 开 发 板 能 够 很 好 地 吻 合 所 以 推 荐 初 学 者 直 接 使 用 厂 商 提 供 的 编 译 器 当 然, 由 于 时 间 滞 后 的 原 因, 这 个 编 译 器 往 往 不 是 最 新 的 版 本, 若 需 要 更 新 时 希 望 读 者 另 外 查 找 相 关 资 料 学 习 本 书 就 以 优 龙 自 带 的 cross 为 例 进 行 讲 解 ( 具 体 的 名 称 不 同 厂 商 可 能 会 有 区 别 ) 安 装 交 叉 编 译 器 的 具 体 步 骤 在 第 2 章 的 实 验 二 中 已 经 进 行 了 详 细 地 讲 解 了, 在 此 仅 回 忆 关 键 步 骤, 对 于 细 节 请 读 者 参 见 第 2 章 的 实 验 二 在 /usr/local/arm 下 解 压 cross bar.bz2 [root@localhost arm]# tar jxvf cross bar.bz2 [root@localhost arm]# ls cross tar.bz2 [root@localhost arm]# cd./3.3.2 [root@localhost arm]# ls arm-linux bin etc include info lib libexec man sbin share VERSIONS [root@localhost bin]# which arm-linux* /usr/local/arm/3.3.2/bin/arm-linux-addr2line /usr/local/arm/3.3.2/bin/arm-linux-ar /usr/local/arm/3.3.2/bin/arm-linux-as /usr/local/arm/3.3.2/bin/arm-linux-c++ /usr/local/arm/3.3.2/bin/arm-linux-c++filt /usr/local/arm/3.3.2/bin/arm-linux-cpp /usr/local/arm/3.3.2/bin/arm-linux-g++ /usr/local/arm/3.3.2/bin/arm-linux-gcc /usr/local/arm/3.3.2/bin/arm-linux-gcc /usr/local/arm/3.3.2/bin/arm-linux-gccbug /usr/local/arm/3.3.2/bin/arm-linux-gcov /usr/local/arm/3.3.2/bin/arm-linux-ld /usr/local/arm/3.3.2/bin/arm-linux-nm /usr/local/arm/3.3.2/bin/arm-linux-objcopy /usr/local/arm/3.3.2/bin/arm-linux-objdump 2
117 /usr/local/arm/3.3.2/bin/arm-linux-ranlib /usr/local/arm/3.3.2/bin/arm-linux-readelf /usr/local/arm/3.3.2/bin/arm-linux-size /usr/local/arm/3.3.2/bin/arm-linux-strings /usr/local/arm/3.3.2/bin/arm-linux-strip 可 以 看 到, 在 /usr/local/arm/3.3.2/bin/ 下 已 经 安 装 了 很 多 交 叉 编 译 工 具 用 户 可 以 查 看 arm 文 件 夹 下 的 VERSIONS 文 件, 显 示 如 下 : Versions gcc glibc binutils-head Tool chain binutils configuration:../binutils-head/configure Tool chain glibc configuration:../glibc-2.3.2/configure Tool chain gcc configuration../gcc-3.3.2/configure 可 以 看 到, 这 个 交 叉 编 译 工 具 确 实 集 成 了 binutils gcc glibc 这 几 个 软 件, 而 每 个 软 件 也 都 有 比 较 复 杂 的 配 置 信 息, 读 者 可 以 查 看 VERSIONS 文 件 了 解 相 关 信 息 超 级 终 端 和 minicom 配 置 及 使 用 前 文 已 知, 嵌 入 式 系 统 开 发 的 程 序 只 能 在 对 应 的 嵌 入 式 硬 件 平 台 上 运 行, 那 么 如 何 把 开 发 板 上 的 信 息 显 示 给 开 发 人 员 呢? 最 常 用 的 就 是 通 过 串 口 线 输 出 到 宿 主 机 的 显 示 器 上, 这 样, 开 发 人 员 就 可 以 看 到 系 统 的 运 行 情 况 了 在 Windows 和 Linux 中 都 有 不 少 串 口 通 信 软 件, 可 以 很 方 便 地 对 串 口 进 行 配 置, 其 中 最 主 要 的 配 置 参 数 是 波 特 率 数 据 位 停 止 位 奇 偶 校 验 位 和 数 据 流 控 制 位 等, 但 是 它 们 一 定 要 根 据 实 际 情 况 进 行 相 应 配 置 下 面 介 绍 Windows 中 典 型 的 串 口 通 信 软 件 超 级 终 端 和 在 Linux 下 的 minicom 1. 超 级 终 端 首 先, 打 开 Windows 下 的 开 始 附 件 通 讯 超 级 终 端, 这 时 会 出 现 如 图 5.1 所 示 的 新 建 超 级 终 端 界 面, 在 名 称 处 可 随 意 输 入 该 连 接 的 名 称 图 5.1 新 建 超 级 终 端 界 面 接 下 来, 将 连 接 时 使 用 的 方 式 改 为 COM1, 即 通 过 串 口 1, 如 图 5.2 所 示 接 下 来 就 到 了 最 关 键 的 一 步 设 置 串 口 连 接 参 数 要 注 意, 每 块 开 发 板 的 连 接 参 数 有 可 能 会 有 差 异, 其 3
118 中 的 具 体 数 据 在 开 发 商 提 供 的 用 户 手 册 中 会 有 说 明 如 优 龙 的 这 款 FS2410 采 用 的 是 波 特 率 为 , 数 据 位 数 为 8, 无 奇 偶 校 验 位, 停 止 位 数 为 1, 无 硬 件 流 控, 其 对 应 配 置 如 图 5.3 所 示 图 5.2 选 择 连 接 时 使 用 方 式 图 5.3 配 置 串 口 相 关 参 数 这 样, 就 基 本 完 成 了 配 置, 最 后 一 步 单 击 确 定 按 钮 就 可 以 了 这 时, 读 者 可 以 把 开 发 板 的 串 口 线 和 PC 机 相 连, 若 配 置 正 确, 在 开 发 板 上 电 后, 在 超 级 终 端 的 窗 口 里 应 能 显 示 类 似 于 图 5.4 的 串 口 信 息 图 5.4 在 超 级 终 端 上 显 示 信 息 要 分 清 开 发 板 上 的 串 口 1 串 口 2, 如 在 优 龙 的 开 发 板 上 标 有 UART1 UATR2, 否 则 串 口 无 法 打 印 出 信 息 2.minicom minicom 是 Linux 下 串 口 通 信 的 软 件, 它 的 使 用 完 全 依 靠 键 盘 的 操 作, 虽 然 没 有 超 级 终 端 那 么 易 用, 但 是 使 用 习 惯 之 后 读 者 将 会 体 会 到 它 的 高 效 与 便 利 下 面 主 要 讲 解 如 何 对 minicom 进 行 串 口 参 数 的 配 置 首 先 在 命 令 行 中 键 入 minicom, 这 就 启 动 了 minicom 软 件 minicom 在 启 动 时 默 认 会 进 行 初 始 化 配 置, 如 图 5.5 所 示 可 以 通 过 minicom -s 命 令 进 行 minicom 的 配 置 图 5.5 minicom 启 动 在 minicom 的 使 用 中, 经 常 会 遇 到 3 个 键 的 操 作, 如 CTRL-A Z, 这 表 示 先 同 时 按 下 CTRL 和 A, 然 后 松 开 这 两 个 键 再 按 下 Z 4
119 正 如 图 5.5 中 的 提 示, 接 下 来 可 键 入 CTRL-A Z, 来 查 看 minicom 的 帮 助, 如 图 5.6 所 示 按 照 帮 助 所 示, 可 键 入 O ( 代 表 Configure minicom) 来 配 置 minicom 的 串 口 参 数, 当 然 也 可 以 直 接 键 入 CTRL-A O 来 进 行 配 置 如 图 5.7 所 示 图 5.6 minicom 帮 助 图 5.7 minicom 配 置 界 面 在 这 个 配 置 框 中 选 择 Serial port setup 子 项, 进 入 如 图 5.8 所 示 的 配 置 界 面 图 5.8 minicom 串 口 属 性 配 置 界 面 上 面 列 出 的 配 置 是 minicom 启 动 时 的 默 认 配 置, 用 户 可 以 通 过 键 入 每 一 项 前 的 大 写 字 母, 分 别 对 每 一 项 进 行 更 改 图 5.9 所 示 为 在 Change which setting 中 键 入 了 A, 此 时 光 标 转 移 到 第 A 项 的 对 应 处 5
120 图 5.9 minicom 串 口 号 配 置 在 minicom 中 ttys0 对 应 COM1, ttys1 对 应 COM2 接 下 来, 要 对 波 特 率 数 据 位 和 停 止 位 进 行 配 置, 键 入 E, 进 入 如 图 5.10 所 示 的 配 置 界 面 图 5.10 minicom 波 特 率 等 配 置 界 面 在 该 配 置 界 面 中, 可 以 键 入 相 应 波 特 率 停 止 位 等 对 应 的 字 母, 即 可 实 现 配 置, 配 置 完 成 后 按 回 车 键 就 退 出 了 该 配 置 界 面, 在 上 层 界 面 中 显 示 如 图 5.11 所 示 配 置 信 息, 要 注 意 与 图 5.8 进 行 对 比, 确 定 相 应 参 数 是 否 已 被 重 新 配 置 图 5.11 minicom 配 置 完 成 后 界 面 在 确 认 配 置 正 确 后, 可 键 入 回 车 返 回 上 级 配 置 界 面, 并 将 其 保 存 为 默 认 配 置, 如 图 5.12 所 示 之 后, 可 重 新 启 动 minicom 使 刚 才 配 置 生 效, 在 用 串 口 线 将 宿 主 机 和 开 发 板 连 接 之 后, 就 可 在 minicom 中 打 印 出 正 确 的 串 口 信 息, 如 图 5.13 所 示 6
121 图 5.12 minicom 保 存 配 置 信 息 图 5.13 minicom 显 示 串 口 信 息 到 此 为 止, 读 者 已 经 能 将 开 发 板 的 系 统 情 况 通 过 串 口 打 印 到 宿 主 机 上 了, 这 样, 就 能 很 好 地 了 解 硬 件 的 运 行 状 况 通 过 串 口 打 印 信 息 是 一 个 很 常 见 的 手 段, 很 多 其 他 情 况 如 路 由 器 等 也 是 通 过 配 置 串 口 的 波 特 率 这 些 参 数 来 显 示 对 应 信 息 的 下 载 映 像 到 开 发 板 正 如 第 4 章 中 所 述, 嵌 入 式 开 发 的 运 行 环 境 是 目 标 板, 而 开 发 环 境 是 宿 主 机 因 此, 需 要 把 宿 主 机 中 经 过 编 译 之 后 的 可 执 行 文 件 下 载 到 目 标 板 上 要 注 意 的 是, 这 里 所 说 的 下 载 是 下 载 到 目 标 机 中 的 SDRAM 然 后, 用 户 可 以 选 择 直 接 从 SDRAM 中 运 行 或 写 入 到 Flash 中 再 运 行 运 行 常 见 的 下 载 方 式 有 网 络 下 载 ( 如 tftp ftp 等 方 式 ) 串 口 下 载 USB 下 载 等, 本 书 主 要 讲 解 网 络 下 载 中 的 tftp 方 式 和 串 口 下 载 方 式 1.tftp tftp 是 简 单 文 件 传 输 协 议, 它 可 以 看 作 是 一 个 FTP 协 议 的 简 化 版 本, 与 FTP 协 议 相 比, 它 的 最 大 区 别 在 于 没 有 用 户 管 理 的 功 能 它 的 传 输 速 度 快, 可 以 通 过 防 火 墙, 使 用 方 便 快 捷, 因 此 在 嵌 入 式 的 文 件 传 输 中 广 泛 使 用 同 FTP 一 样,tftp 分 为 客 户 端 和 服 务 器 端 两 种 通 常, 首 先 在 宿 主 机 上 开 启 tftp 服 务 器 端 服 务, 设 置 好 tftp 的 根 目 录 内 容 ( 也 就 是 供 客 户 端 访 问 的 根 目 录 ), 接 着, 在 目 标 板 上 开 启 tftp 的 客 户 端 程 序 ( 现 在 很 多 Bootloader 几 乎 都 提 供 该 服 务 ) 这 样, 把 目 标 板 和 宿 主 机 用 直 连 线 相 连 之 后, 就 可 以 通 过 tftp 协 议 传 输 可 执 行 文 件 了 下 面 分 别 讲 述 在 Linux 下 和 Windows 下 的 配 置 方 法 (1)Linux 下 tftp 服 务 配 置 7
122 Linux 下 tftp 的 服 务 器 服 务 是 由 xinetd 所 设 定 的, 默 认 情 况 下 是 处 于 关 闭 状 态 首 先, 要 修 改 tftp 的 配 置 文 件, 开 启 tftp 服 务, 如 下 所 示 : [root@localhost tftpboot]# vim /etc/xinetd.d/tftp # default: off # description: The tftp server serves files using the trivial file transfer\ # protocol. The tftp protocol is often used to boot diskless \ # workstations, download configuration files to network-aware printers,\ # and to start the installation process for some operating systems. service tftp socket_type = dgram /* 使 用 数 据 报 套 接 字 */ protocol = udp /* 使 用 UDP 协 议 */ wait = yes /* 允 许 等 待 */ user = root /* 用 户 */ server = /usr/sbin/in.tftpd /* 服 务 程 序 */ server_args = -s /tftpboot /* 服 务 器 端 的 根 目 录 */ disable = no /* 使 能 */ per_source = 11 cps = flags = IPv4 在 这 里, 主 要 要 将 disable=yes 改 为 no, 另 外, 从 server_args 可 以 看 出,tftp 服 务 器 端 的 默 认 根 目 录 为 /tftpboot, 用 户 如 果 需 要 则 可 以 更 改 为 其 他 目 录 接 下 来, 重 启 xinetd 服 务, 使 刚 才 的 更 改 生 效, 如 下 所 示 : [root@localhost tftpboot]# service xinetd restart ( 或 者 使 用 /etc/init.d/xinetd restart, 而 且 因 发 行 版 的 不 同 具 体 路 径 会 有 所 不 同 ) 关 闭 xinetd: [ 确 定 ] 启 动 xinetd: [ 确 定 ] 接 着, 使 用 命 令 netstat -au 以 确 认 tftp 服 务 是 否 已 经 开 启, 如 下 所 示 : [root@localhost tftpboot]# netstat au grep tftp Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State udp 0 0 *:tftp *:* 这 时, 用 户 就 可 以 把 所 需 要 的 传 输 文 件 放 到 /tftpboot 目 录 下, 这 样, 主 机 上 的 tftp 服 务 就 可 以 建 立 起 来 了 ( 注 意 : 需 要 在 服 务 端 关 闭 防 火 墙 ) 接 下 来, 用 直 连 线 把 目 标 板 和 宿 主 机 连 起 来, 并 且 将 其 配 置 成 一 个 网 段 的 地 址 ( 例 如 两 个 IP 都 可 以 设 置 为 XXX 格 式 ), 再 在 目 标 板 上 启 动 tftp 客 户 端 程 序 ( 注 意 : 不 同 的 Bootloader 所 使 用 的 命 令 可 能 会 不 同, 例 如 : 在 RedBoot 中 使 用 load 命 令 下 载 文 件 是 基 于 tftp 协 议 的 读 者 可 以 查 看 帮 助 来 获 得 确 切 的 命 令 名 及 格 式 ), 如 下 所 示 : =>tftpboot 0x zimage TFTP from server ; our IP address is Filename 'zimage'. Load address: 0x Loading: ################################################################# ############################################################### ############################################# 8
123 done Bytes transferred = (d7544 hex) 可 以 看 到, 此 处 目 标 板 使 用 的 IP 为 , 宿 主 机 使 用 的 IP 为 , 下 载 到 目 标 板 的 地 址 为 0x , 文 件 名 为 zimage (2)Windows 下 tftp 服 务 配 置 在 Windows 下 配 置 tftp 服 务 器 端 需 要 下 载 tftp 服 务 器 软 件, 常 见 的 为 tftpd32 首 先, 单 击 tftpd32 下 方 的 设 置 按 钮, 进 入 设 置 界 面, 如 图 5.14 所 示, 在 这 里, 主 要 配 置 tftp 服 务 器 端 地 址, 也 就 是 宿 主 机 的 地 址 接 下 来, 重 新 启 动 tftpd32 软 件 使 刚 才 的 配 置 生 效, 这 样 服 务 器 端 的 配 置 就 完 成 了, 这 时, 就 可 以 用 直 连 线 连 接 目 标 机 和 宿 主 机, 且 在 目 标 机 上 开 启 tftp 服 务 进 行 文 件 传 输, 这 时,tftp 服 务 器 端 如 图 5.15 和 图 5.16 所 示 图 5.14 tftp 文 件 传 输 图 5.15 tftpd32 配 置 界 面 图 5.16 tftp 服 务 器 端 显 示 情 况 tftp 是 一 个 很 好 的 文 件 传 输 协 议, 它 的 简 单 易 用 吸 引 了 广 大 用 户 但 它 同 时 也 存 在 着 较 大 的 安 全 隐 患 由 于 tftp 不 需 要 用 户 的 身 份 认 证, 因 此 给 了 黑 客 的 可 乘 之 机 2003 年 8 月 12 日 爆 发 的 全 球 冲 击 波 (Worm.Blaster) 病 毒 就 是 模 拟 一 个 tftp 服 务 器, 并 启 动 一 个 攻 击 传 播 线 程, 不 断 地 随 机 生 成 攻 击 地 址 进 行 入 侵 因 此 在 使 用 tftp 时 一 定 要 设 置 一 个 单 独 的 目 录 作 为 tftp 服 务 的 根 目 录, 如 上 文 所 述 的 /tftpboot 等 2. 串 口 下 载 使 用 串 口 下 载 需 要 配 合 特 定 的 下 载 软 件, 如 优 龙 公 司 提 供 的 DNW 软 件 等, 一 般 在 Windows 下 进 行 操 作 虽 然 串 口 下 载 的 速 度 没 有 网 络 下 载 快, 但 由 于 它 很 方 便, 不 需 要 额 外 的 连 线 和 设 置 IP 等 操 作, 因 此 也 广 受 用 户 的 青 睐 下 面 就 以 DNW 软 件 为 例, 介 绍 串 口 下 载 的 方 式 与 其 他 串 口 通 信 的 软 件 一 样, 在 DNW 中 也 要 设 置 波 特 率 端 口 号 等 打 开 Configuration 下 的 Options 界 面, 如 图 5.17 所 示 图 5.17 DNW 配 置 界 面 9
124 在 配 置 完 之 后, 单 击 Serial Port 下 的 Connect, 再 将 开 发 板 上 电, 选 择 串 口 下 载, 接 着 再 在 Serial Port 下 选 择 Transmit, 这 时, 就 可 以 进 行 文 件 传 输 了, 如 图 5.18 和 图 5.19 所 示 这 里 DNW 默 认 串 口 下 载 的 地 址 为 0x 图 5.18 DNW 串 口 下 载 图 图 5.19 DNW 串 口 下 载 情 形 图 编 译 嵌 入 式 Linux 内 核 在 做 完 了 前 期 的 准 备 工 作 之 后, 在 这 一 步, 读 者 就 可 以 编 译 嵌 入 式 Linux 的 内 核 了 在 这 里, 本 书 主 要 介 绍 嵌 入 式 Linux 内 核 的 编 译 过 程, 在 下 一 节 会 进 一 步 介 绍 嵌 入 式 Linux 中 体 系 结 构 相 关 的 内 核 代 码, 读 者 在 此 之 后 就 可 以 尝 试 嵌 入 式 Linux 操 作 系 统 的 移 植 编 译 嵌 入 式 Linux 内 核 都 是 通 过 make 的 不 同 命 令 来 实 现 的, 它 的 执 行 配 置 文 件 就 是 在 第 3 章 中 讲 述 的 makefile Linux 内 核 中 不 同 的 目 录 结 构 里 都 有 相 应 的 makefile, 而 不 同 的 makefile 又 通 过 彼 此 之 间 的 依 赖 关 系 构 成 统 一 的 整 体, 共 同 完 成 建 立 依 赖 关 系 建 立 内 核 等 功 能 内 核 的 编 译 根 据 不 同 的 情 况 会 有 不 同 的 步 骤, 但 其 中 最 主 要 分 别 为 3 个 步 骤 : 内 核 配 置 建 立 依 赖 关 系 创 建 内 核 映 像, 除 此 之 外 还 有 一 些 辅 助 功 能, 如 清 除 文 件 和 依 赖 关 系 等 读 者 在 实 际 编 译 时 若 出 现 错 误 等 情 况, 可 以 考 虑 采 用 其 他 辅 助 功 能 下 面 分 别 讲 述 这 3 步 主 要 的 步 骤 (1) 内 核 配 置 第 一 步 内 核 配 置 中 的 选 项 主 要 是 用 户 用 来 为 目 标 板 选 择 处 理 器 架 构 的 选 项, 不 同 的 处 理 器 架 构 会 有 不 同 的 处 理 器 选 项, 比 如 ARM 就 有 其 专 用 的 选 项 如 Multimedia capabilities port drivers 等 因 此, 在 此 之 前, 必 须 确 保 在 根 目 录 中 makefile 里 ARCH 的 值 已 设 定 了 目 标 板 的 类 型, 如 : ARCH := arm 接 下 来 就 可 以 进 行 内 核 配 置 了, 内 核 支 持 4 种 不 同 的 配 置 方 法, 这 几 种 方 法 只 是 与 用 户 交 互 的 界 面 不 同, 其 实 现 的 功 能 是 一 样 的 每 种 方 法 都 会 通 过 读 入 一 个 默 认 的 配 置 文 件 根 目 录 下.config 隐 藏 文 件 ( 用 户 也 可 以 手 动 修 改 该 文 件, 但 不 推 荐 使 用 ) 当 然, 用 户 也 可 以 自 己 加 载 其 他 配 置 文 件, 也 可 以 将 当 前 的 配 置 保 存 为 其 他 名 字 的 配 置 文 件 这 4 种 方 式 如 下 make config: 基 于 文 本 的 最 为 传 统 的 配 置 界 面, 不 推 荐 使 用 make menuconfig: 基 于 文 本 选 单 的 配 置 界 面, 字 符 终 端 下 推 荐 使 用 make xconfig: 基 于 图 形 窗 口 模 式 的 配 置 界 面,Xwindow 下 推 荐 使 用 make oldconfig: 自 动 读 入.config 配 置 文 件, 并 且 只 要 求 用 户 设 定 前 次 没 有 设 定 过 的 选 项 在 这 4 种 模 式 中,make menuconfig 使 用 最 为 广 泛, 下 面 就 以 make menuconfig 为 例 进 行 讲 解, 如 图 5.20 所 示 10
125 图 5.20 make menuconfig 配 置 界 面 从 该 图 中 可 以 看 出,Linux 内 核 允 许 用 户 对 其 各 类 功 能 逐 项 配 置, 一 共 有 18 类 配 置 选 项, 这 里 就 不 对 这 18 类 配 置 选 项 进 行 一 一 讲 解 了, 需 要 的 时 候 读 者 可 以 参 见 相 关 选 项 的 help 在 menuconfig 的 配 置 界 面 中 是 纯 键 盘 的 操 作, 用 户 可 使 用 上 下 键 和 Tab 键 移 动 光 标 以 进 入 相 关 子 项, 图 5.21 所 示 为 进 入 了 System Type 子 项 的 界 面, 该 子 项 是 一 个 重 要 的 选 项, 主 要 用 来 选 择 处 理 器 的 类 型 图 5.21 System Type 子 项 可 以 看 到, 每 个 选 项 前 都 有 个 括 号, 可 以 通 过 按 空 格 键 或 Y 键 表 示 包 含 该 选 项, 按 N 表 示 不 包 含 该 选 项 另 外, 读 者 可 以 注 意 到, 这 里 的 括 号 有 3 种, 即 中 括 号 尖 括 号 或 圆 括 号 读 者 可 以 用 空 格 键 选 择 相 应 的 选 项 时 可 以 发 现 中 括 号 里 要 么 是 空, 要 么 是 * ; 尖 括 号 里 可 以 是 空, * 和 M, 分 别 表 示 包 含 选 项 不 包 含 选 项 和 编 译 成 模 块 ; 圆 括 号 的 内 容 是 要 求 用 户 在 所 提 供 的 几 个 选 项 中 选 择 一 项 此 外, 要 注 意 2.4 和 2.6 内 核 在 串 口 命 名 上 的 一 个 重 要 区 别, 在 2.4 内 核 中 COM1 对 应 的 是 ttys0, 而 在 2.6 内 核 中 COM1 对 应 ttysac0, 因 此 在 启 动 参 数 的 子 项 要 格 外 注 意, 如 图 5.22 所 示, 否 则 串 口 打 印 不 出 信 息 图 5.22 启 动 参 数 配 置 子 项 11
126 一 般 情 况 下, 使 用 厂 商 提 供 的 默 认 配 置 文 件 都 能 正 常 运 行, 所 以 用 户 初 次 使 用 时 可 以 不 用 对 其 进 行 额 外 的 配 置, 在 以 后 需 要 使 用 其 他 功 能 时 再 另 行 添 加, 这 样 可 以 大 大 减 少 出 错 的 几 率, 有 利 于 错 误 定 位 在 完 成 配 置 之 后, 就 可 以 保 存 退 出, 如 图 5.23 所 示 图 5.23 保 存 退 出 (2) 建 立 依 赖 关 系 由 于 内 核 源 码 树 中 的 大 多 数 文 件 都 与 一 些 头 文 件 有 依 赖 关 系, 因 此 要 顺 利 建 立 内 核, 内 核 源 码 树 中 的 每 个 Makefile 都 必 须 知 道 这 些 依 赖 关 系 建 立 依 赖 关 系 通 常 在 第 一 次 编 译 内 核 的 时 候 ( 或 者 源 码 目 录 树 的 结 构 发 生 变 化 的 时 候 ) 进 行, 它 会 在 内 核 源 码 树 中 每 个 子 目 录 产 生 一 个.depend 文 件 运 行 make dep 即 可 在 编 译 2.6 版 本 的 内 核 通 常 不 需 要 这 个 过 程, 直 接 输 入 make 即 可 (3) 建 立 内 核 建 立 内 核 可 以 使 用 make make zimage 或 make bzimage, 这 里 建 立 的 为 压 缩 的 内 核 映 像 通 常 在 Linux 中, 内 核 映 像 分 为 压 缩 的 内 核 映 像 和 未 压 缩 的 内 核 映 像 其 中, 压 缩 的 内 核 映 像 通 常 名 为 zimage, 位 于 arch/$(arch)/boot 目 录 中 而 未 压 缩 的 内 核 映 像 通 常 名 为 vmlinux, 位 于 源 码 树 的 根 目 录 中 到 这 一 步 就 完 成 了 内 核 源 代 码 的 编 译, 之 后, 读 者 可 以 使 用 上 一 小 节 所 讲 述 的 方 法 把 内 核 压 缩 文 件 下 载 到 开 发 板 上 运 行 在 嵌 入 式 Linux 的 源 码 树 中 通 常 有 以 下 几 个 配 置 文 件,.config autoconf.h config.h, 其 中.config 文 件 是 make menuconfig 默 认 的 配 置 文 件, 位 于 源 码 树 的 根 目 录 中 autoconf.h 和 config.h 是 以 宏 的 形 式 表 示 了 内 核 的 配 置, 当 用 户 使 用 make menuconfig 做 了 一 定 的 更 改 之 后, 系 统 自 动 会 在 autoconf.h 和 config.h 中 做 出 相 应 的 更 改 它 们 位 于 源 码 树 的 /include/linux/ 下 Linux 内 核 源 码 目 录 结 构 Linux 内 核 源 码 的 目 录 结 构 如 图 5.24 所 示 /include 子 目 录 包 含 了 建 立 内 核 代 码 时 所 需 的 大 部 分 包 含 文 件, 这 个 模 块 利 用 其 他 模 块 重 建 内 核 /init 子 目 录 包 含 了 内 核 的 初 始 化 代 码, 这 里 的 代 码 是 内 核 工 作 的 起 始 入 口 /arch 子 目 录 包 含 了 所 有 处 理 器 体 系 结 构 特 定 的 内 核 代 码 如 : arm i386 alpha /drivers 子 目 录 包 含 了 内 核 中 所 有 的 设 备 驱 动 程 序, 如 块 设 备 和 SCSI 设 备 /fs 子 目 录 包 含 了 所 有 的 文 件 系 统 的 代 码, 如 :ext2 vfat 等 /net 子 目 录 包 含 了 内 核 的 网 络 相 关 代 码 图 5.24 Linux 内 核 目 录 结 构 /mm 子 目 录 包 含 了 所 有 内 存 管 理 代 码 12
127 /ipc 子 目 录 包 含 了 进 程 间 通 信 代 码 /kernel 子 目 录 包 含 了 内 核 核 心 代 码 制 作 文 件 系 统 读 者 把 上 一 节 中 所 编 译 的 内 核 压 缩 映 像 下 载 到 开 发 板 后 会 发 现, 系 统 在 进 行 了 一 些 初 始 化 的 工 作 之 后, 并 不 能 正 常 启 动, 如 图 5.25 所 示 可 以 看 到, 系 统 启 动 时 发 生 了 加 载 文 件 系 统 的 错 误 要 记 住, 上 一 节 所 编 译 的 仅 仅 是 内 核, 文 件 系 统 和 内 核 是 完 全 独 立 的 两 个 部 分 读 者 可 以 回 忆 一 下 第 2 章 讲 解 的 Linux 启 动 过 程 的 分 析 ( 嵌 入 式 Linux 是 Linux 裁 减 后 的 版 本, 其 精 髓 部 分 是 一 样 的 ), 其 中 在 head.s 中 就 加 载 了 根 文 件 系 统 因 此, 加 载 根 文 件 系 统 是 Linux 启 动 中 不 可 缺 少 的 一 部 分 本 节 将 讲 解 嵌 入 式 Linux 中 文 件 系 统 的 制 作 方 法 图 5.25 系 统 启 动 错 误 制 作 文 件 系 统 的 方 法 有 很 多, 可 以 从 零 开 始 手 工 制 作, 也 可 以 在 现 有 的 基 础 上 添 加 部 分 内 容 并 加 载 到 目 标 板 上 去 由 于 完 全 手 工 制 作 工 作 量 比 较 大, 而 且 也 很 容 易 出 错, 因 此, 本 节 将 主 要 介 绍 把 现 有 的 文 件 系 统 加 载 到 目 标 板 上 的 方 法, 主 要 包 括 制 作 文 件 系 统 映 像 和 用 NFS 加 载 文 件 系 统 的 方 法 1. 制 作 文 件 系 统 映 像 读 者 已 经 知 道,Linux 支 持 多 种 文 件 系 统, 同 样, 嵌 入 式 Linux 也 支 持 多 种 文 件 系 统 虽 然 在 嵌 入 式 系 统 中, 由 于 资 源 受 限 的 原 因, 它 的 文 件 系 统 和 PC 机 Linux 的 文 件 系 统 有 较 大 的 区 别, 但 是, 它 们 的 总 体 架 构 是 一 样 的, 都 是 采 用 目 录 树 的 结 构 在 嵌 入 式 系 统 中 常 见 的 文 件 系 统 有 cramfs romfs jffs yaffs 等, 这 里 就 以 制 作 cramfs 文 件 系 统 为 例 进 行 讲 解 cramfs 文 件 系 统 是 一 种 经 过 压 缩 的 极 为 简 单 的 只 读 文 件 系 统, 因 此 非 常 适 合 嵌 入 式 系 统 要 注 意 的 是, 不 同 的 文 件 系 统 都 有 相 应 的 制 作 工 具, 但 是 其 主 要 的 原 理 和 制 作 方 法 是 类 似 的 在 嵌 入 式 Linux 中,busybox 是 构 造 文 件 系 统 最 常 用 的 软 件 工 具 包, 它 被 非 常 形 象 地 称 为 嵌 入 式 Linux 系 统 中 的 瑞 士 军 刀, 因 为 它 将 许 多 常 用 的 Linux 命 令 和 工 具 结 合 到 了 一 个 单 独 的 可 执 行 程 序 (busybox) 中 虽 然 与 相 应 的 GNU 工 具 比 较 起 来,busybox 所 提 供 的 功 能 和 参 数 略 少, 但 在 比 较 小 的 系 统 ( 例 如 启 动 盘 ) 或 者 嵌 入 式 系 统 中 已 经 足 够 了 busybox 在 设 计 上 就 充 分 考 虑 了 硬 件 资 源 受 限 的 特 殊 工 作 环 境 它 采 用 一 种 很 巧 妙 的 办 法 减 少 自 己 的 体 积 : 所 有 的 命 令 都 通 过 插 件 的 方 式 集 中 到 一 个 可 执 行 文 件 中, 在 实 际 应 用 过 程 中 通 过 不 同 的 符 号 链 接 来 确 定 到 底 要 执 行 哪 个 操 作 例 如 最 终 生 成 的 可 执 行 文 件 为 busybox, 当 为 它 建 立 一 个 符 号 链 接 ls 的 时 候, 就 可 以 通 过 执 行 这 个 新 命 令 实 现 列 出 目 录 的 功 能 采 用 单 一 执 行 文 件 的 方 式 最 大 限 度 地 共 享 了 程 序 代 码, 甚 至 连 文 件 头 内 存 中 的 程 序 控 制 块 等 其 他 系 统 资 源 都 共 享 了, 对 于 资 源 比 较 紧 张 的 系 统 来 说, 真 是 最 合 适 不 过 了 在 busybox 的 编 译 过 程 中, 可 以 非 常 方 便 地 加 减 它 的 插 件, 最 后 的 符 号 链 接 也 可 以 由 编 译 系 统 自 动 生 成 下 面 用 busybox 构 建 FS2410 开 发 板 的 cramfs 文 件 系 统 首 先 从 busybox 网 站 下 载 busybox 源 码 ( 本 实 例 采 用 的 busybox-1.0.0) 并 解 压, 接 下 来, 根 据 实 际 需 要 进 行 busybox 的 配 置 [root@localhost fs2410]# tar jxvf busybox-1.00.tar.bz2 13
128 fs2410]# cd busybox-1.00 busybox-1.00]# make defconfig /* 首 先 进 行 默 认 配 置 */ busybox-1.00]# make menuconfig 此 时 需 要 设 置 平 台 相 关 的 交 叉 编 译 选 项, 操 作 步 骤 为 : 先 选 中 Build Options 项 的 Do you want to build Busybox with a Cross Complier? 选 项, 然 后 将 Cross Compiler prefix 设 置 为 /usr/local/arm/3.3.2/bin/arm-linux- ( 这 是 在 实 验 主 机 中 的 交 叉 编 译 器 的 安 装 路 径 ) 图 5.26 busybox 配 置 画 面 下 一 步 编 译 并 安 装 busybox [root@localhost busybox-1.00]# make [root@localhost busybox-1.00]# make install PREFIX=/home/david/fs2410/cramfs 其 中,PREFIX 用 于 指 定 安 装 目 录, 如 果 不 设 置 该 选 项, 则 默 认 在 当 前 目 录 下 创 建 _install 目 录 创 建 的 安 装 目 录 的 内 容 如 下 所 示 : [root@localhost cramfs]# ls bin linuxrc sbin usr 从 此 可 知, 使 用 busybox 软 件 包 所 创 建 的 文 件 系 统 还 缺 少 很 多 东 西 下 面 我 们 通 过 创 建 系 统 所 需 要 的 目 录 和 文 件 来 完 善 一 下 文 件 系 统 的 内 容 [root@localhost cramfs]# mkdir mnt root var tmp proc boot etc lib [root@localhost cramfs]# mkdir /var/lock,log,mail,run,spool 如 果 busybox 是 动 态 编 译 的 ( 即 在 配 置 busybox 时 没 选 中 静 态 编 译 ), 则 把 所 需 的 交 叉 编 译 的 动 态 链 接 库 文 件 复 制 到 lib 目 录 中 接 下 来, 需 要 创 建 一 些 重 要 文 件 首 先 要 创 建 /etc/inittab 和 /etc/fstab 文 件 inittab 是 Linux 启 动 之 后 第 一 个 被 访 问 的 脚 本 文 件, 而 fstab 文 件 是 定 义 了 文 件 系 统 的 各 个 挂 接 点, 需 要 与 实 际 的 系 统 相 配 合 接 下 来 要 创 建 用 户 和 用 户 组 文 件 以 上 用 busybox 构 造 了 文 件 系 统 的 内 容, 下 面 要 创 建 cramfs 文 件 系 统 映 像 文 件 制 作 cramfs 映 像 文 件 需 要 用 到 的 工 具 是 mkcramfs 此 时 可 以 采 用 两 种 方 法, 一 种 方 法 是 使 用 我 们 所 构 建 的 文 件 系 统 ( 在 目 录 /home/david/fs2410/cramfs 中 ), 另 一 种 方 法 是 在 已 经 做 好 的 cramfs 映 像 文 件 的 基 础 上 进 行 适 当 的 改 动 下 面 的 示 例 使 用 第 二 种 方 法, 因 为 这 个 方 法 包 含 了 第 一 种 方 法 的 所 有 步 骤 ( 假 设 已 经 做 好 的 映 像 文 件 名 为 fs2410.cramfs ) 首 先 用 mount 命 令 将 映 像 文 件 挂 载 到 一 个 目 录 下, 打 开 该 目 录 并 查 看 其 内 容 [root@localhost fs2410]# mkdir cramfs [root@localhost fs2410]# mount fs2410.cramgs cramfs o loop [root@localhost fs2410]# ls cramfs bin dev etc home lib linuxrc proc Qtopia ramdisk sbin testshell tmp usr var 因 为 cramfs 文 件 系 统 是 只 读 的, 所 以 不 能 在 这 个 挂 载 目 录 下 直 接 进 行 修 改, 因 此 需 要 将 文 件 系 统 中 的 内 容 14
129 复 制 到 另 一 个 目 录 中, 具 体 操 作 如 下 所 示 : [root@localhost fs2410]# mkdir backup_cramfs [root@localhost fs2410]# tar cvf backup.cramfs.tar cramfs/ [root@localhost fs2410]# mv backup.cramfs.tar backup_cramfs/ [root@localhost fs2410]# umount cramfs [root@localhost fs2410]# cd backup_cramfs [root@localhost backup_cramfs]# tar zvf backup.cramfs.tar [root@localhost backup_cramfs]# rm backup.cramfs.tar 此 时 我 们 就 像 用 busybox 所 构 建 的 文 件 系 统 一 样, 可 以 在 backup_cramfs 的 cramfs 子 目 录 中 任 意 进 行 修 改 例 如 可 以 添 加 用 户 自 己 的 程 序 : [root@localhost fs2410]# cp ~/hello backup_cramfs/cramfs/ 在 用 户 的 修 改 工 作 结 束 之 后, 用 下 面 的 命 令 可 以 创 建 cramfs 映 像 文 件 : [root@localhost fs2410]# mkcramfs backup_cramfs/cramfs/ new.cramfs 接 下 来, 就 可 以 将 新 创 建 的 new.cramfs 映 像 文 件 烧 入 到 开 发 板 的 相 应 位 置 了 2.NFS 文 件 系 统 NFS 为 Network File System 的 简 称, 最 早 是 由 Sun 公 司 提 出 发 展 起 来 的, 其 目 的 就 是 让 不 同 的 机 器 不 同 的 操 作 系 统 之 间 通 过 网 络 可 以 彼 此 共 享 文 件 NFS 可 以 让 不 同 的 主 机 通 过 网 络 将 远 端 的 NFS 服 务 器 共 享 出 来 的 文 件 安 装 到 自 己 的 系 统 中, 从 客 户 端 看 来, 使 用 NFS 的 远 端 文 件 就 像 是 使 用 本 地 文 件 一 样 在 嵌 入 式 中 使 用 NFS 会 使 应 用 程 序 的 开 发 变 得 十 分 方 便, 并 且 不 用 反 复 地 烧 写 映 像 文 件 NFS 的 使 用 分 为 服 务 端 和 客 户 端, 其 中 服 务 端 是 提 供 要 共 享 的 文 件, 而 客 户 端 则 通 过 挂 载 ( mount ) 这 一 动 作 来 实 现 对 共 享 文 件 的 访 问 操 作 下 面 主 要 介 绍 NFS 服 务 端 的 使 用 在 嵌 入 式 开 发 中, 通 常 NFS 服 务 端 在 宿 主 机 上 运 行, 而 客 户 端 在 目 标 板 上 运 行 NFS 服 务 端 是 通 过 读 入 它 的 配 置 文 件 /etc/exports 来 决 定 所 共 享 的 文 件 目 录 的 下 面 首 先 讲 解 这 个 配 置 文 件 的 书 写 规 范 在 这 个 配 置 文 件 中, 每 一 行 都 代 表 一 项 要 共 享 的 文 件 目 录 以 及 所 指 定 的 客 户 端 对 它 的 操 作 权 限 客 户 端 可 以 根 据 相 应 的 权 限, 对 该 目 录 下 的 所 有 目 录 文 件 进 行 访 问 配 置 文 件 中 每 一 行 的 格 式 如 下 : [ 共 享 的 目 录 ] [ 客 户 端 主 机 名 称 或 IP] [ 参 数 1, 参 数 2 ] 在 这 里, 主 机 名 或 IP 是 可 供 共 享 的 客 户 端 主 机 名 或 IP, 若 对 所 有 的 IP 都 可 以 访 问, 则 可 用 * 表 示 这 里 的 参 数 有 很 多 种 组 合 方 式, 常 见 的 参 数 如 表 5.1 所 示 表 5.1 常 见 参 数 选 项 参 数 含 义 rw ro no_root_squash sync async 可 读 写 的 权 限 只 读 的 权 限 NFS 客 户 端 分 享 目 录 使 用 者 的 权 限, 即 如 果 客 户 端 使 用 的 是 root 用 户, 那 么 对 于 这 个 共 享 的 目 录 而 言, 该 客 户 端 就 具 有 root 的 权 限 资 料 同 步 写 入 到 内 存 与 硬 盘 当 中 资 料 会 先 暂 存 于 内 存 当 中, 而 非 直 接 写 入 硬 盘 如 在 本 例 中, 配 置 文 件 /etc/exports 的 代 码 如 下 : [root@localhost fs]# cat /etc/exports /root/workplace *(rw,no_root_squash) 在 设 定 完 配 置 文 件 之 后, 需 要 启 动 nfs 服 务 和 portmap 服 务, 这 里 的 portmap 服 务 是 允 许 NFS 客 户 端 查 看 15
130 NFS 服 务 在 用 的 端 口, 在 它 被 激 活 之 后, 就 会 出 现 一 个 端 口 号 为 111 的 sun RPC( 远 端 过 程 调 用 ) 的 服 务 这 是 NFS 服 务 中 必 须 实 现 的 一 项, 因 此, 也 必 须 把 它 开 启 如 下 所 示 : [root@localhost fs]# service portmap start 启 动 portmap: [ 确 定 ] [root@localhost fs]# service nfs start 启 动 NFS 服 务 : [ 确 定 ] 关 掉 NFS 配 额 : [ 确 定 ] 启 动 NFS 守 护 进 程 : [ 确 定 ] 启 动 NFS mountd: [ 确 定 ] 可 以 看 到, 在 启 动 NFS 服 务 的 时 候 启 动 了 mountd 进 程 这 是 NFS 挂 载 服 务, 用 于 处 理 NFS 递 交 过 来 的 客 户 端 请 求 另 外 还 会 激 活 至 少 两 个 以 上 的 系 统 守 护 进 程, 然 后 就 开 始 监 听 客 户 端 的 请 求, 用 cat /var/log/messages 命 令 可 以 查 看 操 作 是 否 成 功 这 样, 就 启 动 了 NFS 的 服 务, 另 外 还 有 两 个 命 令, 可 以 便 于 使 用 NFS 其 中 一 个 是 exportfs, 它 可 以 重 新 扫 描 /etc/exports, 使 用 户 在 修 改 了 /etc/exports 配 置 文 件 之 后 不 需 要 每 次 重 启 NFS 服 务 其 格 式 为 : exportfs [ 选 项 ] exportfs 的 常 见 选 项 如 表 5.2 所 示 表 5.2 常 见 选 项 选 项 参 数 含 义 -a 全 部 挂 载 ( 或 卸 载 )/etc/exports 中 的 设 定 文 件 目 录 -r 重 新 挂 载 /etc/exports 中 的 设 定 文 件 目 录 -u 卸 载 某 一 目 录 -v 在 export 的 时 候, 将 共 享 的 目 录 显 示 到 屏 幕 上 另 外 一 个 是 showmount 命 令, 它 用 于 当 前 的 挂 载 情 况 其 格 式 为 : showmount [ 选 项 ] hostname showmount 的 常 见 选 项 如 表 5.3 所 示 表 5.3 常 见 选 项 选 项 参 数 含 义 -a 在 屏 幕 上 显 示 目 前 主 机 与 客 户 端 所 连 上 来 的 使 用 目 录 状 态 -e 显 示 hostname 中 /etc/exports 里 设 定 的 共 享 目 录 5.2 U-Boot 移 植 Bootloader 介 绍 1. 概 念 简 单 地 说,Bootloader 就 是 在 操 作 系 统 内 核 运 行 之 前 运 行 的 一 段 程 序, 它 类 似 于 PC 机 中 的 BIOS 程 序 通 过 这 段 程 序, 可 以 完 成 硬 件 设 备 的 初 始 化, 并 建 立 内 存 空 间 的 映 射 关 系, 从 而 将 系 统 的 软 硬 件 环 境 带 到 一 个 合 适 的 状 态, 为 最 终 加 载 系 统 内 核 做 好 准 备 通 常,Bootloader 比 较 依 赖 于 硬 件 平 台, 特 别 是 在 嵌 入 式 系 统 中, 更 为 如 此 因 此, 在 嵌 入 式 世 界 里 建 立 16
131 一 个 通 用 的 Bootloader 是 一 件 比 较 困 难 的 事 情 尽 管 如 此, 仍 然 可 以 对 Bootloader 归 纳 出 一 些 通 用 的 概 念 来 指 导 面 向 用 户 定 制 的 Bootloader 设 计 与 实 现 (1)Bootloader 所 支 持 的 CPU 和 嵌 入 式 开 发 板 每 种 不 同 的 CPU 体 系 结 构 都 有 不 同 的 Bootloader 有 些 Bootloader 也 支 持 多 种 体 系 结 构 的 CPU, 如 后 面 要 介 绍 的 U-Boot 支 持 ARM MIPS PowerPC 等 众 多 体 系 结 构 除 了 依 赖 于 CPU 的 体 系 结 构 外,Bootloader 实 际 上 也 依 赖 于 具 体 的 嵌 入 式 板 级 设 备 的 配 置 (2)Bootloader 的 存 储 位 置 系 统 加 电 或 复 位 后, 所 有 的 CPU 通 常 都 从 某 个 由 CPU 制 造 商 预 先 安 排 的 地 址 上 取 指 令 而 基 于 CPU 构 建 的 嵌 入 式 系 统 通 常 都 有 某 种 类 型 的 固 态 存 储 设 备 ( 比 如 ROM EEPROM 或 Flash 等 ) 被 映 射 到 这 个 预 先 安 排 的 地 址 上 因 此 在 系 统 加 电 后,CPU 将 首 先 执 行 Bootloader 程 序 (3)Bootloader 的 启 动 过 程 分 为 单 阶 段 和 多 阶 段 两 种 通 常 多 阶 段 的 Bootloader 能 提 供 更 为 复 杂 的 功 能, 以 及 更 好 的 可 移 植 性 (4)Bootloader 的 操 作 模 式 大 多 数 Bootloader 都 包 含 两 种 不 同 的 操 作 模 式 : 启 动 加 载 模 式 和 下 载 模 式, 这 种 区 别 仅 对 于 开 发 人 员 才 有 意 义 启 动 加 载 模 式 : 这 种 模 式 也 称 为 自 主 模 式 也 就 是 Bootloader 从 目 标 机 上 的 某 个 固 态 存 储 设 备 上 将 操 作 系 统 加 载 到 RAM 中 运 行, 整 个 过 程 并 没 有 用 户 的 介 入 这 种 模 式 是 嵌 入 式 产 品 发 布 时 的 通 用 模 式 下 载 模 式 : 在 这 种 模 式 下, 目 标 机 上 的 Bootloader 将 通 过 串 口 连 接 或 网 络 连 接 等 通 信 手 段 从 主 机 (Host) 下 载 文 件, 比 如 : 下 载 内 核 映 像 和 根 文 件 系 统 映 像 等 从 主 机 下 载 的 文 件 通 常 首 先 被 Bootloader 保 存 到 目 标 机 的 RAM 中, 然 后 再 被 Bootloader 写 入 到 目 标 机 上 的 Flash 类 固 态 存 储 设 备 中 Bootloader 的 这 种 模 式 在 系 统 更 新 时 使 用 工 作 于 这 种 模 式 下 的 Bootloader 通 常 都 会 向 它 的 终 端 用 户 提 供 一 个 简 单 的 命 令 行 接 口 (5)Bootloader 与 主 机 之 间 进 行 文 件 传 输 所 用 的 通 信 设 备 及 协 议, 最 常 见 的 情 况 就 是, 目 标 机 上 的 Bootloader 通 过 串 口 与 主 机 之 间 进 行 文 件 传 输, 传 输 协 议 通 常 是 xmodem/ ymodem/zmodem 等 但 是, 串 口 传 输 的 速 度 是 有 限 的, 因 此 通 过 以 太 网 连 接 并 借 助 TFTP 等 协 议 来 下 载 文 件 是 个 更 好 的 选 择 2.Bootloader 启 动 流 程 Bootloader 的 启 动 流 程 一 般 分 为 两 个 阶 段 :stage1 和 stage2, 下 面 分 别 对 这 两 个 阶 段 进 行 讲 解 (1)Bootloader 的 stage1 在 stage1 中 Bootloader 主 要 完 成 以 下 工 作 基 本 的 硬 件 初 始 化, 包 括 屏 蔽 所 有 的 中 断 设 置 CPU 的 速 度 和 时 钟 频 率 RAM 初 始 化 初 始 化 外 围 设 备 关 闭 CPU 内 部 指 令 和 数 据 cache 等 为 加 载 stage2 准 备 RAM 空 间, 通 常 为 了 获 得 更 快 的 执 行 速 度, 通 常 把 stage2 加 载 到 RAM 空 间 中 来 执 行, 因 此 必 须 为 加 载 Bootloader 的 stage2 准 备 好 一 段 可 用 的 RAM 空 间 复 制 stage2 到 RAM 中, 在 这 里 要 确 定 两 点 :1stage2 的 可 执 行 映 像 在 固 态 存 储 设 备 的 存 放 起 始 地 址 和 终 止 地 址 ;2RAM 空 间 的 起 始 地 址 设 置 堆 栈 指 针 sp, 这 是 为 执 行 stage2 的 C 语 言 代 码 做 好 准 备 (2)Bootloader 的 stage2 在 stage2 中 Bootloader 主 要 完 成 以 下 工 作 用 汇 编 语 言 跳 转 到 main 入 口 函 数 由 于 stage2 的 代 码 通 常 用 C 语 言 来 实 现, 目 的 是 实 现 更 复 杂 的 功 能 和 取 得 更 好 的 代 码 可 读 性 和 可 移 植 性 但 是 与 普 通 C 语 言 应 用 程 序 不 同 的 是, 在 编 译 和 链 接 Bootloader 这 样 的 程 序 时, 不 能 使 用 glibc 库 中 的 任 何 支 持 函 数 初 始 化 本 阶 段 要 使 用 到 的 硬 件 设 备, 包 括 初 始 化 串 口 初 始 化 计 时 器 等 在 初 始 化 这 些 设 备 之 前 可 以 输 出 一 些 打 印 信 息 17
132 检 测 系 统 的 内 存 映 射, 所 谓 内 存 映 射 就 是 指 在 整 个 4GB 物 理 地 址 空 间 中 指 出 哪 些 地 址 范 围 被 分 配 用 来 寻 址 系 统 的 内 存 加 载 内 核 映 像 和 根 文 件 系 统 映 像, 这 里 包 括 规 划 内 存 占 用 的 布 局 和 从 Flash 上 复 制 数 据 设 置 内 核 的 启 动 参 数 U-Boot 概 述 1.U Boot 简 介 U-Boot(UniversalBootloader) 是 遵 循 GPL 条 款 的 开 放 源 码 项 目 它 是 从 FADSROM 8xxROM PPCBOOT 逐 步 发 展 演 化 而 来 其 源 码 目 录 编 译 形 式 与 Linux 内 核 很 相 似, 事 实 上, 不 少 U-Boot 源 码 就 是 相 应 的 Linux 内 核 源 程 序 的 简 化, 尤 其 是 一 些 设 备 的 驱 动 程 序, 这 从 U-Boot 源 码 的 注 释 中 能 体 现 这 一 点 但 是 U-Boot 不 仅 仅 支 持 嵌 入 式 Linux 系 统 的 引 导, 而 且 还 支 持 NetBSD VxWorks QNX RTEMS ARTOS LynxOS 等 嵌 入 式 操 作 系 统 其 目 前 要 支 持 的 目 标 操 作 系 统 是 OpenBSD NetBSD FreeBSD 4.4BSD Linux SVR4 Esix Solaris Irix SCO Dell NCR VxWorks LynxOS psos QNX RTEMS ARTOS 这 是 U-Boot 中 Universal 的 一 层 含 义, 另 外 一 层 含 义 则 是 U-Boot 除 了 支 持 PowerPC 系 列 的 处 理 器 外, 还 能 支 持 MIPS x86 ARM NIOS XScale 等 诸 多 常 用 系 列 的 处 理 器 这 两 个 特 点 正 是 U-Boot 项 目 的 开 发 目 标, 即 支 持 尽 可 能 多 的 嵌 入 式 处 理 器 和 嵌 入 式 操 作 系 统 就 目 前 为 止,U-Boot 对 PowerPC 系 列 处 理 器 支 持 最 为 丰 富, 对 Linux 的 支 持 最 完 善 2.U Boot 特 点 U-Boot 的 特 点 如 下 开 放 源 码 ; 支 持 多 种 嵌 入 式 操 作 系 统 内 核, 如 Linux NetBSD VxWorks QNX RTEMS ARTOS LynxOS; 支 持 多 个 处 理 器 系 列, 如 PowerPC ARM x86 MIPS XScale; 较 高 的 可 靠 性 和 稳 定 性 ; 高 度 灵 活 的 功 能 设 置, 适 合 U-Boot 调 试 操 作 系 统 不 同 引 导 要 求 和 产 品 发 布 等 ; 丰 富 的 设 备 驱 动 源 码, 如 串 口 以 太 网 SDRAM Flash LCD NVRAM EEPROM RTC 键 盘 等 ; 较 为 丰 富 的 开 发 调 试 文 档 与 强 大 的 网 络 技 术 支 持 3.U Boot 主 要 功 能 U-Boot 可 支 持 的 主 要 功 能 列 表 系 统 引 导 : 支 持 NFS 挂 载 RAMDISK( 压 缩 或 非 压 缩 ) 形 式 的 根 文 件 系 统 支 持 NFS 挂 载, 并 从 Flash 中 引 导 压 缩 或 非 压 缩 系 统 内 核 基 本 辅 助 功 能 : 强 大 的 操 作 系 统 接 口 功 能 ; 可 灵 活 设 置 传 递 多 个 关 键 参 数 给 操 作 系 统, 适 合 系 统 在 不 同 开 发 阶 段 的 调 试 要 求 与 产 品 发 布, 尤 其 对 Linux 支 持 最 为 强 劲 ; 支 持 目 标 板 环 境 参 数 多 种 存 储 方 式, 如 Flash NVRAM EEPROM;CRC32 校 验, 可 校 验 Flash 中 内 核 RAMDISK 映 像 文 件 是 否 完 好 设 备 驱 动 : 串 口 SDRAM Flash 以 太 网 LCD NVRAM EEPROM 键 盘 USB PCMCIA PCI RTC 等 驱 动 支 持 上 电 自 检 功 能 :SDRAM Flash 大 小 自 动 检 测 ;SDRAM 故 障 检 测 ;CPU 型 号 18
133 特 殊 功 能 :XIP 内 核 引 导 专 业 始 于 专 注 卓 识 源 于 远 见 U-Boot 源 码 导 读 1.U Boot 源 码 结 构 U-Boot 源 码 结 构 如 图 5.27 所 示 board: 和 一 些 已 有 开 发 板 有 关 的 代 码, 比 如 makefile 和 U-Boot.lds 等 都 和 具 体 开 发 板 的 硬 件 和 地 址 分 配 有 关 common: 与 体 系 结 构 无 关 的 代 码, 用 来 实 现 各 种 命 令 的 C 程 序 cpu: 包 含 CPU 相 关 代 码, 其 中 的 子 目 录 都 是 以 U-BOOT 所 支 持 的 CPU 为 名, 比 如 有 子 目 录 arm926ejs mips mpc8260 和 nios 等, 每 个 特 定 的 子 目 录 中 都 包 括 cpu.c 和 interrupt.c,start.s 等 其 中 cpu.c 初 始 化 CPU 设 置 指 令 Cache 和 数 据 Cache 等 ; interrupt.c 设 置 系 统 的 各 种 中 断 和 异 常, 比 如 快 速 中 断 开 关 中 断 时 钟 中 断 软 件 中 断 预 取 中 止 和 未 定 义 指 令 等 ; 汇 编 代 码 文 件 start.s 是 U-BOOT 启 动 时 执 行 的 第 一 个 文 件, 它 主 要 是 设 置 系 统 图 5.27 U-Boot 源 码 结 构 堆 栈 和 工 作 方 式, 为 进 入 C 程 序 奠 定 基 础 disk:disk 驱 动 的 分 区 相 关 代 码 doc: 文 档 drivers: 通 用 设 备 驱 动 程 序, 比 如 各 种 网 卡 支 持 CFI 的 Flash 串 口 和 USB 总 线 等 fs: 支 持 文 件 系 统 的 文 件,U-BOOT 现 在 支 持 cramfs fat fdos jffs2 和 registerfs 等 include: 头 文 件, 还 有 对 各 种 硬 件 平 台 支 持 的 汇 编 文 件, 系 统 的 配 置 文 件 和 对 文 件 系 统 支 持 的 文 件 net: 与 网 络 有 关 的 代 码,BOOTP 协 议 TFTP 协 议 RARP 协 议 和 NFS 文 件 系 统 的 实 现 lib_arm: 与 ARM 体 系 结 构 相 关 的 代 码 tools: 创 建 S-Record 格 式 文 件 和 U-BOOT images 的 工 具 2.U Boot 重 要 代 码 (1)cpu/arm920t/start.S 这 是 U-Boot 的 起 始 位 置 在 这 个 文 件 中 设 置 了 处 理 器 的 状 态 初 始 化 中 断 向 量 和 内 存 时 序 等, 从 Flash 中 跳 转 到 定 位 好 的 内 存 位 置 执 行.globl_start ( 起 始 位 置 : 中 断 向 量 设 置 ) _start: b reset ldr pc, _undefined_instruction ldr pc, _software_interrupt ldr pc, _prefetch_abort ldr pc, _data_abort ldr pc, _not_used ldr pc, _irq ldr pc, _fiq _undefined_instruction:.word undefined_instruction _software_interrupt:.word software_interrupt 19
134 _prefetch_abort:.word prefetch_abort _data_abort:.word data_abort _not_used:.word not_used _irq:.word irq _fiq:.word fiq 专 业 始 于 专 注 卓 识 源 于 远 见.word _TEXT_BASE: ( 代 码 段 起 始 位 置 ) TEXT_BASE.globl _armboot_start _armboot_start:.word _start /* * These are defined in the board-specific linker script. */.globl _bss_start (BSS 段 起 始 位 置 ) _bss_start:.word bss_start.globl _bss_end _bss_end:.word _end reset: ( 执 行 入 口 ) /* * set the cpu to SVC32 mode; 使 处 理 器 进 入 特 权 模 式 */ mrs r0,cpsr bic r0,r0,#0x1f orr r0,r0,#0xd3 msr cpsr,r0 relocate: ( 代 码 的 重 置 ) /* relocate U-Boot to RAM */ adr r0, _start /* r0 <- current position of code */ ldr r1, _TEXT_BASE /* test if we run from flash or RAM */ cmp r0, r1 /* don't reloc during debug */ beq stack_setup ldr r2, _armboot_start ldr r3, _bss_start sub r2, r3, r2 /* r2 <- size of armboot */ add r2, r0, r2 /* r2 <- source end address */ copy_loop: ( 拷 贝 过 程 ) ldmia r0!, r3-r10 /* copy from source address [r0] */ stmia r1!, r3-r10 /* copy to target address [r1] */ cmp r0, r2 /* until source end addreee [r2] */ ble copy_loop 20
135 /* Set up the stack; 设 置 堆 栈 */ stack_setup: ldr r0, _TEXT_BASE /* upper 128 KiB: relocated uboot */ sub r0, r0, #CFG_MALLOC_LEN /* malloc area */ sub r0, r0, #CFG_GBL_DATA_SIZE /* bdinfo */ clear_bss: ( 清 空 BSS 段 ) ldr r0, _bss_start /* find start of bss segment */ ldr r1, _bss_end /* stop here */ mov r2, #0x /* clear */ clbss_l:str r2, [r0] /* clear loop... */ add r0, r0, #4 cmp bne ldr r0, r1 clbss_l pc, _start_armboot _start_armboot:.word start_armboot (2)interrupts.c 这 个 文 件 是 处 理 中 断 的, 如 打 开 和 关 闭 中 断 等 #ifdef CONFIG_USE_IRQ /* enable IRQ interrupts; 中 断 使 能 函 数 */ void enable_interrupts (void) unsigned long temp; asm volatile ("mrs %0, cpsr\n" "bic %0, %0, #0x80\n" "msr cpsr_c, %0" : "=r" (temp) : : "memory"); /* * disable IRQ/FIQ interrupts; 中 断 屏 蔽 函 数 * returns true if interrupts had been enabled before we disabled them */ int disable_interrupts (void) unsigned long old,temp; asm volatile ("mrs %0, cpsr\n" "orr %1, %0, #0xc0\n" "msr cpsr_c, %1" : "=r" (old), "=r" (temp) : : "memory"); 21
136 return (old & 0x80) == 0; #endif void show_regs (struct pt_regs *regs) unsigned long flags; const char *processor_modes[] = "USER_26", "FIQ_26", "IRQ_26", "SVC_26", "UK4_26", "UK5_26", "UK6_26", "UK7_26", "UK8_26", "UK9_26", "UK10_26", "UK11_26", "UK12_26", "UK13_26", "UK14_26", "UK15_26", "USER_32", "FIQ_32", "IRQ_32", "SVC_32", "UK4_32", "UK5_32", "UK6_32", "ABT_32", "UK8_32", "UK9_32", "UK10_32", "UND_32", "UK12_32", "UK13_32", "UK14_32", "SYS_32", ; /* 在 U-Boot 启 动 模 式 下, 在 原 则 上 要 禁 止 中 断 处 理, 所 以 如 果 发 生 中 断, 当 作 出 错 处 理 */ void do_fiq (struct pt_regs *pt_regs) printf ("fast interrupt request\n"); show_regs (pt_regs); bad_mode (); void do_irq (struct pt_regs *pt_regs) printf ("interrupt request\n"); show_regs (pt_regs); bad_mode (); (3)cpu.c 这 个 文 件 是 对 处 理 器 进 行 操 作, 如 下 所 示 : int cpu_init (void) /* * setup up stacks if necessary; 设 置 需 要 的 堆 栈 */ #ifdef CONFIG_USE_IRQ DECLARE_GLOBAL_DATA_PTR; IRQ_STACK_START=_armboot_start - CFG_MALLOC_LEN - CFG_GBL_DATA_SIZE - 4; FIQ_STACK_START = IRQ_STACK_START - CONFIG_STACKSIZE_IRQ; #endif return 0; 22
137 int cleanup_before_linux (void) /* 准 备 加 载 linux */ /* * this function is called just before we call linux * it prepares the processor for linux * * we turn off caches etc... */ unsigned long i; disable_interrupts (); 专 业 始 于 专 注 卓 识 源 于 远 见 /* turn off I/D-cache: 关 闭 cache */ asm ("mrc p15, 0, %0, c1, c0, 0":"=r" (i)); i &= ~(C1_DC C1_IC); asm ("mcr p15, 0, %0, c1, c0, 0": :"r" (i)); /* flush I/D-cache */ i = 0; asm ("mcr p15, 0, %0, c7, c7, 0": :"r" (i)); return (0); OUTPUT_ARCH(arm) ENTRY(_start) SECTIONS. = 0x ;. = ALIGN(4);.text : cpu/arm920t/start.o (.text) *(.text). = ALIGN(4);.rodata : *(.rodata). = ALIGN(4);.data : *(.data). = ALIGN(4);.got : *(.got) u_boot_cmd_start =.;.u_boot_cmd : *(.u_boot_cmd) u_boot_cmd_end =.; 23
138 . = ALIGN(4); bss_start =.;.bss : *(.bss) _end =.; (4)memsetup.S 这 个 文 件 是 用 于 配 置 开 发 板 参 数 的, 如 下 所 示 : /* memsetup.c */ /* memory control configuration */ /* make r0 relative the current location so that it */ /* reads SMRDATA out of FLASH rather than memory! */ 0: ldr r0, =SMRDATA ldr r1, _TEXT_BASE sub r0, r0, r1 ldr r1, =BWSCON /* Bus Width Status Controller */ add r2, r0, #52 ldr r3, [r0], #4 str r3, [r1], #4 cmp r2, r0 bne 0b /* everything is fine now */ mov pc, lr.ltorg U-Boot 移 植 主 要 步 骤 (1) 建 立 自 己 的 开 发 板 类 型 阅 读 makefile 文 件, 在 makefile 文 件 中 添 加 两 行, 如 下 所 示 : fs2410_config: $(@:_config=) arm arm920t fs2410 其 中 arm 为 表 示 处 理 器 体 系 结 构 的 种 类, arm920t 表 示 处 理 器 体 系 结 构 的 名 称, fs2410 为 主 板 名 称 在 board 目 录 中 建 立 fs2410 目 录, 并 将 smdk2410 目 录 中 的 内 容 (cp a smdk2410/* fs2410) 复 制 到 该 目 录 中 在 include/configs/ 目 录 下 将 smdk2410.h 复 制 到 (cp smdk2410.h fs2410.h) 修 改 ARM 编 译 器 的 目 录 名 及 前 缀 ( 都 要 改 成 以 fs2410 开 头 ) 完 成 之 后, 可 以 测 试 配 置 $ make fs2410_config;make (2) 修 改 程 序 链 接 地 址 在 board/s3c2410 中 有 一 个 config.mk 文 件, 它 是 用 于 设 置 程 序 链 接 的 起 始 地 址, 因 为 会 在 U-Boot 中 增 加 功 能, 所 以 留 下 6MB 的 空 间, 修 改 33F80000 为 33A00000 为 了 以 后 能 用 U-Boot 的 go 命 令 执 行 修 改 过 的 用 loadb 或 tftp 下 载 的 U-Boot, 需 要 在 board/ s3c2410 的 24
139 memsetup.s 中 标 记 符 0: 上 加 入 5 句 : mov r3, pc ldr r4, =0x3FFF0000 and r3, r3, r4 ( 以 上 3 句 得 到 实 际 代 码 启 动 的 内 存 地 址 ) aad r0, r0, r3 ( 用 go 命 令 调 试 u-boot 时, 启 动 地 址 在 RAM) add r2, r2, r3 ( 把 初 始 化 内 存 信 息 的 地 址, 加 上 实 际 启 动 地 址 ) (3) 将 中 断 禁 止 的 部 分 应 该 改 为 如 下 所 示 (/cpu/arm920t/start.s): # if defined(config_s3c2410) ldr ldr str # endif r1, =0x7ff r0, =INTSUBMSK r1, [r0] (4) 因 为 在 fs2410 开 发 板 启 动 时 是 直 接 从 Nand Flash 加 载 代 码, 所 以 启 动 代 码 应 该 改 成 如 下 所 示 (/cpu/arm920t/start.s): reset NAND mov r1, #NAND_CTL_BASE ldr r2, initial value str r2, [r1, #onfconf] ldr r2, [r1, #onfconf] bic r2, r2, enable chip str r2, [r1, #onfconf] mov r2, RESET command strb r2, [r1, #onfcmd] mov r3, wait nand1: add r3, r3, #0x1 cmp r3, #0xa blt nand1 nand2: ldr r2, [r1, wait ready tst r2, #0x1 beq nand2 ldr r2, [r1, #onfconf] orr r2, r2, disable chip str r2, [r1, get read to call C functions (for nand_read()) ldr sp, setup stack pointer mov fp, no previous frame, so copy U-Boot to RAM ldr r0, =TEXT_BASE mov r1, #0x0 mov r2, #0x20000 bl nand_read_ll tst r0, #0x0 25
140 beq ok_nand_read bad_nand_read: loop2: b infinite loop verify mov r0, #0 ldr r1, =TEXT_BASE mov r2, 4 bytes * 1024 = 4K-bytes go_next: ldr r3, [r0], #4 ldr r4, [r1], #4 teq r3, r4 bne notmatch subs r2, r2, #4 beq stack_setup bne go_next notmatch: loop3: b infinite loop 专 业 始 于 专 注 卓 识 源 于 远 见 在 _start_armboot:.word start_armboot 后 加 入 :.align 2 DW_STACK_START:.word STACK_BASE+STACK_SIZE-4 (5) 修 改 内 存 配 置 (board/fs2410/lowlevel_init.s) #define BWSCON 0x #define PLD_BASE 0x2C #define SDRAM_REG 0x2C /* BWSCON */ #define DW8 #define DW16 #define DW32 #define WAIT #define UBLB (0x0) (0x1) (0x2) (0x1<<2) (0x1<<3) /* BANKSIZE */ #define BURST_EN (0x1<<7) #define B1_BWSCON (DW16 + WAIT) #define B2_BWSCON (DW32) #define B3_BWSCON (DW32) #define B4_BWSCON (DW16 + WAIT + UBLB) #define B5_BWSCON (DW8 + UBLB) #define B6_BWSCON (DW32) #define B7_BWSCON (DW32) /* BANK0CON */ #define B0_Tacs 0x0 /* 0clk */ 26
141 #define B0_Tcos 0x1 /* 1clk */ #define B0_Tacc 0x7 /* 14clk */ #define B0_Tcoh 0x0 /* 0clk */ #define B0_Tah 0x0 /* 0clk */ #define B0_Tacp 0x0 /* page mode is not used */ #define B0_PMC 0x0 /* page mode disabled */ 专 业 始 于 专 注 卓 识 源 于 远 见 /* BANK1CON */ #define B1_Tacs 0x0 /* 0clk */ #define B1_Tcos 0x1 /* 1clk */ #define B1_Tacc 0x7 /* 14clk */ #define B1_Tcoh 0x0 /* 0clk */ #define B1_Tah 0x0 /* 0clk */ #define B1_Tacp 0x0 /* page mode is not used */ #define B1_PMC 0x0 /* page mode disabled */ /* REFRESH parameter */ #define REFEN 0x1 /* Refresh enable */ #define TREFMD 0x0 /* CBR(CAS before RAS)/Auto refresh */ #define Trp 0x0 /* 2clk */ #define Trc 0x3 /* 7clk */ #define Tchr 0x2 /* 3clk */ #define REFCNT 1113 /*period=15.6us,hclk=60mhz, ( *60) */....word ((B6_MT<<15)+(B6_Trcd<<2)+(B6_SCAN)).word ((B7_MT<<15)+(B7_Trcd<<2)+(B7_SCAN)).word ((REFEN<<23)+(TREFMD<<22)+(Trp<<20)+(Trc<<18)+(Tchr<<16)+REFCNT).word 0x32.word 0x30.word 0x30 (6) 加 入 Nand Flash 读 函 数 ( 创 建 board/fs2410/nand_read.c 文 件 ) #include <config.h> #define REGb(x) (*(volatile unsigned char *)(x)) #define REGi(x) (*(volatile unsigned int *)(x)) #define NF_BASE 0x4e #define NFCONF REGi(NF_BASE + 0x0) #define NFCMD REGb(NF_BASE + 0x4) #define NFADDR REGb(NF_BASE + 0x8) #define NFDATA REGb(NF_BASE + 0xc) #define NFSTAT REGb(NF_BASE + 0x10) #define BUSY 1 inline void wait_idle(void) Int i; while(!(nfstat & BUSY)) for (i = 0; i < 10; i++); 27
142 专 业 始 于 专 注 卓 识 源 于 远 见 /* low level nand read function */ int nand_read_ll(unsigned char *buf, unsigned long start_addr, int size) int i, j; if ((start_addr & NAND_BLOCK_MASK) (size & NAND_BLOCK_MASK)) return -1; /* invalid alignment */ /* chip Enable */ NFCONF &= ~0x800; for (i = 0; i < 10; i++); for (i = start_addr; i < (start_addr + size);) /* READ0 */ NFCMD = 0; /* Write Address */ NFADDR = i & 0xff; NFADDR = (i >> 9) & 0xff; NFADDR = (i >> 17) & 0xff; NFADDR = (i >> 25) & 0xff; wait_idle(); for (j = 0; j < NAND_SECTOR_SIZE; j++, i++) *buf = (NFDATA & 0xff); buf++; /* chip Disable */ NFCONF = 0x800; /* chip disable */ return 0; 修 改 board/fs2410/makefile 文 件, 以 增 加 nand_read() 函 数 OBJS := fs2410.o flash.o nand_read.o (7) 加 入 Nand Flash 的 初 始 化 函 数 (board/fs2410/fs2410.c) #if (CONFIG_COMMANDS & CFG_CMD_NAND) typedef enum NFCE_LOW, NFCE_HIGH NFCE_STATE; static inline void NF_Conf(u16 conf) S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); 28
143 nand->nfconf = conf; static inline void NF_Cmd(u8 cmd) S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); nand->nfcmd = cmd; static inline void NF_CmdW(u8 cmd) NF_Cmd(cmd); udelay(1); static inline void NF_Addr(u8 addr) S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); nand->nfaddr = addr; static inline void NF_SetCE(NFCE_STATE s) S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); switch (s) case NFCE_LOW: nand->nfconf &= ~(1<<11); break; case NFCE_HIGH: nand->nfconf = (1<<11); break; static inline void NF_WaitRB(void) S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); while (!(nand->nfstat & (1<<0))); static inline void NF_Write(u8 data) S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); nand->nfdata = data; static inline u8 NF_Read(void) S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); return(nand->nfdata); static inline void NF_Init_ECC(void) 专 业 始 于 专 注 卓 识 源 于 远 见 29
144 S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); nand->nfconf = (1<<12); static inline u32 NF_Read_ECC(void) S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); return(nand->nfecc); #endif /* * NAND flash initialization. */ #if (CONFIG_COMMANDS & CFG_CMD_NAND) extern ulong nand_probe(ulong physadr); static inline void NF_Reset(void) int i; NF_SetCE(NFCE_LOW); NF_Cmd(0xFF); /* reset command */ for (i = 0; i < 10; i++); /* twb = 100ns. */ NF_WaitRB(); /* wait 200~500us; */ NF_SetCE(NFCE_HIGH); static inline void NF_Init(void) #define TACLS 0 #define TWRPH0 4 #define TWRPH1 2 NF_Conf((1<<15) (0<<14) (0<<13) (1<<12) (1<<11) (TACLS<<8) (TWRPH0<<4) (TWRPH1<<0)); /* , 1 xxx, r xxx, r xxx */ /* En 512B 4step ECCR nfce=h tacls twrph0 twrph1 */ NF_Reset(); void nand_init(void) S3C2410_NAND * const nand = S3C2410_GetBase_NAND(); NF_Init(); #ifdef DEBUG printf("nand flash probing at 0x%.8lX\n", (ulong)nand); #endif printf ("%4lu MB\n", nand_probe((ulong)nand) >> 20); #endif (8) 修 改 GPIO 配 置 (board/fs2410/fs2410.c) /* set up the I/O ports */ gpio->gpacon = 0x007FFFFF; 专 业 始 于 专 注 卓 识 源 于 远 见 30
145 gpio->gpbcon = 0x002AAAAA; gpio->gpbup = 0x000002BF; gpio->gpccon = 0xAAAAAAAA; gpio->gpcup = 0x0000FFFF; gpio->gpdcon = 0xAAAAAAAA; gpio->gpdup = 0x0000FFFF; gpio->gpecon = 0xAAAAAAAA; gpio->gpeup = 0x000037F7; gpio->gpfcon = 0x ; gpio->gpfup = 0x ; gpio->gpgcon = 0xFFEAFF5A; gpio->gpgup = 0x0000F0DC; gpio->gphcon = 0x0018AAAA; gpio->gphdat = 0x000001FF; gpio->gphup = 0x (9) 提 供 nand flash 相 关 宏 定 义 (include/configs/fs2410.h), 具 体 参 考 源 码 (10) 加 入 Nand Flash 设 备 (include/linux/mtd/nand_ids.h) static struct nand_flash_dev nand_flash_ids[] =... "Samsung KM29N16000",NAND_MFR_SAMSUNG, 0x64, 21, 1, 2, 0x1000, 0, "Samsung K9F1208U0M", NAND_MFR_SAMSUNG, 0x76, 26, 0, 3, 0x4000, 0, ; "Samsung unknown 4Mb", NAND_MFR_SAMSUNG, 0x6b, 22, 0, 2, 0x2000, 0,... NULL, (11) 设 置 Nand Flash 环 境 (common/env_nand.c) int nand_legacy_rw (struct nand_chip* nand, int cmd, size_t start, size_t len, size_t * retlen, u_char * buf); extern struct nand_chip nand_dev_desc[cfg_max_nand_device]; extern int nand_legacy_erase(struct nand_chip *nand, size_t ofs, size_t len, int clean); /* info for NAND chips, defined in drivers/nand/nand.c */ extern nand_info_t nand_info[cfg_max_nand_device];... #else /*! CFG_ENV_OFFSET_REDUND */ int saveenv(void) ulong total; int ret = 0; puts ("Erasing Nand..."); if (nand_legacy_erase(nand_dev_desc + 0, CFG_ENV_OFFSET, CFG_ENV_SIZE, 0)) return 1; 31
146 puts ("Writing to Nand... "); total = CFG_ENV_SIZE; 专 业 始 于 专 注 卓 识 源 于 远 见 ret = nand_legacy_rw(nand_dev_desc + 0, 0x00 0x02, CFG_ENV_OFFSET, CFG_ENV_SIZE, &total, (u_char*)env_ptr); if (ret total!= CFG_ENV_SIZE) return 1; puts ("done\n"); return ret;... #else /*! CFG_ENV_OFFSET_REDUND */ void env_relocate_spec (void) #if!defined(env_is_embedded) ulong total; int ret; total = CFG_ENV_SIZE; ret = nand_legacy_rw(nand_dev_desc + 0, 0x01 0x02, CFG_ENV_OFFSET,... CFG_ENV_SIZE, &total, (u_char*)env_ptr); 5.3 实 验 内 容 创 建 Linux 内 核 和 文 件 系 统 1. 实 验 目 的 通 过 移 植 Linux 内 核, 熟 悉 嵌 入 式 开 发 环 境 的 搭 建 和 Linux 内 核 的 编 译 配 置 通 过 创 建 文 件 系 统, 熟 练 掌 握 使 用 busybox 创 建 文 件 系 统 和 如 何 创 建 文 件 系 统 映 像 文 件 由 于 具 体 步 骤 在 前 面 已 经 详 细 讲 解 过 了, 因 此, 相 关 部 分 请 读 者 查 阅 本 章 前 面 内 容 2. 实 验 内 容 首 先 在 Linux 环 境 下 配 置 minicom, 使 之 能 够 正 常 显 示 串 口 的 信 息 然 后 再 编 译 配 置 Linux 2.6 内 核, 并 下 载 到 开 发 板 接 下 来, 用 busybox 创 建 文 件 系 统 并 完 善 所 缺 的 内 容 用 mkcramfs 创 建 cramfs 映 像 文 件 并 下 载 到 开 发 板 在 Linux 内 核 和 文 件 系 统 加 载 完 了 之 后, 在 开 发 板 上 启 动 Linux 3. 实 验 步 骤 (1) 设 置 minicom, 按 键 CTRL-A O 配 置 相 应 参 数 (2) 连 接 开 发 板 与 主 机, 查 看 串 口 是 否 有 正 确 输 出 (3) 查 看 Linux 内 核 顶 层 的 Makefile, 确 定 相 关 参 数 是 否 正 确 (4) 运 行 make menuconfig, 进 行 相 应 配 置 (5) 运 行 make dep (6) 运 行 make zimage (7) 将 生 成 的 内 核 映 像 通 过 tftp 或 串 口 下 载 到 开 发 板 中 (8) 用 busybox 创 建 文 件 系 统 (9) 创 建 添 加 和 修 改 所 缺 的 目 录 和 文 件 (10) 在 文 件 系 统 添 加 用 户 程 序 或 者 删 除 不 需 要 的 文 件 32
147 (11) 用 mkcramfs 创 建 文 件 系 统 映 像 文 件 (12) 将 生 成 的 文 件 系 统 映 像 通 过 tftp 或 串 口 下 载 到 开 发 板 中 (13) 在 开 发 板 上 启 动 Linux 4. 实 验 结 果 开 发 板 能 够 正 确 运 行 新 生 成 的 内 核 映 像 5.4 本 章 小 结 本 章 详 细 讲 解 了 嵌 入 式 Linux 开 发 环 境 的 搭 建, 包 括 minicom 和 超 级 终 端 的 配 置, 如 何 创 建 并 下 载 映 像 文 件 到 开 发 板, 如 何 移 植 嵌 入 式 Linux 内 核 以 及 如 何 移 植 U-Boot 这 些 都 是 操 作 性 很 强 的 内 容, 而 且 在 嵌 入 式 的 开 发 中 也 是 必 不 可 少 的 一 部 分, 因 此 希 望 读 者 切 实 掌 握 5.5 思 考 与 练 习 1. 适 当 更 改 Linux 内 核 配 置, 再 进 行 编 译 下 载 查 看 结 果 2. 配 置 NFS 服 务 3. 深 入 研 究 一 下 U-Boot 源 码 以 及 移 植 的 具 体 步 骤 联 系 方 式 集 团 官 网 : 嵌 入 式 学 院 : 移 动 互 联 网 学 院 : 企 业 学 院 : 物 联 网 学 院 : 研 发 中 心 :dev.hqyj.com 集 团 总 部 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 内 华 清 远 见 教 育 集 团 北 京 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 区, 电 话 : /5 上 海 地 址 : 上 海 市 徐 汇 区 漕 溪 路 银 海 大 厦 A 座 8 层, 电 话 : 深 圳 地 址 : 深 圳 市 龙 华 新 区 人 民 北 路 美 丽 AAA 大 厦 15 层, 电 话 : 成 都 地 址 : 成 都 市 武 侯 区 科 华 北 路 99 号 科 华 大 厦 6 层, 电 话 : 南 京 地 址 : 南 京 市 白 下 区 汉 中 路 185 号 鸿 运 大 厦 10 层, 电 话 : 武 汉 地 址 : 武 汉 市 工 程 大 学 卓 刀 泉 校 区 科 技 孵 化 器 大 楼 8 层, 电 话 : 西 安 地 址 : 西 安 市 高 新 区 高 新 一 路 12 号 创 业 大 厦 D3 楼 5 层, 电 话 :
148 嵌 入 式 LINUX 应 用 程 序 开 发 标 准 教 程 作 者 : 华 清 远 见 第 6 章 文 件 I/O 编 程 掌 握 Linux 中 系 统 调 用 的 基 本 概 念 掌 握 Linux 中 用 户 编 程 接 口 (API) 及 系 统 命 令 的 相 互 关 系 掌 握 文 件 描 述 符 的 概 念 掌 握 Linux 下 文 件 相 关 的 不 带 缓 存 I/O 函 数 的 使 用 掌 握 Linux 下 设 备 文 件 读 写 方 法 掌 握 Linux 中 对 串 口 的 操 作 熟 悉 Linux 中 标 准 文 件 I/O 函 数 的 使 用
149 6.1 Linux 系 统 调 用 及 用 户 编 程 接 口 (API) 专 业 始 于 专 注 卓 识 源 于 远 见 由 于 本 章 是 讲 解 Linux 编 程 开 发 的 第 1 章, 因 此 希 望 读 者 更 加 明 确 Linux 系 统 调 用 和 用 户 编 程 接 口 (API) 的 概 念 在 了 解 了 这 些 之 后, 会 对 Linux 以 及 Linux 的 应 用 编 程 有 更 深 入 的 理 解 系 统 调 用 所 谓 系 统 调 用 是 指 操 作 系 统 提 供 给 用 户 程 序 调 用 的 一 组 特 殊 接 口, 用 户 程 序 可 以 通 过 这 组 特 殊 接 口 来 获 得 操 作 系 统 内 核 提 供 的 服 务 例 如 用 户 可 以 通 过 进 程 控 制 相 关 的 系 统 调 用 来 创 建 进 程 实 现 进 程 调 度 进 程 管 理 等 在 这 里, 为 什 么 用 户 程 序 不 能 直 接 访 问 系 统 内 核 提 供 的 服 务 呢? 这 是 由 于 在 Linux 中, 为 了 更 好 地 保 护 内 核 空 间, 将 程 序 的 运 行 空 间 分 为 内 核 空 间 和 用 户 空 间 ( 也 就 是 常 称 的 内 核 态 和 用 户 态 ), 它 们 分 别 运 行 在 不 同 的 级 别 上, 在 逻 辑 上 是 相 互 隔 离 的 因 此, 用 户 进 程 在 通 常 情 况 下 不 允 许 访 问 内 核 数 据, 也 无 法 使 用 内 核 函 数, 它 们 只 能 在 用 户 空 间 操 作 用 户 数 据, 调 用 用 户 空 间 的 函 数 但 是, 在 有 些 情 况 下, 用 户 空 间 的 进 程 需 要 获 得 一 定 的 系 统 服 务 ( 调 用 内 核 空 间 程 序 ), 这 时 操 作 系 统 就 必 须 利 用 系 统 提 供 给 用 户 的 特 殊 接 口 系 统 调 用 规 定 用 户 进 程 进 入 内 核 空 间 的 具 体 位 置 进 行 系 统 调 用 时, 程 序 运 行 空 间 需 要 从 用 户 空 间 进 入 内 核 空 间, 处 理 完 后 再 返 回 用 户 空 间 Linux 系 统 调 用 部 分 是 非 常 精 简 的 系 统 调 用 ( 只 有 250 个 左 右 ), 它 继 承 了 UNIX 系 统 调 用 中 最 基 本 和 最 有 用 的 部 分 这 些 系 统 调 用 按 照 功 能 逻 辑 大 致 可 分 为 进 程 控 制 进 程 间 通 信 文 件 系 统 控 制 系 统 控 制 存 储 管 理 网 络 管 理 socket 控 制 用 户 管 理 等 几 类 用 户 编 程 接 口 (API) 前 面 讲 到 的 系 统 调 用 并 不 是 直 接 与 程 序 员 进 行 交 互 的, 它 仅 仅 是 一 个 通 过 软 中 断 机 制 向 内 核 提 交 请 求, 以 获 取 内 核 服 务 的 接 口 在 实 际 使 用 中 程 序 员 调 用 的 通 常 是 用 户 编 程 接 口 API, 也 就 是 本 书 后 面 要 讲 到 的 API 函 数 但 并 不 是 所 有 的 函 数 都 一 一 对 应 一 个 系 统 调 用, 有 时, 一 个 API 函 数 会 需 要 几 个 系 统 调 用 来 共 同 完 成 函 数 的 功 能, 甚 至 还 有 一 些 API 函 数 不 需 要 调 用 相 应 的 系 统 调 用 ( 因 此 它 所 完 成 的 不 是 内 核 提 供 的 服 务 ) 在 Linux 中, 用 户 编 程 接 口 (API) 遵 循 了 在 UNIX 中 最 流 行 的 应 用 编 程 界 面 标 准 POSIX 标 准 POSIX 标 准 是 由 IEEE 和 ISO/IEC 共 同 开 发 的 标 准 系 统 该 标 准 基 于 当 时 现 有 的 UNIX 实 践 和 经 验, 描 述 了 操 作 系 统 的 系 统 调 用 编 程 接 口 ( 实 际 上 就 是 API), 用 于 保 证 应 用 程 序 可 以 在 源 代 码 一 级 上 在 多 种 操 作 系 统 上 移 植 运 行 这 些 系 统 调 用 编 程 接 口 主 要 是 通 过 C 库 (libc) 实 现 的 系 统 命 令 以 上 讲 解 了 系 统 调 用 用 户 编 程 接 口 (API) 的 概 念, 分 析 了 它 们 之 间 的 相 互 关 系, 那 么, 读 者 在 第 2 章 中 学 到 的 那 么 多 的 Shell 系 统 命 令 与 它 们 之 间 又 是 怎 样 的 关 系 呢? 系 统 命 令 相 对 API 更 高 了 一 层, 它 实 际 上 是 一 个 可 执 行 程 序, 它 的 内 部 引 用 了 用 户 编 程 接 口 (API) 来 实 现 相 应 的 功 能 它 们 之 间 的 关 系 如 图 6.1 所 示 2
150 6.2 Linux 中 文 件 及 文 件 描 述 符 概 述 图 6.1 系 统 调 用 API 及 系 统 命 令 之 间 的 关 系 在 Linux 中 对 目 录 和 设 备 的 操 作 都 等 同 于 文 件 的 操 作, 因 此, 大 大 简 化 了 系 统 对 不 同 设 备 的 处 理, 提 高 了 效 率 Linux 中 的 文 件 主 要 分 为 4 种 : 普 通 文 件 目 录 文 件 链 接 文 件 和 设 备 文 件 那 么, 内 核 如 何 区 分 和 引 用 特 定 的 文 件 呢? 这 里 用 到 了 一 个 重 要 的 概 念 文 件 描 述 符 对 于 Linux 而 言, 所 有 对 设 备 和 文 件 的 操 作 都 是 使 用 文 件 描 述 符 来 进 行 的 文 件 描 述 符 是 一 个 非 负 的 整 数, 它 是 一 个 索 引 值, 并 指 向 在 内 核 中 每 个 进 程 打 开 文 件 的 记 录 表 当 打 开 一 个 现 存 文 件 或 创 建 一 个 新 文 件 时, 内 核 就 向 进 程 返 回 一 个 文 件 描 述 符 ; 当 需 要 读 写 文 件 时, 也 需 要 把 文 件 描 述 符 作 为 参 数 传 递 给 相 应 的 函 数 通 常, 一 个 进 程 启 动 时, 都 会 打 开 3 个 文 件 : 标 准 输 入 标 准 输 出 和 标 准 出 错 处 理 这 3 个 文 件 分 别 对 应 文 件 描 述 符 为 0 1 和 2( 也 就 是 宏 替 换 STDIN_FILENO STDOUT_FILENO 和 STDERR_FILENO, 鼓 励 读 者 使 用 这 些 宏 替 换 ) 基 于 文 件 描 述 符 的 I/O 操 作 虽 然 不 能 移 植 到 类 Linux 以 外 的 系 统 上 去 ( 如 Windows), 但 它 往 往 是 实 现 某 些 I/O 操 作 的 惟 一 途 径, 如 Linux 中 低 级 文 件 操 作 函 数 多 路 I/O TCP/IP 套 接 字 编 程 接 口 等 同 时, 它 们 也 很 好 地 兼 容 POSIX 标 准, 因 此, 可 以 很 方 便 地 移 植 到 任 何 POSIX 平 台 上 基 于 文 件 描 述 符 的 I/O 操 作 是 Linux 中 最 常 用 的 操 作 之 一, 希 望 读 者 能 够 很 好 地 掌 握 6.3 底 层 文 件 I/O 操 作 本 节 主 要 介 绍 文 件 I/O 操 作 的 系 统 调 用, 主 要 用 到 5 个 函 数 :open() read() write() lseek() 和 close() 这 些 函 数 的 特 点 是 不 带 缓 存, 直 接 对 文 件 ( 包 括 设 备 ) 进 行 读 写 操 作 这 些 函 数 虽 然 不 是 ANSI C 的 组 成 部 分, 但 是 是 POSIX 的 组 成 部 分 基 本 文 件 操 作 (1) 函 数 说 明 open() 函 数 用 于 打 开 或 创 建 文 件, 在 打 开 或 创 建 文 件 时 可 以 指 定 文 件 的 属 性 及 用 户 的 权 限 等 各 种 参 数 close() 函 数 用 于 关 闭 一 个 被 打 开 的 文 件 当 一 个 进 程 终 止 时, 所 有 被 它 打 开 的 文 件 都 由 内 核 自 动 关 闭, 很 多 程 序 都 使 用 这 一 功 能 而 不 显 示 地 关 闭 一 个 文 件 read() 函 数 用 于 将 从 指 定 的 文 件 描 述 符 中 读 出 的 数 据 放 到 缓 存 区 中, 并 返 回 实 际 读 入 的 字 节 数 若 返 回 0, 则 表 示 没 有 数 据 可 读, 即 已 达 到 文 件 尾 读 操 作 从 文 件 的 当 前 指 针 位 置 开 始 当 从 终 端 设 备 文 件 中 读 出 数 据 时, 通 常 一 次 最 多 读 一 行 write() 函 数 用 于 向 打 开 的 文 件 写 数 据, 写 操 作 从 文 件 的 当 前 指 针 位 置 开 始 对 磁 盘 文 件 进 行 写 操 作, 若 磁 盘 已 满 或 超 出 该 文 件 的 长 度, 则 write() 函 数 返 回 失 败 lseek() 函 数 用 于 在 指 定 的 文 件 描 述 符 中 将 文 件 指 针 定 位 到 相 应 的 位 置 它 只 能 用 在 可 定 位 ( 可 随 机 访 问 ) 文 件 操 作 中 管 道 套 接 字 和 大 部 分 字 符 设 备 文 件 是 不 可 定 位 的, 所 以 在 这 些 文 件 的 操 作 中 无 法 使 用 lseek() 调 用 (2) 函 数 格 式 open() 函 数 的 语 法 格 式 如 表 6.1 所 示 表 6.1 所 需 头 文 件 函 数 原 型 open() 函 数 语 法 要 点 #include <sys/types.h> /* 提 供 类 型 pid_t 的 定 义 */ #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags, int perms) 函 数 传 入 值 pathname 被 打 开 的 文 件 名 ( 可 包 括 路 径 名 ) 3
151 O_RDONLY: 以 只 读 方 式 打 开 文 件 O_WRONLY: 以 只 写 方 式 打 开 文 件 O_RDWR: 以 读 写 方 式 打 开 文 件 O_CREAT: 如 果 该 文 件 不 存 在, 就 创 建 一 个 新 的 文 件, 并 用 第 三 个 参 数 为 其 设 置 权 限 flag: 文 件 打 开 的 方 式 O_EXCL: 如 果 使 用 O_CREAT 时 文 件 存 在, 则 可 返 回 错 误 消 息 这 一 参 数 可 测 试 文 件 是 否 存 在 此 时 open 是 原 子 操 作, 防 止 多 个 进 程 同 时 创 建 同 一 个 文 件 O_NOCTTY: 使 用 本 参 数 时, 若 文 件 为 终 端, 那 么 该 终 端 不 会 成 为 调 用 open() 的 那 个 进 程 的 控 制 终 端 O_TRUNC: 若 文 件 已 经 存 在, 那 么 会 删 除 文 件 中 的 全 部 原 有 数 据, 并 且 设 置 文 件 大 小 为 0 O_APPEND: 以 添 加 方 式 打 开 文 件, 在 打 开 文 件 的 同 时, 文 件 指 针 指 向 文 件 的 末 尾, 即 将 写 入 的 数 据 添 加 到 文 件 的 末 尾 perms 被 打 开 文 件 的 存 取 权 限 可 以 用 一 组 宏 定 义 :S_I(R/W/X)(USR/GRP/OTH) 其 中 R/W/X 分 别 表 示 读 / 写 / 执 行 权 限 USR/GRP/OTH 分 别 表 示 文 件 所 有 者 / 文 件 所 属 组 / 其 他 用 户 例 如,S_IRUSR S_IWUSR 表 示 设 置 文 件 所 有 者 的 可 读 可 写 属 性 八 进 制 表 示 法 中 600 也 表 示 同 样 的 权 限 函 数 返 回 值 成 功 : 返 回 文 件 描 述 符 失 败 : 1 在 open() 函 数 中,flag 参 数 可 通 过 组 合 构 成, 但 前 3 个 标 志 常 量 (O_RDONLY O_WRONLY 以 及 O_RDWR) 不 能 相 互 组 合 perms 是 文 件 的 存 取 权 限, 既 可 以 用 宏 定 义 表 示 法, 也 可 以 用 八 进 制 表 示 法 close() 函 数 的 语 法 格 式 表 6.2 所 示 表 6.2 所 需 头 文 件 函 数 原 型 函 数 输 入 值 函 数 返 回 值 #include <unistd.h> int close(int fd) fd: 文 件 描 述 符 0: 成 功 1: 出 错 close() 函 数 语 法 要 点 read() 函 数 的 语 法 格 式 如 表 6.3 所 示 表 6.3 所 需 头 文 件 函 数 原 型 #include <unistd.h> read() 函 数 语 法 要 点 ssize_t read(int fd, void *buf, size_t count) fd: 文 件 描 述 符 函 数 传 入 值 buf: 指 定 存 储 器 读 出 数 据 的 缓 冲 区 count: 指 定 读 出 的 字 节 数 函 数 返 回 值 成 功 : 读 到 的 字 节 数 0: 已 到 达 文 件 尾 1: 出 错 在 读 普 通 文 件 时, 若 读 到 要 求 的 字 节 数 之 前 已 到 达 文 件 的 尾 部, 则 返 回 的 字 节 数 会 小 于 希 望 读 出 的 字 节 数 write() 函 数 的 语 法 格 式 如 表 6.4 所 示 表 6.4 write() 函 数 语 法 要 点 4
152 所 需 头 文 件 函 数 原 型 #include <unistd.h> ssize_t write(int fd, void *buf, size_t count) fd: 文 件 描 述 符 函 数 传 入 值 buf: 指 定 存 储 器 写 入 数 据 的 缓 冲 区 count: 指 定 读 出 的 字 节 数 函 数 返 回 值 成 功 : 已 写 的 字 节 数 1: 出 错 在 写 普 通 文 件 时, 写 操 作 从 文 件 的 当 前 指 针 位 置 开 始 lseek() 函 数 的 语 法 格 式 如 表 6.5 所 示 表 6.5 所 需 头 文 件 函 数 原 型 lseek() 函 数 语 法 要 点 #include <unistd.h> #include <sys/types.h> off_t lseek(int fd, off_t offset, int whence) fd: 文 件 描 述 符 函 数 传 入 值 offset: 偏 移 量, 每 一 读 写 操 作 所 需 要 移 动 的 距 离, 单 位 是 字 节, 可 正 可 负 ( 向 前 移, 向 后 移 ) SEEK_SET: 当 前 位 置 为 文 件 的 开 头, 新 位 置 为 偏 移 量 的 大 小 whence: 当 前 位 置 的 基 点 SEEK_CUR: 当 前 位 置 为 文 件 指 针 的 位 置, 新 位 置 为 当 前 位 置 加 上 偏 移 量 SEEK_END: 当 前 位 置 为 文 件 的 结 尾, 新 位 置 为 文 件 的 大 小 加 上 偏 移 量 的 大 小 函 数 返 回 值 成 功 : 文 件 的 当 前 位 移 1: 出 错 (3) 函 数 使 用 实 例 下 面 实 例 中 的 open() 函 数 带 有 3 个 flag 参 数 :O_CREAT O_TRUNC 和 O_WRONLY, 这 样 就 可 以 对 不 同 的 情 况 指 定 相 应 的 处 理 方 法 另 外, 这 里 对 该 文 件 的 权 限 设 置 为 0600 其 源 码 如 下 所 示 : 下 面 列 出 文 件 基 本 操 作 的 实 例, 基 本 功 能 是 从 一 个 文 件 ( 源 文 件 ) 中 读 取 最 后 10KB 数 据 并 到 另 一 个 文 件 ( 目 标 文 件 ) 在 实 例 中 源 文 件 是 以 只 读 方 式 打 开, 目 标 文 件 是 以 只 写 方 式 打 开 ( 可 以 是 读 写 方 式 ) 若 目 标 文 件 不 存 在, 可 以 创 建 并 设 置 权 限 的 初 始 值 为 644, 即 文 件 所 有 者 可 读 可 写, 文 件 所 属 组 和 其 他 用 户 只 能 读 读 者 需 要 留 意 的 地 方 是 改 变 每 次 读 写 的 缓 存 大 小 ( 实 例 中 为 1KB) 会 怎 样 影 响 运 行 效 率 /* copy_file.c */ #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdlib.h> #include <stdio.h> #define BUFFER_SIZE 1024 /* 每 次 读 写 缓 存 大 小, 影 响 运 行 效 率 */ #define SRC_FILE_NAME "src_file" /* 源 文 件 名 */ #define DEST_FILE_NAME "dest_file" /* 目 标 文 件 名 文 件 名 */ #define OFFSE /* 复 制 的 数 据 大 小 */ int main() 5
153 int src_file, dest_file; unsigned char buff[buffer_size]; int real_read_len; /* 以 只 读 方 式 打 开 源 文 件 */ src_file = open(src_file_name, O_RDONLY); /* 以 只 写 方 式 打 开 目 标 文 件, 若 此 文 件 不 存 在 则 创 建 该 文 件, 访 问 权 限 值 为 644 */ dest_file = open(dest_file_name, O_WRONLY O_CREAT, S_IRUSR S_IWUSR S_IRGRP S_IROTH); if (src_file < 0 dest_file < 0) printf("open file error\n"); exit(1); /* 将 源 文 件 的 读 写 指 针 移 到 最 后 10KB 的 起 始 位 置 */ lseek(src_file, -OFFSET, SEEK_END); /* 读 取 源 文 件 的 最 后 10KB 数 据 并 写 到 目 标 文 件 中, 每 次 读 写 1KB */ while ((real_read_len = read(src_file, buff, sizeof(buff))) > 0) write(dest_file, buff, real_read_len); close(dest_file); close(src_file); return 0; $./copy_file $ ls -lh dest_file -rw-r--r-- 1 david root 10K 14:06 dest_file open() 函 数 返 回 的 文 件 描 述 符 一 定 是 最 小 的 未 用 文 件 描 述 符 由 于 一 个 进 程 在 启 动 时 自 动 打 开 了 三 个 文 件 描 述 符, 因 此, 该 文 件 运 行 结 果 中 返 回 的 文 件 描 述 符 为 3 读 者 可 以 尝 试 在 调 用 open() 函 数 之 前, 加 一 句 close(0), 则 此 后 在 调 用 open() 函 数 时 返 回 的 文 件 描 述 符 为 0( 若 关 闭 文 件 描 述 符 1, 则 在 程 序 执 行 时 会 由 于 没 有 标 准 输 出 文 件 而 无 法 输 出 ) 文 件 锁 (1)fcntl() 函 数 说 明 前 面 的 这 5 个 基 本 函 数 实 现 了 文 件 的 打 开 读 写 等 基 本 操 作, 本 小 节 将 讨 论 的 是, 在 文 件 已 经 共 享 的 情 况 下 如 何 操 作, 也 就 是 当 多 个 用 户 共 同 使 用 操 作 一 个 文 件 的 情 况, 这 时,Linux 通 常 采 用 的 方 法 是 给 文 件 上 锁, 来 避 免 共 享 的 资 源 产 生 竞 争 的 状 态 文 件 锁 包 括 建 议 性 锁 和 强 制 性 锁 建 议 性 锁 要 求 每 个 上 锁 文 件 的 进 程 都 要 检 查 是 否 有 锁 存 在, 并 6
154 且 尊 重 已 有 的 锁 在 一 般 情 况 下, 内 核 和 系 统 都 不 使 用 建 议 性 锁 强 制 性 锁 是 由 内 核 执 行 的 锁, 当 一 个 文 件 被 上 锁 进 行 写 入 操 作 的 时 候, 内 核 将 阻 止 其 他 任 何 文 件 对 其 进 行 读 写 操 作 采 用 强 制 性 锁 对 性 能 的 影 响 很 大, 每 次 读 写 操 作 都 必 须 检 查 是 否 有 锁 存 在 在 Linux 中, 实 现 文 件 上 锁 的 函 数 有 lockf() 和 fcntl(), 其 中 lockf() 用 于 对 文 件 施 加 建 议 性 锁, 而 fcntl() 不 仅 可 以 施 加 建 议 性 锁, 还 可 以 施 加 强 制 锁 同 时,fcntl() 还 能 对 文 件 的 某 一 记 录 上 锁, 也 就 是 记 录 锁 记 录 锁 又 可 分 为 读 取 锁 和 写 入 锁, 其 中 读 取 锁 又 称 为 共 享 锁, 它 能 够 使 多 个 进 程 都 能 在 文 件 的 同 一 部 分 建 立 读 取 锁 而 写 入 锁 又 称 为 排 斥 锁, 在 任 何 时 刻 只 能 有 一 个 进 程 在 文 件 的 某 个 部 分 上 建 立 写 入 锁 当 然, 在 文 件 的 同 一 部 分 不 能 同 时 建 立 读 取 锁 和 写 入 锁 fcntl() 是 一 个 非 常 通 用 的 函 数, 它 可 以 对 已 打 开 的 文 件 描 述 符 进 行 各 种 操 作, 不 仅 包 括 管 理 文 件 锁, 还 包 括 获 得 和 设 置 文 件 描 述 符 和 文 件 描 述 符 标 志 文 件 描 述 符 的 复 制 等 很 多 功 能 在 本 节 中, 主 要 介 绍 建 立 记 录 锁 的 方 法 (2)fcntl() 函 数 格 式 用 于 建 立 记 录 锁 的 fcntl() 函 数 格 式 如 表 6.6 所 示 表 6.6 fcntl() 函 数 语 法 要 点 所 需 头 文 件 函 数 原 型 #include <sys/types.h> #include <unistd.h> #include <fcntl.h> int fcnt1(int fd, int cmd, struct flock *lock) fd: 文 件 描 述 符 F_DUPFD: 复 制 文 件 描 述 符 F_GETFD: 获 得 fd 的 close-on-exec 标 志, 若 标 志 未 设 置, 则 文 件 经 过 exec() 函 数 之 后 仍 保 持 打 开 状 态 F_SETFD: 设 置 close-on-exec 标 志, 该 标 志 由 参 数 arg 的 FD_CLOEXEC 位 决 定 函 数 传 入 值 cmd F_GETFL: 得 到 open 设 置 的 标 志 F_SETFL: 改 变 open 设 置 的 标 志 F_GETLK: 根 据 lock 参 数 值, 决 定 是 否 上 文 件 锁 F_SETLK: 设 置 lock 参 数 值 的 文 件 锁 F_SETLKW: 这 是 F_SETLK 的 阻 塞 版 本 ( 命 令 名 中 的 W 表 示 等 待 (wait)) 在 无 法 获 取 锁 时, 会 进 入 睡 眠 状 态 ; 如 果 可 以 获 取 锁 或 者 捕 捉 到 信 号 则 会 返 回 lock: 结 构 为 flock, 设 置 记 录 锁 的 具 体 状 态 函 数 返 回 值 0: 成 功 1: 出 错 这 里,lock 的 结 构 如 下 所 示 : struct flock short l_type; off_t l_start; 7
155 short l_whence; off_t l_len; pid_t l_pid; lock 结 构 中 每 个 变 量 的 取 值 含 义 如 表 6.7 所 示 表 6.7 lock 结 构 变 量 取 值 F_RDLCK: 读 取 锁 ( 共 享 锁 ) l_type F_WRLCK: 写 入 锁 ( 排 斥 锁 ) F_UNLCK: 解 锁 l_stat 相 对 位 移 量 ( 字 节 ) l_whence: 相 对 位 移 量 的 起 点 ( 同 lseek 的 whence) SEEK_SET: 当 前 位 置 为 文 件 的 开 头, 新 位 置 为 偏 移 量 的 大 小 SEEK_CUR: 当 前 位 置 为 文 件 指 针 的 位 置, 新 位 置 为 当 前 位 置 加 上 偏 移 量 SEEK_END: 当 前 位 置 为 文 件 的 结 尾, 新 位 置 为 文 件 的 大 小 加 上 偏 移 量 的 大 小 l_len 加 锁 区 域 的 长 度 为 加 锁 整 个 文 件, 通 常 的 方 法 是 将 l_start 设 置 为 0,l_whence 设 置 为 SEEK_SET, l_len 设 置 为 0 (3)fcntl() 使 用 实 例 下 面 首 先 给 出 了 使 用 fcntl() 函 数 的 文 件 记 录 锁 功 能 的 代 码 实 现 在 该 代 码 中, 首 先 给 flock 结 构 体 的 对 应 位 赋 予 相 应 的 值 接 着 使 用 两 次 fcntl() 函 数, 分 别 用 于 判 断 文 件 是 否 可 以 上 锁 和 给 相 关 文 件 上 锁, 这 里 用 到 的 cmd 值 分 别 为 F_GETLK 和 F_SETLK( 或 F_SETLKW) 用 F_GETLK 命 令 判 断 是 否 可 以 进 行 flock 结 构 所 描 述 的 锁 操 作 : 若 可 以 进 行, 则 flock 结 构 的 l_type 会 被 设 置 为 F_UNLCK, 其 他 域 不 变 ; 若 不 可 行, 则 l_pid 被 设 置 为 拥 有 文 件 锁 的 进 程 号, 其 他 域 不 变 用 F_SETLK 和 F_SETLKW 命 令 设 置 flock 结 构 所 描 述 的 锁 操 作, 后 者 是 前 者 的 阻 塞 版 文 件 记 录 锁 功 能 的 源 代 码 如 下 所 示 : /* lock_set.c */ int lock_set(int fd, int type) struct flock old_lock, lock; lock.l_whence = SEEK_SET; lock.l_start = 0; lock.l_len = 0; lock.l_type = type; lock.l_pid = -1; /* 判 断 文 件 是 否 可 以 上 锁 */ fcntl(fd, F_GETLK, &lock); if (lock.l_type!= F_UNLCK) /* 判 断 文 件 不 能 上 锁 的 原 因 */ if (lock.l_type == F_RDLCK) /* 该 文 件 已 有 读 取 锁 */ printf("read lock already set by %d\n", lock.l_pid); else if (lock.l_type == F_WRLCK) /* 该 文 件 已 有 写 入 锁 */ 8
156 printf("write lock already set by %d\n", lock.l_pid); /* l_type 可 能 已 被 F_GETLK 修 改 过 */ lock.l_type = type; /* 根 据 不 同 的 type 值 进 行 阻 塞 式 上 锁 或 解 锁 */ if ((fcntl(fd, F_SETLKW, &lock)) < 0) printf("lock failed:type = %d\n", lock.l_type); return 1; switch(lock.l_type) case F_RDLCK: printf("read lock set by %d\n", getpid()); break; case F_WRLCK: printf("write lock set by %d\n", getpid()); break; case F_UNLCK: printf("release lock by %d\n", getpid()); return 1; break; default: break; /* end of switch */ return 0; 下 面 的 实 例 是 文 件 写 入 锁 的 测 试 用 例, 这 里 首 先 创 建 了 一 个 hello 文 件, 之 后 对 其 上 写 入 锁, 最 后 释 放 写 入 锁, 代 码 如 下 所 示 : /* write_lock.c */ #include <unistd.h> #include <sys/file.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <stdlib.h> #include "lock_set.c" 9
157 int main(void) int fd; /* 首 先 打 开 文 件 */ fd = open("hello",o_rdwr O_CREAT, 0644); if(fd < 0) printf("open file error\n"); exit(1); /* 给 文 件 上 写 入 锁 */ lock_set(fd, F_WRLCK); getchar(); /* 给 文 件 解 锁 */ lock_set(fd, F_UNLCK); getchar(); close(fd); exit(0); 为 了 能 够 使 用 多 个 终 端, 更 好 地 显 示 写 入 锁 的 作 用, 本 实 例 主 要 在 PC 机 上 测 试, 读 者 可 将 其 交 叉 编 译, 下 载 到 目 标 板 上 运 行 下 面 是 在 PC 机 上 的 运 行 结 果 为 了 使 程 序 有 较 大 的 灵 活 性, 笔 者 采 用 文 件 上 锁 后 由 用 户 键 入 一 任 意 键 使 程 序 继 续 运 行 建 议 读 者 开 启 两 个 终 端, 并 且 在 两 个 终 端 上 同 时 运 行 该 程 序, 以 达 到 多 个 进 程 操 作 一 个 文 件 的 效 果 在 这 里, 笔 者 首 先 运 行 终 端 一, 请 读 者 注 意 终 端 二 中 的 第 一 句 终 端 一 : 终 端 二 : $./write_lock write lock set by 4994 release lock by 4994 $./write_lock write lock already set by 4994 write lock set by 4997 release lock by 4997 由 此 可 见, 写 入 锁 为 互 斥 锁, 同 一 时 刻 只 能 有 一 个 写 入 锁 存 在 接 下 来 的 程 序 是 文 件 读 取 锁 的 测 试 用 例, 原 理 和 上 面 的 程 序 一 样 /* fcntl_read.c */ #include <unistd.h> #include <sys/file.h> #include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <stdlib.h> #include "lock_set.c" int main(void) int fd; fd = open("hello",o_rdwr O_CREAT, 0644); 10
158 if(fd < 0) printf("open file error\n"); exit(1); /* 给 文 件 上 读 取 锁 */ lock_set(fd, F_RDLCK); getchar(); /* 给 文 件 解 锁 */ lock_set(fd, F_UNLCK); getchar(); close(fd); exit(0); 同 样 开 启 两 个 终 端, 并 首 先 启 动 终 端 一 上 的 程 序, 其 运 行 结 果 如 下 所 示 : 终 端 一 : $./read_lock read lock set by 5009 终 端 二 : release lock by 5009 $./read_lock read lock set by 5010 release lock by 5010 读 者 可 以 将 此 结 果 与 写 入 锁 的 运 行 结 果 相 比 较, 可 以 看 出, 读 取 锁 为 共 享 锁, 当 进 程 5009 已 设 置 读 取 锁 后, 进 程 5010 仍 然 可 以 设 置 读 取 锁 如 果 在 一 个 终 端 上 运 行 设 置 读 取 锁 的 程 序, 则 在 另 一 个 终 端 上 运 行 设 置 写 入 锁 的 程 序, 会 有 什 么 结 果 呢? 多 路 复 用 (1) 函 数 说 明 前 面 的 fcntl() 函 数 解 决 了 文 件 的 共 享 问 题, 接 下 来 该 处 理 I/O 复 用 的 情 况 了 总 的 来 说,I/O 处 理 的 模 型 有 5 种 阻 塞 I/O 模 型 : 在 这 种 模 型 下, 若 所 调 用 的 I/O 函 数 没 有 完 成 相 关 的 功 能, 则 会 使 进 程 挂 起, 直 到 相 关 数 据 到 达 才 会 返 回 对 管 道 设 备 终 端 设 备 和 网 络 设 备 进 行 读 写 时 经 常 会 出 现 这 种 情 况 非 阻 塞 模 型 : 在 这 种 模 型 下, 当 请 求 的 I/O 操 作 不 能 完 成 时, 则 不 让 进 程 睡 眠, 而 且 立 即 返 回 非 阻 塞 I/O 使 用 户 可 以 调 用 不 会 阻 塞 的 I/O 操 作, 如 open() write() 和 read() 如 果 该 操 作 不 能 完 成, 则 会 立 即 返 回 出 错 ( 例 如 : 打 不 开 文 件 ) 或 者 返 回 0( 例 如 : 在 缓 冲 区 中 没 有 数 据 可 以 读 取 或 者 没 有 空 间 可 以 写 入 数 据 ) I/O 多 路 转 接 模 型 : 在 这 种 模 型 下, 如 果 请 求 的 I/O 操 作 阻 塞, 且 它 不 是 真 正 阻 塞 I/O, 而 是 让 其 中 的 一 个 函 数 等 待, 在 这 期 间,I/O 还 能 进 行 其 他 操 作 本 节 要 介 绍 的 select() 和 poll 函 数 () 就 是 属 于 这 种 模 型 信 号 驱 动 I/O 模 型 : 在 这 种 模 型 下, 通 过 安 装 一 个 信 号 处 理 程 序, 系 统 可 以 自 动 捕 获 特 定 信 号 的 到 来, 从 而 启 动 I/O 这 是 由 内 核 通 知 用 户 何 时 可 以 启 动 一 个 I/O 操 作 决 定 的 11
159 异 步 I/O 模 型 : 在 这 种 模 型 下, 当 一 个 描 述 符 已 准 备 好, 可 以 启 动 I/O 时, 进 程 会 通 知 内 核 现 在, 并 不 是 所 有 的 系 统 都 支 持 这 种 模 型 可 以 看 到,select() 和 poll() 的 I/O 多 路 转 接 模 型 是 处 理 I/O 复 用 的 一 个 高 效 的 方 法 它 可 以 具 体 设 置 程 序 中 每 一 个 所 关 心 的 文 件 描 述 符 的 条 件 希 望 等 待 的 时 间 等, 从 select() 和 poll() 函 数 返 回 时, 内 核 会 通 知 用 户 已 准 备 好 的 文 件 描 述 符 的 数 量 已 准 备 好 的 条 件 等 通 过 使 用 select() 和 poll() 函 数 的 返 回 结 果, 就 可 以 调 用 相 应 的 I/O 处 理 函 数 (2) 函 数 格 式 select() 函 数 的 语 法 格 式 如 表 6.8 所 示 表 6.8 所 需 头 文 件 函 数 原 型 #include <sys/types.h> #include <sys/time.h> #include <unistd.h> select() 函 数 语 法 要 点 int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exeptfds, struct timeval *timeout) numfds: 该 参 数 值 为 需 要 监 视 的 文 件 描 述 符 的 最 大 值 加 1 readfds: 由 select() 监 视 的 读 文 件 描 述 符 集 合 函 数 传 入 值 函 数 返 回 值 writefds: 由 select() 监 视 的 写 文 件 描 述 符 集 合 exeptfds: 由 select() 监 视 的 异 常 处 理 文 件 描 述 符 集 合 timeout NULL: 永 远 等 待, 直 到 捕 捉 到 信 号 或 文 件 描 述 符 已 准 备 好 为 止 具 体 值 :struct timeval 类 型 的 指 针, 若 等 待 了 timeout 时 间 还 没 有 检 测 到 任 何 文 件 描 符 准 备 好, 就 立 即 返 回 0: 从 不 等 待, 测 试 所 有 指 定 的 描 述 符 并 立 即 返 回 大 于 0: 成 功, 返 回 准 备 好 的 文 件 描 述 符 的 数 目 0: 超 时 ; 1: 出 错 请 读 者 考 虑 一 下 如 何 确 定 被 监 视 的 文 件 描 述 符 的 最 大 值? 可 以 看 到,select() 函 数 根 据 希 望 进 行 的 文 件 操 作 对 文 件 描 述 符 进 行 了 分 类 处 理, 这 里, 对 文 件 描 述 符 的 处 理 主 要 涉 及 4 个 宏 函 数, 如 表 6.9 所 示 表 6.9 FD_ZERO(fd_set *set) FD_SET(int fd, fd_set *set) FD_CLR(int fd, fd_set *set) FD_ISSET(int fd, fd_set *set) select() 文 件 描 述 符 处 理 函 数 清 除 一 个 文 件 描 述 符 集 将 一 个 文 件 描 述 符 加 入 文 件 描 述 符 集 中 将 一 个 文 件 描 述 符 从 文 件 描 述 符 集 中 清 除 如 果 文 件 描 述 符 fd 为 fd_set 集 中 的 一 个 元 素, 则 返 回 非 零 值, 可 以 用 于 调 用 select() 之 后 测 试 文 件 描 述 符 集 中 的 文 件 描 述 符 是 否 有 变 化 一 般 来 说, 在 使 用 select() 函 数 之 前, 首 先 使 用 FD_ZERO() 和 FD_SET() 来 初 始 化 文 件 描 述 符 集, 在 使 用 了 select() 函 数 时, 可 循 环 使 用 FD_ISSET() 来 测 试 描 述 符 集, 在 执 行 完 对 相 关 文 件 描 述 符 的 操 作 之 后, 使 用 FD_CLR() 来 清 除 描 述 符 集 另 外,select() 函 数 中 的 timeout 是 一 个 struct timeval 类 型 的 指 针, 该 结 构 体 如 下 所 示 : struct timeval long tv_sec; /* 秒 */ long tv_unsec; /* 微 秒 */ 可 以 看 到, 这 个 时 间 结 构 体 的 精 确 度 可 以 设 置 到 微 秒 级, 这 对 于 大 多 数 的 应 用 而 言 已 经 足 够 了 poll() 函 数 的 语 法 格 式 如 表 6.10 所 示 表 6.10 poll() 函 数 语 法 要 点 12
160 所 需 头 文 件 函 数 原 型 函 数 传 入 值 #include <sys/types.h> #include <poll.h> int poll(struct pollfd *fds, int numfds, int timeout) fds:struct pollfd 结 构 的 指 针, 用 于 描 述 需 要 对 哪 些 文 件 的 哪 种 类 型 的 操 作 进 行 监 控 struct pollfd int fd; /* 需 要 监 听 的 文 件 描 述 符 */ short events; /* 需 要 监 听 的 事 件 */ short revents; /* 已 发 生 的 事 件 */ events 成 员 描 述 需 要 监 听 哪 些 类 型 的 事 件, 可 以 用 以 下 几 种 标 志 来 描 述 POLLIN: 文 件 中 有 数 据 可 读, 下 面 实 例 中 使 用 到 了 这 个 标 志 POLLPRI:: 文 件 中 有 紧 急 数 据 可 读 POLLOUT: 可 以 向 文 件 写 入 数 据 POLLERR: 文 件 中 出 现 错 误, 只 限 于 输 出 POLLHUP: 与 文 件 的 连 接 被 断 开 了, 只 限 于 输 出 POLLNVAL: 文 件 描 述 符 是 不 合 法 的, 即 它 并 没 有 指 向 一 个 成 功 打 开 的 文 件 numfds: 需 要 监 听 的 文 件 个 数, 即 第 一 个 参 数 所 指 向 的 数 组 中 的 元 素 数 目 timeout: 表 示 poll 阻 塞 的 超 时 时 间 ( 毫 秒 ) 如 果 该 值 小 于 等 于 0, 则 表 示 无 限 等 待 函 数 返 回 值 成 功 : 返 回 大 于 0 的 值, 表 示 事 件 发 生 的 pollfd 结 构 的 个 数 0: 超 时 ; 1: 出 错 (3) 使 用 实 例 由 于 多 路 复 用 通 常 用 于 I/O 操 作 可 能 会 被 阻 塞 的 情 况, 而 对 于 可 能 会 有 阻 塞 I/O 的 管 道 网 络 编 程, 本 书 到 现 在 为 止 还 没 有 涉 及 这 里 通 过 手 动 创 建 ( 用 mknod 命 令 ) 两 个 管 道 文 件, 重 点 说 明 如 何 使 用 两 个 多 路 复 用 函 数 在 本 实 例 中, 分 别 用 select() 函 数 和 poll() 函 数 实 现 同 一 个 功 能, 以 下 功 能 说 明 是 以 select() 函 数 为 例 描 述 的 本 实 例 中 主 要 实 现 通 过 调 用 select() 函 数 来 监 听 3 个 终 端 的 输 入 ( 分 别 重 定 向 到 两 个 管 道 文 件 的 虚 拟 终 端 以 及 主 程 序 所 运 行 的 虚 拟 终 端 ), 并 分 别 进 行 相 应 的 处 理 在 这 里 我 们 建 立 了 一 个 select() 函 数 监 视 的 读 文 件 描 述 符 集, 其 中 包 含 3 个 文 件 描 述 符, 分 别 为 一 个 标 准 输 入 文 件 描 述 符 和 两 个 管 道 文 件 描 述 符 通 过 监 视 主 程 序 的 虚 拟 终 端 标 准 输 入 来 实 现 程 序 的 控 制 ( 例 如 : 程 序 结 束 ); 以 两 个 管 道 作 为 数 据 输 入, 主 程 序 将 从 两 个 管 道 读 取 的 输 入 字 符 串 写 入 到 标 准 输 出 文 件 ( 屏 幕 ) 为 了 充 分 表 现 select() 调 用 的 功 能, 在 运 行 主 程 序 的 时 候, 需 要 打 开 3 个 虚 拟 终 端 : 首 先 用 mknod 命 令 创 建 两 个 管 道 in1 和 in2 接 下 来, 在 两 个 虚 拟 终 端 上 分 别 运 行 cat>in1 和 cat>in2 同 时 在 第 三 个 虚 拟 终 端 上 运 行 主 程 序 在 程 序 运 行 之 后, 如 果 在 两 个 管 道 终 端 上 输 入 字 符 串, 则 可 以 观 察 到 同 样 的 内 容 将 在 主 程 序 的 虚 拟 终 端 上 逐 行 显 示 如 果 想 结 束 主 程 序, 只 要 在 主 程 序 的 虚 拟 终 端 下 输 入 以 q 或 Q 字 符 开 头 的 字 符 串 即 可 如 果 三 个 文 件 一 直 在 无 输 入 状 态 中, 则 主 程 序 一 直 处 于 阻 塞 状 态 为 了 防 止 无 限 期 的 阻 塞, 在 select 程 序 中 设 置 超 时 值 ( 本 实 例 中 设 置 为 60s), 当 无 输 入 状 态 持 续 到 超 时 值 时, 主 程 序 主 动 结 束 运 行 并 退 出 而 poll 程 序 中 依 然 无 限 等 待, 当 然 poll() 函 数 也 可 以 设 置 超 时 参 数 该 程 序 的 流 程 图 如 图 6.2 所 示 13
161 图 6.2 select 实 例 流 程 图 使 用 select() 函 数 实 现 的 代 码 如 下 所 示 : /* multiplex_select */ #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <time.h> #include <errno.h> #define MAX_BUFFER_SIZE 1024 /* 缓 冲 区 大 小 */ #define IN_FILES 3 /* 多 路 复 用 输 入 文 件 数 目 */ #define TIME_DELAY 60 /* 超 时 值 秒 数 */ #define MAX(a, b) ((a > b)?(a):(b)) int main(void) int fds[in_files]; char buf[max_buffer_size]; int i, res, real_read, maxfd; struct timeval tv; fd_set inset,tmp_inset; /* 首 先 以 只 读 非 阻 塞 方 式 打 开 两 个 管 道 文 件 */ fds[0] = 0; if((fds[1] = open ("in1", O_RDONLY O_NONBLOCK)) < 0) printf("open in1 error\n"); return 1; if((fds[2] = open ("in2", O_RDONLY O_NONBLOCK)) < 0) 14
162 printf("open in2 error\n"); return 1; 专 业 始 于 专 注 卓 识 源 于 远 见 /* 取 出 两 个 文 件 描 述 符 中 的 较 大 者 */ maxfd = MAX(MAX(fds[0], fds[1]), fds[2]); /* 初 始 化 读 集 合 inset, 并 在 读 集 合 中 加 入 相 应 的 描 述 集 */ FD_ZERO(&inset); for (i = 0; i < IN_FILES; i++) FD_SET(fds[i], &inset); FD_SET(0, &inset); tv.tv_sec = TIME_DELAY; tv.tv_usec = 0; /* 循 环 测 试 该 文 件 描 述 符 是 否 准 备 就 绪, 并 调 用 select 函 数 对 相 关 文 件 描 述 符 做 对 应 操 作 */ while(fd_isset(fds[0],&inset) FD_ISSET(fds[1],&inset) FD_ISSET(fds[2], &inset)) /* 文 件 描 述 符 集 合 的 备 份, 这 样 可 以 避 免 每 次 进 行 初 始 化 */ tmp_inset = inset; res = select(maxfd + 1, &tmp_inset, NULL, NULL, &tv); switch(res) case -1: printf("select error\n"); return 1; break; case 0: /* Timeout */ printf("time out\n"); return 1; break; default: for (i = 0; i < IN_FILES; i++) f (FD_ISSET(fds[i], &tmp_inset)) memset(buf, 0, MAX_BUFFER_SIZE); 15
163 real_read = read(fds[i], buf, MAX_BUFFER_SIZE); if (real_read < 0) if (errno!= EAGAIN) return 1; else if (!real_read) close(fds[i]); FD_CLR(fds[i], &inset); else if (i == 0) /* 主 程 序 终 端 控 制 */ if ((buf[0] == 'q') (buf[0] == 'Q')) return 1; else /* 显 示 管 道 输 入 字 符 串 */ buf[real_read] = '\0'; printf("%s", buf); /* end of if */ /* end of for */ break; /* end of switch */ /*end of while */ return 0; 读 者 可 以 将 以 上 程 序 交 叉 编 译, 并 下 载 到 开 发 板 上 运 行 以 下 是 运 行 结 果 : $ mknod in1 p $ mknod in2 p $ cat > in1 SELECT CALL TEST PROGRAMME END $ cat > in2 16
164 select call test programme end $./multiplex_select SELECT CALL select call TEST PROGRAMME test programme END end q /* 在 终 端 上 输 入 q 或 Q 则 立 刻 结 束 程 序 运 行 */ 程 序 的 超 时 结 束 结 果 如 下 : $./multiplex_select Time out 可 以 看 到, 使 用 select() 可 以 很 好 地 实 现 I/O 多 路 复 用 但 是 当 使 用 select() 函 数 时, 存 在 一 系 列 的 问 题, 例 如 : 内 核 必 须 检 查 多 余 的 文 件 描 述 符, 每 次 调 用 select() 之 后 必 须 重 置 被 监 听 的 文 件 描 述 符 集, 而 且 可 监 听 的 文 件 个 数 受 限 制 ( 使 用 FD_SETSIZE 宏 来 表 示 fd_set 结 构 能 够 容 纳 的 文 件 描 述 符 的 最 大 数 目 ) 等 实 际 上,poll 机 制 与 select 机 制 相 比 效 率 更 高, 使 用 范 围 更 广, 下 面 给 出 用 poll() 函 数 实 现 同 样 功 能 的 代 码 /* multiplex_poll.c */ #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <errno.h> #include <poll.h> #define MAX_BUFFER_SIZE 1024 /* 缓 冲 区 大 小 */ #define IN_FILES 3 /* 多 路 复 用 输 入 文 件 数 目 */ #define TIME_DELAY 60 /* 超 时 时 间 秒 数 */ #define MAX(a, b) ((a > b)?(a):(b)) int main(void) struct pollfd fds[in_files]; char buf[max_buffer_size]; int i, res, real_read, maxfd; /* 首 先 按 一 定 的 权 限 打 开 两 个 源 文 件 */ fds[0].fd = 0; if((fds[1].fd = open ("in1", O_RDONLY O_NONBLOCK)) < 0) printf("open in1 error\n"); 17
165 return 1; if((fds[2].fd = open ("in2", O_RDONLY O_NONBLOCK)) < 0) printf("open in2 error\n"); return 1; /* 取 出 两 个 文 件 描 述 符 中 的 较 大 者 */ for (i = 0; i < IN_FILES; i++) fds[i].events = POLLIN; /* 循 环 测 试 该 文 件 描 述 符 是 否 准 备 就 绪, 并 调 用 select 函 数 对 相 关 文 件 描 述 符 做 对 应 操 作 */ while(fds[0].events fds[1].events fds[2].events) if (poll(fds, IN_FILES, 0) < 0) printf("poll error\n"); return 1; for (i = 0; i< IN_FILES; i++) if (fds[i].revents) memset(buf, 0, MAX_BUFFER_SIZE); real_read = read(fds[i].fd, buf, MAX_BUFFER_SIZE); if (real_read < 0) if (errno!= EAGAIN) return 1; else if (!real_read) close(fds[i].fd); fds[i].events = 0; else if (i == 0) if ((buf[0] == 'q') (buf[0] == 'Q')) return 1; 18
166 else buf[real_read] = '\0'; printf("%s", buf); /* end of if real_read*/ /* end of if revents */ /* end of for */ /*end of while */ exit(0); 专 业 始 于 专 注 卓 识 源 于 远 见 运 行 结 果 与 select 程 序 类 似 请 读 者 比 较 使 用 select() 和 poll() 函 数 实 现 的 代 码 的 运 行 效 率 ( 提 示 : 使 用 获 得 时 间 的 函 数 计 算 程 序 运 行 时 间, 可 以 证 明 poll() 函 数 的 效 率 更 高 ) 6.4 嵌 入 式 Linux 串 口 应 用 编 程 串 口 概 述 常 见 的 数 据 通 信 的 基 本 方 式 可 分 为 并 行 通 信 与 串 行 通 信 两 种 并 行 通 信 是 指 利 用 多 条 数 据 传 输 线 将 一 个 字 数 据 的 各 比 特 位 同 时 传 送 它 的 特 点 是 传 输 速 度 快, 适 用 于 传 输 距 离 短 且 传 输 速 度 较 高 的 通 信 串 行 通 信 是 指 利 用 一 条 传 输 线 将 数 据 以 比 特 位 为 单 位 顺 序 传 送 特 点 是 通 信 线 路 简 单, 利 用 简 单 的 线 缆 就 可 实 现 通 信, 降 低 成 本, 适 用 于 传 输 距 离 长 且 传 输 速 度 较 慢 的 通 信 串 口 是 计 算 机 一 种 常 用 的 接 口, 常 用 的 串 口 有 RS-232-C 接 口 它 是 于 1970 年 由 美 国 电 子 工 业 协 会 (EIA) 联 合 贝 尔 系 统 调 制 解 调 器 厂 家 及 计 算 机 终 端 生 产 厂 家 共 同 制 定 的 用 于 串 行 通 信 的 标 准, 它 的 全 称 是 数 据 终 端 设 备 (DTE) 和 数 据 通 信 设 备 (DCE) 之 间 串 行 二 进 制 数 据 交 换 接 口 技 术 标 准 该 标 准 规 定 采 用 一 个 DB25 芯 引 脚 的 连 接 器 或 9 芯 引 脚 的 连 接 器, 其 中 25 芯 引 脚 的 连 接 器 如 图 6.3 所 示 S3C2410X 内 部 具 有 两 个 独 立 的 UART 控 制 器, 每 个 控 制 器 都 可 以 工 作 在 Interrupt( 中 断 ) 模 式 或 者 DMA( 直 接 存 储 访 问 ) 模 式 同 时, 每 个 UART 均 具 有 16 字 节 的 FIFO( 先 入 先 出 寄 存 器 ), 支 持 的 最 高 波 特 率 可 达 到 230.4Kbps UART 的 操 作 主 要 可 分 为 以 下 几 个 部 分 : 数 据 发 送 数 据 接 收 产 生 中 断 设 置 波 图 引 脚 串 行 接 口 图 特 率 Loopback 模 式 红 外 模 式 以 及 硬 软 流 控 模 式 串 口 参 数 的 配 置 读 者 在 配 置 超 级 终 端 和 minicom 时 也 已 经 接 触 过, 一 般 包 括 波 特 率 起 始 位 比 特 数 数 据 位 比 特 数 停 止 位 比 特 数 和 流 控 模 式 在 此, 可 以 将 其 配 置 为 波 特 率 起 始 位 1b 数 据 位 8b 停 止 位 1b 和 无 流 控 模 式 在 Linux 中, 所 有 的 设 备 文 件 一 般 都 位 于 /dev 下, 其 中 串 口 1 和 串 口 2 对 应 的 设 备 名 依 次 为 /dev/ttys0 和 /dev/ttys1, 而 且 USB 转 串 口 的 设 备 名 通 常 为 /dev/ttyusb0 和 /dev/ttyusb1 ( 因 版 本 不 同 该 设 备 名 会 有 所 不 同 ), 可 以 查 看 在 /dev 下 的 文 件 以 确 认 在 本 章 中 已 经 提 到 过, 在 Linux 下 对 设 备 的 操 作 方 法 与 对 文 件 的 操 作 方 法 是 一 样 的, 因 此, 对 串 口 的 读 写 就 可 以 使 用 简 单 的 read() write() 函 数 来 完 成, 所 不 同 的 只 是 需 要 对 串 口 的 其 他 参 数 另 做 配 置, 下 面 就 来 详 细 讲 解 串 口 应 用 开 发 的 步 骤 19
167 6.4.2 串 口 设 置 详 解 串 口 的 设 置 主 要 是 设 置 struct termios 结 构 体 的 各 成 员 值, 如 下 所 示 : #include<termios.h> struct termios unsigned short c_iflag; /* 输 入 模 式 标 志 */ unsigned short c_oflag; /* 输 出 模 式 标 志 */ unsigned short c_cflag; /* 控 制 模 式 标 志 */ unsigned short c_lflag; /* 本 地 模 式 标 志 */ unsigned char c_line; /* 线 路 规 程 */ unsigned char c_cc[ncc]; /* 控 制 特 性 */ speed_t c_ispeed; /* 输 入 速 度 */ speed_t c_ospeed; /* 输 出 速 度 */ ; termios 是 在 POSIX 规 范 中 定 义 的 标 准 接 口, 表 示 终 端 设 备 ( 包 括 虚 拟 终 端 串 口 等 ) 口 是 一 种 终 端 设 备, 一 般 通 过 终 端 编 程 接 口 对 其 进 行 配 置 和 控 制 在 具 体 讲 解 串 口 相 关 编 程 之 前, 先 了 解 一 下 终 端 相 关 知 识 终 端 有 3 种 工 作 模 式, 分 别 为 规 范 模 式 (canonical mode) 非 规 范 模 式 (non-canonical mode) 和 原 始 模 式 (raw mode) 通 过 在 termios 结 构 的 c_lflag 中 设 置 ICANNON 标 志 来 定 义 终 端 是 以 规 范 模 式 ( 设 置 ICANNON 标 志 ) 还 是 以 非 规 范 模 式 ( 清 除 ICANNON 标 志 ) 工 作, 默 认 情 况 为 规 范 模 式 在 规 范 模 式 下, 所 有 的 输 入 是 基 于 行 进 行 处 理 在 用 户 输 入 一 个 行 结 束 符 ( 回 车 符 EOF 等 ) 之 前, 系 统 调 用 read() 函 数 读 不 到 用 户 输 入 的 任 何 字 符 除 了 EOF 之 外 的 行 结 束 符 ( 回 车 符 等 ) 与 普 通 字 符 一 样 会 被 read() 函 数 读 取 到 缓 冲 区 之 中 在 规 范 模 式 中, 行 编 辑 是 可 行 的, 而 且 一 次 调 用 read() 函 数 最 多 只 能 读 取 一 行 数 据 如 果 在 read() 函 数 中 被 请 求 读 取 的 数 据 字 节 数 小 于 当 前 行 可 读 取 的 字 节 数, 则 read() 函 数 只 会 读 取 被 请 求 的 字 节 数, 剩 下 的 字 节 下 次 再 被 读 取 在 非 规 范 模 式 下, 所 有 的 输 入 是 即 时 有 效 的, 不 需 要 用 户 另 外 输 入 行 结 束 符, 而 且 不 可 进 行 行 编 辑 在 非 规 范 模 式 下, 对 参 数 MIN(c_cc[VMIN]) 和 TIME(c_cc[VTIME]) 的 设 置 决 定 read() 函 数 的 调 用 方 式 设 置 可 以 有 4 种 不 同 的 情 况 MIN = 0 和 TIME = 0:read() 函 数 立 即 返 回 若 有 可 读 数 据, 则 读 取 数 据 并 返 回 被 读 取 的 字 节 数, 否 则 读 取 失 败 并 返 回 0 MIN > 0 和 TIME = 0:read() 函 数 会 被 阻 塞 直 到 MIN 个 字 节 数 据 可 被 读 取 MIN = 0 和 TIME > 0: 只 要 有 数 据 可 读 或 者 经 过 TIME 个 十 分 之 一 秒 的 时 间,read() 函 数 则 立 即 返 回, 返 回 值 为 被 读 取 的 字 节 数 如 果 超 时 并 且 未 读 到 数 据, 则 read() 函 数 返 回 0 MIN > 0 和 TIME > 0: 当 有 MIN 个 字 节 可 读 或 者 两 个 输 入 字 符 之 间 的 时 间 间 隔 超 过 TIME 个 十 分 之 一 秒 时,read() 函 数 才 返 回 因 为 在 输 入 第 一 个 字 符 之 后 系 统 才 会 启 动 定 时 器, 所 以 在 这 种 情 况 下,read() 函 数 至 少 读 取 一 个 字 节 之 后 才 返 回 按 照 严 格 意 义 来 讲, 原 始 模 式 是 一 种 特 殊 的 非 规 范 模 式 在 原 始 模 式 下, 所 有 的 输 入 数 据 以 字 节 为 单 位 被 处 理 在 这 个 模 式 下, 终 端 是 不 可 回 显 的, 而 且 所 有 特 定 的 终 端 输 入 / 输 出 控 制 处 理 不 可 用 通 过 调 用 cfmakeraw() 函 数 可 以 将 终 端 设 置 为 原 始 模 式, 而 且 该 函 数 通 过 以 下 代 码 可 以 得 到 实 现 termios_p->c_iflag &= ~(IGNBRK BRKINT PARMRK ISTRIP INLCR IGNCR ICRNL IXON); termios_p->c_oflag &= ~OPOST; termios_p->c_lflag &= ~(ECHO ECHONL ICANON ISIG IEXTEN); termios_p->c_cflag &= ~(CSIZE PARENB); termios_p->c_cflag = CS8; 20
168 下 面 讲 解 设 置 串 口 的 基 本 方 法 设 置 串 口 中 最 基 本 的 包 括 波 特 率 设 置, 校 验 位 和 停 止 位 设 置 在 这 个 结 构 中 最 为 重 要 的 是 c_cflag, 通 过 对 它 的 赋 值, 用 户 可 以 设 置 波 特 率 字 符 大 小 数 据 位 停 止 位 奇 偶 校 验 位 和 硬 软 流 控 等 另 外 c_iflag 和 c_cc 也 是 比 较 常 用 的 标 志 在 此 主 要 对 这 3 个 成 员 进 行 详 细 说 明 c_cflag 支 持 的 常 量 名 称 如 表 6.11 所 示 其 中 设 置 波 特 率 宏 名 为 相 应 的 波 特 率 数 值 前 加 上 B, 由 于 数 值 较 多, 本 表 没 有 全 部 列 出 表 6.11 CBAUD B0 B1800 B2400 c_cflag 支 持 的 常 量 名 称 波 特 率 的 位 掩 码 0 波 特 率 ( 放 弃 DTR) 1800 波 特 率 2400 波 特 率 21
169 续 表 B4800 B9600 B19200 B38400 B57600 B EXTA EXTB CSIZE CS5 CS6 CS7 CS 波 特 率 9600 波 特 率 波 特 率 波 特 率 波 特 率 波 特 率 外 部 时 钟 率 外 部 时 钟 率 数 据 位 的 位 掩 码 5 个 数 据 位 6 个 数 据 位 7 个 数 据 位 8 个 数 据 位 CSTOPB 2 个 停 止 位 ( 不 设 则 是 1 个 停 止 位 ) CREAD PARENB PARODD HUPCL 接 收 使 能 校 验 位 使 能 使 用 奇 校 验 而 不 使 用 偶 校 验 最 后 关 闭 时 挂 线 ( 放 弃 DTR) CLOCAL 本 地 连 接 ( 不 改 变 端 口 所 有 者 ) CRTSCTS 硬 件 流 控 在 这 里, 不 能 直 接 对 c_cflag 成 员 初 始 化, 而 要 将 其 通 过 与 或 操 作 使 用 其 中 的 某 些 选 项 输 入 模 式 标 志 c_iflag 用 于 控 制 端 口 接 收 端 的 字 符 输 入 处 理 c_iflag 支 持 的 常 量 名 称 如 表 6.12 所 示 表 6.12 c_iflag 支 持 的 常 量 名 称 INPCK 奇 偶 校 验 使 能 IGNPAR 忽 略 奇 偶 校 验 错 误 PARMRK 奇 偶 校 验 错 误 掩 码 ISTRIP 裁 减 掉 第 8 位 比 特 IXON 启 动 输 出 软 件 流 控 IXOFF 启 动 输 入 软 件 流 控 IXANY 输 入 任 意 字 符 可 以 重 新 启 动 输 出 ( 默 认 为 输 入 起 始 字 符 才 重 启 输 出 ) IGNBRK 忽 略 输 入 终 止 条 件 BRKINT 当 检 测 到 输 入 终 止 条 件 时 发 送 SIGINT 信 号 INLCR 将 接 收 到 的 NL( 换 行 符 ) 转 换 为 CR( 回 车 符 ) IGNCR 忽 略 接 收 到 的 CR( 回 车 符 ) ICRNL 将 接 收 到 的 CR( 回 车 符 ) 转 换 为 NL( 换 行 符 ) IUCLC 将 接 收 到 的 大 写 字 符 映 射 为 小 写 字 符 IMAXBEL 当 输 入 队 列 满 时 响 铃 c_oflag 用 于 控 制 终 端 端 口 发 送 出 去 的 字 符 处 理,c_oflag 支 持 的 常 量 名 称 如 表 6.12 所 示 因 为 现 在 终 端 的 速 度 比 以 前 快 得 多, 所 以 大 部 分 延 时 掩 码 几 乎 没 什 么 用 途 表 6.13 OPOST OLCUC ONLCR ONOCR OCRNL c_oflag 支 持 的 常 量 名 称 启 用 输 出 处 理 功 能, 如 果 不 设 置 该 标 志, 则 其 他 标 志 都 被 忽 略 将 输 出 中 的 大 写 字 符 转 换 成 小 写 字 符 将 输 出 中 的 换 行 符 ( \n ) 转 换 成 回 车 符 ( \r ) 如 果 当 前 列 号 为 0, 则 不 输 出 回 车 符 将 输 出 中 的 回 车 符 ( \r ) 转 换 成 换 行 符 ( \n ) 22
170 ONLRET OFILL OFDEL NLDLY CRDLY TABDLY BSDLY VTDLY FFLDY 不 输 出 回 车 符 发 送 填 充 字 符 以 提 供 延 时 如 果 设 置 该 标 志, 则 表 示 填 充 字 符 为 DEL 字 符, 否 则 为 NUL 字 符 换 行 延 时 掩 码 回 车 延 时 掩 码 制 表 符 延 时 掩 码 水 平 退 格 符 延 时 掩 码 垂 直 退 格 符 延 时 掩 码 换 页 符 延 时 掩 码 c_lflag 用 于 控 制 控 制 终 端 的 本 地 数 据 处 理 和 工 作 模 式,c_lflag 所 支 持 的 常 量 名 称 如 表 6.14 所 示 表 6.14 ISIG ICANON ECHO ECHOE ECHOK ECHONL ECHOCTL ECHOPRT ECHOKE NOFLSH TOSTOP IEXTEN c_lflag 支 持 的 常 量 名 称 若 收 到 信 号 字 符 (INTR QUIT 等 ), 则 会 产 生 相 应 的 信 号 启 用 规 范 模 式 启 用 本 地 回 显 功 能 若 设 置 ICANON, 则 允 许 退 格 操 作 若 设 置 ICANON, 则 KILL 字 符 会 删 除 当 前 行 若 设 置 ICANON, 则 允 许 回 显 换 行 符 若 设 置 ECHO, 则 控 制 字 符 ( 制 表 符 换 行 符 等 ) 会 显 示 成 ^X, 其 中 X 的 ASCII 码 等 于 给 相 应 控 制 字 符 的 ASCII 码 加 上 0x40 例 如 : 退 格 字 符 (0x08) 会 显 示 为 ^H ( H 的 ASCII 码 为 0x48) 若 设 置 ICANON 和 IECHO, 则 删 除 字 符 ( 退 格 符 等 ) 和 被 删 除 的 字 符 都 会 被 显 示 若 设 置 ICANON, 则 允 许 回 显 在 ECHOE 和 ECHOPRT 中 设 定 的 KILL 字 符 在 通 常 情 况 下, 当 接 收 到 INTR QUIT 和 SUSP 控 制 字 符 时, 会 清 空 输 入 和 输 出 队 列 如 果 设 置 该 标 志, 则 所 有 的 队 列 不 会 被 清 空 若 一 个 后 台 进 程 试 图 向 它 的 控 制 终 端 进 行 写 操 作, 则 系 统 向 该 后 台 进 程 的 进 程 组 发 送 SIGTTOU 信 号 该 信 号 通 常 终 止 进 程 的 执 行 启 用 输 入 处 理 功 能 c_cc 定 义 特 殊 控 制 特 性 c_cc 所 支 持 的 常 量 名 称 如 表 6.13 所 示 表 6.13 VINTR VQUIT VERASE VKILL VEOF VEOL VEOL2 VMIN VTIME c_cc 支 持 的 常 量 名 称 中 断 控 制 字 符, 对 应 键 为 CTRL+C 退 出 操 作 符, 对 应 键 为 CRTL+Z 删 除 操 作 符, 对 应 键 为 Backspace(BS) 删 除 行 符, 对 应 键 为 CTRL+U 文 件 结 尾 符, 对 应 键 为 CTRL+D 附 加 行 结 尾 符, 对 应 键 为 Carriage return(cr) 第 二 行 结 尾 符, 对 应 键 为 Line feed(lf) 指 定 最 少 读 取 的 字 符 数 指 定 读 取 的 每 个 字 符 之 间 的 超 时 时 间 下 面 就 详 细 讲 解 设 置 串 口 属 性 的 基 本 流 程 23
171 1. 保 存 原 先 串 口 配 置 首 先, 为 了 安 全 起 见 和 以 后 调 试 程 序 方 便, 可 以 先 保 存 原 先 串 口 的 配 置, 在 这 里 可 以 使 用 函 数 tcgetattr(fd, &old_cfg) 该 函 数 得 到 fd 指 向 的 终 端 的 配 置 参 数, 并 将 它 们 保 存 于 termios 结 构 变 量 old_cfg 中 该 函 数 还 可 以 测 试 配 置 是 否 正 确 该 串 口 是 否 可 用 等 若 调 用 成 功, 函 数 返 回 值 为 0, 若 调 用 失 败, 函 数 返 回 值 为 1, 其 使 用 如 下 所 示 : if (tcgetattr(fd, &old_cfg)!= 0) perror("tcgetattr"); return -1; 2. 激 活 选 项 CLOCAL 和 CREAD 分 别 用 于 本 地 连 接 和 接 受 使 能, 因 此, 首 先 要 通 过 位 掩 码 的 方 式 激 活 这 两 个 选 项 newtio.c_cflag = CLOCAL CREAD; 调 用 cfmakeraw() 函 数 可 以 将 终 端 设 置 为 原 始 模 式, 在 后 面 的 实 例 中, 采 用 原 始 模 式 进 行 串 口 数 据 通 信 cfmakeraw(&new_cfg); 3. 设 置 波 特 率 设 置 波 特 率 有 专 门 的 函 数, 用 户 不 能 直 接 通 过 位 掩 码 来 操 作 设 置 波 特 率 的 主 要 函 数 有 :cfsetispeed() 和 cfsetospeed() 这 两 个 函 数 的 使 用 很 简 单, 如 下 所 示 : cfsetispeed(&new_cfg, B115200); cfsetospeed(&new_cfg, B115200); 一 般 地, 用 户 需 将 终 端 的 输 入 和 输 出 波 特 率 设 置 成 一 样 的 这 几 个 函 数 在 成 功 时 返 回 0, 失 败 时 返 回 1 4. 设 置 字 符 大 小 与 设 置 波 特 率 不 同, 设 置 字 符 大 小 并 没 有 现 成 可 用 的 函 数, 需 要 用 位 掩 码 一 般 首 先 去 除 数 据 位 中 的 位 掩 码, 再 重 新 按 要 求 设 置 如 下 所 示 : new_cfg.c_cflag &= ~CSIZE; /* 用 数 据 位 掩 码 清 空 数 据 位 设 置 */ new_cfg.c_cflag = CS8; 5. 设 置 奇 偶 校 验 位 设 置 奇 偶 校 验 位 需 要 用 到 termios 中 的 两 个 成 员 :c_cflag 和 c_iflag 首 先 要 激 活 c_cflag 中 的 校 验 位 使 能 标 志 PARENB 和 是 否 要 进 行 偶 校 验, 同 时 还 要 激 活 c_iflag 中 的 对 于 输 入 数 据 的 奇 偶 校 验 使 能 (INPCK) 如 使 能 奇 校 验 时, 代 码 如 下 所 示 : 24
172 new_cfg.c_cflag = (PARODD PARENB); new_cfg.c_iflag = INPCK; 而 使 能 偶 校 验 时, 代 码 如 下 所 示 : new_cfg.c_cflag = PARENB; new_cfg.c_cflag &= ~PARODD; /* 清 除 偶 校 验 标 志, 则 配 置 为 奇 校 验 */ new_cfg.c_iflag = INPCK; 6. 设 置 停 止 位 设 置 停 止 位 是 通 过 激 活 c_cflag 中 的 CSTOPB 而 实 现 的 若 停 止 位 为 一 个, 则 清 除 CSTOPB, 若 停 止 位 为 两 个, 则 激 活 CSTOPB 以 下 分 别 是 停 止 位 为 一 个 和 两 个 比 特 时 的 代 码 : new_cfg.c_cflag &= ~CSTOPB; /* 将 停 止 位 设 置 为 一 个 比 特 */ new_cfg.c_cflag = CSTOPB; /* 将 停 止 位 设 置 为 两 个 比 特 */ 7. 设 置 最 少 字 符 和 等 待 时 间 在 对 接 收 字 符 和 等 待 时 间 没 有 特 别 要 求 的 情 况 下, 可 以 将 其 设 置 为 0, 则 在 任 何 情 况 下 read() 函 数 立 即 返 回, 如 下 所 示 : new_cfg.c_cc[vtime] = 0; new_cfg.c_cc[vmin] = 0; 8. 清 除 串 口 缓 冲 由 于 串 口 在 重 新 设 置 之 后, 需 要 对 当 前 的 串 口 设 备 进 行 适 当 的 处 理, 这 时 就 可 调 用 在 <termios.h> 中 声 明 的 tcdrain() tcflow() tcflush() 等 函 数 来 处 理 目 前 串 口 缓 冲 中 的 数 据, 它 们 的 格 式 如 下 所 示 int tcdrain(int fd); /* 使 程 序 阻 塞, 直 到 输 出 缓 冲 区 的 数 据 全 部 发 送 完 毕 */ int tcflow(int fd, int action) ; /* 用 于 暂 停 或 重 新 开 始 输 出 */ int tcflush(int fd, int queue_selector); /* 用 于 清 空 输 入 / 输 出 缓 冲 区 */ 在 本 实 例 中 使 用 tcflush() 函 数, 对 于 在 缓 冲 区 中 的 尚 未 传 输 的 数 据, 或 者 收 到 的 但 是 尚 未 读 取 的 数 据, 其 处 理 方 法 取 决 于 queue_selector 的 值, 它 可 能 的 取 值 有 以 下 几 种 TCIFLUSH: 对 接 收 到 而 未 被 读 取 的 数 据 进 行 清 空 处 理 TCOFLUSH: 对 尚 未 传 送 成 功 的 输 出 数 据 进 行 清 空 处 理 TCIOFLUSH: 包 括 前 两 种 功 能, 即 对 尚 未 处 理 的 输 入 输 出 数 据 进 行 清 空 处 理 如 在 本 例 中 所 采 用 的 是 第 一 种 方 法 : tcflush(fd, TCIFLUSH); 9. 激 活 配 置 在 完 成 全 部 串 口 配 置 之 后, 要 激 活 刚 才 的 配 置 并 使 配 置 生 效 这 里 用 到 的 函 数 是 tcsetattr(), 它 的 函 数 原 型 是 : 25
173 tcsetattr(int fd, int optional_actions, const struct termios *termios_p); 其 中 参 数 termios_p 是 termios 类 型 的 新 配 置 变 量 参 数 optional_actions 可 能 的 取 值 有 以 下 3 种 : TCSANOW: 配 置 的 修 改 立 即 生 效 TCSADRAIN: 配 置 的 修 改 在 所 有 写 入 fd 的 输 出 都 传 输 完 毕 之 后 生 效 TCSAFLUSH: 所 有 已 接 受 但 未 读 入 的 输 入 都 将 在 修 改 生 效 之 前 被 丢 弃 该 函 数 若 调 用 成 功 则 返 回 0, 若 失 败 则 返 回 1, 代 码 如 下 所 示 : if ((tcsetattr(fd, TCSANOW, &new_cfg))!= 0) perror("tcsetattr"); return -1; 下 面 给 出 了 串 口 配 置 的 完 整 函 数 通 常, 为 了 函 数 的 通 用 性, 通 常 将 常 用 的 选 项 都 在 函 数 中 列 出, 这 样 可 以 大 大 方 便 以 后 用 户 的 调 试 使 用 该 设 置 函 数 如 下 所 示 : int set_com_config(int fd,int baud_rate, int data_bits, char parity, int stop_bits) struct termios new_cfg,old_cfg; int speed; /* 保 存 并 测 试 现 有 串 口 参 数 设 置, 在 这 里 如 果 串 口 号 等 出 错, 会 有 相 关 的 出 错 信 息 */ if (tcgetattr(fd, &old_cfg)!= 0) perror("tcgetattr"); return -1; /* 设 置 字 符 大 小 */ new_cfg = old_cfg; cfmakeraw(&new_cfg); /* 配 置 为 原 始 模 式 */ new_cfg.c_cflag &= ~CSIZE; /* 设 置 波 特 率 */ switch (baud_rate) case 2400: speed = B2400; break; case 4800: speed = B4800; break; case 9600: speed = B9600; 26
174 break; case 19200: speed = B19200; break; case 38400: speed = B38400; break; default: case : speed = B115200; break; cfsetispeed(&new_cfg, speed); cfsetospeed(&new_cfg, speed); /* 设 置 停 止 位 */ switch (data_bits) case 7: new_cfg.c_cflag = CS7; break; default: case 8: new_cfg.c_cflag = CS8; break; /* 设 置 奇 偶 校 验 位 */ switch (parity) default: case 'n': case 'N': new_cfg.c_cflag &= ~PARENB; new_cfg.c_iflag &= ~INPCK; 27
175 break; 专 业 始 于 专 注 卓 识 源 于 远 见 case 'o': case 'O': new_cfg.c_cflag = (PARODD PARENB); new_cfg.c_iflag = INPCK; break; case 'e': case 'E': new_cfg.c_cflag = PARENB; new_cfg.c_cflag &= ~PARODD; new_cfg.c_iflag = INPCK; break; case 's': /*as no parity*/ case 'S': new_cfg.c_cflag &= ~PARENB; new_cfg.c_cflag &= ~CSTOPB; break; /* 设 置 停 止 位 */ switch (stop_bits) default: case 1: new_cfg.c_cflag &= ~CSTOPB; break; case 2: new_cfg.c_cflag = CSTOPB; /* 设 置 等 待 时 间 和 最 小 接 收 字 符 */ new_cfg.c_cc[vtime] = 0; 28
176 new_cfg.c_cc[vmin] = 1; /* 处 理 未 接 收 字 符 */ tcflush(fd, TCIFLUSH); /* 激 活 新 配 置 */ if ((tcsetattr(fd, TCSANOW, &new_cfg))!= 0) perror("tcsetattr"); return -1; return 0; 串 口 使 用 详 解 在 配 置 完 串 口 的 相 关 属 性 后, 就 可 以 对 串 口 进 行 打 开 和 读 写 操 作 了 它 所 使 用 的 函 数 和 普 通 文 件 的 读 写 函 数 一 样, 都 是 open() write() 和 read() 它 们 之 间 的 区 别 的 只 是 串 口 是 一 个 终 端 设 备, 因 此 在 选 择 函 数 的 具 体 参 数 时 会 有 一 些 区 别 另 外, 这 里 会 用 到 一 些 附 加 的 函 数, 用 于 测 试 终 端 设 备 的 连 接 情 况 等 下 面 将 对 其 进 行 具 体 讲 解 1. 打 开 串 口 打 开 串 口 和 打 开 普 通 文 件 一 样, 都 是 使 用 open() 函 数, 如 下 所 示 : fd = open( "/dev/ttys0", O_RDWR O_NOCTTY O_NDELAY); 可 以 看 到, 这 里 除 了 普 通 的 读 写 参 数 外, 还 有 两 个 参 数 O_NOCTTY 和 O_NDELAY O_NOCTTY 标 志 用 于 通 知 Linux 系 统, 该 参 数 不 会 使 打 开 的 文 件 成 为 这 个 进 程 的 控 制 终 端 如 果 没 有 指 定 这 个 标 志, 那 么 任 何 一 个 输 入 ( 诸 如 键 盘 中 止 信 号 等 ) 都 将 会 影 响 用 户 的 进 程 O_NDELAY 标 志 通 知 Linux 系 统, 这 个 程 序 不 关 心 DCD 信 号 线 所 处 的 状 态 ( 端 口 的 另 一 端 是 否 激 活 或 者 停 止 ) 如 果 用 户 指 定 了 这 个 标 志, 则 进 程 将 会 一 直 处 在 睡 眠 状 态, 直 到 DCD 信 号 线 被 激 活 接 下 来 可 恢 复 串 口 的 状 态 为 阻 塞 状 态, 用 于 等 待 串 口 数 据 的 读 入, 可 用 fcntl() 函 数 实 现, 如 下 所 示 : fcntl(fd, F_SETFL, 0); 再 接 着 可 以 测 试 打 开 文 件 描 述 符 是 否 连 接 到 一 个 终 端 设 备, 以 进 一 步 确 认 串 口 是 否 正 确 打 开, 如 下 所 示 : isatty(stdin_fileno); 该 函 数 调 用 成 功 则 返 回 0, 若 失 败 则 返 回 -1 这 时, 一 个 串 口 就 已 经 成 功 打 开 了 接 下 来 就 可 以 对 这 个 串 口 进 行 读 和 写 操 作 下 面 给 出 了 一 个 完 整 的 打 开 串 口 的 函 数, 同 样 考 虑 到 了 各 种 不 同 的 情 况 程 序 如 下 所 示 : /* 打 开 串 口 函 数 */ int open_port(int com_port) int fd; #if (COM_TYPE == GNR_COM) /* 使 用 普 通 串 口 */ char *dev[] = "/dev/ttys0", "/dev/ttys1", "/dev/ttys2"; 29
177 #else /* 使 用 USB 转 串 口 */ char *dev[] = "/dev/ttyusb0", "/dev/ttyusb1", "/dev/ttyusb2"; #endif if ((com_port < 0) (com_port > MAX_COM_NUM)) return -1; /* 打 开 串 口 */ fd = open(dev[com_port - 1], O_RDWR O_NOCTTY O_NDELAY); if (fd < 0) perror("open serial port"); return(-1); 专 业 始 于 专 注 卓 识 源 于 远 见 /* 恢 复 串 口 为 阻 塞 状 态 */ if (fcntl(fd, F_SETFL, 0) < 0) perror("fcntl F_SETFL\n"); /* 测 试 是 否 为 终 端 设 备 */ if (isatty(stdin_fileno) == 0) perror("standard input is not a terminal device"); return fd; 2. 读 写 串 口 读 写 串 口 操 作 和 读 写 普 通 文 件 一 样, 使 用 read() 和 write() 函 数 即 可, 如 下 所 示 : write(fd, buff, strlen(buff)); read(fd, buff, BUFFER_SIZE); 下 面 两 个 实 例 给 出 了 串 口 读 和 写 的 两 个 程 序, 其 中 用 到 前 面 所 讲 述 的 open_port() 和 set_com_config () 函 数 写 串 口 的 程 序 将 在 宿 主 机 上 运 行, 读 串 口 的 程 序 将 在 目 标 板 上 运 行 写 串 口 的 程 序 如 下 所 示 /* com_writer.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include "uart_api.h" 30
178 int main(void) int fd; char buff[buffer_size]; if((fd = open_port(host_com_port)) < 0) /* 打 开 串 口 */ perror("open_port"); return 1; if(set_com_config(fd, , 8, 'N', 1) < 0) /* 配 置 串 口 */ perror("set_com_config"); return 1; do printf("input some words(enter 'quit' to exit):"); memset(buff, 0, BUFFER_SIZE); if (fgets(buff, BUFFER_SIZE, stdin) == NULL) perror("fgets"); break; write(fd, buff, strlen(buff)); while(strncmp(buff, "quit", 4)); close(fd); return 0; 读 串 口 的 程 序 如 下 所 示 : /* com_reader.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include "uart_api.h" int main(void) int fd; char buff[buffer_size]; if((fd = open_port(target_com_port)) < 0) /* 打 开 串 口 */ 31
179 perror("open_port"); return 1; if(set_com_config(fd, , 8, 'N', 1) < 0) /* 配 置 串 口 */ perror("set_com_config"); return 1; do memset(buff, 0, BUFFER_SIZE); if (read(fd, buff, BUFFER_SIZE) > 0) printf("the received words are : %s", buff); while(strncmp(buff, "quit", 4)); close(fd); return 0; 在 宿 主 机 上 运 行 写 串 口 的 程 序, 而 在 目 标 板 上 运 行 读 串 口 的 程 序, 运 行 结 果 如 下 所 示 /* 宿 主 机, 写 串 口 */ $./com_writer Input some words(enter 'quit' to exit):hello, Reader! Input some words(enter 'quit' to exit):i'm Writer! Input some words(enter 'quit' to exit):this is a serial port testing program. Input some words(enter 'quit' to exit):quit /* 目 标 板, 读 串 口 */ $./com_reader The received words are : hello, Reader! The received words are : I'm Writer! The received words are : This is a serial port testing program. The received words are : quit 另 外, 读 者 还 可 以 考 虑 一 下 如 何 使 用 select() 函 数 实 现 串 口 的 非 阻 塞 读 写, 具 体 实 例 会 在 本 章 的 后 面 的 实 验 中 给 出 6.5 标 准 I/O 编 程 本 章 前 面 几 节 所 述 的 文 件 及 I/O 读 写 都 是 基 于 文 件 描 述 符 的 这 些 都 是 基 本 的 I/O 控 制, 是 不 带 缓 存 的 而 本 节 所 要 讨 论 的 I/O 操 作 都 是 基 于 流 缓 冲 的, 它 是 符 合 ANSI C 的 标 准 I/O 处 理, 这 里 有 很 多 函 数 读 者 已 经 非 常 熟 悉 了 ( 如 printf() scantf() 函 数 等 ), 因 此 本 节 中 仅 简 要 介 绍 最 主 要 的 函 数 前 面 讲 述 的 系 统 调 用 是 操 作 系 统 直 接 提 供 的 函 数 接 口 因 为 运 行 系 统 调 用 时,Linux 必 须 从 用 户 态 切 换 到 内 核 态, 执 行 相 应 的 请 求, 然 后 再 返 回 到 用 户 态, 所 以 应 该 尽 量 减 少 系 统 调 用 的 次 数, 从 而 提 高 程 序 的 效 率 标 准 I/O 提 供 流 缓 冲 的 目 的 是 尽 可 能 减 少 使 用 read() 和 write() 等 系 统 调 用 的 数 量 标 准 I/O 提 供 了 3 种 类 型 的 缓 冲 存 储 32
180 全 缓 冲 : 在 这 种 情 况 下, 当 填 满 标 准 I/O 缓 存 后 才 进 行 实 际 I/O 操 作 存 放 在 磁 盘 上 的 文 件 通 常 是 由 标 准 I/O 库 实 施 全 缓 冲 的 在 一 个 流 上 执 行 第 一 次 I/O 操 作 时, 通 常 调 用 malloc() 就 是 使 用 全 缓 冲 行 缓 冲 : 在 这 种 情 况 下, 当 在 输 入 和 输 出 中 遇 到 行 结 束 符 时, 标 准 I/O 库 执 行 I/O 操 作 这 允 许 我 们 一 次 输 出 一 个 字 符 ( 如 fputc() 函 数 ), 但 只 有 写 了 一 行 之 后 才 进 行 实 际 I/O 操 作 标 准 输 入 和 标 准 输 出 就 是 使 用 行 缓 冲 的 典 型 例 子 不 带 缓 冲 : 标 准 I/O 库 不 对 字 符 进 行 缓 冲 如 果 用 标 准 I/O 函 数 写 若 干 字 符 到 不 带 缓 冲 的 流 中, 则 相 当 于 用 系 统 调 用 write() 函 数 将 这 些 字 符 全 写 到 被 打 开 的 文 件 上 标 准 出 错 stderr 通 常 是 不 带 缓 存 的, 这 就 使 得 出 错 信 息 可 以 尽 快 显 示 出 来, 而 不 管 它 们 是 否 含 有 一 个 行 结 束 符 在 下 面 讨 论 具 体 函 数 时, 请 读 者 注 意 区 分 以 上 的 三 种 不 同 情 况 基 本 操 作 1. 打 开 文 件 (1) 函 数 说 明 打 开 文 件 有 三 个 标 准 函 数, 分 别 为 :fopen() fdopen() 和 freopen() 它 们 可 以 以 不 同 的 模 式 打 开, 但 都 返 回 一 个 指 向 FILE 的 指 针, 该 指 针 指 向 对 应 的 I/O 流 此 后, 对 文 件 的 读 写 都 是 通 过 这 个 FILE 指 针 来 进 行 其 中 fopen() 可 以 指 定 打 开 文 件 的 路 径 和 模 式,fdopen() 可 以 指 定 打 开 的 文 件 描 述 符 和 模 式, 而 freopen() 除 可 指 定 打 开 的 文 件 模 式 外, 还 可 指 定 特 定 的 I/O 流 (2) 函 数 格 式 定 义 fopen() 函 数 格 式 如 表 6.14 所 示 表 6.14 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <stdio.h> fopen() 函 数 语 法 要 点 FILE * fopen(const char * path, const char * mode) Path: 包 含 要 打 开 的 文 件 路 径 及 文 件 名 mode: 文 件 打 开 状 态 ( 后 面 会 具 体 说 明 ) 成 功 : 指 向 FILE 的 指 针 失 败 :NULL 其 中,mode 类 似 于 open() 函 数 中 的 flag, 可 以 定 义 打 开 文 件 的 访 问 权 限 等, 表 6.15 说 明 了 fopen() 中 mode 的 各 种 取 值 表 6.15 r 或 rb r+ 或 r+b W 或 wb w+ 或 w+b a 或 ab a+ 或 a+b mode 取 值 说 明 打 开 只 读 文 件, 该 文 件 必 须 存 在 打 开 可 读 写 的 文 件, 该 文 件 必 须 存 在 打 开 只 写 文 件, 若 文 件 存 在 则 文 件 长 度 清 为 0, 即 会 擦 写 文 件 以 前 的 内 容 若 文 件 不 存 在 则 建 立 该 文 件 打 开 可 读 写 文 件, 若 文 件 存 在 则 文 件 长 度 清 为 0, 即 会 擦 写 文 件 以 前 的 内 容 若 文 件 不 存 在 则 建 立 该 文 件 以 附 加 的 方 式 打 开 只 写 文 件 若 文 件 不 存 在, 则 会 建 立 该 文 件 ; 如 果 文 件 存 在, 写 入 的 数 据 会 被 加 到 文 件 尾, 即 文 件 原 先 的 内 容 会 被 保 留 以 附 加 方 式 打 开 可 读 写 的 文 件 若 文 件 不 存 在, 则 会 建 立 该 文 件 ; 如 果 文 件 存 在, 写 入 的 数 据 会 被 加 到 文 件 尾 后, 即 文 件 原 先 的 内 容 会 被 保 留 注 意 在 每 个 选 项 中 加 入 b 字 符 用 来 告 诉 函 数 库 打 开 的 文 件 为 二 进 制 文 件, 而 非 纯 文 本 文 件 不 过 在 Linux 系 统 中 会 自 动 识 别 不 同 类 型 的 文 件 而 将 此 符 号 忽 略 fdopen() 函 数 格 式 如 表 6.16 所 示 表 6.16 fdopen() 函 数 语 法 要 点 33
181 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <stdio.h> FILE * fdopen(int fd, const char * mode) fd: 要 打 开 的 文 件 描 述 符 mode: 文 件 打 开 状 态 ( 后 面 会 具 体 说 明 ) 成 功 : 指 向 FILE 的 指 针 失 败 :NULL freopen() 函 数 格 式 如 表 6.17 所 示 表 6.17 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <stdio.h> freopen() 函 数 语 法 要 点 FILE * freopen(const char *path, const char * mode, FILE * stream) path: 包 含 要 打 开 的 文 件 路 径 及 文 件 名 mode: 文 件 打 开 状 态 ( 后 面 会 具 体 说 明 ) stream: 已 打 开 的 文 件 指 针 成 功 : 指 向 FILE 的 指 针 失 败 :NULL 2. 关 闭 文 件 (1) 函 数 说 明 关 闭 标 准 流 文 件 的 函 数 为 fclose(), 该 函 数 将 缓 冲 区 内 的 数 据 全 部 写 入 到 文 件 中, 并 释 放 系 统 所 提 供 的 文 件 资 源 (2) 函 数 格 式 说 明 fclose() 函 数 格 式 如 表 6.18 所 示 表 6.18 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <stdio.h> int fclose(file * stream) stream: 已 打 开 的 文 件 指 针 成 功 :0 失 败 :EOF fclose() 函 数 语 法 要 点 3. 读 文 件 (1)fread() 函 数 说 明 在 文 件 流 被 打 开 之 后, 可 对 文 件 流 进 行 读 写 等 操 作, 其 中 读 操 作 的 函 数 为 fread() (2)fread() 函 数 格 式 fread() 函 数 格 式 如 表 6.19 所 示 表 6.19 所 需 头 文 件 函 数 原 型 函 数 传 入 值 fread() 函 数 语 法 要 点 #include <stdio.h> size_t fread(void * ptr,size_t size,size_t nmemb,file * stream) ptr: 存 放 读 入 记 录 的 缓 冲 区 size: 读 取 的 记 录 大 小 nmemb: 读 取 的 记 录 数 stream: 要 读 取 的 文 件 流 函 数 返 回 值 成 功 : 返 回 实 际 读 取 到 的 nmemb 数 目 34
182 失 败 :EOF 4. 写 文 件 (1)fwrite() 函 数 说 明 fwrite() 函 数 用 于 对 指 定 的 文 件 流 进 行 写 操 作 (2)fwrite() 函 数 格 式 fwrite() 函 数 格 式 如 表 6.20 所 示 表 6.20 所 需 头 文 件 函 数 原 型 #include <stdio.h> fwrite() 函 数 语 法 要 点 size_t fwrite(const void * ptr,size_t size, size_t nmemb, FILE * stream) 35
183 续 表 函 数 传 入 值 函 数 返 回 值 ptr: 存 放 写 入 记 录 的 缓 冲 区 size: 写 入 的 记 录 大 小 nmemb: 写 入 的 记 录 数 stream: 要 写 入 的 文 件 流 成 功 : 返 回 实 际 写 入 的 记 录 数 目 失 败 :EOF 5. 使 用 实 例 下 面 实 例 的 功 能 跟 底 层 I/O 操 作 的 实 例 基 本 相 同, 运 行 结 果 也 相 同 ( 请 参 考 节 的 实 例 ), 只 是 用 标 准 I/O 库 的 文 件 操 作 来 替 代 原 先 的 底 层 文 件 系 统 调 用 而 已 读 者 可 以 观 察 哪 种 方 法 的 效 率 更 高, 其 原 因 又 是 什 么 #include <stdlib.h> #include <stdio.h> #define BUFFER_SIZE 1024 /* 每 次 读 写 缓 存 大 小 */ #define SRC_FILE_NAME "src_file" /* 源 文 件 名 */ #define DEST_FILE_NAME "dest_file" /* 目 标 文 件 名 文 件 名 */ #define OFFSET /* 复 制 的 数 据 大 小 */ int main() FILE *src_file, *dest_file; unsigned char buff[buffer_size]; int real_read_len; /* 以 只 读 方 式 打 开 源 文 件 */ src_file = fopen(src_file_name, "r"); /* 以 写 方 式 打 开 目 标 文 件, 若 此 文 件 不 存 在 则 创 建 */ dest_file = fopen(dest_file_name, "w"); if (!src_file!dest_file) printf("open file error\n"); exit(1); /* 将 源 文 件 的 读 写 指 针 移 到 最 后 10KB 的 起 始 位 置 */ fseek(src_file, -OFFSET, SEEK_END); /* 读 取 源 文 件 的 最 后 10KB 数 据 并 写 到 目 标 文 件 中, 每 次 读 写 1KB */ while ((real_read_len = fread(buff, 1, sizeof(buff), src_file)) > 0) fwrite(buff, 1, real_read_len, dest_file); fclose(dest_file); fclose(src_file); return 0; 36
184 读 者 可 以 尝 试 用 其 他 文 件 打 开 函 数 进 行 练 习 其 他 操 作 文 件 打 开 之 后, 根 据 一 次 读 写 文 件 中 字 符 的 数 目 可 分 为 字 符 输 入 输 出 行 输 入 输 出 和 格 式 化 输 入 输 出, 下 面 分 别 对 这 3 种 不 同 的 方 式 进 行 讲 解 1. 字 符 输 入 输 出 字 符 输 入 输 出 函 数 一 次 仅 读 写 一 个 字 符 其 中 字 符 输 入 输 出 函 数 如 表 6.21 和 表 6.22 所 示 表 6.21 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 表 6.22 所 需 头 文 件 函 数 原 型 函 数 返 回 值 字 符 输 出 函 数 语 法 要 点 #include <stdio.h> int getc(file * stream) int fgetc(file * stream) int getchar(void) stream: 要 输 入 的 文 件 流 成 功 : 下 一 个 字 符 失 败 :EOF 字 符 输 入 函 数 语 法 要 点 #include <stdio.h> int putc(int c, FILE * stream) int fputc(int c, FILE * stream) int putchar(int c) 成 功 : 字 符 c 失 败 :EOF 这 几 个 函 数 功 能 类 似, 其 区 别 仅 在 于 getc() 和 putc() 通 常 被 实 现 为 宏, 而 fgetc() 和 fputc() 不 能 实 现 为 宏, 因 此, 函 数 的 实 现 时 间 会 有 所 差 别 下 面 这 个 实 例 结 合 fputc() 和 fgetc() 将 标 准 输 入 复 制 到 标 准 输 出 中 去 /*fput.c*/ #include<stdio.h> main() int c; /* 把 fgetc() 的 结 果 作 为 fputc() 的 输 入 */ fputc(fgetc(stdin), stdout); 运 行 结 果 如 下 所 示 : $./fput w( 用 户 输 入 ) w( 屏 幕 输 出 ) 2. 行 输 入 输 出 行 输 入 输 出 函 数 一 次 操 作 一 行 其 中 行 输 入 输 出 函 数 如 表 6.23 和 表 6.24 所 示 表 6.23 所 需 头 文 件 函 数 原 型 #include <stdio.h> 行 输 出 函 数 语 法 要 点 char * gets(char *s) char fgets(char * s, int size, FILE * stream) 37
185 函 数 传 入 值 函 数 返 回 值 s: 要 输 入 的 字 符 串 size: 输 入 的 字 符 串 长 度 stream: 对 应 的 文 件 流 成 功 :s 失 败 :NULL 表 6.24 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <stdio.h> 行 输 入 函 数 语 法 要 点 int puts(const char *s) int fputs(const char * s, FILE * stream) s: 要 输 出 的 字 符 串 stream: 对 应 的 文 件 流 成 功 :s 失 败 :NULL 这 里 以 gets() 和 puts() 为 例 进 行 说 明, 本 实 例 将 标 准 输 入 复 制 到 标 准 输 出, 如 下 所 示 : /*gets.c*/ #include<stdio.h> main() char s[80]; /* 同 上 例, 把 fgets() 的 结 果 作 为 fputs() 的 输 入 */ fputs(fgets(s, 80, stdin), stdout); 运 行 该 程 序, 结 果 如 下 所 示 : $./gets This is stdin( 用 户 输 入 ) This is stdin( 屏 幕 输 出 ) 3. 格 式 化 输 入 输 出 格 式 化 输 入 输 出 函 数 可 以 指 定 输 入 输 出 的 具 体 格 式, 这 里 有 读 者 已 经 非 常 熟 悉 的 printf() scanf() 等 函 数, 这 里 就 简 要 介 绍 一 下 它 们 的 格 式, 如 表 6.25~ 表 6.27 所 示 表 6.25 格 式 化 输 出 函 数 1 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <stdio.h> int printf(const char *format, ) int fprintf(file *fp, const char *format, ) int sprintf(char *buf, const char *format, ) format: 记 录 输 出 格 式 fp: 文 件 描 述 符 buf: 记 录 输 出 缓 冲 区 成 功 : 输 出 字 符 数 (sprintf 返 回 存 入 数 组 中 的 字 符 数 ) 失 败 :NULL 表 6.26 格 式 化 输 出 函 数 2 所 需 头 文 件 函 数 原 型 #include <stdarg.h> #include <stdio.h> int vprintf(const char *format, va_list arg) int vfprintf(file *fp, const char *format, va_list arg) int vsprintf(char *buf, const char *format, va_list arg) 38
186 函 数 传 入 值 函 数 返 回 值 表 6.27 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 format: 记 录 输 出 格 式 fp: 文 件 描 述 符 arg: 相 关 命 令 参 数 成 功 : 存 入 数 组 的 字 符 数 失 败 :NULL #include <stdio.h> 格 式 化 输 入 函 数 int scanf(const char *format, ) int fscanf(file *fp, const char *format, ) int sscanf(char *buf, const char *format, ) format: 记 录 输 出 格 式 fp: 文 件 描 述 符 buf: 记 录 输 入 缓 冲 区 成 功 : 输 出 字 符 数 (sprintf 返 回 存 入 数 组 中 的 字 符 数 ) 失 败 :NULL 由 于 本 节 的 函 数 用 法 比 较 简 单, 并 且 比 较 常 用, 因 此 就 不 再 举 例 了, 请 读 者 需 要 用 到 时 自 行 查 找 其 用 法 6.6 实 验 内 容 文 件 读 写 及 上 锁 1. 实 验 目 的 通 过 编 写 文 件 读 写 及 上 锁 的 程 序, 进 一 步 熟 悉 Linux 中 文 件 I/O 相 关 的 应 用 开 发, 并 且 熟 练 掌 握 open() read() write() fcntl() 等 函 数 的 使 用 2. 实 验 内 容 在 Linux 中 FIFO 是 一 种 进 程 之 间 的 管 道 通 信 机 制 Linux 支 持 完 整 的 FIFO 通 信 机 制 本 实 验 内 容 比 较 有 趣, 通 过 使 用 文 件 操 作, 仿 真 FIFO( 先 进 先 出 ) 结 构 以 及 生 产 者 - 消 费 者 运 行 模 型 本 实 验 中 需 要 打 开 两 个 虚 拟 终 端, 分 别 运 行 生 产 者 程 序 (producer) 和 消 费 者 程 序 (customer) 此 时 两 个 进 程 同 时 对 同 一 个 文 件 进 行 读 写 操 作 因 为 这 个 文 件 是 临 界 资 源, 所 以 可 以 使 用 文 件 锁 机 制 来 保 证 两 个 进 程 对 文 件 的 访 问 都 是 原 子 操 作 先 启 动 生 产 者 进 程, 它 负 责 创 建 仿 真 FIFO 结 构 的 文 件 ( 其 实 是 一 个 普 通 文 件 ) 并 投 入 生 产, 就 是 按 照 给 定 的 时 间 间 隔, 向 FIFO 文 件 写 入 自 动 生 成 的 字 符 ( 在 程 序 中 用 宏 定 义 选 择 使 用 数 字 还 是 使 用 英 文 字 符 ), 生 产 周 期 以 及 要 生 产 的 资 源 数 通 过 参 数 传 递 给 进 程 ( 默 认 生 产 周 期 为 1s, 要 生 产 的 资 源 数 为 10 个 字 符 ) 后 启 动 的 消 费 者 进 程 按 照 给 定 的 数 目 进 行 消 费, 首 先 从 文 件 中 读 取 相 应 数 目 的 字 符 并 在 屏 幕 上 显 示, 然 后 从 文 件 中 删 除 刚 才 消 费 过 的 数 据 为 了 仿 真 FIFO 结 构, 此 时 需 要 使 用 两 次 复 制 来 实 现 文 件 内 容 的 偏 移 每 次 消 费 的 资 源 数 通 过 参 数 传 递 给 进 程, 默 认 值 为 10 个 字 符 3. 实 验 步 骤 (1) 画 出 实 验 流 程 图 本 实 验 的 两 个 程 序 的 流 程 图 如 图 6.4 所 示 39
187 开 始 (producer) 开 始 (customer) 创 建 FIFO 结 构 文 件 消 费 资 源 ( 打 印 字 符 ) 否 生 产 一 个 资 源 消 费 够 了 吗? 是 等 待 一 秒 上 锁 将 生 产 的 字 符 写 入 到 FIFO 结 构 文 件 解 锁 上 锁 将 剩 下 的 数 据 拷 贝 到 临 时 文 件 tmp 中 用 临 时 文 件 tmp 覆 盖 原 数 据 文 件, 这 样 模 拟 FIFO 结 构 解 锁 生 产 完 了 吗? 删 除 临 时 文 件 结 束 图 6.4 节 流 程 图 结 束 (2) 编 写 代 码 本 实 验 中 的 生 产 者 程 序 的 源 代 码 如 下 所 示, 其 中 用 到 的 lock_set() 函 数 可 参 见 第 节 /* producer.c */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include "mylock.h" #define MAXLEN 10 /* 缓 冲 区 大 小 最 大 值 */ #define ALPHABET 1 /* 表 示 使 用 英 文 字 符 */ #define ALPHABET_START 'a' /* 头 一 个 字 符, 可 以 用 'A'*/ #define COUNT_OF_ALPHABET 26 /* 字 母 字 符 的 个 数 */ #define DIGIT 2 /* 表 示 使 用 数 字 字 符 */ #define DIGIT_START '0' /* 头 一 个 字 符 */ #define COUNT_OF_DIGIT 10 /* 数 字 字 符 的 个 数 */ #define SIGN_TYPE ALPHABET /* 本 实 例 选 用 英 文 字 符 */ const char *fifo_file = "./myfifo"; /* 仿 真 FIFO 文 件 名 */ char buff[maxlen]; /* 缓 冲 区 */ /* 功 能 : 生 产 一 个 字 符 并 写 入 仿 真 FIFO 文 件 中 */ int product(void) int fd; unsigned int sign_type, sign_start, sign_count, size; static unsigned int counter = 0; /* 打 开 仿 真 FIFO 文 件 */ 40
188 if ((fd = open(fifo_file, O_CREAT O_RDWR O_APPEND, 0644)) < 0) printf("open fifo file error\n"); exit(1); 专 业 始 于 专 注 卓 识 源 于 远 见 sign_type = SIGN_TYPE; switch(sign_type) case ALPHABET:/* 英 文 字 符 */ sign_start = ALPHABET_START; sign_count = COUNT_OF_ALPHABET; break; case DIGIT:/* 数 字 字 符 */ sign_start = DIGIT_START; sign_count = COUNT_OF_DIGIT; break; default: return -1; /*end of switch*/ sprintf(buff, "%c", (sign_start + counter)); counter = (counter + 1) % sign_count; lock_set(fd, F_WRLCK); /* 上 写 锁 */ if ((size = write(fd, buff, strlen(buff))) < 0) printf("producer: write error\n"); return -1; lock_set(fd, F_UNLCK); /* 解 锁 */ close(fd); return 0; int main(int argc,char *argv[]) int time_step = 1; /* 生 产 周 期 */ int time_life = 10; /* 需 要 生 产 的 资 源 数 */ if (argc > 1) 41
189 /* 第 一 个 参 数 表 示 生 产 周 期 */ sscanf(argv[1], "%d", &time_step); if (argc > 2) /* 第 二 个 参 数 表 示 需 要 生 产 的 资 源 数 */ sscanf(argv[2], "%d", &time_life); while (time_life--) if (product() < 0) break; sleep(time_step); exit(exit_success); 本 实 验 中 的 消 费 者 程 序 的 源 代 码 如 下 所 示 /* customer.c */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #define MAX_FILE_SIZE 100 * 1024 * 1024 /* 100M*/ const char *fifo_file = "./myfifo"; /* 仿 真 FIFO 文 件 名 */ const char *tmp_file = "./tmp"; /* 临 时 文 件 名 */ /* 资 源 消 费 函 数 */ int customing(const char *myfifo, int need) int fd; char buff; int counter = 0; if ((fd = open(myfifo, O_RDONLY)) < 0) printf("function customing error\n"); return -1; printf("enjoy:"); lseek(fd, SEEK_SET, 0); while (counter < need) while ((read(fd, &buff, 1) == 1) && (counter < need)) fputc(buff, stdout); /* 消 费 就 是 在 屏 幕 上 简 单 的 显 示 */ 42
190 counter++; 专 业 始 于 专 注 卓 识 源 于 远 见 fputs("\n", stdout); close(fd); return 0; /* 功 能 : 从 sour_file 文 件 的 offset 偏 移 处 开 始 将 count 个 字 节 数 据 复 制 到 dest_file 文 件 */ int myfilecopy(const char *sour_file, const char *dest_file, int offset, int count, int copy_mode) int in_file, out_file; int counter = 0; char buff_unit; if ((in_file = open(sour_file, O_RDONLY O_NONBLOCK)) < 0) printf("function myfilecopy error in source file\n"); return -1; if ((out_file = open(dest_file, O_CREAT O_RDWR O_TRUNC O_NONBLOCK, 0644)) < 0) printf("function myfilecopy error in destination file:"); return -1; lseek(in_file, offset, SEEK_SET); while ((read(in_file, &buff_unit, 1) == 1) && (counter < count)) write(out_file, &buff_unit, 1); counter++; close(in_file); close(out_file); return 0; /* 功 能 : 实 现 FIFO 消 费 者 */ int custom(int need) int fd; 43
191 /* 对 资 源 进 行 消 费,need 表 示 该 消 费 的 资 源 数 目 */ customing(fifo_file, need); if ((fd = open(fifo_file, O_RDWR)) < 0) printf("function myfilecopy error in source_file:"); return -1; /* 为 了 模 拟 FIFO 结 构, 对 整 个 文 件 内 容 进 行 平 行 移 动 */ lock_set(fd, F_WRLCK); myfilecopy(fifo_file, tmp_file, need, MAX_FILE_SIZE, 0); myfilecopy(tmp_file, fifo_file, 0, MAX_FILE_SIZE, 0); lock_set(fd, F_UNLCK); unlink(tmp_file); close(fd); return 0; int main(int argc,char *argv[]) int customer_capacity = 10; if (argc > 1) /* 第 一 个 参 数 指 定 需 要 消 费 的 资 源 数 目, 默 认 值 为 10 */ sscanf(argv[1], "%d", &customer_capacity); if (customer_capacity > 0) custom(customer_capacity); exit(exit_success); (3) 先 在 宿 主 机 上 编 译 该 程 序, 如 下 所 示 : $ make clean; make (4) 在 确 保 没 有 编 译 错 误 后, 交 叉 编 译 该 程 序, 此 时 需 要 修 改 Makefile 中 的 变 量 CC = arm-linux-gcc /* 修 改 Makefile 中 的 编 译 器 */ $ make clean; make (5) 将 生 成 的 可 执 行 程 序 下 载 到 目 标 板 上 运 行 4. 实 验 结 果 此 实 验 在 目 标 板 上 的 运 行 结 果 如 下 所 示 实 验 结 果 会 和 这 两 个 进 程 运 行 的 具 体 过 程 相 关, 希 望 读 者 能 具 体 分 析 每 种 情 况 下 面 列 出 其 中 一 种 情 况 : 终 端 一 : 44
192 $./producer 1 20 /* 生 产 周 期 为 1s, 需 要 生 产 的 资 源 数 为 20 个 */ Write lock set by Release lock by Write lock set by Release lock by 终 端 二 : $./customer 5 /* 需 要 消 费 的 资 源 数 为 5 个 */ Enjoy:abcde /* 消 费 资 源, 即 打 印 到 屏 幕 上 */ Write lock set by /* 为 了 仿 真 FIFO 结 构, 进 行 两 次 复 制 */ Release lock by 在 两 个 进 程 结 束 之 后, 仿 真 FIFO 文 件 的 内 容 如 下 : $ cat myfifo fghijklmnopqr /* a~e 的 5 个 字 符 已 经 被 消 费, 就 剩 下 后 面 15 个 字 符 */ 专 业 始 于 专 注 卓 识 源 于 远 见 多 路 复 用 式 串 口 操 作 1. 实 验 目 的 通 过 编 写 多 路 复 用 式 串 口 读 写, 进 一 步 理 解 多 路 复 用 函 数 的 用 法, 同 时 更 加 熟 练 掌 握 Linux 设 备 文 件 的 读 写 方 法 2. 实 验 内 容 本 实 验 主 要 实 现 两 台 机 器 ( 宿 主 机 和 目 标 板 ) 之 间 的 串 口 通 信, 每 台 机 器 都 可 以 发 送 和 接 收 数 据 除 了 串 口 设 备 名 称 不 同 ( 宿 主 机 上 使 用 串 口 1:/dev/ttyS0, 而 在 目 标 板 上 使 用 串 口 2:/dev/ttyS1), 两 台 机 器 上 的 程 序 基 本 相 同 3. 实 验 步 骤 (1) 画 出 流 程 图 如 图 6.5 所 示 为 程 序 流 程 图, 两 台 机 器 上 的 程 序 使 用 同 样 的 流 程 图 45
193 开 始 打 开 并 设 置 串 口 select 等 待 ( 终 端 / 串 口 ) 从 串 口 读 入 数 据 从 终 端 输 入 数 据 否 将 读 取 的 数 据 写 入 到 普 通 文 件 中 将 读 取 的 数 据 写 入 到 串 口 读 取 quit? 是 结 束 图 6.5 宿 主 机 / 目 标 板 程 序 的 流 程 图 (2) 编 写 代 码 编 写 宿 主 机 和 目 标 板 上 的 代 码, 在 这 些 程 序 中 用 到 的 open_port() 和 set_com_config() 函 数 请 参 照 6.4 节 这 里 只 列 出 宿 主 机 上 的 代 码 /* com_host.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <fcntl.h> #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include "uart_api.h" int main(void) int fds[sel_file_num], recv_fd, maxfd; char buff[buffer_size]; fd_set inset,tmp_inset; struct timeval tv; unsigned loop = 1; int res, real_read, i; /* 将 从 串 口 读 取 的 数 据 写 入 这 个 文 件 中 */ if ((recv_fd = open(recv_file_name, O_CREAT O_WRONLY, 0644)) < 0) perror("open"); return 1; fds[0] = STDIN_FILENO; /* 标 准 输 入 */ if ((fds[1] = open_port(host_com_port)) < 0) /* 打 开 串 口 */ 46
194 perror("open_port"); return 1; 专 业 始 于 专 注 卓 识 源 于 远 见 if (set_com_config(fds[1], , 8, 'N', 1) < 0) /* 配 置 串 口 */ perror("set_com_config"); return 1; FD_ZERO(&inset); FD_SET(fds[0], &inset); FD_SET(fds[1], &inset); maxfd = (fds[0] > fds[1])?fds[0]:fds[1]; tv.tv_sec = TIME_DELAY; tv.tv_usec = 0; printf("input some words(enter 'quit' to exit):\n"); while (loop && (FD_ISSET(fds[0], &inset) FD_ISSET(fds[1], &inset))) tmp_inset = inset; res = select(maxfd + 1, &tmp_inset, NULL, NULL, &tv); switch(res) case -1: perror("select"); loop = 0; break; case 0: /* Timeout */ perror("select time out"); loop = 0; break; default: for (i = 0; i < SEL_FILE_NUM; i++) if (FD_ISSET(fds[i], &tmp_inset)) memset(buff, 0, BUFFER_SIZE); /* 读 取 标 准 输 入 或 者 串 口 设 备 文 件 */ real_read = read(fds[i], buff, BUFFER_SIZE); 47
195 if ((real_read < 0) && (errno!= EAGAIN)) loop = 0; else if (!real_read) close(fds[i]); FD_CLR(fds[i], &inset); else buff[real_read] = '\0'; if (i == 0) /* 将 从 终 端 读 取 的 数 据 写 入 串 口 */ write(fds[1], buff, strlen(buff)); printf("input some words (enter 'quit' to exit):\n"); else if (i == 1) /* 将 从 串 口 读 取 的 数 据 写 入 普 通 文 件 中 */ write(recv_fd, buff, real_read); if (strncmp(buff, "quit", 4) == 0) 专 业 始 于 专 注 卓 识 源 于 远 见 /* 如 果 读 取 为 quit 则 退 出 */ loop = 0; /* end of if FD_ISSET */ /* for i */ /* end of switch */ /* end of while */ close(recv_fd); return 0; (3) 接 下 来, 将 目 标 板 的 串 口 程 序 交 叉 编 译, 再 将 宿 主 机 的 串 口 程 序 在 PC 机 上 编 译 (4) 连 接 PC 的 串 口 1 和 开 发 板 的 串 口 2 然 后 将 目 标 板 串 口 程 序 下 载 到 开 发 板 上, 分 别 在 两 台 机 器 上 运 行 串 口 程 序 4. 实 验 结 果 宿 主 机 上 的 运 行 结 果 如 下 所 示 : $./com_host Input some words(enter 'quit' to exit): Hello, Target! Input some words(enter 'quit' to exit): 48
196 I'm host program! Input some words(enter 'quit' to exit): Byebye! Input some words(enter 'quit' to exit): quit /* 这 个 输 入 使 双 方 的 程 序 都 结 束 */ 从 串 口 读 取 的 数 据 ( 即 目 标 板 中 发 送 过 来 的 数 据 ) 写 入 同 目 录 下 的 recv.dat 文 件 中 $ cat recv.dat Hello, Host! I'm target program! Byebye! 目 标 板 上 的 运 行 结 果 如 下 所 示 : $./com_target Input some words(enter 'quit' to exit): Hello, Host! Input some words(enter 'quit' to exit): I'm target program! Input some words(enter 'quit' to exit): Byebye! 与 宿 主 机 上 的 代 码 相 同, 从 串 口 读 取 的 数 据 ( 即 目 标 板 中 发 送 过 来 的 数 据 ) 写 入 同 目 录 下 的 recv.dat 文 件 中 $ cat recv.dat Hello, Target! I'm host program! Byebye! Quit 请 读 者 用 poll() 函 数 实 现 具 有 以 上 功 能 的 代 码 6.7 本 章 小 结 本 章 首 先 讲 解 了 系 统 调 用 (System Call) 用 户 函 数 接 口 (API) 和 系 统 命 令 之 间 的 联 系 和 区 别, 这 也 是 贯 穿 本 书 的 一 条 主 线, 本 书 就 是 按 照 系 统 命 令 用 户 函 数 接 口 (API) 系 统 调 用 的 顺 序 逐 层 深 入 讲 解, 希 望 读 者 能 有 一 个 较 为 深 刻 的 认 识 接 着, 本 章 讲 解 了 嵌 入 式 Linux 中 文 件 I/O 相 关 的 开 发, 在 这 里 主 要 讲 解 了 不 带 缓 存 的 I/O 系 统 调 用 函 数 的 使 用, 这 也 是 本 章 的 重 点, 其 中 主 要 讲 解 了 open() close() read() write() lseek() fcntl() select() 以 及 poll() 等 函 数 接 下 来, 本 章 讲 解 了 嵌 入 式 Linux 串 口 编 程 这 其 实 是 Linux 中 设 备 文 件 读 写 的 实 例, 由 于 它 能 很 好 地 体 现 前 面 所 介 绍 的 内 容, 而 且 在 嵌 入 式 开 发 中 也 较 为 常 见, 因 此 对 它 进 行 了 比 较 详 细 的 讲 解 之 后, 本 章 简 单 介 绍 了 标 准 I/O 的 相 关 函 数, 希 望 读 者 也 能 对 它 有 一 个 总 体 的 认 识 最 后, 本 章 安 排 了 两 个 实 验, 分 别 是 文 件 使 用 及 上 锁 和 多 用 复 用 串 口 操 作 希 望 读 者 能 够 认 真 完 成 6.8 思 考 与 练 习 使 用 多 路 复 用 函 数 实 现 3 个 串 口 的 通 信 : 串 口 1 接 收 数 据, 串 口 2 和 串 口 3 向 串 口 1 发 送 数 据 49
197 联 系 方 式 集 团 官 网 : 嵌 入 式 学 院 : 移 动 互 联 网 学 院 : 企 业 学 院 : 物 联 网 学 院 : 研 发 中 心 :dev.hqyj.com 集 团 总 部 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 内 华 清 远 见 教 育 集 团 北 京 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 区, 电 话 : /5 上 海 地 址 : 上 海 市 徐 汇 区 漕 溪 路 银 海 大 厦 A 座 8 层, 电 话 : 深 圳 地 址 : 深 圳 市 龙 华 新 区 人 民 北 路 美 丽 AAA 大 厦 15 层, 电 话 : 成 都 地 址 : 成 都 市 武 侯 区 科 华 北 路 99 号 科 华 大 厦 6 层, 电 话 : 南 京 地 址 : 南 京 市 白 下 区 汉 中 路 185 号 鸿 运 大 厦 10 层, 电 话 : 武 汉 地 址 : 武 汉 市 工 程 大 学 卓 刀 泉 校 区 科 技 孵 化 器 大 楼 8 层, 电 话 : 西 安 地 址 : 西 安 市 高 新 区 高 新 一 路 12 号 创 业 大 厦 D3 楼 5 层, 电 话 :
198 嵌 入 式 LINUX 应 用 程 序 开 发 标 准 教 程 作 者 : 华 清 远 见 第 7 章 进 程 控 制 开 发 掌 握 进 程 相 关 的 基 本 概 念 掌 握 Linux 下 的 进 程 结 构 掌 握 Linux 下 进 程 创 建 及 进 程 管 理 掌 握 Linux 下 进 程 创 建 相 关 的 系 统 调 用 掌 握 守 护 进 程 的 概 念 掌 握 守 护 进 程 的 启 动 方 法 掌 握 守 护 进 程 的 输 出 及 建 立 方 法 学 会 编 写 多 进 程 程 序 学 会 编 写 守 护 进 程
199 7.1 Linux 进 程 概 述 进 程 的 基 本 概 念 1. 进 程 的 定 义 进 程 的 概 念 首 先 是 在 20 世 纪 60 年 代 初 期 由 MIT 的 Multics 系 统 和 IBM 的 TSS/360 系 统 引 入 的 在 40 多 年 的 发 展 中, 人 们 对 进 程 有 过 各 种 各 样 的 定 义 现 列 举 较 为 著 名 的 几 种 (1) 进 程 是 一 个 独 立 的 可 调 度 的 活 动 (E. Cohen,D. Jofferson) (2) 进 程 是 一 个 抽 象 实 体, 当 它 执 行 某 个 任 务 时, 要 分 配 和 释 放 各 种 资 源 (P. Denning) (3) 进 程 是 可 以 并 行 执 行 的 计 算 单 位 (S. E. Madnick,J. T. Donovan) 以 上 进 程 的 概 念 都 不 相 同, 但 其 本 质 是 一 样 的 它 指 出 了 进 程 是 一 个 程 序 的 一 次 执 行 的 过 程, 同 时 也 是 资 源 分 配 的 最 小 单 元 它 和 程 序 是 有 本 质 区 别 的, 程 序 是 静 态 的, 它 是 一 些 保 存 在 磁 盘 上 的 指 令 的 有 序 集 合, 没 有 任 何 执 行 的 概 念 ; 而 进 程 是 一 个 动 态 的 概 念, 它 是 程 序 执 行 的 过 程, 包 括 了 动 态 创 建 调 度 和 消 亡 的 整 个 过 程 它 是 程 序 执 行 和 资 源 管 理 的 最 小 单 位 因 此, 对 系 统 而 言, 当 用 户 在 系 统 中 键 入 命 令 执 行 一 个 程 序 的 时 候, 它 将 启 动 一 个 进 程 2. 进 程 控 制 块 进 程 是 Linux 系 统 的 基 本 调 度 和 管 理 资 源 的 单 位, 那 么 从 系 统 的 角 度 看 如 何 描 述 并 表 示 它 的 变 化 呢? 在 这 里, 是 通 过 进 程 控 制 块 来 描 述 的 进 程 控 制 块 包 含 了 进 程 的 描 述 信 息 控 制 信 息 以 及 资 源 信 息, 它 是 进 程 的 一 个 静 态 描 述 在 Linux 中, 进 程 控 制 块 中 的 每 一 项 都 是 一 个 task_struct 结 构, 它 是 在 include/linux/sched.h 中 定 义 的 3. 进 程 的 标 识 在 Linux 中 最 主 要 的 进 程 标 识 有 进 程 号 (PID,Process Idenity Number) 和 它 的 父 进 程 号 (PPID,parent process ID) 其 中 PID 惟 一 地 标 识 一 个 进 程 PID 和 PPID 都 是 非 零 的 正 整 数 在 Linux 中 获 得 当 前 进 程 的 PID 和 PPID 的 系 统 调 用 函 数 为 getpid() 和 getppid(), 通 常 程 序 获 得 当 前 进 程 的 PID 和 PPID 之 后, 可 以 将 其 写 入 日 志 文 件 以 做 备 份 getpid() 和 getppid() 系 统 调 用 过 程 如 下 所 示 : /* pid.c */ #include<stdio.h> #include<unistd.h> #include <stdlib.h> int main() /* 获 得 当 前 进 程 的 进 程 ID 和 其 父 进 程 ID*/ printf("the PID of this process is %d\n", getpid()); printf("the PPID of this process is %d\n", getppid()); 使 用 arm-linux-gcc 进 行 交 叉 编 译, 再 将 其 下 载 到 目 标 板 上 运 行 该 程 序, 可 以 得 到 如 下 结 果, 该 值 在 不 同 的 系 统 上 会 有 所 不 同 : 2
200 $./pid The PID of this process is 78 THe PPID of this process is 36 另 外, 进 程 标 识 还 有 用 户 和 用 户 组 标 识 进 程 时 间 资 源 利 用 情 况 等, 这 里 就 不 做 一 一 介 绍, 感 兴 趣 的 读 者 可 以 参 见 W.Richard Stevens 编 著 的 Advanced Programming in the UNIX Environmen 4. 进 程 运 行 的 状 态 进 程 是 程 序 的 执 行 过 程, 根 据 它 的 生 命 周 期 可 以 划 分 成 3 种 状 态 执 行 态 : 该 进 程 正 在 运 行, 即 进 程 正 在 占 用 CPU 就 绪 态 : 进 程 已 经 具 备 执 行 的 一 切 条 件, 正 在 等 待 分 配 CPU 的 处 理 时 间 片 等 待 态 : 进 程 不 能 使 用 CPU, 若 等 待 事 件 发 生 ( 等 待 的 资 源 分 配 到 ) 则 可 将 其 唤 醒 它 们 之 间 转 换 的 关 系 如 图 7.1 所 示 图 7.1 进 程 3 种 状 态 的 转 化 关 系 Linux 下 的 进 程 结 构 Linux 系 统 是 一 个 多 进 程 的 系 统, 它 的 进 程 之 间 具 有 并 行 性 互 不 干 扰 等 特 点 也 就 是 说, 每 个 进 程 都 是 一 个 独 立 的 运 行 单 位, 拥 有 各 自 的 权 利 和 责 任 其 中, 各 个 进 在 独 立 的 虚 拟 地 址 空 间, 因 此, 即 使 一 个 进 程 发 生 异 常, 它 也 到 系 统 中 的 其 他 进 程 程 都 运 行 不 会 影 响 Linux 中 的 进 程 包 含 3 个 段, 分 别 为 数 据 段 代 码 段 和 堆 栈 段 数 据 段 存 放 的 是 全 局 变 量 常 数 以 及 动 态 数 据 分 配 的 数 据 空 间, 根 据 存 放 的 数 据, 数 据 段 又 可 以 分 成 普 通 括 可 读 可 写 / 只 读 数 据 段, 存 放 静 态 初 始 化 的 全 局 量 ) BSS 数 据 段 ( 存 放 未 初 始 化 的 全 局 变 量 ) 以 动 态 分 配 的 数 据 ) 代 码 段 存 放 的 是 程 序 代 码 的 数 据 数 据 段 ( 包 变 量 或 常 及 堆 ( 存 放 堆 栈 段 存 放 的 是 子 程 序 的 返 回 地 址 子 程 序 的 参 数 以 及 程 序 的 局 部 变 量 等 如 图 7.2 所 示 图 7.2 Linux 中 进 程 结 构 示 意 图 Linux 下 进 程 的 模 式 和 类 型 高 地 址 存 放 传 递 参 数 及 环 境 变 量 堆 栈 低 地 址 堆 BSS 数 据 段 数 据 段 ( 可 读 / 只 读 ) 数 据 段 代 码 段 在 Linux 系 统 中, 进 程 的 执 行 模 式 划 分 为 用 户 模 式 和 内 核 模 式 如 果 当 前 运 行 的 是 用 户 程 序 应 用 程 序 或 者 内 核 之 外 的 系 统 程 序, 那 么 对 应 进 程 就 在 用 户 模 式 下 运 行 ; 如 果 在 用 户 程 序 执 行 过 程 中 出 现 系 统 调 用 或 者 发 生 中 断 事 件, 那 么 就 要 运 行 操 作 系 统 ( 即 核 心 ) 程 序, 进 程 模 式 就 变 成 内 核 模 式 在 内 核 模 式 下 运 行 的 进 程 可 以 执 行 机 器 的 特 权 指 令, 而 且 此 时 该 进 程 的 运 行 不 受 用 户 的 干 扰, 即 使 是 root 用 户 也 不 能 干 扰 内 核 模 式 下 进 程 的 运 行 用 户 进 程 既 可 以 在 用 户 模 式 下 运 行, 也 可 以 在 内 核 模 式 下 运 行, 如 图 7.3 所 示 3
201 用 户 进 程 用 户 态 中 断 或 系 统 调 用 专 业 始 于 专 注 卓 识 源 于 远 见 Linux 下 的 进 程 管 理 内 核 进 程 内 核 态 Linux 下 的 进 程 管 理 包 括 启 动 进 程 和 调 度 进 程, 下 面 就 两 方 面 进 行 简 要 讲 解 图 7.3 用 户 进 程 的 两 种 运 行 模 式 分 别 对 这 1. 启 动 进 程 Linux 下 启 动 一 个 进 程 有 两 种 主 要 途 径 : 手 工 启 动 和 调 度 启 动 手 工 启 动 是 由 用 户 输 入 命 令 直 接 启 动 进 程, 而 调 度 启 动 是 指 系 统 根 据 用 户 的 设 置 自 行 启 动 进 程 (1) 手 工 启 动 手 工 启 动 进 程 又 可 分 为 前 台 启 动 和 后 台 启 动 前 台 启 动 是 手 工 启 动 一 个 进 程 的 最 常 用 方 式 一 般 地, 当 用 户 键 入 一 个 命 令 如 ls -l 时, 就 已 经 启 动 了 一 个 进 程, 并 且 是 一 个 前 台 的 进 程 后 台 启 动 往 往 是 在 该 进 程 非 常 耗 时, 且 用 户 也 不 急 着 需 要 结 果 的 时 候 启 动 的 比 如 用 户 要 启 动 一 个 需 要 长 时 间 运 行 的 格 式 化 文 本 文 件 的 进 程 为 了 不 使 整 个 shell 在 格 式 化 过 程 中 都 处 于 瘫 痪 状 态, 从 后 台 启 动 这 个 进 程 是 明 智 的 选 择 (2) 调 度 启 动 有 时, 系 统 需 要 进 行 一 些 比 较 费 时 而 且 占 用 资 源 的 维 护 工 作, 并 且 这 些 工 作 适 合 在 深 夜 无 人 值 守 的 时 候 进 行, 这 时 用 户 就 可 以 事 先 进 行 调 度 安 排, 指 定 任 务 运 行 的 时 间 或 者 场 合, 到 时 候 系 统 就 会 自 动 完 成 这 一 切 工 作 使 用 调 度 启 动 进 程 有 几 个 常 用 的 命 令, 如 at 命 令 在 指 定 时 刻 执 行 相 关 进 程,cron 命 令 可 以 自 动 周 期 性 地 执 行 相 关 进 程, 在 需 要 使 用 时 读 者 可 以 查 看 相 关 帮 助 手 册 2. 调 度 进 程 调 度 进 程 包 括 对 进 程 的 中 断 操 作 改 变 优 先 级 查 看 进 程 状 态 等, 在 Linux 下 可 以 使 用 相 关 的 系 统 命 令 实 现 其 操 作, 在 表 7.1 中 列 出 了 Linux 中 常 见 的 调 用 进 程 的 系 统 命 令, 读 者 在 需 要 的 时 候 可 以 自 行 查 找 其 用 法 表 7.1 Linux 中 进 程 调 度 常 见 命 令 选 项 参 数 含 义 ps top nice renice 查 看 系 统 中 的 进 程 动 态 显 示 系 统 中 的 进 程 按 用 户 指 定 的 优 先 级 运 行 改 变 正 在 运 行 进 程 的 优 先 级 kill 向 进 程 发 送 信 号 ( 包 括 后 台 进 程 ) crontab 用 于 安 装 删 除 或 者 列 出 用 于 驱 动 cron 后 台 进 程 的 任 务 bg 将 挂 起 的 进 程 放 到 后 台 执 行 4
202 7.2 Linux 进 程 控 制 编 程 1.fork() 在 Linux 中 创 建 一 个 新 进 程 的 惟 一 方 法 是 使 用 fork() 函 数 fork() 函 数 是 Linux 中 一 个 非 常 重 要 的 函 数, 和 读 者 以 往 遇 到 的 函 数 有 一 些 区 别, 因 为 它 看 起 来 执 行 一 次 却 返 回 两 个 值 难 道 一 个 函 数 真 的 能 返 回 两 个 值 吗? 希 望 读 者 能 认 真 地 学 习 这 一 部 分 的 内 容 (1)fork() 函 数 说 明 fork() 函 数 用 于 从 已 存 在 的 进 程 中 创 建 一 个 新 进 程 新 进 程 称 为 子 进 程, 而 原 进 程 称 为 父 进 程 使 用 fork() 函 数 得 到 的 子 进 程 是 父 进 程 的 一 个 复 制 品, 它 从 父 进 程 处 继 承 了 整 个 进 程 的 地 址 空 间, 包 括 进 程 上 下 文 代 码 段 进 程 堆 栈 内 存 信 息 打 开 的 文 件 描 述 符 信 号 控 制 设 定 进 程 优 先 级 进 程 组 号 当 前 工 作 目 录 根 目 录 资 源 限 制 和 控 制 终 端 等, 而 子 进 程 所 独 有 的 只 有 它 的 进 程 号 资 源 使 用 和 计 时 器 等 因 为 子 进 程 几 乎 是 父 进 程 的 完 全 复 制, 所 以 父 子 两 个 进 程 会 运 行 同 一 个 程 序 因 此 需 要 用 一 种 方 式 来 区 分 它 们, 并 使 它 们 照 此 运 行, 否 则, 这 两 个 进 程 不 可 能 做 不 同 的 事 实 际 上 是 在 父 进 程 中 执 行 fork() 函 数 时, 父 进 程 会 复 制 出 一 个 子 进 程, 而 且 父 子 进 程 的 代 码 从 fork() 函 数 的 返 回 开 始 分 别 在 两 个 地 址 空 间 中 同 时 运 行 从 而 两 个 进 程 分 别 获 得 其 所 属 fork() 的 返 回 值, 其 中 在 父 进 程 中 的 返 回 值 是 子 进 程 的 进 程 号, 而 在 子 进 程 中 返 回 0 因 此, 可 以 通 过 返 回 值 来 判 定 该 进 程 是 父 进 程 还 是 子 进 程 同 时 可 以 看 出, 使 用 fork() 函 数 的 代 价 是 很 大 的, 它 复 制 了 父 进 程 中 的 代 码 段 数 据 段 和 堆 栈 段 里 的 大 部 分 内 容, 使 得 fork() 函 数 的 系 统 开 销 比 较 大, 而 且 执 行 速 度 也 不 是 很 快 (2)fork() 函 数 语 法 表 7.2 列 出 了 fork() 函 数 的 语 法 要 点 表 7.2 所 需 头 文 件 函 数 原 型 fork() 函 数 语 法 要 点 #include <sys/types.h> // 提 供 类 型 pid_t 的 定 义 #include <unistd.h> pid_t fork(void) 0: 子 进 程 函 数 返 回 值 子 进 程 ID( 大 于 0 的 整 数 ): 父 进 程 1: 出 错 (3)fork() 函 数 使 用 实 例 /* fork.c */ #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main(void) pid_t result; /* 调 用 fork() 函 数 */ result = fork(); /* 通 过 result 的 值 来 判 断 fork() 函 数 的 返 回 情 况, 首 先 进 行 出 错 处 理 */ 5
203 if(result == -1) printf("fork error\n"); else if (result == 0) /* 返 回 值 为 0 代 表 子 进 程 */ printf("the returned value is %d\n In child process!!\nmy PID is %d\n",result,getpid()); else /* 返 回 值 大 于 0 代 表 父 进 程 */ printf("the returned value is %d\n In father process!!\nmy PID is %d\n",result,getpid()); return result; 将 可 执 行 程 序 下 载 到 目 标 板 上, 运 行 结 果 如 下 所 示 : $ arm-linux-gcc fork.c o fork ( 或 者 修 改 Makefile) $./fork The returned value is 76 /* 在 父 进 程 中 打 印 的 信 息 */ In father process!! My PID is 75 The returned value is :0 /* 在 子 进 程 中 打 印 的 信 息 */ In child process!! My PID is 76 从 该 实 例 中 可 以 看 出, 使 用 fork() 函 数 新 建 了 一 个 子 进 程, 其 中 的 父 进 程 返 回 子 进 程 的 PID, 而 子 进 程 的 返 回 值 为 0 (4) 函 数 使 用 注 意 点 fork() 函 数 使 用 一 次 就 创 建 一 个 进 程, 所 以 若 把 fork() 函 数 放 在 了 if else 判 断 语 句 中 则 要 小 心, 不 能 多 次 使 用 fork() 函 数 由 于 fork() 完 整 地 复 制 了 父 进 程 的 整 个 地 址 空 间, 因 此 执 行 速 度 是 比 较 慢 的 为 了 加 快 fork() 的 执 行 速 度, 有 些 UNIX 系 统 设 计 者 创 建 了 vfork() vfork() 也 能 创 建 新 进 程, 但 它 不 产 生 父 进 程 的 副 本 它 是 通 过 允 许 父 子 进 程 可 访 问 相 同 物 理 内 存 从 而 伪 装 了 对 进 程 地 址 空 间 的 真 实 拷 贝, 当 子 进 程 需 要 改 变 内 存 中 数 据 时 才 复 制 父 进 程 这 就 是 著 名 的 写 操 作 时 复 制 (copy-on-write) 技 术 现 在 很 多 嵌 入 式 Linux 系 统 的 fork() 函 数 调 用 都 采 用 vfork() 函 数 的 实 现 方 式, 实 际 上 uclinux 所 有 的 多 进 程 管 理 都 通 过 vfork() 来 实 现 2.exec 函 数 族 (1)exec 函 数 族 说 明 fork() 函 数 是 用 于 创 建 一 个 子 进 程, 该 子 进 程 几 乎 复 制 了 父 进 程 的 全 部 内 容, 但 是, 这 个 新 创 建 的 进 程 如 何 执 行 呢? 这 个 exec 函 数 族 就 提 供 了 一 个 在 进 程 中 启 动 另 一 个 程 序 执 行 的 方 法 它 可 以 根 据 指 定 的 文 件 名 或 目 录 名 找 到 可 执 行 文 件, 并 用 它 来 取 代 原 调 用 进 程 的 数 据 段 代 码 段 和 堆 栈 段, 在 执 行 完 之 后, 原 调 用 进 6
204 程 的 内 容 除 了 进 程 号 外, 其 他 全 部 被 新 的 进 程 替 换 了 另 外, 这 里 的 可 执 行 文 件 既 可 以 是 二 进 制 文 件, 也 可 以 是 Linux 下 任 何 可 执 行 的 脚 本 文 件 在 Linux 中 使 用 exec 函 数 族 主 要 有 两 种 情 况 当 进 程 认 为 自 己 不 能 再 为 系 统 和 用 户 做 出 任 何 贡 献 时, 就 可 以 调 用 exec 函 数 族 中 的 任 意 一 个 函 数 让 自 己 重 生 如 果 一 个 进 程 想 执 行 另 一 个 程 序, 那 么 它 就 可 以 调 用 fork() 函 数 新 建 一 个 进 程, 然 后 调 用 exec 函 数 族 中 的 任 意 一 个 函 数, 这 样 看 起 来 就 像 通 过 执 行 应 用 程 序 而 产 生 了 一 个 新 进 程 ( 这 种 情 况 非 常 普 遍 ) (2)exec 函 数 族 语 法 实 际 上, 在 Linux 中 并 没 有 exec() 函 数, 而 是 有 6 个 以 exec 开 头 的 函 数, 它 们 之 间 语 法 有 细 微 差 别, 本 书 在 下 面 会 详 细 讲 解 下 表 7.3 列 举 了 exec 函 数 族 的 6 个 成 员 函 数 的 语 法 表 7.3 所 需 头 文 件 #include <unistd.h> exec 函 数 族 成 员 函 数 语 法 int execl(const char *path, const char *arg,...) int execv(const char *path, char *const argv[]) 函 数 原 型 int execle(const char *path, const char *arg,..., char *const envp[]) int execve(const char *path, char *const argv[], char *const envp[]) int execlp(const char *file, const char *arg,...) int execvp(const char *file, char *const argv[]) 函 数 返 回 值 1: 出 错 这 6 个 函 数 在 函 数 名 和 使 用 语 法 的 规 则 上 都 有 细 微 的 区 别, 下 面 就 可 执 行 文 件 查 找 方 式 参 数 表 传 递 方 式 及 环 境 变 量 这 几 个 方 面 进 行 比 较 查 找 方 式 读 者 可 以 注 意 到, 表 7.3 中 的 前 4 个 函 数 的 查 找 方 式 都 是 完 整 的 文 件 目 录 路 径, 而 最 后 2 个 函 数 ( 也 就 是 以 p 结 尾 的 两 个 函 数 ) 可 以 只 给 出 文 件 名, 系 统 就 会 自 动 按 照 环 境 变 量 $PATH 所 指 定 的 路 径 进 行 查 找 参 数 传 递 方 式 exec 函 数 族 的 参 数 传 递 有 两 种 方 式 : 一 种 是 逐 个 列 举 的 方 式, 而 另 一 种 则 是 将 所 有 参 数 整 体 构 造 指 针 数 组 传 递 在 这 里 是 以 函 数 名 的 第 5 位 字 母 来 区 分 的, 字 母 为 l (list) 的 表 示 逐 个 列 举 参 数 的 方 式, 其 语 法 为 char *arg; 字 母 为 v (vertor) 的 表 示 将 所 有 参 数 整 体 构 造 指 针 数 组 传 递, 其 语 法 为 *const argv[] 读 者 可 以 观 察 execl() execle() execlp() 的 语 法 与 execv() execve() execvp() 的 区 别 它 们 具 体 的 用 法 在 后 面 的 实 例 讲 解 中 会 具 体 说 明 这 里 的 参 数 实 际 上 就 是 用 户 在 使 用 这 个 可 执 行 文 件 时 所 需 的 全 部 命 令 选 项 字 符 串 ( 包 括 该 可 执 行 程 序 命 令 本 身 ) 要 注 意 的 是, 这 些 参 数 必 须 以 NULL 表 示 结 束, 如 果 使 用 逐 个 列 举 方 式, 那 么 要 把 它 强 制 转 化 成 一 个 字 符 指 针, 否 则 exec 将 会 把 它 解 释 为 一 个 整 型 参 数, 如 果 一 个 整 型 数 的 长 度 char * 的 长 度 不 同, 那 么 exec 函 数 就 会 报 错 环 境 变 量 exec 函 数 族 可 以 默 认 系 统 的 环 境 变 量, 也 可 以 传 入 指 定 的 环 境 变 量 这 里 以 e (environment) 结 尾 的 两 个 函 数 execle() 和 execve() 就 可 以 在 envp[] 中 指 定 当 前 进 程 所 使 用 的 环 境 变 量 表 7.4 是 对 这 4 个 函 数 中 函 数 名 和 对 应 语 法 的 小 结, 主 要 指 出 了 函 数 名 中 每 一 位 所 表 明 的 含 义, 希 望 读 者 结 合 此 表 加 以 记 忆 表 7.4 exec 函 数 名 对 应 含 义 前 4 位 统 一 为 :exec 第 5 位 l: 参 数 传 递 为 逐 个 列 举 方 式 execl execle execlp 7
205 v: 参 数 传 递 为 构 造 指 针 数 组 方 式 execv execve execvp 专 业 始 于 专 注 卓 识 源 于 远 见 第 6 位 e: 可 传 递 新 进 程 环 境 变 量 execle execve p: 可 执 行 文 件 查 找 方 式 为 文 件 名 execlp execvp (3)exec 使 用 实 例 下 面 的 第 一 个 示 例 说 明 了 如 何 使 用 文 件 名 的 方 式 来 查 找 可 执 行 文 件, 同 时 使 用 参 数 列 表 的 方 式 这 里 用 的 函 数 是 execlp() /*execlp.c*/ #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() if (fork() == 0) /* 调 用 execlp() 函 数, 这 里 相 当 于 调 用 了 "ps -ef" 命 令 */ if ((ret = execlp("ps", "ps", "-ef", NULL)) < 0) printf("execlp error\n"); 在 该 程 序 中, 首 先 使 用 fork() 函 数 创 建 一 个 子 进 程, 然 后 在 子 进 程 里 使 用 execlp() 函 数 读 者 可 以 看 到, 这 里 的 参 数 列 表 列 出 了 在 shell 中 使 用 的 命 令 名 和 选 项 并 且 当 使 用 文 件 名 进 行 查 找 时, 系 统 会 在 默 认 的 环 境 变 量 PATH 中 寻 找 该 可 执 行 文 件 读 者 可 将 编 译 后 的 结 果 下 载 到 目 标 板 上, 运 行 结 果 如 下 所 示 : $./execlp PID TTY Uid Size State Command 1 root 1832 S init 2 root 0 S [keventd] 3 root 0 S [ksoftirqd_cpu0] 4 root 0 S [kswapd] 5 root 0 S [bdflush] 6 root 0 S [kupdated] 7 root 0 S [mtdblockd] 8 root 0 S [khubd] 35 root 2104 S /bin/bash /usr/etc/rc.local 36 root 2324 S /bin/bash 41 root 1364 S /sbin/inetd 53 root S /Qtopia/qtopia-free-1.7.0/bin/qpe -qws 54 root S quicklauncher 65 root 0 S [usb-storage-0] 66 root 0 S [scsi_eh_0] 83 root 2020 R ps -ef $ env 8
206 PATH=/Qtopia/qtopia-free-1.7.0/bin:/usr/bin:/bin:/usr/sbin:/sbin 此 程 序 的 运 行 结 果 与 在 shell 中 直 接 键 入 命 令 ps -ef 是 一 样 的, 当 然, 在 不 同 系 统 的 不 同 时 刻 都 可 能 会 有 不 同 的 结 果 接 下 来 的 示 例 使 用 完 整 的 文 件 目 录 来 查 找 对 应 的 可 执 行 文 件 注 意 目 录 必 须 以 / 开 头, 否 则 将 其 视 为 文 件 名 /*execl.c*/ #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() if (fork() == 0) /* 调 用 execl() 函 数, 注 意 这 里 要 给 出 ps 程 序 所 在 的 完 整 路 径 */ if (execl("/bin/ps","ps","-ef",null) < 0) printf("execl error\n"); 同 样 下 载 到 目 标 板 上 运 行, 运 行 结 果 同 上 例 下 面 的 示 例 利 用 函 数 execle(), 将 环 境 变 量 添 加 到 新 建 的 子 进 程 中, 这 里 的 env 是 查 看 当 前 进 程 环 境 变 量 的 命 令, 如 下 所 示 : /* execle.c */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() /* 命 令 参 数 列 表, 必 须 以 NULL 结 尾 */ char *envp[]="path=/tmp","user=david", NULL; if (fork() == 0) /* 调 用 execle() 函 数, 注 意 这 里 也 要 指 出 env 的 完 整 路 径 */ if (execle("/usr/bin/env", "env", NULL, envp) < 0) printf("execle error\n"); 下 载 到 目 标 板 后 的 运 行 结 果 如 下 所 示 : 9
207 $./execle PATH=/tmp USER=sunq 专 业 始 于 专 注 卓 识 源 于 远 见 最 后 一 个 示 例 使 用 execve() 函 数, 通 过 构 造 指 针 数 组 的 方 式 来 传 递 参 数, 注 意 参 数 列 表 一 定 要 以 NULL 作 为 结 尾 标 识 符 其 代 码 和 运 行 结 果 如 下 所 示 : #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() /* 命 令 参 数 列 表, 必 须 以 NULL 结 尾 */ char *arg[] = "env", NULL; char *envp[] = "PATH=/tmp", "USER=david", NULL; if (fork() == 0) if (execve("/usr/bin/env", arg, envp) < 0) printf("execve error\n"); 下 载 到 目 标 板 后 的 运 行 结 果 如 下 所 示 : $./execve PATH=/tmp USER=david (4)exec 函 数 族 使 用 注 意 点 在 使 用 exec 函 数 族 时, 一 定 要 加 上 错 误 判 断 语 句 exec 很 容 易 执 行 失 败, 其 中 最 常 见 的 原 因 有 : 找 不 到 文 件 或 路 径, 此 时 errno 被 设 置 为 ENOENT; 数 组 argv 和 envp 忘 记 用 NULL 结 束, 此 时 errno 被 设 置 为 EFAULT; 没 有 对 应 可 执 行 文 件 的 运 行 权 限, 此 时 errno 被 设 置 为 EACCES 事 实 上, 这 6 个 函 数 中 真 正 的 系 统 调 用 只 有 execve(), 其 他 5 个 都 是 库 函 数, 它 们 最 终 都 会 调 用 execve() 这 个 系 统 调 用 3.exit() 和 _exit() (1)exit() 和 _exit() 函 数 说 明 exit() 和 _exit() 函 数 都 是 用 来 终 止 进 程 的 当 程 序 执 行 到 exit() 或 _exit() 时, 进 程 会 无 条 件 地 停 止 剩 下 的 所 有 操 作, 清 除 包 括 PCB 在 内 的 各 种 数 据 结 构, 并 终 止 本 进 程 的 运 行 但 是, 这 两 个 函 数 还 是 有 区 别 的, 这 两 个 函 数 的 调 用 过 程 如 图 7.4 所 示 从 图 中 可 以 看 出,_exit() 函 数 的 作 用 是 : 直 接 使 进 程 停 止 运 行, 的 内 存 空 间, 并 清 除 其 在 内 核 中 的 各 种 数 据 结 构 ;exit() 函 数 则 进 程 运 行 调 用 退 出 处 理 函 数 exit() _exit() 清 理 I/O 缓 冲 调 用 exit 系 统 调 用 清 除 其 使 用 在 这 些 基 础 进 程 终 止 运 行 图 7.4 exit 和 _exit 函 数 流 程 图 10
208 上 做 了 一 些 包 装, 在 执 行 退 出 之 前 加 了 若 干 道 工 序 exit() 函 数 与 _exit() 函 数 最 大 的 区 别 就 在 于 exit() 函 数 在 调 用 exit 系 统 之 前 要 检 查 文 件 的 打 开 情 况, 把 文 件 缓 冲 区 中 的 内 容 写 回 文 件, 就 是 图 中 的 清 理 I/O 缓 冲 一 项 由 于 在 Linux 的 标 准 函 数 库 中, 有 一 种 被 称 作 缓 冲 I/O(buffered I/O) 操 作, 其 特 征 就 是 对 应 每 一 个 打 开 的 文 件, 在 内 存 中 都 有 一 片 缓 冲 区 每 次 读 文 件 时, 会 连 续 读 出 若 干 条 记 录, 这 样 在 下 次 读 文 件 时 就 可 以 直 接 从 内 存 的 缓 冲 区 中 读 取 ; 同 样, 每 次 写 文 件 的 时 候, 也 仅 仅 是 写 入 内 存 中 的 缓 冲 区, 等 满 足 了 一 定 的 条 件 ( 如 达 到 一 定 数 量 或 遇 到 特 定 字 符 等 ), 再 将 缓 冲 区 中 的 内 容 一 次 性 写 入 文 件 这 种 技 术 大 大 增 加 了 文 件 读 写 的 速 度, 但 也 为 编 程 带 来 了 一 些 麻 烦 比 如 有 些 数 据, 认 为 已 经 被 写 入 文 件 中, 实 际 上 因 为 没 有 满 足 特 定 的 条 件, 它 们 还 只 是 被 保 存 在 缓 冲 区 内, 这 时 用 _exit() 函 数 直 接 将 进 程 关 闭, 缓 冲 区 中 的 数 据 就 会 丢 失 因 此, 若 想 保 证 数 据 的 完 整 性, 就 一 定 要 使 用 exit() 函 数 (2)exit() 和 _exit() 函 数 语 法 表 7.5 列 出 了 exit() 和 _exit() 函 数 的 语 法 规 范 表 7.5 所 需 头 文 件 函 数 原 型 函 数 传 入 值 exit:#include <stdlib.h> _exit:#include <unistd.h> exit:void exit(int status) _exit:void _exit(int status) exit() 和 _exit() 函 数 族 语 法 status 是 一 个 整 型 的 参 数, 可 以 利 用 这 个 参 数 传 递 进 程 结 束 时 的 状 态 一 般 来 说, 0 表 示 正 常 结 束 ; 其 他 的 数 值 表 示 出 现 了 错 误, 进 程 非 正 常 结 束 在 实 际 编 程 时, 可 以 用 wait() 系 统 调 用 接 收 子 进 程 的 返 回 值, 从 而 针 对 不 同 的 情 况 进 行 不 同 的 处 理 (3)exit() 和 _exit() 使 用 实 例 这 两 个 示 例 比 较 了 exit() 和 _exit() 两 个 函 数 的 区 别 由 于 printf() 函 数 使 用 的 是 缓 冲 I/O 方 式, 该 函 数 在 遇 到 \n 换 行 符 时 自 动 从 缓 冲 区 中 将 记 录 读 出 示 例 中 就 是 利 用 这 个 性 质 来 进 行 比 较 的 以 下 是 示 例 1 的 代 码 : /* exit.c */ #include <stdio.h> #include <stdlib.h> int main() printf("using exit...\n"); printf("this is the content in buffer"); exit(0); $./exit Using exit... This is the content in buffer $ 读 者 从 输 出 的 结 果 中 可 以 看 到, 调 用 exit() 函 数 时, 缓 冲 区 中 的 记 录 也 能 正 常 输 出 以 下 是 示 例 2 的 代 码 : /* _exit.c */ #include <stdio.h> #include <unistd.h> int main() 11
209 printf("using _exit...\n"); printf("this is the content in buffer"); /* 加 上 回 车 符 之 后 结 果 又 如 何 */ _exit(0); $./_exit Using _exit... $ 读 者 从 最 后 的 结 果 中 可 以 看 到, 调 用 _exit() 函 数 无 法 输 出 缓 冲 区 中 的 记 录 在 一 个 进 程 调 用 了 exit() 之 后, 该 进 程 并 不 会 立 刻 完 全 消 失, 而 是 留 下 一 个 称 为 僵 尸 进 程 (Zombie) 的 数 据 结 构 僵 尸 进 程 是 一 种 非 常 特 殊 的 进 程, 它 已 经 放 弃 了 几 乎 所 有 的 内 存 空 间, 没 有 任 何 可 执 行 代 码, 也 不 能 被 调 度, 仅 仅 在 进 程 列 表 中 保 留 一 个 位 置, 记 载 该 进 程 的 退 出 状 态 等 信 息 供 其 他 进 程 收 集, 除 此 之 外, 僵 尸 进 程 不 再 占 有 任 何 内 存 空 间 4.wait() 和 waitpid() (1)wait() 和 waitpid() 函 数 说 明 wait() 函 数 是 用 于 使 父 进 程 ( 也 就 是 调 用 wait() 的 进 程 ) 阻 塞, 直 到 一 个 子 进 程 结 束 或 者 该 进 程 接 到 了 一 个 指 定 的 信 号 为 止 如 果 该 父 进 程 没 有 子 进 程 或 者 他 的 子 进 程 已 经 结 束, 则 wait() 就 会 立 即 返 回 waitpid() 的 作 用 和 wait() 一 样, 但 它 并 不 一 定 要 等 待 第 一 个 终 止 的 子 进 程, 它 还 有 若 干 选 项, 如 可 提 供 一 个 非 阻 塞 版 本 的 wait() 功 能, 也 能 支 持 作 业 控 制 实 际 上 wait() 函 数 只 是 waitpid() 函 数 的 一 个 特 例, 在 Linux 内 部 实 现 wait() 函 数 时 直 接 调 用 的 就 是 waitpid() 函 数 (2)wait() 和 waitpid() 函 数 格 式 说 明 表 7.6 列 出 了 wait() 函 数 的 语 法 规 范 表 7.6 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status) wait() 函 数 族 语 法 这 里 的 status 是 一 个 整 型 指 针, 是 该 子 进 程 退 出 时 的 状 态 status 若 不 为 空, 则 通 过 它 可 以 获 得 子 进 程 的 结 束 状 态 另 外, 子 进 程 的 结 束 状 态 可 由 Linux 中 一 些 特 定 的 宏 来 测 定 成 功 : 已 结 束 运 行 的 子 进 程 的 进 程 号 失 败 : 1 表 7.7 列 出 了 waitpid() 函 数 的 语 法 规 范 表 7.7 所 需 头 文 件 函 数 原 型 #include <sys/types.h> #include <sys/wait.h> waitpid() 函 数 语 法 pid_t waitpid(pid_t pid, int *status, int options) 12
210 pid > 0: 只 等 待 进 程 ID 等 于 pid 的 子 进 程, 不 管 已 经 有 其 他 子 进 程 运 行 结 束 退 出 了, 只 要 指 定 的 子 进 程 还 没 有 结 束,waitpid() 就 会 一 直 等 下 去 续 表 函 数 传 入 值 Pid status options pid = 1: 等 待 任 何 一 个 子 进 程 退 出, 此 时 和 wait() 作 用 一 样 pid = 0: 等 待 其 组 ID 等 于 调 用 进 程 的 组 ID 的 任 一 子 进 程 pid < 1: 等 待 其 组 ID 等 于 pid 的 绝 对 值 的 任 一 子 进 程 同 wait() WNOHANG: 若 由 pid 指 定 的 子 进 程 不 立 即 可 用, 则 waitpid() 不 阻 塞, 此 时 返 回 值 为 0 WUNTRACED: 若 实 现 某 支 持 作 业 控 制, 则 由 pid 指 定 的 任 一 子 进 程 状 态 已 暂 停, 且 其 状 态 自 暂 停 以 来 还 未 报 告 过, 则 返 回 其 状 态 0: 同 wait(), 阻 塞 父 进 程, 等 待 子 进 程 退 出 正 常 : 已 经 结 束 运 行 的 子 进 程 的 进 程 号 函 数 返 回 值 使 用 选 项 WNOHANG 且 没 有 子 进 程 退 出 :0 调 用 出 错 : 1 3)waitpid() 使 用 实 例 由 于 wait() 函 数 的 使 用 较 为 简 单, 在 此 仅 以 waitpid() 为 例 进 行 讲 解 本 例 中 首 先 使 用 fork() 创 建 一 个 子 进 程, 然 后 让 其 子 进 程 暂 停 5s( 使 用 了 sleep() 函 数 ) 接 下 来 对 原 有 的 父 进 程 使 用 waitpid() 函 数, 并 使 用 参 数 WNOHANG 使 该 父 进 程 不 会 阻 塞 若 有 子 进 程 退 出, 则 waitpid() 返 回 子 进 程 号 ; 若 没 有 子 进 程 退 出, 则 waitpid() 返 回 0, 并 且 父 进 程 每 隔 一 秒 循 环 判 断 一 次 该 程 序 的 流 程 图 如 图 7.5 所 示 开 始 fork() 返 回 值 = 0 fork() 返 回 值 返 回 值 >0 子 进 程 暂 停 5s 子 进 程 退 出 父 进 程 调 用 waitpid() waitpid() 返 回 值 返 回 值 <0( 出 错 ) 返 回 值 = 子 进 程 号 捕 获 子 进 程 退 出 父 进 程 暂 停 5s 返 回 值 = 0 结 束 图 7.5 waitpid 示 例 程 序 流 该 程 序 源 代 码 如 下 所 示 : /* waitpid.c */ #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() pid_t pc, pr; pc = fork(); 13
211 if (pc < 0) printf("error fork\n"); else if (pc == 0) /* 子 进 程 */ /* 子 进 程 暂 停 5s*/ sleep(5); /* 子 进 程 正 常 退 出 */ exit(0); else /* 父 进 程 */ /* 循 环 测 试 子 进 程 是 否 退 出 */ do /* 调 用 waitpid, 且 父 进 程 不 阻 塞 */ pr = waitpid(pc, NULL, WNOHANG); /* 若 子 进 程 还 未 退 出, 则 父 进 程 暂 停 1s*/ if (pr == 0) printf("the child process has not exited\n"); sleep(1); while (pr == 0); /* 若 发 现 子 进 程 退 出, 打 印 出 相 应 情 况 */ if (pr == pc) printf("get child exit code: %d\n",pr); else printf("some error occured.\n"); 将 该 程 序 交 叉 编 译, 下 载 到 目 标 板 后 的 运 行 结 果 如 下 所 示 : $./waitpid The child process has not exited The child process has not exited The child process has not exited The child process has not exited The child process has not exited Get child exit code: 75 可 见, 该 程 序 在 经 过 5 次 循 环 之 后, 捕 获 到 了 子 进 程 的 退 出 信 号, 具 体 的 子 进 程 号 在 不 同 的 系 统 上 会 有 所 14
212 区 别 读 者 还 可 以 尝 试 把 pr = waitpid(pc, NULL, WNOHANG); 这 句 改 为 pr = waitpid(pc, NULL, 0); 或 者 pr = wait(null);, 运 行 的 结 果 为 : $./waitpid Get child exit code: 76 可 见, 在 上 述 两 种 情 况 下, 父 进 程 在 调 用 waitpid() 或 wait() 之 后 就 将 自 己 阻 塞, 直 到 有 子 进 程 退 出 为 止 7.3 Linux 守 护 进 程 守 护 进 程 概 述 守 护 进 程, 也 就 是 通 常 所 说 的 Daemon 进 程, 是 Linux 中 的 后 台 服 务 进 程 它 是 一 个 生 存 期 较 长 的 进 程, 通 常 独 立 于 控 制 终 端 并 且 周 期 性 地 执 行 某 种 任 务 或 等 待 处 理 某 些 发 生 的 事 件 守 护 进 程 常 常 在 系 统 引 导 载 入 时 启 动, 在 系 统 关 闭 时 终 止 Linux 有 很 多 系 统 服 务, 大 多 数 服 务 都 是 通 过 守 护 进 程 实 现 的, 如 本 书 在 第 二 章 中 讲 到 的 多 种 系 统 服 务 都 是 守 护 进 程 同 时, 守 护 进 程 还 能 完 成 许 多 系 统 任 务, 例 如, 作 业 规 划 进 程 crond 打 印 进 程 lqd 等 ( 这 里 的 结 尾 字 母 d 就 是 Daemon 的 意 思 ) 由 于 在 Linux 中, 每 一 个 系 统 与 用 户 进 行 交 流 的 界 面 称 为 终 端, 每 一 个 从 此 终 端 开 始 运 行 的 进 程 都 会 依 附 于 这 个 终 端, 这 个 终 端 就 称 为 这 些 进 程 的 控 制 终 端, 当 控 制 终 端 被 关 闭 时, 相 应 的 进 程 都 会 自 动 关 闭 但 是 守 护 进 程 却 能 够 突 破 这 种 限 制, 它 从 被 执 行 开 始 运 转, 直 到 整 个 系 统 关 闭 时 才 会 退 出 如 果 想 让 某 个 进 程 不 因 为 用 户 终 端 或 者 其 他 的 变 化 而 受 到 影 响, 那 么 就 必 须 把 这 个 进 程 变 成 一 个 守 护 进 程 可 见, 守 护 进 程 是 非 常 重 要 的 编 写 守 护 进 程 编 写 守 护 进 程 看 似 复 杂, 但 实 际 上 也 是 遵 循 一 个 特 定 的 流 程 只 要 将 此 流 程 掌 握 了, 就 能 很 方 便 地 编 写 出 用 户 自 己 的 守 护 进 程 下 面 就 分 4 个 步 骤 来 讲 解 怎 样 创 建 一 个 简 单 的 守 护 进 程 在 讲 解 的 同 时, 会 配 合 介 绍 与 创 建 守 护 进 程 相 关 的 几 个 系 统 函 数, 希 望 读 者 能 很 好 地 掌 握 1. 创 建 子 进 程, 父 进 程 退 出 这 是 编 写 守 护 进 程 的 第 一 步 由 于 守 护 进 程 是 脱 离 控 制 终 端 的, 因 此, 完 成 第 一 步 后 就 会 在 shell 终 端 里 造 成 一 种 程 序 已 经 运 行 完 毕 的 假 象 之 后 的 所 有 工 作 都 在 子 进 程 中 完 成, 而 用 户 在 shell 终 端 里 则 可 以 执 行 其 他 的 命 令, 从 而 在 形 式 上 做 到 了 与 控 制 终 端 的 脱 离 到 这 里, 有 心 的 读 者 可 能 会 问, 父 进 程 创 建 了 子 进 程 之 后 退 出, 此 时 该 子 进 程 不 就 没 有 父 进 程 了 吗? 守 护 进 程 中 确 实 会 出 现 这 么 一 个 有 趣 的 现 象, 由 于 父 进 程 已 经 先 于 子 进 程 退 出, 会 造 成 子 进 程 没 有 父 进 程, 从 而 变 成 一 个 孤 儿 进 程 在 Linux 中, 每 当 系 统 发 现 一 个 孤 儿 进 程, 就 会 自 动 由 1 号 进 程 ( 也 就 是 init 进 程 ) 收 养 它, 这 样, 原 先 的 子 进 程 就 会 变 成 init 进 程 的 子 进 程 了 其 关 键 代 码 如 下 所 示 : pid = fork(); if (pid > 0) exit(0); /* 父 进 程 退 出 */ 15
213 2. 在 子 进 程 中 创 建 新 会 话 这 个 步 骤 是 创 建 守 护 进 程 中 最 重 要 的 一 步, 虽 然 它 的 实 现 非 常 简 单, 但 它 的 意 义 却 非 常 重 大 在 这 里 使 用 的 是 系 统 函 数 setsid(), 在 具 体 介 绍 setsid() 之 前, 读 者 首 先 要 了 解 两 个 概 念 : 进 程 组 和 会 话 期 进 程 组 进 程 组 是 一 个 或 多 个 进 程 的 集 合 进 程 组 由 进 程 组 ID 来 惟 一 标 识 除 了 进 程 号 (PID) 之 外, 进 程 组 ID 也 是 一 个 进 程 的 必 备 属 性 每 个 进 程 组 都 有 一 个 组 长 进 程, 其 组 长 进 程 的 进 程 号 等 于 进 程 组 ID 且 该 进 程 ID 不 会 因 组 长 进 程 的 退 出 而 受 到 影 响 会 话 期 会 话 组 是 一 个 或 多 个 进 程 组 的 集 合 通 常, 一 个 会 话 开 始 于 用 户 登 录, 终 止 于 用 户 退 出, 在 此 期 间 该 用 户 运 行 的 所 有 进 程 会 话 期, 它 们 之 间 的 关 系 如 图 7.6 所 示 接 下 来 就 可 以 具 体 介 绍 setsid() 的 相 关 内 容 (1)setsid() 函 数 作 用 setsid() 函 数 用 于 创 建 一 个 新 的 会 话, 并 担 任 该 会 话 组 的 setsid() 有 下 面 的 3 个 作 用 让 进 程 摆 脱 原 会 话 的 控 制 让 进 程 摆 脱 原 进 程 组 的 控 制 让 进 程 摆 脱 原 控 制 终 端 的 控 制 登 录 shell 进 程 1 进 程 2 进 程 组 1 进 程 组 2 会 话 期 图 7.6 进 程 组 和 会 话 期 之 间 的 关 系 图 都 属 于 这 个 组 长 调 用 那 么, 在 创 建 守 护 进 程 时 为 什 么 要 调 用 setsid() 函 数 呢? 读 者 可 以 回 忆 一 下 创 建 守 护 进 程 的 第 一 步, 在 那 里 调 用 了 fork() 函 数 来 创 建 子 进 程 再 令 父 进 程 退 出 由 于 在 调 用 fork() 函 数 时, 子 进 程 全 盘 复 制 了 父 进 程 的 会 话 期 进 程 组 和 控 制 终 端 等, 虽 然 父 进 程 退 出 了, 但 原 先 的 会 话 期 进 程 组 和 控 制 终 端 等 并 没 有 改 变, 因 此, 还 不 是 真 正 意 义 上 的 独 立, 而 setsid() 函 数 能 够 使 进 程 完 全 独 立 出 来, 从 而 脱 离 所 有 其 他 进 程 的 控 制 (2)setsid() 函 数 格 式 表 7.8 列 出 了 setsid() 函 数 的 语 法 规 范 表 7.8 所 需 头 文 件 函 数 原 型 函 数 返 回 值 setsid() 函 数 语 法 #include <sys/types.h> #include <unistd.h> pid_t setsid(void) 成 功 : 该 进 程 组 ID 出 错 : 1 3. 改 变 当 前 目 录 为 根 目 录 这 一 步 也 是 必 要 的 步 骤 使 用 fork() 创 建 的 子 进 程 继 承 了 父 进 程 的 当 前 工 作 目 录 由 于 在 进 程 运 行 过 程 中, 当 前 目 录 所 在 的 文 件 系 统 ( 比 如 /mnt/usb 等 ) 是 不 能 卸 载 的, 这 对 以 后 的 使 用 会 造 成 诸 多 的 麻 烦 ( 比 如 系 统 由 于 某 种 原 因 要 进 入 单 用 户 模 式 ) 因 此, 通 常 的 做 法 是 让 / 作 为 守 护 进 程 的 当 前 工 作 目 录, 这 样 就 可 以 避 免 上 述 的 问 题, 当 然, 如 有 特 殊 需 要, 也 可 以 把 当 前 工 作 目 录 换 成 其 他 的 路 径, 如 /tmp 改 变 工 作 目 录 的 常 见 函 数 是 chdir() 4. 重 设 文 件 权 限 掩 码 文 件 权 限 掩 码 是 指 屏 蔽 掉 文 件 权 限 中 的 对 应 位 比 如, 有 一 个 文 件 权 限 掩 码 是 050, 它 就 屏 蔽 了 文 件 组 拥 有 者 的 可 读 与 可 执 行 权 限 由 于 使 用 fork() 函 数 新 建 的 子 进 程 继 承 了 父 进 程 的 文 件 权 限 掩 码, 这 就 给 该 子 16
214 进 程 使 用 文 件 带 来 了 诸 多 的 麻 烦 因 此, 把 文 件 权 限 掩 码 设 置 为 0, 可 以 大 大 增 强 该 守 护 进 程 的 灵 活 性 设 置 文 件 权 限 掩 码 的 函 数 是 umask() 在 这 里, 通 常 的 使 用 方 法 为 umask(0) 5. 关 闭 文 件 描 述 符 同 文 件 权 限 掩 码 一 样, 用 fork() 函 数 新 建 的 子 进 程 会 从 父 进 程 那 里 继 承 一 些 已 经 打 开 了 的 文 件 这 些 被 打 开 的 文 件 可 能 永 远 不 会 被 守 护 进 程 读 或 写, 但 它 们 一 样 消 耗 系 统 资 源, 而 且 可 能 导 致 所 在 的 文 件 系 统 无 法 被 卸 载 在 上 面 的 第 二 步 之 后, 守 护 进 程 已 经 与 所 属 的 控 制 终 端 失 去 了 联 系 因 此 从 终 端 输 入 的 字 符 不 可 能 达 到 守 护 进 程, 守 护 进 程 中 用 常 规 方 法 ( 如 printf()) 输 出 的 字 符 也 不 可 能 在 终 端 上 显 示 出 来 所 以, 文 件 描 述 符 为 0 1 和 2 的 3 个 文 件 ( 常 说 的 输 入 输 出 和 报 错 这 3 个 文 件 ) 已 经 失 去 了 存 在 的 价 值, 也 应 被 关 闭 通 常 按 如 下 方 式 关 闭 文 件 描 述 符 : for(i = 0; i < MAXFILE; i++) close(i); 这 样, 一 个 简 单 的 守 护 进 程 就 建 立 起 来 了, 创 建 守 护 进 程 的 开 始 chdir( / ) 流 程 图 如 设 置 工 作 目 录 图 7.7 所 示 fork() 创 建 子 进 程 umask(0) 下 面 是 实 现 守 护 进 程 的 一 个 完 整 实 例, 该 实 例 首 先 按 照 以 上 exit() 使 父 进 程 退 出 重 设 文 件 权 限 掩 码 的 创 建 流 程 建 立 了 一 个 守 护 进 程, 然 后 让 该 守 护 进 程 每 隔 10s 向 日 志 文 件 setsid() close() 创 建 新 会 话 /tmp/daemon.log 写 入 一 句 话 关 闭 文 件 描 述 符 /* daemon.c 创 建 守 护 进 程 实 例 */ #include<stdio.h> #include<stdlib.h> #include<string.h> #include<fcntl.h> #include<sys/types.h> #include<unistd.h> #include<sys/wait.h> 结 束 图 7.7 创 建 守 护 进 程 流 程 图 int main() pid_t pid; int i, fd; char *buf = "This is a Daemon\n"; pid = fork(); /* 第 一 步 */ if (pid < 0) printf("error fork\n"); exit(1); else if (pid > 0) exit(0); /* 父 进 程 推 出 */ 17
215 setsid(); /* 第 二 步 */ chdir("/"); /* 第 三 步 */ umask(0); /* 第 四 步 */ for(i = 0; i < getdtablesize(); i++) /* 第 五 步 */ close(i); /* 这 时 创 建 完 守 护 进 程, 以 下 开 始 正 式 进 入 守 护 进 程 工 作 */ while(1) if ((fd = open("/tmp/daemon.log", O_CREAT O_WRONLY O_APPEND, 0600)) < 0) printf("open file error\n"); exit(1); write(fd, buf, strlen(buf) + 1); close(fd); sleep(10); exit(0); 将 该 程 序 下 载 到 开 发 板 上, 可 以 看 到 该 程 序 每 隔 10s 就 会 在 对 应 的 文 件 中 输 入 相 关 内 容 并 且 使 用 ps 可 以 看 到 该 进 程 在 后 台 运 行 如 下 所 示 : $ tail -f /tmp/daemon.log This is a Daemon This is a Daemon This is a Daemon This is a Daemon $ ps -ef grep daemon 76 root 1272 S./daemon 85 root 1520 S grep daemon 守 护 进 程 的 出 错 处 理 读 者 在 前 面 编 写 守 护 进 程 的 具 体 调 试 过 程 中 会 发 现, 由 于 守 护 进 程 完 全 脱 离 了 控 制 终 端, 因 此, 不 能 像 其 他 普 通 进 程 一 样 将 错 误 信 息 输 出 到 控 制 终 端 来 通 知 程 序 员, 即 使 使 用 gdb 也 无 法 正 常 调 试 那 么, 守 护 进 程 的 进 程 要 如 何 调 试 呢? 一 种 通 用 的 办 法 是 使 用 syslog 服 务, 将 程 序 中 的 出 错 信 息 输 入 到 系 统 日 志 文 件 中 ( 例 如 : /var/log/messages ), 从 而 可 以 直 观 地 看 到 程 序 的 问 题 所 在 /var/log/message 系 统 日 志 文 件 只 能 由 拥 有 root 权 限 的 超 级 用 户 查 看 在 不 同 Linux 发 行 版 本 中, 系 统 日 志 文 件 路 径 全 名 可 能 有 所 不 同, 例 如 可 能 是 /var/log/syslog 18
216 syslog 是 Linux 中 的 系 统 日 志 管 理 服 务, 通 过 守 护 进 程 syslogd 来 维 护 该 守 护 进 程 在 启 动 时 会 读 一 个 配 置 文 件 /etc/syslog.conf 该 文 件 决 定 了 不 同 种 类 的 消 息 会 发 送 向 何 处 例 如, 紧 急 消 息 可 被 送 向 系 统 管 理 员 并 在 控 制 台 上 显 示, 而 警 告 消 息 则 可 被 记 录 到 一 个 文 件 中 该 机 制 提 供 了 3 个 syslog 相 关 函 数, 分 别 为 openlog() syslog() 和 closelog() 下 面 就 分 别 介 绍 这 3 个 函 数 (1)syslog 相 关 函 数 说 明 通 常,openlog() 函 数 用 于 打 开 系 统 日 志 服 务 的 一 个 连 接 ;syslog() 函 数 是 用 于 向 日 志 文 件 中 写 入 消 息, 在 这 里 可 以 规 定 消 息 的 优 先 级 消 息 输 出 格 式 等 ;closelog() 函 数 是 用 于 关 闭 系 统 日 志 服 务 的 连 接 (2)syslog 相 关 函 数 格 式 表 7.9 列 出 了 openlog() 函 数 的 语 法 规 范 表 7.9 所 需 头 文 件 函 数 原 型 #include <syslog.h> openlog() 函 数 语 法 void openlog (char *ident, int option, int facility) ident option 要 向 每 个 消 息 加 入 的 字 符 串, 通 常 为 程 序 的 名 称 LOG_CONS: 如 果 消 息 无 法 送 到 系 统 日 志 服 务, 则 直 接 输 出 到 系 统 控 制 终 端 LOG_NDELAY: 立 即 打 开 系 统 日 志 服 务 的 连 接 在 正 常 情 况 下, 直 接 发 送 到 第 一 条 消 息 时 才 打 开 连 接 LOG_PERROR: 将 消 息 也 同 时 送 到 stderr 上 LOG_PID: 在 每 条 消 息 中 包 含 进 程 的 PID 函 数 传 入 值 facility: 指 定 程 序 发 送 的 消 息 类 型 LOG_AUTHPRIV: 安 全 / 授 权 信 息 LOG_CRON: 时 间 守 护 进 程 (cron 及 at) LOG_DAEMON: 其 他 系 统 守 护 进 程 LOG_KERN: 内 核 信 息 LOG_LOCAL[0~7]: 保 留 LOG_LPR: 行 打 印 机 子 系 统 LOG_MAIL: 邮 件 子 系 统 LOG_NEWS: 新 闻 子 系 统 LOG_SYSLOG:syslogd 内 部 所 产 生 的 信 息 LOG_USER: 一 般 使 用 者 等 级 信 息 LOG_UUCP:UUCP 子 系 统 表 7.10 列 出 了 syslog() 函 数 的 语 法 规 范 表 7.10 syslog() 函 数 语 法 所 需 头 文 件 #include <syslog.h> 函 数 原 型 void syslog(int priority, char *format,...) LOG_EMERG: 系 统 无 法 使 用 LOG_ALERT: 需 要 立 即 采 取 措 施 LOG_CRIT: 有 重 要 情 况 发 生 函 数 传 入 值 priority: 指 定 消 息 的 重 要 性 LOG_ERR: 有 错 误 发 生 LOG_WARNING: 有 警 告 发 生 LOG_NOTICE: 正 常 情 况, 但 也 是 重 要 情 况 LOG_INFO: 信 息 消 息 LOG_DEBUG: 调 试 信 息 format 表 7.11 列 出 了 closelog() 函 数 的 语 法 规 范 以 字 符 串 指 针 的 形 式 表 示 输 出 的 格 式, 类 似 printf 中 的 格 式 表 7.11 closelog 函 数 语 法 19
217 所 需 头 文 件 函 数 原 型 #include <syslog.h> void closelog(void) (3) 使 用 实 例 这 里 将 上 一 节 中 的 示 例 程 序 用 syslog 服 务 进 行 重 写, 其 中 有 区 别 的 地 方 用 加 粗 的 字 体 表 示, 源 代 码 如 下 所 示 : /* syslog_daemon.c 利 用 syslog 服 务 的 守 护 进 程 实 例 */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include <syslog.h> int main() pid_t pid, sid; int i, fd; char *buf = "This is a Daemon\n"; pid = fork(); /* 第 一 步 */ if (pid < 0) printf("error fork\n"); exit(1); else if (pid > 0) exit(0); /* 父 进 程 推 出 */ /* 打 开 系 统 日 志 服 务,openlog */ openlog("daemon_syslog", LOG_PID, LOG_DAEMON); if ((sid = setsid()) < 0) /* 第 二 步 */ syslog(log_err, "%s\n", "setsid"); exit(1); if ((sid = chdir("/")) < 0) /* 第 三 步 */ syslog(log_err, "%s\n", "chdir"); exit(1); umask(0); /* 第 四 步 */ for(i = 0; i < getdtablesize(); i++) /* 第 五 步 */ close(i); 20
218 /* 这 时 创 建 完 守 护 进 程, 以 下 开 始 正 式 进 入 守 护 进 程 工 作 */ while(1) if ((fd = open("/tmp/daemon.log", O_CREAT O_WRONLY O_APPEND, 0600))<0) syslog(log_err, "open"); exit(1); write(fd, buf, strlen(buf) + 1); close(fd); sleep(10); closelog(); exit(0); 读 者 可 以 尝 试 用 普 通 用 户 的 身 份 执 行 此 程 序, 由 于 这 里 的 open() 函 数 必 须 具 有 root 权 限, 因 此,syslog 就 会 将 错 误 信 息 写 入 到 系 统 日 志 文 件 ( 例 如 /var/log/messages ) 中, 如 下 所 示 : Jan 30 18:20:08 localhost daemon_syslog[612]: open 7.4 实 验 内 容 编 写 多 进 程 程 序 1. 实 验 目 的 通 过 编 写 多 进 程 程 序, 使 读 者 熟 练 掌 握 fork() exec() wait() 和 waitpid() 等 函 数 的 使 用, 进 一 步 理 解 在 Linux 中 多 进 程 编 程 的 步 骤 2. 实 验 内 容 该 实 验 有 3 个 进 程, 其 中 一 个 为 父 进 程, 其 余 两 个 是 该 父 进 程 创 建 的 子 进 程, 其 中 一 个 子 进 程 运 行 ls -l 指 令, 另 一 个 子 进 程 在 暂 停 5s 之 后 异 常 退 出, 父 进 程 先 用 阻 塞 方 式 等 待 第 一 个 子 进 程 的 结 束, 然 后 用 非 阻 塞 方 式 等 待 另 一 个 子 进 程 的 退 出, 待 收 集 到 第 二 个 子 进 程 结 束 的 信 息, 父 进 程 就 返 回 3. 实 验 步 骤 (1) 画 出 该 实 验 流 程 图 该 实 验 流 程 图 如 图 7.8 所 示 21
219 开 始 子 进 程 1 fork() 创 建 两 个 子 进 程 父 进 程 阻 塞 式 等 待 子 进 程 1 的 结 束 子 进 程 2 运 行 ( 调 用 execlp() 执 行 ls -l 命 令 ) 子 进 程 2 是 否 结 束? 是 等 待 1s 运 行 ( 调 用 sleep 函 数 ) 结 束 图 7.8 实 验 流 程 图 (2) 实 验 源 代 码 先 看 一 下 下 面 的 代 码, 这 个 程 序 能 得 到 我 们 所 希 望 的 结 果 吗, 它 的 运 行 会 产 生 几 个 进 程? 请 读 者 回 忆 一 下 fork() 调 用 的 具 体 过 程 /* multi_proc_wrong.c */ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> int main(void) pid_t child1, child2, child; /* 创 建 两 个 子 进 程 */ child1 = fork(); child2 = fork(); /* 子 进 程 1 的 出 错 处 理 */ if (child1 == -1) printf("child1 fork error\n"); exit(1); else if (child1 == 0) /* 在 子 进 程 1 中 调 用 execlp() 函 数 */ printf("in child1: execute 'ls -l'\n"); if (execlp("ls", "ls","-l", NULL)<0) printf("child1 execlp error\n"); if (child2 == -1) /* 子 进 程 2 的 出 错 处 理 */ 22
220 printf("child2 fork error\n"); exit(1); else if( child2 == 0 ) /* 在 子 进 程 2 中 使 其 暂 停 5s*/ printf("in child2: sleep for 5 seconds and then exit\n"); sleep(5); exit(0); else /* 在 父 进 程 中 等 待 两 个 子 进 程 的 退 出 */ printf("in father process:\n"); child = waitpid(child1, NULL, 0); /* 阻 塞 式 等 待 */ if (child == child1) printf("get child1 exit code\n"); else printf("error occured!\n"); do child =waitpid(child2, NULL, WNOHANG);/* 非 阻 塞 式 等 待 */ if (child == 0) printf("the child2 process has not exited!\n"); sleep(1); while (child == 0); if (child == child2) printf("get child2 exit code\n"); else printf("error occured!\n"); exit(0); 编 译 和 运 行 以 上 代 码, 并 观 察 其 运 行 结 果 它 的 结 果 是 我 们 所 希 望 的 吗? 看 完 前 面 的 代 码 之 后, 再 观 察 下 面 的 代 码, 它 们 之 间 有 什 么 区 别, 会 解 决 哪 些 问 题 /*multi_proc.c */ 23
221 #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> 专 业 始 于 专 注 卓 识 源 于 远 见 int main(void) pid_t child1, child2, child; /* 创 建 两 个 子 进 程 */ child1 = fork(); /* 子 进 程 1 的 出 错 处 理 */ if (child1 == -1) printf("child1 fork error\n"); exit(1); else if (child1 == 0) /* 在 子 进 程 1 中 调 用 execlp() 函 数 */ printf("in child1: execute 'ls -l'\n"); if (execlp("ls", "ls", "-l", NULL) < 0) printf("child1 execlp error\n"); else /* 在 父 进 程 中 再 创 建 进 程 2, 然 后 等 待 两 个 子 进 程 的 退 出 */ child2 = fork(); if (child2 == -1) /* 子 进 程 2 的 出 错 处 理 */ printf("child2 fork error\n"); exit(1); else if(child2 == 0) /* 在 子 进 程 2 中 使 其 暂 停 5s*/ printf("in child2: sleep for 5 seconds and then exit\n"); sleep(5); exit(0); printf("in father process:\n"); child = waitpid(child1, NULL, 0); /* 阻 塞 式 等 待 */ if (child == child1) printf("get child1 exit code\n"); 24
222 else printf("error occured!\n"); 专 业 始 于 专 注 卓 识 源 于 远 见 do child = waitpid(child2, NULL, WNOHANG ); /* 非 阻 塞 式 等 待 */ if (child == 0) printf("the child2 process has not exited!\n"); sleep(1); while (child == 0); if (child == child2) printf("get child2 exit code\n"); else printf("error occured!\n"); exit(0); (3) 首 先 在 宿 主 机 上 编 译 调 试 该 程 序 : $ gcc multi_proc.c o multi_proc( 或 者 使 用 Makefile) (4) 在 确 保 没 有 编 译 错 误 后, 使 用 交 叉 编 译 该 程 序 : $ arm-linux-gcc multi_proc.c o multi_proc ( 或 者 使 用 Makefile) (5) 将 生 成 的 可 执 行 程 序 下 载 到 目 标 板 上 运 行 4. 实 验 结 果 在 目 标 板 上 运 行 的 结 果 如 下 所 示 ( 具 体 内 容 与 各 自 的 系 统 有 关 ): $./multi_proc In child1: execute 'ls -l' /* 子 进 程 1 的 显 示, 以 下 是 ls l 的 运 行 结 果 */ total 28 -rwxr-xr-x 1 david root :18 Makefile -rwxr-xr-x 1 david root :51 multi_proc -rw-r--r-- 1 david root :51 multi_proc.c -rw-r--r-- 1 david root :51 multi_proc.o -rw-r--r-- 1 david root :55 multi_proc_wrong.c In child2: sleep for 5 seconds and then exit /* 子 进 程 2 的 显 示 */ 25
223 In father process: /* 以 下 是 父 进 程 显 示 */ Get child1 exit code /* 表 示 子 进 程 1 结 束 ( 阻 塞 等 待 )*/ The child2 process has not exited! /* 等 待 子 进 程 2 结 束 ( 非 阻 塞 等 待 )*/ The child2 process has not exited! The child2 process has not exited! The child2 process has not exited! The child2 process has not exited! Get child2 exit code /* 表 示 子 进 程 2 终 于 结 束 了 */ 因 为 几 个 子 进 程 的 执 行 有 竞 争 关 系, 因 此, 结 果 中 的 顺 序 是 随 机 的 读 者 可 以 思 考, 怎 样 才 可 以 保 证 子 进 程 的 执 行 顺 序 呢? 编 写 守 护 进 程 1. 实 验 目 的 通 过 编 写 一 个 完 整 的 守 护 进 程, 使 读 者 掌 握 守 护 进 程 编 写 和 调 试 的 方 法, 并 且 进 一 步 熟 悉 如 何 编 写 多 进 程 程 序 2. 实 验 内 容 在 该 实 验 中, 读 者 首 先 建 立 起 一 个 守 护 进 程, 然 后 在 该 守 护 进 程 中 新 建 一 个 子 进 程, 该 子 进 程 暂 停 10s, 然 后 自 动 退 出, 并 由 守 护 进 程 收 集 子 进 程 退 出 的 消 息 在 这 里, 子 进 程 和 守 护 进 程 的 退 出 消 息 都 在 系 统 日 志 文 件 ( 例 如 /var/log/messages, 日 志 文 件 的 全 路 径 名 因 版 本 的 不 同 可 能 会 有 所 不 同 ) 中 输 出 子 进 程 退 出 后, 守 护 进 程 循 环 暂 停, 其 间 隔 时 间 为 10s 开 始 子 进 程 1 fork 创 建 子 进 程 1 fork 创 建 子 进 程 2 子 进 程 2 3. 实 验 步 骤 等 待 子 进 程 2 退 出 将 消 息 写 入 到 系 统 日 志 文 件 中 (1) 画 出 该 实 验 流 程 图 该 程 序 流 程 图 如 图 7.9 所 示 (2) 实 验 源 代 码 具 体 代 码 设 置 如 下 : /* daemon_proc.c */ #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include <syslog.h> 将 消 息 写 入 到 系 统 日 志 文 件 中 暂 停 10 秒 暂 停 10 秒 结 束 父 进 程 图 7.9 实 验 流 程 图 int main(void) pid_t child1,child2; int i; 26
224 /* 创 建 子 进 程 1*/ child1 = fork(); if (child1 == 1) perror("child1 fork"); exit(1); else if (child1 > 0) exit(0); /* 父 进 程 退 出 */ /* 打 开 日 志 服 务 */ openlog("daemon_proc_info", LOG_PID, LOG_DAEMON); /* 以 下 几 步 是 编 写 守 护 进 程 的 常 规 步 骤 */ setsid(); chdir("/"); umask(0); for(i = 0; i < getdtablesize(); i++) close(i); /* 创 建 子 进 程 2*/ child2 = fork(); if (child2 == 1) perror("child2 fork"); exit(1); else if (child2 == 0) /* 进 程 child2 */ /* 在 日 志 中 写 入 字 符 串 */ syslog(log_info, " child2 will sleep for 10s "); sleep(10); syslog(log_info, " child2 is going to exit! "); exit(0); else /* 进 程 child1*/ waitpid(child2, NULL, 0); syslog(log_info, " child1 noticed that child2 has exited "); /* 关 闭 日 志 服 务 */ closelog(); while(1) sleep(10); 27
225 (3) 由 于 有 些 嵌 入 式 开 发 板 没 有 syslog 服 务, 读 者 可 以 在 宿 主 机 上 编 译 运 行 $ gcc daemon_proc.c o daemon_proc ( 或 者 使 用 Makefile) (4) 运 行 该 程 序 (5) 等 待 10s 后, 以 root 身 份 查 看 系 统 日 志 文 件 ( 例 如 /var/log/messages ) (6) 使 用 ps ef grep daemon_proc 查 看 该 守 护 进 程 是 否 在 运 行 4. 实 验 结 果 (1) 在 系 统 日 志 文 件 中 有 类 似 如 下 的 信 息 显 示 : Jul 20 21:15:08 localhost daemon_proc_info[4940]: child2 will sleep for 10s Jul 20 21:15:18 localhost daemon_proc_info[4940]: child2 is going to exit! Jul 20 21:15:18 localhost daemon_proc_info[4939]: child1 noticed that child2 has exited 读 者 可 以 从 时 间 戳 里 清 楚 地 看 到 child2 确 实 暂 停 了 10s (2) 使 用 命 令 ps ef grep daemon_proc 可 看 到 如 下 结 果 : david :15? 00:00:00./daemon_proc 可 见,daemon_proc 确 实 一 直 在 运 行 7.5 本 章 小 结 本 章 主 要 介 绍 进 程 的 控 制 开 发, 首 先 给 出 了 进 程 的 基 本 概 念,Linux 下 进 程 的 基 本 结 构 模 式 与 类 型 以 及 Linux 进 程 管 理 进 程 是 Linux 中 程 序 运 行 和 资 源 管 理 的 最 小 单 位, 对 进 程 的 处 理 也 是 嵌 入 式 Linux 应 用 编 程 的 基 础, 因 此, 读 者 一 定 要 牢 牢 掌 握 接 下 来, 本 章 具 体 讲 解 了 进 程 控 制 编 程, 主 要 讲 解 了 fork() 函 数 和 exec 函 数 族, 并 且 举 实 例 加 以 区 别 exec 函 数 族 较 为 庞 大, 希 望 读 者 能 够 仔 细 比 较 它 们 之 间 的 区 别, 认 真 体 会 并 理 解 最 后, 本 章 讲 解 了 Linux 守 护 进 程 的 编 写, 包 括 守 护 进 程 的 概 念 编 写 守 护 进 程 的 步 骤 以 及 守 护 进 程 的 出 错 处 理 由 于 守 护 进 程 非 常 特 殊, 因 此, 在 编 写 时 有 不 少 的 细 节 需 要 特 别 注 意 守 护 进 程 的 编 写 实 际 上 涉 及 进 程 控 制 编 程 的 很 多 部 分, 需 要 加 以 综 合 应 用 本 章 的 实 验 安 排 了 多 进 程 编 程 和 编 写 完 整 的 守 护 进 程 两 个 部 分 这 两 个 实 验 都 是 较 为 综 合 的, 希 望 读 者 能 够 认 真 完 成 7.6 思 考 与 练 习 查 阅 资 料, 明 确 Linux 中 进 程 处 理 和 嵌 入 式 Linux 中 的 进 程 处 理 有 什 么 区 别? 联 系 方 式 集 团 官 网 : 嵌 入 式 学 院 : 移 动 互 联 网 学 院 : 企 业 学 院 : 物 联 网 学 院 : 研 发 中 心 :dev.hqyj.com 28
226 集 团 总 部 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 内 华 清 远 见 教 育 集 团 专 业 始 于 专 注 卓 识 源 于 远 见 北 京 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 区, 电 话 : /5 上 海 地 址 : 上 海 市 徐 汇 区 漕 溪 路 银 海 大 厦 A 座 8 层, 电 话 : 深 圳 地 址 : 深 圳 市 龙 华 新 区 人 民 北 路 美 丽 AAA 大 厦 15 层, 电 话 : 成 都 地 址 : 成 都 市 武 侯 区 科 华 北 路 99 号 科 华 大 厦 6 层, 电 话 : 南 京 地 址 : 南 京 市 白 下 区 汉 中 路 185 号 鸿 运 大 厦 10 层, 电 话 : 武 汉 地 址 : 武 汉 市 工 程 大 学 卓 刀 泉 校 区 科 技 孵 化 器 大 楼 8 层, 电 话 : 西 安 地 址 : 西 安 市 高 新 区 高 新 一 路 12 号 创 业 大 厦 D3 楼 5 层, 电 话 :
227 嵌 入 式 LINUX 应 用 程 序 开 发 标 准 教 程 作 者 : 华 清 远 见 第 8 章 进 程 间 通 信 掌 握 Linux 中 管 道 的 基 本 概 念 掌 握 Linux 中 管 道 的 创 建 掌 握 Linux 中 管 道 的 读 写 掌 握 Linux 中 有 名 管 道 的 创 建 读 写 方 法 掌 握 Linux 中 消 息 队 列 的 处 理 掌 握 Linux 共 享 内 存 的 处 理
228 8.1 Linux 下 进 程 间 通 信 概 述 在 上 一 章 中, 读 者 已 经 知 道 了 进 程 是 一 个 程 序 的 一 次 执 行 这 里 所 说 的 进 程 一 般 是 指 运 行 在 用 户 态 的 进 程, 而 由 于 处 于 用 户 态 的 不 同 进 程 之 间 是 彼 此 隔 离 的, 就 像 处 于 不 同 城 市 的 人 们, 它 们 必 须 通 过 某 种 方 式 来 进 行 通 信, 例 如 人 们 现 在 广 泛 使 用 的 手 机 等 方 式 本 章 就 是 讲 述 如 何 建 立 这 些 不 同 的 通 话 方 式, 就 像 人 们 有 多 种 通 信 方 式 一 样 Linux 下 的 进 程 通 信 手 段 基 本 上 是 从 UNIX 平 台 上 的 进 程 通 信 手 段 继 承 而 来 的 而 对 UNIX 发 展 做 出 重 大 贡 献 的 两 大 主 力 AT&T 的 贝 尔 实 验 室 及 BSD( 加 州 大 学 伯 克 利 分 校 的 伯 克 利 软 件 发 布 中 心 ) 在 进 程 间 的 通 信 方 面 的 侧 重 点 有 所 不 同 前 者 是 对 UNIX 早 期 的 进 程 间 通 信 手 段 进 行 了 系 统 的 改 进 和 扩 充, 形 成 了 system V IPC, 其 通 信 进 程 主 要 局 限 在 单 个 计 算 机 内 ; 后 者 则 跳 过 了 该 限 制, 形 成 了 基 于 套 接 口 (socket) 的 进 程 间 通 信 机 制 而 Linux 则 把 两 者 的 优 势 都 继 承 了 下 来, 如 图 8.1 所 示 最 初 UNIX 的 进 程 间 通 信 System V 进 程 间 通 信 Socket 进 程 间 通 信 Linux 进 程 间 通 信 POSIX 进 程 间 通 信 UNIX 进 程 间 通 信 图 8.1 进 程 间 通 信 发 展 历 程 (IPC) 方 式 包 括 管 道 FIFO 以 及 信 号 System V 进 程 间 通 信 (IPC) 包 括 System V 消 息 队 列 System V 信 号 量 以 及 System V 共 享 内 存 区 Posix 进 程 间 通 信 (IPC) 包 括 Posix 消 息 队 列 Posix 信 号 量 以 及 Posix 共 享 内 存 区 现 在 在 Linux 中 使 用 较 多 的 进 程 间 通 信 方 式 主 要 有 以 下 几 种 (1) 管 道 (Pipe) 及 有 名 管 道 (named pipe): 管 道 可 用 于 具 有 亲 缘 关 系 进 程 间 的 通 信, 有 名 管 道, 除 具 有 管 道 所 具 有 的 功 能 外, 它 还 允 许 无 亲 缘 关 系 进 程 间 的 通 信 (2) 信 号 (Signal): 信 号 是 在 软 件 层 次 上 对 中 断 机 制 的 一 种 模 拟, 它 是 比 较 复 杂 的 通 信 方 式, 用 于 通 知 进 程 有 某 事 件 发 生, 一 个 进 程 收 到 一 个 信 号 与 处 理 器 收 到 一 个 中 断 请 求 效 果 上 可 以 说 是 一 样 的 (3) 消 息 队 列 (Messge Queue): 消 息 队 列 是 消 息 的 链 接 表, 包 括 Posix 消 息 队 列 SystemV 消 息 队 列 它 克 服 了 前 两 种 通 信 方 式 中 信 息 量 有 限 的 缺 点, 具 有 写 权 限 的 进 程 可 以 按 照 一 定 的 规 则 向 消 息 队 列 中 添 加 新 消 息 ; 对 消 息 队 列 有 读 权 限 的 进 程 则 可 以 从 消 息 队 列 中 读 取 消 息 (4) 共 享 内 存 (Shared memory): 可 以 说 这 是 最 有 用 的 进 程 间 通 信 方 式 它 使 得 多 个 进 程 可 以 访 问 同 一 块 内 存 空 间, 不 同 进 程 可 以 及 时 看 到 对 方 进 程 中 对 共 享 内 存 中 数 据 的 更 新 这 种 通 信 方 式 需 要 依 靠 某 种 同 步 机 制, 如 互 斥 锁 和 信 号 量 等 (5) 信 号 量 (Semaphore): 主 要 作 为 进 程 之 间 以 及 同 一 进 程 的 不 同 线 程 之 间 的 同 步 和 互 斥 手 段 (6) 套 接 字 (Socket): 这 是 一 种 更 为 一 般 的 进 程 间 通 信 机 制, 它 可 用 于 网 络 中 不 同 机 器 之 间 的 进 程 间 通 信, 应 用 非 常 广 泛 本 章 会 详 细 介 绍 前 5 种 进 程 通 信 方 式, 对 第 6 种 通 信 方 式 将 会 在 第 10 章 中 单 独 介 绍 8.2 管 道 管 道 概 述 本 书 在 第 2 章 中 介 绍 ps 的 命 令 时 提 到 过 管 道, 当 时 指 出 了 管 道 是 Linux 中 一 种 很 重 要 的 通 信 方 式, 它 是 把 一 个 程 序 的 输 出 直 接 连 接 到 另 一 个 程 序 的 输 入, 这 里 仍 以 第 2 章 中 的 ps ef grep ntp 为 例, 2
229 描 述 管 道 的 通 信 过 程, 如 图 8.2 所 示 进 程 ps -ef 进 程 grep ntp 内 核 管 道 图 8.2 管 道 的 通 信 过 程 管 道 是 Linux 中 进 程 间 通 信 的 一 种 方 式 这 里 所 说 的 管 道 主 要 指 无 名 管 道, 它 具 有 如 下 特 点 它 只 能 用 于 具 有 亲 缘 关 系 的 进 程 之 间 的 通 信 ( 也 就 是 父 子 进 程 或 者 兄 弟 进 程 之 间 ) 它 是 一 个 半 双 工 的 通 信 模 式, 具 有 固 定 的 读 端 和 写 端 管 道 也 可 以 看 成 是 一 种 特 殊 的 文 件, 对 于 它 的 读 写 也 可 以 使 用 普 通 的 read() 和 write() 等 函 数 但 是 它 不 是 普 通 的 文 件, 并 不 属 于 其 他 任 何 文 件 系 统, 并 且 只 存 在 于 内 核 的 内 存 空 间 中 管 道 系 统 调 用 1. 管 道 创 建 与 关 闭 说 明 管 道 是 基 于 文 件 描 述 符 的 通 信 方 式, 当 一 个 管 道 建 立 时, 它 会 创 建 两 个 文 件 描 述 符 fds[0] 和 fds[1], 其 中 fds[0] 固 定 用 于 读 管 道, 而 fd[1] 固 定 用 于 写 管 道, 如 图 8.3 所 示, 这 样 就 构 成 了 一 个 半 双 工 的 通 道 用 户 进 程 fd[0] fd[1] 读 内 核 管 道 写 图 8.3 Linux 中 管 道 与 文 件 描 述 符 的 关 系 管 道 关 闭 时 只 需 将 这 两 个 文 件 描 述 符 关 闭 即 可, 可 使 用 普 通 的 close() 函 数 逐 个 关 闭 各 个 文 件 描 述 符 当 一 个 管 道 共 享 多 对 文 件 描 述 符 时, 若 将 其 中 的 一 对 读 写 文 件 描 述 符 都 删 除, 则 该 管 道 就 失 效 2. 管 道 创 建 函 数 创 建 管 道 可 以 通 过 调 用 pipe() 来 实 现, 表 8.1 列 出 了 pipe() 函 数 的 语 法 要 点 表 8.1 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 pipe() 函 数 语 法 要 点 #include <unistd.h> int pipe(int fd[2]) fd[2]: 管 道 的 两 个 文 件 描 述 符, 之 后 就 可 以 直 接 操 作 这 两 个 文 件 描 述 符 成 功 :0 出 错 : 1 3. 管 道 读 写 说 明 用 pipe() 函 数 创 建 的 管 道 两 端 处 于 一 个 进 程 中, 由 于 管 道 是 主 要 用 于 在 不 同 进 程 间 通 信 的, 因 此 这 在 实 际 应 用 中 没 有 太 大 意 义 实 际 上, 通 常 先 是 创 建 一 个 管 道, 再 通 过 fork() 函 数 创 建 一 子 进 程, 该 子 进 程 会 继 承 父 进 程 所 创 建 的 管 道, 这 时, 父 子 进 程 管 道 的 文 件 描 述 符 对 应 关 系 如 图 8.4 所 示 此 时 的 关 系 看 似 非 常 复 杂, 实 际 上 却 已 经 给 不 同 进 程 之 间 的 读 写 创 造 了 很 好 的 条 件 父 子 进 程 分 别 拥 有 自 3
230 己 的 读 写 通 道, 为 了 实 现 父 子 进 程 之 间 的 读 写, 只 需 把 无 关 的 读 端 或 写 端 的 文 件 描 述 符 关 闭 即 可 例 如 在 图 8.5 中 将 父 进 程 的 写 端 fd[1] 和 子 进 程 的 读 端 fd[0] 关 闭 此 时, 父 子 进 程 之 间 就 建 立 起 了 一 条 子 进 程 写 入 父 进 程 读 取 的 通 道 父 进 程 fd[0] fd[1] 子 进 程 fd[0] fd[1] 父 进 程 fd[0] fd[1] 子 进 程 fd[0] fd[1] 内 核 管 道 内 核 管 道 图 8.4 父 子 进 程 管 道 的 文 件 描 述 符 对 应 关 系 图 8.5 关 闭 父 进 程 fd[1] 和 子 进 程 fd[0] 同 样, 也 可 以 关 闭 父 进 程 的 fd[0] 和 子 进 程 的 fd[1], 这 样 就 可 以 建 立 一 条 父 进 程 写 入 子 进 程 读 取 的 通 道 另 外, 父 进 程 还 可 以 创 建 多 个 子 进 程, 各 个 子 进 程 都 继 承 了 相 应 的 fd[0] 和 fd[1], 这 时, 只 需 要 关 闭 相 应 端 口 就 可 以 建 立 其 各 子 进 程 之 间 的 通 道 为 什 么 无 名 管 道 只 能 在 具 有 亲 缘 关 系 的 进 程 之 间 建 立? 4. 管 道 使 用 实 例 在 本 例 中, 首 先 创 建 管 道, 之 后 父 进 程 使 用 fork() 函 数 创 建 子 进 程, 之 后 通 过 关 闭 父 进 程 的 读 描 述 符 和 子 进 程 的 写 描 述 符, 建 立 起 它 们 之 间 的 管 道 通 信 /* pipe.c */ #include <unistd.h> #include <sys/types.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #define MAX_DATA_LEN 256 #define DELAY_TIME 1 int main() pid_t pid; int pipe_fd[2]; char buf[max_data_len]; const char data[] = "Pipe Test Program"; int real_read, real_write; memset((void*)buf, 0, sizeof(buf)); /* 创 建 管 道 */ if (pipe(pipe_fd) < 0) printf("pipe create error\n"); exit(1); /* 创 建 一 子 进 程 */ 4
231 if ((pid = fork()) == 0) /* 子 进 程 关 闭 写 描 述 符, 并 通 过 使 子 进 程 暂 停 1s 等 待 父 进 程 已 关 闭 相 应 的 读 描 述 符 */ close(pipe_fd[1]); sleep(delay_time * 3); /* 子 进 程 读 取 管 道 内 容 */ if ((real_read = read(pipe_fd[0], buf, MAX_DATA_LEN)) > 0) printf("%d bytes read from the pipe is '%s'\n", real_read, buf); /* 关 闭 子 进 程 读 描 述 符 */ close(pipe_fd[0]); exit(0); else if (pid > 0) /* 父 进 程 关 闭 读 描 述 符, 并 通 过 使 父 进 程 暂 停 1s 等 待 子 进 程 已 关 闭 相 应 的 写 描 述 符 */ close(pipe_fd[0]); sleep(delay_time); if((real_write = write(pipe_fd[1], data, strlen(data)))!= -1) printf("parent wrote %d bytes : '%s'\n", real_write, data); /* 关 闭 父 进 程 写 描 述 符 */ close(pipe_fd[1]); /* 收 集 子 进 程 退 出 信 息 */ waitpid(pid, NULL, 0); exit(0); 将 该 程 序 交 叉 编 译, 下 载 到 开 发 板 上 的 运 行 结 果 如 下 所 示 : $./pipe Parent wrote 17 bytes : 'Pipe Test Program' 17 bytes read from the pipe is 'Pipe Test Program' 5. 管 道 读 写 注 意 点 只 有 在 管 道 的 读 端 存 在 时, 向 管 道 写 入 数 据 才 有 意 义 否 则, 向 管 道 写 入 数 据 的 进 程 将 收 到 内 核 传 来 的 SIGPIPE 信 号 ( 通 常 为 Broken pipe 错 误 ) 向 管 道 写 入 数 据 时,Linux 将 不 保 证 写 入 的 原 子 性, 管 道 缓 冲 区 一 有 空 闲 区 域, 写 进 程 就 会 试 图 5
232 向 管 道 写 入 数 据 如 果 读 进 程 不 读 取 管 道 缓 冲 区 中 的 数 据, 那 么 写 操 作 将 会 一 直 阻 塞 父 子 进 程 在 运 行 时, 它 们 的 先 后 次 序 并 不 能 保 证, 因 此, 在 这 里 为 了 保 证 父 子 进 程 已 经 关 闭 了 相 应 的 文 件 描 述 符, 可 在 两 个 进 程 中 调 用 sleep() 函 数, 当 然 这 种 调 用 不 是 很 好 的 解 决 方 法, 在 后 面 学 到 进 程 之 间 的 同 步 与 互 斥 机 制 之 后, 请 读 者 自 行 修 改 本 小 节 的 实 例 程 序 标 准 流 管 道 1. 标 准 流 管 道 函 数 说 明 与 Linux 的 文 件 操 作 中 有 基 于 文 件 流 的 标 准 I/O 操 作 一 样, 管 道 的 操 作 也 支 持 基 于 文 件 流 的 模 式 这 种 基 于 文 件 流 的 管 道 主 要 是 用 来 创 建 一 个 连 接 到 另 一 个 进 程 的 管 道, 这 里 的 另 一 个 进 程 也 就 是 一 个 可 以 进 行 一 定 操 作 的 可 执 行 文 件, 例 如, 用 户 执 行 ls -l 或 者 自 己 编 写 的 程 序./pipe 等 由 于 这 一 类 操 作 很 常 用, 因 此 标 准 流 管 道 就 将 一 系 列 的 创 建 过 程 合 并 到 一 个 函 数 popen() 中 完 成 它 所 完 成 的 工 作 有 以 下 几 步 创 建 一 个 管 道 fork() 一 个 子 进 程 在 父 子 进 程 中 关 闭 不 需 要 的 文 件 描 述 符 执 行 exec 函 数 族 调 用 执 行 函 数 中 所 指 定 的 命 令 这 个 函 数 的 使 用 可 以 大 大 减 少 代 码 的 编 写 量, 但 同 时 也 有 一 些 不 利 之 处, 例 如, 它 不 如 前 面 管 道 创 建 的 函 数 那 样 灵 活 多 样, 并 且 用 popen() 创 建 的 管 道 必 须 使 用 标 准 I/O 函 数 进 行 操 作, 但 不 能 使 用 前 面 的 read() write() 一 类 不 带 缓 冲 的 I/O 函 数 与 之 相 对 应, 关 闭 用 popen() 创 建 的 流 管 道 必 须 使 用 函 数 pclose() 来 关 闭 该 管 道 流 该 函 数 关 闭 标 准 I/O 流, 并 等 待 命 令 执 行 结 束 2. 函 数 格 式 popen() 和 pclose() 函 数 格 式 如 表 8.2 和 表 8.3 所 示 表 8.2 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 表 8.3 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <stdio.h> popen() 函 数 语 法 要 点 FILE *popen(const char *command, const char *type) command: 指 向 的 是 一 个 以 null 结 束 符 结 尾 的 字 符 串, 这 个 字 符 串 包 含 一 个 shell 命 令, 并 被 送 到 /bin/sh 以 -c 参 数 执 行, 即 由 shell 来 执 行 type: 成 功 : 文 件 流 指 针 出 错 : 1 #include <stdio.h> r : 文 件 指 针 连 接 到 command 的 标 准 输 出, 即 该 命 令 的 结 果 产 生 输 出 w : 文 件 指 针 连 接 到 command 的 标 准 输 入, 即 该 命 令 的 结 果 产 生 输 入 int pclose(file *stream) stream: 要 关 闭 的 文 件 流 pclose() 函 数 语 法 要 点 成 功 : 返 回 由 popen() 所 执 行 的 进 程 的 退 出 码 出 错 : 1 6
233 3. 函 数 使 用 实 例 在 该 实 例 中, 使 用 popen() 来 执 行 ps -ef 命 令 可 以 看 出,popen() 函 数 的 使 用 能 够 使 程 序 变 得 短 小 精 悍 /* standard_pipe.c */ #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <fcntl.h> #define BUFSIZE 1024 int main() FILE *fp; char *cmd = "ps -ef"; char buf[bufsize]; /* 调 用 popen() 函 数 执 行 相 应 的 命 令 */ if ((fp = popen(cmd, "r")) == NULL) printf("popen error\n"); exit(1); while ((fgets(buf, BUFSIZE, fp))!= NULL) printf("%s",buf); pclose(fp); exit(0); 下 面 是 该 程 序 在 目 标 板 上 的 执 行 结 果 $./standard_pipe PID TTY Uid Size State Command 1 root 1832 S init 2 root 0 S [keventd] 3 root 0 S [ksoftirqd_cpu0] 74 root 1284 S./standard_pipe 75 root 1836 S sh -c ps -ef 76 root 2020 R ps ef 7
234 8.2.5 FIFO 1. 有 名 管 道 说 明 前 面 介 绍 的 管 道 是 无 名 管 道, 它 只 能 用 于 具 有 亲 缘 关 系 的 进 程 之 间, 这 就 大 大 地 限 制 了 管 道 的 使 用 有 名 管 道 的 出 现 突 破 了 这 种 限 制, 它 可 以 使 互 不 相 关 的 两 个 进 程 实 现 彼 此 通 信 该 管 道 可 以 通 过 路 径 名 来 指 出, 并 且 在 文 件 系 统 中 是 可 见 的 在 建 立 了 管 道 之 后, 两 个 进 程 就 可 以 把 它 当 作 普 通 文 件 一 样 进 行 读 写 操 作, 使 用 非 常 方 便 不 过 值 得 注 意 的 是,FIFO 是 严 格 地 遵 循 先 进 先 出 规 则 的, 对 管 道 及 FIFO 的 读 总 是 从 开 始 处 返 回 数 据, 对 它 们 的 写 则 把 数 据 添 加 到 末 尾, 它 们 不 支 持 如 lseek() 等 文 件 定 位 操 作 有 名 管 道 的 创 建 可 以 使 用 函 数 mkfifo(), 该 函 数 类 似 文 件 中 的 open() 操 作, 可 以 指 定 管 道 的 路 径 和 打 开 的 模 式 用 户 还 可 以 在 命 令 行 使 用 mknod 管 道 名 p 来 创 建 有 名 管 道 在 创 建 管 道 成 功 之 后, 就 可 以 使 用 open() read() 和 write() 这 些 函 数 了 与 普 通 文 件 的 开 发 设 置 一 样, 对 于 为 读 而 打 开 的 管 道 可 在 open() 中 设 置 O_RDONLY, 对 于 为 写 而 打 开 的 管 道 可 在 open() 中 设 置 O_WRONLY, 在 这 里 与 普 通 文 件 不 同 的 是 阻 塞 问 题 由 于 普 通 文 件 的 读 写 时 不 会 出 现 阻 塞 问 题, 而 在 管 道 的 读 写 中 却 有 阻 塞 的 可 能, 这 里 的 非 阻 塞 标 志 可 以 在 open() 函 数 中 设 定 为 O_NONBLOCK 下 面 分 别 对 阻 塞 打 开 和 非 阻 塞 打 开 的 读 写 进 行 讨 论 (1) 对 于 读 进 程 若 该 管 道 是 阻 塞 打 开, 且 当 前 FIFO 内 没 有 数 据, 则 对 读 进 程 而 言 将 一 直 阻 塞 到 有 数 据 写 入 若 该 管 道 是 非 阻 塞 打 开, 则 不 论 FIFO 内 是 否 有 数 据, 读 进 程 都 会 立 即 执 行 读 操 作 即 如 果 FIFO 内 没 有 数 据, 则 读 函 数 将 立 刻 返 回 0 (2) 对 于 写 进 程 若 该 管 道 是 阻 塞 打 开, 则 写 操 作 将 一 直 阻 塞 到 数 据 可 以 被 写 入 若 该 管 道 是 非 阻 塞 打 开 而 不 能 写 入 全 部 数 据, 则 读 操 作 进 行 部 分 写 入 或 者 调 用 失 败 2.mkfifo() 函 数 格 式 表 8.4 列 出 了 mkfifo() 函 数 的 语 法 要 点 表 8.4 mkfifo() 函 数 语 法 要 点 所 需 头 文 件 函 数 原 型 函 数 传 入 值 #include <sys/types.h> #include <sys/state.h> int mkfifo(const char *filename,mode_t mode) filename: 要 创 建 的 管 道 函 数 传 入 值 函 数 传 入 值 mode: mode: O_RDONLY: 读 管 道 O_WRONLY: 写 管 道 O_RDWR: 读 写 管 道 O_NONBLOCK: 非 阻 塞 O_CREAT: 如 果 该 文 件 不 存 在, 那 么 就 创 建 一 个 新 的 文 件, 并 用 第 三 个 参 数 为 其 设 置 权 限 O_EXCL: 如 果 使 用 O_CREAT 时 文 件 存 在, 那 么 可 返 回 错 误 消 息 这 一 参 数 可 测 试 文 件 是 否 存 在 函 数 返 回 值 成 功 :0 8
235 出 错 : 1 表 8.5 再 对 FIFO 相 关 的 出 错 信 息 做 一 归 纳, 以 方 便 用 户 查 错 表 8.5 EACCESS EEXIST ENAMETOOLONG ENOENT ENOSPC ENOTDIR EROFS FIFO 相 关 的 出 错 信 息 参 数 filename 所 指 定 的 目 录 路 径 无 可 执 行 的 权 限 参 数 filename 所 指 定 的 文 件 已 存 在 参 数 filename 的 路 径 名 称 太 长 参 数 filename 包 含 的 目 录 不 存 在 文 件 系 统 的 剩 余 空 间 不 足 参 数 filename 路 径 中 的 目 录 存 在 但 却 非 真 正 的 目 录 参 数 filename 指 定 的 文 件 存 在 于 只 读 文 件 系 统 内 3. 使 用 实 例 下 面 的 实 例 包 含 了 两 个 程 序, 一 个 用 于 读 管 道, 另 一 个 用 于 写 管 道 其 中 在 读 管 道 的 程 序 里 创 建 管 道, 并 且 作 为 main() 函 数 里 的 参 数 由 用 户 输 入 要 写 入 的 内 容 读 管 道 的 程 序 会 读 出 用 户 写 入 到 管 道 的 内 容, 这 两 个 程 序 采 用 的 是 阻 塞 式 读 写 管 道 模 式 以 下 是 写 管 道 的 程 序 : /* fifo_write.c */ #include <sys/types.h> #include <sys/stat.h> #include <errno.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <limits.h> #define MYFIFO "/tmp/myfifo" /* 有 名 管 道 文 件 名 */ #define MAX_BUFFER_SIZE PIPE_BUF /* 定 义 在 于 limits.h 中 */ int main(int argc, char * argv[]) /* 参 数 为 即 将 写 入 的 字 符 串 */ int fd; char buff[max_buffer_size]; int nwrite; if(argc <= 1) printf("usage:./fifo_write string\n"); exit(1); sscanf(argv[1], "%s", buff); /* 以 只 写 阻 塞 方 式 打 开 FIFO 管 道 */ fd = open(myfifo, O_WRONLY); if (fd == -1) printf("open fifo file error\n"); exit(1); 9
236 /* 向 管 道 中 写 入 字 符 串 */ if ((nwrite = write(fd, buff, MAX_BUFFER_SIZE)) > 0) printf("write '%s' to FIFO\n", buff); close(fd); exit(0); 以 下 是 读 管 道 程 序 : /*fifo_read.c*/ ( 头 文 件 和 宏 定 义 同 fifo_write.c) int main() char buff[max_buffer_size]; int fd; int nread; 专 业 始 于 专 注 卓 识 源 于 远 见 /* 判 断 有 名 管 道 是 否 已 存 在, 若 尚 未 创 建, 则 以 相 应 的 权 限 创 建 */ if (access(myfifo, F_OK) == -1) if ((mkfifo(myfifo, 0666) < 0) && (errno!= EEXIST)) printf("cannot create fifo file\n"); exit(1); /* 以 只 读 阻 塞 方 式 打 开 有 名 管 道 */ fd = open(myfifo, O_RDONLY); if (fd == -1) printf("open fifo file error\n"); exit(1); while (1) memset(buff, 0, sizeof(buff)); if ((nread = read(fd, buff, MAX_BUFFER_SIZE)) > 0) printf("read '%s' from FIFO\n", buff); close(fd); exit(0); 为 了 能 够 较 好 地 观 察 运 行 结 果, 需 要 把 这 两 个 程 序 分 别 在 两 个 终 端 里 运 行, 在 这 里 首 先 启 动 读 管 道 程 序 读 管 道 进 程 在 建 立 管 道 之 后 就 开 始 循 环 地 从 管 道 里 读 出 内 容, 如 果 没 有 数 据 可 读, 则 一 直 阻 塞 到 写 管 道 进 程 向 管 道 写 入 数 据 在 启 动 了 写 管 道 程 序 后, 读 进 程 能 够 从 管 道 里 读 出 用 户 的 输 入 内 容, 程 序 运 行 结 果 如 下 所 示 终 端 一 : 10
237 $./fifo_read Read 'FIFO' from FIFO Read 'Test' from FIFO Read 'Program' from FIFO 终 端 二 : $./fifo_write FIFO Write 'FIFO' to FIFO $./fifo_write Test Write 'Test' to FIFO $./fifo_write Program Write 'Program' to FIFO 8.3 信 号 信 号 概 述 信 号 是 UNIX 中 所 使 用 的 进 程 通 信 的 一 种 最 古 老 的 方 法 它 是 在 软 件 层 次 上 对 中 断 机 制 的 一 种 模 拟, 是 一 种 异 步 通 信 方 式 信 号 可 以 直 接 进 行 用 户 空 间 进 程 和 内 核 进 程 之 间 的 交 互, 内 核 进 程 也 可 以 利 用 它 来 通 知 用 户 空 间 进 程 发 生 了 哪 些 系 统 事 件 它 可 以 在 任 何 时 候 发 给 某 一 进 程, 而 无 需 知 道 该 进 程 的 状 态 如 果 该 进 程 当 前 并 未 处 于 执 行 态, 则 该 信 号 就 由 内 核 保 存 起 来, 直 到 该 进 程 恢 复 执 行 再 传 递 给 它 为 止 ; 如 果 一 个 信 号 被 进 程 设 置 为 阻 塞, 则 该 信 号 的 传 递 被 延 迟, 直 到 其 阻 塞 被 取 消 时 才 被 传 递 给 进 程 在 第 2 章 kill 命 令 中 曾 讲 解 到 l 选 项, 这 个 选 项 可 以 列 出 该 系 统 所 支 持 的 所 有 信 号 的 列 表 在 笔 者 的 系 统 中, 信 号 值 在 32 之 前 的 则 有 不 同 的 名 称, 而 信 号 值 在 32 以 后 的 都 是 用 SIGRTMIN 或 SIGRTMAX 开 头 的, 这 就 是 两 类 典 型 的 信 号 前 者 是 从 UNIX 系 统 中 继 承 下 来 的 信 号, 为 不 可 靠 信 号 ( 也 称 为 非 实 时 信 号 ); 后 者 是 为 了 解 决 前 面 不 可 靠 信 号 的 问 题 而 进 行 了 更 改 和 扩 充 的 信 号, 称 为 可 靠 信 号 ( 也 称 为 实 时 信 号 ) 那 么 为 什 么 之 前 的 信 号 不 可 靠 呢? 这 里 首 先 要 介 绍 一 下 信 号 的 生 命 周 期 一 个 完 整 的 信 号 生 命 周 期 可 以 分 为 3 个 重 要 阶 段, 这 3 个 阶 段 由 4 个 重 要 事 件 来 刻 画 的 : 信 号 产 生 信 号 在 进 程 中 注 册 信 号 在 进 程 中 注 销 执 行 信 号 处 理 函 数, 如 图 8.6 所 示 相 邻 两 个 事 件 的 时 间 间 隔 构 成 信 号 生 命 周 期 的 一 个 阶 段 要 注 意 这 里 的 信 号 处 理 有 多 种 方 式, 一 般 是 由 内 核 完 成 的, 当 然 也 可 以 由 用 户 进 程 来 完 成, 故 在 此 没 有 明 确 画 出 内 核 进 程 用 户 进 程 信 号 产 生 信 号 注 册 信 号 注 销 信 号 处 理 图 8.6 信 号 生 命 周 期 一 个 不 可 靠 信 号 的 处 理 过 程 是 这 样 的 : 如 果 发 现 该 信 号 已 经 在 进 程 中 注 册, 那 么 就 忽 略 该 信 号 因 此, 若 前 一 个 信 号 还 未 注 销 又 产 生 了 相 同 的 信 号 就 会 产 生 信 号 丢 失 而 当 可 靠 信 号 发 送 给 一 个 进 程 时, 不 管 该 信 号 是 否 已 经 在 进 程 中 注 册, 都 会 被 再 注 册 一 次, 因 此 信 号 就 不 会 丢 失 所 有 可 靠 信 号 都 支 持 排 队, 而 所 有 不 可 靠 信 号 都 不 支 持 排 队 11
238 这 里 信 号 的 产 生 注 册 和 注 销 等 是 指 信 号 的 内 部 实 现 机 制, 而 不 是 调 用 信 号 的 函 数 实 现 因 此, 信 号 注 册 与 否, 与 本 节 后 面 讲 到 的 发 送 信 号 函 数 ( 如 kill() 等 ) 以 及 信 号 安 装 函 数 ( 如 signal() 等 ) 无 关, 只 与 信 号 值 有 关 用 户 进 程 对 信 号 的 响 应 可 以 有 3 种 方 式 忽 略 信 号, 即 对 信 号 不 做 任 何 处 理, 但 是 有 两 个 信 号 不 能 忽 略, 即 SIGKILL 及 SIGSTOP 捕 捉 信 号, 定 义 信 号 处 理 函 数, 当 信 号 发 生 时, 执 行 相 应 的 自 定 义 处 理 函 数 执 行 缺 省 操 作,Linux 对 每 种 信 号 都 规 定 了 默 认 操 作 Linux 中 的 大 多 数 信 号 是 提 供 给 内 核 的, 表 8.6 列 出 了 Linux 中 最 为 常 见 信 号 的 含 义 及 其 默 认 操 作 表 8.6 常 见 信 号 的 含 义 及 其 默 认 操 作 信 号 名 含 义 默 认 操 作 SIGHUP SIGINT 该 信 号 在 用 户 终 端 连 接 ( 正 常 或 非 正 常 ) 结 束 时 发 出, 通 常 是 在 终 端 的 控 制 进 程 结 束 时, 通 知 同 一 会 话 内 的 各 个 作 业 与 控 制 终 端 不 再 关 联 该 信 号 在 用 户 键 入 INTR 字 符 ( 通 常 是 Ctrl-C) 时 发 出, 终 端 驱 动 程 序 发 送 此 信 号 并 送 到 前 台 进 程 中 的 每 一 个 进 程 SIGQUIT 该 信 号 和 SIGINT 类 似, 但 由 QUIT 字 符 ( 通 常 是 Ctrl-\) 来 控 制 终 止 SIGILL SIGFPE 该 信 号 在 一 个 进 程 企 图 执 行 一 条 非 法 指 令 时 ( 可 执 行 文 件 本 身 出 现 错 误, 或 者 试 图 执 行 数 据 段 堆 栈 溢 出 时 ) 发 出 该 信 号 在 发 生 致 命 的 算 术 运 算 错 误 时 发 出 这 里 不 仅 包 括 浮 点 运 算 错 误, 还 包 括 溢 出 及 除 数 为 0 等 其 他 所 有 的 算 术 错 误 SIGKILL 该 信 号 用 来 立 即 结 束 程 序 的 运 行, 并 且 不 能 被 阻 塞 处 理 或 忽 略 终 止 SIGALRM 该 信 号 当 一 个 定 时 器 到 时 的 时 候 发 出 终 止 SIGSTOP 该 信 号 用 于 暂 停 一 个 进 程, 且 不 能 被 阻 塞 处 理 或 忽 略 暂 停 进 程 SIGTSTP 该 信 号 用 于 交 互 停 止 进 程, 用 户 键 入 SUSP 字 符 时 ( 通 常 是 Ctrl+Z) 发 出 这 个 信 号 终 止 终 止 终 止 终 止 停 止 进 程 SIGCHLD 子 进 程 改 变 状 态 时, 父 进 程 会 收 到 这 个 信 号 忽 略 SIGABORT 进 程 异 常 终 止 时 发 出 信 号 发 送 与 捕 捉 发 送 信 号 的 函 数 主 要 有 kill() raise() alarm() 以 及 pause(), 下 面 就 依 次 对 其 进 行 介 绍 1.kill() 和 raise() (1) 函 数 说 明 kill() 函 数 同 读 者 熟 知 的 kill 系 统 命 令 一 样, 可 以 发 送 信 号 给 进 程 或 进 程 组 ( 实 际 上,kill 系 统 命 令 只 是 kill() 函 数 的 一 个 用 户 接 口 ) 这 里 需 要 注 意 的 是, 它 不 仅 可 以 中 止 进 程 ( 实 际 上 发 出 SIGKILL 信 号 ), 也 可 以 向 进 程 发 送 其 他 信 号 与 kill() 函 数 所 不 同 的 是,raise() 函 数 允 许 进 程 向 自 身 发 送 信 号 (2) 函 数 格 式 表 8.7 列 出 了 kill() 函 数 的 语 法 要 点 表 8.7 所 需 头 文 件 函 数 原 型 函 数 传 入 值 #include <signal.h> #include <sys/types.h> int kill(pid_t pid, int sig) pid: kill() 函 数 语 法 要 点 正 数 : 要 发 送 信 号 的 进 程 号 0: 信 号 被 发 送 到 所 有 和 当 前 进 程 在 同 一 个 进 程 组 的 进 程 12
239 1: 信 号 发 给 所 有 的 进 程 表 中 的 进 程 ( 除 了 进 程 号 最 大 的 进 程 外 ) <-1: 信 号 发 送 给 进 程 组 号 为 -pid 的 每 一 个 进 程 sig: 信 号 成 功 :0 函 数 返 回 值 出 错 : 1 表 8.8 列 出 了 raise() 函 数 的 语 法 要 点 表 8.8 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <signal.h> #include <sys/types.h> int raise(int sig) sig: 信 号 成 功 :0 出 错 : 1 raise() 函 数 语 法 要 点 (3) 函 数 实 例 下 面 这 个 示 例 首 先 使 用 fork() 创 建 了 一 个 子 进 程, 接 着 为 了 保 证 子 进 程 不 在 父 进 程 调 用 kill() 之 前 退 出, 在 子 进 程 中 使 用 raise() 函 数 向 自 身 发 送 SIGSTOP 信 号, 使 子 进 程 暂 停 接 下 来 再 在 父 进 程 中 调 用 kill() 向 子 进 程 发 送 信 号, 在 该 示 例 中 使 用 的 是 SIGKILL, 读 者 可 以 使 用 其 他 信 号 进 行 练 习 /* kill_raise.c */ #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <sys/types.h> #include <sys/wait.h> int main() pid_t pid; int ret; /* 创 建 一 子 进 程 */ if ((pid = fork()) < 0) printf("fork error\n"); exit(1); if (pid == 0) /* 在 子 进 程 中 使 用 raise() 函 数 发 出 SIGSTOP 信 号, 使 子 进 程 暂 停 */ printf("child(pid : %d) is waiting for any signal\n", getpid()); raise(sigstop); exit(0); else /* 在 父 进 程 中 收 集 子 进 程 发 出 的 信 号, 并 调 用 kill() 函 数 进 行 相 应 的 操 作 */ 13
240 if ((waitpid(pid, NULL, WNOHANG)) == 0) if ((ret = kill(pid, SIGKILL)) == 0) printf("parent kill %d\n",pid); waitpid(pid, NULL, 0); exit(0); 该 程 序 运 行 结 果 如 下 所 示 : $./kill_raise Child(pid : 4877) is waiting for any signal Parent kill alarm() 和 pause() (1) 函 数 说 明 alarm() 也 称 为 闹 钟 函 数, 它 可 以 在 进 程 中 设 置 一 个 定 时 器, 当 定 时 器 指 定 的 时 间 到 时, 它 就 向 进 程 发 送 SIGALARM 信 号 要 注 意 的 是, 一 个 进 程 只 能 有 一 个 闹 钟 时 间, 如 果 在 调 用 alarm() 之 前 已 设 置 过 闹 钟 时 间, 则 任 何 以 前 的 闹 钟 时 间 都 被 新 值 所 代 替 pause() 函 数 是 用 于 将 调 用 进 程 挂 起 直 至 捕 捉 到 信 号 为 止 这 个 函 数 很 常 用, 通 常 可 以 用 于 判 断 信 号 是 否 已 到 (2) 函 数 格 式 表 8.9 列 出 了 alarm() 函 数 的 语 法 要 点 表 8.9 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <unistd.h> alarm() 函 数 语 法 要 点 unsigned int alarm(unsigned int seconds) seconds: 指 定 秒 数, 系 统 经 过 seconds 秒 之 后 向 该 进 程 发 送 SIGALRM 信 号 成 功 : 如 果 调 用 此 alarm() 前, 进 程 中 已 经 设 置 了 闹 钟 时 间, 则 返 回 上 一 个 闹 钟 时 间 的 剩 余 时 间, 否 则 返 回 0 出 错 : 1 表 8.10 列 出 了 pause() 函 数 的 语 法 要 点 表 8.10 pause() 函 数 语 法 要 点 所 需 头 文 件 #include <unistd.h> 函 数 原 型 int pause(void) 函 数 返 回 值 1, 并 且 把 error 值 设 为 EINTR (3) 函 数 实 例 该 实 例 实 际 上 已 完 成 了 一 个 简 单 的 sleep() 函 数 的 功 能, 由 于 SIGALARM 默 认 的 系 统 动 作 为 终 止 该 进 程, 因 此 程 序 在 打 印 信 息 之 前, 就 会 被 结 束 了 代 码 如 下 所 示 : /* alarm_pause.c */ #include <unistd.h> #include <stdio.h> 14
241 #include <stdlib.h> int main() /* 调 用 alarm 定 时 器 函 数 */ int ret = alarm(5); pause(); printf("i have been waken up.\n",ret); /* 此 语 句 不 会 被 执 行 */ $./alarm_pause Alarm clock 用 这 种 形 式 实 现 的 sleep() 功 能 有 什 么 问 题? 信 号 的 处 理 在 了 解 了 信 号 的 产 生 与 捕 获 之 后, 接 下 来 就 要 对 信 号 进 行 具 体 的 操 作 了 从 前 面 的 信 号 概 述 中 读 者 也 可 以 看 到, 特 定 的 信 号 是 与 一 定 的 进 程 相 联 系 的 也 就 是 说, 一 个 进 程 可 以 决 定 在 该 进 程 中 需 要 对 哪 些 信 号 进 行 什 么 样 的 处 理 例 如, 一 个 进 程 可 以 选 择 忽 略 某 些 信 号 而 只 处 理 其 他 一 些 信 号, 另 外, 一 个 进 程 还 可 以 选 择 如 何 处 理 信 号 总 之, 这 些 都 是 与 特 定 的 进 程 相 联 系 的 因 此, 首 先 就 要 建 立 进 程 与 其 信 号 之 间 的 对 应 关 系, 这 就 是 信 号 的 处 理 请 读 者 注 意 信 号 的 注 册 与 信 号 的 处 理 之 间 的 区 别, 前 者 信 号 是 主 动 方, 而 后 者 进 程 是 主 动 方 信 号 的 注 册 是 在 进 程 选 择 了 特 定 信 号 处 理 之 后 特 定 信 号 的 主 动 行 为 信 号 处 理 的 主 要 方 法 有 两 种, 一 种 是 使 用 简 单 的 signal() 函 数, 另 一 种 是 使 用 信 号 集 函 数 组 下 面 分 别 介 绍 这 两 种 处 理 方 式 1. 信 号 处 理 函 数 (1) 函 数 说 明 使 用 signal() 函 数 处 理 时, 只 需 要 指 出 要 处 理 的 信 号 和 处 理 函 数 即 可 它 主 要 是 用 于 前 32 种 非 实 时 信 号 的 处 理, 不 支 持 信 号 传 递 信 息, 但 是 由 于 使 用 简 单 易 于 理 解, 因 此 也 受 到 很 多 程 序 员 的 欢 迎 Linux 还 支 持 一 个 更 健 壮 更 新 的 信 号 处 理 函 数 sigaction(), 推 荐 使 用 该 函 数 (2) 函 数 格 式 signal() 函 数 的 语 法 要 点 如 表 8.11 所 示 表 8.11 所 需 头 文 件 函 数 原 型 #include <signal.h> signal() 函 数 语 法 要 点 void (*signal(int signum, void (*handler)(int)))(int) signum: 指 定 信 号 代 码 函 数 传 入 值 handler: SIG_IGN: 忽 略 该 信 号 SIG_DFL: 采 用 系 统 默 认 方 式 处 理 信 号 自 定 义 的 信 号 处 理 函 数 指 针 函 数 返 回 值 成 功 : 以 前 的 信 号 处 理 配 置 出 错 : 1 15
242 这 里 需 要 对 这 个 函 数 原 型 进 行 说 明 这 个 函 数 原 型 有 点 复 杂 可 先 用 如 下 的 typedef 进 行 替 换 说 明 : typedef void sign(int); sign *signal(int, handler *); 可 见, 首 先 该 函 数 原 型 整 体 指 向 一 个 无 返 回 值 并 且 带 一 个 整 型 参 数 的 函 数 指 针, 也 就 是 信 号 的 原 始 配 置 函 数 接 着 该 原 型 又 带 有 两 个 参 数, 其 中 的 第 二 个 参 数 可 以 是 用 户 自 定 义 的 信 号 处 理 函 数 的 函 数 指 针 表 8.12 列 举 了 sigaction() 的 语 法 要 点 表 8.12 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 sigaction() 函 数 语 法 要 点 #include <signal.h> int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact) signum: 信 号 代 码, 可 以 为 除 SIGKILL 及 SIGSTOP 外 的 任 何 一 个 特 定 有 效 的 信 号 act: 指 向 结 构 sigaction 的 一 个 实 例 的 指 针, 指 定 对 特 定 信 号 的 处 理 oldact: 保 存 原 来 对 相 应 信 号 的 处 理 成 功 :0 出 错 : 1 这 里 要 说 明 的 是 sigaction() 函 数 中 第 2 个 和 第 3 个 参 数 用 到 的 sigaction 结 构 这 是 一 个 看 似 非 常 复 杂 的 结 构, 希 望 读 者 能 够 慢 慢 阅 读 此 段 内 容 首 先 给 出 了 sigaction 的 定 义, 如 下 所 示 : struct sigaction void (*sa_handler)(int signo); sigset_t sa_mask; int sa_flags; void (*sa_restore)(void); sa_handler 是 一 个 函 数 指 针, 指 定 信 号 处 理 函 数, 这 里 除 可 以 是 用 户 自 定 义 的 处 理 函 数 外, 还 可 以 为 SIG_DFL( 采 用 缺 省 的 处 理 方 式 ) 或 SIG_IGN( 忽 略 信 号 ) 它 的 处 理 函 数 只 有 一 个 参 数, 即 信 号 值 sa_mask 是 一 个 信 号 集, 它 可 以 指 定 在 信 号 处 理 程 序 执 行 过 程 中 哪 些 信 号 应 当 被 屏 蔽, 在 调 用 信 号 捕 获 函 数 之 前, 该 信 号 集 要 加 入 到 信 号 的 信 号 屏 蔽 字 中 sa_flags 中 包 含 了 许 多 标 志 位, 是 对 信 号 进 行 处 理 的 各 个 选 择 项 它 的 常 见 可 选 值 如 表 8.13 所 示 表 8.13 常 见 信 号 的 含 义 及 其 默 认 操 作 选 项 含 义 SA_NODEFER \ SA_NOMASK SA_NOCLDSTOP SA_RESTART SA_ONESHOT \ SA_RESETHAND 当 捕 捉 到 此 信 号 时, 在 执 行 其 信 号 捕 捉 函 数 时, 系 统 不 会 自 动 屏 蔽 此 信 号 进 程 忽 略 子 进 程 产 生 的 任 何 SIGSTOP SIGTSTP SIGTTIN 和 SIGTTOU 信 号 令 重 启 的 系 统 调 用 起 作 用 自 定 义 信 号 只 执 行 一 次, 在 执 行 完 毕 后 恢 复 信 号 的 系 统 默 认 动 作 (3) 使 用 实 例 第 一 个 实 例 表 明 了 如 何 使 用 signal() 函 数 捕 捉 相 应 信 号, 并 做 出 给 定 的 处 理 这 里,my_func 就 是 信 号 处 理 的 函 数 指 针 读 者 还 可 以 将 其 改 为 SIG_IGN 或 SIG_DFL 查 看 运 行 结 果 第 二 个 实 例 是 用 sigaction() 函 数 实 现 同 样 的 功 能 以 下 是 使 用 signal() 函 数 的 示 例 : /* signal.c */ 16
243 #include <signal.h> #include <stdio.h> #include <stdlib.h> 专 业 始 于 专 注 卓 识 源 于 远 见 /* 自 定 义 信 号 处 理 函 数 */ void my_func(int sign_no) if (sign_no == SIGINT) printf("i have get SIGINT\n"); else if (sign_no == SIGQUIT) printf("i have get SIGQUIT\n"); int main() printf("waiting for signal SIGINT or SIGQUIT...\n"); /* 发 出 相 应 的 信 号, 并 跳 转 到 信 号 处 理 函 数 处 */ signal(sigint, my_func); signal(sigquit, my_func); pause(); exit(0); 运 行 结 果 如 下 所 示 $./signal Waiting for signal SIGINT or SIGQUIT... I have get SIGINT ( 按 ctrl-c 组 合 键 ) $./signal Waiting for signal SIGINT or SIGQUIT... I have get SIGQUIT ( 按 ctrl-\ 组 合 键 ) 以 下 是 用 sigaction() 函 数 实 现 同 样 的 功 能, 下 面 只 列 出 更 新 的 main() 函 数 部 分 /* sigaction.c */ /* 前 部 分 省 略 */ int main() struct sigaction action; printf("waiting for signal SIGINT or SIGQUIT...\n"); /* sigaction 结 构 初 始 化 */ action.sa_handler = my_func; sigemptyset(&action.sa_mask); action.sa_flags = 0; 17
244 /* 发 出 相 应 的 信 号, 并 跳 转 到 信 号 处 理 函 数 处 */ sigaction(sigint, &action, 0); sigaction(sigquit, &action, 0); pause(); exit(0); 2. 信 号 集 函 数 组 (1) 函 数 说 明 使 用 信 号 集 函 数 组 处 理 信 号 时 涉 及 一 系 列 的 函 数, 这 些 函 数 按 照 调 用 的 先 后 次 序 可 分 为 以 下 几 大 功 能 模 块 : 创 建 信 号 集 合 注 册 信 号 处 理 函 数 以 及 检 测 信 号 其 中, 创 建 信 号 集 合 主 要 用 于 处 理 用 户 感 兴 趣 的 一 些 信 号, 其 函 数 包 括 以 下 几 个 sigemptyset(): 将 信 号 集 合 初 始 化 为 空 sigfillset(): 将 信 号 集 合 初 始 化 为 包 含 所 有 已 定 义 的 信 号 的 集 合 sigaddset(): 将 指 定 信 号 加 入 到 信 号 集 合 中 去 sigdelset(): 将 指 定 信 号 从 信 号 集 合 中 删 除 sigismember(): 查 询 指 定 信 号 是 否 在 信 号 集 合 之 中 注 册 信 号 处 理 函 数 主 要 用 于 决 定 进 程 如 何 处 理 信 号 这 里 要 注 意 的 是, 信 号 集 里 的 信 号 并 不 是 真 正 可 以 处 理 的 信 号, 只 有 当 信 号 的 状 态 处 于 非 阻 塞 状 态 时 才 会 真 正 起 作 用 因 此, 首 先 使 用 sigprocmask() 函 数 检 测 并 更 改 信 号 屏 蔽 字 ( 信 号 屏 蔽 字 是 用 来 指 定 当 前 被 阻 塞 的 一 组 信 号, 它 们 不 会 被 进 程 接 收 ), 然 后 使 用 sigaction() 函 数 来 定 义 进 程 接 收 到 特 定 信 号 之 后 的 行 为 检 测 信 号 是 信 号 处 理 的 后 续 步 骤, 因 为 被 阻 塞 的 信 号 不 会 传 递 给 进 程, 所 以 这 些 信 号 就 处 于 未 处 理 状 态 ( 也 就 是 进 程 不 清 楚 它 的 存 在 ) sigpending() 函 数 允 许 进 程 检 测 未 处 理 信 号, 并 进 一 步 决 定 对 它 们 作 何 处 理 (2) 函 数 格 式 首 先 介 绍 创 建 信 号 集 合 的 函 数 格 式, 表 8.14 列 举 了 这 一 组 函 数 的 语 法 要 点 表 8.14 所 需 头 文 件 #include <signal.h> 创 建 信 号 集 合 函 数 语 法 要 点 int sigemptyset(sigset_t *set) int sigfillset(sigset_t *set) 函 数 原 型 int sigaddset(sigset_t *set, int signum) int sigdelset(sigset_t *set, int signum) int sigismember(sigset_t *set, int signum) 函 数 传 入 值 函 数 返 回 值 set: 信 号 集 signum: 指 定 信 号 代 码 成 功 :0(sigismember 成 功 返 回 1, 失 败 返 回 0) 出 错 : 1 表 8.15 列 举 了 sigprocmask 的 语 法 要 点 表 8.15 所 需 头 文 件 函 数 原 型 #include <signal.h> sigprocmask 函 数 语 法 要 点 int sigprocmask(int how, const sigset_t *set, sigset_t *oset) 函 数 传 入 值 how: 决 定 函 SIG_BLOCK: 增 加 一 个 信 号 集 合 到 当 前 进 程 的 阻 塞 集 合 之 中 18
245 数 的 操 作 方 式 SIG_UNBLOCK: 从 当 前 的 阻 塞 集 合 之 中 删 除 一 个 信 号 集 合 SIG_SETMASK: 将 当 前 的 信 号 集 合 设 置 为 信 号 阻 塞 集 合 set: 指 定 信 号 集 oset: 信 号 屏 蔽 字 函 数 返 回 值 成 功 :0 出 错 : 1 此 处, 若 set 是 一 个 非 空 指 针, 则 参 数 how 表 示 函 数 的 操 作 方 式 ; 若 how 为 空, 则 表 示 忽 略 此 操 作 最 后, 表 8.16 列 举 了 sigpending 函 数 的 语 法 要 点 表 8.16 所 需 头 文 件 函 数 原 型 函 数 传 入 值 sigpending 函 数 语 法 要 点 #include <signal.h> int sigpending(sigset_t *set) set: 要 检 测 的 信 号 集 函 数 返 回 值 成 功 :0 出 错 : 1 总 之, 在 处 理 信 号 时, 一 般 遵 循 如 图 8.7 所 示 的 操 作 流 程 图 8.7 一 般 的 信 号 操 作 处 理 流 程 (3) 使 用 实 例 该 实 例 首 先 把 SIGQUIT SIGINT 两 个 信 号 加 入 信 号 集, 然 后 将 该 信 号 集 合 设 为 阻 塞 状 态, 并 进 入 用 户 输 入 状 态 用 户 只 需 按 任 意 键, 就 可 以 立 刻 将 信 号 集 合 设 置 为 非 阻 塞 状 态, 再 对 这 两 个 信 号 分 别 操 作, 其 中 SIGQUIT 执 行 默 认 操 作, 而 SIGINT 执 行 用 户 自 定 义 函 数 的 操 作 源 代 码 如 下 所 示 : /* sigset.c */ #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> /* 自 定 义 的 信 号 处 理 函 数 */ void my_func(int signum) printf("if you want to quit,please try SIGQUIT\n"); int main() sigset_t set,pendset; struct sigaction action1,action2; /* 初 始 化 信 号 集 为 空 */ if (sigemptyset(&set) < 0) 19
246 perror("sigemptyset"); exit(1); 专 业 始 于 专 注 卓 识 源 于 远 见 /* 将 相 应 的 信 号 加 入 信 号 集 */ if (sigaddset(&set, SIGQUIT) < 0) perror("sigaddset"); exit(1); if (sigaddset(&set, SIGINT) < 0) perror("sigaddset"); exit(1); if (sigismember(&set, SIGINT)) sigemptyset(&action1.sa_mask); action1.sa_handler = my_func; action1.sa_flags = 0; sigaction(sigint, &action1, NULL); if (sigismember(&set, SIGQUIT)) sigemptyset(&action2.sa_mask); action2.sa_handler = SIG_DFL; action2.sa_flags = 0; sigaction(sigquit, &action2,null); /* 设 置 信 号 集 屏 蔽 字, 此 时 set 中 的 信 号 不 会 被 传 递 给 进 程, 暂 时 进 入 待 处 理 状 态 */ if (sigprocmask(sig_block, &set, NULL) < 0) perror("sigprocmask"); exit(1); else printf("signal set was blocked, Press any key!"); getchar(); /* 在 信 号 屏 蔽 字 中 删 除 set 中 的 信 号 */ if (sigprocmask(sig_unblock, &set, NULL) < 0) 20
247 perror("sigprocmask"); exit(1); else printf("signal set is in unblock state\n"); while(1); exit(0); 该 程 序 的 运 行 结 果 如 下 所 示, 可 以 看 见, 在 信 号 处 于 阻 塞 状 态 时, 所 发 出 的 信 号 对 进 程 不 起 作 用, 并 且 该 信 号 进 入 待 处 理 状 态 读 者 输 入 任 意 键, 并 且 信 号 脱 离 了 阻 塞 状 态 之 后, 用 户 发 出 的 信 号 才 能 正 常 运 行 这 里 SIGINT 已 按 照 用 户 自 定 义 的 函 数 运 行, 请 读 者 注 意 阻 塞 状 态 下 SIGINT 的 处 理 和 非 阻 塞 状 态 下 SIGINT 的 处 理 有 何 不 同 $./sigset Signal set was blocked, Press any key! /* 此 时 按 任 何 键 可 以 解 除 阻 塞 屏 蔽 字 */ If you want to quit,please try SIGQUIT /* 阻 塞 状 态 下 SIGINT 的 处 理 */ Signal set is in unblock state /* 从 信 号 屏 蔽 字 中 删 除 set 中 的 信 号 */ If you want to quit,please try SIGQUIT /* 非 阻 塞 状 态 下 SIGINT 的 处 理 */ If you want to quit,please try SIGQUIT Quit /* 非 阻 塞 状 态 下 SIGQUIT 处 理 */ 8.4 信 号 量 信 号 量 概 述 在 多 任 务 操 作 系 统 环 境 下, 多 个 进 程 会 同 时 运 行, 并 且 一 些 进 程 之 间 可 能 存 在 一 定 的 关 联 多 个 进 程 可 能 为 了 完 成 同 一 个 任 务 会 相 互 协 作, 这 样 形 成 进 程 之 间 的 同 步 关 系 而 且 在 不 同 进 程 之 间, 为 了 争 夺 有 限 的 系 统 资 源 ( 硬 件 或 软 件 资 源 ) 会 进 入 竞 争 状 态, 这 就 是 进 程 之 间 的 互 斥 关 系 进 程 之 间 的 互 斥 与 同 步 关 系 存 在 的 根 源 在 于 临 界 资 源 临 界 资 源 是 在 同 一 个 时 刻 只 允 许 有 限 个 ( 通 常 只 有 一 个 ) 进 程 可 以 访 问 ( 读 ) 或 修 改 ( 写 ) 的 资 源, 通 常 包 括 硬 件 资 源 ( 处 理 器 内 存 存 储 器 以 及 其 他 外 围 设 备 等 ) 和 软 件 资 源 ( 共 享 代 码 段, 共 享 结 构 和 变 量 等 ) 访 问 临 界 资 源 的 代 码 叫 做 临 界 区, 临 界 区 本 身 也 会 成 为 临 界 资 源 信 号 量 是 用 来 解 决 进 程 之 间 的 同 步 与 互 斥 问 题 的 一 种 进 程 之 间 通 信 机 制, 包 括 一 个 称 为 信 号 量 的 变 量 和 在 该 信 号 量 下 等 待 资 源 的 进 程 等 待 队 列, 以 及 对 信 号 量 进 行 的 两 个 原 子 操 作 (PV 操 作 ) 其 中 信 号 量 对 应 于 某 一 种 资 源, 取 一 个 非 负 的 整 型 值 信 号 量 值 指 的 是 当 前 可 用 的 该 资 源 的 数 量, 若 它 等 于 0 则 意 味 着 目 前 没 有 可 用 的 资 源 PV 原 子 操 作 的 具 体 定 义 如 下 : P 操 作 : 如 果 有 可 用 的 资 源 ( 信 号 量 值 >0), 则 占 用 一 个 资 源 ( 给 信 号 量 值 减 去 一, 进 入 临 界 区 代 码 ); 如 果 没 有 可 用 的 资 源 ( 信 号 量 值 等 于 0), 则 被 阻 塞 到, 直 到 系 统 将 资 源 分 配 给 该 进 程 ( 进 入 等 待 队 列, 一 直 等 到 资 源 轮 到 该 进 程 ) V 操 作 : 如 果 在 该 信 号 量 的 等 待 队 列 中 有 进 程 在 等 待 资 源, 则 唤 醒 一 个 阻 塞 进 程 如 果 没 有 进 程 等 待 它, 则 释 放 一 个 资 源 ( 给 信 号 量 值 加 一 ) 21
248 使 用 信 号 量 访 问 临 界 区 的 伪 代 码 所 下 所 示 : /* 设 R 为 某 种 资 源,S 为 资 源 R 的 信 号 量 */ INIT_VAL(S); /* 对 信 号 量 S 进 行 初 始 化 */ 非 临 界 区 ; P(S); /* 进 行 P 操 作 */ 临 界 区 ( 使 用 资 源 R); /* 只 有 有 限 个 ( 通 常 只 有 一 个 ) 进 程 被 允 许 进 入 该 区 */ V(S); /* 进 行 V 操 作 */ 非 临 界 区 ; 最 简 单 的 信 号 量 是 只 能 取 0 和 1 两 种 值, 这 种 信 号 量 被 叫 做 二 维 信 号 量 在 本 节 中, 主 要 讨 论 二 维 信 号 量 二 维 信 号 量 的 应 用 比 较 容 易 地 扩 展 到 使 用 多 维 信 号 量 的 情 况 信 号 量 的 应 用 1. 函 数 说 明 在 Linux 系 统 中, 使 用 信 号 量 通 常 分 为 以 下 几 个 步 骤 (1) 创 建 信 号 量 或 获 得 在 系 统 已 存 在 的 信 号 量, 此 时 需 要 调 用 semget() 函 数 不 同 进 程 通 过 使 用 同 一 个 信 号 量 键 值 来 获 得 同 一 个 信 号 量 (2) 初 始 化 信 号 量, 此 时 使 用 semctl() 函 数 的 SETVAL 操 作 当 使 用 二 维 信 号 量 时, 通 常 将 信 号 量 初 始 化 为 1 (3) 进 行 信 号 量 的 PV 操 作, 此 时 调 用 semop() 函 数 这 一 步 是 实 现 进 程 之 间 的 同 步 和 互 斥 的 核 心 工 作 部 分 (4) 如 果 不 需 要 信 号 量, 则 从 系 统 中 删 除 它, 此 时 使 用 semclt() 函 数 的 IPC_RMID 操 作 此 时 需 要 注 意, 在 程 序 中 不 应 该 出 现 对 已 经 被 删 除 的 信 号 量 的 操 作 2. 函 数 格 式 表 8.17 列 举 了 semget() 函 数 的 语 法 要 点 表 8.17 所 需 头 文 件 函 数 原 型 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> semget() 函 数 语 法 要 点 int semget(key_t key, int nsems, int semflg) key: 信 号 量 的 键 值, 多 个 进 程 可 以 通 过 它 访 问 同 一 个 信 号 量, 其 中 有 个 特 殊 值 IPC_PRIVATE 它 用 于 创 建 当 前 进 程 的 私 有 信 号 量 nsems: 需 要 创 建 的 信 号 量 数 目, 通 常 取 值 为 1 函 数 传 入 值 函 数 返 回 值 semflg: 同 open() 函 数 的 权 限 位, 也 可 以 用 八 进 制 表 示 法, 其 中 使 用 IPC_CREAT 标 志 创 建 新 的 信 号 量, 即 使 该 信 号 量 已 经 存 在 ( 具 有 同 一 个 键 值 的 信 号 量 已 在 系 统 中 存 在 ), 也 不 会 出 错 如 果 同 时 使 用 IPC_EXCL 标 志 可 以 创 建 一 个 新 的 唯 一 的 信 号 量, 此 时 如 果 该 信 号 量 已 经 存 在, 该 函 数 会 返 回 出 错 成 功 : 信 号 量 标 识 符, 在 信 号 量 的 其 他 函 数 中 都 会 使 用 该 值 22
249 出 错 :-1 表 8.18 列 举 了 semctl() 函 数 的 语 法 要 点 表 8.18 所 需 头 文 件 函 数 原 型 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> semctl() 函 数 语 法 要 点 int semctl(int semid, int semnum, int cmd, union semun arg) semid:semget() 函 数 返 回 的 信 号 量 标 识 符 semnum: 信 号 量 编 号, 当 使 用 信 号 量 集 时 才 会 被 用 到 通 常 取 值 为 0, 就 是 使 用 单 个 信 号 量 ( 也 是 第 一 个 信 号 量 ) 函 数 传 入 值 cmd: 指 定 对 信 号 量 的 各 种 操 作, 当 使 用 单 个 信 号 量 ( 而 不 是 信 号 量 集 ) 时, 常 用 的 有 以 下 几 种 : IPC_STAT: 获 得 该 信 号 量 ( 或 者 信 号 量 集 合 ) 的 semid_ds 结 构, 并 存 放 在 由 第 4 个 参 数 arg 的 buf 指 向 的 semid_ds 结 构 中 semid_ds 是 在 系 统 中 描 述 信 号 量 的 数 据 结 构 IPC_SETVAL: 将 信 号 量 值 设 置 为 arg 的 val 值 IPC_GETVAL: 返 回 信 号 量 的 当 前 值 IPC_RMID: 从 系 统 中, 删 除 信 号 量 ( 或 者 信 号 量 集 ) arg: 是 union semnn 结 构, 该 结 构 可 能 在 某 些 系 统 中 并 不 给 出 定 义, 此 时 必 须 由 程 序 员 自 己 定 义 union semun int val; struct semid_ds *buf; unsigned short *array; 函 数 返 回 值 成 功 : 根 据 cmd 值 的 不 同 而 返 回 不 同 的 值 IPC_STAT IPC_SETVAL IPC_RMID: 返 回 0 IPC_GETVAL: 返 回 信 号 量 的 当 前 值 出 错 :-1 表 8.19 列 举 了 semop() 函 数 的 语 法 要 点 表 8.19 所 需 头 文 件 函 数 原 型 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> semop() 函 数 语 法 要 点 int semop(int semid, struct sembuf *sops, size_t nsops) semid:semget() 函 数 返 回 的 信 号 量 标 识 符 函 数 传 入 值 sops: 指 向 信 号 量 操 作 数 组, 一 个 数 组 包 括 以 下 成 员 : struct sembuf short sem_num; /* 信 号 量 编 号, 使 用 单 个 信 号 量 时, 通 常 取 值 为 0 */ short sem_op; /* 信 号 量 操 作 : 取 值 为 -1 则 表 示 P 操 作, 取 值 为 +1 则 表 示 V 操 作 */ short sem_flg; /* 通 常 设 置 为 SEM_UNDO 这 样 在 进 程 没 释 放 信 号 量 而 退 出 时, 系 统 自 动 释 放 该 进 程 中 未 释 放 的 信 号 量 */ nsops: 操 作 数 组 sops 中 的 操 作 个 数 ( 元 素 数 目 ), 通 常 取 值 为 1( 一 个 操 作 ) 函 数 返 回 值 成 功 : 信 号 量 标 识 符, 在 信 号 量 的 其 他 函 数 中 都 会 使 用 该 值 23
250 出 错 :-1 3. 使 用 实 例 本 实 例 说 明 信 号 量 的 概 念 以 及 基 本 用 法 在 实 例 程 序 中, 首 先 创 建 一 个 子 进 程, 接 下 来 使 用 信 号 量 来 控 制 两 个 进 程 ( 父 子 进 程 ) 之 间 的 执 行 顺 序 因 为 信 号 量 相 关 的 函 数 调 用 接 口 比 较 复 杂, 我 们 可 以 将 它 们 封 装 成 二 维 单 个 信 号 量 的 几 个 基 本 函 数 它 们 分 别 为 信 号 量 初 始 化 函 数 ( 或 者 信 号 量 赋 值 函 数 )init_sem() P 操 作 函 数 sem_p() V 操 作 函 数 sem_v() 以 及 删 除 信 号 量 的 函 数 del_sem() 等, 具 体 实 现 如 下 所 示 : /* sem_com.c */ #include "sem_com.h" /* 信 号 量 初 始 化 ( 赋 值 ) 函 数 */ int init_sem(int sem_id, int init_value) union semun sem_union; sem_union.val = init_value; /* init_value 为 初 始 值 */ if (semctl(sem_id, 0, SETVAL, sem_union) == -1) perror("initialize semaphore"); return -1; return 0; /* 从 系 统 中 删 除 信 号 量 的 函 数 */ int del_sem(int sem_id) union semun sem_union; if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1) perror("delete semaphore"); return -1; /* P 操 作 函 数 */ int sem_p(int sem_id) struct sembuf sem_b; sem_b.sem_num = 0; /* 单 个 信 号 量 的 编 号 应 该 为 0 */ sem_b.sem_op = -1; /* 表 示 P 操 作 */ sem_b.sem_flg = SEM_UNDO; /* 系 统 自 动 释 放 将 会 在 系 统 中 残 留 的 信 号 量 */ if (semop(sem_id, &sem_b, 1) == -1) perror("p operation"); return -1; return 0; 24
251 /* V 操 作 函 数 */ int sem_v(int sem_id) struct sembuf sem_b; sem_b.sem_num = 0; /* 单 个 信 号 量 的 编 号 应 该 为 0 */ sem_b.sem_op = 1; /* 表 示 V 操 作 */ sem_b.sem_flg = SEM_UNDO; /* 系 统 自 动 释 放 将 会 在 系 统 中 残 留 的 信 号 量 */ if (semop(sem_id, &sem_b, 1) == -1) perror("v operation"); return -1; return 0; 现 在 我 们 调 用 这 些 简 单 易 用 的 接 口, 可 以 轻 松 解 决 控 制 两 个 进 程 之 间 的 执 行 顺 序 的 同 步 问 题 实 现 代 码 如 下 所 示 : /* fork.c */ #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define DELAY_TIME 3 /* 为 了 突 出 演 示 效 果, 等 待 几 秒 钟,*/ int main(void) pid_t result; int sem_id; sem_id = semget(ftok(".", 'a'), 1, 0666 IPC_CREAT); /* 创 建 一 个 信 号 量 */ init_sem(sem_id, 0); /* 调 用 fork() 函 数 */ result = fork(); if(result == -1) perror("fork\n"); else if (result == 0) /* 返 回 值 为 0 代 表 子 进 程 */ printf("child process will wait for some seconds...\n"); sleep(delay_time); printf("the returned value is %d in the child process(pid = %d)\n", result, getpid()); 25
252 sem_v(sem_id); else /* 返 回 值 大 于 0 代 表 父 进 程 */ sem_p(sem_id); printf("the returned value is %d in the father process(pid = %d)\n", result, getpid()); sem_v(sem_id); del_sem(sem_id); exit(0); 读 者 可 以 先 从 该 程 序 中 删 除 掉 信 号 量 相 关 的 代 码 部 分 并 观 察 运 行 结 果 $./simple_fork Child process will wait for some seconds /* 子 进 程 在 运 行 中 */ The returned value is 4185 in the father process(pid = 4184)/* 父 进 程 先 结 束 */ [ ]$ The returned value is 0 in the child process(pid = 4185) /* 子 进 程 后 结 束 了 */ 再 添 加 信 号 量 的 控 制 部 分 并 运 行 结 果 $./sem_fork Child process will wait for some seconds /* 子 进 程 在 运 行 中, 父 进 程 在 等 待 子 进 程 结 束 */ The returned value is 0 in the child process(pid = 4185) /* 子 进 程 结 束 了 */ The returned value is 4185 in the father process(pid = 4184) /* 父 进 程 结 束 */ 本 实 例 说 明 使 用 信 号 量 怎 么 解 决 多 进 程 之 间 存 在 的 同 步 问 题 我 们 将 在 后 面 讲 述 的 共 享 内 存 和 消 息 队 列 的 实 例 中, 看 到 使 用 信 号 量 实 现 多 进 程 之 间 的 互 斥 8.5 共 享 内 存 共 享 内 存 概 述 可 以 说, 共 享 内 存 是 一 种 最 为 高 效 的 进 程 间 通 信 方 式 因 为 进 程 可 以 直 接 读 写 内 存, 不 需 要 任 何 数 据 的 复 制 为 了 在 多 个 进 程 间 交 换 信 息, 内 核 专 门 留 出 了 一 块 内 存 区 这 段 内 存 区 可 以 由 需 要 访 问 的 进 程 将 其 映 射 到 自 己 的 私 有 地 址 空 间 因 此, 进 程 就 可 以 直 接 读 写 这 一 内 存 区 而 不 需 要 进 行 数 据 的 复 制, 从 而 大 大 提 高 了 效 率 当 然, 由 于 多 个 进 程 共 享 一 段 内 存, 因 此 也 需 要 依 靠 某 种 同 步 机 制, 如 互 斥 锁 和 信 号 量 等 ( 请 参 考 本 章 的 共 享 内 存 实 验 ) 其 原 理 示 意 图 如 图 8.8 所 示 图 8.8 共 享 内 存 原 理 示 意 图 26
253 8.5.2 共 享 内 存 的 应 用 1. 函 数 说 明 共 享 内 存 的 实 现 分 为 两 个 步 骤, 第 一 步 是 创 建 共 享 内 存, 这 里 用 到 的 函 数 是 shmget(), 也 就 是 从 内 存 中 获 得 一 段 共 享 内 存 区 域, 第 二 步 映 射 共 享 内 存, 也 就 是 把 这 段 创 建 的 共 享 内 存 映 射 到 具 体 的 进 程 空 间 中, 这 里 使 用 的 函 数 是 shmat() 到 这 里, 就 可 以 使 用 这 段 共 享 内 存 了, 也 就 是 可 以 使 用 不 带 缓 冲 的 I/O 读 写 命 令 对 其 进 行 操 作 除 此 之 外, 当 然 还 有 撤 销 映 射 的 操 作, 其 函 数 为 shmdt() 这 里 就 主 要 介 绍 这 3 个 函 数 2. 函 数 格 式 表 8.20 列 举 了 shmget() 函 数 的 语 法 要 点 表 8.20 所 需 头 文 件 函 数 原 型 函 数 传 入 值 #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> shmget() 函 数 语 法 要 点 int shmget(key_t key, int size, int shmflg) key: 共 享 内 存 的 键 值, 多 个 进 程 可 以 通 过 它 访 问 同 一 个 共 享 内 存, 其 中 有 个 特 殊 值 IPC_PRIVATE 它 用 于 创 建 当 前 进 程 的 私 有 共 享 内 存 size: 共 享 内 存 区 大 小 shmflg: 同 open() 函 数 的 权 限 位, 也 可 以 用 八 进 制 表 示 法 函 数 返 回 值 成 功 : 共 享 内 存 段 标 识 符 出 错 : 1 表 8.21 列 举 了 shmat() 函 数 的 语 法 要 点 表 8.21 所 需 头 文 件 函 数 原 型 #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> shmat() 函 数 语 法 要 点 char *shmat(int shmid, const void *shmaddr, int shmflg) shmid: 要 映 射 的 共 享 内 存 区 标 识 符 函 数 传 入 值 函 数 返 回 值 shmaddr: 将 共 享 内 存 映 射 到 指 定 地 址 ( 若 为 0 则 表 示 系 统 自 动 分 配 地 址 并 把 该 段 共 享 内 存 映 射 到 调 用 进 程 的 地 址 空 间 ) shmflg 成 功 : 被 映 射 的 段 地 址 出 错 : 1 SHM_RDONLY: 共 享 内 存 只 读 默 认 0: 共 享 内 存 可 读 写 表 8.22 列 举 了 shmdt() 函 数 的 语 法 要 点 表 8.22 所 需 头 文 件 函 数 原 型 函 数 传 入 值 shmdt() 函 数 语 法 要 点 #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmdt(const void *shmaddr) shmaddr: 被 映 射 的 共 享 内 存 段 地 址 函 数 返 回 值 成 功 :0 27
254 出 错 : 1 3. 使 用 实 例 该 实 例 说 明 如 何 使 用 基 本 的 共 享 内 存 函 数 首 先 是 创 建 一 个 共 享 内 存 区 ( 采 用 的 共 享 内 存 的 键 值 为 IPC_PRIVATE, 是 因 为 本 实 例 中 创 建 的 共 享 内 存 是 父 子 进 程 之 间 的 共 用 部 分 ), 之 后 创 建 子 进 程, 在 父 子 两 个 进 程 中 将 共 享 内 存 分 别 映 射 到 各 自 的 进 程 地 址 空 间 之 中 父 进 程 先 等 待 用 户 输 入, 然 后 将 用 户 输 入 的 字 符 串 写 入 到 共 享 内 存, 之 后 往 共 享 内 存 的 头 部 写 入 WROTE 字 符 串 表 示 父 进 程 已 成 功 写 入 数 据 子 进 程 一 直 等 到 共 享 内 存 的 头 部 字 符 串 为 WROTE, 然 后 将 共 享 内 存 的 有 效 数 据 ( 在 父 进 程 中 用 户 输 入 的 字 符 串 ) 在 屏 幕 上 打 印 父 子 两 个 进 程 在 完 成 以 上 工 作 之 后, 分 别 解 除 与 共 享 内 存 的 映 射 关 系 最 后 在 子 进 程 中 删 除 共 享 内 存 因 为 共 享 内 存 自 身 并 不 提 供 同 步 机 制, 所 以 应 该 额 外 实 现 不 同 进 程 之 间 的 同 步 ( 例 如 : 信 号 量 ) 为 了 简 单 起 见, 在 本 实 例 中 用 标 志 字 符 串 来 实 现 非 常 简 单 的 父 子 进 程 之 间 的 同 步 这 里 要 介 绍 的 一 个 命 令 是 ipcs, 这 是 用 于 报 告 进 程 间 通 信 机 制 状 态 的 命 令 它 可 以 查 看 共 享 内 存 消 息 队 列 等 各 种 进 程 间 通 信 机 制 的 情 况, 这 里 使 用 了 system() 函 数 用 于 调 用 shell 命 令 ipcs 程 序 源 代 码 如 下 所 示 : /* shmem.c */ #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define BUFFER_SIZE 2048 int main() pid_t pid; int shmid; char *shm_addr; char flag[] = "WROTE"; char *buff; /* 创 建 共 享 内 存 */ if ((shmid = shmget(ipc_private, BUFFER_SIZE, 0666)) < 0) perror("shmget"); exit(1); else printf("create shared-memory: %d\n",shmid); /* 显 示 共 享 内 存 情 况 */ 28
255 system("ipcs -m"); 专 业 始 于 专 注 卓 识 源 于 远 见 pid = fork(); if (pid == -1) perror("fork"); exit(1); else if (pid == 0) /* 子 进 程 处 理 */ /* 映 射 共 享 内 存 */ if ((shm_addr = shmat(shmid, 0, 0)) == (void*)-1) perror("child: shmat"); exit(1); else printf("child: Attach shared-memory: %p\n", shm_addr); system("ipcs -m"); /* 通 过 检 查 在 共 享 内 存 的 头 部 是 否 标 志 字 符 串 "WROTE" 来 确 认 父 进 程 已 经 向 共 享 内 存 写 入 有 效 数 据 */ while (strncmp(shm_addr, flag, strlen(flag))) printf("child: Wait for enable data...\n"); sleep(5); /* 获 取 共 享 内 存 的 有 效 数 据 并 显 示 */ strcpy(buff, shm_addr + strlen(flag)); printf("child: Shared-memory :%s\n", buff); /* 解 除 共 享 内 存 映 射 */ if ((shmdt(shm_addr)) < 0) perror("shmdt"); exit(1); else printf("child: Deattach shared-memory\n"); system("ipcs -m"); /* 删 除 共 享 内 存 */ 29
256 if (shmctl(shmid, IPC_RMID, NULL) == -1) perror("child: shmctl(ipc_rmid)\n"); exit(1); else printf("delete shared-memory\n"); 专 业 始 于 专 注 卓 识 源 于 远 见 system("ipcs -m"); else /* 父 进 程 处 理 */ /* 映 射 共 享 内 存 */ if ((shm_addr = shmat(shmid, 0, 0)) == (void*)-1) perror("parent: shmat"); exit(1); else printf("parent: Attach shared-memory: %p\n", shm_addr); sleep(1); printf("\ninput some string:\n"); fgets(buff, BUFFER_SIZE, stdin); strncpy(shm_addr + strlen(flag), buff, strlen(buff)); strncpy(shm_addr, flag, strlen(flag)); /* 解 除 共 享 内 存 映 射 */ if ((shmdt(shm_addr)) < 0) perror("parent: shmdt"); exit(1); else printf("parent: Deattach shared-memory\n"); system("ipcs -m"); waitpid(pid, NULL, 0); printf("finished\n"); 30
257 exit(0); 下 面 是 运 行 结 果 从 该 结 果 可 以 看 出,nattch 的 值 随 着 共 享 内 存 状 态 的 变 化 而 变 化, 共 享 内 存 的 值 根 据 不 同 的 系 统 会 有 所 不 同 $./shmem Create shared-memory: /* 在 刚 创 建 共 享 内 存 时 ( 尚 未 有 任 何 地 址 映 射 ) 共 享 内 存 的 情 况 */ Shared Memory Segments key shmid owner perms bytes nattch status 0x david Child: Attach shared-memory: 0xb7f59000 /* 共 享 内 存 的 映 射 地 址 */ Parent: Attach shared-memory: 0xb7f59000 /* 在 父 子 进 程 中 进 行 共 享 内 存 的 地 址 映 射 之 后 共 享 内 存 的 情 况 */ Shared Memory Segments key shmid owner perms bytes nattch status 0x david Child: Wait for enable data... Input some string: Hello /* 用 户 输 入 字 符 串 Hello */ Parent: Deattach shared-memory /* 在 父 进 程 中 解 除 共 享 内 存 的 映 射 关 系 之 后 共 享 内 存 的 情 况 */ Shared Memory Segments key shmid owner perms bytes nattch status 0x david /* 在 子 进 程 中 读 取 共 享 内 存 的 有 效 数 据 并 打 印 */ Child: Shared-memory :hello Child: Deattach shared-memory /* 在 子 进 程 中 解 除 共 享 内 存 的 映 射 关 系 之 后 共 享 内 存 的 情 况 */ Shared Memory Segments key shmid owner perms bytes nattch status 0x david Delete shared-memory /* 在 删 除 共 享 内 存 之 后 共 享 内 存 的 情 况 */ Shared Memory Segments key shmid owner perms bytes nattch status Finished 31
258 8.6 消 息 队 列 消 息 队 列 概 述 顾 名 思 义, 消 息 队 列 就 是 一 些 消 息 的 列 表 用 户 可 以 从 消 息 队 列 中 添 加 消 息 和 读 取 消 息 等 从 这 点 上 看, 消 息 队 列 具 有 一 定 的 FIFO 特 性, 但 是 它 可 以 实 现 消 息 的 随 机 查 询, 比 FIFO 具 有 更 大 的 优 势 同 时, 这 些 消 息 又 是 存 在 于 内 核 中 的, 由 队 列 ID 来 标 识 消 息 队 列 的 应 用 1. 函 数 说 明 消 息 队 列 的 实 现 包 括 创 建 或 打 开 消 息 队 列 添 加 消 息 读 取 消 息 和 控 制 消 息 队 列 这 4 种 操 作 其 中 创 建 或 打 开 消 息 队 列 使 用 的 函 数 是 msgget(), 这 里 创 建 的 消 息 队 列 的 数 量 会 受 到 系 统 消 息 队 列 数 量 的 限 制 ; 添 加 消 息 使 用 的 函 数 是 msgsnd() 函 数, 它 把 消 息 添 加 到 已 打 开 的 消 息 队 列 末 尾 ; 读 取 消 息 使 用 的 函 数 是 msgrcv(), 它 把 消 息 从 消 息 队 列 中 取 走, 与 FIFO 不 同 的 是, 这 里 可 以 指 定 取 走 某 一 条 消 息 ; 最 后 控 制 消 息 队 列 使 用 的 函 数 是 msgctl(), 它 可 以 完 成 多 项 功 能 2. 函 数 格 式 表 8.23 列 举 了 msgget() 函 数 的 语 法 要 点 表 8.23 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> msgget() 函 数 语 法 要 点 int msgget(key_t key, int msgflg) key: 消 息 队 列 的 键 值, 多 个 进 程 可 以 通 过 它 访 问 同 一 个 消 息 队 列, 其 中 有 个 特 殊 值 IPC_PRIVATE 它 用 于 创 建 当 前 进 程 的 私 有 消 息 队 列 msgflg: 权 限 标 志 位 成 功 : 消 息 队 列 ID 出 错 : 1 表 8.24 列 举 了 msgsnd() 函 数 的 语 法 要 点 表 8.24 所 需 头 文 件 函 数 原 型 #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> msgsnd() 函 数 语 法 要 点 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg) msqid: 消 息 队 列 的 队 列 ID 函 数 传 入 值 msgp: 指 向 消 息 结 构 的 指 针 该 消 息 结 构 msgbuf 通 常 为 : struct msgbuf long mtype; /* 消 息 类 型, 该 结 构 必 须 从 这 个 域 开 始 */ char mtext[1]; /* 消 息 正 文 */ msgsz: 消 息 正 文 的 字 节 数 ( 不 包 括 消 息 类 型 指 针 变 量 ) 32
259 msgflg: IPC_NOWAIT 若 消 息 无 法 立 即 发 送 ( 比 如 : 当 前 消 息 队 列 已 满 ), 函 数 会 立 即 返 回 0:msgsnd 调 阻 塞 直 到 发 送 成 功 为 止 函 数 返 回 值 成 功 :0 出 错 : 1 表 8.25 列 举 了 msgrcv() 函 数 的 语 法 要 点 表 8.25 所 需 头 文 件 函 数 原 型 #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> msgrcv() 函 数 语 法 要 点 int msgrcv(int msgid, void *msgp, size_t msgsz, long int msgtyp, int msgflg) msqid: 消 息 队 列 的 队 列 ID msgp: 消 息 缓 冲 区, 同 于 msgsnd() 函 数 的 msgp 函 数 传 入 值 msgsz: 消 息 正 文 的 字 节 数 ( 不 包 括 消 息 类 型 指 针 变 量 ) 0: 接 收 消 息 队 列 中 第 一 个 消 息 msgtyp: 大 于 0: 接 收 消 息 队 列 中 第 一 个 类 型 为 msgtyp 的 消 息 小 于 0: 接 收 消 息 队 列 中 第 一 个 类 型 值 不 小 于 msgtyp 绝 对 值 且 类 型 值 又 最 小 的 消 息 函 数 传 入 值 函 数 返 回 值 msgflg: 成 功 :0 出 错 : 1 MSG_NOERROR: 若 返 回 的 消 息 比 msgsz 字 节 多, 则 消 息 就 会 截 短 到 msgsz 字 节, 且 不 通 知 消 息 发 送 进 程 IPC_NOWAIT 若 在 消 息 队 列 中 并 没 有 相 应 类 型 的 消 息 可 以 接 收, 则 函 数 立 即 返 回 0:msgsnd() 调 用 阻 塞 直 到 接 收 一 条 相 应 类 型 的 消 息 为 止 表 8.26 列 举 了 msgctl() 函 数 的 语 法 要 点 表 8.26 所 需 头 文 件 msgctl() 函 数 语 法 要 点 #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> 函 数 原 型 int msgctl (int msgqid, int cmd, struct msqid_ds *buf ) msqid: 消 息 队 列 的 队 列 ID 函 数 传 入 值 cmd: 命 令 参 数 IPC_STAT: 读 取 消 息 队 列 的 数 据 结 构 msqid_ds, 并 将 其 存 储 在 buf 指 定 的 地 址 中 IPC_SET: 设 置 消 息 队 列 的 数 据 结 构 msqid_ds 中 的 ipc_perm 域 (IPC 操 作 权 限 描 述 结 构 ) 值 这 个 值 取 自 buf 参 数 IPC_RMID: 从 系 统 内 核 中 删 除 消 息 队 列 buf: 描 述 消 息 队 列 的 msgqid_ds 结 构 类 型 变 量 函 数 返 回 值 成 功 :0 出 错 : 1 3. 使 用 实 例 这 个 实 例 体 现 了 如 何 使 用 消 息 队 列 进 行 两 个 进 程 ( 发 送 端 和 接 收 端 ) 之 间 的 通 信, 包 括 消 息 队 列 的 创 建 33
260 消 息 发 送 与 读 取 消 息 队 列 的 撤 消 和 删 除 等 多 种 操 作 消 息 发 送 端 进 程 和 消 息 接 收 端 进 程 之 间 不 需 要 额 外 实 现 进 程 之 间 的 同 步 在 该 实 例 中, 发 送 端 发 送 的 消 息 类 型 设 置 为 该 进 程 的 进 程 号 ( 可 以 取 其 他 值 ), 因 此 接 收 端 根 据 消 息 类 型 确 定 消 息 发 送 者 的 进 程 号 注 意 这 里 使 用 了 函 数 fotk(), 它 可 以 根 据 不 同 的 路 径 和 关 键 字 产 生 标 准 的 key 以 下 是 消 息 队 列 发 送 端 的 代 码 : /* msgsnd.c */ #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #define BUFFER_SIZE 512 struct message long msg_type; char msg_text[buffer_size]; ; int main() int qid; key_t key; struct message msg; /* 根 据 不 同 的 路 径 和 关 键 字 产 生 标 准 的 key*/ if ((key = ftok(".", 'a')) == -1) perror("ftok"); exit(1); /* 创 建 消 息 队 列 */ if ((qid = msgget(key, IPC_CREAT 0666)) == -1) perror("msgget"); exit(1); printf("open queue %d\n",qid); while(1) printf("enter some message to the queue:"); if ((fgets(msg.msg_text, BUFFER_SIZE, stdin)) == NULL) puts("no message"); 34
261 exit(1); 专 业 始 于 专 注 卓 识 源 于 远 见 msg.msg_type = getpid(); /* 添 加 消 息 到 消 息 队 列 */ if ((msgsnd(qid, &msg, strlen(msg.msg_text), 0)) < 0) perror("message posted"); exit(1); if (strncmp(msg.msg_text, "quit", 4) == 0) break; exit(0); 以 下 是 消 息 队 列 接 收 端 的 代 码 : /* msgrcv.c */ #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #define BUFFER_SIZE 512 struct message long msg_type; char msg_text[buffer_size]; ; int main() int qid; key_t key; struct message msg; /* 根 据 不 同 的 路 径 和 关 键 字 产 生 标 准 的 key*/ if ((key = ftok(".", 'a')) == -1) perror("ftok"); exit(1); 35
262 专 业 始 于 专 注 卓 识 源 于 远 见 /* 创 建 消 息 队 列 */ if ((qid = msgget(key, IPC_CREAT 0666)) == -1) perror("msgget"); exit(1); printf("open queue %d\n", qid); do /* 读 取 消 息 队 列 */ memset(msg.msg_text, 0, BUFFER_SIZE); if (msgrcv(qid, (void*)&msg, BUFFER_SIZE, 0, 0) < 0) perror("msgrcv"); exit(1); printf("the message from process %d : %s", msg.msg_type, msg.msg_text); while(strncmp(msg.msg_text, "quit", 4)); /* 从 系 统 内 核 中 移 走 消 息 队 列 */ if ((msgctl(qid, IPC_RMID, NULL)) < 0) perror("msgctl"); exit(1); exit(0); 以 下 是 程 序 的 运 行 结 果 输 入 quit 则 两 个 进 程 都 将 结 束 $./msgsnd Open queue Enter some message to the queue:first message Enter some message to the queue:second message Enter some message to the queue:quit $./msgrcv Open queue The message from process 6072 : first message The message from process 6072 : second message The message from process 6072 : quit 36
263 8.7 实 验 内 容 管 道 通 信 实 验 1. 实 验 目 的 通 过 编 写 有 名 管 道 多 路 通 信 实 验, 读 者 可 进 一 步 掌 握 管 道 的 创 建 读 写 等 操 作, 同 时, 也 复 习 使 用 select() 函 数 实 现 管 道 的 通 信 2. 实 验 内 容 读 者 还 记 得 在 小 节 中, 通 过 mknod 命 令 创 建 两 个 管 道 的 实 例 吗? 本 实 例 只 是 在 它 的 基 础 上 添 加 有 名 管 道 的 创 建, 而 不 用 再 输 入 mknod 命 令 3. 实 验 步 骤 (1) 画 出 流 程 图 该 实 验 流 程 图 如 图 8.9 所 示 图 实 验 流 程 图 (2) 编 写 代 码 该 实 验 源 代 码 如 下 所 示 /* pipe_select.c*/ #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <time.h> 37
264 #include <errno.h> 专 业 始 于 专 注 卓 识 源 于 远 见 #define FIFO1 "in1" #define FIFO2 "in2" #define MAX_BUFFER_SIZE 1024 /* 缓 冲 区 大 小 */ #define IN_FILES 3 /* 多 路 复 用 输 入 文 件 数 目 */ #define TIME_DELAY 60 /* 超 时 值 秒 数 */ #define MAX(a, b) ((a > b)?(a):(b)) int main(void) int fds[in_files]; char buf[max_buffer_size]; int i, res, real_read, maxfd; struct timeval tv; fd_set inset,tmp_inset; fds[0] = 0; /* 创 建 两 个 有 名 管 道 */ if (access(fifo1, F_OK) == -1) if ((mkfifo(fifo1, 0666) < 0) && (errno!= EEXIST)) printf("cannot create fifo file\n"); exit(1); if (access(fifo2, F_OK) == -1) if ((mkfifo(fifo2, 0666) < 0) && (errno!= EEXIST)) printf("cannot create fifo file\n"); exit(1); /* 以 只 读 非 阻 塞 方 式 打 开 两 个 管 道 文 件 */ if((fds[1] = open (FIFO1, O_RDONLY O_NONBLOCK)) < 0) printf("open in1 error\n"); return 1; if((fds[2] = open (FIFO2, O_RDONLY O_NONBLOCK)) < 0) printf("open in2 error\n"); return 1; 38
265 专 业 始 于 专 注 卓 识 源 于 远 见 /* 取 出 两 个 文 件 描 述 符 中 的 较 大 者 */ maxfd = MAX(MAX(fds[0], fds[1]), fds[2]); /* 初 始 化 读 集 合 inset, 并 在 读 文 件 描 述 符 集 合 中 加 入 相 应 的 描 述 集 */ FD_ZERO(&inset); for (i = 0; i < IN_FILES; i++) FD_SET(fds[i], &inset); FD_SET(0, &inset); tv.tv_sec = TIME_DELAY; tv.tv_usec = 0; /* 循 环 测 试 该 文 件 描 述 符 是 否 准 备 就 绪, 并 调 用 select() 函 数 对 相 关 文 件 描 述 符 做 相 应 操 作 */ while(fd_isset(fds[0],&inset) FD_ISSET(fds[1],&inset) FD_ISSET(fds[2], &inset)) /* 文 件 描 述 符 集 合 的 备 份, 免 得 每 次 进 行 初 始 化 */ tmp_inset = inset; res = select(maxfd + 1, &tmp_inset, NULL, NULL, &tv); switch(res) case -1: printf("select error\n"); return 1; break; case 0: /* Timeout */ printf("time out\n"); return 1; break; default: for (i = 0; i < IN_FILES; i++) if (FD_ISSET(fds[i], &tmp_inset)) memset(buf, 0, MAX_BUFFER_SIZE); real_read = read(fds[i], buf, MAX_BUFFER_SIZE); if (real_read < 0) if (errno!= EAGAIN) 39
266 return 1; else if (!real_read) close(fds[i]); FD_CLR(fds[i], &inset); else if (i == 0) /* 主 程 序 终 端 控 制 */ if ((buf[0] == 'q') (buf[0] == 'Q')) return 1; else /* 显 示 管 道 输 入 字 符 串 */ buf[real_read] = '\0'; printf("%s", buf); /* end of if */ /* end of for */ break; /* end of switch */ /*end of while */ return 0; (3) 编 译 并 运 行 该 程 序 (4) 另 外 打 开 两 个 虚 拟 终 端, 分 别 键 入 cat > in1 和 cat > in2, 接 着 在 该 管 道 中 键 入 相 关 内 容, 并 观 察 实 验 结 果 4. 实 验 结 果 实 验 运 行 结 果 与 第 6 章 的 例 子 完 全 相 同 $./pipe_select ( 必 须 先 运 行 主 程 序 ) SELECT CALL select call TEST PROGRAMME test programme END end q /* 在 终 端 上 输 入 q 或 Q 立 刻 结 束 程 序 运 行 */ 40
267 $ cat > in1 SELECT CALL TEST PROGRAMME END $ cat > in2 select call test programme end 共 享 内 存 实 验 1. 实 验 目 的 通 过 编 写 共 享 内 存 实 验, 读 者 可 以 进 一 步 了 解 使 用 共 享 内 存 的 具 体 步 骤, 同 时 也 进 一 步 加 深 对 共 享 内 存 的 理 解 在 本 实 验 中, 采 用 信 号 量 作 为 同 步 机 制 完 善 两 个 进 程 ( 生 产 者 和 消 费 者 ) 之 间 的 通 信 其 功 能 类 似 于 消 息 队 列 中 的 实 例, 详 见 小 节 在 实 例 中 使 用 的 与 信 号 量 相 关 的 函 数, 详 见 小 节 2. 实 验 内 容 该 实 现 要 求 利 用 共 享 内 存 实 现 文 件 的 打 开 和 读 写 操 作 3. 实 验 步 骤 (1) 画 出 流 程 图 该 实 验 流 程 图 如 图 8.10 所 示 41
268 图 8.10 实 验 流 程 图 (2) 编 写 代 码 下 面 是 共 享 内 存 缓 冲 区 的 数 据 结 构 的 定 义 /* shm_com.h */ #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #define SHM_BUFF_SZ 2048 struct shm_buff int pid; char buffer[shm_buff_sz]; ; 以 下 是 生 产 者 程 序 部 分 /* sem_com.h 和 sem_com.c 与 信 号 量 小 节 示 例 中 的 同 名 程 序 相 同 */ /* producer.c */ #include "shm_com.h" #include "sem_com.h" #include <signal.h> int ignore_signal(void) /* 忽 略 一 些 信 号, 免 得 非 法 退 出 程 序 */ signal(sigint, SIG_IGN); signal(sigstop, SIG_IGN); 42
269 signal(sigquit, SIG_IGN); return 0; 专 业 始 于 专 注 卓 识 源 于 远 见 int main() void *shared_memory = NULL; struct shm_buff *shm_buff_inst; char buffer[bufsiz]; int shmid, semid; /* 定 义 信 号 量, 用 于 实 现 访 问 共 享 内 存 的 进 程 之 间 的 互 斥 */ ignore_signal(); /* 防 止 程 序 非 正 常 退 出 */ semid = semget(ftok(".", 'a'), 1, 0666 IPC_CREAT); /* 创 建 一 个 信 号 量 */ init_sem(semid);/* 初 始 值 为 1 */ /* 创 建 共 享 内 存 */ shmid = shmget(ftok(".", 'b'), sizeof(struct shm_buff), 0666 IPC_CREAT); if (shmid == -1) perror("shmget failed"); del_sem(semid); exit(1); /* 将 共 享 内 存 地 址 映 射 到 当 前 进 程 地 址 空 间 */ shared_memory = shmat(shmid, (void*)0, 0); if (shared_memory == (void*)-1) perror("shmat"); del_sem(semid); exit(1); printf("memory attached at %X\n", (int)shared_memory); /* 获 得 共 享 内 存 的 映 射 地 址 */ shm_buff_inst = (struct shared_use_st *)shared_memory; do sem_p(semid); printf("enter some text to the shared memory(enter 'quit' to exit):"); /* 向 共 享 内 存 写 入 数 据 */ if (fgets(shm_buff_inst->buffer, SHM_BUFF_SZ, stdin) == NULL) perror("fgets"); sem_v(semid); break; shm_buff_inst->pid = getpid(); 43
270 sem_v(semid); while(strncmp(shm_buff_inst->buffer, "quit", 4)!= 0); 专 业 始 于 专 注 卓 识 源 于 远 见 /* 删 除 信 号 量 */ del_sem(semid); /* 删 除 共 享 内 存 到 当 前 进 程 地 址 空 间 中 的 映 射 */ if (shmdt(shared_memory) == 1) perror("shmdt"); exit(1); exit(0); 以 下 是 消 费 者 程 序 部 分 /* customer.c */ #include "shm_com.h" #include "sem_com.h" int main() void *shared_memory = NULL; struct shm_buff *shm_buff_inst; int shmid, semid; /* 获 得 信 号 量 */ semid = semget(ftok(".", 'a'), 1, 0666); if (semid == -1) perror("producer is'nt exist"); exit(1); /* 获 得 共 享 内 存 */ shmid = shmget(ftok(".", 'b'), sizeof(struct shm_buff), 0666 IPC_CREAT); if (shmid == -1) perror("shmget"); exit(1); /* 将 共 享 内 存 地 址 映 射 到 当 前 进 程 地 址 空 间 */ shared_memory = shmat(shmid, (void*)0, 0); if (shared_memory == (void*)-1) perror("shmat"); exit(1); printf("memory attached at %X\n", (int)shared_memory); /* 获 得 共 享 内 存 的 映 射 地 址 */ shm_buff_inst = (struct shm_buff *)shared_memory; 44
271 do sem_p(semid); printf("shared memory was written by process %d :%s", shm_buff_inst->pid, shm_buff_inst->buffer); if (strncmp(shm_buff_inst->buffer, "quit", 4) == 0) break; shm_buff_inst->pid = 0; memset(shm_buff_inst->buffer, 0, SHM_BUFF_SZ); sem_v(semid); while(1); 专 业 始 于 专 注 卓 识 源 于 远 见 /* 删 除 共 享 内 存 到 当 前 进 程 地 址 空 间 中 的 映 射 */ if (shmdt(shared_memory) == -1) perror("shmdt"); exit(1); /* 删 除 共 享 内 存 */ if (shmctl(shmid, IPC_RMID, NULL) == -1) perror("shmctl(ipc_rmid)"); exit(1); exit(0); 4. 实 验 结 果 $./producer Memory attached at B7F90000 Enter some text to the shared memory(enter 'quit' to exit):first message Enter some text to the shared memory(enter 'quit' to exit):second message Enter some text to the shared memory(enter 'quit' to exit):quit $./customer Memory attached at B7FAF000 Shared memory was written by process 3815 :First message Shared memory was written by process 3815 :Second message Shared memory was written by process 3815 :quit 8.8 本 章 小 结 本 章 详 细 讲 解 了 Linux 中 进 程 间 通 信 的 几 种 机 制, 包 括 管 道 通 信 信 号 通 信 消 息 队 列 信 号 量 以 及 共 享 45
272 内 存 机 制 等, 并 且 讲 解 了 进 程 间 通 信 的 演 进 接 下 来 对 管 道 通 信 信 号 通 信 消 息 队 列 和 共 享 内 存 机 制 进 行 了 详 细 讲 解 其 中, 管 道 通 信 又 分 为 有 名 管 道 和 无 名 管 道 信 号 通 信 中 要 着 重 掌 握 如 何 对 信 号 进 行 适 当 的 处 理, 如 采 用 信 号 集 等 方 式 信 号 量 是 用 于 实 现 进 程 之 间 的 同 步 和 互 斥 的 进 程 间 通 信 机 制 消 息 队 列 和 共 享 内 存 也 是 很 好 的 进 程 间 通 信 的 手 段, 其 中 共 享 内 存 具 有 很 高 的 效 率, 并 经 常 以 信 号 量 作 为 同 步 机 制 本 章 的 最 后 安 排 了 管 道 通 信 实 验 和 共 享 内 存 的 实 验, 具 体 的 实 验 数 据 根 据 系 统 的 不 同 可 能 会 有 所 区 别, 希 望 读 者 认 真 分 析 8.9 思 考 与 练 习 1. 通 过 自 定 义 信 号 完 成 进 程 间 的 通 信 2. 编 写 一 个 简 单 的 管 道 程 序 实 现 文 件 传 输 联 系 方 式 集 团 官 网 : 嵌 入 式 学 院 : 移 动 互 联 网 学 院 : 企 业 学 院 : 物 联 网 学 院 : 研 发 中 心 :dev.hqyj.com 集 团 总 部 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 内 华 清 远 见 教 育 集 团 北 京 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 区, 电 话 : /5 上 海 地 址 : 上 海 市 徐 汇 区 漕 溪 路 银 海 大 厦 A 座 8 层, 电 话 : 深 圳 地 址 : 深 圳 市 龙 华 新 区 人 民 北 路 美 丽 AAA 大 厦 15 层, 电 话 : 成 都 地 址 : 成 都 市 武 侯 区 科 华 北 路 99 号 科 华 大 厦 6 层, 电 话 : 南 京 地 址 : 南 京 市 白 下 区 汉 中 路 185 号 鸿 运 大 厦 10 层, 电 话 : 武 汉 地 址 : 武 汉 市 工 程 大 学 卓 刀 泉 校 区 科 技 孵 化 器 大 楼 8 层, 电 话 : 西 安 地 址 : 西 安 市 高 新 区 高 新 一 路 12 号 创 业 大 厦 D3 楼 5 层, 电 话 :
273 嵌 入 式 LINUX 应 用 程 序 开 发 标 准 教 程 作 者 : 华 清 远 见 第 9 章 多 线 程 编 程 Linux 掌 握 Linux 中 线 程 的 基 本 概 念 掌 握 Linux 中 线 程 的 创 建 及 使 用 掌 握 Linux 中 线 程 属 性 的 设 置 能 够 独 立 编 写 多 线 程 程 序
274 9.1 Linux 线 程 概 述 线 程 概 述 前 面 已 经 提 到, 进 程 是 系 统 中 程 序 执 行 和 资 源 分 配 的 基 本 单 位 每 个 进 程 都 拥 有 自 己 的 数 据 段 代 码 段 和 堆 栈 段, 这 就 造 成 了 进 程 在 进 行 切 换 等 操 作 时 都 需 要 有 比 较 复 杂 的 上 下 文 切 换 等 动 作 为 了 进 一 步 减 少 处 理 机 的 空 转 时 间, 支 持 多 处 理 器 以 及 减 少 上 下 文 切 换 开 销, 进 程 在 演 化 中 出 现 了 另 一 个 概 念 线 程 它 是 进 程 内 独 立 的 一 条 运 行 路 线, 处 理 器 调 度 的 最 小 单 元, 也 可 以 称 为 轻 量 级 进 程 线 程 可 以 对 进 程 的 内 存 空 间 和 资 源 进 行 访 问, 并 与 同 一 进 程 中 的 其 他 线 程 共 享 因 此, 线 程 的 上 下 文 切 换 的 开 销 比 创 建 进 程 小 很 多 同 进 程 一 样, 线 程 也 将 相 关 的 执 行 状 态 和 存 储 变 量 放 在 线 程 控 制 表 内 一 个 进 程 可 以 有 多 个 线 程, 也 就 是 有 多 个 线 程 控 制 表 及 堆 栈 寄 存 器, 但 却 共 享 一 个 用 户 地 址 空 间 要 注 意 的 是, 由 于 线 程 共 享 了 进 程 的 资 源 和 地 址 空 间, 因 此, 任 何 线 程 对 系 统 资 源 的 操 作 都 会 给 其 他 线 程 带 来 影 响 由 此 可 知, 多 线 程 中 的 同 步 是 非 常 重 要 的 问 题 在 多 线 程 系 统 中, 进 程 与 进 程 的 关 系 如 图 9.1 所 示 进 程 用 户 地 址 空 间 线 程 一 线 程 二 线 程 三 线 程 机 制 的 分 类 和 特 性 图 9.1 进 程 与 线 程 关 系 线 程 按 照 其 调 度 者 可 以 分 为 用 户 级 线 程 和 核 心 级 线 程 两 种 (1) 用 户 级 线 程 用 户 级 线 程 主 要 解 决 的 是 上 下 文 切 换 的 问 题, 它 的 调 度 算 法 和 调 度 过 程 全 部 由 用 户 自 行 选 择 决 定, 在 运 行 时 不 需 要 特 定 的 内 核 支 持 在 这 里, 操 作 系 统 往 往 会 提 供 一 个 用 户 空 间 的 线 程 库, 该 线 程 库 提 供 了 线 程 的 创 建 调 度 和 撤 销 等 功 能, 而 内 核 仍 然 仅 对 进 程 进 行 管 理 如 果 一 个 进 程 中 的 某 一 个 线 程 调 用 了 一 个 阻 塞 的 系 统 调 用 函 数, 那 么 该 进 程 包 括 该 进 程 中 的 其 他 所 有 线 程 也 同 时 被 阻 塞 这 种 用 户 级 线 程 的 主 要 缺 点 是 在 一 个 进 程 中 的 多 个 线 程 的 调 度 中 无 法 发 挥 多 处 理 器 的 优 势 (2) 轻 量 级 进 程 轻 量 级 进 程 是 内 核 支 持 的 用 户 线 程, 是 内 核 线 程 的 一 种 抽 象 对 象 每 个 线 程 拥 有 一 个 或 多 个 轻 量 级 线 程, 而 每 个 轻 量 级 线 程 分 别 被 绑 定 在 一 个 内 核 线 程 上 (3) 内 核 线 程 这 种 线 程 允 许 不 同 进 程 中 的 线 程 按 照 同 一 相 对 优 先 调 度 方 法 进 行 调 度, 这 样 就 可 以 发 挥 多 处 理 器 的 并 发 优 势 现 在 大 多 数 系 统 都 采 用 用 户 级 线 程 与 核 心 级 线 程 并 存 的 方 法 一 个 用 户 级 线 程 可 以 对 应 一 个 或 几 个 核 心 级 线 程, 也 就 是 一 对 一 或 多 对 一 模 型 这 样 既 可 满 足 多 处 理 机 系 统 的 需 要, 也 可 以 最 大 限 度 地 减 少 调 度 开 销 使 用 线 程 机 制 大 大 加 快 上 下 文 切 换 速 度 而 且 节 省 很 多 资 源 但 是 因 为 在 用 户 态 和 内 核 态 均 要 实 现 调 度 管 理, 所 以 会 增 加 实 现 的 复 杂 度 和 引 起 优 先 级 翻 转 的 可 能 性 一 个 多 线 程 程 序 的 同 步 设 计 与 调 试 也 会 增 加 程 序 实 现 的 难 度 2
275 9.2 Linux 线 程 编 程 线 程 基 本 编 程 这 里 要 讲 的 线 程 相 关 操 作 都 是 用 户 空 间 中 的 线 程 的 操 作 在 Linux 中, 一 般 pthread 线 程 库 是 一 套 通 用 的 线 程 库, 是 由 POSIX 提 出 的, 因 此 具 有 很 好 的 可 移 植 性 (1) 函 数 说 明 创 建 线 程 实 际 上 就 是 确 定 调 用 该 线 程 函 数 的 入 口 点, 这 里 通 常 使 用 的 函 数 是 pthread_create() 在 线 程 创 建 以 后, 就 开 始 运 行 相 关 的 线 程 函 数, 在 该 函 数 运 行 完 之 后, 该 线 程 也 就 退 出 了, 这 也 是 线 程 退 出 一 种 方 法 另 一 种 退 出 线 程 的 方 法 是 使 用 函 数 pthread_exit(), 这 是 线 程 的 主 动 行 为 这 里 要 注 意 的 是, 在 使 用 线 程 函 数 时, 不 能 随 意 使 用 exit() 退 出 函 数 进 行 出 错 处 理, 由 于 exit() 的 作 用 是 使 调 用 进 程 终 止, 往 往 一 个 进 程 包 含 多 个 线 程, 因 此, 在 使 用 exit() 之 后, 该 进 程 中 的 所 有 线 程 都 终 止 了 因 此, 在 线 程 中 就 可 以 使 用 pthread_exit() 来 代 替 进 程 中 的 exit() 由 于 一 个 进 程 中 的 多 个 线 程 是 共 享 数 据 段 的, 因 此 通 常 在 线 程 退 出 之 后, 退 出 线 程 所 占 用 的 资 源 并 不 会 随 着 线 程 的 终 止 而 得 到 释 放 正 如 进 程 之 间 可 以 用 wait() 系 统 调 用 来 同 步 终 止 并 释 放 资 源 一 样, 线 程 之 间 也 有 类 似 机 制, 那 就 是 pthread_join() 函 数 pthread_join() 可 以 用 于 将 当 前 线 程 挂 起 来 等 待 线 程 的 结 束 这 个 函 数 是 一 个 线 程 阻 塞 的 函 数, 调 用 它 的 函 数 将 一 直 等 待 到 被 等 待 的 线 程 结 束 为 止, 当 函 数 返 回 时, 被 等 待 线 程 的 资 源 就 被 收 回 前 面 已 提 到 线 程 调 用 pthread_exit() 函 数 主 动 终 止 自 身 线 程 但 是 在 很 多 线 程 应 用 中, 经 常 会 遇 到 在 别 的 线 程 中 要 终 止 另 一 个 线 程 的 执 行 的 问 题 此 时 调 用 pthread_cancel() 函 数 实 现 这 种 功 能, 但 在 被 取 消 的 线 程 的 内 部 需 要 调 用 pthread_setcancel() 函 数 和 pthread_setcanceltype() 函 数 设 置 自 己 的 取 消 状 态, 例 如 被 取 消 的 线 程 接 收 到 另 一 个 线 程 的 取 消 请 求 之 后, 是 接 受 还 是 忽 略 这 个 请 求 ; 如 果 接 受, 是 立 刻 进 行 终 止 操 作 还 是 等 待 某 个 函 数 的 调 用 等 (2) 函 数 格 式 表 9.1 列 出 了 pthread_create() 函 数 的 语 法 要 点 表 9.1 所 需 头 文 件 函 数 原 型 函 数 传 入 值 #include <pthread.h> pthread_create() 函 数 语 法 要 点 int pthread_create ((pthread_t *thread, pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)) thread: 线 程 标 识 符 attr: 线 程 属 性 设 置 ( 其 具 体 设 置 参 见 小 节 ), 通 常 取 为 NULL start_routine: 线 程 函 数 的 起 始 地 址, 是 一 个 以 指 向 void 的 指 针 作 为 参 数 和 返 回 值 的 函 数 指 针 arg: 传 递 给 start_routine 的 参 数 函 数 返 回 值 成 功 :0 出 错 : 返 回 错 误 码 表 9.2 列 出 了 pthread_exit() 函 数 的 语 法 要 点 表 9.2 所 需 头 文 件 函 数 原 型 函 数 传 入 值 pthread_exit() 函 数 语 法 要 点 #include <pthread.h> void pthread_exit(void *retval) retval: 线 程 结 束 时 的 返 回 值, 可 由 其 他 函 数 如 pthread_join() 来 获 取 表 9.3 列 出 了 pthread_join() 函 数 的 语 法 要 点 表 9.3 所 需 头 文 件 #include <pthread.h> pthread_join() 函 数 语 法 要 点 3
276 函 数 原 型 函 数 传 入 值 函 数 返 回 值 int pthread_join ((pthread_t th, void **thread_return)) th: 等 待 线 程 的 标 识 符 thread_return: 用 户 定 义 的 指 针, 用 来 存 储 被 等 待 线 程 结 束 时 的 返 回 值 ( 不 为 NULL 时 ) 成 功 :0 出 错 : 返 回 错 误 码 表 9.4 列 出 了 pthread_cancel() 函 数 的 语 法 要 点 表 9.4 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 pthread_cancel() 函 数 语 法 要 点 #include <pthread.h> int pthread_cancel((pthread_t th) th: 要 取 消 的 线 程 的 标 识 符 成 功 :0 出 错 : 返 回 错 误 码 (3) 函 数 使 用 以 下 实 例 中 创 建 了 3 个 线 程, 为 了 更 好 地 描 述 线 程 之 间 的 并 行 执 行, 让 3 个 线 程 重 用 同 一 个 执 行 函 数 每 个 线 程 都 有 5 次 循 环 ( 可 以 看 成 5 个 小 任 务 ), 每 次 循 环 之 间 会 随 机 等 待 1~10s 的 时 间, 意 义 在 于 模 拟 每 个 任 务 的 到 达 时 间 是 随 机 的, 并 没 有 任 何 特 定 规 律 /* thread.c */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define THREAD_NUMBER 3 /* 线 程 数 */ #define REPEAT_NUMBER 5 /* 每 个 线 程 中 的 小 任 务 数 */ #define DELAY_TIME_LEVELS 10.0 /* 小 任 务 之 间 的 最 大 时 间 间 隔 */ void *thrd_func(void *arg) /* 线 程 函 数 例 程 */ int thrd_num = (int)arg; int delay_time = 0; int count = 0; printf("thread %d is starting\n", thrd_num); for (count = 0; count < REPEAT_NUMBER; count++) delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tthread %d: job %d delay = %d\n", thrd_num, count, delay_time); printf("thread %d finished\n", thrd_num); pthread_exit(null); int main(void) 4
277 pthread_t thread[thread_number]; int no = 0, res; void * thrd_ret; 专 业 始 于 专 注 卓 识 源 于 远 见 srand(time(null)); for (no = 0; no < THREAD_NUMBER; no++) /* 创 建 多 线 程 */ res = pthread_create(&thread[no], NULL, thrd_func, (void*)no); if (res!= 0) printf("create thread %d failed\n", no); exit(res); printf("create treads success\n Waiting for threads to finish...\n"); for (no = 0; no < THREAD_NUMBER; no++) /* 等 待 线 程 结 束 */ res = pthread_join(thread[no], &thrd_ret); if (!res) printf("thread %d joined\n", no); else printf("thread %d join failed\n", no); return 0; 以 下 是 程 序 运 行 结 果 可 以 看 出 每 个 线 程 的 运 行 和 结 束 是 独 立 与 并 行 的 $./thread Create treads success Waiting for threads to finish... Thread 0 is starting Thread 1 is starting Thread 2 is starting Thread 1: job 0 delay = 6 Thread 2: job 0 delay = 6 Thread 0: job 0 delay = 9 Thread 1: job 1 delay = 6 Thread 2: job 1 delay = 8 Thread 0: job 1 delay = 8 5
278 Thread 2: job 2 delay = 3 Thread 0: job 2 delay = 3 Thread 2: job 3 delay = 3 Thread 2: job 4 delay = 1 Thread 2 finished Thread 1: job 2 delay = 10 Thread 1: job 3 delay = 4 Thread 1: job 4 delay = 1 Thread 1 finished Thread 0: job 3 delay = 9 Thread 0: job 4 delay = 2 Thread 0 finished Thread 0 joined Thread 1 joined Thread 2 joined 线 程 之 间 的 同 步 与 互 斥 由 于 线 程 共 享 进 程 的 资 源 和 地 址 空 间, 因 此 在 对 这 些 资 源 进 行 操 作 时, 必 须 考 虑 到 线 程 间 资 源 访 问 的 同 步 与 互 斥 问 题 这 里 主 要 介 绍 POSIX 中 两 种 线 程 同 步 机 制, 分 别 为 互 斥 锁 和 信 号 量 这 两 个 同 步 机 制 可 以 互 相 通 过 调 用 对 方 来 实 现, 但 互 斥 锁 更 适 合 用 于 同 时 可 用 的 资 源 是 惟 一 的 情 况 ; 信 号 量 更 适 合 用 于 同 时 可 用 的 资 源 为 多 个 的 情 况 1. 互 斥 锁 线 程 控 制 (1) 函 数 说 明 互 斥 锁 是 用 一 种 简 单 的 加 锁 方 法 来 控 制 对 共 享 资 源 的 原 子 操 作 这 个 互 斥 锁 只 有 两 种 状 态, 也 就 是 上 锁 和 解 锁, 可 以 把 互 斥 锁 看 作 某 种 意 义 上 的 全 局 变 量 在 同 一 时 刻 只 能 有 一 个 线 程 掌 握 某 个 互 斥 锁, 拥 有 上 锁 状 态 的 线 程 能 够 对 共 享 资 源 进 行 操 作 若 其 他 线 程 希 望 上 锁 一 个 已 经 被 上 锁 的 互 斥 锁, 则 该 线 程 就 会 挂 起, 直 到 上 锁 的 线 程 释 放 掉 互 斥 锁 为 止 可 以 说, 这 把 互 斥 锁 保 证 让 每 个 线 程 对 共 享 资 源 按 顺 序 进 行 原 子 操 作 互 斥 锁 机 制 主 要 包 括 下 面 的 基 本 函 数 互 斥 锁 初 始 化 :pthread_mutex_init() 互 斥 锁 上 锁 :pthread_mutex_lock() 互 斥 锁 判 断 上 锁 :pthread_mutex_trylock() 互 斥 锁 接 锁 :pthread_mutex_unlock() 消 除 互 斥 锁 :pthread_mutex_destroy() 其 中, 互 斥 锁 可 以 分 为 快 速 互 斥 锁 递 归 互 斥 锁 和 检 错 互 斥 锁 这 3 种 锁 的 区 别 主 要 在 于 其 他 未 占 有 互 斥 锁 的 线 程 在 希 望 得 到 互 斥 锁 时 是 否 需 要 阻 塞 等 待 快 速 锁 是 指 调 用 线 程 会 阻 塞 直 至 拥 有 互 斥 锁 的 线 程 解 锁 为 止 递 归 互 斥 锁 能 够 成 功 地 返 回, 并 且 增 加 调 用 线 程 在 互 斥 上 加 锁 的 次 数, 而 检 错 互 斥 锁 则 为 快 速 互 斥 锁 的 非 阻 塞 版 本, 它 会 立 即 返 回 并 返 回 一 个 错 误 信 息 默 认 属 性 为 快 速 互 斥 锁 (2) 函 数 格 式 表 9.5 列 出 了 pthread_mutex_init() 函 数 的 语 法 要 点 表 9.5 所 需 头 文 件 函 数 原 型 函 数 传 入 值 pthread_mutex_init() 函 数 语 法 要 点 #include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr) mutex: 互 斥 锁 Mutexattr PTHREAD_MUTEX_INITIALIZER: 创 建 快 速 互 斥 锁 6
279 PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP: 创 建 递 归 互 斥 锁 PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP: 创 建 检 错 互 斥 锁 函 数 返 回 值 成 功 :0 出 错 : 返 回 错 误 码 表 9.6 列 出 了 pthread_mutex_lock() 等 函 数 的 语 法 要 点 表 9.6 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <pthread.h> pthread_mutex_lock() 等 函 数 语 法 要 点 int pthread_mutex_lock(pthread_mutex_t *mutex,) int pthread_mutex_trylock(pthread_mutex_t *mutex,) int pthread_mutex_unlock(pthread_mutex_t *mutex,) int pthread_mutex_destroy(pthread_mutex_t *mutex,) mutex: 互 斥 锁 成 功 :0 出 错 : 1 (3) 使 用 实 例 下 面 的 实 例 是 在 小 节 示 例 代 码 的 基 础 上 增 加 互 斥 锁 功 能, 实 现 原 本 独 立 与 无 序 的 多 个 线 程 能 够 按 顺 序 执 行 /*thread_mutex.c*/ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define THREAD_NUMBER 3 /* 线 程 数 */ #define REPEAT_NUMBER 3 /* 每 个 线 程 的 小 任 务 数 */ #define DELAY_TIME_LEVELS 10.0 /* 小 任 务 之 间 的 最 大 时 间 间 隔 */ pthread_mutex_t mutex; void *thrd_func(void *arg) int thrd_num = (int)arg; int delay_time = 0, count = 0; int res; /* 互 斥 锁 上 锁 */ res = pthread_mutex_lock(&mutex); if (res) printf("thread %d lock failed\n", thrd_num); pthread_exit(null); printf("thread %d is starting\n", thrd_num); for (count = 0; count < REPEAT_NUMBER; count++) delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tthread %d: job %d delay = %d\n", thrd_num, count, delay_time); 7
280 printf("thread %d finished\n", thrd_num); pthread_exit(null); 专 业 始 于 专 注 卓 识 源 于 远 见 int main(void) pthread_t thread[thread_number]; int no = 0, res; void * thrd_ret; srand(time(null)); /* 互 斥 锁 初 始 化 */ pthread_mutex_init(&mutex, NULL); for (no = 0; no < THREAD_NUMBER; no++) res = pthread_create(&thread[no], NULL, thrd_func, (void*)no); if (res!= 0) printf("create thread %d failed\n", no); exit(res); printf("create treads success\n Waiting for threads to finish...\n"); for (no = 0; no < THREAD_NUMBER; no++) res = pthread_join(thread[no], &thrd_ret); if (!res) printf("thread %d joined\n", no); else printf("thread %d join failed\n", no); /* 互 斥 锁 解 锁 */ pthread_mutex_unlock(&mutex); pthread_mutex_destroy(&mutex); return 0; 该 实 例 的 运 行 结 果 如 下 所 示 这 里 3 个 线 程 之 间 的 运 行 顺 序 跟 创 建 线 程 的 顺 序 相 同 $./thread_mutex Create treads success Waiting for threads to finish... Thread 0 is starting 8
281 Thread 0: job 0 delay = 7 Thread 0: job 1 delay = 7 Thread 0: job 2 delay = 6 Thread 0 finished Thread 0 joined Thread 1 is starting Thread 1: job 0 delay = 3 Thread 1: job 1 delay = 5 Thread 1: job 2 delay = 10 Thread 1 finished Thread 1 joined Thread 2 is starting Thread 2: job 0 delay = 6 Thread 2: job 1 delay = 10 Thread 2: job 2 delay = 8 Thread 2 finished Thread 2 joined 2. 信 号 量 线 程 控 制 (1) 信 号 量 说 明 在 第 8 章 中 已 经 讲 到, 信 号 量 也 就 是 操 作 系 统 中 所 用 到 的 PV 原 子 操 作, 它 广 泛 用 于 进 程 或 线 程 间 的 同 步 与 互 斥 信 号 量 本 质 上 是 一 个 非 负 的 整 数 计 数 器, 它 被 用 来 控 制 对 公 共 资 源 的 访 问 这 里 先 来 简 单 复 习 一 下 PV 原 子 操 作 的 工 作 原 理 PV 原 子 操 作 是 对 整 数 计 数 器 信 号 量 sem 的 操 作 一 次 P 操 作 使 sem 减 一, 而 一 次 V 操 作 使 sem 加 一 进 程 ( 或 线 程 ) 根 据 信 号 量 的 值 来 判 断 是 否 对 公 共 资 源 具 有 访 问 权 限 当 信 号 量 sem 的 值 大 于 等 于 零 时, 该 进 程 ( 或 线 程 ) 具 有 公 共 资 源 的 访 问 权 限 ; 相 反, 当 信 号 量 sem 的 值 小 于 零 时, 该 进 程 ( 或 线 程 ) 就 将 阻 塞 直 到 信 号 量 sem 的 值 大 于 等 于 0 为 止 PV 原 子 操 作 主 要 用 于 进 程 或 线 程 间 的 同 步 和 互 斥 这 两 种 典 型 情 况 若 用 于 互 斥, 几 个 进 程 ( 或 线 程 ) 往 往 只 设 置 一 个 信 号 量 sem, 它 们 的 操 作 流 程 如 图 9.2 所 示 当 信 号 量 用 于 同 步 操 作 时, 往 往 会 设 置 多 个 信 号 量, 并 安 排 不 同 的 初 始 值 来 实 现 它 们 之 间 的 顺 序 执 行, 它 们 的 操 作 流 程 如 图 9.3 所 示 9
282 图 9.2 信 号 量 互 斥 操 作 图 9.3 信 号 量 同 步 操 作 (2) 函 数 说 明 Linux 实 现 了 POSIX 的 无 名 信 号 量, 主 要 用 于 线 程 间 的 互 斥 与 同 步 这 里 主 要 介 绍 几 个 常 见 函 数 sem_init() 用 于 创 建 一 个 信 号 量, 并 初 始 化 它 的 值 sem_wait() 和 sem_trywait() 都 相 当 于 P 操 作, 在 信 号 量 大 于 零 时 它 们 都 能 将 信 号 量 的 值 减 一, 两 者 的 区 别 在 于 若 信 号 量 小 于 零 时,sem_wait() 将 会 阻 塞 进 程, 而 sem_trywait() 则 会 立 即 返 回 sem_post() 相 当 于 V 操 作, 它 将 信 号 量 的 值 加 一 同 时 发 出 信 号 来 唤 醒 等 待 的 进 程 sem_getvalue() 用 于 得 到 信 号 量 的 值 sem_destroy() 用 于 删 除 信 号 量 (3) 函 数 格 式 表 9.7 列 出 了 sem_init() 函 数 的 语 法 要 点 表 9.7 sem_init() 函 数 语 法 要 点 所 需 头 文 件 函 数 原 型 #include <semaphore.h> int sem_init(sem_t *sem,int pshared,unsigned int value) sem: 信 号 量 指 针 函 数 传 入 值 pshared: 决 定 信 号 量 能 否 在 几 个 进 程 间 共 享 由 于 目 前 Linux 还 没 有 实 现 进 程 间 共 享 信 号 量, 所 以 这 个 值 只 能 够 取 0, 就 表 示 这 个 信 号 量 是 当 前 进 程 的 局 部 信 号 量 value: 信 号 量 初 始 化 值 函 数 返 回 值 成 功 :0 出 错 : 1 表 9.8 列 出 了 sem_wait() 等 函 数 的 语 法 要 点 表 9.8 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <pthread.h> int sem_wait(sem_t *sem) int sem_trywait(sem_t *sem) int sem_post(sem_t *sem) int sem_getvalue(sem_t *sem) int sem_destroy(sem_t *sem) sem: 信 号 量 指 针 成 功 :0 出 错 : 1 sem_wait() 等 函 数 语 法 要 点 10
283 (4) 使 用 实 例 在 前 面 已 经 通 过 互 斥 锁 同 步 机 制 实 现 了 多 线 程 的 顺 序 执 行 下 面 的 例 子 是 用 信 号 量 同 步 机 制 实 现 3 个 线 程 之 间 的 有 序 执 行, 只 是 执 行 顺 序 是 跟 创 建 线 程 的 顺 序 相 反 /*thread_sem.c*/ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define THREAD_NUMBER 3 /* 线 程 数 */ #define REPEAT_NUMBER 3 /* 每 个 线 程 中 的 小 任 务 数 */ #define DELAY_TIME_LEVELS 10.0 /* 小 任 务 之 间 的 最 大 时 间 间 隔 */ sem_t sem[thread_number]; void *thrd_func(void *arg) int thrd_num = (int)arg; int delay_time = 0; int count = 0; /* 进 行 P 操 作 */ sem_wait(&sem[thrd_num]); printf("thread %d is starting\n", thrd_num); for (count = 0; count < REPEAT_NUMBER; count++) delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tthread %d: job %d delay = %d\n", thrd_num, count, delay_time); printf("thread %d finished\n", thrd_num); pthread_exit(null); int main(void) pthread_t thread[thread_number]; int no = 0, res; void * thrd_ret; srand(time(null)); for (no = 0; no < THREAD_NUMBER; no++) sem_init(&sem[no], 0, 0); res = pthread_create(&thread[no], NULL, thrd_func, (void*)no); if (res!= 0) 11
284 printf("create thread %d failed\n", no); exit(res); 专 业 始 于 专 注 卓 识 源 于 远 见 printf("create treads success\n Waiting for threads to finish...\n"); /* 对 最 后 创 建 的 线 程 的 信 号 量 进 行 V 操 作 */ sem_post(&sem[thread_number - 1]); for (no = THREAD_NUMBER - 1; no >= 0; no--) res = pthread_join(thread[no], &thrd_ret); if (!res) printf("thread %d joined\n", no); else printf("thread %d join failed\n", no); /* 进 行 V 操 作 */ sem_post(&sem[(no + THREAD_NUMBER - 1) % THREAD_NUMBER]); for (no = 0; no < THREAD_NUMBER; no++) /* 删 除 信 号 量 */ sem_destroy(&sem[no]); return 0; 该 程 序 运 行 结 果 如 下 所 示 : $./thread_sem Create treads success Waiting for threads to finish... Thread 2 is starting Thread 2: job 0 delay = 9 Thread 2: job 1 delay = 5 Thread 2: job 2 delay = 10 Thread 2 finished Thread 2 joined Thread 1 is starting Thread 1: job 0 delay = 7 Thread 1: job 1 delay = 4 Thread 1: job 2 delay = 4 Thread 1 finished 12
285 Thread 1 joined Thread 0 is starting Thread 0: job 0 delay = 10 Thread 0: job 1 delay = 8 Thread 0: job 2 delay = 9 Thread 0 finished Thread 0 joined 线 程 属 性 (1) 函 数 说 明 pthread_create() 函 数 的 第 二 个 参 数 (pthread_attr_t *attr) 表 示 线 程 的 属 性 在 上 一 个 实 例 中, 将 该 值 设 为 NULL, 也 就 是 采 用 默 认 属 性, 线 程 的 多 项 属 性 都 是 可 以 更 改 的 这 些 属 性 主 要 包 括 绑 定 属 性 分 离 属 性 堆 栈 地 址 堆 栈 大 小 以 及 优 先 级 其 中 系 统 默 认 的 属 性 为 非 绑 定 非 分 离 缺 省 1M 的 堆 栈 以 及 与 父 进 程 同 样 级 别 的 优 先 级 下 面 首 先 对 绑 定 属 性 和 分 离 属 性 的 基 本 概 念 进 行 讲 解 绑 定 属 性 前 面 已 经 提 到,Linux 中 采 用 一 对 一 的 线 程 机 制, 也 就 是 一 个 用 户 线 程 对 应 一 个 内 核 线 程 绑 定 属 性 就 是 指 一 个 用 户 线 程 固 定 地 分 配 给 一 个 内 核 线 程, 因 为 CPU 时 间 片 的 调 度 是 面 向 内 核 线 程 ( 也 就 是 轻 量 级 进 程 ) 的, 因 此 具 有 绑 定 属 性 的 线 程 可 以 保 证 在 需 要 的 时 候 总 有 一 个 内 核 线 程 与 之 对 应 而 与 之 对 应 的 非 绑 定 属 性 就 是 指 用 户 线 程 和 内 核 线 程 的 关 系 不 是 始 终 固 定 的, 而 是 由 系 统 来 控 制 分 配 的 分 离 属 性 分 离 属 性 是 用 来 决 定 一 个 线 程 以 什 么 样 的 方 式 来 终 止 自 己 在 非 分 离 情 况 下, 当 一 个 线 程 结 束 时, 它 所 占 用 的 系 统 资 源 并 没 有 被 释 放, 也 就 是 没 有 真 正 的 终 止 只 有 当 pthread_join() 函 数 返 回 时, 创 建 的 线 程 才 能 释 放 自 己 占 用 的 系 统 资 源 而 在 分 离 属 性 情 况 下, 一 个 线 程 结 束 时 立 即 释 放 它 所 占 有 的 系 统 资 源 这 里 要 注 意 的 一 点 是, 如 果 设 置 一 个 线 程 的 分 离 属 性, 而 这 个 线 程 运 行 又 非 常 快, 那 么 它 很 可 能 在 pthread_create() 函 数 返 回 之 前 就 终 止 了, 它 终 止 以 后 就 可 能 将 线 程 号 和 系 统 资 源 移 交 给 其 他 的 线 程 使 用, 这 时 调 用 pthread_create() 的 线 程 就 得 到 了 错 误 的 线 程 号 这 些 属 性 的 设 置 都 是 通 过 特 定 的 函 数 来 完 成 的, 通 常 首 先 调 用 pthread_attr_init() 函 数 进 行 初 始 化, 之 后 再 调 用 相 应 的 属 性 设 置 函 数, 最 后 调 用 pthread_attr_destroy() 函 数 对 分 配 的 属 性 结 构 指 针 进 行 清 理 和 回 收 设 置 绑 定 属 性 的 函 数 为 pthread_attr_setscope(), 设 置 线 程 分 离 属 性 的 函 数 为 pthread_attr_setdetachstate(), 设 置 线 程 优 先 级 的 相 关 函 数 为 pthread_attr_getschedparam()( 获 取 线 程 优 先 级 ) 和 pthread_attr_setschedparam() ( 设 置 线 程 优 先 级 ) 在 设 置 完 这 些 属 性 后, 就 可 以 调 用 pthread_create() 函 数 来 创 建 线 程 了 (2) 函 数 格 式 表 9.9 列 出 了 pthread_attr_init() 函 数 的 语 法 要 点 表 9.9 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 pthread_attr_init() 函 数 语 法 要 点 #include <pthread.h> int pthread_attr_init(pthread_attr_t *attr) attr: 线 程 属 性 结 构 指 针 成 功 :0 出 错 : 返 回 错 误 码 表 9.10 列 出 了 pthread_attr_setscope() 函 数 的 语 法 要 点 表 9.10 所 需 头 文 件 函 数 原 型 函 数 传 入 值 pthread_attr_setscope() 函 数 语 法 要 点 #include <pthread.h> int pthread_attr_setscope(pthread_attr_t *attr, int scope) attr: 线 程 属 性 结 构 指 针 13
286 scope PTHREAD_SCOPE_SYSTEM: 绑 定 PTHREAD_SCOPE_PROCESS: 非 绑 定 函 数 返 回 值 成 功 :0 出 错 : 1 表 9.11 列 出 了 pthread_attr_setdetachstate() 函 数 的 语 法 要 点 表 9.11 所 需 头 文 件 函 数 原 型 #include <pthread.h> pthread_attr_setdetachstate() 函 数 语 法 要 点 int pthread_attr_setscope(pthread_attr_t *attr, int detachstate) 函 数 传 入 值 attr: 线 程 属 性 detachstate PTHREAD_CREATE_DETACHED: 分 离 PTHREAD _CREATE_JOINABLE: 非 分 离 函 数 返 回 值 成 功 :0 出 错 : 返 回 错 误 码 表 9.12 列 出 了 pthread_attr_getschedparam() 函 数 的 语 法 要 点 表 9.12 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 pthread_attr_getschedparam() 函 数 语 法 要 点 #include <pthread.h> int pthread_attr_getschedparam (pthread_attr_t *attr, struct sched_param *param) attr: 线 程 属 性 结 构 指 针 param: 线 程 优 先 级 成 功 :0 出 错 : 返 回 错 误 码 表 9.13 列 出 了 pthread_attr_setschedparam() 函 数 的 语 法 要 点 表 9.13 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 pthread_attr_setschedparam() 函 数 语 法 要 点 #include <pthread.h> int pthread_attr_setschedparam (pthread_attr_t *attr, struct sched_param *param) attr: 线 程 属 性 结 构 指 针 param: 线 程 优 先 级 成 功 :0 出 错 : 返 回 错 误 码 (3) 使 用 实 例 下 面 的 实 例 是 在 我 们 已 经 很 熟 悉 的 实 例 的 基 础 上 增 加 线 程 属 性 设 置 的 功 能 为 了 避 免 不 必 要 的 复 杂 性, 这 里 就 创 建 一 个 线 程, 这 个 线 程 具 有 绑 定 和 分 离 属 性, 而 且 主 线 程 通 过 一 个 finish_flag 标 志 变 量 来 获 得 线 程 结 束 的 消 息, 而 并 不 调 用 pthread_join() 函 数 /*thread_attr.c*/ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define REPEAT_NUMBER 3 /* 线 程 中 的 小 任 务 数 */ #define DELAY_TIME_LEVELS 10.0 /* 小 任 务 之 间 的 最 大 时 间 间 隔 */ int finish_flag = 0; 14
287 void *thrd_func(void *arg) int delay_time = 0; int count = 0; 专 业 始 于 专 注 卓 识 源 于 远 见 printf("thread is starting\n"); for (count = 0; count < REPEAT_NUMBER; count++) delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tthread : job %d delay = %d\n", count, delay_time); printf("thread finished\n"); finish_flag = 1; pthread_exit(null); int main(void) pthread_t thread; pthread_attr_t attr; int no = 0, res; void * thrd_ret; srand(time(null)); /* 初 始 化 线 程 属 性 对 象 */ res = pthread_attr_init(&attr); if (res!= 0) printf("create attribute failed\n"); exit(res); /* 设 置 线 程 绑 定 属 性 */ res = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); /* 设 置 线 程 分 离 属 性 */ res += pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (res!= 0) printf("setting attribute failed\n"); exit(res); res = pthread_create(&thread, &attr, thrd_func, NULL); if (res!= 0) printf("create thread failed\n"); 15
288 exit(res); /* 释 放 线 程 属 性 对 象 */ pthread_attr_destroy(&attr); printf("create tread success\n"); 专 业 始 于 专 注 卓 识 源 于 远 见 while(!finish_flag) printf("waiting for thread to finish...\n"); sleep(2); return 0; 接 下 来 可 以 在 线 程 运 行 前 后 使 用 free 命 令 查 看 内 存 的 使 用 情 况 以 下 是 运 行 结 果 : $./thread_attr Create tread success Waiting for thread to finish... Thread is starting Waiting for thread to finish... Thread : job 0 delay = 3 Waiting for thread to finish... Thread : job 1 delay = 2 Waiting for thread to finish... Waiting for thread to finish... Waiting for thread to finish... Waiting for thread to finish... Thread : job 2 delay = 9 Thread finished /* 程 序 运 行 之 前 */ $ free total used free shared buffers cached Mem: /+ buffers/cache: Swap: /* 程 序 运 行 之 中 */ $ free total used free shared buffers cached Mem: /+ buffers/cache: Swap: /* 程 序 运 行 之 后 */ $ free total used free shared buffers cached Mem:
289 -/+ buffers/cache: Swap: 可 以 看 到, 线 程 在 运 行 结 束 后 就 收 回 了 系 统 资 源, 并 释 放 内 存 9.3 实 验 内 容 生 产 者 消 费 者 实 验 1. 实 验 目 的 生 产 者 消 费 者 问 题 是 一 个 著 名 的 同 时 性 编 程 问 题 的 集 合 通 过 学 习 经 典 的 生 产 者 消 费 者 问 题 的 实 验, 读 者 可 以 进 一 步 熟 悉 Linux 中 的 多 线 程 编 程, 并 且 掌 握 用 信 号 量 处 理 线 程 间 的 同 步 和 互 斥 问 题 2. 实 验 内 容 生 产 者 消 费 者 问 题 描 述 如 下 有 一 个 有 限 缓 冲 区 和 两 个 线 程 : 生 产 者 和 消 费 者 他 们 分 别 不 停 地 把 产 品 放 入 缓 冲 区 和 从 缓 冲 区 中 拿 走 产 品 一 个 生 产 者 在 缓 冲 区 满 的 时 候 必 须 等 待, 一 个 消 费 者 在 缓 冲 区 空 的 时 候 也 必 须 等 待 另 外, 因 为 缓 冲 区 是 临 界 资 源, 所 以 生 产 者 和 消 费 者 之 间 必 须 互 斥 执 行 它 们 之 间 的 关 系 如 图 9.4 所 示 生 产 者 N 消 费 者 图 9.4 生 产 者 消 费 者 问 题 描 述 这 里 要 求 使 用 有 名 管 道 来 模 拟 有 限 缓 冲 区, 并 且 使 用 信 号 量 来 解 决 生 产 者 消 费 者 问 题 中 的 同 步 和 互 斥 问 题 3. 实 验 步 骤 (1) 信 号 量 的 考 虑 这 里 使 用 3 个 信 号 量, 其 中 两 个 信 号 量 avail 和 full 分 别 用 于 解 决 生 产 者 和 消 费 者 线 程 之 间 的 同 步 问 题,mutex 是 用 于 这 两 个 线 程 之 间 的 互 斥 问 题 其 中 avail 表 示 有 界 缓 冲 区 中 的 空 单 元 数, 初 始 值 为 N;full 表 示 有 界 缓 冲 区 中 非 空 单 元 数, 初 始 值 为 0;mutex 是 互 斥 信 号 量, 初 始 值 为 1 (2) 画 出 流 程 图 本 实 验 流 程 图 如 图 9.5 所 示 17
290 图 9.5 生 产 者 消 费 者 实 验 流 程 图 (3) 编 写 代 码 本 实 验 的 代 码 中 采 用 的 有 界 缓 冲 区 拥 有 3 个 单 元, 每 个 单 元 为 5 个 字 节 为 了 尽 量 体 现 每 个 信 号 量 的 意 义, 在 程 序 中 生 产 过 程 和 消 费 过 程 是 随 机 ( 采 取 0~5s 的 随 机 时 间 间 隔 ) 进 行 的, 而 且 生 产 者 的 速 度 比 消 费 者 的 速 度 平 均 快 两 倍 左 右 ( 这 种 关 系 可 以 相 反 ) 生 产 者 一 次 生 产 一 个 单 元 的 产 品 ( 放 入 hello 字 符 串 ), 消 费 者 一 次 消 费 一 个 单 元 的 产 品 /*producer-customer.c*/ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <pthread.h> #include <errno.h> #include <semaphore.h> #include <sys/ipc.h> #define MYFIFO "myfifo" /* 缓 冲 区 有 名 管 道 的 名 字 */ #define BUFFER_SIZE 3 /* 缓 冲 区 的 单 元 数 */ #define UNIT_SIZE 5 /* 每 个 单 元 的 大 小 */ #define RUN_TIME 30 /* 运 行 时 间 */ #define DELAY_TIME_LEVELS 5.0 /* 周 期 的 最 大 值 */ int fd; time_t end_time; sem_t mutex, full, avail; /* 3 个 信 号 量 */ /* 生 产 者 线 程 */ void *producer(void *arg) int real_write; 18
291 int delay_time = 0; 专 业 始 于 专 注 卓 识 源 于 远 见 while(time(null) < end_time) delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX) / 2.0) + 1; sleep(delay_time); /*P 操 作 信 号 量 avail 和 mutex*/ sem_wait(&avail); sem_wait(&mutex); printf("\nproducer: delay = %d\n", delay_time); /* 生 产 者 写 入 数 据 */ if ((real_write = write(fd, "hello", UNIT_SIZE)) == -1) if(errno == EAGAIN) printf("the FIFO has not been read yet.please try later\n"); else printf("write %d to the FIFO\n", real_write); /*V 操 作 信 号 量 full 和 mutex*/ sem_post(&full); sem_post(&mutex); pthread_exit(null); /* 消 费 者 线 程 */ void *customer(void *arg) unsigned char read_buffer[unit_size]; int real_read; int delay_time; while(time(null) < end_time) delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); /*P 操 作 信 号 量 full 和 mutex*/ sem_wait(&full); sem_wait(&mutex); memset(read_buffer, 0, UNIT_SIZE); printf("\ncustomer: delay = %d\n", delay_time); 19
292 if ((real_read = read(fd, read_buffer, UNIT_SIZE)) == -1) if (errno == EAGAIN) printf("no data yet\n"); printf("read %s from FIFO\n", read_buffer); /*V 操 作 信 号 量 avail 和 mutex*/ sem_post(&avail); sem_post(&mutex); pthread_exit(null); 专 业 始 于 专 注 卓 识 源 于 远 见 int main() pthread_t thrd_prd_id,thrd_cst_id; pthread_t mon_th_id; int ret; srand(time(null)); end_time = time(null) + RUN_TIME; /* 创 建 有 名 管 道 */ if((mkfifo(myfifo, O_CREAT O_EXCL) < 0) && (errno!= EEXIST)) printf("cannot create fifo\n"); return errno; /* 打 开 管 道 */ fd = open(myfifo, O_RDWR); if (fd == -1) printf("open fifo error\n"); return fd; /* 初 始 化 互 斥 信 号 量 为 1*/ ret = sem_init(&mutex, 0, 1); /* 初 始 化 avail 信 号 量 为 N*/ ret += sem_init(&avail, 0, BUFFER_SIZE); /* 初 始 化 full 信 号 量 为 0*/ ret += sem_init(&full, 0, 0); if (ret!= 0) printf("any semaphore initialization failed\n"); return ret; 20
293 /* 创 建 两 个 线 程 */ ret = pthread_create(&thrd_prd_id, NULL, producer, NULL); if (ret!= 0) printf("create producer thread error\n"); return ret; ret = pthread_create(&thrd_cst_id, NULL, customer, NULL); if(ret!= 0) printf("create customer thread error\n"); return ret; pthread_join(thrd_prd_id, NULL); pthread_join(thrd_cst_id, NULL); close(fd); unlink(myfifo); return 0; 专 业 始 于 专 注 卓 识 源 于 远 见 4. 实 验 结 果 运 行 该 程 序, 得 到 如 下 结 果 : $./producer_customer Producer: delay = 3 Write 5 to the FIFO Customer: delay = 3 Read hello from FIFO Producer: delay = 1 Write 5 to the FIFO Producer: delay = 2 Write 5 to the FIFO Customer: delay = 4 Read hello from FIFO Customer: delay = 1 Read hello from FIFO Producer: delay = 2 Write 5 to the FIFO 21
294 9.4 本 章 小 结 本 章 首 先 介 绍 了 线 程 的 基 本 概 念 线 程 的 分 类 和 特 性 以 及 线 程 的 发 展 历 程 接 下 来 讲 解 了 Linux 中 线 程 库 的 基 本 操 作 函 数, 包 括 线 程 的 创 建 退 出 和 取 消 等, 通 过 实 例 程 序 给 出 了 比 较 典 型 的 线 程 编 程 框 架 再 接 下 来, 本 章 讲 解 了 线 程 的 控 制 操 作 在 线 程 的 操 作 中 必 须 实 现 线 程 间 的 同 步 和 互 斥, 其 中 包 括 互 斥 锁 线 程 控 制 和 信 号 量 线 程 控 制 后 面 还 简 单 描 述 了 线 程 属 性 相 关 概 念 相 关 函 数 以 及 比 较 简 单 的 典 型 实 例 最 后, 本 章 的 实 验 是 一 个 经 典 的 生 产 者 消 费 者 问 题, 可 以 使 用 线 程 机 制 很 好 地 实 现, 希 望 读 者 能 够 认 真 地 编 程 实 验, 进 一 步 理 解 多 线 程 的 同 步 和 互 斥 操 作 9.5 思 考 与 练 习 1. 通 过 查 找 资 料, 查 看 主 流 的 嵌 入 式 操 作 系 统 ( 如 嵌 入 式 Linux Vxworks 等 ) 是 如 何 处 理 多 线 程 操 作 的 2. 通 过 线 程 实 现 串 口 通 信 3. 通 过 线 程 和 网 络 编 程 实 现 网 上 聊 天 程 序 联 系 方 式 集 团 官 网 : 嵌 入 式 学 院 : 移 动 互 联 网 学 院 : 企 业 学 院 : 物 联 网 学 院 : 研 发 中 心 :dev.hqyj.com 集 团 总 部 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 内 华 清 远 见 教 育 集 团 北 京 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 区, 电 话 : /5 上 海 地 址 : 上 海 市 徐 汇 区 漕 溪 路 银 海 大 厦 A 座 8 层, 电 话 : 深 圳 地 址 : 深 圳 市 龙 华 新 区 人 民 北 路 美 丽 AAA 大 厦 15 层, 电 话 : 成 都 地 址 : 成 都 市 武 侯 区 科 华 北 路 99 号 科 华 大 厦 6 层, 电 话 : 南 京 地 址 : 南 京 市 白 下 区 汉 中 路 185 号 鸿 运 大 厦 10 层, 电 话 : 武 汉 地 址 : 武 汉 市 工 程 大 学 卓 刀 泉 校 区 科 技 孵 化 器 大 楼 8 层, 电 话 : 西 安 地 址 : 西 安 市 高 新 区 高 新 一 路 12 号 创 业 大 厦 D3 楼 5 层, 电 话 :
295 嵌 入 式 LINUX 应 用 程 序 开 发 标 准 教 程 作 者 : 华 清 远 见 第 10 章 嵌 入 式 Linux 网 络 编 程 Linux 掌 握 TCP/IP 协 议 的 基 础 知 识 掌 握 嵌 入 式 Linux 基 础 网 络 编 程 掌 握 嵌 入 式 Linux 高 级 网 络 编 程 分 析 理 解 Ping 源 代 码 能 够 独 立 编 写 客 户 端 服 务 器 端 的 通 信 程 序 能 够 独 立 编 写 NTP 协 议 实 现 程 序
296 10.1 TCP/IP 协 议 概 述 OSI 参 考 模 型 及 TCP/IP 参 考 模 型 读 者 一 定 都 听 说 过 著 名 的 OSI 协 议 参 考 模 型, 它 是 基 于 国 际 标 准 化 组 织 (ISO) 的 建 议 发 展 起 来 的, 从 上 到 下 共 分 为 7 层 : 应 用 层 表 示 层 会 话 层 传 输 层 网 络 层 数 据 链 路 层 及 物 理 层 这 个 7 层 的 协 议 模 型 虽 然 规 定 得 非 常 细 致 和 完 善, 但 在 实 际 中 却 得 不 到 广 泛 的 应 用, 其 重 要 的 原 因 之 一 就 在 于 它 过 于 复 杂 但 它 仍 是 此 后 很 多 协 议 模 型 的 基 础, 这 种 分 层 架 构 的 思 想 在 很 多 领 域 都 得 到 了 广 泛 的 应 用 与 此 相 区 别 的 TCP/IP 协 议 模 型 从 一 开 始 就 遵 循 简 单 明 确 的 设 计 思 路, 它 将 TCP/IP 的 7 层 协 议 模 型 简 化 为 4 层, 从 而 更 有 利 于 实 现 和 使 用 TCP/IP 的 协 议 参 考 模 型 和 OSI 协 议 参 考 模 型 的 对 应 关 系 如 图 10.1 所 示 图 10.1 OSI 模 型 和 TCP/IP 参 考 模 型 对 应 关 系 下 面 分 别 对 TCP/IP 的 4 层 模 型 进 行 简 要 介 绍 网 络 接 口 层 : 负 责 将 二 进 制 流 转 换 为 数 据 帧, 并 进 行 数 据 帧 的 发 送 和 接 收 要 注 意 的 是 数 据 帧 是 独 立 的 网 络 信 息 传 输 单 元 网 络 层 : 负 责 将 数 据 帧 封 装 成 IP 数 据 包, 并 运 行 必 要 的 路 由 算 法 传 输 层 : 负 责 端 对 端 之 间 的 通 信 会 话 连 接 与 建 立 传 输 协 议 的 选 择 根 据 数 据 传 输 方 式 而 定 应 用 层 : 负 责 应 用 程 序 的 网 络 访 问, 这 里 通 过 端 口 号 来 识 别 各 个 不 同 的 进 程 TCP/IP 协 议 族 虽 然 TCP/IP 名 称 只 包 含 了 两 个 协 议, 但 实 际 上,TCP/IP 是 一 个 庞 大 的 协 议 族, 它 包 括 了 各 个 层 次 上 的 众 多 协 议, 图 10.2 列 举 了 各 层 中 一 些 重 要 的 协 议, 并 给 出 了 各 个 协 议 在 不 同 层 次 中 所 处 的 位 置, 如 下 所 示 ARP: 用 于 获 得 同 一 物 理 网 络 中 的 硬 件 主 机 地 址 MPLS: 多 协 议 标 签 协 议, 是 很 有 发 展 前 景 的 下 一 代 网 络 协 议 IP: 负 责 在 主 机 和 网 络 之 间 寻 址 和 路 由 数 据 包 ICMP: 用 于 发 送 有 关 数 据 包 的 传 送 错 误 的 协 议 IGMP: 被 IP 主 机 用 来 向 本 地 多 路 广 播 路 由 器 报 告 主 机 组 成 员 的 协 议 TCP: 为 应 用 程 序 提 供 可 靠 的 通 信 连 接 适 合 于 MPLS 一 次 传 输 大 批 数 据 的 情 况 并 适 用 于 要 求 得 到 响 应 的 应 用 程 序 UDP: 提 供 了 无 连 接 通 信, 且 不 对 传 送 包 进 行 可 图 10.2 TCP/IP 协 议 族 靠 性 保 证 适 合 于 一 次 传 输 少 量 数 据, 可 靠 性 则 由 应 用 层 来 负 责 TCP 和 UDP 在 此 主 要 介 绍 在 网 络 编 程 中 涉 及 的 传 输 层 TCP 和 UDP 协 议 telnet ARP RARP ftp 应 用 层 传 输 层 TCP UDP ICMP IGMP 网 络 层 IPv4 IPv6 网 络 接 口 层 1.TCP (1) 概 述 2
297 同 其 他 任 何 协 议 栈 一 样,TCP 向 相 邻 的 高 层 提 供 服 务 因 为 TCP 的 上 一 层 就 是 应 用 层, 因 此,TCP 数 据 传 输 实 现 了 从 一 个 应 用 程 序 到 另 一 个 应 用 程 序 的 数 据 传 递 应 用 程 序 通 过 编 程 调 用 TCP 并 使 用 TCP 服 务, 提 供 需 要 准 备 发 送 的 数 据, 用 来 区 分 接 收 数 据 应 用 的 目 的 地 址 和 端 口 号 通 常 应 用 程 序 通 过 打 开 一 个 socket 来 使 用 TCP 服 务,TCP 管 理 到 其 他 socket 的 数 据 传 递 可 以 说, 通 过 IP 的 源 / 目 的 可 以 惟 一 地 区 分 网 络 中 两 个 设 备 的 连 接, 通 过 socket 的 源 / 目 的 可 以 惟 一 地 区 分 网 络 中 两 个 应 用 程 序 的 连 接 (2) 三 次 握 手 协 议 TCP 对 话 通 过 三 次 握 手 来 进 行 初 始 化 三 次 握 手 的 目 的 是 使 数 据 段 的 发 送 和 接 收 同 步, 告 诉 其 他 主 机 其 一 次 可 接 收 的 数 据 量, 并 建 立 虚 连 接 下 面 描 述 了 这 三 次 握 手 的 简 单 过 程 初 始 化 主 机 通 过 一 个 同 步 标 志 置 位 的 数 据 段 发 出 会 话 请 求 接 收 主 机 通 过 发 回 具 有 以 下 项 目 的 数 据 段 表 示 回 复 : 同 步 标 志 置 位 即 将 发 送 的 数 据 段 的 起 始 字 节 的 顺 序 号 应 答 并 带 有 将 收 到 的 下 一 个 数 据 段 的 字 节 顺 序 号 请 求 主 机 再 回 送 一 个 数 据 段, 并 带 有 确 认 顺 序 号 和 确 认 号 图 10.3 就 是 这 个 流 程 的 简 单 示 意 图 SYN J SYN K, ACK J+1 ACK K+1 图 10.3 TCP 三 次 握 手 协 议 TCP 实 体 所 采 用 的 基 本 协 议 是 滑 动 窗 口 协 议 当 发 送 方 传 送 一 个 数 据 报 时, 它 将 启 动 计 时 器 当 该 数 据 报 到 达 目 的 地 后, 接 收 方 的 TCP 实 体 往 回 发 送 一 个 数 据 报, 其 中 包 含 有 一 个 确 认 序 号, 它 表 示 希 望 收 到 的 下 一 个 数 据 包 的 顺 序 号 如 果 发 送 方 的 定 时 器 在 确 认 信 息 到 达 之 前 超 时, 那 么 发 送 方 会 重 发 该 数 据 包 (3)TCP 数 据 包 头 图 10.4 给 出 了 TCP 数 据 包 头 的 格 式 TCP 数 据 包 头 的 含 义 如 下 所 示 源 端 口 目 的 端 口 :16 位 长 标 识 出 远 端 和 本 地 的 端 口 号 图 10.4 TCP 数 据 包 头 的 格 式 序 号 :32 位 长 标 识 发 送 的 数 据 报 的 顺 序 确 认 号 :32 位 长 希 望 收 到 的 下 一 个 数 据 包 的 序 列 号 TCP 头 长 :4 位 长 表 明 TCP 头 中 包 含 多 少 个 32 位 字 6 位 未 用 ACK:ACK 位 置 1 表 明 确 认 号 是 合 法 的 如 果 ACK 为 0, 那 么 数 据 报 不 包 含 确 认 信 息, 确 认 字 段 被 省 略 PSH: 表 示 是 带 有 PUSH 标 志 的 数 据 接 收 方 因 此 请 求 数 据 包 一 到 便 将 其 送 往 应 用 程 序 而 不 必 等 到 缓 冲 区 装 满 时 才 传 送 RST: 用 于 复 位 由 于 主 机 崩 溃 或 其 他 原 因 而 出 现 的 错 误 连 接 还 可 以 用 于 拒 绝 非 法 的 数 据 包 或 拒 3
298 绝 连 接 请 求 SYN: 用 于 建 立 连 接 FIN: 用 于 释 放 连 接 窗 口 大 小 :16 位 长 窗 口 大 小 字 段 表 示 在 确 认 了 字 节 之 后 还 可 以 发 送 多 少 个 字 节 校 验 和 :16 位 长 是 为 了 确 保 高 可 靠 性 而 设 置 的 它 校 验 头 部 数 据 和 伪 TCP 头 部 之 和 可 选 项 :0 个 或 多 个 32 位 字 包 括 最 大 TCP 载 荷, 滑 动 窗 口 比 例 以 及 选 择 重 发 数 据 包 等 选 项 2.UDP (1) 概 述 UDP 即 用 户 数 据 报 协 议, 它 是 一 种 无 连 接 协 议, 因 此 不 需 要 像 TCP 那 样 通 过 三 次 握 手 来 建 立 一 个 连 接 同 时, 一 个 UDP 应 用 可 同 时 作 为 应 用 的 客 户 或 服 务 器 方 由 于 UDP 协 议 并 不 需 要 建 立 一 个 明 确 的 连 接, 因 此 建 立 UDP 应 用 要 比 建 立 TCP 应 用 简 单 得 多 UDP 协 议 从 问 世 至 今 已 经 被 使 用 了 很 多 年, 虽 然 其 最 初 的 光 彩 已 经 被 一 些 类 似 协 议 所 掩 盖, 但 是 在 网 络 质 量 越 来 越 高 的 今 天,UDP 的 应 用 得 到 了 大 大 的 增 强 它 比 TCP 协 议 更 为 高 效, 也 能 更 好 地 解 决 实 时 性 的 问 题 如 今, 包 括 网 络 视 频 会 议 系 统 在 内 的 众 多 的 客 户 / 服 务 器 模 式 的 网 络 应 用 都 使 用 UDP 协 议 (2)UDP 数 据 报 头 UDP 数 据 报 头 如 下 图 10.5 所 示 源 地 址 目 的 地 址 :16 位 长 标 识 出 远 端 和 本 地 的 端 口 号 数 据 报 的 长 度 是 指 包 括 报 头 和 数 据 部 分 在 内 的 总 的 字 节 数 因 为 报 头 的 长 度 是 固 定 的, 所 以 该 域 主 要 用 来 计 算 可 变 长 度 的 数 据 部 分 ( 又 称 为 数 据 负 载 ) 图 10.5 UDP 数 据 报 头 3. 协 议 的 选 择 协 议 的 选 择 应 该 考 虑 到 以 下 3 个 方 面 (1) 对 数 据 可 靠 性 的 要 求 对 数 据 要 求 高 可 靠 性 的 应 用 需 选 择 TCP 协 议, 如 验 证 密 码 字 段 的 传 送 都 是 不 允 许 出 错 的, 而 对 数 据 的 可 靠 性 要 求 不 那 么 高 的 应 用 可 选 择 UDP 传 送 (2) 应 用 的 实 时 性 TCP 协 议 在 传 送 过 程 中 要 使 用 三 次 握 手 重 传 确 认 等 手 段 来 保 证 数 据 传 输 的 可 靠 性 使 用 TCP 协 议 会 有 较 大 的 时 延, 因 此 不 适 合 对 实 时 性 要 求 较 高 的 应 用, 如 VOIP 视 频 监 控 等 相 反,UDP 协 议 则 在 这 些 应 用 中 能 发 挥 很 好 的 作 用 (3) 网 络 的 可 靠 性 由 于 TCP 协 议 的 提 出 主 要 是 解 决 网 络 的 可 靠 性 问 题, 它 通 过 各 种 机 制 来 减 少 错 误 发 生 的 概 率 因 此, 在 网 络 状 况 不 是 很 好 的 情 况 下 需 选 用 TCP 协 议 ( 如 在 广 域 网 等 情 况 ), 但 是 若 在 网 络 状 况 很 好 的 情 况 下 ( 如 局 域 网 等 ) 就 不 需 要 再 采 用 TCP 协 议, 而 建 议 选 择 UDP 协 议 来 减 少 网 络 负 荷 4
299 10.2 网 络 基 础 编 程 socket 概 述 1.socket 定 义 在 Linux 中 的 网 络 编 程 是 通 过 socket 接 口 来 进 行 的 人 们 常 说 的 socket 是 一 种 特 殊 的 I/O 接 口, 它 也 是 一 种 文 件 描 述 符 socket 是 一 种 常 用 的 进 程 之 间 通 信 机 制, 通 过 它 不 仅 能 实 现 本 地 机 器 上 的 进 程 之 间 的 通 信, 而 且 通 过 网 络 能 够 在 不 同 机 器 上 的 进 程 之 间 进 行 通 信 每 一 个 socket 都 用 一 个 半 相 关 描 述 协 议 本 地 地 址 本 地 端 口 来 表 示 ; 一 个 完 整 的 套 接 字 则 用 一 个 相 关 描 述 协 议 本 地 地 址 本 地 端 口 远 程 地 址 远 程 端 口 来 表 示 socket 也 有 一 个 类 似 于 打 开 文 件 的 函 数 调 用, 该 函 数 返 回 一 个 整 型 的 socket 描 述 符, 随 后 的 连 接 建 立 数 据 传 输 等 操 作 都 是 通 过 socket 来 实 现 的 2.socket 类 型 常 见 的 socket 有 3 种 类 型 如 下 (1) 流 式 socket(sock_stream) 流 式 套 接 字 提 供 可 靠 的 面 向 连 接 的 通 信 流 ; 它 使 用 TCP 协 议, 从 而 保 证 了 数 据 传 输 的 正 确 性 和 顺 序 性 (2) 数 据 报 socket(sock_dgram) 数 据 报 套 接 字 定 义 了 一 种 无 连 接 的 服 务, 数 据 通 过 相 互 独 立 的 报 文 进 行 传 输, 是 无 序 的, 并 且 不 保 证 是 可 靠 无 差 错 的 它 使 用 数 据 报 协 议 UDP (3) 原 始 socket 原 始 套 接 字 允 许 对 底 层 协 议 如 IP 或 ICMP 进 行 直 接 访 问, 它 功 能 强 大 但 使 用 较 为 不 便, 主 要 用 于 一 些 协 议 的 开 发 地 址 及 顺 序 处 理 1. 地 址 结 构 相 关 处 理 (1) 数 据 结 构 介 绍 下 面 首 先 介 绍 两 个 重 要 的 数 据 类 型 :sockaddr 和 sockaddr_in, 这 两 个 结 构 类 型 都 是 用 来 保 存 socket 信 息 的, 如 下 所 示 : struct sockaddr unsigned short sa_family; /* 地 址 族 */ char sa_data[14]; /*14 字 节 的 协 议 地 址, 包 含 该 socket 的 IP 地 址 和 端 口 号 */ ; struct sockaddr_in short int sa_family; /* 地 址 族 */ unsigned short int sin_port; /* 端 口 号 */ struct in_addr sin_addr; /*IP 地 址 */ unsigned char sin_zero[8]; /* 填 充 0 以 保 持 与 struct sockaddr 同 样 大 小 */ 5
300 ; 这 两 个 数 据 类 型 是 等 效 的, 可 以 相 互 转 化, 通 常 sockaddr_in 数 据 类 型 使 用 更 为 方 便 在 建 立 socketadd 或 sockaddr_in 后, 就 可 以 对 该 socket 进 行 适 当 的 操 作 了 (2) 结 构 字 段 表 10.1 列 出 了 该 结 构 sa_family 字 段 可 选 的 常 见 值 表 10.1 结 构 定 义 头 文 件 #include <netinet/in.h> AF_INET:IPv4 协 议 AF_INET6:IPv6 协 议 sa_family AF_LOCAL:UNIX 域 协 议 AF_LINK: 链 路 地 址 协 议 AF_KEY: 密 钥 套 接 字 (socket) sockaddr_in 其 他 字 段 的 含 义 非 常 清 楚, 具 体 的 设 置 涉 及 其 他 函 数, 在 后 面 会 有 详 细 的 讲 解 2. 数 据 存 储 优 先 顺 序 (1) 函 数 说 明 计 算 机 数 据 存 储 有 两 种 字 节 优 先 顺 序 : 高 位 字 节 优 先 ( 称 为 大 端 模 式 ) 和 低 位 字 节 优 先 ( 称 为 小 端 模 式, PC 机 通 常 采 用 小 端 模 式 ) Internet 上 数 据 以 高 位 字 节 优 先 顺 序 在 网 络 上 传 输, 因 此 在 有 些 情 况 下, 需 要 对 这 两 个 字 节 存 储 优 先 顺 序 进 行 相 互 转 化 这 里 用 到 了 4 个 函 数 :htons() ntohs() htonl() 和 ntohl() 这 4 个 地 址 分 别 实 现 网 络 字 节 序 和 主 机 字 节 序 的 转 化, 这 里 的 h 代 表 host,n 代 表 network,s 代 表 short,l 代 表 long 通 常 16 位 的 IP 端 口 号 用 s 代 表, 而 IP 地 址 用 l 来 代 表 (2) 函 数 格 式 说 明 表 10.2 列 出 了 这 4 个 函 数 的 语 法 格 式 表 10.2 所 需 头 文 件 函 数 原 型 函 数 传 入 值 htons 等 函 数 语 法 要 点 #include <netinet/in.h> uint16_t htons(unit16_t host16bit) uint32_t htonl(unit32_t host32bit) uint16_t ntohs(unit16_t net16bit) uint32_t ntohs(unit32_t net32bit) host16bit: 主 机 字 节 序 的 16 位 数 据 host32bit: 主 机 字 节 序 的 32 位 数 据 net16bit: 网 络 字 节 序 的 16 位 数 据 net32bit: 网 络 字 节 序 的 32 位 数 据 函 数 返 回 值 成 功 : 返 回 要 转 换 的 字 节 序 出 错 : 1 调 用 该 函 数 只 是 使 其 得 到 相 应 的 字 节 序, 用 户 不 需 清 楚 该 系 统 的 主 机 字 节 序 和 网 络 字 节 序 是 否 真 正 相 等 如 果 是 相 同 不 需 要 转 换 的 话, 该 系 统 的 这 些 函 数 会 定 义 成 空 宏 6
301 3. 地 址 格 式 转 化 (1) 函 数 说 明 通 常 用 户 在 表 达 地 址 时 采 用 的 是 点 分 十 进 制 表 示 的 数 值 ( 或 者 是 以 冒 号 分 开 的 十 进 制 IPv6 地 址 ), 而 在 通 常 使 用 的 socket 编 程 中 所 使 用 的 则 是 二 进 制 值, 这 就 需 要 将 这 两 个 数 值 进 行 转 换 这 里 在 IPv4 中 用 到 的 函 数 有 inet_aton() inet_addr() 和 inet_ntoa(), 而 IPv4 和 IPv6 兼 容 的 函 数 有 inet_pton() 和 inet_ntop() 由 于 IPv6 是 下 一 代 互 联 网 的 标 准 协 议, 因 此, 本 书 讲 解 的 函 数 都 能 够 同 时 兼 容 IPv4 和 IPv6, 但 在 具 体 举 例 时 仍 以 IPv4 为 例 这 里 inet_pton() 函 数 是 将 点 分 十 进 制 地 址 映 射 为 二 进 制 地 址, 而 inet_ntop() 是 将 二 进 制 地 址 映 射 为 点 分 十 进 制 地 址 (2) 函 数 格 式 表 10.3 列 出 了 inet_pton 函 数 的 语 法 要 点 表 10.3 所 需 头 文 件 函 数 原 型 函 数 传 入 值 inet_pton 函 数 语 法 要 点 #include <arpa/inet.h> int inet_pton(int family, const char *strptr, void *addrptr) AF_INET:IPv4 协 议 family AF_INET6:IPv6 协 议 strptr: 要 转 化 的 值 addrptr: 转 化 后 的 地 址 函 数 返 回 值 成 功 :0 出 错 : 1 表 10.4 列 出 了 inet_ntop 函 数 的 语 法 要 点 表 10.4 所 需 头 文 件 函 数 原 型 #include <arpa/inet.h> inet_ntop 函 数 语 法 要 点 int inet_ntop(int family, void *addrptr, char *strptr, size_t len) 函 数 传 入 值 family AF_INET:IPv4 协 议 AF_INET6:IPv6 协 议 addrptr: 转 化 后 的 地 址 函 数 传 入 值 strptr: 要 转 化 的 值 len: 转 化 后 值 的 大 小 函 数 返 回 值 成 功 :0 出 错 : 1 4. 名 字 地 址 转 化 (1) 函 数 说 明 通 常, 人 们 在 使 用 过 程 中 都 不 愿 意 记 忆 冗 长 的 IP 地 址, 尤 其 到 IPv6 时, 地 址 长 度 多 达 128 位, 那 时 就 更 加 不 可 能 一 次 次 记 忆 那 么 长 的 IP 地 址 了 因 此, 使 用 主 机 名 将 会 是 很 好 的 选 择 在 Linux 中, 同 样 有 一 些 函 数 可 以 实 现 主 机 名 和 地 址 的 转 化, 最 为 常 见 的 有 gethostbyname() gethostbyaddr() 和 getaddrinfo() 等, 它 们 都 可 以 实 现 IPv4 和 IPv6 的 地 址 和 主 机 名 之 间 的 转 化 其 中 gethostbyname() 是 将 主 机 名 转 化 为 IP 地 址, gethostbyaddr() 则 是 逆 操 作, 是 将 IP 地 址 转 化 为 主 机 名, 另 外 getaddrinfo() 还 能 实 现 自 动 识 别 IPv4 地 址 和 IPv6 7
302 地 址 gethostbyname() 和 gethostbyaddr() 都 涉 及 一 个 hostent 的 结 构 体, 如 下 所 示 : struct hostent char *h_name;/* 正 式 主 机 名 */ char **h_aliases;/* 主 机 别 名 */ int h_addrtype;/* 地 址 类 型 */ int h_length;/* 地 址 字 节 长 度 */ char **h_addr_list;/* 指 向 IPv4 或 IPv6 的 地 址 指 针 数 组 */ 调 用 gethostbyname() 函 数 或 gethostbyaddr() 函 数 后 就 能 返 回 hostent 结 构 体 的 相 关 信 息 getaddrinfo() 函 数 涉 及 一 个 addrinfo 的 结 构 体, 如 下 所 示 : struct addrinfo int ai_flags;/*ai_passive, AI_CANONNAME;*/ int ai_family;/* 地 址 族 */ int ai_socktype;/*socket 类 型 */ int ai_protocol;/* 协 议 类 型 */ size_t ai_addrlen;/* 地 址 字 节 长 度 */ char *ai_canonname;/* 主 机 名 */ struct sockaddr *ai_addr;/*socket 结 构 体 */ struct addrinfo *ai_next;/* 下 一 个 指 针 链 表 */ hostent 结 构 体 而 言,addrinfo 结 构 体 包 含 更 多 的 信 息 (2) 函 数 格 式 表 10.5 列 出 了 gethostbyname() 函 数 的 语 法 要 点 表 10.5 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 gethostbyname 函 数 语 法 要 点 #include <netdb.h> struct hostent *gethostbyname(const char *hostname) hostname: 主 机 名 成 功 :hostent 类 型 指 针 出 错 : 1 调 用 该 函 数 时 可 以 首 先 对 hostent 结 构 体 中 的 h_addrtype 和 h_length 进 行 设 置, 若 为 IPv4 可 设 置 为 AF_INET 和 4; 若 为 IPv6 可 设 置 为 AF_INET6 和 16; 若 不 设 置 则 默 认 为 IPv4 地 址 类 型 表 10.6 列 出 了 getaddrinfo() 函 数 的 语 法 要 点 表 10.6 所 需 头 文 件 函 数 原 型 #include <netdb.h> getaddrinfo() 函 数 语 法 要 点 int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **result) node: 网 络 地 址 或 者 网 络 主 机 名 函 数 传 入 值 service: 服 务 名 或 十 进 制 的 端 口 号 字 符 串 hints: 服 务 线 索 result: 返 回 结 果 8
303 函 数 返 回 值 成 功 :0 出 错 : 1 在 调 用 之 前, 首 先 要 对 hints 服 务 线 索 进 行 设 置 它 是 一 个 addrinfo 结 构 体, 表 10.7 列 举 了 该 结 构 体 常 见 的 选 项 值 表 10.7 addrinfo 结 构 体 常 见 选 项 值 结 构 体 头 文 件 ai_flags #include <netdb.h> AI_PASSIVE: 该 套 接 口 是 用 作 被 动 地 打 开 AI_CANONNAME: 通 知 getaddrinfo 函 数 返 回 主 机 的 名 字 AF_INET:IPv4 协 议 ai_family AF_INET6:IPv6 协 议 AF_UNSPEC:IPv4 或 IPv6 均 可 ai_socktype SOCK_STREAM: 字 节 流 套 接 字 socket(tcp) SOCK_DGRAM: 数 据 报 套 接 字 socket(udp) IPPROTO_IP:IP 协 议 IPPROTO_IPV4:IPv4 协 议 4 IPv4 ai_protocol IPPROTO_IPV6:IPv6 协 议 IPPROTO_UDP:UDP IPPROTO_TCP:TCP (1) 通 常 服 务 器 端 在 调 用 getaddrinfo() 之 前,ai_flags 设 置 AI_PASSIVE, 用 于 bind() 函 数 ( 用 于 端 口 和 地 址 的 绑 定, 后 面 会 讲 到 ), 主 机 名 nodename 通 常 会 设 置 为 NULL (2) 客 户 端 调 用 getaddrinfo() 时,ai_flags 一 般 不 设 置 AI_PASSIVE, 但 是 主 机 名 nodename 和 服 务 名 servname( 端 口 ) 则 应 该 不 为 空 (3) 即 使 不 设 置 ai_flags 为 AI_PASSIVE, 取 出 的 地 址 也 可 以 被 绑 定, 很 多 程 序 中 ai_flags 直 接 设 置 为 0, 即 3 个 标 志 位 都 不 设 置, 这 种 情 况 下 只 要 hostname 和 servname 设 置 的 没 有 问 题 就 可 以 正 确 绑 定 (3) 使 用 实 例 下 面 的 实 例 给 出 了 getaddrinfo 函 数 用 法 的 示 例, 在 后 面 小 节 中 会 给 出 gethostbyname 函 数 用 法 的 例 子 /* getaddrinfo.c */ #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> int main() struct addrinfo hints, *res = NULL; int rc; memset(&hints, 0, sizeof(hints)); 9
304 /* 设 置 addrinfo 结 构 体 中 各 参 数 */ hints.ai_flags = AI_CANONNAME; hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; /* 调 用 getaddinfo 函 数 */ rc = getaddrinfo("localhost", NULL, &hints, &res); if (rc!= 0) perror("getaddrinfo"); exit(1); else printf("host name is %s\n", res->ai_canonname); exit(0); socket 基 础 编 程 (1) 函 数 说 明 socket 编 程 的 基 本 函 数 有 socket() bind() listen() accept() send() sendto() recv() 以 及 recvfrom() 等, 其 中 根 据 客 户 端 还 是 服 务 端, 或 者 根 据 使 用 TCP 协 议 还 是 UDP 协 议, 这 些 函 数 的 调 用 流 程 都 有 所 区 别, 这 里 先 对 每 个 函 数 进 行 说 明, 再 给 出 各 种 情 况 下 使 用 的 流 程 图 socket(): 该 函 数 用 于 建 立 一 个 socket 连 接, 可 指 定 socket 类 型 等 信 息 在 建 立 了 socket 连 接 之 后, 可 对 sockaddr 或 sockaddr_in 结 构 进 行 初 始 化, 以 保 存 所 建 立 的 socket 地 址 信 息 bind(): 该 函 数 是 用 于 将 本 地 IP 地 址 绑 定 到 端 口 号, 若 绑 定 其 他 IP 地 址 则 不 能 成 功 另 外, 它 主 要 用 于 TCP 的 连 接, 而 在 UDP 的 连 接 中 则 无 必 要 listen(): 在 服 务 端 程 序 成 功 建 立 套 接 字 和 与 地 址 进 行 绑 定 之 后, 还 需 要 准 备 在 该 套 接 字 上 接 收 新 的 连 接 请 求 此 时 调 用 listen() 函 数 来 创 建 一 个 等 待 队 列, 在 其 中 存 放 未 处 理 的 客 户 端 连 接 请 求 accept(): 服 务 端 程 序 调 用 listen() 函 数 创 建 等 待 队 列 之 后, 调 用 accept() 函 数 等 待 并 接 收 客 户 端 的 连 接 请 求 它 通 常 从 由 bind() 所 创 建 的 等 待 队 列 中 取 出 第 一 个 未 处 理 的 连 接 请 求 connect(): 该 函 数 在 TCP 中 是 用 于 bind() 的 之 后 的 client 端, 用 于 与 服 务 器 端 建 立 连 接, 而 在 UDP 中 由 于 没 有 了 bind() 函 数, 因 此 用 connect() 有 点 类 似 bind() 函 数 的 作 用 send() 和 recv(): 这 两 个 函 数 分 别 用 于 发 送 和 接 收 数 据, 可 以 用 在 TCP 中, 也 可 以 用 在 UDP 中 当 用 在 UDP 时, 可 以 在 connect() 函 数 建 立 连 接 之 后 再 用 sendto() 和 recvfrom(): 这 两 个 函 数 的 作 用 与 send() 和 recv() 函 数 类 似, 也 可 以 用 在 TCP 和 UDP 中 当 用 在 TCP 时, 后 面 的 几 个 与 地 址 有 关 参 数 不 起 作 用, 函 数 作 用 等 同 于 send() 和 recv(); 当 用 在 UDP 时, 可 以 用 在 之 前 没 有 使 用 connect() 的 情 况 下, 这 两 个 函 数 可 以 自 动 寻 找 指 定 地 址 并 进 行 连 接 服 务 器 端 和 客 户 端 使 用 TCP 协 议 的 流 程 如 图 10.6 所 示 服 务 器 端 和 客 户 端 使 用 UDP 协 议 的 流 程 如 图 10.7 所 示 10
305 图 10.6 使 用 TCP 协 议 socket 编 程 流 程 图 图 10.7 使 用 UDP 协 议 socket 编 程 流 程 图 (2) 函 数 格 式 表 10.8 列 出 了 socket() 函 数 的 语 法 要 点 表 10.8 所 需 头 文 件 函 数 原 型 #include <sys/socket.h> socket() 函 数 语 法 要 点 int socket(int family, int type, int protocol) AF_INET:IPv4 协 议 family: 协 议 族 AF_INET6:IPv6 协 议 AF_LOCAL:UNIX 域 协 议 AF_ROUTE: 路 由 套 接 字 (socket) 函 数 传 入 值 AF_KEY: 密 钥 套 接 字 (socket) type: 套 接 字 类 型 SOCK_STREAM: 字 节 流 套 接 字 socket SOCK_DGRAM: 数 据 报 套 接 字 socket SOCK_RAW: 原 始 套 接 字 socket protoco:0( 原 始 套 接 字 除 外 ) 函 数 返 回 值 成 功 : 非 负 套 接 字 描 述 符 出 错 : 1 表 10.9 列 出 了 bind() 函 数 的 语 法 要 点 表 10.9 所 需 头 文 件 函 数 原 型 #include <sys/socket.h> bind() 函 数 语 法 要 点 int bind(int sockfd, struct sockaddr *my_addr, int addrlen) socktd: 套 接 字 描 述 符 函 数 传 入 值 my_addr: 本 地 地 址 addrlen: 地 址 长 度 函 数 返 回 值 成 功 :0 出 错 : 1 端 口 号 和 地 址 在 my_addr 中 给 出 了, 若 不 指 定 地 址, 则 内 核 随 意 分 配 一 个 临 时 端 口 给 该 应 用 程 序 表 列 出 了 listen() 函 数 的 语 法 要 点 表 所 需 头 文 件 函 数 原 型 函 数 传 入 值 listen() 函 数 语 法 要 点 #include <sys/socket.h> int listen(int sockfd, int backlog) socktd: 套 接 字 描 述 符 backlog: 请 求 队 列 中 允 许 的 最 大 请 求 数, 大 多 数 系 统 缺 省 值 为 5 11
306 函 数 返 回 值 成 功 :0 出 错 : 1 表 列 出 了 accept() 函 数 的 语 法 要 点 表 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 accept() 函 数 语 法 要 点 #include <sys/socket.h> int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) socktd: 套 接 字 描 述 符 addr: 客 户 端 地 址 addrlen: 地 址 长 度 成 功 :0 出 错 : 1 表 列 出 了 connect() 函 数 的 语 法 要 点 表 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 connect() 函 数 语 法 要 点 #include <sys/socket.h> int connect(int sockfd, struct sockaddr *serv_addr, int addrlen) socktd: 套 接 字 描 述 符 serv_addr: 服 务 器 端 地 址 addrlen: 地 址 长 度 成 功 :0 出 错 : 1 表 列 出 了 send() 函 数 的 语 法 要 点 表 send() 函 数 语 法 要 点 所 需 头 文 件 #include <sys/socket.h> 函 数 原 型 int send(int sockfd, const void *msg, int len, int flags) socktd: 套 接 字 描 述 符 msg: 指 向 要 发 送 数 据 的 指 针 函 数 传 入 值 len: 数 据 长 度 flags: 一 般 为 0 成 功 : 发 送 的 字 节 数 函 数 返 回 值 出 错 : 1 表 列 出 了 recv() 函 数 的 语 法 要 点 表 所 需 头 文 件 函 数 原 型 #include <sys/socket.h> recv() 函 数 语 法 要 点 int recv(int sockfd, void *buf,int len, unsigned int flags) socktd: 套 接 字 描 述 符 函 数 传 入 值 buf: 存 放 接 收 数 据 的 缓 冲 区 len: 数 据 长 度 flags: 一 般 为 0 函 数 返 回 值 成 功 : 接 收 的 字 节 数 出 错 : 1 表 列 出 了 sendto() 函 数 的 语 法 要 点 表 所 需 头 文 件 #include <sys/socket.h> sendto() 函 数 语 法 要 点 12
307 函 数 原 型 int sendto(int sockfd, const void *msg,int len, unsigned int flags, const struct sockaddr *to, int tolen) socktd: 套 接 字 描 述 符 msg: 指 向 要 发 送 数 据 的 指 针 函 数 传 入 值 len: 数 据 长 度 flags: 一 般 为 0 to: 目 地 机 的 IP 地 址 和 端 口 号 信 息 tolen: 地 址 长 度 函 数 返 回 值 成 功 : 发 送 的 字 节 数 出 错 : 1 表 列 出 了 recvfrom() 函 数 的 语 法 要 点 表 所 需 头 文 件 函 数 原 型 #include <sys/socket.h> recvfrom() 函 数 语 法 要 点 int recvfrom(int sockfd,void *buf, int len, unsigned int flags, struct sockaddr *from, int *fromlen) socktd: 套 接 字 描 述 符 buf: 存 放 接 收 数 据 的 缓 冲 区 函 数 传 入 值 len: 数 据 长 度 flags: 一 般 为 0 from: 源 主 机 的 IP 地 址 和 端 口 号 信 息 tolen: 地 址 长 度 函 数 返 回 值 成 功 : 接 收 的 字 节 数 出 错 : 1 (3) 使 用 实 例 该 实 例 分 为 客 户 端 和 服 务 器 端 两 部 分, 其 中 服 务 器 端 首 先 建 立 起 socket, 然 后 与 本 地 端 口 进 行 绑 定, 接 着 就 开 始 接 收 从 客 户 端 的 连 接 请 求 并 建 立 与 它 的 连 接, 接 下 来, 接 收 客 户 端 发 送 的 消 息 客 户 端 则 在 建 立 socket 之 后 调 用 connect() 函 数 来 建 立 连 接 服 务 端 的 代 码 如 下 所 示 : /*server.c*/ #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <netinet/in.h> #define PORT 4321 #define BUFFER_SIZE 1024 #define MAX_QUE_CONN_NM 5 int main() 13
308 struct sockaddr_in server_sockaddr,client_sockaddr; int sin_size,recvbytes; int sockfd, client_fd; char buf[buffer_size]; 专 业 始 于 专 注 卓 识 源 于 远 见 /* 建 立 socket 连 接 */ if ((sockfd = socket(af_inet,sock_stream,0))== -1) perror("socket"); exit(1); printf("socket id = %d\n",sockfd); /* 设 置 sockaddr_in 结 构 体 中 相 关 参 数 */ server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(port); server_sockaddr.sin_addr.s_addr = INADDR_ANY; bzero(&(server_sockaddr.sin_zero), 8); int i = 1;/* 允 许 重 复 使 用 本 地 地 址 与 套 接 字 进 行 绑 定 */ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); /* 绑 定 函 数 bind()*/ if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr)) == -1) perror("bind"); exit(1); printf("bind success!\n"); /* 调 用 listen() 函 数, 创 建 未 处 理 请 求 的 队 列 */ if (listen(sockfd, MAX_QUE_CONN_NM) == -1) perror("listen"); exit(1); printf("listening...\n"); /* 调 用 accept() 函 数, 等 待 客 户 端 的 连 接 */ if ((client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, &sin_size)) == -1) perror("accept"); exit(1); 14
309 /* 调 用 recv() 函 数 接 收 客 户 端 的 请 求 */ memset(buf, 0, sizeof(buf)); if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) == -1) perror("recv"); exit(1); printf("received a message: %s\n", buf); close(sockfd); exit(0); 专 业 始 于 专 注 卓 识 源 于 远 见 客 户 端 的 代 码 如 下 所 示 : /*client.c*/ #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <netdb.h> #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #define PORT 4321 #define BUFFER_SIZE 1024 int main(int argc, char *argv[]) int sockfd,sendbytes; char buf[buffer_size]; struct hostent *host; struct sockaddr_in serv_addr; if(argc < 3) fprintf(stderr,"usage:./client Hostname(or ip address) Text\n"); exit(1); /* 地 址 解 析 函 数 */ if ((host = gethostbyname(argv[1])) == NULL) perror("gethostbyname"); exit(1); memset(buf, 0, sizeof(buf)); sprintf(buf, "%s", argv[2]); 15
310 /* 创 建 socket*/ if ((sockfd = socket(af_inet, SOCK_STREAM, 0)) == -1) perror("socket"); exit(1); /* 设 置 sockaddr_in 结 构 体 中 相 关 参 数 */ serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(port); serv_addr.sin_addr = *((struct in_addr *)host->h_addr); bzero(&(serv_addr.sin_zero), 8); /* 调 用 connect 函 数 主 动 发 起 对 服 务 器 端 的 连 接 */ if(connect(sockfd,(struct sockaddr *)&serv_addr, sizeof(struct sockaddr))== -1) perror("connect"); exit(1); /* 发 送 消 息 给 服 务 器 端 */ if ((sendbytes = send(sockfd, buf, strlen(buf), 0)) == -1) perror("send"); exit(1); close(sockfd); exit(0); 在 运 行 时 需 要 先 启 动 服 务 器 端, 再 启 动 客 户 端 这 里 可 以 把 服 务 器 端 下 载 到 开 发 板 上, 客 户 端 在 宿 主 机 上 运 行, 然 后 配 置 双 方 的 IP 地 址, 在 确 保 双 方 可 以 通 信 ( 如 使 用 ping 命 令 验 证 ) 的 情 况 下 运 行 该 程 序 即 可 $./server Socket id = 3 Bind success! Listening... Received a message: Hello,Server! $./client localhost( 或 者 输 入 IP 地 址 ) Hello,Server! 10.3 网 络 高 级 编 程 在 实 际 情 况 中, 人 们 往 往 遇 到 多 个 客 户 端 连 接 服 务 器 端 的 情 况 由 于 之 前 介 绍 的 如 connet() recv() 和 send() 等 都 是 阻 塞 性 函 数, 如 果 资 源 没 有 准 备 好, 则 调 用 该 函 数 的 进 程 将 进 入 睡 眠 状 态, 这 样 就 无 法 处 理 I/O 多 路 复 用 的 情 况 了 本 节 给 出 了 两 种 解 决 I/O 多 路 复 用 的 解 决 方 法, 这 两 个 函 数 都 是 之 前 学 过 的 fcntl() 和 select()( 请 读 者 先 复 习 第 6 章 中 的 相 关 内 容 ) 可 以 看 到, 由 于 在 Linux 中 把 socket 也 作 为 一 种 特 殊 文 件 描 16
311 述 符, 这 给 用 户 的 处 理 带 来 了 很 大 的 方 便 1.fcntl() 函 数 fcntl() 针 对 socket 编 程 提 供 了 如 下 的 编 程 特 性 非 阻 塞 I/O: 可 将 cmd 设 置 为 F_SETFL, 将 lock 设 置 为 O_NONBLOCK 异 步 I/O: 可 将 cmd 设 置 为 F_SETFL, 将 lock 设 置 为 O_ASYNC 下 面 是 用 fcntl() 将 套 接 字 设 置 为 非 阻 塞 I/O 的 实 例 代 码 : /* net_fcntl.c */ #include <sys/types.h> #include <sys/socket.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/un.h> #include <sys/time.h> #include <sys/ioctl.h> #include <unistd.h> #include <netinet/in.h> #include <fcntl.h> #define PORT 1234 #define MAX_QUE_CONN_NM 5 #define BUFFER_SIZE 1024 int main() struct sockaddr_in server_sockaddr, client_sockaddr; int sin_size, recvbytes, flags; int sockfd, client_fd; char buf[buffer_size]; if ((sockfd = socket(af_inet, SOCK_STREAM, 0)) == -1) perror("socket"); exit(1); server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(port); server_sockaddr.sin_addr.s_addr = INADDR_ANY; bzero(&(server_sockaddr.sin_zero), 8); int i = 1;/* 允 许 重 复 使 用 本 地 地 址 与 套 接 字 进 行 绑 定 */ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); if (bind(sockfd, (struct sockaddr *)&server_sockaddr, 17
312 sizeof(struct sockaddr)) == -1) perror("bind"); exit(1); if(listen(sockfd,max_que_conn_nm) == -1) perror("listen"); exit(1); printf("listening...\n"); /* 调 用 fcntl() 函 数 给 套 接 字 设 置 非 阻 塞 属 性 */ flags = fcntl(sockfd, F_GETFL); if (flags < 0 fcntl(sockfd, F_SETFL, flags O_NONBLOCK) < 0) perror("fcntl"); exit(1); 专 业 始 于 专 注 卓 识 源 于 远 见 while(1) sin_size = sizeof(struct sockaddr_in); if ((client_fd = accept(sockfd, (struct sockaddr*)&client_sockaddr, &sin_size)) < 0) perror("accept"); exit(1); if ((recvbytes = recv(client_fd, buf, BUFFER_SIZE, 0)) < 0) perror("recv"); exit(1); printf("received a message: %s\n", buf); /*while*/ close(client_fd); exit(1); 运 行 该 程 序, 结 果 如 下 所 示 : $./net_fcntl Listening... accept: Resource temporarily unavailable 可 以 看 到, 当 accept() 的 资 源 不 可 用 ( 没 有 任 何 未 处 理 的 等 待 连 接 的 请 求 ) 时, 程 序 就 会 自 动 返 回 18
313 2.select() 使 用 fcntl() 函 数 虽 然 可 以 实 现 非 阻 塞 I/O 或 信 号 驱 动 I/O, 但 在 实 际 使 用 时 往 往 会 对 资 源 是 否 准 备 完 毕 进 行 循 环 测 试, 这 样 就 大 大 增 加 了 不 必 要 的 CPU 资 源 的 占 用 在 这 里 可 以 使 用 select() 函 数 来 解 决 这 个 问 题, 同 时, 使 用 select() 函 数 还 可 以 设 置 等 待 的 时 间, 可 以 说 功 能 更 加 强 大 下 面 是 使 用 select() 函 数 的 服 务 器 端 源 代 码 客 户 端 程 序 基 本 上 与 小 节 中 的 例 子 相 同, 仅 加 入 一 行 sleep() 函 数, 使 得 客 户 端 进 程 等 待 几 秒 钟 才 结 束 /* net_select.c */ #include <sys/types.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/time.h> #include <sys/ioctl.h> #include <unistd.h> #include <netinet/in.h> #define PORT 4321 #define MAX_QUE_CONN_NM 5 #define MAX_SOCK_FD FD_SETSIZE #define BUFFER_SIZE 1024 int main() struct sockaddr_in server_sockaddr, client_sockaddr; int sin_size, count; fd_set inset, tmp_inset; int sockfd, client_fd, fd; char buf[buffer_size]; if ((sockfd = socket(af_inet, SOCK_STREAM, 0)) == -1) perror("socket"); exit(1); server_sockaddr.sin_family = AF_INET; server_sockaddr.sin_port = htons(port); server_sockaddr.sin_addr.s_addr = INADDR_ANY; bzero(&(server_sockaddr.sin_zero), 8); int i = 1;/* 允 许 重 复 使 用 本 地 地 址 与 套 接 字 进 行 绑 定 */ setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i)); if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr)) == -1) perror("bind"); exit(1); 19
314 if(listen(sockfd, MAX_QUE_CONN_NM) == -1) perror("listen"); exit(1); printf("listening...\n"); /* 将 调 用 socket() 函 数 的 描 述 符 作 为 文 件 描 述 符 */ FD_ZERO(&inset); FD_SET(sockfd, &inset); while(1) tmp_inset = inset; sin_size=sizeof(struct sockaddr_in); memset(buf, 0, sizeof(buf)); /* 调 用 select() 函 数 */ if (!(select(max_sock_fd, &tmp_inset, NULL, NULL, NULL) > 0)) perror("select"); for (fd = 0; fd < MAX_SOCK_FD; fd++) if (FD_ISSET(fd, &tmp_inset) > 0) if (fd == sockfd) /* 服 务 端 接 收 客 户 端 的 连 接 请 求 */ if ((client_fd = accept(sockfd, (struct sockaddr *)&client_sockaddr, &sin_size))== -1) perror("accept"); exit(1); FD_SET(client_fd, &inset); printf("new connection from %d(socket)\n", client_fd); else /* 处 理 从 客 户 端 发 来 的 消 息 */ if ((count = recv(client_fd, buf, BUFFER_SIZE, 0)) > 0) printf("received a message from %d: %s\n", client_fd, buf); else close(fd); FD_CLR(fd, &inset); printf("client %d(socket) has left\n", fd); 20
315 /* end of if FD_ISSET*/ /* end of for fd*/ /* end if while while*/ close(sockfd); exit(0); 运 行 该 程 序 时, 可 以 先 启 动 服 务 器 端, 再 反 复 运 行 客 户 端 程 序 ( 这 里 启 动 两 个 客 户 端 进 程 ) 即 可, 服 务 器 端 运 行 结 果 如 下 所 示 : $./server listening... New connection from 4(socket) /* 接 受 第 一 个 客 户 端 的 连 接 请 求 */ Received a message from 4: Hello,First! /* 接 收 第 一 个 客 户 端 发 送 的 数 据 */ New connection from 5(socket) /* 接 受 第 二 个 客 户 端 的 连 接 请 求 */ Received a message from 5: Hello,Second! /* 接 收 第 二 个 客 户 端 发 送 的 数 据 */ Client 4(socket) has left /* 检 测 到 第 一 个 客 户 端 离 线 了 */ Client 5(socket) has left /* 检 测 到 第 二 个 客 户 端 离 线 了 */ $./client localhost Hello,First! &./client localhost Hello,Second 10.4 实 验 内 容 NTP 协 议 实 现 1. 实 验 目 的 通 过 实 现 NTP 协 议 的 练 习, 进 一 步 掌 握 Linux 网 络 编 程, 并 且 提 高 协 议 的 分 析 与 实 现 能 力, 为 参 与 完 成 综 合 性 项 目 打 下 良 好 的 基 础 2. 实 验 内 容 Network Time Protocol(NTP) 协 议 是 用 来 使 计 算 机 时 间 同 步 化 的 一 种 协 议, 它 可 以 使 计 算 机 对 其 服 务 器 或 时 钟 源 ( 如 石 英 钟,GPS 等 ) 做 同 步 化, 它 可 以 提 供 高 精 确 度 的 时 间 校 正 (LAN 上 与 标 准 时 间 差 小 于 1 毫 秒,WAN 上 几 十 毫 秒 ), 且 可 用 加 密 确 认 的 方 式 来 防 止 恶 毒 的 协 议 攻 击 NTP 提 供 准 确 时 间, 首 先 要 有 准 确 的 时 间 来 源, 这 一 时 间 应 该 是 国 际 标 准 时 间 UTC NTP 获 得 UTC 的 时 间 来 源 可 以 是 原 子 钟 天 文 台 卫 星, 也 可 以 从 Internet 上 获 取 这 样 就 有 了 准 确 而 可 靠 的 时 间 源 时 间 是 按 NTP 服 务 器 的 等 级 传 播 按 照 距 离 外 部 UTC 源 的 远 近 将 所 有 服 务 器 归 入 不 同 的 Stratun( 层 ) 中 Stratum-1 在 顶 层, 有 外 部 UTC 接 入, 而 Stratum-2 则 从 Stratum-1 获 取 时 间,Stratum-3 从 Stratum-2 获 取 时 间, 以 此 类 推, 但 Stratum 层 的 总 数 限 制 在 15 以 内 所 有 这 些 服 务 器 在 逻 辑 上 形 成 阶 梯 式 的 架 构 并 相 互 连 接, 而 Stratum-1 的 时 间 服 务 器 是 整 个 系 统 的 基 础 进 行 网 络 协 议 实 现 时 最 重 要 的 是 了 解 协 议 数 据 格 式 NTP 数 据 包 有 48 个 字 节, 其 中 NTP 包 头 16 字 节, 时 间 戳 32 个 字 节 其 协 议 格 式 如 图 10.9 所 示 21
316 图 10.9 NTP 协 议 数 据 格 式 其 协 议 字 段 的 含 义 如 下 所 示 LI: 跳 跃 指 示 器, 警 告 在 当 月 最 后 一 天 的 最 终 时 刻 插 入 的 迫 近 闺 秒 ( 闺 秒 ) VN: 版 本 号 Mode: 工 作 模 式 该 字 段 包 括 以 下 值 :0- 预 留 ;1- 对 称 行 为 ;3- 客 户 机 ;4- 服 务 器 ;5- 广 播 ;6-NTP 控 制 信 息 NTP 协 议 具 有 3 种 工 作 模 式, 分 别 为 主 / 被 动 对 称 模 式 客 户 / 服 务 器 模 式 广 播 模 式 在 主 / 被 动 对 称 模 式 中, 有 一 对 一 的 连 接, 双 方 均 可 同 步 对 方 或 被 对 方 同 步, 先 发 出 申 请 建 立 连 接 的 一 方 工 作 在 主 动 模 式 下, 另 一 方 工 作 在 被 动 模 式 下 ; 客 户 / 服 务 器 模 式 与 主 / 被 动 模 式 基 本 相 同, 惟 一 区 别 在 于 客 户 方 可 被 服 务 器 同 步, 但 服 务 器 不 能 被 客 户 同 步 ; 在 广 播 模 式 中, 有 一 对 多 的 连 接, 服 务 器 不 论 客 户 工 作 在 何 种 模 式 下, 都 会 主 动 发 出 时 间 信 息, 客 户 根 据 此 信 息 调 整 自 己 的 时 间 Stratum: 对 本 地 时 钟 级 别 的 整 体 识 别 Poll: 有 符 号 整 数 表 示 连 续 信 息 间 的 最 大 间 隔 Precision: 有 符 号 整 数 表 示 本 地 时 钟 精 确 度 Root Delay: 表 示 到 达 主 参 考 源 的 一 次 往 复 的 总 延 迟, 它 是 有 15~16 位 小 数 部 分 的 符 号 定 点 小 数 Root Dispersion: 表 示 一 次 到 达 主 参 考 源 的 标 准 误 差, 它 是 有 15~16 位 小 数 部 分 的 无 符 号 定 点 小 数 Reference Identifier: 识 别 特 殊 参 考 源 Originate Timestamp: 这 是 向 服 务 器 请 求 分 离 客 户 机 的 时 间, 采 用 64 位 时 标 格 式 Receive Timestamp: 这 是 向 服 务 器 请 求 到 达 客 户 机 的 时 间, 采 用 64 位 时 标 格 式 Transmit Timestamp: 这 是 向 客 户 机 答 复 分 离 服 务 器 的 时 间, 采 用 64 位 时 标 格 式 Authenticator(Optional): 当 实 现 了 NTP 认 证 模 式 时, 主 要 标 识 符 和 信 息 数 字 域 就 包 括 已 定 义 的 信 息 认 证 代 码 (MAC) 信 息 由 于 NTP 协 议 中 涉 及 比 较 多 的 时 间 相 关 的 操 作, 为 了 简 化 实 现 过 程, 在 本 实 验 中, 仅 要 求 实 现 NTP 协 议 客 户 端 部 分 的 网 络 通 信 模 块, 也 就 是 构 造 NTP 协 议 字 段 进 行 发 送 和 接 收, 最 后 与 时 间 相 关 的 操 作 不 需 进 行 处 理 NTP 协 议 是 作 为 OSI 参 考 模 型 的 高 层 协 议 比 较 适 合 采 用 UDP 传 输 协 议 进 行 数 据 传 输, 专 用 端 口 号 为 123 在 实 验 中, 以 国 家 授 时 中 心 服 务 器 (IP 地 址 为 ) 作 为 NTP( 网 络 时 间 ) 服 务 器 3. 实 验 步 骤 (1) 画 出 流 程 图 简 易 NTP 客 户 端 的 实 现 流 程 如 图 所 示 22
317 图 简 易 NTP 客 户 端 流 程 图 (2) 编 写 程 序 具 体 代 码 如 下 : /* ntp.c */ #include <sys/socket.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <sys/un.h> #include <sys/time.h> #include <sys/ioctl.h> #include <unistd.h> #include <netinet/in.h> #include <string.h> #include <netdb.h> #define NTP_PORT 123 /*NTP 专 用 端 口 号 字 符 串 */ #define TIME_PORT 37 /* TIME/UDP 端 口 号 */ #define NTP_SERVER_IP " " /* 国 家 授 时 中 心 IP*/ #define NTP_PORT_STR "123" /*NTP 专 用 端 口 号 字 符 串 */ #define NTPV1 "NTP/V1" /* 协 议 及 其 版 本 号 */ #define NTPV2 "NTP/V2" #define NTPV3 "NTP/V3" #define NTPV4 "NTP/V4" #define TIME "TIME/UDP" #define NTP_PCK_LEN 48 #define LI 0 #define VN 3 #define MODE 3 #define STRATUM 0 #define POLL 4 #define PREC -6 #define JAN_1970 0x83aa7e80 /* 1900 年 ~1970 年 之 间 的 时 间 秒 数 */ #define NTPFRAC(x) (4294 * (x) + ((1981 * (x)) >> 11)) 23
318 #define USEC(x) (((x) >> 12) * ((((x) >> 10) ) >> 16)) typedef struct _ntp_time unsigned int coarse; unsigned int fine; ntp_time; struct ntp_packet unsigned char leap_ver_mode; unsigned char startum; char poll; char precision; int root_delay; int root_dispersion; int reference_identifier; ntp_time reference_timestamp; ntp_time originage_timestamp; ntp_time receive_timestamp; ntp_time transmit_timestamp; ; char protocol[32]; /* 构 建 NTP 协 议 包 */ int construct_packet(char *packet) char version = 1; long tmp_wrd; int port; time_t timer; strcpy(protocol, NTPV3); /* 判 断 协 议 版 本 */ if(!strcmp(protocol, NTPV1)!strcmp(protocol, NTPV2)!strcmp(protocol, NTPV3)!strcmp(protocol, NTPV4)) memset(packet, 0, NTP_PCK_LEN); port = NTP_PORT; /* 设 置 16 字 节 的 包 头 */ version = protocol[6] - 0x30; tmp_wrd = htonl((li << 30) (version << 27) (MODE << 24) (STRATUM << 16) (POLL << 8) (PREC & 0xff)); memcpy(packet, &tmp_wrd, sizeof(tmp_wrd)); /* 设 置 Root Delay Root Dispersion 和 Reference Indentifier */ tmp_wrd = htonl(1<<16); memcpy(&packet[4], &tmp_wrd, sizeof(tmp_wrd)); 24
319 memcpy(&packet[8], &tmp_wrd, sizeof(tmp_wrd)); /* 设 置 Timestamp 部 分 */ time(&timer); /* 设 置 Transmit Timestamp coarse*/ tmp_wrd = htonl(jan_ (long)timer); memcpy(&packet[40], &tmp_wrd, sizeof(tmp_wrd)); /* 设 置 Transmit Timestamp fine*/ tmp_wrd = htonl((long)ntpfrac(timer)); memcpy(&packet[44], &tmp_wrd, sizeof(tmp_wrd)); return NTP_PCK_LEN; else if (!strcmp(protocol, TIME))/* "TIME/UDP" */ port = TIME_PORT; memset(packet, 0, 4); return 4; return 0; 专 业 始 于 专 注 卓 识 源 于 远 见 /* 获 取 NTP 时 间 */ int get_ntp_time(int sk, struct addrinfo *addr, struct ntp_packet *ret_time) fd_set pending_data; struct timeval block_time; char data[ntp_pck_len * 8]; int packet_len, data_len = addr->ai_addrlen, count = 0, result, i, re; if (!(packet_len = construct_packet(data))) return 0; /* 客 户 端 给 服 务 器 端 发 送 NTP 协 议 数 据 包 */ if ((result = sendto(sk, data, packet_len, 0, addr->ai_addr, data_len)) < 0) perror("sendto"); return 0; /* 调 用 select() 函 数, 并 设 定 超 时 时 间 为 1s*/ FD_ZERO(&pending_data); FD_SET(sk, &pending_data); block_time.tv_sec=10; block_time.tv_usec=0; if (select(sk + 1, &pending_data, NULL, NULL, &block_time) > 0) 25
320 /* 接 收 服 务 器 端 的 信 息 */ if ((count = recvfrom(sk, data, NTP_PCK_LEN * 8, 0, addr->ai_addr, &data_len)) < 0) perror("recvfrom"); return 0; if (protocol == TIME) memcpy(&ret_time->transmit_timestamp, data, 4); return 1; else if (count < NTP_PCK_LEN) return 0; /* 设 置 接 收 NTP 包 的 数 据 结 构 */ ret_time->leap_ver_mode = ntohl(data[0]); ret_time->startum = ntohl(data[1]); ret_time->poll = ntohl(data[2]); ret_time->precision = ntohl(data[3]); ret_time->root_delay = ntohl(*(int*)&(data[4])); ret_time->root_dispersion = ntohl(*(int*)&(data[8])); ret_time->reference_identifier = ntohl(*(int*)&(data[12])); ret_time->reference_timestamp.coarse = ntohl *(int*)&(data[16])); ret_time->reference_timestamp.fine = ntohl(*(int*)&(data[20])); ret_time->originage_timestamp.coarse = ntohl(*(int*)&(data[24])); ret_time->originage_timestamp.fine = ntohl(*(int*)&(data[28])); ret_time->receive_timestamp.coarse = ntohl(*(int*)&(data[32])); ret_time->receive_timestamp.fine = ntohl(*(int*)&(data[36])); ret_time->transmit_timestamp.coarse = ntohl(*(int*)&(data[40])); ret_time->transmit_timestamp.fine = ntohl(*(int*)&(data[44])); return 1; /* end of if select */ return 0; /* 修 改 本 地 时 间 */ int set_local_time(struct ntp_packet * pnew_time_packet) struct timeval tv; tv.tv_sec = pnew_time_packet->transmit_timestamp.coarse - JAN_1970; tv.tv_usec = USEC(pnew_time_packet->transmit_timestamp.fine); return settimeofday(&tv, NULL); 26
321 int main() int sockfd, rc; struct addrinfo hints, *res = NULL; struct ntp_packet new_time_packet; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; /* 调 用 getaddrinfo() 函 数, 获 取 地 址 信 息 */ rc = getaddrinfo(ntp_server_ip, NTP_PORT_STR, &hints, &res); if (rc!= 0) perror("getaddrinfo"); return 1; /* 创 建 套 接 字 */ sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (sockfd <0 ) perror("socket"); return 1; /* 调 用 取 得 NTP 时 间 的 函 数 */ if (get_ntp_time(sockfd, res, &new_time_packet)) /* 调 整 本 地 时 间 */ if (!set_local_time(&new_time_packet)) printf("ntp client success!\n"); close(sockfd); return 0; 为 了 更 好 地 观 察 程 序 的 效 果, 先 用 date 命 令 修 改 一 下 系 统 时 间, 再 运 行 实 例 程 序 运 行 完 了 之 后 再 查 看 系 统 时 间, 可 以 发 现 已 经 恢 复 准 确 的 系 统 时 间 了 具 体 运 行 结 果 如 下 所 示 $ date -s " :00:00" 2001 年 01 月 01 日 星 期 一 01:00:00 EST $ date 2001 年 01 月 01 日 星 期 一 01:00:00 EST $./ntp NTP client success! $ date 能 够 显 示 当 前 准 确 的 日 期 和 时 间 了! 27
322 10.5 本 章 小 结 本 章 首 先 概 括 地 讲 解 了 OSI 分 层 结 构 以 及 TCP/IP 协 议 各 层 的 主 要 功 能, 介 绍 了 常 见 的 TCP/IP 协 议 族, 并 且 重 点 讲 解 了 网 络 编 程 中 需 要 用 到 的 TCP 和 UDP 协 议, 为 嵌 入 式 Linux 的 网 络 编 程 打 下 良 好 的 基 础 接 着 本 章 介 绍 了 socket 的 定 义 及 其 类 型, 并 逐 个 介 绍 常 见 的 socket 相 关 的 基 本 函 数, 包 括 地 址 处 理 函 数 数 据 存 储 转 换 函 数 等, 这 些 函 数 都 是 最 为 常 用 的 函 数, 要 在 理 解 概 念 的 基 础 上 熟 练 掌 握 接 下 来 介 绍 的 是 网 络 编 程 中 的 基 本 函 数, 这 也 是 最 为 常 见 的 几 个 函 数, 这 里 要 注 意 TCP 和 UDP 在 处 理 过 程 中 的 不 同 同 时, 本 章 还 介 绍 了 较 为 高 级 的 网 络 编 程, 包 括 调 用 fcntl() 和 select() 函 数, 这 两 个 函 数 在 前 面 的 章 节 中 都 已 经 讲 解 过, 但 在 本 章 中 有 特 殊 的 用 途 最 后, 本 章 以 ping 程 序 为 例, 讲 解 了 常 见 协 议 的 实 现 过 程, 读 者 可 以 看 到 一 个 成 熟 的 协 议 是 如 何 实 现 的 本 章 的 实 验 安 排 了 实 现 一 个 比 较 简 单 但 完 整 的 NTP 客 户 端 程 序, 主 要 实 现 了 其 中 数 据 收 发 的 主 要 功 能, 以 及 时 间 同 步 调 整 的 功 能 10.6 思 考 与 练 习 1. 分 别 用 多 线 程 和 多 路 复 用 实 现 网 络 聊 天 程 序 2. 实 现 一 个 小 型 模 拟 的 路 由 器, 就 是 接 收 从 某 个 IP 地 址 的 连 接 请 求, 再 把 该 请 求 转 发 到 另 一 个 IP 地 址 的 主 机 上 去 联 系 方 式 集 团 官 网 : 嵌 入 式 学 院 : 移 动 互 联 网 学 院 : 企 业 学 院 : 物 联 网 学 院 : 研 发 中 心 :dev.hqyj.com 集 团 总 部 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 内 华 清 远 见 教 育 集 团 北 京 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 区, 电 话 : /5 上 海 地 址 : 上 海 市 徐 汇 区 漕 溪 路 银 海 大 厦 A 座 8 层, 电 话 : 深 圳 地 址 : 深 圳 市 龙 华 新 区 人 民 北 路 美 丽 AAA 大 厦 15 层, 电 话 : 成 都 地 址 : 成 都 市 武 侯 区 科 华 北 路 99 号 科 华 大 厦 6 层, 电 话 : 南 京 地 址 : 南 京 市 白 下 区 汉 中 路 185 号 鸿 运 大 厦 10 层, 电 话 : 武 汉 地 址 : 武 汉 市 工 程 大 学 卓 刀 泉 校 区 科 技 孵 化 器 大 楼 8 层, 电 话 : 西 安 地 址 : 西 安 市 高 新 区 高 新 一 路 12 号 创 业 大 厦 D3 楼 5 层, 电 话 :
323 嵌 入 式 LINUX 应 用 程 序 开 发 标 准 教 程 作 者 : 华 清 远 见 第 11 章 嵌 入 式 Linux 设 备 驱 动 开 发 6 10 Linux Linux Linux Linux 设 备 驱 动 的 基 本 概 念 Linux 设 备 驱 动 程 序 的 基 本 功 能 Linux 设 备 驱 动 的 运 作 过 程 常 见 设 备 驱 动 接 口 函 数 掌 握 LCD 设 备 驱 动 程 序 编 写 步 骤 掌 握 键 盘 设 备 驱 动 程 序 编 写 步 骤
324 11.1 设 备 驱 动 概 述 设 备 驱 动 简 介 及 驱 动 模 块 操 作 系 统 是 通 过 各 种 驱 动 程 序 来 驾 驭 硬 件 设 备 的, 它 为 用 户 屏 蔽 了 各 种 各 样 的 设 备, 驱 动 硬 件 是 操 作 系 统 最 基 本 的 功 能, 并 且 提 供 统 一 的 操 作 方 式 设 备 驱 动 程 序 是 内 核 的 一 部 分, 硬 件 驱 动 程 序 是 操 作 系 统 最 基 本 的 组 成 部 分, 在 Linux 内 核 源 程 序 中 也 占 有 60% 以 上 因 此, 熟 悉 驱 动 的 编 写 是 很 重 要 的 在 第 2 章 中 已 经 提 到 过,Linux 内 核 中 采 用 可 加 载 的 模 块 化 设 计 (LKMs,Loadable Kernel Modules), 一 般 情 况 下 编 译 的 Linux 内 核 是 支 持 可 插 入 式 模 块 的, 也 就 是 将 最 基 本 的 核 心 代 码 编 译 在 内 核 中, 其 他 的 代 码 可 以 编 译 到 内 核 中, 或 者 编 译 为 内 核 的 模 块 文 件 ( 在 需 要 时 动 态 加 载 ) 常 见 的 驱 动 程 序 是 作 为 内 核 模 块 动 态 加 载 的, 比 如 声 卡 驱 动 和 网 卡 驱 动 等, 而 Linux 最 基 础 的 驱 动, 如 CPU PCI 总 线 TCP/IP 协 议 APM( 高 级 电 源 管 理 ) VFS 等 驱 动 程 序 则 直 接 编 译 在 内 核 文 件 中 有 时 也 把 内 核 模 块 叫 做 驱 动 程 序, 只 不 过 驱 动 的 内 容 不 一 定 是 硬 件 罢 了, 比 如 ext3 文 件 系 统 的 驱 动 因 此, 加 载 驱 动 就 是 加 载 内 核 模 块 这 里, 首 先 列 举 一 些 模 块 相 关 的 命 令 lsmod 列 出 当 前 系 统 中 加 载 的 模 块, 其 中 左 边 第 一 列 是 模 块 名, 第 二 列 是 该 模 块 大 小, 第 三 列 则 是 使 用 该 模 块 的 对 象 数 目 如 下 所 示 : $ lsmod Module Size Used by Autofs (autoclean) (unused) eepro iptable_nat (autoclean) (unused) ip_conntrack (autoclean) [iptable_nat] iptable_mangle (autoclean) (unused) iptable_filter (autoclean) (unused) ip_tables [iptable_nat iptable_mangle iptable_filter] usb-ohci (unused) usbcore [usb-ohci] ext jbd [ext3] aic7xxx sd_mod scsi_mod [aic7xxx sd_mod] rmmod 是 用 于 将 当 前 模 块 卸 载 insmod 和 modprobe 是 用 于 加 载 当 前 模 块, 但 insmod 不 会 自 动 解 决 依 存 关 系, 即 如 果 要 加 载 的 模 块 引 用 了 当 前 内 核 符 号 表 中 不 存 在 的 符 号, 则 无 法 加 载, 也 不 会 去 查 在 其 他 尚 未 加 载 的 模 块 中 是 否 定 义 了 该 符 号 ;modprobe 可 以 根 据 模 块 间 依 存 关 系 以 及 /etc/modules.conf 文 件 中 的 内 容 自 动 加 载 其 他 有 依 赖 关 系 的 模 块 设 备 分 类 本 书 在 前 面 也 提 到 过,Linux 的 一 个 重 要 特 点 就 是 将 所 有 的 设 备 都 当 做 文 件 进 行 处 理, 这 一 类 特 殊 文 件 就 是 设 备 文 件, 它 们 可 以 使 用 前 面 提 到 的 文 件 I/O 相 关 函 数 进 行 操 作, 这 样 就 大 大 方 便 了 对 设 备 的 处 理 它 通 常 在 /dev 下 面 存 在 一 个 对 应 的 逻 辑 设 备 节 点, 这 个 节 点 以 文 件 的 形 式 存 在 Linux 系 统 的 设 备 分 为 3 类 : 字 符 设 备 块 设 备 和 网 络 设 备 2
325 字 符 设 备 通 常 指 像 普 通 文 件 或 字 节 流 一 样, 以 字 节 为 单 位 顺 序 读 写 的 设 备, 如 并 口 设 备 虚 拟 控 制 台 等 字 符 设 备 可 以 通 过 设 备 文 件 节 点 访 问, 它 与 普 通 文 件 之 间 的 区 别 在 于 普 通 文 件 可 以 被 随 机 访 问 ( 可 以 前 后 移 动 访 问 指 针 ), 而 大 多 数 字 符 设 备 只 能 提 供 顺 序 访 问, 因 为 对 它 们 的 访 问 不 会 被 系 统 所 缓 存 但 也 有 例 外, 例 如 帧 缓 存 (framebuffer) 是 一 个 可 以 被 随 机 访 问 的 字 符 设 备 块 设 备 通 常 指 一 些 需 要 以 块 为 单 位 随 机 读 写 的 设 备, 如 IDE 硬 盘 SCSI 硬 盘 光 驱 等 块 设 备 也 是 通 过 文 件 节 点 来 访 问, 它 不 仅 可 以 提 供 随 机 访 问, 而 且 可 以 容 纳 文 件 系 统 ( 例 如 硬 盘 闪 存 等 ) Linux 可 以 使 用 户 态 程 序 像 访 问 字 符 设 备 一 样 每 次 进 行 任 意 字 节 的 操 作, 只 是 在 内 核 态 内 部 中 的 管 理 方 式 和 内 核 提 供 的 驱 动 接 口 上 不 同 通 过 文 件 属 性 可 以 查 看 它 们 是 哪 种 设 备 文 件 ( 字 符 设 备 文 件 或 块 设 备 文 件 ) $ ls l /dev crw-rw root uucp 4, :58 ttys0 /* 串 口 设 备, c 表 示 字 符 设 备 */ brw-r root floppy 2, :58 fd0/* 软 盘 设 备,b 表 示 块 设 备 */ 网 络 设 备 通 常 是 指 通 过 网 络 能 够 与 其 他 主 机 进 行 数 据 通 信 的 设 备, 如 网 卡 等 内 核 和 网 络 设 备 驱 动 程 序 之 间 的 通 信 调 用 一 套 数 据 包 处 理 函 数, 它 们 完 全 不 同 于 内 核 和 字 符 以 及 块 设 备 驱 动 程 序 之 间 的 通 信 (read() write() 等 函 数 ) Linux 网 络 设 备 不 是 面 向 流 的 设 备, 因 此 不 会 将 网 络 设 备 的 名 字 ( 例 如 eth0) 映 射 到 文 件 系 统 中 去 对 这 3 种 设 备 文 件 编 写 驱 动 程 序 时 会 有 一 定 的 区 别, 本 书 在 后 面 会 有 相 关 内 容 的 讲 解 设 备 号 设 备 号 是 一 个 数 字, 它 是 设 备 的 标 志 就 如 前 面 所 述, 一 个 设 备 文 件 ( 也 就 是 设 备 节 点 ) 可 以 通 过 mknod 命 令 来 创 建, 其 中 指 定 了 主 设 备 号 和 次 设 备 号 主 设 备 号 表 明 设 备 的 类 型 ( 例 如 串 口 设 备 SCSI 硬 盘 ), 与 一 个 确 定 的 驱 动 程 序 对 应 ; 次 设 备 号 通 常 是 用 于 标 明 不 同 的 属 性, 例 如 不 同 的 使 用 方 法 不 同 的 位 置 不 同 的 操 作 等, 它 标 志 着 某 个 具 体 的 物 理 设 备 高 字 节 为 主 设 备 号, 底 字 节 为 次 设 备 号 例 如, 在 系 统 中 的 块 设 备 IDE 硬 盘 的 主 设 备 号 是 3, 而 多 个 IDE 硬 盘 及 其 各 个 分 区 分 别 赋 予 次 设 备 号 $ ls l /dev crw-rw root uucp 4, :58 ttys0 /* 主 设 备 号 4, 此 设 备 号 64 */ 驱 动 层 次 结 构 入 输 入 / 输 出 请 求 用 户 程 序 的 进 程 入 输 入 / 输 出 响 应 ( 设 备 ) 文 件 系 统 Linux 下 的 设 备 驱 动 程 序 是 内 核 的 一 部 分, 运 行 在 内 核 模 式 下, 也 就 是 设 备 服 务 子 程 序 设 备 驱 动 程 序 中 断 处 理 程 序 说 设 备 驱 动 程 序 为 内 核 提 供 了 一 个 I/O 接 口, 用 户 使 用 这 个 接 口 实 现 对 设 备 的 操 作 图 11.1 显 示 了 典 型 的 Linux 输 入 / 输 出 系 统 中 各 物 理 设 备 控 制 器 层 次 结 构 和 物 理 设 备 功 能 Linux 设 备 驱 动 程 序 包 含 中 断 处 理 程 序 和 设 备 服 务 子 程 序 两 部 图 11.1 Linux 输 入 / 输 出 系 统 分 层 次 结 构 和 功 能 设 备 服 务 子 程 序 包 含 了 所 有 与 设 备 操 作 相 关 的 处 理 代 码 它 从 面 向 用 户 进 程 的 设 备 文 件 系 统 中 接 受 用 户 命 令, 并 对 设 备 控 制 器 执 行 操 作 这 样, 设 备 驱 动 程 序 屏 蔽 了 设 备 的 特 殊 性, 使 用 户 可 以 像 对 待 文 件 一 样 操 作 设 备 设 备 控 制 器 获 得 系 统 服 务 有 两 种 方 式 : 查 询 和 中 断 因 为 Linux 的 设 备 驱 动 程 序 是 内 核 的 一 部 分, 在 设 备 查 询 期 间 系 统 不 能 运 行 其 他 代 码, 查 询 方 式 的 工 作 效 率 比 较 低, 所 以 只 有 少 数 设 备 如 软 盘 驱 动 程 序 采 取 这 种 方 式, 大 多 设 备 以 中 断 方 式 向 设 备 驱 动 程 序 发 出 输 入 / 输 出 请 求 3
326 设 备 驱 动 程 序 与 外 界 的 接 口 每 种 类 型 的 驱 动 程 序, 不 管 是 字 符 设 备 还 是 块 设 备 都 为 内 核 提 供 相 同 的 调 用 接 口, 因 此 内 核 能 以 相 同 的 方 式 处 理 不 同 的 设 备 Linux 为 每 种 不 同 类 型 的 设 备 驱 动 程 序 维 护 相 应 的 数 据 结 构, 以 便 定 义 统 一 的 接 口 并 实 现 驱 动 程 序 的 可 装 载 性 和 动 态 性 Linux 设 备 驱 动 程 序 与 外 界 的 接 口 可 以 分 为 如 下 3 个 部 分 驱 动 程 序 与 操 作 系 统 内 核 的 接 口 : 这 是 通 过 数 据 结 构 file_operations( 在 本 书 后 面 会 有 详 细 介 绍 ) 来 完 成 的 驱 动 程 序 与 系 统 引 导 的 接 口 : 这 部 分 利 用 驱 动 程 序 对 设 备 进 行 初 始 化 驱 动 程 序 与 设 备 的 接 口 : 这 部 分 描 述 了 驱 动 程 序 如 何 与 设 备 进 行 交 互, 这 与 具 体 设 备 密 切 相 关 它 们 之 间 的 相 互 关 系 如 图 11.2 所 示 操 作 系 统 内 核 各 设 备 接 口 实 现 数 据 结 构 file_operations 初 始 化 设 备 驱 动 程 序 接 口 具 体 设 备 系 统 引 导 接 口 进 行 交 互 驱 动 程 序 与 设 备 间 图 11.2 设 备 驱 动 程 序 与 外 界 的 接 口 设 备 驱 动 程 序 的 特 点 综 上 所 述,Linux 中 的 设 备 驱 动 程 序 有 如 下 特 点 (1) 内 核 代 码 : 设 备 驱 动 程 序 是 内 核 的 一 部 分, 如 果 驱 动 程 序 出 错, 则 可 能 导 致 系 统 崩 溃 (2) 内 核 接 口 : 设 备 驱 动 程 序 必 须 为 内 核 或 者 其 子 系 统 提 供 一 个 标 准 接 口 比 如, 一 个 终 端 驱 动 程 序 必 须 为 内 核 提 供 一 个 文 件 I/O 接 口 ; 一 个 SCSI 设 备 驱 动 程 序 应 该 为 SCSI 子 系 统 提 供 一 个 SCSI 设 备 接 口, 同 时 SCSI 子 系 统 也 必 须 为 内 核 提 供 文 件 的 I/O 接 口 及 缓 冲 区 (3) 内 核 机 制 和 服 务 : 设 备 驱 动 程 序 使 用 一 些 标 准 的 内 核 服 务, 如 内 存 分 配 等 (4) 可 装 载 : 大 多 数 的 Linux 操 作 系 统 设 备 驱 动 程 序 都 可 以 在 需 要 时 装 载 进 内 核, 在 不 需 要 时 从 内 核 中 卸 载 (5) 可 设 置 :Linux 操 作 系 统 设 备 驱 动 程 序 可 以 集 成 为 内 核 的 一 部 分, 并 可 以 根 据 需 要 把 其 中 的 某 一 部 分 集 成 到 内 核 中, 这 只 需 要 在 系 统 编 译 时 进 行 相 应 的 设 置 即 可 (6) 动 态 性 : 在 系 统 启 动 且 各 个 设 备 驱 动 程 序 初 始 化 后, 驱 动 程 序 将 维 护 其 控 制 的 设 备 如 果 该 设 备 驱 动 程 序 控 制 的 设 备 不 存 在 也 不 影 响 系 统 的 运 行, 那 么 此 时 的 设 备 驱 动 程 序 只 是 多 占 用 了 一 点 系 统 内 存 罢 了 11.2 字 符 设 备 驱 动 编 程 1. 字 符 设 备 驱 动 编 写 流 程 设 备 驱 动 程 序 可 以 使 用 模 块 的 方 式 动 态 加 载 到 内 核 中 去 加 载 模 块 的 方 式 与 以 往 的 应 用 程 序 开 发 有 很 大 的 不 同 以 往 在 开 发 应 用 程 序 时 都 有 一 个 main() 函 数 作 为 程 序 的 入 口 点, 而 在 驱 动 开 发 时 却 没 4
327 有 main() 函 数, 模 块 在 调 用 insmod 命 令 时 被 加 载, 此 时 的 入 口 点 是 init_module() 函 数, 通 常 在 该 函 数 中 完 成 设 备 的 注 册 同 样, 模 块 在 调 用 rmmod 命 令 时 被 卸 载, 此 时 的 入 口 点 是 cleanup_module() 函 数, 在 该 函 数 中 完 成 设 备 的 卸 载 在 设 备 完 成 注 册 加 载 之 后, 用 户 的 应 用 程 序 就 可 以 对 该 设 备 进 行 一 定 的 操 作, 如 open() read() write() 等, 而 驱 动 程 序 就 是 用 于 实 现 这 些 操 作, 在 用 户 应 用 程 序 调 用 相 应 入 口 函 数 时 执 行 相 关 的 操 作,init_module() 入 口 点 函 数 则 不 需 要 完 成 其 他 如 read() write() 之 类 功 能 上 述 函 数 之 间 的 关 系 如 图 11.3 所 示 图 11.3 设 备 驱 动 程 序 流 程 图 2. 重 要 数 据 结 构 用 户 应 用 程 序 调 用 设 备 的 一 些 功 能 是 在 设 备 驱 动 程 序 中 定 义 的, 也 就 是 设 备 驱 动 程 序 的 入 口 点, 它 是 一 个 在 <linux/fs.h> 中 定 义 的 struct file_operations 结 构, 这 是 一 个 内 核 结 构, 不 会 出 现 在 用 户 空 间 的 程 序 中, 它 定 义 了 常 见 文 件 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, unsigned long); 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 章 中 的 文 件 I/O 系 统 调 用 类 似? 其 实 当 时 的 系 统 调 用 函 数 通 过 内 核, 最 终 调 用 对 应 的 struct file_operations 结 构 的 接 口 函 数 ( 例 如,open() 文 件 操 作 是 通 过 调 用 对 应 文 件 的 file_operations 结 构 的 open 函 数 接 口 而 被 实 现 ) 当 然, 每 个 设 备 的 驱 动 程 序 不 一 定 要 实 现 其 中 所 有 的 函 数 操 作, 若 不 需 要 定 义 实 现 时, 则 只 需 将 其 设 为 NULL 即 可 5
328 struct inode 结 构 提 供 了 关 于 设 备 文 件 /dev/driver( 假 设 此 设 备 名 为 driver) 的 信 息,struct file 结 构 提 供 关 于 被 打 开 的 文 件 信 息, 主 要 用 于 与 文 件 系 统 对 应 的 设 备 驱 动 程 序 使 用 struct file 结 构 较 为 重 要, 这 里 列 出 了 它 的 定 义 : struct file mode_t f_mode;/* 标 识 文 件 是 否 可 读 或 可 写,FMODE_READ 或 FMODE_WRITE*/ dev_t f_rdev; /* 用 于 /dev/tty */ off_t f_pos; /* 当 前 文 件 位 移 */ unsigned short f_flags; /* 文 件 标 志, 如 O_RDONLY O_NONBLOCK 和 O_SYNC */ unsigned short f_count; /* 打 开 的 文 件 数 目 */ unsigned short f_reada; struct inode *f_inode; /* 指 向 inode 的 结 构 指 针 */ ; struct file_operations *f_op;/* 文 件 索 引 指 针 */ 3. 设 备 驱 动 程 序 主 要 组 成 (1) 早 期 版 本 的 字 符 设 备 注 册 早 期 版 本 的 设 备 注 册 使 用 函 数 register_chrdev(), 调 用 该 函 数 后 就 可 以 向 系 统 申 请 主 设 备 号, 如 果 register_chrdev() 操 作 成 功, 设 备 名 就 会 出 现 在 /proc/devices 文 件 里 在 关 闭 设 备 时, 通 常 需 要 解 除 原 先 的 设 备 注 册, 此 时 可 使 用 函 数 unregister_chrdev(), 此 后 该 设 备 就 会 从 /proc/devices 里 消 失 其 中 主 设 备 号 和 次 设 备 号 不 能 大 于 255 当 前 不 少 的 字 符 设 备 驱 动 代 码 仍 然 使 用 这 些 早 期 版 本 的 函 数 接 口, 但 在 未 来 内 核 的 代 码 中, 将 不 会 出 现 这 种 编 程 接 口 机 制 因 此 应 该 尽 量 使 用 后 面 讲 述 的 编 程 机 制 register_chrdev() 函 数 格 式 如 表 11.1 所 示 表 11.1 所 需 头 文 件 函 数 原 型 函 数 传 入 值 #include <linux/fs.h> register_chrdev() 函 数 语 法 要 点 int register_chrdev(unsigned int major, const char *name,struct file_operations *fops) major: 设 备 驱 动 程 序 向 系 统 申 请 的 主 设 备 号, 如 果 为 0 则 系 统 为 此 驱 动 程 序 动 态 地 分 配 一 个 主 设 备 号 name: 设 备 名 fops: 对 各 个 调 用 的 入 口 点 函 数 返 回 值 成 功 : 如 果 是 动 态 分 配 主 设 备 号, 此 返 回 所 分 配 的 主 设 备 号 且 设 备 名 就 会 出 现 在 /proc/devices 文 件 里 出 错 : 1 unregister_chrdev() 函 数 格 式 如 下 表 11.2 所 示 : 表 11.2 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 unregister_chrdev() 函 数 语 法 要 点 #include <linux/fs.h> int unregister_chrdev(unsigned int major, const char *name) major: 设 备 的 主 设 备 号, 必 须 和 注 册 时 的 主 设 备 号 相 同 name: 设 备 名 成 功 :0, 且 设 备 名 从 /proc/devices 文 件 里 消 失 出 错 : 1 6
329 (2) 设 备 号 相 关 函 数 在 前 面 已 经 提 到 设 备 号 有 主 设 备 号 和 次 设 备 号, 其 中 主 设 备 号 表 示 设 备 类 型, 对 应 于 确 定 的 驱 动 程 序, 具 备 相 同 主 设 备 号 的 设 备 之 间 共 用 同 一 个 驱 动 程 序, 而 用 次 设 备 号 来 标 识 具 体 物 理 设 备 因 此 在 创 建 字 符 设 备 之 前, 必 须 先 获 得 设 备 的 编 号 ( 可 能 需 要 分 配 多 个 设 备 号 ) 在 Linux 2.6 的 版 本 中, 用 dev_t 类 型 来 描 述 设 备 号 (dev_t 是 32 位 数 值 类 型, 其 中 高 12 位 表 示 主 设 备 号, 低 20 位 表 示 次 设 备 号 ) 用 两 个 宏 MAJOR 和 MINOR 分 别 获 得 dev_t 设 备 号 的 主 设 备 号 和 次 设 备 号, 而 且 用 MKDEV 宏 来 实 现 逆 过 程, 即 组 合 主 设 备 号 和 次 设 备 号 而 获 得 dev_t 类 型 设 备 号 分 配 设 备 号 有 静 态 和 动 态 的 两 种 方 法 静 态 分 配 (register_chrdev_region() 函 数 ) 是 指 在 事 先 知 道 设 备 主 设 备 号 的 情 况 下, 通 过 参 数 函 数 指 定 第 一 个 设 备 号 ( 它 的 次 设 备 号 通 常 为 0) 而 向 系 统 申 请 分 配 一 定 数 目 的 设 备 号 动 态 分 配 (alloc_chrdev_region()) 是 指 通 过 参 数 仅 设 置 第 一 个 次 设 备 号 ( 通 常 为 0, 事 先 不 会 知 道 主 设 备 号 ) 和 要 分 配 的 设 备 数 目 而 系 统 动 态 分 配 所 需 的 设 备 号 通 过 unregister_chrdev_region() 函 数 释 放 已 分 配 的 ( 无 论 是 静 态 的 还 是 动 态 的 ) 设 备 号 它 们 的 函 数 格 式 如 表 11.3 所 示 表 11.3 设 备 号 分 配 与 释 放 函 数 语 法 要 点 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <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 (dev_t first, unsigned int count) first: 要 分 配 的 设 备 号 的 初 始 值 count: 要 分 配 ( 释 放 ) 的 设 备 号 数 目 name: 要 申 请 设 备 号 的 设 备 名 称 ( 在 /proc/devices 和 sysfs 中 显 示 ) dev: 动 态 分 配 的 第 一 个 设 备 号 成 功 :0( 只 限 于 两 种 注 册 函 数 ) 出 错 : 1( 只 限 于 两 种 注 册 函 数 ) (3) 最 新 版 本 的 字 符 设 备 注 册 在 获 得 了 系 统 分 配 的 设 备 号 之 后, 通 过 注 册 设 备 才 能 实 现 设 备 号 和 驱 动 程 序 之 间 的 关 联 这 里 讲 解 2.6 内 核 中 的 字 符 设 备 的 注 册 和 注 销 过 程 在 Linux 内 核 中 使 用 struct cdev 结 构 来 描 述 字 符 设 备, 我 们 在 驱 动 程 序 中 必 须 将 已 分 配 到 的 设 备 号 以 及 设 备 操 作 接 口 ( 即 为 struct file_operations 结 构 ) 赋 予 struct cdev 结 构 变 量 首 先 使 用 cdev_alloc() 函 数 向 系 统 申 请 分 配 struct cdev 结 构, 再 用 cdev_init() 函 数 初 始 化 已 分 配 到 的 结 构 并 与 file_operations 结 构 关 联 起 来 最 后 调 用 cdev_add() 函 数 将 设 备 号 与 struct cdev 结 构 进 行 关 联 并 向 内 核 正 式 报 告 新 设 备 的 注 册, 这 样 新 设 备 可 以 被 用 起 来 了 如 果 要 从 系 统 中 删 除 一 个 设 备, 则 要 调 用 cdev_del() 函 数 具 体 函 数 格 式 如 表 11.4 所 示 表 11.4 最 新 版 本 的 字 符 设 备 注 册 所 需 头 文 件 函 数 原 型 函 数 传 入 值 #include <linux/cdev.h> sturct cdev *cdev_alloc(void) void cdev_init(struct cdev *cdev, struct file_operations *fops) int cdev_add (struct cdev *cdev, dev_t num, unsigned int count) void cdev_del(struct cdev *dev) cdev: 需 要 初 始 化 / 注 册 / 删 除 的 struct cdev 结 构 fops: 该 字 符 设 备 的 file_operations 结 构 num: 系 统 给 该 设 备 分 配 的 第 一 个 设 备 号 count: 该 设 备 对 应 的 设 备 号 数 量 7
330 函 数 返 回 值 成 功 : cdev_alloc: 返 回 分 配 到 的 struct cdev 结 构 指 针 cdev_add: 返 回 0 出 错 : cdev_alloc: 返 回 NULL cdev_add: 返 回 内 核 仍 然 保 留 早 期 版 本 的 register_chrdev() 等 字 符 设 备 相 关 函 数, 其 实 从 内 核 代 码 中 可 以 发 现, 在 register_chrdev() 函 数 的 实 现 中 用 到 cdev_alloc() 和 cdev_add() 函 数, 而 在 unregister_chrdev() 函 数 的 实 现 中 调 用 cdev_del() 函 数 因 此 很 多 代 码 仍 然 使 用 早 期 版 本 接 口, 但 这 种 机 制 将 来 会 从 内 核 中 消 失 前 面 已 经 提 到 字 符 设 备 的 实 际 操 作 在 struct file_operations 结 构 的 一 组 函 数 中 定 义, 并 在 驱 动 程 序 中 需 要 与 字 符 设 备 结 构 关 联 起 来 下 面 讨 论 struct file_operations 结 构 中 最 主 要 的 成 员 函 数 和 它 们 的 用 法 (4) 打 开 设 备 打 开 设 备 的 函 数 接 口 是 open, 根 据 设 备 的 不 同,open 函 数 接 口 完 成 的 功 能 也 有 所 不 同, 但 通 常 情 况 下 在 open 函 数 接 口 中 要 完 成 如 下 工 作 递 增 计 数 器, 检 查 错 误 如 果 未 初 始 化, 则 进 行 初 始 化 识 别 次 设 备 号, 如 果 必 要, 更 新 f_op 指 针 分 配 并 填 写 被 置 于 filp->private_data 的 数 据 结 构 其 中 递 增 计 数 器 是 用 于 设 备 计 数 的 由 于 设 备 在 使 用 时 通 常 会 打 开 多 次, 也 可 以 由 不 同 的 进 程 所 使 用, 所 以 若 有 一 进 程 想 要 删 除 该 设 备, 则 必 须 保 证 其 他 设 备 没 有 使 用 该 设 备 因 此 使 用 计 数 器 就 可 以 很 好 地 完 成 这 项 功 能 这 里, 实 现 计 数 器 操 作 的 是 在 2.6 内 核 早 期 版 本 的 <linux/module.h> 中 定 义 的 3 个 宏, 它 们 在 最 新 版 本 里 早 就 消 失 了, 在 下 面 列 出 只 是 为 了 帮 读 者 理 解 老 版 本 中 的 驱 动 代 码 MOD_INC_USE_COUNT: 计 数 器 加 1 MOD_DEC_USE_COUNT: 计 数 器 减 1 MOD_IN_USE: 计 数 器 非 零 时 返 回 真 另 外, 当 有 多 个 物 理 设 备 时, 就 需 要 识 别 次 设 备 号 来 对 各 个 不 同 的 设 备 进 行 不 同 的 操 作, 在 有 些 驱 动 程 序 中 并 不 需 要 用 到 虽 然 这 是 对 设 备 文 件 执 行 的 第 一 个 操 作, 但 却 不 是 驱 动 程 序 一 定 要 声 明 的 操 作 若 这 个 函 数 的 入 口 为 NULL, 那 么 设 备 的 打 开 操 作 将 永 远 成 功, 但 系 统 不 会 通 知 驱 动 程 序 (5) 释 放 设 备 释 放 设 备 的 函 数 接 口 是 release() 要 注 意 释 放 设 备 和 关 闭 设 备 是 完 全 不 同 的 当 一 个 进 程 释 放 设 备 时, 其 他 进 程 还 能 继 续 使 用 该 设 备, 只 是 该 进 程 暂 时 停 止 对 该 设 备 的 使 用 ; 而 当 一 个 进 程 关 闭 设 备 时, 其 他 进 程 必 须 重 新 打 开 此 设 备 才 能 使 用 它 释 放 设 备 时 要 完 成 的 工 作 如 下 递 减 计 数 器 MOD_DEC_USE_COUNT( 最 新 版 本 已 经 不 再 使 用 ) 释 放 打 开 设 备 时 系 统 所 分 配 的 内 存 空 间 ( 包 括 filp->private_data 指 向 的 内 存 空 间 ) 在 最 后 一 次 释 放 设 备 操 作 时 关 闭 设 备 (6) 读 写 设 备 读 写 设 备 的 主 要 任 务 就 是 把 内 核 空 间 的 数 据 复 制 到 用 户 空 间, 或 者 从 用 户 空 间 复 制 到 内 核 空 间, 也 就 是 将 内 核 空 间 缓 冲 区 里 的 数 据 复 制 到 用 户 空 间 的 缓 冲 区 中 或 者 相 反 这 里 首 先 解 释 一 个 read() 和 write() 函 数 的 入 口 函 数, 如 表 11.5 所 示 表 11.5 所 需 头 文 件 #include <linux/fs.h> read write 函 数 接 口 语 法 要 点 8
331 函 数 原 型 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) filp: 文 件 指 针 函 数 传 入 值 buff: 指 向 用 户 缓 冲 区 count: 传 入 的 数 据 长 度 offp: 用 户 在 文 件 中 的 位 置 函 数 返 回 值 成 功 : 写 入 的 数 据 长 度 虽 然 这 个 过 程 看 起 来 很 简 单, 但 是 内 核 空 间 地 址 和 应 用 空 间 地 址 是 有 很 大 区 别 的, 其 中 一 个 区 别 是 用 户 空 间 的 内 存 是 可 以 被 换 出 的, 因 此 可 能 会 出 现 页 面 失 效 等 情 况 所 以 不 能 使 用 诸 如 memcpy() 之 类 的 函 数 来 完 成 这 样 的 操 作 在 这 里 要 使 用 copy_to_user() 或 copy_from_user() 等 函 数, 它 们 是 用 来 实 现 用 户 空 间 和 内 核 空 间 的 数 据 交 换 的 copy_to_user() 和 copy_from_user() 的 格 式 如 表 11.6 所 示 表 11.6 所 需 头 文 件 函 数 原 型 copy_to_user()/copy_from_user() 函 数 语 法 要 点 #include <asm/uaccess.h> 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) to: 数 据 目 的 缓 冲 区 函 数 传 入 值 from: 数 据 源 缓 冲 区 count: 数 据 长 度 函 数 返 回 值 成 功 : 写 入 的 数 据 长 度 失 败 :-EFAULT 要 注 意, 这 两 个 函 数 不 仅 实 现 了 用 户 空 间 和 内 核 空 间 的 数 据 转 换, 而 且 还 会 检 查 用 户 空 间 指 针 的 有 效 性 如 果 指 针 无 效, 那 么 就 不 进 行 复 制 (7)ioctl 大 部 分 设 备 除 了 读 写 操 作, 还 需 要 硬 件 配 置 和 控 制 ( 例 如, 设 置 串 口 设 备 的 波 特 率 ) 等 很 多 其 他 操 作 在 字 符 设 备 驱 动 中 ioctl 函 数 接 口 给 用 户 提 供 对 设 备 的 非 读 写 操 作 机 制 ioctl 函 数 接 口 的 具 体 格 式 如 表 11.7 所 示 表 11.7 ioctl 函 数 接 口 语 法 要 点 所 需 头 文 件 函 数 原 型 #include <linux/fs.h> int(*ioctl)(struct inode* inode, struct file* filp, unsigned int cmd, unsigned long arg) inode: 文 件 的 内 核 内 部 结 构 指 针 函 数 传 入 值 filp: 被 打 开 的 文 件 描 述 符 cmd: 命 令 类 型 arg: 命 令 相 关 参 数 下 面 列 出 其 他 在 驱 动 程 序 中 常 用 的 内 核 函 数 (8) 获 取 内 存 在 应 用 程 序 中 获 取 内 存 通 常 使 用 函 数 malloc(), 但 在 设 备 驱 动 程 序 中 动 态 开 辟 内 存 可 以 以 字 节 或 页 面 为 单 位 其 中, 以 字 节 为 单 位 分 配 内 存 的 函 数 有 kmalloc(), 注 意 的 是,kmalloc() 函 数 返 回 的 是 物 理 地 址, 而 malloc() 等 返 回 的 是 线 性 虚 拟 地 址, 因 此 在 驱 动 程 序 中 不 能 使 用 malloc() 函 数 与 malloc() 不 同,kmalloc() 申 请 空 间 有 大 小 限 制 长 度 是 2 的 整 次 方, 并 且 不 会 对 所 获 取 的 内 存 空 间 清 零 以 页 为 单 位 分 配 内 存 的 函 数 如 下 所 示 get_zeroed_page(): 获 得 一 个 已 清 零 页 面 get_free_page(): 获 得 一 个 或 几 个 连 续 页 面 9
332 get_dma_pages(): 获 得 用 于 DMA 传 输 的 页 面 与 之 相 对 应 的 释 放 内 存 用 也 有 kfree() 或 free_page 函 数 族 表 11.8 给 出 了 kmalloc() 函 数 的 语 法 格 式 表 11.8 所 需 头 文 件 函 数 原 型 #include <linux/malloc.h> kmalloc() 函 数 语 法 要 点 void *kmalloc(unsigned int len,int flags) len: 希 望 申 请 的 字 节 数 GFP_KERNEL: 内 核 内 存 的 通 常 分 配 方 法, 可 能 引 起 睡 眠 GFP_BUFFER: 用 于 管 理 缓 冲 区 高 速 缓 存 函 数 传 入 值 flags GFP_ATOMIC: 为 中 断 处 理 程 序 或 其 他 运 行 于 进 程 上 下 文 之 外 的 代 码 分 配 内 存, 且 不 会 引 起 睡 眠 GFP_USER: 用 户 分 配 内 存, 可 能 引 起 睡 眠 GFP_HIGHUSER: 优 先 高 端 内 存 分 配 GFP_DMA:DMA 数 据 传 输 请 求 内 存 GFP_HIGHMEN: 请 求 高 端 内 存 函 数 返 回 值 成 功 : 写 入 的 数 据 长 度 失 败 :-EFAULT 表 11.9 给 出 了 kfree() 函 数 的 语 法 格 式 表 11.9 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <linux/malloc.h> void kfree(void * obj) obj: 要 释 放 的 内 存 指 针 成 功 : 写 入 的 数 据 长 度 失 败 :-EFAULT kfree() 函 数 语 法 要 点 表 给 出 了 以 页 为 单 位 的 分 配 函 数 get_free_ page 类 函 数 的 语 法 格 式 10
333 表 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <linux/malloc.h> get_free_ page 类 函 数 语 法 要 点 unsigned long get_zeroed_page(int flags) unsigned long get_free_page(int flags) unsigned long get_free_page(int flags,unsigned long order) unsigned long get_dma_page(int flags,unsigned long order) flags: 同 kmalloc() order: 要 请 求 的 页 面 数, 以 2 为 底 的 对 数 成 功 : 返 回 指 向 新 分 配 的 页 面 的 指 针 失 败 :-EFAULT 专 业 始 于 专 注 卓 识 源 于 远 见 表 给 出 了 基 于 页 的 内 存 释 放 函 数 free_ page 族 函 数 的 语 法 格 式 表 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <linux/malloc.h> free_page 类 函 数 语 法 要 点 unsigned long free_page(unsigned long addr) unsigned long free_pages(unsigned long addr, unsigned long order) addr: 要 释 放 的 内 存 起 始 地 址 order: 要 请 求 的 页 面 数, 以 2 为 底 的 对 数 成 功 : 写 入 的 数 据 长 度 失 败 :-EFAULT (9) 打 印 信 息 就 如 同 在 编 写 用 户 空 间 的 应 用 程 序, 打 印 信 息 有 时 是 很 好 的 调 试 手 段, 也 是 在 代 码 中 很 常 用 的 组 成 部 分 但 是 与 用 户 空 间 不 同, 在 内 核 空 间 要 用 函 数 printk() 而 不 能 用 平 常 的 函 数 printf() printk() 和 printf() 很 类 似, 都 可 以 按 照 一 定 的 格 式 打 印 消 息, 所 不 同 的 是,printk() 还 可 以 定 义 打 印 消 息 的 优 先 级 表 给 出 了 printk() 函 数 的 语 法 格 式 表 所 需 头 文 件 函 数 原 型 函 数 传 入 值 函 数 返 回 值 #include <linux/kernel> int printk(const char * fmt, ) fmt: 日 志 级 别 : 与 printf() 相 同 成 功 :0 失 败 : 1 printk 类 函 数 语 法 要 点 KERN_EMERG: 紧 急 时 间 消 息 KERN_ALERT: 需 要 立 即 采 取 动 作 的 情 况 KERN_CRIT: 临 界 状 态, 通 常 涉 及 严 重 的 硬 件 或 软 件 操 作 失 败 KERN_ERR: 错 误 报 告 KERN_WARNING: 对 可 能 出 现 的 问 题 提 出 警 告 KERN_NOTICE: 有 必 要 进 行 提 示 的 正 常 情 况 KERN_INFO: 提 示 性 信 息 KERN_DEBUG: 调 试 信 息 这 些 不 同 优 先 级 的 信 息 输 出 到 系 统 日 志 文 件 ( 例 如 : /var/log/messages ), 有 时 也 可 以 输 出 到 虚 拟 控 制 台 上 其 中, 对 输 出 给 控 制 台 的 信 息 有 一 个 特 定 的 优 先 级 console_loglevel 只 有 打 印 信 息 的 优 先 级 小 于 这 个 整 数 值, 信 息 才 能 被 输 出 到 虚 拟 控 制 台 上, 否 则, 信 息 仅 仅 被 写 入 到 系 统 日 志 文 件 中 若 不 加 任 何 优 先 级 选 项, 则 消 息 默 认 输 出 到 系 统 日 志 文 件 中 要 开 启 klogd 和 syslogd 服 务, 消 息 才 能 正 常 输 出 11
334 4.proc 文 件 系 统 /proc 文 件 系 统 是 一 个 伪 文 件 系 统, 它 是 一 种 内 核 和 内 核 模 块 用 来 向 进 程 发 送 信 息 的 机 制 这 个 伪 文 件 系 统 让 用 户 可 以 和 内 核 内 部 数 据 结 构 进 行 交 互, 获 取 有 关 系 统 和 进 程 的 有 用 信 息, 在 运 行 时 通 过 改 变 内 核 参 数 来 改 变 设 置 与 其 他 文 件 系 统 不 同,/proc 存 在 于 内 存 之 中 而 不 是 在 硬 盘 上 读 者 可 以 通 过 ls 查 看 /proc 文 件 系 统 的 内 容 表 列 出 了 /proc 文 件 系 统 的 主 要 目 录 内 容 表 /proc 文 件 系 统 主 要 目 录 内 容 目 录 名 称 目 录 内 容 目 录 名 称 目 录 内 容 apm 高 级 电 源 管 理 信 息 locks 内 核 锁 cmdline 内 核 命 令 行 meminfo 内 存 信 息 cpuinfo CPU 相 关 信 息 misc 杂 项 devices 设 备 信 息 ( 块 设 备 / 字 符 设 备 ) modules 加 载 模 块 列 表 dma 使 用 的 DMA 通 道 信 息 mounts 加 载 的 文 件 系 统 filesystems 支 持 的 文 件 系 统 信 息 partitions 系 统 识 别 的 分 区 表 interrupts 中 断 的 使 用 信 息 rtc 实 时 时 钟 ioports I/O 端 口 的 使 用 信 息 stat 全 面 统 计 状 态 表 kcore 内 核 映 像 swaps 对 换 空 间 的 利 用 情 况 kmsg 内 核 消 息 version 内 核 版 本 ksyms 内 核 符 号 表 uptime 系 统 正 常 运 行 时 间 loadavg 负 载 均 衡 除 此 之 外, 还 有 一 些 是 以 数 字 命 名 的 目 录, 它 们 是 进 程 目 录 系 统 中 当 前 运 行 的 每 一 个 进 程 都 有 对 应 的 一 个 目 录 在 /proc 下, 以 进 程 的 PID 号 为 目 录 名, 它 们 是 读 取 进 程 信 息 的 接 口 进 程 目 录 的 结 构 如 表 所 示 表 /proc 中 进 程 目 录 结 构 目 录 名 称 目 录 内 容 目 录 名 称 目 录 内 容 cmdline 命 令 行 参 数 cwd 当 前 工 作 目 录 的 链 接 environ 环 境 变 量 值 exe 指 向 该 进 程 的 执 行 命 令 文 件 fd 一 个 包 含 所 有 文 件 描 述 符 的 目 录 maps 内 存 映 像 mem 进 程 的 内 存 被 利 用 情 况 statm 进 程 内 存 状 态 信 息 stat 进 程 状 态 root 链 接 此 进 程 的 root 目 录 status 进 程 当 前 状 态, 以 可 读 的 方 式 显 示 出 来 用 户 可 以 使 用 cat 命 令 来 查 看 其 中 的 内 容 可 以 看 到,/proc 文 件 系 统 体 现 了 内 核 及 进 程 运 行 的 内 容, 在 加 载 模 块 成 功 后, 读 者 可 以 通 过 查 看 /proc/device 文 件 获 得 相 关 设 备 的 主 设 备 号 11.3 GPIO 驱 动 程 序 实 例 GPIO 工 作 原 理 FS2410 开 发 板 的 S3C2410 处 理 器 具 有 117 个 多 功 能 通 用 I/O(GPIO) 端 口 管 脚, 包 括 GPIO 8 个 端 口 组, 分 别 为 GPA(23 个 输 出 端 口 ) GPB(11 个 输 入 / 输 出 端 口 ) GPC(16 个 输 入 / 输 出 端 口 ) GPD(16 个 输 入 / 输 出 端 口 ) GPE(16 个 输 入 / 输 出 端 口 ) GPF(8 个 输 入 / 输 出 端 口 ) GPH(11 个 输 入 / 输 出 端 口 ) 根 12
335 据 各 种 系 统 设 计 的 需 求, 通 过 软 件 方 法 可 以 将 这 些 端 口 配 置 成 具 有 相 应 功 能 ( 例 如 : 外 部 中 断 或 数 据 总 线 ) 的 端 口 为 了 控 制 这 些 端 口,S3C2410 处 理 器 为 每 个 端 口 组 分 别 提 供 几 种 相 应 的 控 制 寄 存 器 其 中 最 常 用 的 有 端 口 配 置 寄 存 器 (GPACON ~ GPHCON) 和 端 口 数 据 寄 存 器 (GPADAT ~ GPHDAT) 因 为 大 部 分 I/O 管 脚 可 以 提 供 多 种 功 能, 通 过 配 置 寄 存 器 (PnCON) 设 定 每 个 管 脚 用 于 何 种 目 的 数 据 寄 存 器 的 每 位 将 对 应 于 某 个 管 脚 上 的 输 入 或 输 出 所 以 通 过 对 数 据 寄 存 器 (PnDAT) 的 位 读 写, 可 以 进 行 对 每 个 端 口 的 输 入 或 输 出 在 此 主 要 以 发 光 二 极 管 (LED) 和 蜂 鸣 器 为 例, 讨 论 GPIO 设 备 的 驱 动 程 序 它 们 的 硬 件 驱 动 电 路 的 原 理 图 如 图 11.4 所 示 图 11.4 LED( 左 ) 和 蜂 鸣 器 ( 右 ) 的 驱 动 电 路 原 理 图 在 图 11.4 中, 可 知 使 用 S3C2410 处 理 器 的 通 用 I/O 口 GPF4 GPF5 GPF6 和 GPF7 分 别 直 接 驱 动 LED D12 D11 D10 以 及 D9, 而 使 用 GPB0 端 口 驱 动 蜂 鸣 器 4 个 LED 分 别 在 对 应 端 口 (GPF4~GPF7) 为 低 电 平 时 发 亮, 而 蜂 鸣 器 在 GPB0 为 高 电 平 时 发 声 这 5 个 端 口 的 数 据 流 方 向 均 为 输 出 在 表 中, 详 细 描 述 了 GPF 的 主 要 控 制 寄 存 器 GPB 的 相 关 寄 存 器 的 描 述 与 此 类 似, 具 体 可 以 参 考 S3C2410 处 理 器 数 据 手 册 表 GPF 端 口 (GPF0-GPF7) 的 主 要 控 制 寄 存 器 寄 存 器 地 址 R/W 功 能 初 始 值 GPFCON 0x R/W 配 置 GPF 端 口 组 0x0 GPFDAT 0x R/W GPF 端 口 的 数 据 寄 存 器 未 定 义 GPFUP 0x R/W GPF 端 口 的 取 消 上 拉 寄 存 器 0x0 GPFCON 位 描 述 GPF7 [15:14] 00 = 输 入 01 = 输 出 10 = EINT7 11 = 保 留 GPF6 [13:12] 00 = 输 入 01 = 输 出 10 = EINT6 11 = 保 留 GPF5 [11:10] 00 = 输 入 01 = 输 出 10 = EINT5 11 = 保 留 GPF4 [9:8] 00 = 输 入 01 = 输 出 10 = EINT4 11 = 保 留 GPF3 [7:6] 00 = 输 入 01 = 输 出 10 = EINT3 11 = 保 留 GPF2 [5:4] 00 = 输 入 01 = 输 出 10 = EINT2 11 = 保 留 GPF1 [3:2] 00 = 输 入 01 = 输 出 10 = EINT1 11 = 保 留 GPF0 [1:0] 00 = 输 入 01 = 输 出 10 = EINT0 11 = 保 留 GPFDAT 位 描 述 GPF[7:0] [7:0] 每 位 对 应 于 相 应 的 端 口, 若 端 口 用 于 输 入, 则 可 以 通 过 相 应 的 位 读 取 数 据 ; 若 端 口 用 于 输 出, 则 可 以 通 过 相 应 的 位 输 出 数 据 ; 若 端 口 用 于 其 他 功 能, 则 其 值 无 法 确 定 GPFUP 位 描 述 GPF[7:0] [7:0] 0: 向 相 应 端 口 管 脚 赋 予 上 拉 (pull-up) 功 能 1: 取 消 上 拉 功 能 为 了 驱 动 LED 和 蜂 鸣 器, 首 先 通 过 端 口 配 置 寄 存 器 将 5 个 相 应 寄 存 器 配 置 为 输 出 模 式 然 后 通 过 对 端 口 数 据 寄 存 器 的 写 操 作, 实 现 对 每 个 GPIO 设 备 的 控 制 ( 发 亮 或 发 声 ) 在 下 一 个 小 节 中 介 绍 的 驱 动 程 序 中, 13
336 s3c2410_gpio_cfgpin() 函 数 和 s3c2410_gpio_pullup() 函 数 将 进 行 对 某 个 端 口 的 配 置, 而 s3c2410_gpio_setpin() 函 数 实 现 向 数 据 寄 存 器 的 某 个 端 口 的 输 出 GPIO 驱 动 程 序 GPIO 驱 动 程 序 代 码 如 下 所 示 : /* gpio_drv.h */ #ifndef FS2410_GPIO_SET_H #define FS2410_GPIO_SET_H #include <linux/ioctl.h> #define GPIO_DEVICE_NAME "gpio" #define GPIO_DEVICE_FILENAME "/dev/gpio" #define LED_NUM 4 #define GPIO_IOCTL_MAGIC 'G' #define LED_D09_SWT _IOW(GPIO_IOCTL_MAGIC, 0, unsigned int) #define LED_D10_SWT _IOW(GPIO_IOCTL_MAGIC, 1, unsigned int) #define LED_D11_SWT _IOW(GPIO_IOCTL_MAGIC, 2, unsigned int) #define LED_D12_SWT _IOW(GPIO_IOCTL_MAGIC, 3, unsigned int) #define BEEP_SWT _IOW(GPIO_IOCTL_MAGIC, 4, unsigned int) #define LED_SWT_ON 0 #define LED_SWT_OFF 1 #define BEEP_SWT_ON 1 #define BEEP_SWT_OFF 0 #endif /* FS2410_GPIO_SET_H */ /* gpio_drv.c */ #include <linux/config.h> #include <linux/module.h> #include <linux/moduleparam.h> #include <linux/init.h> #include <linux/kernel.h> /* printk() */ #include <linux/slab.h> /* kmalloc() */ #include <linux/fs.h> /* everything... */ #include <linux/errno.h> /* error codes */ #include <linux/types.h> /* size_t */ #include <linux/mm.h> #include <linux/kdev_t.h> #include <linux/cdev.h> #include <linux/delay.h> #include <linux/device.h> #include <asm/io.h> #include <asm/uaccess.h> #include <asm/arch-s3c2410/regs-gpio.h> #include "gpio_drv.h" static int major = 0; /* 采 用 字 符 设 备 号 的 动 态 分 配 */ 14
337 module_param(major, int, 0); /* 以 参 数 的 方 式 可 以 指 定 设 备 的 主 设 备 号 */ 专 业 始 于 专 注 卓 识 源 于 远 见 void s3c2410_gpio_cfgpin(unsigned int pin, unsigned int function) /* 对 某 个 管 脚 进 行 配 置 ( 输 入 / 输 出 / 其 他 功 能 )*/ unsigned long base = S3C2410_GPIO_BASE(pin); /* 获 得 端 口 的 组 基 地 址 */ unsigned long shift = 1; unsigned long mask = 0x03; /* 通 常 用 配 置 寄 存 器 的 两 位 表 示 一 个 端 口 */ unsigned long con; unsigned long flags; if (pin < S3C2410_GPIO_BANKB) shift = 0; mask = 0x01; /* 在 GPA 端 口 中 用 配 置 寄 存 器 的 一 位 表 示 一 个 端 口 */ mask <<= (S3C2410_GPIO_OFFSET(pin) << shift); local_irq_save(flags); /* 保 存 现 场, 保 证 下 面 一 段 是 原 子 操 作 */ con = raw_readl(base + 0x00); con &= ~mask; con = function; raw_writel(con, base + 0x00); /* 向 配 置 寄 存 器 写 入 新 配 置 数 据 */ local_irq_restore(flags); /* 恢 复 现 场 */ void s3c2410_gpio_pullup(unsigned int pin, unsigned int to) /* 配 置 上 拉 功 能 */ unsigned long base = S3C2410_GPIO_BASE(pin); /* 获 得 端 口 的 组 基 地 址 */ unsigned long offs = S3C2410_GPIO_OFFSET(pin);/* 获 得 端 口 的 组 内 偏 移 地 址 */ unsigned long flags; unsigned long up; if (pin < S3C2410_GPIO_BANKB) return; local_irq_save(flags); up = raw_readl(base + 0x08); up &= ~(1 << offs); up = to << offs; raw_writel(up, base + 0x08); /* 向 上 拉 功 能 寄 存 器 写 入 新 配 置 数 据 */ local_irq_restore(flags); void s3c2410_gpio_setpin(unsigned int pin, unsigned int to) /* 向 某 个 管 脚 进 行 输 出 */ unsigned long base = S3C2410_GPIO_BASE(pin); 15
338 unsigned long offs = S3C2410_GPIO_OFFSET(pin); unsigned long flags; unsigned long dat; 专 业 始 于 专 注 卓 识 源 于 远 见 local_irq_save(flags); dat = raw_readl(base + 0x04); dat &= ~(1 << offs); dat = to << offs; raw_writel(dat, base + 0x04); /* 向 数 据 寄 存 器 写 入 新 数 据 */ local_irq_restore(flags); int gpio_open (struct inode *inode, struct file *filp) /* open 操 作 函 数 : 进 行 寄 存 器 配 置 */ s3c2410_gpio_pullup(s3c2410_gpb0, 1); /* BEEP*/ s3c2410_gpio_pullup(s3c2410_gpf4, 1); /* LED D12 */ s3c2410_gpio_pullup(s3c2410_gpf5, 1); /* LED D11 */ s3c2410_gpio_pullup(s3c2410_gpf6, 1); /* LED D10 */ s3c2410_gpio_pullup(s3c2410_gpf7, 1); /* LED D9 */ s3c2410_gpio_cfgpin(s3c2410_gpb0, S3C2410_GPB0_OUTP); s3c2410_gpio_cfgpin(s3c2410_gpf4, S3C2410_GPF4_OUTP); s3c2410_gpio_cfgpin(s3c2410_gpf4, S3C2410_GPF5_OUTP); s3c2410_gpio_cfgpin(s3c2410_gpf4, S3C2410_GPF6_OUTP); s3c2410_gpio_cfgpin(s3c2410_gpf4, S3C2410_GPF7_OUTP); return 0; ssize_t gpio_read(struct file *file, char user *buff, size_t count, loff_t *offp) /* read 操 作 函 数 : 没 有 实 际 功 能 */ return 0; ssize_t gpio_write(struct file *file, const char user *buff, size_t count, loff_t *offp) /* write 操 作 函 数 : 没 有 实 际 功 能 */ return 0; int switch_gpio(unsigned int pin, unsigned int swt) /* 向 5 个 端 口 中 的 一 个 输 出 ON/OFF 值 */ if (!((pin <= S3C2410_GPF7) && (pin >= S3C2410_GPF4)) && (pin!= S3C2410_GPB0)) printk("unsupported pin"); return 1; s3c2410_gpio_setpin(pin, swt); return 0; 16
339 专 业 始 于 专 注 卓 识 源 于 远 见 static int gpio_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) /* ioctl 函 数 接 口 : 主 要 接 口 的 实 现 对 5 个 GPIO 设 备 进 行 控 制 ( 发 亮 或 发 声 ) */ unsigned int swt = (unsigned int)arg; switch (cmd) case LED_D09_SWT: switch_gpio(s3c2410_gpf7, swt); break; case LED_D10_SWT: switch_gpio(s3c2410_gpf6, swt); break; case LED_D11_SWT: switch_gpio(s3c2410_gpf5, swt); break; case LED_D12_SWT: switch_gpio(s3c2410_gpf4, swt); break; case BEEP_SWT: switch_gpio(s3c2410_gpb0, swt); break; default: printk("unsupported command\n"); break; return 0; static int gpio_release(struct inode *node, struct file *file) 17
340 /* release 操 作 函 数, 熄 灭 所 有 灯 和 关 闭 蜂 鸣 器 */ switch_gpio(s3c2410_gpb0, BEEP_SWT_OFF); switch_gpio(s3c2410_gpf4, LED_SWT_OFF); switch_gpio(s3c2410_gpf5, LED_SWT_OFF); switch_gpio(s3c2410_gpf6, LED_SWT_OFF); switch_gpio(s3c2410_gpf7, LED_SWT_OFF); return 0; 专 业 始 于 专 注 卓 识 源 于 远 见 static void gpio_setup_cdev(struct cdev *dev, int minor, struct file_operations *fops) /* 字 符 设 备 的 创 建 和 注 册 */ int err, devno = MKDEV(major, minor); cdev_init(dev, fops); dev->owner = THIS_MODULE; dev->ops = fops; err = cdev_add (dev, devno, 1); if (err) printk (KERN_NOTICE "Error %d adding gpio %d", err, minor); static struct file_operations gpio_fops = /* gpio 设 备 的 file_operations 结 构 定 义 */.owner = THIS_MODULE,.open = gpio_open, /* 进 行 初 始 化 配 置 */.release = gpio_release, /* 关 闭 设 备 */.read = gpio_read,.write = gpio_write,.ioctl = gpio_ioctl, /* 实 现 主 要 控 制 功 能 */ ; static struct cdev gpio_devs; static int gpio_init(void) int result; dev_t dev = MKDEV(major, 0); if (major) /* 设 备 号 的 动 态 分 配 */ result = register_chrdev_region(dev, 1, GPIO_DEVICE_NAME); else /* 设 备 号 的 动 态 分 配 */ result = alloc_chrdev_region(&dev, 0, 1, GPIO_DEVICE_NAME); major = MAJOR(dev); 18
341 if (result < 0) printk(kern_warning "Gpio: unable to get major %d\n", major); return result; gpio_setup_cdev(&gpio_devs, 0, &gpio_fops); printk("the major of the gpio device is %d\n", major); return 0; 专 业 始 于 专 注 卓 识 源 于 远 见 static void gpio_cleanup(void) cdev_del(&gpio_devs); /* 字 符 设 备 的 注 销 */ unregister_chrdev_region(mkdev(major, 0), 1); /* 设 备 号 的 注 销 */ printk("gpio device uninstalled\n"); module_init(gpio_init); module_exit(gpio_cleanup); MODULE_AUTHOR("David"); MODULE_LICENSE("Dual BSD/GPL"); 下 面 列 出 GPIO 驱 动 程 序 的 测 试 用 例 : /* gpio_test.c */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include "gpio_drv.h" int led_timer(int dev_fd, int led_no, unsigned int time) /* 指 定 LED 发 亮 一 段 时 间 之 后 熄 灭 它 */ led_no %= 4; ioctl(dev_fd, LED_D09_SWT + led_no, LED_SWT_ON); /* 发 亮 */ sleep(time); ioctl(dev_fd, LED_D09_SWT + led_no, LED_SWT_OFF); /* 熄 灭 */ int beep_timer(int dev_fd, unsigned int time) /* 开 蜂 鸣 器 一 段 时 间 之 后 关 闭 */ ioctl(dev_fd, BEEP_SWT, BEEP_SWT_ON); /* 发 声 */ sleep(time); ioctl(dev_fd, BEEP_SWT, BEEP_SWT_OFF); /* 关 闭 */ 19
342 int main() int i = 0; int dev_fd; /* 打 开 gpio 设 备 */ dev_fd = open(gpio_device_filename, O_RDWR O_NONBLOCK); if ( dev_fd == -1 ) printf("cann't open gpio device file\n"); exit(1); while(1) i = (i + 1) % 4; led_timer(dev_fd, i, 1); beep_timer(dev_fd, 1); close(dev_fd); return 0; 具 体 运 行 过 程 如 下 所 示 首 先 编 译 并 加 载 驱 动 程 序 : $ make clean;make /* 驱 动 程 序 的 编 译 */ $ insmod gpio_drv.ko /* 加 载 gpio 驱 动 */ $ cat /proc/devices /* 通 过 这 个 命 令 可 以 查 到 gpio 设 备 的 主 设 备 号 */ $ mknod /dev/gpio c /* 假 设 主 设 备 号 为 252, 创 建 设 备 文 件 节 点 */ 然 后 编 译 并 运 行 驱 动 测 试 程 序 : $ arm-linux-gcc o gpio_test gpio_test.c $./gpio_test 运 行 结 果 为 4 个 LED 轮 流 闪 烁, 同 时 蜂 鸣 器 以 一 定 周 期 发 出 声 响 11.4 块 设 备 驱 动 编 程 块 设 备 通 常 指 一 些 需 要 以 块 ( 如 512 字 节 ) 的 方 式 写 入 的 设 备, 如 IDE 硬 盘 SCSI 硬 盘 光 驱 等 它 的 驱 动 程 序 的 编 写 过 程 与 字 符 型 设 备 驱 动 程 序 的 编 写 有 很 大 的 区 别 块 设 备 驱 动 编 程 接 口 相 对 复 杂, 不 如 字 符 设 备 明 晰 易 用 块 设 备 驱 动 程 序 对 整 个 系 统 的 性 能 影 响 较 大, 速 度 和 效 率 是 设 计 块 设 备 驱 动 程 要 重 点 考 虑 的 问 题 系 统 中 使 用 缓 冲 区 与 访 问 请 求 的 优 化 管 理 ( 合 并 与 重 新 排 序 ) 来 提 高 系 统 性 能 1. 编 程 流 程 说 明 块 设 备 驱 动 程 序 的 编 写 流 程 同 字 符 设 备 驱 动 程 序 的 编 写 流 程 很 类 似, 也 包 括 了 注 册 和 使 用 两 部 分 但 与 字 符 驱 动 设 备 所 不 同 的 是, 块 设 备 驱 动 程 序 包 括 一 个 request 请 求 队 列 它 是 当 内 核 安 排 一 次 数 据 传 输 时 在 列 表 中 的 一 个 请 求 20
343 队 列, 以 最 大 化 系 统 性 能 为 原 则 进 行 排 序 在 后 面 的 读 写 操 作 时 会 详 细 讲 解 这 个 函 数, 图 11.5 为 块 设 备 驱 动 程 序 的 流 程 图, 请 读 者 注 意 与 字 符 设 备 驱 动 程 序 的 区 别 图 11.5 块 设 备 驱 动 程 序 流 程 图 2. 重 要 数 据 结 构 每 个 块 设 备 物 理 实 体 由 一 个 gendisk 结 构 体 来 表 示 ( 在 </linux/genhd.h> 中 定 义 ), 每 个 gendisk 可 以 支 持 多 个 分 区 每 个 gendisk 中 包 含 了 本 物 理 实 体 的 全 部 信 息 以 及 操 作 函 数 接 口 整 个 块 设 备 的 注 册 过 程 是 围 绕 gendisk 来 展 开 的 在 驱 动 程 序 中 需 要 初 始 化 的 gendisk 的 一 些 成 员 如 下 所 示 struct gendisk int major; /* 主 设 备 号 */ int first_minor; /* 第 一 个 次 设 备 号 */ ; int minors; /* 次 设 备 号 个 数, 一 个 块 设 备 至 少 需 要 使 用 一 个 次 设 备 号, 而 且 块 设 备 的 每 个 分 区 都 需 要 一 个 次 设 备 号, 因 此 这 个 成 员 等 于 1, 则 表 明 该 块 设 备 是 不 可 被 分 区 的, 否 则 可 以 包 含 minors 1 个 分 区 */ char disk_name[32]; /* 块 设 备 名 称, 在 /proc/partions 中 显 示 */ struct hd_struct **part; /* 分 区 表 */ struct block_device_operations *fops; /* 块 设 备 操 作 接 口, 与 字 符 设 备 的 file_operations 结 构 对 应 */ struct request_queue *queue; /* I/O 请 求 队 列 */ void *private_data; /* 指 向 驱 动 程 序 私 有 数 据 */ sector_t capacity; /* 块 设 备 可 包 含 的 扇 区 数 */ /* 其 他 省 略 */ 与 字 符 设 备 驱 动 程 序 一 样, 块 设 备 驱 动 程 序 也 包 含 一 个 在 <linux/fs.h> 中 定 义 的 block_device_operations 结 构, 其 定 义 如 下 所 示 struct block_device_operations int (*open) (struct inode *, struct file *); int (*release) (struct inode *, struct file *); int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned, unsigned long); long (*compat_ioctl) (struct file *, unsigned, unsigned long); int (*direct_access) (struct block_device *, sector_t, unsigned long *); int (*media_changed) (struct gendisk *); int (*revalidate_disk) (struct gendisk *); int (*getgeo)(struct block_device *, struct hd_geometry *); 21
344 struct module *owner; ; 从 该 结 构 的 定 义 中, 可 以 看 出 块 设 备 并 不 提 供 read() write() 等 函 数 接 口 对 块 设 备 的 读 写 请 求 都 是 以 异 步 方 式 发 送 到 设 备 相 关 的 request 队 列 之 中 3. 块 设 备 注 册 和 初 始 化 块 设 备 的 初 始 化 过 程 要 比 字 符 设 备 复 杂, 它 既 需 要 像 字 符 设 备 一 样 在 加 载 内 核 时 完 成 一 定 的 工 作, 还 需 要 在 内 核 编 译 时 增 加 一 些 内 容 块 设 备 驱 动 程 序 初 始 化 时, 由 驱 动 程 序 的 init() 完 成 块 设 备 的 初 始 化 过 程 如 图 11.6 所 示 图 11.6 块 设 备 驱 动 程 序 初 始 化 过 程 (1) 向 内 核 注 册 使 用 register_blkdev() 函 数 对 设 备 进 行 注 册 int register_blkdev(unsigned int major, const char *name); 其 中 参 数 major 为 要 注 册 的 块 设 备 的 主 设 备 号, 如 果 其 值 等 于 0, 则 系 统 动 态 分 配 并 返 回 主 设 备 号 参 数 name 为 设 备 名, 在 /proc/devices 中 显 示 如 果 出 错, 则 该 函 数 返 回 负 值 与 其 对 应 的 块 设 备 的 注 销 函 数 为 unregister_blkdev(), 其 格 式 如 下 所 示 int unregister_blkdev(unsigned int major, const char *name); 其 参 数 必 须 与 注 册 函 数 中 的 参 数 相 同 如 果 出 错 则 返 回 负 值 (2) 申 请 并 初 始 化 请 求 队 列 这 一 步 要 调 用 blk_init_queue() 函 数 来 申 请 并 初 始 化 请 求 队 列, 其 格 式 如 下 所 示 struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) 其 中 参 数 rfn 是 请 求 队 列 的 处 理 函 数 指 针, 它 负 责 执 行 块 设 备 的 读 写 请 求 参 数 lock 为 自 旋 锁, 用 于 控 制 对 所 分 配 的 队 列 的 访 问 (3) 初 始 化 并 注 册 gendisk 结 构 内 核 提 供 的 gendisk 结 构 相 关 函 数 如 表 所 示 表 gendisk 结 构 相 关 函 数 函 数 格 式 struct gendisk *alloc_disk(int minors) void add_disk(struct gendisk *disk) void del_gendisk(struct gendisk *disk) 说 明 动 态 分 配 gendisk 结 构, 参 数 为 次 设 备 号 的 个 数 向 系 统 注 册 gendisk 结 构 从 系 统 注 销 gendisk 结 构 首 先 使 用 alloc_disk() 函 数 动 态 分 配 gendisk 结 构, 接 下 来, 对 gendisk 结 构 的 主 设 备 号 (major) 次 设 备 号 相 关 成 员 (first_minor 和 minors) 块 设 备 操 作 函 数 (fops) 请 求 队 列 (queue) 可 包 含 的 扇 区 数 (capacity) 以 及 设 备 名 称 (disk_name) 等 成 员 进 行 初 始 化 在 完 成 对 gendisk 的 分 配 和 初 始 化 之 后, 调 用 add_disk() 函 数 向 系 统 注 册 块 设 备 在 卸 载 gendisk 结 构 的 时 候, 要 调 用 del_gendisk() 函 数 22
345 4. 块 设 备 请 求 处 理 块 设 备 驱 动 中 一 般 要 实 现 一 个 请 求 队 列 处 理 函 数 来 处 理 队 列 中 的 请 求 从 块 设 备 的 运 行 流 程, 可 知 请 求 处 理 是 块 设 备 的 基 本 处 理 单 位, 也 是 最 核 心 的 部 分 对 块 设 备 的 读 写 操 作 被 封 装 到 了 每 一 个 请 求 中 已 经 提 过 调 用 blk_init_queue() 函 数 来 申 请 并 初 始 化 请 求 队 列 表 列 出 了 一 些 与 请 求 处 理 相 关 的 函 数 表 请 求 处 理 相 关 函 数 函 数 格 式 说 明 request_queue_t *blk_alloc_queue(int gfp_mask) request_queue_t *blk_init_queue (request_fn_proc *rfn, spinlock_t *lock) struct request *blk_get_request (request_queue_t *q, int rw, int gfp_mask) void blk_requeue_request(request_queue_t *q, struct request *rq) void blk_queue_max_sectors (request_queue_t *q, unsigned short max_sectors) void blk_queue_max_phys_segments (request_queue_t *q, unsigned short max_segments) void end_request(struct request *req, int uptodate) void blk_queue_hardsect_size (request_queue_t *q, unsigned short size) 分 配 请 求 队 列 分 配 并 初 始 化 请 求 队 列 从 队 列 中 获 取 一 个 请 求 将 请 求 再 次 加 入 队 列 设 置 最 大 访 问 扇 区 数 设 置 最 大 物 理 段 数 结 束 本 次 请 求 处 理 设 置 物 理 扇 区 大 小 以 上 简 单 地 介 绍 了 块 设 备 驱 动 编 程 的 最 基 本 的 概 念 和 流 程 更 深 入 的 内 容 不 是 本 书 的 重 点, 有 兴 趣 的 读 者 可 以 参 考 其 他 书 籍 11.5 中 断 编 程 前 面 所 讲 述 的 驱 动 程 序 中 都 没 有 涉 及 中 断 处 理, 而 实 际 上, 有 很 多 Linux 的 驱 动 都 是 通 过 中 断 的 方 式 来 进 行 内 核 和 硬 件 的 交 互 中 断 机 制 提 供 了 硬 件 和 软 件 之 间 异 步 传 递 信 息 的 方 式 硬 件 设 备 在 发 生 某 个 事 件 时 通 过 中 断 通 知 软 件 进 行 处 理 中 断 实 现 了 硬 件 设 备 按 需 获 得 处 理 器 关 注 的 机 制, 与 查 询 方 式 相 比 可 以 大 大 节 省 CPU 资 源 的 开 销 在 此 将 介 绍 在 驱 动 程 序 中 用 于 申 请 中 断 的 request_irq() 调 用, 和 用 于 释 放 中 断 的 free_irq() 调 用 request_irq() 函 数 调 用 的 格 式 如 下 所 示 : int request_irq(unsigned int irq, void (*handler)(int irq, void *dev_id, struct pt_regs *regs), unsigned long irqflags, const char * devname, oid *dev_id); 其 中 irq 是 要 申 请 的 硬 件 中 断 号 在 Intel 平 台, 范 围 是 0~15 参 数 handler 为 将 要 向 系 统 注 册 的 中 断 处 理 函 数 这 是 一 个 回 调 函 数, 中 断 发 生 时, 系 统 调 用 这 个 函 数, 传 入 的 参 数 包 括 硬 件 中 断 号 设 备 id 以 及 寄 存 器 值 设 备 id 就 是 在 调 用 request_irq() 时 传 递 给 系 统 的 参 数 dev_id 参 数 irqflags 是 中 断 处 理 的 一 些 属 性, 其 中 比 较 重 要 的 有 SA_INTERRUPT 这 个 参 数 用 于 标 明 中 断 处 理 程 序 是 快 速 处 理 程 序 ( 设 置 SA_INTERRUPT) 还 是 慢 速 处 理 程 序 ( 不 设 置 SA_INTERRUPT) 快 速 处 理 程 序 被 调 用 时 屏 蔽 所 有 中 断 慢 速 处 理 程 序 只 屏 蔽 正 在 处 理 的 中 断 还 有 一 个 SA_SHIRQ 属 性, 设 置 了 以 后 运 行 多 个 设 备 共 享 中 断, 在 中 断 处 理 程 序 中 根 据 dev_id 区 分 不 同 设 备 产 生 的 中 断 参 数 devname 为 设 备 名, 会 在 /dev/interrupts 中 显 示 参 数 dev_id 在 中 断 共 享 时 会 用 到 一 般 设 置 为 这 个 设 备 的 device 结 构 本 身 或 者 NULL 中 断 处 理 程 序 可 以 用 dev_id 找 到 相 应 的 控 制 这 个 中 断 的 设 备, 或 者 用 irq2dev_map() 找 到 中 断 对 应 的 设 备 释 放 中 断 的 free_irq() 函 数 调 用 的 格 式 如 下 所 示 该 函 数 的 参 数 与 request_irq() 相 同 23
346 void free_irq(unsigned int irq, void *dev_id); 11.6 按 键 驱 动 程 序 实 例 按 键 工 作 原 理 LED 和 蜂 鸣 器 是 最 简 单 的 GPIO 的 应 用, 都 不 需 要 任 何 外 部 输 入 或 控 制 按 键 同 样 使 用 GPIO 接 口, 但 按 键 本 身 需 要 外 部 的 输 入, 即 在 驱 动 程 序 中 要 处 理 外 部 中 断 按 键 硬 件 驱 动 原 理 图 如 图 11-7 所 示 在 图 11-7 的 4 4 矩 阵 按 键 (K1~K16) 电 路 中, 使 用 4 个 输 入 / 输 出 端 口 (EINT0 EINT2 EINT11 和 EINT19) 和 4 个 输 出 端 口 (KSCAN0~KSCAN3) 图 11.7 按 键 驱 动 电 路 原 理 图 按 键 驱 动 电 路 使 用 的 端 口 和 对 应 的 寄 存 器 如 表 所 示 表 按 键 电 路 的 主 要 端 口 管 脚 端 口 输 入 / 输 出 管 脚 端 口 输 入 / 输 出 KEYSCAN0 GPE11 输 出 EINT0 EINIT0/GPF0 输 入 / 输 出 KEYSCAN1 GPG6 输 出 EINT2 EINT2/GPF2 输 入 / 输 出 KEYSCAN2 GPE13 输 出 EINT11 EINT11/GPG3 输 入 / 输 出 KEYSCAN3 GPG2 输 出 EINT19 EINT19/GPG11 输 入 / 输 出 因 为 通 常 中 断 端 口 是 比 较 珍 贵 且 有 限 的 资 源, 所 以 在 本 电 路 设 计 中,16 个 按 键 复 用 了 4 个 中 断 线 那 怎 么 样 才 能 及 时 而 准 确 地 对 矩 阵 按 键 进 行 扫 描 呢? 某 个 中 断 的 产 生 表 示, 与 它 所 对 应 的 矩 阵 行 的 4 个 按 键 中, 至 少 有 一 个 按 键 被 按 住 了 因 此 可 以 通 过 查 看 产 生 了 哪 个 中 断, 来 确 定 在 矩 阵 的 哪 一 行 中 发 生 了 按 键 操 作 ( 按 住 或 释 放 ) 例 如, 如 果 产 生 了 外 部 2 号 线 中 断 (EINT2 变 为 低 电 平 ), 则 表 示 K7 K8 K9 和 K15 中 至 少 有 一 个 按 键 被 按 住 了 这 时 候 4 个 EINT 端 口 应 该 通 过 GPIO 配 置 寄 存 器 被 设 置 为 外 部 中 断 端 口, 而 且 4 个 KSCAN 端 口 的 输 出 必 须 为 低 电 平 在 确 定 按 键 操 作 所 在 行 的 位 置 之 后, 我 们 还 得 查 看 按 键 操 作 所 在 列 的 位 置 此 时 要 使 用 KSCAN 端 口 组, 同 时 将 4 个 EINT 端 口 配 置 为 通 用 输 入 端 口 ( 而 不 是 中 断 端 口 ) 在 4 个 KSCAN 端 口 中, 轮 流 将 其 中 某 一 个 端 口 的 输 出 置 为 低 电 平, 其 他 3 个 端 口 的 输 出 置 为 高 电 平 这 样 逐 列 进 行 扫 描, 直 到 按 键 所 在 列 的 KSCAN 端 口 输 出 为 低 电 平, 此 时 按 键 操 作 所 在 行 的 EINT 管 脚 的 输 入 端 口 的 值 会 变 成 低 电 平 例 如, 在 确 认 产 生 了 外 部 2 号 中 断 之 后, 进 行 逐 列 扫 描 若 发 现 在 KSCAN1 为 低 电 平 时 ( 其 他 端 口 输 出 均 为 高 电 平 ),GPF2 (EINT2 管 脚 的 输 入 端 口 ) 变 为 低 电 平, 则 可 以 断 定 按 键 K8 被 按 住 了 以 上 的 讨 论 都 是 在 按 键 的 理 想 状 态 下 进 行 的, 但 实 际 的 按 键 动 作 会 在 短 时 间 ( 几 毫 秒 至 几 十 毫 秒 ) 内 产 生 24
347 信 号 抖 动 例 如, 当 按 键 被 按 下 时, 其 动 作 就 像 弹 簧 的 若 干 次 往 复 运 动, 将 产 生 几 个 脉 冲 信 号 一 次 按 键 操 作 将 会 产 生 若 干 次 按 键 中 断, 从 而 会 产 生 抖 动 现 象 因 此 驱 动 程 序 中 必 须 要 解 决 去 除 抖 动 所 产 生 的 毛 刺 信 号 的 问 题 按 键 驱 动 程 序 首 先 按 键 设 备 相 关 的 数 据 结 构 的 定 义 如 下 所 示 : /* butt_drv.h */ typedef struct _st_key_info_matrix /* 按 键 数 据 结 构 */ unsigned char key_id; /* 按 键 ID */ unsigned int irq_no; /* 对 应 的 中 断 号 */ unsigned int irq_gpio_port; /* 对 应 的 中 断 线 的 输 入 端 口 地 址 */ unsigned int kscan_gpio_port; /* 对 应 的 KSCAN 端 口 地 址 */ st_key_info_matrix; typedef struct _st_key_buffer /* 按 键 缓 冲 数 据 结 构 */ unsigned long jiffy[max_key_count]; /* 按 键 时 间, 5s 以 前 的 铵 键 作 废 */ unsigned char buf[max_key_count]; /* 按 键 缓 冲 区 */ unsigned int head,tail; /* 按 键 缓 冲 区 头 和 尾 */ st_key_buffer; 下 面 是 矩 阵 按 键 数 组 的 定 义, 数 组 元 素 的 信 息 ( 一 个 按 键 信 息 ) 按 照 0 行 0 列,0 行 1 列,,3 行 2 列, 3 行 3 列 的 顺 序 逐 行 排 列 static st_key_info_matrix key_info_matrix[max_column][max_row] = 10, IRQ_EINT0, S3C2410_GPF0, S3C2410_GPE11, /* 0 行 0 列 */ 11, IRQ_EINT0, S3C2410_GPF0, S3C2410_GPG6, 12, IRQ_EINT0, S3C2410_GPF0, S3C2410_GPE13, 16, IRQ_EINT0, S3C2410_GPF0, S3C2410_GPG2, 7, IRQ_EINT2, S3C2410_GPF2, S3C2410_GPE11, /* 1 行 0 列 */ 8, IRQ_EINT2, S3C2410_GPF2, S3C2410_GPG6, 9, IRQ_EINT2, S3C2410_GPF2, S3C2410_GPE13, 15, IRQ_EINT2, S3C2410_GPF2, S3C2410_GPG2, 4, IRQ_EINT11, S3C2410_GPG3, S3C2410_GPE11, /* 2 行 0 列 */ 5, IRQ_EINT11, S3C2410_GPG3, S3C2410_GPG6, 6, IRQ_EINT11, S3C2410_GPG3, S3C2410_GPE13, 14, IRQ_EINT11, S3C2410_GPG3, S3C2410_GPG2, 1, IRQ_EINT19, S3C2410_GPG11, S3C2410_GPE11, /* 3 行 0 列 */ 2, IRQ_EINT19, S3C2410_GPG11, S3C2410_GPG6, 3, IRQ_EINT19, S3C2410_GPG11, S3C2410_GPE13, 25
348 13, IRQ_EINT19, S3C2410_GPG11, S3C2410_GPG2, ; 下 面 是 与 按 键 相 关 的 端 口 的 初 始 化 函 数 这 些 函 数 已 经 在 简 单 的 GPIO 字 符 设 备 驱 动 程 序 里 被 使 用 过 此 外,set_irq_type() 函 数 用 于 设 定 中 断 线 的 类 型, 在 本 实 例 中 通 过 该 函 数 将 4 个 中 断 线 的 类 型 配 置 为 下 降 沿 触 发 式 static void init_gpio(void) s3c2410_gpio_cfgpin(s3c2410_gpe11, S3C2410_GPE11_OUTP); /* GPE11 */ s3c2410_gpio_setpin(s3c2410_gpe11, 0); s3c2410_gpio_cfgpin(s3c2410_gpe13, S3C2410_GPE13_OUTP); /* GPE13 */ s3c2410_gpio_setpin(s3c2410_gpe13, 0); s3c2410_gpio_cfgpin(s3c2410_gpg2, S3C2410_GPG2_OUTP); /* GPG2 */ s3c2410_gpio_setpin(s3c2410_gpg2, 0); s3c2410_gpio_cfgpin(s3c2410_gpg6, S3C2410_GPG6_OUTP); /* GPG6 */ s3c2410_gpio_setpin(s3c2410_gpg6, 0); s3c2410_gpio_cfgpin(s3c2410_gpf0, S3C2410_GPF0_EINT0); /* GPF0 */ s3c2410_gpio_cfgpin(s3c2410_gpf2, S3C2410_GPF2_EINT2); /* GPF2 */ s3c2410_gpio_cfgpin(s3c2410_gpg3, S3C2410_GPG3_EINT11); /* GPG3 */ s3c2410_gpio_cfgpin(s3c2410_gpg11, S3C2410_GPG11_EINT19); /* GPG11 */ set_irq_type(irq_eint0, IRQT_FALLING); set_irq_type(irq_eint2, IRQT_FALLING); set_irq_type(irq_eint11, IRQT_FALLING); set_irq_type(irq_eint19, IRQT_FALLING); 下 面 讲 解 按 键 驱 动 的 主 要 接 口, 以 下 为 驱 动 模 块 的 入 口 和 卸 载 函 数 /* 初 始 化 并 添 加 struct cdev 结 构 到 系 统 之 中 */ static void button_setup_cdev(struct cdev *dev, int minor, struct file_operations *fops) int err; int devno = MKDEV(button_major,minor); cdev_init(dev, fops); /* 初 始 化 结 构 体 struct cdev */ dev->owner = THIS_MODULE; dev->ops = fops; /* 关 联 到 设 备 的 file_operations 结 构 */ err = cdev_add(dev, devno, 1); /* 将 struct cdev 结 构 添 加 到 系 统 之 中 */ if (err) printk(kern_info"error %d adding button %d\n",err, minor); /* 驱 动 初 始 化 */ static int button_init(void) 26
349 int ret; /* 将 主 设 备 号 和 次 设 备 号 定 义 到 一 个 dev_t 数 据 类 型 的 结 构 体 之 中 */ dev_t dev = MKDEV(button_major, 0); if (button_major) /* 静 态 注 册 一 个 设 备, 设 备 号 先 前 指 定 好, 并 设 定 设 备 名, 用 cat /proc/devices 来 查 看 */ ret = register_chrdev_region(dev, 1, BUTTONS_DEVICE_NAME); else /* 由 系 统 动 态 分 配 主 设 备 号 */ ret = alloc_chrdev_region(&dev, 0, 1, BUTTONS_DEVICE_NAME); button_major = MAJOR(dev); /* 获 得 主 设 备 号 */ if (ret < 0) printk(kern_warning"button:unable to get major %d\n",button_major); return ret; /* 初 始 化 和 添 加 结 构 体 struct cdev 到 系 统 之 中 */ button_setup_cdev(&button_dev, 0, &button_fops); printk("button driver initialized.\n"); return 0; /* 驱 动 卸 载 */ static void exit button_exit(void) cdev_del(&button_dev); /* 删 除 结 构 体 struct cdev */ /* 卸 载 设 备 驱 动 所 占 有 的 资 源 */ unregister_chrdev_region(mkdev(button_major, 0), 1); printk("button driver uninstalled\n"); module_init(button_init); /* 初 始 化 设 备 驱 动 程 序 的 入 口 */ module_exit(button_exit); /* 卸 载 设 备 驱 动 程 序 的 入 口 */ MODULE_AUTHOR("David"); MODULE_LICENSE("Dual BSD/GPL"); 按 键 字 符 设 备 的 file_operations 结 构 定 义 为 : static struct file_operations button_fops =.owner = THIS_MODULE,.ioctl = button_ioctl,.open = button_open,.read = button_read,.release = button_release, ; 以 下 为 open 和 release 函 数 接 口 的 实 现 /* 打 开 文 件, 申 请 中 断 */ 27
350 static int button_open(struct inode *inode,struct file *filp) int ret = nonseekable_open(inode, filp); if (ret < 0) return ret; 专 业 始 于 专 注 卓 识 源 于 远 见 init_gpio(); /* 相 关 GPIO 端 口 的 初 始 化 */ ret = request_irqs(); /* 申 请 4 个 中 断 */ if (ret < 0) return ret; init_keybuffer(); /* 初 始 化 按 键 缓 冲 数 据 结 构 */ return ret; /* 关 闭 文 件, 屏 蔽 中 断 */ static int button_release(struct inode *inode,struct file *filp) free_irqs(); /* 屏 蔽 中 断 */ return 0; 在 open 函 数 接 口 中, 进 行 了 GPIO 端 口 的 初 始 化 申 请 硬 件 中 断 以 及 按 键 缓 冲 的 初 始 化 等 工 作 在 以 前 的 章 节 中 提 过, 中 断 端 口 是 比 较 宝 贵 而 且 数 量 有 限 的 资 源 因 此 需 要 注 意, 最 好 要 在 第 一 次 打 开 设 备 时 申 请 ( 调 用 request_irq 函 数 ) 中 断 端 口, 而 不 是 在 驱 动 模 块 加 载 的 时 候 申 请 如 果 已 加 载 的 设 备 驱 动 占 用 而 在 一 定 时 间 段 内 不 使 用 某 些 中 断 资 源, 则 这 些 资 源 不 会 被 其 他 驱 动 所 使 用, 只 能 白 白 浪 费 掉 而 在 打 开 设 备 的 时 候 ( 调 用 open 函 数 接 口 ) 申 请 中 断, 则 不 同 的 设 备 驱 动 可 以 共 享 这 些 宝 贵 的 中 断 资 源 以 下 为 中 断 申 请 和 释 放 的 部 分 以 及 中 断 处 理 函 数 /* 中 断 处 理 函 数, 其 中 irq 为 中 断 号 */ static irqreturn_t button_irq(int irq, void *dev_id, struct pt_regs *regs) unsigned char uckey = 0; disable_irqs(); /* 屏 蔽 中 断 */ /* 延 迟 50ms, 屏 蔽 按 键 毛 刺 */ udelay(50000); uckey = button_scan(irq); /* 扫 描 按 键, 获 得 进 行 操 作 的 按 键 的 ID */ if ((uckey >= 1) && (uckey <= 16)) /* 如 果 缓 冲 区 已 满, 则 不 添 加 */ if (((key_buffer.head + 1) & (MAX_KEY_COUNT - 1))!= key_buffer.tail) spin_lock_irq(&buffer_lock); key_buffer.buf[key_buffer.tail] = uckey; key_buffer.jiffy[key_buffer.tail] = get_tick_count(); 28
351 key_buffer.tail ++; key_buffer.tail &= (MAX_KEY_COUNT -1); spin_unlock_irq(&buffer_lock); init_gpio(); /* 初 始 化 GPIO 端 口, 主 要 是 为 了 恢 复 中 断 端 口 配 置 */ enable_irqs(); /* 开 启 中 断 */ return IRQ_HANDLED;/* 2.6 内 核 返 回 值 一 般 是 这 个 宏 */ /* 申 请 4 个 中 断 */ static int request_irqs(void) int ret, i, j; for (i = 0; i < MAX_COLUMN; i++) ret = request_irq(key_info_matrix[i][0].irq_no, button_irq, SA_INTERRUPT, BUTTONS_DEVICE_NAME, NULL); if (ret < 0) for (j = 0; j < i; j++) free_irq(key_info_matrix[j][0].irq_no, NULL); return -EFAULT; return 0; /* 释 放 中 断 */ static inline void free_irqs(void) int i; for (i = 0; i < MAX_COLUMN; i++) free_irq(key_info_matrix[i][0].irq_no, NULL); 中 断 处 理 函 数 在 每 次 中 断 产 生 的 时 候 会 被 调 用, 因 此 它 的 执 行 时 间 要 尽 可 能 得 短 通 常 中 断 处 理 函 数 只 是 简 单 地 唤 醒 等 待 资 源 的 任 务, 而 复 杂 且 耗 时 的 工 作 则 让 这 个 任 务 去 完 成 中 断 处 理 函 数 不 能 向 用 户 空 间 发 送 数 据 或 者 接 收 数 据, 不 能 做 任 何 可 能 发 生 睡 眠 的 操 作, 而 且 不 能 调 用 schedule() 函 数 为 了 简 单 起 见, 而 且 考 虑 到 按 键 操 作 的 时 间 比 较 长, 在 本 实 例 中 的 中 断 处 理 函 数 button_irq() 里, 通 过 调 用 睡 眠 函 数 来 消 除 毛 刺 信 号 读 者 可 以 根 据 以 上 介 绍 的 对 中 断 处 理 函 数 的 要 求 改 进 该 部 分 代 码 按 键 扫 描 函 数 如 下 所 示 首 先 根 据 中 断 号 确 定 操 作 按 键 所 在 行 的 位 置, 然 后 采 用 逐 列 扫 描 法 最 终 确 定 操 作 按 键 所 在 的 位 置 /* ** 进 入 中 断 后, 扫 描 铵 键 码 ** 返 回 : 按 键 码 (1~16), 0xff 表 示 错 误 29
352 */ static inline unsigned char button_scan(int irq) unsigned char key_id = 0xff; unsigned char column = 0xff, row = 0xff; 专 业 始 于 专 注 卓 识 源 于 远 见 s3c2410_gpio_cfgpin(s3c2410_gpf0, S3C2410_GPF0_INP); /* GPF0 */ s3c2410_gpio_cfgpin(s3c2410_gpf2, S3C2410_GPF2_INP); /* GPF2 */ s3c2410_gpio_cfgpin(s3c2410_gpg3, S3C2410_GPG3_INP); /* GPG3 */ s3c2410_gpio_cfgpin(s3c2410_gpg11, S3C2410_GPG11_INP); /* GPG11 */ switch (irq) /* 根 据 irq 值 确 定 操 作 按 键 所 在 行 的 位 置 */ case IRQ_EINT0: column = 0; break; case IRQ_EINT2: column = 1; break; case IRQ_EINT11: column = 2; break; case IRQ_EINT19: column = 3; break; if (column!= 0xff) /* 开 始 逐 列 扫 描, 扫 描 第 0 列 */ s3c2410_gpio_setpin(s3c2410_gpe11, 0); /* 将 KSCAN0 置 为 低 电 平 */ s3c2410_gpio_setpin(s3c2410_gpg6, 1); s3c2410_gpio_setpin(s3c2410_gpe13, 1); s3c2410_gpio_setpin(s3c2410_gpg2, 1); if(!s3c2410_gpio_getpin(key_info_matrix[column][0].irq_gpio_port)) /* 观 察 对 应 的 中 断 线 的 输 入 端 口 值 */ key_id = key_info_matrix[column][0].key_id; return key_id; /* 扫 描 第 1 列 */ s3c2410_gpio_setpin(s3c2410_gpe11, 1); 30
353 s3c2410_gpio_setpin(s3c2410_gpg6, 0); /* 将 KSCAN1 置 为 低 电 平 */ s3c2410_gpio_setpin(s3c2410_gpe13, 1); s3c2410_gpio_setpin(s3c2410_gpg2, 1); if(!s3c2410_gpio_getpin(key_info_matrix[column][1].irq_gpio_port)) key_id = key_info_matrix[column][1].key_id; return key_id; /* 扫 描 第 2 列 */ s3c2410_gpio_setpin(s3c2410_gpe11, 1); s3c2410_gpio_setpin(s3c2410_gpg6, 1); s3c2410_gpio_setpin(s3c2410_gpe13, 0); /* 将 KSCAN2 置 为 低 电 平 */ s3c2410_gpio_setpin(s3c2410_gpg2, 1); if(!s3c2410_gpio_getpin(key_info_matrix[column][2].irq_gpio_port)) key_id = key_info_matrix[column][2].key_id; return key_id; /* 扫 描 第 3 列 */ s3c2410_gpio_setpin(s3c2410_gpe11, 1); s3c2410_gpio_setpin(s3c2410_gpg6, 1); s3c2410_gpio_setpin(s3c2410_gpe13, 1); 专 业 始 于 专 注 卓 识 源 于 远 见 s3c2410_gpio_setpin(s3c2410_gpg2, 0); /* 将 KSCAN3 置 为 低 电 平 */ if(!s3c2410_gpio_getpin(key_info_matrix[column][3].irq_gpio_port)) key_id = key_info_matrix[column][3].key_id; return key_id; return key_id; 以 下 是 read 函 数 接 口 的 实 现 首 先 在 按 键 缓 冲 中 删 除 已 经 过 时 的 按 键 操 作 信 息, 接 下 来, 从 按 键 缓 冲 中 读 取 一 条 信 息 ( 按 键 ID) 并 传 递 给 用 户 层 /* 从 缓 冲 删 除 过 时 数 据 (5s 前 的 按 键 值 ) */ static void remove_timeoutkey(void) unsigned long tick; spin_lock_irq(&buffer_lock); /* 获 得 一 个 自 旋 锁 */ while(key_buffer.head!= key_buffer.tail) tick = get_tick_count() - key_buffer.jiffy[key_buffer.head]; if (tick < 5000) /* 5s */ break; key_buffer.buf[key_buffer.head] = 0; key_buffer.jiffy[key_buffer.head] = 0; key_buffer.head ++; key_buffer.head &= (MAX_KEY_COUNT -1); 31
354 spin_unlock_irq(&buffer_lock); /* 释 放 自 旋 锁 */ /* 读 键 盘 */ static ssize_t button_read(struct file *filp, char *buffer, size_t count, loff_t *f_pos) ssize_t ret = 0; remove_timeoutkey(); /* 删 除 过 时 的 按 键 操 作 信 息 */ spin_lock_irq(&buffer_lock); while((key_buffer.head!= key_buffer.tail) && (((size_t)ret) < count)) put_user((char)(key_buffer.buf[key_buffer.head]), &buffer[ret]); key_buffer.buf[key_buffer.head] = 0; key_buffer.jiffy[key_buffer.head] = 0; key_buffer.head ++; key_buffer.head &= (MAX_KEY_COUNT -1); ret ++; spin_unlock_irq(&buffer_lock); return ret; 以 上 介 绍 了 按 键 驱 动 程 序 中 的 主 要 内 容 按 键 驱 动 的 测 试 程 序 按 键 驱 动 程 序 的 测 试 程 序 所 下 所 示 在 测 试 程 序 中, 首 先 打 开 按 键 设 备 文 件 和 gpio 设 备 ( 包 括 4 个 LED 和 蜂 鸣 器 ) 文 件, 接 下 来, 根 据 按 键 的 输 入 值 ( 按 键 ID) 的 二 进 制 形 式,LED D9~D12 发 亮 ( 例 如, 按 下 11 号 按 键, 则 D9 D10 和 D12 会 发 亮 ), 而 蜂 鸣 器 当 每 次 按 键 时 发 出 声 响 /* butt_test.c */ #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> #include <asm/delay.h> #include "butt_drv.h" #include "gpio_drv.h" main() int butt_fd, gpios_fd, i; unsigned char key = 0x0; butt_fd = open(buttons_device_filename, O_RDWR); /* 打 开 按 钮 设 备 */ 32
355 if (butt_fd == -1) printf("open button device button errr!\n"); return 0; 专 业 始 于 专 注 卓 识 源 于 远 见 gpios_fd = open(gpio_device_filename, O_RDWR); /* 打 开 GPIO 设 备 */ if (gpios_fd == -1) printf("open button device button errr!\n"); return 0; ioctl(butt_fd, 0); /* 清 空 键 盘 缓 冲 区, 后 面 参 数 没 有 意 义 */ printf("press No.16 key to exit\n"); do if (read(butt_fd, &key, 1) <= 0) /* 读 键 盘 设 备, 得 到 相 应 的 键 值 */ continue; printf("key Value = %d\n", key); for (i = 0; i < LED_NUM; i++) if ((key & (1 << i))!= 0) ioctl(gpios_fd, LED_D09_SWT + i, LED_SWT_ON); /* LED 发 亮 */ ioctl(gpios_fd, BEEP_SWT, BEEP_SWT_ON); /* 发 声 */ sleep(1); for (i = 0; i < LED_NUM; i++) ioctl(gpios_fd, LED_D09_SWT + i, LED_SWT_OFF); /* LED 熄 灭 */ ioctl(gpios_fd, BEEP_SWT, BEEP_SWT_OFF); while(key!= 16); /* 按 16 号 键 则 退 出 */ close(gpios_fd); close(butt_fd); return 0; 首 先 编 译 和 加 载 按 键 驱 动 程 序, 而 且 要 创 建 设 备 文 件 节 点 $ make clean;make /* 驱 动 程 序 的 编 译 */ $ insmod butt_dev.ko /* 加 载 buttons 设 备 驱 动 */ 33
356 $ cat /proc/devices /* 通 过 这 个 命 令 可 以 查 到 buttons 设 备 的 主 设 备 号 */ $ mknod /dev/buttons c /* 假 设 主 设 备 号 为 252, 创 建 设 备 文 件 节 点 */ 接 下 来, 编 译 和 加 载 GPIO 驱 动 程 序, 而 且 要 创 建 设 备 文 件 节 点 $ make clean;make /* 驱 动 程 序 的 编 译 */ $ insmod gpio_drv.ko /* 加 载 GPIO 驱 动 */ $ cat /proc/devices /* 通 过 这 个 命 令 可 以 查 到 GPIO 设 备 的 主 设 备 号 */ $ mknod /dev/gpio c /* 假 设 主 设 备 号 为 251, 创 建 设 备 文 件 节 点 */ 然 后 编 译 并 运 行 驱 动 测 试 程 序 $ arm-linux-gcc o butt_test butt_test.c $./butt_test 专 业 始 于 专 注 卓 识 源 于 远 见 11.7 实 验 内 容 test 驱 动 1. 实 验 目 的 该 实 验 是 编 写 最 简 单 的 字 符 驱 动 程 序, 这 里 的 设 备 也 就 是 一 段 内 存, 实 现 简 单 的 读 写 功 能, 并 列 出 常 用 格 式 的 Makefile 以 及 驱 动 的 加 载 和 卸 载 脚 本 读 者 可 以 熟 悉 字 符 设 备 驱 动 的 整 个 编 写 流 程 2. 实 验 内 容 该 实 验 要 求 实 现 对 虚 拟 设 备 ( 一 段 内 存 ) 的 打 开 关 闭 读 写 的 操 作, 并 要 通 过 编 写 测 试 程 序 来 测 试 虚 拟 设 备 及 其 驱 动 运 行 是 否 正 常 3. 实 验 步 骤 (1) 编 写 代 码 这 个 简 单 的 驱 动 程 序 的 源 代 码 如 下 所 示 : /* test_drv.c */ #include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/kernel.h> #include <linux/slab.h> #include <linux/types.h> #include <linux/errno.h> #include <linux/cdev.h> #include <asm/uaccess.h> #define TEST_DEVICE_NAME "test_dev" #define BUFF_SZ 1024 /* 全 局 变 量 */ 34
357 static struct cdev test_dev; unsigned int major =0; static char *data = NULL; 专 业 始 于 专 注 卓 识 源 于 远 见 /* 读 函 数 */ static ssize_t test_read(struct file *file, char *buf, size_t count, loff_t *f_pos) int len; if (count < 0 ) return -EINVAL; len = strlen(data); count = (len > count)?count:len; if (copy_to_user(buf, data, count)) /* 将 内 核 缓 冲 的 数 据 拷 贝 到 用 户 空 间 */ return -EFAULT; return count; /* 写 函 数 */ static ssize_t test_write(struct file *file, const char *buffer, size_t count, loff_t *f_pos) if(count < 0) return -EINVAL; memset(data, 0, BUFF_SZ); count = (BUFF_SZ > count)?count:buff_sz; if (copy_from_user(data, buffer, count)) /* 将 用 户 缓 冲 的 数 据 复 制 到 内 核 空 间 */ return -EFAULT; return count; /* 打 开 函 数 */ static int test_open(struct inode *inode, struct file *file) printk("this is open operation\n"); /* 分 配 并 初 始 化 缓 冲 区 */ data = (char*)kmalloc(sizeof(char) * BUFF_SZ, GFP_KERNEL); if (!data) return -ENOMEM; 35
358 memset(data, 0, BUFF_SZ); return 0; /* 关 闭 函 数 */ static int test_release(struct inode *inode,struct file *file) printk("this is release operation\n"); if (data) kfree(data); /* 释 放 缓 冲 区 */ data = NULL; /* 防 止 出 现 野 指 针 */ return 0; /* 创 建 初 始 化 字 符 设 备, 并 且 注 册 到 系 统 */ static void test_setup_cdev(struct cdev *dev, int minor, struct file_operations *fops) int err, devno = MKDEV(major, minor); cdev_init(dev, fops); dev->owner = THIS_MODULE; dev->ops = fops; err = cdev_add (dev, devno, 1); if (err) printk (KERN_NOTICE "Error %d adding test %d", err, minor); 专 业 始 于 专 注 卓 识 源 于 远 见 /* 虚 拟 设 备 的 file_operations 结 构 */ static struct file_operations test_fops =.owner = THIS_MODULE,.read = test_read,.write = test_write,.open = test_open,.release = test_release, ; /* 模 块 注 册 入 口 */ int init_module(void) int result; dev_t dev = MKDEV(major, 0); if (major) /* 静 态 注 册 一 个 设 备, 设 备 号 先 前 指 定 好, 并 设 定 设 备 名, 用 cat /proc/devices 来 查 看 */ 36
359 result = register_chrdev_region(dev, 1, TEST_DEVICE_NAME); 专 业 始 于 专 注 卓 识 源 于 远 见 else result = alloc_chrdev_region(&dev, 0, 1, TEST_DEVICE_NAME); if (result < 0) printk(kern_warning "Test device: unable to get major %d\n", major); return result; test_setup_cdev(&test_dev, 0, &test_fops); printk("the major of the test device is %d\n", major); return 0; /* 卸 载 模 块 */ void cleanup_module(void) cdev_del(&test_dev); unregister_chrdev_region(mkdev(major, 0), 1); printk("test device uninstalled\n"); (2) 编 译 代 码 虚 拟 设 备 的 驱 动 程 序 的 Makefile 如 下 所 示 : ifeq ($(KERNELRELEASE),) KERNELDIR?= /lib/modules/$(shell uname -r)/build /* 内 核 代 码 编 译 路 径 */ PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules modules_install: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install clean: rm -rf *.o *~ core.depend.*.cmd *.ko *.mod.c.tmp_versions.phony: modules modules_install clean else endif obj-m := test_drv.o /* 将 生 成 的 模 块 为 test_drv.ko*/ (3) 加 载 和 卸 载 模 块 通 过 下 面 两 个 脚 本 代 码 分 别 实 现 驱 动 模 块 的 加 载 和 卸 载 加 载 脚 本 test_drv_load 如 下 所 示 : #!/bin/sh # 驱 动 模 块 名 称 module="test_drv" # 设 备 名 称 在 /proc/devices 中 出 现 37
360 device="test_dev" # 设 备 文 件 的 属 性 mode="664" group="david" # 删 除 已 存 在 的 设 备 节 点 rm -f /dev/$device # 加 载 驱 动 模 块 /sbin/insmod -f./$module.ko $* exit 1 # 查 到 创 建 设 备 的 主 设 备 号 major=`cat /proc/devices awk "\\$2==\"$device\" print \\$1"` # 创 建 设 备 文 件 节 点 mknod /dev/$device c $major 0 # 设 置 设 备 文 件 属 性 chgrp $group /dev/$device chmod $mode /dev/$device 卸 载 脚 本 test_drv_unload 如 下 所 示 : #!/bin/sh module="test_drv" device="test_dev" # 卸 载 驱 动 模 块 /sbin/rmmod $module $* exit 1 # 删 除 设 备 文 件 rm -f /dev/$device exit 0 (6) 编 写 测 试 代 码 最 后 一 步 是 编 写 测 试 代 码, 也 就 是 用 户 空 间 的 程 序, 该 程 序 调 用 设 备 驱 动 来 测 试 驱 动 的 运 行 是 否 正 常 以 下 实 例 只 实 现 了 简 单 的 读 写 功 能, 测 试 代 码 如 下 所 示 : /* test.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> #include <fcntl.h> #define TEST_DEVICE_FILENAME "/dev/test_dev" /* 设 备 文 件 名 */ #define BUFF_SZ 1024 /* 缓 冲 大 小 */ int main() int fd, nwrite, nread; char buff[buff_sz]; /* 缓 冲 区 */ /* 打 开 设 备 文 件 */ fd = open(test_device_filename, O_RDWR); if (fd < 0) 38
361 perror("open"); exit(1); do printf("input some words to kernel(enter 'quit' to exit):"); memset(buff, 0, BUFF_SZ); if (fgets(buff, BUFF_SZ, stdin) == NULL) perror("fgets"); break; buff[strlen(buff) - 1] = '\0'; if (write(fd, buff, strlen(buff)) < 0) /* 向 设 备 写 入 数 据 */ perror("write"); break; if (read(fd, buff, BUFF_SZ) < 0) /* 从 设 备 读 取 数 据 */ perror("read"); break; else printf("the read string is from kernel:%s\n", buff); while(strncmp(buff, "quit", 4)); close(fd); exit(0); 4. 实 验 结 果 首 先 在 虚 拟 设 备 驱 动 源 码 目 录 下 编 译 并 加 载 驱 动 模 块 $ make clean;make $./test_drv_load 接 下 来, 编 译 并 运 行 测 试 程 序 $ gcc o test test.c $./test 测 试 程 序 运 行 效 果 如 下 : Input some words to kernel(enter 'quit' to exit):hello, everybody! 39
362 The read string is from kernel:hello, everybody! /* 从 内 核 读 取 的 数 据 */ Input some words to kernel(enter 'quit' to exit):this is a simple driver The read string is from kernel: This is a simple driver Input some words to kernel(enter 'quit' to exit):quit The read string is from kernel:quit 最 后, 卸 载 驱 动 程 序 $./test_drv_unload 通 过 dmesg 命 令 可 以 查 看 内 核 打 印 的 信 息 : $ dmesg tail n 10 The major of the test device is 250 /* 当 加 载 模 块 时 打 印 */ This is open operation /* 当 打 开 设 备 时 打 印 */ This is release operation /* 关 闭 设 备 时 打 印 */ Test device uninstalled /* 当 卸 载 设 备 时 打 印 */ 11.8 本 章 小 结 本 章 主 要 介 绍 了 嵌 入 式 Linux 设 备 驱 动 程 序 的 开 发 首 先 介 绍 了 设 备 驱 动 程 序 的 概 念 及 Linux 对 设 备 驱 动 的 处 理, 这 里 要 明 确 驱 动 程 序 在 Linux 中 的 定 位 接 下 来 介 绍 了 字 符 设 备 驱 动 程 序 的 编 写, 这 里 详 细 介 绍 了 字 符 设 备 驱 动 程 序 的 编 写 流 程 重 要 的 数 据 结 构 设 备 驱 动 程 序 的 主 要 组 成 以 及 proc 文 件 系 统 接 着 又 以 GPIO 驱 动 为 例 介 绍 了 一 个 简 单 的 字 符 驱 动 程 序 的 编 写 步 骤 再 接 下 来, 本 章 介 绍 了 块 设 备 驱 动 程 序 的 编 写, 主 要 包 括 块 设 备 驱 动 程 序 描 述 符 和 块 设 备 驱 动 的 编 写 流 程 最 后, 本 章 介 绍 了 中 断 编 程, 并 以 编 写 完 整 的 按 键 驱 动 程 序 为 例 进 行 讲 解 本 章 的 实 验 安 排 的 是 简 单 虚 拟 设 备 驱 动 程 序 的 编 写, 通 过 该 实 验, 读 者 可 以 了 解 到 编 写 驱 动 程 序 的 完 整 流 程 11.9 思 考 与 练 习 根 据 书 上 的 提 示, 将 本 章 中 所 述 的 按 键 驱 动 程 序 进 行 进 一 步 的 改 进, 并 在 开 发 板 上 进 行 测 试 联 系 方 式 集 团 官 网 : 嵌 入 式 学 院 : 移 动 互 联 网 学 院 : 企 业 学 院 : 物 联 网 学 院 : 研 发 中 心 :dev.hqyj.com 集 团 总 部 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 内 华 清 远 见 教 育 集 团 北 京 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 区, 电 话 : /5 上 海 地 址 : 上 海 市 徐 汇 区 漕 溪 路 银 海 大 厦 A 座 8 层, 电 话 : 深 圳 地 址 : 深 圳 市 龙 华 新 区 人 民 北 路 美 丽 AAA 大 厦 15 层, 电 话 :
363 成 都 地 址 : 成 都 市 武 侯 区 科 华 北 路 99 号 科 华 大 厦 6 层, 电 话 : 南 京 地 址 : 南 京 市 白 下 区 汉 中 路 185 号 鸿 运 大 厦 10 层, 电 话 : 武 汉 地 址 : 武 汉 市 工 程 大 学 卓 刀 泉 校 区 科 技 孵 化 器 大 楼 8 层, 电 话 : 西 安 地 址 : 西 安 市 高 新 区 高 新 一 路 12 号 创 业 大 厦 D3 楼 5 层, 电 话 :
364 嵌 入 式 LINUX 应 用 程 序 开 发 标 准 教 程 作 者 : 华 清 远 见 第 12 章 Qt 图 形 编 程 基 础 掌 握 嵌 入 式 GUI 的 种 类 和 特 点 掌 握 Qt 中 的 信 号 与 槽 的 机 制 掌 握 Qt/Embedded 的 安 装 和 配 置 掌 握 Qt/Embedded 应 用 程 序 的 基 本 流 程
365 12.1 嵌 入 式 GUI 简 介 目 前 的 桌 面 机 操 作 系 统 大 多 有 着 美 观 操 作 方 便 功 能 齐 全 的 GUI( 图 形 用 户 界 面 ), 例 如 KDE 或 者 GNOME GUI( 图 形 用 户 界 面 ) 是 指 计 算 机 与 其 使 用 者 之 间 的 对 话 接 口, 可 以 说,GUI 是 当 今 计 算 机 技 术 的 重 大 成 就 它 的 存 在 为 使 用 者 提 供 了 友 好 便 利 的 界 面, 并 大 大 地 方 便 了 非 专 业 用 户 的 使 用, 使 得 人 们 从 繁 琐 的 命 令 中 解 脱 出 来, 可 以 通 过 窗 口 菜 单 方 便 地 进 行 操 作 而 在 嵌 入 式 系 统 中,GUI 的 地 位 也 越 来 越 重 要, 但 是 不 同 于 桌 面 机 系 统, 嵌 入 式 GUI 要 求 简 单 直 观 可 靠 占 用 资 源 小 且 反 应 快 速, 以 适 应 系 统 硬 件 资 源 有 限 的 条 件 另 外, 由 于 嵌 入 式 系 统 硬 件 本 身 的 特 殊 性, 嵌 入 式 GUI 应 具 备 高 度 可 移 植 性 与 可 裁 减 性, 以 适 应 不 同 的 硬 件 条 件 和 使 用 需 求 总 体 来 讲, 嵌 入 式 GUI 具 备 以 下 特 点 : 体 积 小 ; 运 行 时 耗 用 系 统 资 源 小 ; 上 层 接 口 与 硬 件 无 关, 高 度 可 移 植 ; 高 可 靠 性 ; 在 某 些 应 用 场 合 应 具 备 实 时 性 UNIX 环 境 下 的 图 形 视 窗 标 准 为 X Window System,Linux 是 类 UNIX 系 统, 所 以 顶 层 运 行 的 GUI 系 统 是 兼 容 X 标 准 的 XFree86 系 统 X 标 准 大 致 可 以 划 分 X Server Graphic Library( 底 层 绘 图 函 数 库 ) Toolkits Window Manager 等 几 大 部 分 其 好 处 是 具 有 可 扩 展 性 可 移 植 性 等 优 点, 但 对 于 嵌 入 式 系 统 而 言 无 疑 太 过 庞 大 累 赘 低 效 目 前 流 行 的 嵌 入 式 GUI 与 X 思 路 不 同, 这 些 GUI 一 般 不 局 限 于 X 标 准, 更 强 调 系 统 的 空 间 和 效 率 Qt/Embedded 表 12.1 归 纳 了 Qt/Embedded 的 一 些 优 缺 点 优 点 缺 点 表 12.1 以 开 发 包 形 式 提 供 跨 平 台 类 库 支 持 跨 平 台 模 块 化 结 构 也 过 于 复 杂 臃 肿, 很 难 进 行 底 层 的 扩 充 定 制 和 移 植 Qt/Embedded 分 析 Qt/Embedded 分 析 包 括 了 图 形 设 计 器 Makefile 制 作 工 具 字 体 国 际 化 工 具 Qt 的 C++ 类 库 等 支 持 Microsoft Windows 95/98/2000 Microsoft Windows NT MacOS X Linux Solaris HP-UX Tru64 (Digital UNIX) Irix FreeBSD BSD/OS SCO AIX 等 众 多 平 台 Qt 类 库 封 装 了 适 应 不 同 操 作 系 统 的 访 问 细 节, 这 正 是 Qt 的 魅 力 所 在 可 以 任 意 裁 减 例 如 : 尽 管 Qt/Embedded 声 称, 它 最 小 可 以 裁 剪 到 几 百 KB, 但 这 时 的 Qt/Embedded 库 已 经 基 本 失 去 了 使 用 价 值 它 提 供 的 控 件 集 沿 用 了 PC 风 格, 并 不 太 适 合 许 多 手 持 设 备 的 操 作 要 求 Qt/Embedded 的 底 层 图 形 引 擎 只 能 采 用 framebuffer, 只 是 针 对 高 端 嵌 入 式 图 形 领 域 的 应 用 而 设 计 的 由 于 该 库 的 代 码 追 求 面 面 俱 到, 以 增 加 它 对 多 种 硬 件 设 备 的 支 持, 造 成 了 其 底 层 代 码 比 较 凌 乱, 各 种 补 丁 较 多 的 问 题 MiniGUI 提 起 国 内 的 开 源 软 件, 就 肯 定 会 提 到 MiniGUI, 它 由 魏 永 明 先 生 和 众 多 志 愿 者 开 发, 是 一 个 基 于 Linux 的 实 时 嵌 入 式 系 统 的 轻 量 级 图 形 用 户 界 面 支 持 系 统 MiniGUI 分 为 最 底 层 的 GAL 层 和 IAL 层, 向 上 为 基 于 标 准 POSIX 接 口 中 pthread 库 的 Mini-thread 架 构 和 基 于 Server/Client 的 Mini-Lite 架 构 其 中 前 者 受 限 于 thread 模 式 对 于 整 个 系 统 的 可 靠 性 进 程 中 某 个 thread 的 意 外 错 误 可 能 导 致 整 个 进 程 的 崩 溃, 该 架 构 应 用 于 系 统 功 能 较 为 单 一 的 场 合 Mini-Lite 应 用 于 多 进 程 的 应 用 场 合, 2
366 采 用 多 进 程 运 行 方 式 设 计 的 Server/Client 架 构 能 够 较 好 地 解 决 各 个 进 程 之 间 的 窗 口 管 理 Z 序 剪 切 等 问 题 MiniGUI 还 有 一 种 从 Mini-Lite 衍 生 出 的 standalone 运 行 模 式 与 Lite 架 构 不 同 的 是,standalone 模 式 一 次 只 能 以 窗 口 最 大 化 的 方 式 显 示 一 个 窗 口 这 在 显 示 屏 尺 寸 较 小 的 应 用 场 合 具 有 一 定 的 应 用 意 义 MiniGUI 的 IAL 层 技 术 SVGA lib LibGGI 基 于 framebuffer 的 native 图 形 引 擎 以 及 哑 图 形 引 擎 等, 对 于 Trolltech 公 司 的 QVFB 在 X Window 下 也 有 较 好 的 支 持 IAL 层 则 支 持 Linux 标 准 控 制 台 下 的 GPM 鼠 标 服 务 触 摸 屏 标 准 键 盘 等 MiniGUI 下 丰 富 的 控 件 资 源 也 是 MiniGUI 的 特 点 之 一 当 前 MiniGUI 的 最 新 版 本 是 在 该 版 本 的 控 件 中 已 经 添 加 了 窗 口 皮 肤 工 具 条 等 桌 面 GUI 中 的 高 级 控 件 支 持 对 比 其 他 系 统, Mini 是 MiniGUI 的 特 色, 轻 量 高 性 能 和 高 效 率 的 MiniGUI 已 经 应 用 在 电 视 机 顶 盒 实 时 控 制 系 统 掌 上 电 脑 等 诸 多 场 合 Microwindows Tiny X 等 Microwindows Open Source Project 成 立 的 宗 旨 在 于 针 对 体 积 小 的 装 置, 建 立 一 套 先 进 的 视 窗 环 境, 在 Linux 桌 面 上 通 过 交 叉 编 译 可 以 很 容 易 地 制 作 出 Microwindows 的 程 序 Microwindows 能 够 在 没 有 任 何 操 作 系 统 或 其 他 图 形 系 统 的 支 持 下 运 行, 它 能 对 裸 显 示 设 备 进 行 直 接 操 作 这 样,Microwindows 就 显 得 十 分 小 巧, 便 于 移 植 到 各 种 硬 件 和 软 件 系 统 上 然 而 Microwindows 的 免 费 版 本 进 展 一 直 很 慢, 几 乎 处 于 停 顿 状 态, 而 且 至 今 为 止, 国 内 没 有 任 何 一 家 对 Microwindows 提 供 全 面 技 术 支 持 服 务 和 担 保 的 专 业 公 司 Tiny X Server 是 XFree86 Project 的 一 部 分, 由 Keith Pachard 发 展 起 来 的, 而 他 本 身 就 是 XFree86 专 案 的 核 心 成 员 之 一 一 般 的 X Server 都 过 于 庞 大, 因 此 Keith Packard 就 以 XFree86 为 基 础, 精 简 而 成 Tiny X Server, 它 的 体 积 可 以 小 到 几 百 KB, 非 常 适 合 应 用 于 嵌 入 式 环 境 就 纯 X Window System 搭 配 Tiny X Server 架 构 来 说, 其 最 大 的 优 点 就 是 具 有 很 好 的 弹 性 开 发 机 制, 并 能 大 大 提 高 开 发 速 度 因 为 与 桌 面 的 X 架 构 相 同, 因 此 相 对 于 很 多 以 Qt GTK+ FLTK 等 为 基 础 开 发 的 软 件 可 以 很 容 易 地 移 植 过 来 虽 然 移 植 方 便, 但 是 却 有 体 积 大 的 缺 点, 由 于 很 多 软 件 本 来 是 针 对 桌 面 环 境 开 发 的, 因 此 无 形 之 中 具 备 了 桌 面 环 境 中 很 多 复 杂 的 功 能 因 此 调 校 变 成 采 用 此 架 构 最 大 的 课 题, 有 时 候 重 新 改 写 可 能 比 调 校 所 需 的 时 间 还 短 表 12.2 总 结 了 常 见 GUI 的 参 数 比 较 表 12.2 参 数 名 称 常 见 GUI 参 数 比 较 MiniGUI OpenGUI Qt/Embedded API( 完 备 性 ) Win32( 很 完 备 ) 私 有 ( 很 完 备 ) Qt(C++)( 很 完 备 ) 函 数 库 的 典 型 大 小 300KB 300KB 600KB 移 植 性 很 好 只 支 持 x86 平 台 较 好 授 权 条 款 LGPL LGPL QPL/GPL 系 统 消 耗 小 最 小 最 大 操 作 系 统 支 持 Linux Linux,DOS,QNX Linux 12.2 Qt/Embedded 开 发 入 门 Qt/Embedded 介 绍 1. 架 构 3
367 Qt/Embedded 以 原 始 Qt 为 基 础, 并 做 了 许 多 出 色 的 调 整 以 适 用 于 应 用 源 代 码 嵌 入 式 环 境 Qt/Embedded 通 过 Qt API 与 Linux I/O 设 施 直 接 交 互, 成 为 嵌 Qt API 入 式 Qt/X11 Linux 端 口 同 Qt/X11 相 比,Qt/Embedded 很 省 内 存, 因 为 它 不 需 要 一 个 X Qt/Embedded Qt/XLib 服 务 器 或 是 Xlib 库, 它 在 底 层 抛 弃 了 X lib, 采 用 framebuffer( 帧 缓 冲 ) 作 X Window Server 为 底 层 图 形 接 口 同 时, 将 外 部 输 入 设 备 抽 象 为 keyboard 和 mouse 输 入 事 帧 缓 冲 件 Qt/Embedde 的 应 用 程 序 可 以 直 接 写 内 核 缓 冲 帧, 这 避 免 开 发 者 使 用 繁 Linux 内 核 琐 的 Xlib/Server 系 统 图 12.1 所 示 比 较 了 Qt/Embedded 与 Qt/X11 的 架 构 区 图 12.1 Qt/Embedded 与 Qt/ 别 X11 的 Linux 版 本 的 比 较 使 用 单 一 的 API 进 行 跨 平 台 的 编 程 可 以 有 很 多 好 处 提 供 嵌 入 式 设 备 和 桌 面 计 算 机 环 境 下 应 用 的 公 司 可 以 培 训 开 发 人 员 使 用 同 一 套 工 具 开 发 包, 这 有 利 于 开 发 人 员 之 间 共 享 开 发 经 验 与 知 识, 也 使 得 管 理 人 员 在 分 配 开 发 人 员 到 项 目 中 的 时 候 增 加 灵 活 性 更 进 一 步 来 说, 针 对 某 个 平 台 而 开 发 的 应 用 和 组 件 也 可 以 销 售 到 Qt 支 持 的 其 他 平 台 上, 从 而 以 低 廉 的 成 本 扩 大 产 品 的 市 场 (1) 窗 口 系 统 一 个 Qt/Embedded 窗 口 系 统 包 含 了 一 个 或 多 个 进 程, 其 中 的 一 个 进 程 可 作 为 服 务 器 该 服 务 进 程 会 分 配 客 户 显 示 区 域, 以 及 产 生 鼠 标 和 键 盘 事 件 该 服 务 进 程 还 能 够 提 供 输 入 方 法 和 一 个 用 户 接 口 给 运 行 起 来 的 客 户 应 用 程 序 该 服 务 进 程 其 实 就 是 一 个 有 某 些 额 外 权 限 的 客 户 进 程 任 何 程 序 都 可 以 在 命 令 行 上 加 上 -qws 的 选 项 来 把 它 作 为 一 个 服 务 器 运 行 客 户 与 服 务 器 之 间 的 通 信 使 用 共 享 内 存 的 方 法 实 现, 通 信 量 应 该 保 持 最 小, 例 如 客 户 进 程 直 接 访 问 帧 缓 冲 来 完 成 全 部 的 绘 制 操 作, 而 不 会 通 过 服 务 器, 客 户 程 序 需 要 负 责 绘 制 它 们 自 己 的 标 题 栏 和 其 他 式 样 这 就 是 Qt/Embedded 库 内 部 层 次 分 明 的 处 理 过 程 客 户 可 以 使 用 QCOP 通 道 交 换 消 息 服 务 进 程 简 单 的 广 播 QCOP 消 息 给 所 有 监 听 指 定 通 道 的 应 用 进 程, 接 着 应 用 进 程 可 以 把 一 个 插 槽 连 接 到 一 个 负 责 接 收 的 信 号 上, 从 而 对 消 息 做 出 响 应 消 息 的 传 递 通 常 伴 随 着 二 进 制 数 据 的 传 输, 这 是 通 过 一 个 QDataStream 类 的 序 列 化 过 程 来 实 现 的, 有 关 这 个 类 的 描 述, 请 读 者 参 考 相 关 资 料 QProcess 类 提 供 了 另 外 一 种 异 步 的 进 程 间 通 信 机 制 它 用 于 启 动 一 个 外 部 的 程 序 并 且 通 过 写 一 个 标 准 的 输 入 和 读 取 外 部 程 序 的 标 准 输 出 和 错 误 码 来 和 它 们 通 信 (2) 字 体 Qt/Embedded 支 持 4 种 不 同 的 字 体 格 式 :True Type 字 体 (TTF),Postscript Type1 字 体, 位 图 发 布 字 体 (BDF) 和 Qt 的 预 呈 现 (Pre-rendered) 字 体 (QPF) Qt 还 可 以 通 过 增 加 Qfont- Factory 的 子 类 来 支 持 其 他 字 体, 也 可 以 支 持 以 插 件 方 式 出 现 的 反 别 名 字 体 每 个 TTF 或 者 TYPE1 类 型 的 字 体 首 次 在 图 形 或 者 文 本 方 式 的 环 境 下 被 使 用 时, 这 些 字 体 的 字 形 都 会 以 指 定 的 大 小 被 预 先 呈 现 出 来, 呈 现 的 结 果 会 被 缓 冲 根 据 给 定 的 字 体 尺 寸 ( 例 如 10 或 12 点 阵 ) 预 先 呈 现 TTF 或 者 TYPE1 类 型 的 字 体 文 件 并 把 结 果 以 QPF 的 格 式 保 存 起 来, 这 样 可 以 节 省 内 存 和 CPU 的 处 理 时 间 QPF 文 件 包 含 了 一 些 必 要 的 字 体, 这 些 字 体 可 以 通 过 makeqpf 工 具 取 得, 或 者 通 过 运 行 程 序 时 加 上 -savefonts 选 项 获 取 如 果 应 用 程 序 中 使 用 到 的 字 体 都 是 QPF 格 式, 那 么 Qt/Embedded 将 被 重 新 配 置, 并 排 除 对 TTF 和 TYPE1 类 型 的 字 体 的 编 译, 这 样 就 可 以 减 少 Qt/Embedded 的 库 的 大 小 和 存 储 字 体 的 空 间 例 如 一 个 10 点 阵 大 小 的 包 含 所 有 ASCII 字 符 的 QPF 字 体 文 件 的 大 小 为 1300 字 节, 这 个 文 件 可 以 直 接 从 物 理 存 储 格 式 映 射 成 为 内 存 存 储 格 式 Qt/Embedded 的 字 体 通 常 包 括 Unicode 字 体 的 一 部 分 子 集,ASCII 和 Latin-1 一 个 完 整 的 16 点 阵 的 Unicode 字 体 的 存 储 空 间 通 常 超 过 1MB, 我 们 应 尽 可 能 存 储 一 个 字 体 的 子 集, 而 不 是 存 储 所 有 的 字, 例 如 在 一 个 应 用 中, 仅 仅 需 要 以 Cappuccino 字 体 粗 体 的 方 式 显 示 产 品 的 名 称, 但 是 却 有 一 个 包 含 了 全 部 字 形 的 字 体 文 件 (3) 输 入 设 备 及 输 入 法 Qt/Embedded 3.0 支 持 几 种 鼠 标 协 议 :BusMouse IntelliMouse,Microsoft 和 MouseMan.Qt/ Embedded 还 支 持 NECVr41XX 和 ipaq 的 触 摸 屏 通 过 从 QWSMouseHandler 或 者 QcalibratedMouseHandler 派 生 子 类, 开 发 人 员 可 以 让 Qt/Embedded 支 持 更 多 的 客 户 指 示 设 备 Qt/Embedded 支 持 标 准 的 101 键 盘 和 Vr41XX 按 键, 通 过 子 类 化 QWSKeyboardHandler 可 以 让 Qt/Embedded 支 持 更 多 的 客 户 键 盘 和 其 他 的 非 指 示 设 备 4
368 对 于 非 拉 丁 语 系 字 符 ( 例 如 阿 拉 伯 中 文 希 伯 来 和 日 语 ) 的 输 入 法, 需 要 把 它 写 成 过 滤 器 的 方 式, 并 改 变 键 盘 的 输 入 输 入 法 的 作 者 应 该 对 全 部 的 Qt API 的 使 用 有 完 整 的 认 识 在 一 个 无 键 盘 的 设 备 上, 输 入 法 成 了 惟 一 的 输 入 字 符 的 手 段 Qtopia 提 供 了 4 种 输 入 方 法 : 笔 迹 识 别 器 图 形 化 的 标 准 键 盘 Unicode 键 盘 和 基 于 字 典 方 式 提 取 的 键 盘 (4) 屏 幕 加 速 通 过 子 类 化 QScreen 和 QgfxRaster 可 以 实 现 硬 件 加 速, 从 而 为 屏 幕 操 作 带 来 好 处 Trolltech 提 供 了 Mach64 和 Voodoo3 视 频 卡 的 硬 件 加 速 的 驱 动 例 子, 同 时 可 以 按 照 协 议 编 写 其 他 的 驱 动 程 序 2.Qt 的 开 发 环 境 Qt/Embedded 的 开 发 环 境 可 以 取 代 那 些 我 们 熟 知 的 UNIX 和 Windows 开 发 工 具 它 提 供 了 几 个 跨 平 台 的 工 具 使 得 开 发 变 得 迅 速 和 方 便, 尤 其 是 它 的 图 形 设 计 器 UNIX 下 的 开 发 者 可 以 在 PC 机 或 者 工 作 站 使 用 虚 拟 缓 冲 帧, 从 而 可 以 模 仿 一 个 和 嵌 入 式 设 备 的 显 示 终 端 大 小, 像 素 相 同 的 显 示 环 境 嵌 入 式 设 备 的 应 用 可 以 在 安 装 了 一 个 跨 平 台 开 发 工 具 链 的 不 同 的 平 台 上 编 译 最 通 常 的 做 法 是 在 一 个 UNIX 系 统 上 安 装 跨 平 台 的 带 有 libc 库 的 GNU C++ 编 译 器 和 二 进 制 工 具 在 开 发 的 许 多 阶 段, 一 个 可 替 代 的 做 法 是 使 用 Qt 的 桌 面 版 本, 例 如 通 过 Qt/X11 或 是 Qt/Windows 来 进 行 开 发 这 样 开 发 人 员 就 可 以 使 用 他 们 熟 悉 的 开 发 环 境, 例 如 微 软 公 司 的 Visual C++ 或 者 Borland C++ 在 UNIX 操 作 系 统 下, 许 多 环 境 也 是 可 用 的, 例 如 Kdevelop, 它 也 支 持 交 互 式 开 发 如 果 Qt/Embedded 的 应 用 是 在 UNIX 平 台 下 开 发 的 话, 那 么 它 就 可 以 在 开 发 的 机 器 上 以 一 个 独 立 的 控 制 台 或 者 虚 拟 缓 冲 帧 的 方 式 来 运 行, 对 于 后 者 来 说, 其 实 是 有 一 个 X11 的 应 用 程 序 虚 拟 了 一 个 缓 冲 帧 通 过 指 定 显 示 设 备 的 宽 度 高 度 和 颜 色 深 度, 虚 拟 出 来 的 缓 冲 帧 将 和 物 理 的 显 示 设 备 在 每 个 像 素 上 保 持 一 致 这 样 每 次 调 试 应 用 时 开 发 人 员 就 不 用 总 是 刷 新 嵌 入 式 设 备 的 Flash 存 储 空 间, 从 而 加 速 了 应 用 的 编 译 链 接 和 运 行 周 期 运 行 Qt 的 虚 拟 缓 冲 帧 工 具 的 方 法 是 在 Linux 的 图 形 模 式 下 运 行 以 下 命 令 : qvfb ( 回 车 ) 当 Qt 嵌 入 式 的 应 用 程 序 要 把 显 示 结 果 输 出 到 虚 拟 缓 冲 帧 时, 我 们 在 命 令 行 运 行 这 个 程 序, 并 在 程 序 名 后 加 上 -qws 的 选 项 例 如 :$> hello qws 3.Qt 的 支 撑 工 具 Qt 包 含 了 许 多 支 持 嵌 入 式 系 统 开 发 的 工 具, 有 两 个 最 实 用 的 工 具 是 qmake 和 Qt designer( 图 形 设 计 器 ) qmake 是 一 个 为 编 译 Qt/Embedded 库 和 应 用 而 提 供 的 Makefile 生 成 器 它 能 够 根 据 一 个 工 程 文 件 (.pro) 产 生 不 同 平 台 下 的 Makefile 文 件 qmake 支 持 跨 平 台 开 发 和 影 子 生 成, 影 子 生 成 是 指 当 工 程 的 源 代 码 共 享 给 网 络 上 的 多 台 机 器 时, 每 台 机 器 编 译 链 接 这 个 工 程 的 代 码 将 在 不 同 的 子 路 径 下 完 成, 这 样 就 不 会 覆 盖 别 人 的 编 译 链 接 生 成 的 文 件 qmake 还 易 于 在 不 同 的 配 置 之 间 切 换 Qt 图 形 设 计 器 可 以 使 开 发 者 可 视 化 地 设 计 对 话 框 而 不 需 编 写 代 码 使 用 Qt 图 形 设 计 器 的 布 局 管 理 可 以 生 成 能 平 滑 改 变 尺 寸 的 对 话 框 qmake 和 Qt 图 形 设 计 器 是 完 全 集 成 在 一 起 的 Qt/Embedded 信 号 和 插 槽 机 制 1. 机 制 概 述 信 号 和 插 槽 机 制 是 Qt 的 核 心 机 制, 要 精 通 Qt 编 程 就 必 须 对 信 号 和 插 槽 有 所 了 解 信 号 和 插 槽 是 一 种 高 级 5
369 接 口, 应 用 于 对 象 之 间 的 通 信, 它 是 Qt 的 核 心 特 性, 也 是 Qt 区 别 于 其 他 工 具 包 的 重 要 地 方 信 号 和 插 槽 是 Qt 自 行 定 义 的 一 种 通 信 机 制, 它 独 立 于 标 准 的 C/C++ 语 言, 因 此 要 正 确 地 处 理 信 号 和 插 槽, 必 须 借 助 一 个 称 为 moc(meta Object Compiler) 的 Qt 工 具, 该 工 具 是 一 个 C++ 预 处 理 程 序, 它 为 高 层 次 的 事 件 处 理 自 动 生 成 所 需 要 的 附 加 代 码 所 谓 图 形 用 户 接 口 的 应 用 就 是 要 对 用 户 的 动 作 做 出 响 应 例 如, 当 用 户 单 击 了 一 个 菜 单 项 或 是 工 具 栏 的 按 钮 时, 应 用 程 序 会 执 行 某 些 代 码 大 部 分 情 况 下, 是 希 望 不 同 类 型 的 对 象 之 间 能 够 进 行 通 信 程 序 员 必 须 把 事 件 和 相 关 代 码 联 系 起 来, 这 样 才 能 对 事 件 做 出 响 应 以 前 的 工 具 开 发 包 使 用 的 事 件 响 应 机 制 是 易 崩 溃 的, 不 够 健 壮 的, 同 时 也 不 是 面 向 对 象 的 以 前, 当 使 用 回 调 函 数 机 制 把 某 段 响 应 代 码 和 一 个 按 钮 的 动 作 相 关 联 时, 通 常 把 那 段 响 应 代 码 写 成 一 个 函 数, 然 后 把 这 个 函 数 的 地 址 指 针 传 给 按 钮, 当 那 个 按 钮 被 单 击 时, 这 个 函 数 就 会 被 执 行 对 于 这 种 方 式, 以 前 的 开 发 包 不 能 够 确 保 回 调 函 数 被 执 行 时 所 传 递 进 来 的 函 数 参 数 就 是 正 确 的 类 型, 因 此 容 易 造 成 进 程 崩 溃 另 外 一 个 问 题 是, 回 调 这 种 方 式 紧 紧 地 绑 定 了 图 形 用 户 接 口 的 功 能 元 素, 因 而 很 难 进 行 独 立 的 开 发 信 号 与 插 槽 机 制 是 不 同 的 它 是 一 种 强 有 力 的 对 象 间 通 信 机 制, 完 全 可 以 取 代 原 始 的 回 调 和 消 息 映 射 机 制 在 Qt 中 信 号 和 插 槽 取 代 了 上 述 这 些 凌 乱 的 函 数 指 针, 使 得 用 户 编 写 这 些 通 信 程 序 更 为 简 洁 明 了 信 号 和 插 槽 能 携 带 任 意 数 量 和 任 意 类 型 的 参 数, 它 们 是 类 型 完 全 安 全 的, 因 此 不 会 像 回 调 函 数 那 样 产 生 core dumps 所 有 从 QObject 或 其 子 类 ( 例 如 Qwidget) 派 生 的 类 都 能 够 包 含 信 号 和 插 槽 当 对 象 改 变 状 态 时, 信 号 就 由 该 对 象 发 射 (emit) 出 去 了, 这 就 是 对 象 所 要 做 的 全 部 工 作, 它 不 知 道 另 一 端 是 谁 在 接 收 这 个 信 号 这 就 是 真 正 的 信 息 封 装, 它 确 保 对 象 被 当 作 一 个 真 正 的 软 件 组 件 来 使 用 插 槽 用 于 接 收 信 号, 但 它 们 是 普 通 的 对 象 成 员 函 数 一 个 插 槽 并 不 知 道 是 否 有 任 何 信 号 与 自 己 相 连 接 而 且, 对 象 并 不 了 解 具 体 的 通 信 机 制 用 户 可 以 将 很 多 信 号 与 单 个 插 槽 进 行 连 接, 也 可 以 将 单 个 信 号 与 很 多 插 槽 进 行 连 接, 甚 至 将 一 个 信 号 与 另 外 一 个 信 号 相 连 接 也 是 可 能 的, 这 时 无 论 第 一 个 信 号 什 么 时 候 发 射, 系 统 都 将 立 刻 发 射 第 二 个 信 号 总 之, 信 号 与 插 槽 构 造 了 一 个 强 大 的 部 件 编 程 机 制 图 12.2 所 示 为 对 象 间 信 号 与 插 槽 的 关 系 2. 信 号 与 插 槽 实 现 实 例 图 12.2 对 象 间 信 号 与 插 槽 的 关 系 (1) 信 号 当 某 个 信 号 对 其 客 户 或 所 有 者 内 部 状 态 发 生 改 变 时, 信 号 就 被 一 个 对 象 发 射 只 有 定 义 了 这 个 信 号 的 类 及 其 派 生 类 才 能 够 发 射 这 个 信 号 当 一 个 信 号 被 发 射 时, 与 其 相 关 联 的 插 槽 将 被 立 刻 执 行, 就 像 一 个 正 常 的 函 数 调 用 一 样 信 号 - 插 槽 机 制 完 全 独 立 于 任 何 GUI 事 件 循 环 只 有 当 所 有 的 槽 返 回 以 后 发 射 函 数 (emit) 才 返 回 如 果 存 在 多 个 槽 与 某 个 信 号 相 关 联, 那 么, 当 这 个 信 号 被 发 射 时, 这 些 槽 将 会 一 个 接 一 个 地 执 行, 但 是 它 们 执 行 的 顺 序 将 会 是 随 机 的 不 确 定 的, 用 户 不 能 人 为 地 指 定 哪 个 先 执 行 哪 个 后 执 行 Qt 的 signals 关 键 字 指 出 进 入 了 信 号 声 明 区, 随 后 即 可 声 明 自 己 的 信 号 例 如, 下 面 定 义 了 3 个 信 号 : signals: void mysignal(); void mysignal(int x); void mysignalparam(int x,int y); 在 上 面 的 定 义 中,signals 是 Qt 的 关 键 字, 而 非 C/C++ 的 接 下 来 的 一 行 void mysignal() 定 义 了 信 号 mysignal, 这 个 信 号 没 有 携 带 参 数 ; 接 下 来 的 一 行 void mysignal(int x) 定 义 了 重 名 信 号 mysignal, 但 是 它 携 带 一 个 整 形 参 数, 这 有 点 类 似 于 C++ 中 的 虚 函 数 从 形 式 上 讲 信 号 的 声 明 与 普 通 的 C++ 函 数 是 一 样 的, 但 是 信 号 却 没 有 函 数 体 定 义 另 外, 信 号 的 返 回 类 型 都 是 void 信 号 由 moc 自 动 产 生, 它 们 不 应 该 在.cpp 文 件 中 实 现 (2) 插 槽 6
370 插 槽 是 普 通 的 C++ 成 员 函 数, 可 以 被 正 常 调 用, 它 们 惟 一 的 特 殊 性 就 是 很 多 信 号 可 以 与 其 相 关 联 当 与 其 关 联 的 信 号 被 发 射 时, 这 个 插 槽 就 会 被 调 用 插 槽 可 以 有 参 数, 但 插 槽 的 参 数 不 能 有 缺 省 值 插 槽 是 普 通 的 成 员 函 数, 因 此 与 其 他 的 函 数 一 样, 它 们 也 有 存 取 权 限 插 槽 的 存 取 权 限 决 定 了 谁 能 够 与 其 相 关 联 同 普 通 的 C++ 成 员 函 数 一 样, 插 槽 函 数 也 分 为 3 种 类 型, 即 public slots private slots 和 protected slots public slots: 在 这 个 区 内 声 明 的 槽 意 味 着 任 何 对 象 都 可 将 信 号 与 之 相 连 接 这 对 于 组 件 编 程 非 常 有 用, 用 户 可 以 创 建 彼 此 互 不 了 解 的 对 象, 将 它 们 的 信 号 与 槽 进 行 连 接 以 便 信 息 能 够 正 确 地 传 递 protected slots: 在 这 个 区 内 声 明 的 槽 意 味 着 当 前 类 及 其 子 类 可 以 将 信 号 与 之 相 连 接 这 适 用 于 那 些 槽, 它 们 是 类 实 现 的 一 部 分, 但 是 其 界 面 接 口 却 面 向 外 部 private slots: 在 这 个 区 内 声 明 的 槽 意 味 着 只 有 类 自 己 可 以 将 信 号 与 之 相 连 接 这 适 用 于 联 系 非 常 紧 密 的 类 插 槽 也 能 够 被 声 明 为 虚 函 数, 这 也 是 非 常 有 用 的 插 槽 的 声 明 也 是 在 头 文 件 中 进 行 的 例 如, 下 面 声 明 了 3 个 插 槽 : public slots: void myslot(); void myslot(int x); void mysignalparam(int x,int y); (3) 信 号 与 插 槽 关 联 通 过 调 用 QObject 对 象 的 connect() 函 数 可 以 将 某 个 对 象 的 信 号 与 另 外 一 个 对 象 的 插 槽 函 数 或 信 号 相 关 联, 当 发 射 者 发 射 信 号 时, 接 收 者 的 槽 函 数 或 信 号 将 被 调 用 该 函 数 的 定 义 如 下 所 示 : bool QObject::connect (const QObject * sender, const char * signal,const QObject * receiver, const char * member) [static] 这 个 函 数 的 作 用 就 是 将 发 射 者 sender 对 象 中 的 信 号 signal 与 接 收 者 receiver 中 的 member 插 槽 函 数 联 系 起 来 当 指 定 信 号 signal 时 必 须 使 用 Qt 的 宏 SIGNAL(), 当 指 定 插 槽 函 数 时 必 须 使 用 宏 SLOT() 如 果 发 射 者 与 接 收 者 属 于 同 一 个 对 象 的 话, 那 么 在 connect() 调 用 中 接 收 者 参 数 可 以 省 略 信 号 与 插 槽 相 关 联 下 例 定 义 了 两 个 对 象 : 标 签 对 象 label 和 滚 动 条 对 象 scroll, 并 将 valuechanged() 信 号 与 标 签 对 象 的 setnum() 插 槽 函 数 相 关 联, 另 外 信 号 还 携 带 了 一 个 整 型 参 数, 这 样 标 签 总 是 显 示 滚 动 条 所 处 位 置 的 值 QLabel *label = new QLabel; QScrollBar *scroll = new QScrollBar; QObject::connect(scroll, SIGNAL(valueChanged(int)),label, SLOT(setNum(int))); 信 号 与 信 号 相 关 联 在 下 面 的 构 造 函 数 中,MyWidget 创 建 了 一 个 私 有 的 按 钮 abutton, 按 钮 的 单 击 事 件 产 生 的 信 号 clicked() 与 另 外 一 个 信 号 asignal() 进 行 关 联 这 样, 当 信 号 clicked() 被 发 射 时, 信 号 asignal() 也 接 着 被 发 射 如 下 所 示 : class MyWidget : public QWidget public: MyWidget();... signals: void asignal();... private: 7
371 ... QPushButton *abutton; ; MyWidget::MyWidget() abutton = new QPushButton(this); connect(abutton, SIGNAL(clicked()), SIGNAL(aSignal())); (4) 解 除 信 号 与 插 槽 关 联 当 信 号 与 槽 没 有 必 要 继 续 保 持 关 联 时, 用 户 可 以 使 用 disconnect() 函 数 来 断 开 连 接 其 定 义 如 下 所 示 : bool QObject::disconnect (const QObject * sender, const char * signal,const Object * receiver, const char * member) [static] 这 个 函 数 断 开 发 射 者 中 的 信 号 与 接 收 者 中 的 槽 函 数 之 间 的 关 联 有 3 种 情 况 必 须 使 用 disconnect() 函 数 断 开 与 某 个 对 象 相 关 联 的 任 何 对 象 当 用 户 在 某 个 对 象 中 定 义 了 一 个 或 者 多 个 信 号, 这 些 信 号 与 另 外 若 干 个 对 象 中 的 槽 相 关 联, 如 果 想 要 切 断 这 些 关 联 的 话, 就 可 以 利 用 这 个 方 法, 非 常 简 洁 如 下 所 示 : disconnect(myobject, 0, 0, 0) 或 者 myobject->disconnect() 断 开 与 某 个 特 定 信 号 的 任 何 关 联 这 种 情 况 是 非 常 常 见 的, 其 典 型 用 法 如 下 所 示 : disconnect(myobject, SIGNAL(mySignal()), 0, 0) 或 者 myobject->disconnect(signal(mysignal())) 断 开 两 个 对 象 之 间 的 关 联 这 也 是 非 常 常 用 的 情 况, 如 下 所 示 : disconnect(myobject, 0, myreceiver, 0) 或 者 myobject->disconnect(myreceiver) 在 disconnect() 函 数 中 0 可 以 用 作 一 个 通 配 符, 分 别 表 示 任 何 信 号 任 何 接 收 对 象 接 收 对 象 中 的 任 何 槽 函 数 但 是 发 射 者 sender 不 能 为 0, 其 他 3 个 参 数 的 值 可 以 等 于 搭 建 Qt/Embedded 开 发 环 境 一 般 来 说, 用 Qt/Embedded 开 发 的 应 用 程 序 最 终 会 发 布 到 安 装 有 嵌 入 式 Linux 操 作 系 统 的 小 型 设 备 上, 所 以 使 用 装 有 Linux 操 作 系 统 的 PC 机 或 者 工 作 站 来 完 成 Qt/Embedded 开 发 当 然 是 最 理 想 的 环 境, 此 外 Qt/Embedded 也 可 以 安 装 在 UNIX 或 Windows 系 统 上 这 里 就 以 在 Linux 操 作 系 统 中 安 装 为 例 进 行 介 绍 这 里 需 要 有 3 个 软 件 安 装 包 :tmake 工 具 安 装 包 Qt/Embedded 安 装 包 和 Qt 的 X11 版 的 安 装 包 tmake1.11 或 更 高 版 本 : 生 成 Qt/Embedded 应 用 工 程 的 Makefile 文 件 Qt/Embedded:Qt/Embedded 安 装 包 8
372 Qt for X11:Qt 的 X11 版 的 安 装 包, 产 生 X11 开 发 环 境 所 需 要 的 两 个 工 具 专 业 始 于 专 注 卓 识 源 于 远 见 这 些 软 件 安 装 包 都 有 许 多 不 同 的 版 本, 由 于 版 本 的 不 同 会 导 致 这 些 软 件 在 使 用 时 可 能 引 起 的 冲 突, 为 此 必 须 依 照 一 定 的 安 装 原 则,Qt/Embedded 安 装 包 的 版 本 必 须 比 Qt for X11 的 安 装 包 的 版 本 新, 这 是 因 为 Qt for X11 的 安 装 包 中 的 两 个 工 具 uic 和 designer 产 生 的 源 文 件 会 和 Qt/Embedded 的 库 一 起 被 编 译 链 接, 因 此 要 本 着 向 前 兼 容 的 原 则,Qt for X11 的 版 本 应 比 Qt/Embedded 的 版 本 旧 1. 安 装 tmake 用 户 使 用 普 通 的 解 压 缩 即 可, 注 意 要 将 路 径 添 加 到 全 局 变 量 中 去, 如 下 所 示 : tar zxvf tmake-1.11.tar.gz export TMAKEDIR=$PWD/tmake-1.11 export TMAKEPATH=$TMAKEDIR/lib/qws/linux-x86-g++ export PATH=$TMAKEDIR/bin:$PATH 2. 安 装 Qt/Embedded 这 里 使 用 常 见 的 解 压 命 令 及 安 装 命 令 即 可, 要 注 意 这 里 的 路 径 与 不 同 的 系 统 有 关, 读 者 要 根 据 实 际 情 况 进 行 修 改 另 外, 这 里 的 configure 命 令 带 有 参 数 -qconfig qvfb depths 4816,32 分 别 为 指 定 Qt 嵌 入 式 开 发 包 生 成 虚 拟 缓 冲 帧 工 具 qvfb, 并 支 持 位 的 显 示 颜 色 深 度 另 外 读 者 也 可 以 在 configure 的 参 数 中 添 加 -system -jpeg 或 gif 命 令, 使 Qt/Embedded 平 台 能 支 持 jpeg gif 格 式 的 图 形 Qt/Embedded 开 发 包 有 5 种 编 译 范 围 的 选 项, 使 用 这 些 选 项 可 控 制 Qt 生 成 的 库 文 件 的 大 小 如 命 令 make sub-src 指 定 按 精 简 方 式 编 译 开 发 包, 也 就 是 说 有 些 Qt 类 未 被 编 译 其 他 编 译 选 项 的 具 体 用 法 可 通 过./configure help 命 令 查 看 精 简 方 式 的 安 装 步 骤 如 下 所 示 : tar zxvf qt-embedded tar.gz cd qt export QTDIR=$PWD export QTEDIR=$QTDIR export PATH=$QTDIR/bin:$PATH export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH./configure -qconfig local-qvfb -depths 4,8,16,32 make sub-src 3. 安 装 Qt/X 与 上 一 步 类 似, 用 户 也 可 以 在 configure 后 添 加 一 定 的 参 数, 如 -no-opengl 或 -no-xfs, 可 以 键 入 命 令./configure help 来 获 得 一 些 帮 助 信 息 tar xfz qt-x tar.gz cd qt export QTDIR=$PWD export PATH=$QTDIR/bin:$PATH export LD_LIBRARY_PATH=$QTDIR/lib:$LD_LIBRARY_PATH 9
373 ./configure -no-opengl make make -C tools/qvfb mv tools/qvfb/qvfb bin cp bin/uic $QTEDIR/bin Qt/Embedded 窗 口 部 件 Qt 提 供 了 一 整 套 的 窗 口 部 件 它 们 组 合 起 来 可 用 于 创 建 用 户 界 面 的 可 视 元 素 按 钮 菜 单 滚 动 条 消 息 框 和 应 用 程 序 窗 口 都 是 窗 口 部 件 的 实 例 因 为 所 有 的 窗 口 部 件 既 是 控 件 又 是 容 器, 因 此 Qt 的 窗 口 部 件 不 能 任 意 地 分 为 控 件 和 容 器 通 过 子 类 化 已 存 在 的 Qt 部 件 或 少 数 时 候 必 要 的 全 新 创 建, 自 定 义 的 窗 口 部 件 能 很 容 易 地 创 建 出 来 窗 口 部 件 是 QWidget 或 其 子 类 的 实 例, 用 户 自 定 义 的 窗 口 通 过 子 类 化 得 到, 如 图 12.3 所 示 图 12.3 源 自 QWidget 的 类 层 次 结 构 一 个 窗 口 部 件 可 包 含 任 意 数 量 的 子 部 件 子 部 件 在 父 部 件 的 区 域 内 显 示 没 有 父 部 件 的 部 件 是 顶 级 部 件 ( 比 如 一 个 窗 口 ), 通 常 在 桌 面 的 任 务 栏 上 有 它 们 的 入 口 Qt 不 在 窗 口 部 件 上 施 加 任 何 限 制 任 何 部 件 都 可 以 是 顶 级 部 件, 任 何 部 件 都 可 以 是 其 他 部 件 的 子 部 件 通 过 自 动 或 手 动 ( 如 果 你 喜 欢 ) 使 用 布 局 管 理 器 可 以 设 定 子 部 件 在 父 部 件 区 域 中 的 位 置 如 果 父 部 件 被 停 用 隐 藏 或 删 除, 则 同 样 的 动 作 会 应 用 于 它 的 所 有 子 部 件 1.Hello 窗 口 实 例 下 面 是 一 个 显 示 Hello Qt/Embedded! 的 程 序 的 完 整 代 码 : #include <qapplication.h> #include <qlabel.h> int main(int argc, char **argv) QApplication app(argc, argv); QLabel *hello=new QLabel ("<font color=blue>hello""<i>qt Embedded!</i></font>",0); app.setmainwidget(hello); hello->show(); return app.exec(); 图 12.4 是 该 Hello 窗 口 的 运 行 效 果 图 : 10
374 2. 常 见 通 用 窗 口 组 合 Qt 中 还 有 一 些 常 见 的 通 用 窗 口, 它 们 使 用 了 Windows 风 格 显 示, 图 分 别 描 述 了 常 见 的 一 些 通 用 窗 口 的 组 合 使 用 图 12.4 Hello 窗 口 运 行 效 果 图 图 12.5 使 用 QHBox 排 列 一 个 标 签 和 一 个 按 钮 图 12.6 使 用 了 QButtonGroup 的 两 个 单 选 框 和 两 个 复 选 框 图 12.7 QGroupBox 组 合 图 示 图 12.8 使 用 了 QGroupBox 进 行 排 列 的 日 期 类 QDateTimeEdit 一 个 行 编 辑 框 类 QLine- Edit 一 个 文 本 编 辑 类 QTextEdit 和 一 个 组 合 框 类 QComboBox 图 12.9 是 以 QGrid 排 列 的 一 个 QDial 一 个 QProgressBar 一 个 QSpinBox 一 个 QScrollBar 一 个 QLCDNumber 和 一 个 QSlider 图 是 以 QGrid 排 列 的 一 个 QIconView 一 个 QListView 一 个 QListBox 和 一 个 QTable 图 12.8 QGrid 组 合 图 示 1 图 12.9 QGrid 组 合 图 示 2 图 钟 表 部 件 图 示 3. 自 定 义 窗 口 开 发 者 可 以 通 过 子 类 化 QWidget 或 它 的 一 个 子 类 创 建 他 们 自 己 的 部 件 或 对 话 框 为 了 举 例 说 明 子 类 化, 下 面 提 供 了 数 字 钟 部 件 的 完 整 代 码 钟 表 部 件 是 一 个 能 显 示 当 前 时 间 并 自 动 更 新 的 LCD 一 个 冒 号 分 隔 符 随 秒 数 的 流 逝 而 闪 烁, 如 图 所 示 Clock 从 QLCDNumber 部 件 继 承 了 LCD 功 能 它 有 一 个 典 型 部 件 类 所 拥 有 的 典 型 构 造 函 数, 带 有 可 选 的 parent 和 name 参 数 ( 如 果 设 置 了 name 参 数, 测 试 和 调 试 会 更 容 易 ) 系 统 有 规 律 地 调 用 从 QObject 继 承 的 timerevent() 函 数 它 在 clock.h 中 定 义 如 下 所 示 : #include <qlcdnumber.h> 11
375 class Clock:public QLCDNumber public: Clock(QWidget *parent=0,const char *name=0); protected: void timerevent(qtimerevent *event); private: void showtime(); bool showingcolon; ; 构 造 函 数 showtime() 是 用 当 前 时 间 初 始 化 钟 表, 并 且 告 诉 系 统 每 1000ms 调 用 一 次 timerevent() 来 刷 新 LCD 的 显 示 在 showtime() 中, 通 过 调 用 QLCDNumber::display() 来 显 示 当 前 时 间 每 次 调 用 showtime() 来 让 冒 号 闪 烁 时, 冒 号 就 被 空 白 代 替 clock.cpp 的 源 码 如 下 所 示 : #include <qdatetime.h> #include "clock.h" Clock::Clock(QWidget *parent,const char *name) :QLCDNumber(parent,name),showingColon(true) showtime(); starttimer(1000); void Clock::timerEvent(QTimerEvent *) showtime(); void Clock::showTime() QString timer=qtime::currenttime().tostring().left(5); if (!showingcolon) time[2]=' '; display(time); showingcolon=!showingcolon; 文 件 clock.h 和 clock.cpp 完 整 地 声 明 并 实 现 了 Clock 部 件 #include <qapplication.h> #include "clock.h" int main(int argc,char **argv) QApplication app(argc,argv); Clock *clock=new Clock; app.setmainwidget(clock); clock->show(); return app.exec(); 12
376 Qt/Embedded 图 形 界 面 编 程 Qt 提 供 了 所 有 可 能 的 类 和 函 数 来 创 建 GUI 程 序 Qt 既 可 用 来 创 建 主 窗 口 式 的 程 序, 即 一 个 有 菜 单 栏, 工 具 栏 和 状 态 栏 作 为 环 绕 的 中 心 区 域 ; 也 可 以 用 来 创 建 对 话 框 式 的 程 序, 使 用 按 钮 和 必 要 的 选 项 卡 来 呈 现 选 项 与 信 息 Qt 支 持 SDI( 单 文 档 界 面 ) 和 MDI( 多 文 档 界 面 ) Qt 还 支 持 拖 动 放 下 和 剪 贴 板 工 具 栏 可 以 在 工 具 栏 区 域 内 移 动, 拖 拽 到 其 他 区 域 或 者 作 为 工 具 托 盘 浮 动 起 来 这 个 功 能 是 内 建 的, 不 需 要 额 外 的 代 码, 但 程 序 员 在 需 要 时 可 以 约 束 工 具 栏 的 行 为 使 用 Qt 可 以 大 大 简 化 编 程 例 如, 如 果 一 个 菜 单 项 一 个 工 具 栏 按 钮 和 一 个 快 捷 键 都 完 成 同 样 的 动 作, 那 么 这 个 动 作 只 需 要 一 份 代 码 Qt 还 提 供 消 息 框 和 一 系 列 标 准 对 话 框, 使 得 程 序 向 用 户 提 问 和 让 用 户 选 择 文 件 文 件 夹 字 体 以 及 颜 色 变 得 更 加 简 单 为 了 呈 现 一 个 消 息 框 或 一 个 标 准 对 话 框, 只 需 要 用 一 个 使 用 方 便 的 Qt 静 态 函 数 的 一 行 的 语 句 1. 主 窗 口 类 QMainWindow 类 提 供 了 一 个 典 型 应 用 程 序 的 主 窗 口 框 架 一 个 主 窗 口 包 含 了 一 组 标 准 窗 体 的 集 合 主 窗 口 的 顶 部 包 含 一 个 菜 单 栏, 它 的 下 方 放 置 着 一 个 工 具 栏, 工 具 栏 可 以 移 动 到 其 他 的 停 靠 区 域 主 窗 口 允 许 停 靠 的 位 置 有 顶 部 左 边 右 边 和 底 部 工 具 栏 可 以 被 拖 放 到 一 个 停 靠 的 位 置, 从 而 形 成 一 个 浮 动 的 工 具 面 板 主 窗 口 的 下 方, 也 就 是 在 底 部 的 停 靠 位 置 下 方 有 一 个 状 态 栏 主 窗 口 的 中 间 区 域 可 以 包 含 其 他 的 窗 体 提 示 工 具 和 这 是 什 么 帮 助 按 钮 以 旁 述 的 方 式 阐 述 了 用 户 接 口 的 使 用 方 法 对 于 小 屏 幕 的 设 备, 使 用 Qt 图 形 设 计 器 定 义 的 标 准 的 QWidget 模 板 比 使 用 主 窗 口 类 更 好 一 些 典 型 的 模 板 包 含 有 菜 单 栏 工 具 栏, 可 能 没 有 状 态 栏 ( 在 必 要 的 情 况 下, 可 以 用 任 务 栏, 标 题 栏 来 显 示 状 态 ) 例 如, 一 个 文 本 编 辑 器 可 以 把 QTextEdit 作 为 中 心 部 件 : QTextEdit *editor = new QTextEdit(mainWindow); mainwindow->setcentralwidget(editor); 2. 菜 单 类 弹 出 式 菜 单 QPopupMenu 类 以 垂 直 列 表 的 方 式 显 示 菜 单 项, 它 可 以 是 单 个 的 ( 例 如 上 下 文 相 关 菜 单 ), 可 以 以 菜 单 栏 的 方 式 出 现, 或 者 是 别 的 弹 出 式 菜 单 的 子 菜 单 出 现 每 个 菜 单 项 可 以 有 一 个 图 标 一 个 复 选 框 和 一 个 加 速 器 ( 快 捷 键 ), 菜 单 项 通 常 对 应 一 个 动 作 ( 例 如 存 盘 ), 分 隔 器 通 常 显 示 成 一 条 竖 线, 它 用 于 把 一 组 相 关 联 的 动 作 菜 单 分 离 成 组 下 面 是 一 个 建 立 包 含 有 New Open 和 Exit 菜 单 项 的 文 件 菜 单 的 例 子 QPopupMenu *filemenu = new QPopupMenu(this); filemenu->insertitem("&new", this, SLOT(newFile()), CTRL+Key_N); filemenu->insertitem("&open...", this, SLOT(open()), CTRL+Key_O); filemenu->insertseparator(); filemenu->insertitem("&exit", qapp, SLOT(quit()), CTRL+Key_Q); 当 一 个 菜 单 项 被 选 中, 和 它 相 关 的 插 槽 将 被 执 行 加 速 器 ( 快 捷 键 ) 很 少 在 一 个 没 有 键 盘 输 入 的 设 备 上 使 用,Qt/Embedded 的 典 型 配 置 并 未 包 含 对 加 速 器 的 支 持 上 面 出 现 的 代 码 &New 意 思 是 在 桌 面 机 器 上 以 New 的 方 式 显 示 出 来, 但 是 在 嵌 入 式 设 备 中, 它 只 会 显 示 为 New QMenuBar 类 实 现 了 一 个 菜 单 栏, 它 会 自 动 地 设 置 几 何 尺 寸 并 在 它 的 父 窗 体 的 顶 部 显 示 出 来, 如 果 父 窗 体 的 宽 度 不 够 宽 以 至 不 能 显 示 一 个 完 整 的 菜 单 栏, 那 么 菜 单 栏 将 会 分 为 多 行 显 示 出 来 Qt 内 置 的 布 局 管 理 能 够 自 动 调 整 菜 单 栏 13
377 Qt 的 菜 单 系 统 是 非 常 灵 活 的, 菜 单 项 可 以 被 动 态 使 能 失 效 添 加 或 者 删 除 通 过 子 类 化 QCustomMenuItem, 用 户 可 以 建 立 客 户 化 外 观 和 功 能 的 菜 单 项 3. 工 具 栏 工 具 栏 可 以 被 移 动 到 中 心 区 域 的 顶 部 底 部 左 边 或 右 边 任 何 工 具 栏 都 可 以 拖 拽 到 工 具 栏 区 域 的 外 边, 作 为 独 立 的 浮 动 工 具 托 盘 QToolButton 类 实 现 了 具 有 一 个 图 标, 一 个 3D 框 架 和 一 个 可 选 标 签 的 工 具 栏 切 换 型 工 具 栏 按 钮 具 有 可 以 打 开 或 关 闭 某 些 特 征 的 功 能 其 他 的 则 会 执 行 一 个 命 令 可 以 为 活 动 关 闭 开 启 等 模 式, 打 开 或 关 闭 等 状 态 提 供 不 同 的 图 标 如 果 只 提 供 一 个 图 标,Qt 能 根 据 可 视 化 线 索 自 动 地 辨 别 状 态, 例 如 将 禁 用 的 按 钮 变 灰, 工 具 栏 按 钮 也 能 触 发 弹 出 式 菜 单 QToolButton 通 常 在 QToolBar 内 并 排 出 现 一 个 程 序 可 含 有 任 意 数 量 的 工 具 栏 并 且 用 户 可 以 自 由 地 移 动 它 们 工 具 栏 可 以 包 括 几 乎 所 有 部 件, 例 如 QComboBox 和 QSpinBox 4. 旁 述 现 在 的 应 用 主 要 使 用 旁 述 的 方 式 去 解 释 用 户 接 口 的 用 法 Qt 提 供 了 两 种 旁 述 的 方 式, 即 提 示 栏 和 这 是 什 么 帮 助 按 钮 提 示 栏 是 小 的, 通 常 是 黄 色 的 矩 形, 当 光 标 在 窗 体 的 某 些 位 置 游 动 时, 它 就 会 自 动 地 出 现 它 主 要 用 于 解 释 工 具 栏 按 钮, 特 别 是 那 些 缺 少 文 字 标 签 说 明 的 工 具 栏 按 钮 的 用 途 下 面 就 是 如 何 设 置 一 个 存 盘 按 钮 的 提 示 代 码 QToolTip::add(saveButton,"Save"); 当 提 示 字 符 出 现 之 后, 还 可 以 在 状 态 栏 显 示 更 详 细 的 文 字 说 明 对 于 一 些 没 有 鼠 标 的 设 备 ( 例 如 那 些 使 用 触 点 输 入 的 设 备 ), 就 不 会 出 现 鼠 标 的 光 标 在 窗 体 上 进 行 游 动, 这 样 就 不 能 激 活 提 示 栏 对 于 这 些 设 备 也 许 就 需 要 使 用 这 是 什 么 帮 助 按 钮, 或 者 使 用 一 种 状 态 来 表 示 输 入 设 备 正 在 进 行 游 动, 例 如 用 按 下 或 者 握 住 的 状 态 来 表 示 现 在 正 在 进 行 游 动 这 是 什 么 帮 助 按 钮 和 提 示 栏 有 些 相 似, 只 不 过 前 者 是 要 用 户 单 击 它 才 会 显 示 旁 述 在 小 屏 幕 设 备 上, 要 想 单 击 这 是 什 么 帮 助 按 钮, 具 体 的 方 法 是, 在 靠 近 应 用 的 X 窗 口 的 关 闭 按 钮 x 附 近 你 会 看 到 一 个? 符 号 的 小 按 钮, 这 个 按 钮 就 是 这 是 什 么 的 帮 助 按 钮 一 般 来 说, 这 是 什 么 帮 助 按 钮 按 下 后 要 显 示 的 提 示 信 息 应 该 比 提 示 栏 要 多 一 些 下 面 是 设 置 一 个 存 盘 按 钮 的 这 是 什 么 文 本 提 示 信 息 的 方 法 : QWhatsThis::add(saveButton, "Save the current file."); QToolTip 和 QWhatsThis 类 提 供 了 可 以 通 过 重 新 实 现 来 获 取 更 多 特 殊 化 行 为 的 虚 函 数, 比 如 根 据 鼠 标 在 部 件 的 位 置 来 显 示 不 同 的 文 本 5. 动 作 应 用 程 序 通 常 提 供 几 种 不 同 的 方 式 来 执 行 特 定 的 动 作 比 如, 许 多 应 用 程 序 通 过 菜 单 (Flie->Save) 工 具 栏 ( 像 一 个 软 盘 的 按 钮 ) 和 快 捷 键 (Ctrl+S) 来 提 供 Save 动 作 QAction 类 封 装 了 动 作 这 个 概 念 它 允 许 程 序 员 在 某 个 地 方 定 义 一 个 动 作 下 面 的 代 码 实 现 了 一 个 Save 菜 单 项 一 个 Save 工 具 栏 按 钮 和 一 个 Save 快 捷 键, 并 且 均 有 旁 述 帮 助 : QAction *saveact = new QAction("Save", saveicon, "&Save",CTRL+Key_S, this); connect(saveact,signal(activated()), this, SLOT(save())); saveact->setwhatsthis("saves the current file."); saveact->addto(filemenu); 14
378 saveact->addto(toolbar); 为 了 避 免 重 复, 使 用 QAction 可 保 证 菜 单 项 的 状 态 与 工 具 栏 保 持 同 步, 而 工 具 提 示 能 在 需 要 的 时 候 显 示 禁 用 一 个 动 作 会 禁 用 相 应 的 菜 单 项 和 工 具 栏 按 钮 类 似 地, 当 用 户 单 击 切 换 型 按 钮 时, 相 应 的 菜 单 项 会 因 此 被 选 中 或 不 选 Qt/Embedded 对 话 框 设 计 Qt/Embedded 对 话 框 的 设 计 比 较 复 杂, 要 使 用 布 局 管 理 自 动 地 设 置 窗 体 与 别 的 窗 体 之 间 相 对 的 尺 寸 和 位 置, 这 样 可 以 确 保 对 话 框 能 够 最 好 地 利 用 屏 幕 上 的 可 用 空 间, 接 着 还 要 使 用 Qt 图 形 设 计 器 可 视 化 设 计 工 具 建 立 对 话 框 下 面 就 详 细 讲 解 具 体 的 步 骤 1. 布 局 Qt 的 布 局 管 理 用 于 组 织 管 理 一 个 父 窗 体 区 域 内 的 子 窗 体 它 的 特 点 是 可 以 自 动 设 置 子 窗 体 的 位 置 和 大 小, 并 可 确 定 出 一 个 顶 级 窗 体 的 最 小 和 缺 省 的 尺 寸, 当 窗 体 的 字 体 或 内 容 变 化 后, 它 可 以 重 置 一 个 窗 体 的 布 局 使 用 布 局 管 理, 开 发 者 可 以 编 写 独 立 于 屏 幕 大 小 和 方 向 之 外 的 程 序, 从 而 不 需 要 浪 费 代 码 空 间 和 重 复 编 写 代 码 对 于 一 些 国 际 化 的 应 用 程 序, 使 用 布 局 管 理, 可 以 确 保 按 钮 和 标 签 在 不 同 的 语 言 环 境 下 有 足 够 的 空 间 显 示 文 本, 不 会 造 成 部 分 文 字 被 剪 掉 布 局 管 理 提 供 部 分 用 户 接 口 组 件, 例 如 输 入 法 和 任 务 栏 变 得 更 容 易 我 们 可 以 通 过 一 个 例 子 说 明 这 一 点, 当 Qtopia 的 用 户 输 入 文 字 时, 输 入 法 会 占 用 一 定 的 文 字 空 间, 应 用 程 序 这 时 也 会 根 据 可 用 屏 幕 尺 寸 的 变 化 调 整 自 己 Qtopia 的 布 局 管 理 示 例 如 图 所 示 (1) 内 建 布 局 管 理 器 Qt 提 供 了 3 种 用 于 布 局 管 理 的 类 :QHBoxLayout QVBox- Layout 和 QGridLayout QHBoxLayout 布 局 管 理 把 窗 体 按 照 水 平 方 向 从 左 至 右 排 成 一 行 QVBoxLayout 布 局 管 理 把 窗 体 按 照 垂 直 方 向 从 上 至 下 排 成 一 列 QGridLayout 布 局 管 理 以 网 格 的 方 式 来 排 列 窗 体, 一 个 窗 体 可 以 占 据 多 个 网 格 它 们 的 示 例 如 图 所 示 图 Qtopia 的 布 局 管 理 在 多 数 情 况 下,Qt 的 布 局 管 理 器 为 其 管 理 的 部 件 挑 选 一 个 最 适 合 的 尺 寸 以 便 窗 口 能 够 平 滑 地 缩 放 如 果 其 缺 省 值 不 合 适, 开 发 者 可 以 使 用 以 下 机 制 微 调 布 局 : 设 置 一 个 最 小 尺 寸 一 个 最 大 尺 寸, 或 者 为 一 些 子 部 件 设 置 固 定 的 大 小 图 种 布 局 管 理 类 示 意 图 设 置 一 些 延 伸 项 目 或 间 隔 项 目, 延 伸 或 间 隔 项 目 会 填 充 空 余 的 布 局 空 间 改 变 子 部 件 的 尺 寸 策 略 通 过 调 用 QWidget::setSizePolicy(), 程 序 员 可 以 仔 细 调 整 子 部 件 的 缩 放 行 为 子 部 件 可 以 设 置 为 扩 展 收 缩 保 持 原 大 小 等 状 态 15
379 改 变 子 部 件 的 建 议 大 小 QWidget::sizeHint() 和 QWidget::minimumSizeHint() 会 根 据 内 容 返 回 部 件 的 首 选 尺 寸 和 最 小 首 选 尺 寸 内 建 部 件 提 供 了 合 适 的 重 新 实 现 设 置 延 伸 因 子 延 伸 因 子 规 定 了 子 部 件 的 相 应 增 量, 比 如,2/3 的 可 用 空 间 分 配 给 部 件 A 而 1/3 分 配 给 B (2) 布 局 嵌 套 布 局 可 以 嵌 套 任 意 层 图 显 示 了 一 个 对 话 框 的 两 种 大 小 图 一 个 对 话 框 的 两 种 大 小 这 个 对 话 框 使 用 了 3 种 布 局 : 一 个 QVBoxLayout 组 合 了 按 钮, 一 个 QHBoxLayout 组 合 了 国 家 列 表 和 那 组 按 钮, 一 个 QVBoxLayout 组 合 了 Select a country 标 签 和 剩 下 的 部 件 一 个 延 伸 项 目 用 来 维 护 Cancel 和 Help 按 钮 间 的 距 离 下 面 的 代 码 创 建 了 对 话 框 部 件 和 布 局 : QVBoxLayout *buttonbox = new QVBoxLayout(6); buttonbox->addwidget(new QPushButton("OK", this)); buttonbox->addwidget(new QPushButton("Cancel", this)); buttonbox->addstretch(1); buttonbox->addwidget(new QPushButton("Help", this)); QListBox *countrylist = new QListBox(this); countrylist->insertitem("canada"); /*...*/ countrylist->insertitem("united States of America"); QHBoxLayout *middlebox = new QHBoxLayout(11); middlebox->addwidget(countylist); middlebox->addlayout(buttonbox); QVBoxLayout *toplevelbox = new QVBoxLayout(this,6,11); toplevelbox->addwidget(new QLabel("Select a country", this)); toplevelbox->addlayout(middlebox); 可 以 看 到,Qt 让 布 局 变 得 非 常 容 易 (3) 自 定 义 布 局 通 过 子 类 化 QLayout, 开 发 者 可 以 定 义 自 己 的 布 局 管 理 器 和 Qt 一 起 提 供 的 customlayout 样 例 展 示 了 3 个 自 定 义 布 局 管 理 器 :BorderLayout CardLayout 和 SimpleFlow, 程 序 员 可 以 使 用 并 修 改 它 们 Qt 还 包 括 QSplitter, 是 一 个 最 终 用 户 可 以 操 纵 的 分 离 器 某 些 情 况 下,QSplitter 可 能 比 布 局 管 理 器 更 为 可 取 为 了 完 全 控 制, 重 新 实 现 每 个 子 部 件 的 QWidget::resizeEvent() 并 调 用 QWidget::setGeometry(), 就 可 以 在 一 个 部 件 中 手 动 地 实 现 布 局 2.Qt/Embedded 图 形 设 计 器 Qt 图 形 设 计 器 是 一 个 具 有 可 视 化 用 户 接 口 的 设 计 工 具 Qt 的 应 用 程 序 可 以 完 全 用 源 代 码 来 编 写, 或 者 使 16
380 用 Qt 图 形 设 计 器 来 加 速 开 发 工 作 启 动 Qt 图 形 设 计 器 的 方 法 是 : cd qt-2.3.2/bin./designer 这 样 就 可 以 启 动 一 个 图 形 化 的 设 计 界 面, 如 图 所 示 图 Qt 图 形 设 计 器 界 面 开 发 者 单 击 工 具 栏 上 的 代 表 不 同 功 能 的 子 窗 体 / 组 件 的 按 钮, 然 后 把 它 拖 放 到 一 个 表 单 (Form) 上, 这 样 就 可 以 把 一 个 子 窗 体 / 组 件 放 到 表 单 上 了 开 发 者 可 以 使 用 属 性 对 话 框 来 设 置 子 窗 体 的 属 性, 精 确 地 设 置 子 窗 体 的 位 置 和 尺 寸 大 小 是 没 必 要 的 开 发 者 可 以 选 择 一 组 窗 体, 然 后 对 它 们 进 行 排 列 例 如, 我 们 选 定 了 一 些 按 钮 窗 体, 然 后 使 用 水 平 排 列 (lay out horizontally) 选 项 对 它 们 进 行 一 个 接 一 个 地 水 平 排 列 这 样 做 不 仅 使 得 设 计 工 作 变 得 更 快, 而 且 完 成 后 的 窗 体 将 能 够 按 照 属 性 设 置 的 比 例 填 充 窗 口 的 可 用 范 围 使 用 Qt 图 形 设 计 器 进 行 图 形 用 户 接 口 的 设 计 可 以 消 除 应 用 的 编 译 链 接 和 运 行 时 间, 同 时 使 修 改 图 形 用 户 接 口 的 设 计 变 得 更 容 易 Qt 图 形 设 计 器 的 预 览 功 能 使 开 发 者 能 够 在 开 发 阶 段 看 到 各 种 样 式 的 图 形 用 户 界 面, 也 包 括 客 户 样 式 的 用 户 界 面 通 过 Qt 集 成 功 能 强 大 的 数 据 库 类,Qt 图 形 设 计 器 还 可 提 供 生 动 的 数 据 库 数 据 浏 览 和 编 辑 操 作 开 发 者 可 以 建 立 同 时 包 含 有 对 话 框 和 主 窗 口 的 应 用, 其 中 主 窗 口 可 以 放 置 菜 单 工 具 栏 旁 述 帮 助 等 子 窗 口 部 件 Qt 图 形 设 计 器 提 供 了 几 种 表 单 模 板, 如 果 窗 体 会 被 多 个 不 同 的 应 用 反 复 使 用, 那 么 开 发 者 也 可 建 立 自 己 的 表 单 模 板 以 确 保 窗 体 的 一 致 性 Qt 图 形 设 计 器 使 用 向 导 来 帮 助 人 们 更 快 更 方 便 地 建 立 包 含 有 工 具 栏 菜 单 和 数 据 库 等 方 面 的 应 用 程 序 员 可 以 建 立 自 己 的 客 户 窗 体, 并 把 它 集 成 到 Qt 图 形 设 计 器 中 Qt 图 形 设 计 器 设 计 的 图 形 界 面 以 扩 展 名 为 ui 的 文 件 进 行 保 存, 这 个 文 件 有 良 好 的 可 读 性, 这 个 文 件 可 被 uic(qt 提 供 的 用 户 接 口 编 译 工 具 ) 编 译 成 为 C++ 的 头 文 件 和 源 文 件 qmake 工 具 在 它 为 工 程 生 成 的 Makefile 文 件 中 自 动 包 含 了 uic 生 成 头 文 件 和 源 文 件 的 规 则 另 一 种 可 选 的 做 法 是 在 应 用 程 序 运 行 期 间 载 入 ui 文 件, 然 后 把 它 转 变 为 具 备 原 先 全 部 功 能 的 表 单 这 样 开 发 者 就 可 以 在 程 序 运 行 期 间 动 态 地 修 改 应 用 的 界 面, 而 不 需 重 新 编 译 应 用, 另 一 方 面, 也 使 得 应 用 的 文 件 尺 寸 减 小 了 3. 建 立 对 话 框 Qt 为 许 多 通 用 的 任 务 提 供 了 现 成 的 包 含 了 实 用 的 静 态 函 数 的 对 话 框 类, 主 要 有 以 下 几 种 17
381 QMessageBox 类 : 是 一 个 用 于 向 用 户 提 供 信 息 或 是 让 用 户 进 行 一 些 简 单 选 择 ( 例 如 yes 或 no ) 的 对 话 框 类, 如 图 所 示 QProgressDialog 类 : 包 含 了 一 个 进 度 栏 和 一 个 Cancel 按 钮, 如 图 所 示 QWizard 类 : 提 供 了 一 个 向 导 对 话 框 的 框 架, 如 图 所 示 图 QMessageBox 类 对 话 框 图 QProgressDialog 类 对 话 框 图 QWizard 类 对 话 框 另 外,Qt 提 供 的 对 话 框 还 包 括 QColorDialog QFileDialog QFontDialog 和 QPrintDialog 这 些 类 通 常 适 用 于 桌 面 应 用, 一 般 不 会 在 Qt/Embedded 中 编 译 使 用 它 们 12.3 实 验 内 容 使 用 Qt 编 写 Hello,World 程 序 1. 实 验 目 的 通 过 编 写 一 个 跳 动 的 Hello,World 字 符 串, 进 一 步 熟 悉 嵌 入 式 Qt 的 开 发 过 程 2. 实 验 步 骤 (1) 生 成 一 个 工 程 文 件 (.pro 文 件 ) 使 用 命 令 progen 产 生 一 个 工 程 文 件 (progen 程 序 可 在 tmake 的 安 装 路 径 下 找 到 ) 如 下 所 示 : progen t app.t o hello.pro 那 样 产 生 的 hello.pro 工 程 文 件 并 不 完 整, 开 发 者 还 需 添 加 工 程 所 包 含 的 头 文 件, 源 文 件 等 信 息 (2) 新 建 一 个 窗 体 启 动 Qt 图 形 编 辑 器, 使 用 如 下 命 令 :./designer( 该 程 序 在 qt-2.3.x for x11 的 安 装 路 径 的 bin 目 录 下 ) 接 着 单 击 编 辑 器 的 new 菜 单, 弹 出 了 一 个 new Form 对 话 框, 在 这 个 对 话 框 里 选 择 Widget, 然 后 单 击 OK 按 钮, 这 样 就 新 建 了 一 个 窗 体 接 下 来 再 对 这 个 窗 体 的 属 性 进 行 设 置, 注 意 把 窗 体 的 name 属 性 设 为 Hello ; 窗 体 的 各 种 尺 寸 设 为 宽 240 高 320, 目 的 是 使 窗 体 大 小 和 FS2410 带 的 显 示 屏 的 大 小 一 致 ; 窗 体 背 景 颜 色 设 置 为 白 色 具 体 设 置 如 图 所 示 18
382 图 Hello 窗 体 的 属 性 设 置 设 置 完 成 后, 将 其 保 存 为 hello.ui 文 件, 这 个 文 件 就 是 Hello 窗 体 的 界 面 存 储 文 件 (3) 生 成 Hello 窗 体 类 的 头 文 件 和 实 现 文 件 下 面 根 据 上 述 的 界 面 文 件 hello.ui 使 用 uic 工 具 产 生 Hello 窗 体 类 的 头 文 件 和 实 现 文 件, 具 体 方 法 是 : $ cd qt-2.3.7/bin $ uic o hello.h hello.ui $ uic o hello.cpp impl hello.h hello.ui 这 样 就 得 到 了 Hello 窗 体 类 的 头 文 件 hello.h 和 实 现 文 件 hello.cpp 下 面 就 可 以 根 据 需 要 实 现 的 具 体 功 能, 在 hello.cpp 文 件 里 添 加 相 应 的 代 码 比 如 要 在 Hello 的 窗 体 上 显 示 一 个 动 态 的 字 符 串 Hello,World, 那 么 需 要 重 新 实 现 paintevent(qpaintevent *) 方 法, 同 时 还 需 要 添 加 一 个 定 时 器 QTimer 实 例, 以 周 期 性 刷 新 屏 幕, 从 而 得 到 动 画 的 效 果 下 面 是 修 改 后 的 hello.h 和 hello.cpp 文 件 /**************************************************************************** ** 以 下 是 hello.h 的 代 码 ****************************************************************************/ #ifndef HELLO_H #define HELLO_H #include <qvariant.h> #include <qwidget.h> class QVBoxLayout; class QHBoxLayout; class QGridLayout; class Hello : public QWidget Q_OBJECT public: Hello(QWidget* parent = 0, const char* name = 0, WFlags fl = 0); ~Hello(); /* 以 下 是 手 动 添 加 的 代 码 */ signals: void clicked(); protected: 19
383 void mousereleaseevent(qmouseevent *); void paintevent(qpaintevent *); private slots: void animate(); private: QString t; int b; ; #endif // HELLO_H /**************************************************************************** ** 以 下 是 hello.cpp 源 代 码 ****************************************************************************/ #include "hello.h" #include <qlayout.h> #include <qvariant.h> #include <qtooltip.h> #include <qwhatsthis.h> #include <qpushbutton.h> #include <qtimer.h> #include <qpainter.h> #include <qpixmap.h> /* * Constructs a Hello which is a child of 'parent', with the * name 'name' and widget flags set to 'f' */ Hello::Hello(QWidget* parent, const char* name, WFlags fl) : QWidget(parent, name, fl) if (!name) setname("hello"); resize(240, 320); setminimumsize(qsize(240, 320)); setmaximumsize(qsize(240, 320)); setsizeincrement(qsize(240, 320)); setbasesize(qsize(240, 320)); QPalette pal; QColorGroup cg; cg.setcolor(qcolorgroup::foreground, black); cg.setcolor(qcolorgroup::button, QColor(192, 192, 192)); cg.setcolor(qcolorgroup::light, white); cg.setcolor(qcolorgroup::midlight, QColor(223, 223, 223)); cg.setcolor(qcolorgroup::dark, QColor(96, 96, 96)); cg.setcolor(qcolorgroup::mid, QColor(128, 128, 128)); cg.setcolor(qcolorgroup::text, black); cg.setcolor(qcolorgroup::brighttext, white); cg.setcolor(qcolorgroup::buttontext, black); cg.setcolor(qcolorgroup::base, white); 20
384 cg.setcolor(qcolorgroup::background, white); cg.setcolor(qcolorgroup::shadow, black); cg.setcolor(qcolorgroup::highlight, black); cg.setcolor(qcolorgroup::highlightedtext, white); pal.setactive(cg); cg.setcolor(qcolorgroup::foreground, black); cg.setcolor(qcolorgroup::button, QColor(192, 192, 192)); cg.setcolor(qcolorgroup::light, white); cg.setcolor(qcolorgroup::midlight, QColor(220, 220, 220)); cg.setcolor(qcolorgroup::dark, QColor(96, 96, 96)); cg.setcolor(qcolorgroup::mid, QColor(128, 128, 128)); cg.setcolor(qcolorgroup::text, black); cg.setcolor(qcolorgroup::brighttext, white); cg.setcolor(qcolorgroup::buttontext, black); cg.setcolor(qcolorgroup::base, white); cg.setcolor(qcolorgroup::background, white); cg.setcolor(qcolorgroup::shadow, black); cg.setcolor(qcolorgroup::highlight, black); cg.setcolor(qcolorgroup::highlightedtext, white); pal.setinactive(cg); cg.setcolor(qcolorgroup::foreground, QColor(128, 128, 128)); cg.setcolor(qcolorgroup::button, QColor(192, 192, 192)); cg.setcolor(qcolorgroup::light, white); cg.setcolor(qcolorgroup::midlight, QColor(220, 220, 220)); cg.setcolor(qcolorgroup::dark, QColor(96, 96, 96)); cg.setcolor(qcolorgroup::mid, QColor(128, 128, 128)); cg.setcolor(qcolorgroup::text, black); cg.setcolor(qcolorgroup::brighttext, white); cg.setcolor(qcolorgroup::buttontext, QColor(128, 128, 128)); cg.setcolor(qcolorgroup::base, white); cg.setcolor(qcolorgroup::background, white); cg.setcolor(qcolorgroup::shadow, black); cg.setcolor(qcolorgroup::highlight, black); cg.setcolor(qcolorgroup::highlightedtext, white); pal.setdisabled(cg); setpalette(pal); QFont f(font()); f.setfamily("adobe-helvetica"); f.setpointsize(29); f.setbold(true); setfont(f); setcaption(tr("")); 专 业 始 于 专 注 卓 识 源 于 远 见 /* 以 下 是 手 动 添 加 的 代 码 */ t = "Hello,World"; b = 0; QTimer *timer = new QTimer(this); 21
385 connect(timer, SIGNAL(timeout()), SLOT(animate())); timer->start(40); /* * Destroys the object and frees any allocated resources */ Hello::~Hello() /* 以 下 至 结 尾 是 手 动 添 加 的 代 码 */ void Hello::animate() b = (b + 1) & 15; repaint(false); /* Handles mouse button release events for the Hello widget. We emit the clicked() signal when the mouse is released inside the widget. */ void Hello::mouseReleaseEvent(QMouseEvent *e) if (rect().contains(e->pos())) emit clicked(); /* Handles paint events for the Hello widget. Flicker-free update. The text is first drawn in the pixmap and the pixmap is then blt'ed to the screen. */ void Hello::paintEvent(QPaintEvent *) static int sin_tbl[16] = 0, 38, 71, 92, 100, 92, 71, 38, 0, -38, -71, -92, -100, -92, -71, -38; if (t.isempty()) eturn; /* 1: Compute some sizes, positions etc. */ QFontMetrics fm = fontmetrics(); int w = fm.width(t) + 20; int h = fm.height() * 2; int pmx = width()/2 - w/2; int pmy = height()/2 - h/2; /* 2: Create the pixmap and fill it with the widget's background */ QPixmap pm(w, h); pm.fill(this, pmx, pmy); /* 3: Paint the pixmap. Cool wave effect */ QPainter p; int x = 10; 22
386 int y = h/2 + fm.descent(); int i = 0; p.begin(&pm); p.setfont(font()); while (!t[i].isnull()) nt i16 = (b+i) & 15;.setPen(QColor((15-i16)*16,255,255,QColor::Hsv)); wtext(x, y-sin_tbl[i16]*h/800, t.mid(i,1), 1); += fm.width(t[i]); +; p.end(); /* 4: Copy the pixmap to the Hello widget */ bitblt(this, pmx, pmy, &pm); (4) 编 写 主 函 数 main() 一 个 Qt/Embeded 应 用 程 序 应 该 包 含 一 个 主 函 数, 主 函 数 所 在 的 文 件 名 是 main.cpp 主 函 数 是 应 用 程 序 执 行 的 入 口 点 以 下 是 Hello,World 例 子 的 主 函 数 文 件 main.cpp 的 实 现 代 码 : /**************************************************************************** ** 以 下 是 main.cpp 源 代 码 ****************************************************************************/ #include "hello.h" #include <qapplication.h> /* The program starts here. It parses the command line and builds a message string to be displayed by the Hello widget. */ #define QT_NO_WIZARD int main(int argc, char **argv) QApplication a(argc,argv); Hello dlg; QObject::connect(&dlg, SIGNAL(clicked()), &a, SLOT(quit())); a.setmainwidget(&dlg); dlg.show(); return a.exec(); (5) 编 辑 工 程 文 件 hello.pro 文 件 到 目 前 为 止, 为 Hello,World 例 子 编 写 了 一 个 头 文 件 和 两 个 源 文 件, 这 3 个 文 件 应 该 被 包 括 在 工 程 文 件 中, 因 此 还 需 要 编 辑 hello.pro 文 件, 加 入 hello.h hello.cpp main.cpp 这 3 个 文 件 名 具 体 定 义 如 下 : /**************************************************************************** ** 以 下 是 hello.pro 文 件 的 内 容 ****************************************************************************/ TEMPLATE = app CONFIG = qt warn_on release 23
387 HEADERS = hello.h SOURCES = hello.cpp \ main.cpp INTERFACES = (6) 生 成 Makefile 文 件 编 译 器 是 根 据 Makefile 文 件 内 容 来 进 行 编 译 的, 所 以 需 要 生 成 Makefile 文 件 Qt 提 供 的 tmake 工 具 可 以 帮 助 我 们 从 一 个 工 程 文 件 (.pro 文 件 ) 中 产 生 Makefile 文 件 结 合 当 前 例 子, 要 从 hello.pro 生 成 一 个 Makefile 文 件 的 做 法 是 首 先 查 看 环 境 变 量 $TMAKEPATH 是 否 指 向 ARM 编 译 器 的 配 置 目 录, 在 命 令 行 下 输 入 以 下 命 令 : ECHO $TMAKEPATH 如 果 返 回 的 结 果 末 尾 不 是 /qws/linux-arm-g++ 的 字 符 串, 那 么 需 要 把 环 境 变 量 $TMAKEPATH 所 指 的 目 录 设 置 为 指 向 arm 编 译 器 的 配 置 目 录, 过 程 如 下 : EXPORT TMAKEPATH = /TMAKE 安 装 路 径 /QWS/LINUX-ARM-G++ 同 时, 应 确 保 当 前 的 QTDIR 环 境 变 量 指 向 Qt/Embedded 的 安 装 路 径, 如 果 不 是, 则 需 要 执 行 以 下 过 程 EXPORT QTDIR = /qt 上 述 步 骤 完 成 后, 就 可 以 使 用 tmake 生 成 Makefile 文 件, 具 体 做 法 是 在 命 令 行 输 入 以 下 命 令 : TMAKE O MAKEFILE HELLO.PRO 这 样 就 可 以 看 到 当 前 目 录 下 新 生 成 了 一 个 名 为 Makefile 的 文 件 下 一 步, 需 要 打 开 这 个 文 件, 做 一 些 小 的 修 改 1 将 LINK = arm-linux-gcc 改 为 :LINK = arm-linux-g++ 这 样 做 是 因 为 要 用 arm-linux-g++ 进 行 链 接 2 将 LIBS = $(SUBLIBS) -L$(QTDIR)/lib -lm lqte 改 为 : LIBS = $(SUBLIBS) -L/usr/local/arm/2.95.3/lib -L$(QTDIR)/lib -lm lqte 这 是 因 为 链 接 时 要 用 到 交 叉 编 译 工 具 toolchain 的 库 (7) 编 译 链 接 整 个 工 程 最 后 就 可 以 在 命 令 行 下 输 入 make 命 令 对 整 个 工 程 进 行 编 译 链 接 了 make 生 成 的 二 进 制 文 件 hello 就 是 可 以 在 FS2410 上 运 行 的 可 执 行 文 件 12.4 本 章 小 结 本 章 主 要 讲 解 了 嵌 入 式 Linux 的 图 形 编 程 首 先 介 绍 了 几 种 常 见 的 嵌 入 式 图 形 界 面 编 程 机 制, 并 给 出 了 它 们 之 间 的 关 系 接 下 来, 本 章 介 绍 了 Qt/Embedded 开 发 入 门, 包 括 环 境 的 搭 建 信 号 与 插 槽 的 概 念 与 应 用 以 及 图 形 设 计 器 的 应 用 本 章 的 实 验 介 绍 了 如 何 使 用 Qt 编 写 Hello,world 小 程 序, 从 中 可 以 了 解 到 Qt 编 程 的 全 过 程 联 系 方 式 集 团 官 网 : 嵌 入 式 学 院 : 移 动 互 联 网 学 院 : 企 业 学 院 : 物 联 网 学 院 : 研 发 中 心 :dev.hqyj.com 集 团 总 部 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 内 华 清 远 见 教 育 集 团 北 京 地 址 : 北 京 市 海 淀 区 西 三 旗 悦 秀 路 北 京 明 园 大 学 校 区, 电 话 : /5 24
388 上 海 地 址 : 上 海 市 徐 汇 区 漕 溪 路 银 海 大 厦 A 座 8 层, 电 话 : 深 圳 地 址 : 深 圳 市 龙 华 新 区 人 民 北 路 美 丽 AAA 大 厦 15 层, 电 话 : 成 都 地 址 : 成 都 市 武 侯 区 科 华 北 路 99 号 科 华 大 厦 6 层, 电 话 : 南 京 地 址 : 南 京 市 白 下 区 汉 中 路 185 号 鸿 运 大 厦 10 层, 电 话 : 武 汉 地 址 : 武 汉 市 工 程 大 学 卓 刀 泉 校 区 科 技 孵 化 器 大 楼 8 层, 电 话 : 西 安 地 址 : 西 安 市 高 新 区 高 新 一 路 12 号 创 业 大 厦 D3 楼 5 层, 电 话 :
说 明 为 了 反 映 教 运 行 的 基 本 状 态, 为 校 和 院 制 定 相 关 政 策 和 进 行 教 建 设 与 改 革 提 供 据 依 据, 校 从 程 资 源 ( 开 类 别 开 量 规 模 ) 教 师 结 构 程 考 核 等 维 度, 对 2015 年 春 季 期 教 运 行 基
内 部 资 料 东 北 师 范 大 教 运 行 基 本 状 态 据 报 告 2015 年 春 季 期 教 务 处 2015 年 10 月 27 日 说 明 为 了 反 映 教 运 行 的 基 本 状 态, 为 校 和 院 制 定 相 关 政 策 和 进 行 教 建 设 与 改 革 提 供 据 依 据, 校 从 程 资 源 ( 开 类 别 开 量 规 模 ) 教 师 结 构 程 考 核 等 维 度,
修改版-操作手册.doc
职 称 信 息 系 统 升 级 指 南 须 使 用 IE9 及 其 以 上 版 本 浏 览 器 或 谷 歌 浏 览 器 登 录 www.njrs.gov.cn 南 京 市 职 称 ( 职 业 资 格 ) 工 作 领 导 小 组 办 公 室 2016 年 5 月 目 录 一 申 报 人 员 操 作 指 南...1 1.1 职 称 初 定 申 报...1 1.1.1 职 称 初 定 基 础 信 息 填
<433A5C446F63756D656E747320616E642053657474696E67735C41646D696E6973747261746F725CD7C0C3E65CC2DBCEC4CFB5CDB3CAB9D3C3D6B8C4CFA3A8BCF2BBAFA3A95CCAB9D3C3D6B8C4CF31302D31392E646F63>
( 一 ) 系 统 整 体 操 作 流 程 简 述 3 ( 二 ) 系 统 中 各 角 色 操 作 功 能 说 明 5 1. 学 院 管 理 员 5 2. 教 学 院 长 8 3. 指 导 教 师 10 4. 答 辩 组 组 长 12 5. 学 生 12 6. 系 统 管 理 员 15 ( 一 ) 论 文 系 统 常 见 问 题 16 ( 二 ) 论 文 查 重 常 见 问 题 22 1 2 主
何 秋 琳 张 立 春 视 觉 学 习 研 究 进 展 视 觉 注 意 视 觉 感 知
第 卷 第 期 年 月 开 放 教 育 研 究 何 秋 琳 张 立 春 华 南 师 范 大 学 未 来 教 育 研 究 中 心 广 东 广 州 随 着 图 像 化 技 术 和 电 子 媒 体 的 发 展 视 觉 学 习 也 逐 步 发 展 为 学 习 科 学 的 一 个 研 究 分 支 得 到 研 究 人 员 和 教 育 工 作 者 的 广 泛 关 注 基 于 此 作 者 试 图 对 视 觉 学 习
I
机 电 一 级 注 册 建 造 师 继 续 教 育 培 训 广 东 培 训 点 网 上 报 名 操 作 使 用 手 册 (2013 年 1 月, 第 一 版 ) 第 一 章 个 人 注 册 与 个 人 信 息 管 理 1. 个 人 注 册 ( 请 每 人 只 申 请 一 个 注 册 号, 如 果 单 位 批 量 报 班 单 位 帮 申 请 注 册, 不 需 个 人 再 注 册 ) 首 次 报 班,
《C语言基础入门》课程教学大纲
C 语 言 开 发 入 门 教 程 课 程 教 学 大 纲 课 程 编 号 :201409210011 学 分 :5 学 分 学 时 :58 学 时 ( 其 中 : 讲 课 学 时 :39 学 时 上 机 学 时 :19 学 时 ) 先 修 课 程 : 计 算 机 导 论 后 续 课 程 :C++ 程 序 设 计 适 用 专 业 : 信 息 及 其 计 算 机 相 关 专 业 开 课 部 门 : 计
评 委 : 李 炎 斌 - 个 人 技 术 标 资 信 标 初 步 审 查 明 细 表 序 号 投 标 单 位 投 标 函 未 按 招 标 文 件 规 定 填 写 漏 填 或 内 容 填 写 错 误 的 ; 不 同 投 标 人 的 投 标 文 件 由 同 一 台 电 脑 或 同 一 家 投 标 单
评 委 : 李 炎 斌 - 个 人 清 标 评 审 明 细 表 评 审 因 素 序 号 投 标 单 位 清 标 评 审 1 深 圳 市 创 捷 科 技 有 限 合 格 2 四 川 川 大 智 胜 软 件 股 份 有 限 合 格 3 北 京 航 天 长 峰 科 技 工 业 集 团 有 限 公 司 合 格 4 深 圳 中 兴 力 维 技 术 有 限 合 格 5 深 圳 键 桥 通 讯 技 术 股 份 有
深圳市新亚电子制程股份有限公司
证 券 代 码 :002388 证 券 简 称 : 新 亚 制 程 公 告 编 号 :2016-053 深 圳 市 新 亚 电 子 制 程 股 份 有 限 公 司 2016 年 第 二 次 临 时 股 东 大 会 决 议 公 告 本 公 司 及 董 事 会 全 体 成 员 保 证 公 告 内 容 真 实 准 确 和 完 整, 不 存 在 虚 假 记 载 误 导 性 陈 述 或 者 重 大 遗 漏 特
,,,,, :,, (.,, );, (, : ), (.., ;. &., ;.. &.., ;, ;, ),,,,,,, ( ) ( ),,,,.,,,,,, : ;, ;,.,,,,, (., : - ),,,, ( ),,,, (, : ),, :,
: 周 晓 虹 : - -., - - - -. :( ), -,.( ),,, -. - ( ).( ) ', -,,,,, ( ).( ),,, -., '.,, :,,,, :,,,, ,,,,, :,, (.,, );, (, : ), (.., ;. &., ;.. &.., ;, ;, ),,,,,,, ( ) ( ),,,,.,,,,,, : ;, ;,.,,,,, (., : - ),,,,
18 上 报 该 学 期 新 生 数 据 至 阳 光 平 台 第 一 学 期 第 四 周 至 第 六 周 19 督 促 学 习 中 心 提 交 新 增 专 业 申 请 第 一 学 期 第 四 周 至 第 八 周 20 编 制 全 国 网 络 统 考 十 二 月 批 次 考 前 模 拟 题 第 一 学
1 安 排 组 织 全 国 网 络 统 考 九 月 批 次 网 上 考 前 辅 导 第 一 学 期 第 一 周 统 考 考 前 半 个 月 2 下 发 全 国 网 络 统 考 九 月 批 次 准 考 证 第 一 学 期 第 一 周 导 出 下 半 年 成 人 本 科 学 士 学 位 英 语 统 一 考 试 报 考 3 信 息 第 一 学 期 第 一 周 4 教 学 计 划 和 考 试 计 划 上 网,
一 公 共 卫 生 硕 士 专 业 学 位 论 文 的 概 述 学 位 论 文 是 对 研 究 生 进 行 科 学 研 究 或 承 担 专 门 技 术 工 作 的 全 面 训 练, 是 培 养 研 究 生 创 新 能 力, 综 合 运 用 所 学 知 识 发 现 问 题, 分 析 问 题 和 解 决
上 海 市 公 共 卫 生 硕 士 专 业 学 位 论 文 基 本 要 求 和 评 价 指 标 体 系 ( 试 行 ) 上 海 市 学 位 委 员 会 办 公 室 二 O 一 二 年 三 月 一 公 共 卫 生 硕 士 专 业 学 位 论 文 的 概 述 学 位 论 文 是 对 研 究 生 进 行 科 学 研 究 或 承 担 专 门 技 术 工 作 的 全 面 训 练, 是 培 养 研 究 生 创
文 化 记 忆 传 统 创 新 与 节 日 遗 产 保 护 根 据 德 国 学 者 阿 斯 曼 的 文 化 记 忆 理 论 仪 式 与 文 本 是 承 载 文 化 记 忆 的 两 大 媒 体 在 各 种 仪 式 行 为 中 节 日 以 其 高 度 的 公 共 性 有 组 织 性 和 历 史 性 而 特 别 适 用 于 文 化 记 忆 的 储 存 和 交 流 节 日 的 文 化 功 能 不 仅 在 于
2006年顺德区高中阶段学校招生录取分数线
2014 年 顺 德 区 高 中 阶 段 学 校 考 试 提 前 批 第 一 批 第 二 批 学 校 录 取 根 据 佛 山 市 办 提 供 的 考 生 数 据, 现 将 我 区 2014 年 高 中 阶 段 学 校 考 试 提 前 批 第 一 批 第 二 批 学 校 的 录 取 公 布 如 下 : 一 顺 德 一 中 录 取 分 第 1 志 愿, 总 分 585, 综 合 表 现 评 价 A, 考
评 委 : 徐 岩 宇 - 个 人 技 术 标 资 信 标 初 步 审 查 明 细 表 序 号 投 标 单 位 投 标 函 未 按 招 标 文 件 规 定 填 写 漏 填 或 内 容 填 写 错 误 的 ; 不 同 投 标 人 的 投 标 文 件 由 同 一 台 电 脑 或 同 一 家 投 标 单
评 委 : 徐 岩 宇 - 个 人 清 标 评 审 明 细 表 评 审 因 素 序 号 投 标 单 位 清 标 评 审 1 深 圳 市 创 捷 科 技 有 限 合 格 2 四 川 川 大 智 胜 软 件 股 份 有 限 合 格 3 北 京 航 天 长 峰 科 技 工 业 集 团 有 限 公 司 合 格 4 深 圳 中 兴 力 维 技 术 有 限 合 格 5 深 圳 键 桥 通 讯 技 术 股 份 有
张 荣 芳 中 山 大 学 历 史 系 广 东 广 州 张 荣 芳 男 广 东 廉 江 人 中 山 大 学 历 史 系 教 授 博 士 生 导 师 我 们 要 打 破 以 前 学 术 界 上 的 一 切 偶 像 以 前 学 术 界 的 一 切 成 见 屏 除 我 们 要 实 地 搜 罗 材 料 到 民 众 中 寻 方 言 到 古 文 化 的 遗 址 去 发 掘 到 各 种 的 人 间 社 会 去
0 年 上 半 年 评 价 与 考 核 细 则 序 号 部 门 要 素 值 考 核 内 容 考 核 方 式 考 核 标 准 考 核 ( 扣 原 因 ) 考 评 得 3 安 全 生 产 目 30 无 同 等 责 任 以 上 道 路 交 通 亡 人 事 故 无 轻 伤 责 任 事 故 无 重 大 质 量
0 年 上 半 年 评 价 与 考 核 细 则 序 号 部 门 要 素 值 考 核 内 容 考 核 方 式 考 核 标 准 无 同 等 责 任 以 上 道 路 交 通 亡 人 事 故 3 无 轻 伤 责 任 事 故 目 标 30 及 事 无 重 大 质 量 工 作 过 失 故 管 无 其 他 一 般 责 任 事 故 理 在 公 司 文 明 环 境 创 建 中, 无 工 作 过 失 及 被 追 究 的
HSK( 一 级 ) 考 查 考 生 的 日 常 汉 语 应 用 能 力, 它 对 应 于 国 际 汉 语 能 力 标 准 一 级 欧 洲 语 言 共 同 参 考 框 架 (CEF) A1 级 通 过 HSK( 一 级 ) 的 考 生 可 以 理 解 并 使 用 一 些 非 常 简 单 的 汉 语
新 汉 语 水 平 考 试 HSK 为 使 汉 语 水 平 考 试 (HSK) 更 好 地 服 务 于 汉 语 学 习 者, 中 国 国 家 汉 办 组 织 中 外 汉 语 教 学 语 言 学 心 理 学 和 教 育 测 量 学 等 领 域 的 专 家, 在 充 分 调 查 了 解 海 外 实 际 汉 语 教 学 情 况 的 基 础 上, 吸 收 原 有 HSK 的 优 点, 借 鉴 近 年 来 国
名 称 生 命 科 学 学 院 083001 环 境 科 学 1 生 物 学 仅 接 收 院 内 调 剂, 初 试 分 数 满 足 我 院 生 物 学 复 试 最 低 分 数 线 生 命 科 学 学 院 071300 生 态 学 5 生 态 学 或 生 物 学 生 命 科 学 学 院 040102
华 中 师 范 大 学 2016 年 接 收 校 内 外 优 秀 硕 士 研 究 生 调 剂 信 息 表 名 称 经 济 与 工 商 管 理 学 院 020101 政 治 经 济 学 1 经 济 学 类 毕 业 学 校 与 报 考 学 校 不 低 于 我 校 办 学 层 次 经 济 与 工 商 管 理 学 院 020105 世 界 经 济 学 1 经 济 学 类 毕 业 学 校 与 报 考 学 校
目 录 关 于 图 标... 3 登 陆 主 界 面... 3 工 单 管 理... 5 工 单 列 表... 5 搜 索 工 单... 5 工 单 详 情... 6 创 建 工 单... 9 设 备 管 理 巡 检 计 划 查 询 详 情 销 售 管
宝 汇 德 Turbocare 微 服 务 系 统 客 户 操 作 手 册 Version 2.0 北 京 宝 汇 德 技 术 服 务 器 有 限 公 司 技 术 研 发 部 目 录 关 于 图 标... 3 登 陆 主 界 面... 3 工 单 管 理... 5 工 单 列 表... 5 搜 索 工 单... 5 工 单 详 情... 6 创 建 工 单... 9 设 备 管 理... 10 巡
龚 亚 夫 在 重 新 思 考 基 础 教 育 英 语 教 学 的 理 念 一 文 中 援 引 的 观 点 认 为 当 跳 出 本 族 语 主 义 的 思 维 定 式 后 需 要 重 新 思 考 许 多 相 连 带 的 问 题 比 如 许 多 发 音 的 细 微 区 别 并 不 影 响 理 解 和
语 音 语 篇 语 感 语 域 林 大 津 毛 浩 然 改 革 开 放 以 来 的 英 语 热 引 发 了 大 中 小 学 英 语 教 育 整 体 规 划 问 题 在 充 分 考 虑 地 区 学 校 和 个 体 差 异 以 及 各 家 观 点 的 基 础 上 遵 循 实 事 求 是 逐 级 定 位 逐 层 分 流 因 材 施 教 的 原 则 本 研 究 所 倡 导 的 语 音 语 篇 语 感 语 域
<4D6963726F736F667420576F7264202D20B9D8D3DAB0BABBAAA3A8C9CFBAA3A3A9D7D4B6AFBBAFB9A4B3CCB9C9B7DDD3D0CFDEB9ABCBBE32303132C4EAC4EAB6C8B9C9B6ABB4F3BBE1B7A8C2C9D2E2BCFBCAE92E646F6378>
上 海 德 载 中 怡 律 师 事 务 所 关 于 昂 华 ( 上 海 ) 自 动 化 工 程 股 份 有 限 公 司 二 〇 一 二 年 年 度 股 东 大 会 法 律 意 见 书 上 海 德 载 中 怡 律 师 事 务 所 上 海 市 银 城 中 路 168 号 上 海 银 行 大 厦 1705 室 (200120) 电 话 :8621-5012 2258 传 真 :8621-5012 2257
Microsoft Word - 第7章 图表反转形态.doc
第 七 章 图 表 反 转 形 态 我 们 知 道 市 场 趋 势 共 有 三 种 : 上 升 趋 势 下 降 趋 势 和 横 向 整 理 市 场 的 价 格 波 动 都 是 运 行 在 这 三 种 趋 势 中, 所 有 的 走 势 都 是 这 三 种 趋 势 的 排 列 组 合 如 图 市 场 趋 势 结 构 示 意 图 7-1 所 示 市 场 趋 势 结 构 示 意 图 7-1 图 市 场 趋
Microsoft Word - 文件汇编.doc
北 京 市 中 医 管 理 局 二 一 五 年 四 月 ... 1... 18 2015... 30 京 中 医 政 字 [2014]160 号 1 2 一 充 分 认 识 中 医 健 康 乡 村 建 设 工 作 的 重 要 意 义 二 建 立 健 全 工 作 保 障 机 制 2014 12 15 三 做 好 工 作 启 动 的 准 备 事 宜 1 2014 12 15 5-10 2014 12 15
课程类 别
美 声 演 唱 方 向 培 养 方 案 一 培 养 目 标 本 方 向 要 求 学 生 德 智 体 美 全 面 发 展, 培 养 能 在 文 艺 团 体 从 事 声 乐 演 唱 及 能 在 艺 术 院 校 从 事 本 方 向 教 学 的 高 级 门 人 才 二 培 养 规 格 本 方 向 学 生 应 系 统 掌 握 声 乐 演 唱 方 面 的 理 论 和 技 能, 具 备 较 高 的 声 乐 演 唱
(2015-2016-2)-0004186-04205-1 140242 信 号 与 系 统 Ⅰ 学 科 基 础 必 修 课 37 37 1 教 203 17 周 2016 年 06 月 13 日 (08:00-09:35) (2015-2016-2)-0004186-04205-1 141011
关 于 2015-2016 学 年 第 二 学 期 期 末 周 内 考 试 时 间 地 点 安 排 选 课 课 号 班 级 名 称 课 程 名 称 课 程 性 质 合 考 人 数 实 际 人 数 考 试 教 室 考 试 段 考 试 时 间 (2015-2016-2)-0006178-04247-1 130101 测 试 技 术 基 础 学 科 基 础 必 修 课 35 35 1 教 401 17 周
科 学 出 版 社 科 学 出 版 社 前 言 本 书 是 针 对 普 通 高 等 院 校 经 济 类 和 工 商 管 理 类 本 科 专 业 财 务 管 理 学 的 教 学 需 求, 结 合 教 育 部 经 济 管 理 类 本 科 财 务 管 理 学 课 程 教 学 大 纲 编 写 而 成 的 本 书 执 笔 者 都 是 长 期 工 作 在 财 务 管 理 教 学 一 线 的 专 业 教 师,
伊 犁 师 范 学 院 611 语 言 学 概 论 全 套 考 研 资 料 <2016 年 最 新 考 研 资 料 > 2-2 语 言 学 纲 要 笔 记, 由 考 取 本 校 本 专 业 高 分 研 究 生 总 结 而 来, 重 点 突 出, 借 助 此 笔 记 可 以 大 大 提 高 复 习 效
伊 犁 师 范 学 院 611 语 言 学 概 论 全 套 考 研 资 料 ......2 伊 犁 师 范 学 院 802 文 学 概 论 全 套 考 研 资 料 ......2 伊 犁 师 范 学 院 702 普 通 物 理 全 套 考 研 资 料 ......3 伊 犁
一 从 分 封 制 到 郡 县 制 一 从 打 虎 亭 汉 墓 说 起
县 乡 两 级 的 政 治 体 制 改 革 如 何 建 立 民 主 的 合 作 新 体 制 县 乡 人 大 运 行 机 制 研 究 课 题 组 引 言 一 从 分 封 制 到 郡 县 制 一 从 打 虎 亭 汉 墓 说 起 二 密 县 在 周 初 是 两 个 小 国 密 国 和 郐 国 三 密 县 的 第 一 任 县 令 卓 茂 四 明 清 时 代 的 密 县 二 从 集 中 的 动 员 体
( 二 ) 现 行 统 一 高 考 制 度 不 利 于 培 养 人 的 创 新 精 神,,,,,,,,,,,,, [ ],,,,,,,,,,, :, ;,,,,,,? ( 三 ) 现 行 统 一 高 考 制 度 不 利 于 全 体 学 生 都 获 得 全 面 发 展,, [ ],,,,,,,,,,,
( ) ( )... 李 雪 岩, 龙 耀 (. 广 西 民 族 大 学 商 学 院, 广 西 南 宁 ;. 中 山 大 学 教 育 学 院, 广 东 广 州 ) : 高 等 教 育 是 专 业 教 育 高 考 是 为 高 等 教 育 服 务 的, 是 为 高 等 专 业 教 育 选 拔 有 专 业 培 养 潜 质 的 人 才 现 行 高 考 制 度 忽 略 专 业 潜 质 的 因 素, 过 份 强
<4D F736F F D D323630D6D0B9FAD3A6B6D4C6F8BAF2B1E4BBAFB5C4D5FEB2DFD3EBD0D0B6AF C4EAB6C8B1A8B8E6>
中 国 应 对 气 候 变 化 的 政 策 与 行 动 2013 年 度 报 告 国 家 发 展 和 改 革 委 员 会 二 〇 一 三 年 十 一 月 100% 再 生 纸 资 源 目 录 前 言... 1 一 应 对 气 候 变 化 面 临 的 形 势... 3 二 完 善 顶 层 设 计 和 体 制 机 制... 4 三 减 缓 气 候 变 化... 8 四 适 应 气 候 变 化... 20
ETF、分级基金规模、份额变化统计20130816
ETF 分 级 基 金 规 模 份 额 变 化 统 计 截 至 上 周 末, 全 市 场 股 票 型 ETF 规 模 约 1451 亿, 份 额 约 1215 亿,ETF 总 份 额 及 规 模 的 周 变 动 值 分 别 为 -23-44 亿, 份 额 与 规 模 均 下 降 ; 分 级 基 金 规 模 约 438 亿, 份 额 572 亿, 总 份 额 及 规 模 的 周 变 动 值 分 别 为
目 录 一 系 统 访 问... 1 二 门 户 首 页 申 报 用 户 审 核 用 户... 2 三 系 统 登 录 用 户 名 密 码 登 录 新 用 户 注 册 用 户 登 录 已 注 册 用
水 路 运 输 建 设 综 合 管 理 信 息 系 统 - 门 户 系 统 用 户 手 册 二 零 一 五 年 十 一 月 目 录 一 系 统 访 问... 1 二 门 户 首 页... 1 1. 申 报 用 户... 1 2. 审 核 用 户... 2 三 系 统 登 录... 4 1. 用 户 名 密 码 登 录... 4 1.1 新 用 户 注 册... 4 1.2 用 户 登 录... 7
公 开 刊 物 须 有 国 内 统 一 刊 (CN), 发 表 文 章 的 刊 物 需 要 在 国 家 新 闻 出 版 广 电 总 局 (www.gapp.gov.cn 办 事 服 务 便 民 查 询 新 闻 出 版 机 构 查 询 ) 上 能 够 查 到 刊 凡 在 有 中 国 标 准 书 公 开
杭 教 人 2014 7 杭 州 市 教 育 局 关 于 中 小 学 教 师 系 列 ( 含 实 验 教 育 管 理 ) 晋 升 高 级 专 业 技 术 资 格 有 关 论 文 要 求 的 通 知 各 区 县 ( 市 ) 教 育 局 ( 社 发 局 ), 直 属 学 校 ( 单 位 ), 委 托 单 位 : 为 进 一 步 规 范 杭 州 市 中 小 学 教 师 系 列 ( 含 实 验 教 育 管
3 月 30 日 在 中 国 证 券 报 上 海 证 券 报 证 券 时 报 证 券 日 报 和 上 海 证 券 交 易 所 网 站 上 发 出 召 开 本 次 股 东 大 会 公 告, 该 公 告 中 载 明 了 召 开 股 东 大 会 的 日 期 网 络 投 票 的 方 式 时 间 以 及 审
北 京 市 君 致 律 师 事 务 所 关 于 浪 潮 软 件 股 份 有 限 公 司 2015 年 度 股 东 大 会 的 法 律 意 见 书 致 : 浪 潮 软 件 股 份 有 限 公 司 北 京 市 君 致 律 师 事 务 所 ( 以 下 简 称 本 所 ) 受 浪 潮 软 件 股 份 有 限 公 司 ( 以 下 简 称 公 司 ) 的 委 托, 指 派 律 师 出 席 2016 年 4 月
随着执业中医师资格考试制度的不断完善,本着为我校中医学专业认证服务的目的,本文通过对我校中医类毕业生参加2012年和2013年的中医执业医师考试成绩及通过率、掌握率进行分析,并与全国的平均水平进行差异比较分析,以此了解我校执业中医师考试的现状,进而反映我校中医类课程总体教学水平,发现考核知识模块教学中存在的不足,反馈给相关学院和教学管理部门,以此提高教学和管理水平。
2012-2013 中 医 类 别 执 业 医 师 综 合 笔 试 成 绩 分 析 反 馈 报 告 教 务 处 二 零 一 三 年 三 月 1 目 录 1 前 言 3 2 2012-2013 中 医 类 别 执 业 医 师 综 合 笔 试 成 绩 分 析 反 馈 报 告 4 附 件 1:2012 年 中 医 类 别 医 师 资 格 综 合 笔 试 院 校 学 科 成 绩 分 析 报 告 附 件 2:2013
抗 战 时 期 国 民 政 府 的 银 行 监 理 体 制 探 析 % # % % % ) % % # # + #, ) +, % % % % % % % %
抗 战 时 期 国 民 政 府 的 银 行 监 理 体 制 探 析 王 红 曼 抗 战 时 期 国 民 政 府 为 适 应 战 时 经 济 金 融 的 需 要 实 行 由 财 政 部 四 联 总 处 中 央 银 行 等 多 家 机 构 先 后 共 同 参 与 的 多 元 化 银 行 监 理 体 制 对 战 时 状 态 下 的 银 行 发 展 与 经 营 安 全 进 行 了 大 规 模 的 设 计 与
中 国 软 科 学 年 第 期!!!
山 寨 模 式 的 形 成 机 理 及 其 对 组 织 创 新 的 启 示 山 寨 模 式 的 形 成 机 理 及 其 对 组 织 创 新 的 启 示 陶 厚 永 李 燕 萍 骆 振 心 武 汉 大 学 经 济 与 管 理 学 院 武 汉 大 学 中 国 产 学 研 合 作 问 题 研 究 中 心 湖 北 武 汉 北 京 大 学 经 济 研 究 所 光 华 天 成 博 士 后 工 作 站 北 京 本
全国建筑市场注册执业人员不良行为记录认定标准(试行).doc
- 1 - - 2 - 附 件 全 国 建 筑 市 场 注 册 执 业 人 员 不 良 记 录 认 定 标 准 ( 试 行 ) 说 明 为 了 完 善 建 筑 市 场 注 册 执 业 人 员 诚 信 体 系 建 设, 规 范 执 业 和 市 场 秩 序, 依 据 相 关 法 律 法 规 和 部 门 规 章, 根 据 各 行 业 特 点, 我 部 制 订 了 全 国 建 筑 市 场 注 册 执 业 人
2014年中央财经大学研究生招生录取工作简报
2015 年 中 央 财 经 大 学 研 究 生 招 生 录 取 工 作 简 报 一 硕 士 研 究 生 招 生 录 取 情 况 2015 年 共 有 8705 人 报 考 我 校 硕 士 研 究 生, 其 中 学 术 型 研 究 生 报 考 3657 人, 专 业 硕 士 研 究 生 报 考 5048 人 ; 总 报 考 人 数 较 2014 年 增 长 1.4%, 学 术 型 报 考 人 数 较
编号:
编 号 : 企 业 内 高 技 能 人 才 培 养 评 价 实 施 方 案 ( 仅 适 用 于 企 业 特 有 行 业 特 有 工 种 ) 实 施 单 位 ( 公 章 ) 申 报 日 期 年 _ 月 日 1 企 业 内 高 技 能 人 才 培 养 评 价 项 目 实 施 方 案 申 报 表 项 目 名 称 等 级 项 目 性 质 课 时 申 报 单 位 联 系 人 通 讯 地 址 电 话 手 机 电
教师上报成绩流程图
教 务 管 理 系 统 使 用 说 明 学 生 端 用 户 1 在 校 内 任 何 一 台 连 接 校 园 网 的 计 算 机 上 登 录 教 务 处 主 页 教 务 处 主 页 地 址 : http://jw.stdu.edu.cn/homepage 随 后 点 击 按 钮 ( 见 下 图 所 示 ), 即 可 进 入 综 合 教 务 管 理 系 统 2 在 综 合 教 务 管 理 区 域 内 键
西 南 大 学 硕 士 学 位 论 文 网 络 购 物 动 机 问 卷 的 编 制 及 实 测 姓 名 : 曹 建 英 申 请 学 位 级 别 : 硕 士 专 业 : 基 础 心 理 学 指 导 教 师 : 张 进 辅 20090401 网 络 购 物 动 机 问 卷 的
珠江钢琴股东大会
证 券 代 码 :002678 证 券 简 称 : 珠 江 钢 琴 公 告 编 号 :2015-038 广 州 珠 江 钢 琴 集 团 股 份 有 限 公 司 2015 年 年 度 股 东 大 会 决 议 公 告 本 公 司 及 董 事 会 全 体 成 员 保 证 信 息 披 露 的 内 容 真 实 准 确 完 整, 没 有 虚 假 记 载 误 导 性 陈 述 或 重 大 遗 漏 特 别 提 示 :
中 中 中 中 部 中 岗 位 条 件 历 其 它 历 史 师 地 理 师 生 物 师 体 与 健 康 师 04 05 06 07 从 事 中 历 史 工 从 事 中 地 理 工 从 事 中 生 物 工 从 事 中 体 与 健 康 工 2. 课 程 与 论 ( 历 史 ); 2. 科 ( 历 史 )
中 中 中 部 中 26 年 系 统 事 业 公 开 计 划 岗 位 条 件 历 其 它 数 师 英 语 师 物 理 师 02 0 从 事 中 数 工 从 事 中 英 语 工 从 事 中 物 理 工 2. 课 程 与 论 ( 数 ); 2. 科 ( 数 );. 数 ; 4. 基 础 数 ; 5. 计 算 数 ; 6. 概 率 论 与 数 理 统 计 ; 7. 应 用 数 ; 8. 数. 课 程 与
自 服 务 按 钮 无 法 访 问 新 系 统 的 自 服 务 页 面 因 此 建 议 用 户 从 信 网 中 心 (http://nc.tju.edu.cn) 主 页, 右 下 角 位 置 的 常 用 下 载, 或 校 园 网 用 户 自 服 务 (http://g.tju.edu.cn) 首 页
校 园 网 认 证 计 费 系 统 变 更 说 明 及 使 用 帮 助 为 提 高 校 园 网 的 可 靠 性 和 可 用 性, 提 升 用 户 的 上 网 体 验, 同 时 也 为 解 决 近 期 校 园 网 无 法 认 证 或 登 录 页 面 弹 出 缓 慢 的 问 题, 信 网 中 心 于 近 期 对 校 园 网 认 证 计 费 系 统 进 行 升 级 切 换 现 将 升 级 后 新 系 统
马 克 思 主 义 公 正 观 的 基 本 向 度 及 方 法 论 原 则!! # #
马 克 思 主 义 公 正 观 的 基 本 向 度 及 方 法 论 原 则 马 俊 峰 在 社 会 公 正 问 题 的 大 讨 论 中 罗 尔 斯 诺 齐 克 哈 耶 克 麦 金 泰 尔 等 当 代 西 方 思 想 家 的 论 述 被 反 复 引 用 和 申 说 而 将 马 克 思 恩 格 斯 等 经 典 作 家 的 观 点 置 于 一 种 被 忽 视 甚 至 被 忘 却 的 状 态 形 成 这 种
登录、注册功能的测试用例设计.doc
注 册 登 陆 测 试 用 例 和 修 改 密 码 测 试 用 例 完 整 版 摘 自 网 络, 狗 狗 整 理 [email protected] 修 改 历 史 日 期 版 本 作 者 修 改 内 容 评 审 号 变 更 控 制 号 2010-11-25 1.0 初 稿 2011-09-17 2.0 整 理 一 注 册 测 试 用 例 序 号 : 1 控 件 名 称 : 功 能 描 述 : 注 册 编
2015-2016 学 年 第 二 学 期 集 中 考 试 安 排 (18 周 ) 考 试 日 期 :6 月 27 日 星 期 一 8:10-9:50 第 二 公 共 教 学 楼 A 区 A303 10811046 高 等 数 学 ( 理 二 2) 复 材 1501-2 材 料 科 学 与 工 程
考 试 时 间 2015-2016 学 年 第 二 学 期 集 中 考 试 安 排 (18 周 ) 考 试 日 期 :6 月 27 日 星 期 一 考 场 所 在 教 学 楼 ( 教 学 区 ) 考 试 教 室 课 程 号 课 程 名 考 生 所 在 专 业 ( 班 级 ) 考 生 所 属 学 院 8:10-9:50 第 二 公 共 教 学 楼 A 区 A101 10811026 高 等 数 学 (
证券代码:000066 证券简称:长城电脑 公告编号:2014-000
证 券 代 码 :000066 证 券 简 称 : 长 城 电 脑 公 告 编 号 :2016-092 中 国 长 城 计 算 机 深 圳 股 份 有 限 公 司 2016 年 度 第 三 次 临 时 股 东 大 会 决 议 公 告 本 公 司 及 其 董 事 会 全 体 成 员 保 证 信 息 披 露 内 容 的 真 实 准 确 完 整, 没 有 虚 假 记 载 误 导 性 陈 述 或 重 大 遗
新, 各 地 各 部 门 ( 单 位 ) 各 文 化 事 业 单 位 要 高 度 重 视, 切 实 加 强 领 导, 精 心 组 织 实 施 要 根 据 事 业 单 位 岗 位 设 置 管 理 的 规 定 和 要 求, 在 深 入 调 查 研 究 广 泛 听 取 意 见 的 基 础 上, 研 究 提
广 西 壮 族 自 治 区 人 事 厅 广 西 壮 族 自 治 区 文 化 厅 文 件 桂 人 发 2009 42 号 关 于 印 发 广 西 壮 族 自 治 区 文 化 事 业 单 位 岗 位 设 置 结 构 比 例 指 导 标 准 的 通 知 各 市 人 事 局 文 化 局, 区 直 各 部 门 ( 单 位 ): 根 据 人 事 部 印 发 的 事 业 单 位 岗 位 设 置 管 理 试 行 办
untitled
( 一 ) 深 刻 认 识 学 习 教 育 的 重 大 意 义 : - 3 - ( 二 ) 明 确 学 习 教 育 的 任 务 目 标 ( 三 ) 把 握 特 点 方 法 - 4 - ( 四 ) 坚 持 六 项 原 则 在 - 5 - ( 五 ) 着 力 解 决 问 题 - 6 - - 7 - - 8 - ( 一 ) 学 党 章 党 规, 进 一 步 明 确 党 员 标 准 树 立 行 为 规 范
<433A5C55736572735C6B73625C4465736B746F705CB9FABCCAD6D0D2BDD2A9D7A8D2B5B8DFBCB6BCBCCAF5D6B0B3C6C6C0C9F3C9EAC7EBD6B8C4CFA3A832303136CDA8D3C3B0E6A3A92E646F63>
附 件 1 国 际 中 药 专 业 高 级 技 术 职 称 评 审 条 件 及 报 名 材 料 一 系 列 ( 一 ) 中 1 高 级 专 科 ( 副 ) 高 级 专 科 ( 副 ) 1 取 得 中 专 科 职 称 后, 独 立 从 事 中 临 床 实 践 5 年 以 上 2 取 得 中 博 士 学 位 后, 临 床 实 践 2 年 以 上 3 取 得 中 硕 士 学 位 后, 临 床 实 践 7
第2章 数据类型、常量与变量
第 2 章 数 据 类 型 常 量 与 变 量 在 计 算 机 程 序 中 都 是 通 过 值 (value) 来 进 行 运 算 的, 能 够 表 示 并 操 作 值 的 类 型 为 数 据 类 型 在 本 章 里 将 会 介 绍 JavaScript 中 的 常 量 (literal) 变 量 (variable) 和 数 据 类 型 (data type) 2.1 基 本 数 据 类 型 JavaScript
1600 1000 40 50 2030 2000 采 取 行 动 的 机 会 90% 开 拓 成 功 的 道 路 2
简 略 版 本 :2015 3 10 2016 2021 全 球 卫 生 部 门 病 毒 性 肝 炎 战 略 2016 2021 2015 3 12 2012 2010 2014 2015 2016 2021 140 55% 35% 5 15% 5 20% 2.4 1.3 1.5 1 1600 1000 40 50 2030 2000 采 取 行 动 的 机 会 90% 开 拓 成 功 的 道 路
作 为 生 产 者 式 文 本 的 女 性 主 义 通 俗 小 说 梅 丽 本 文 借 鉴 文 化 研 究 理 论 家 约 翰 费 斯 克 的 生 产 者 式 文 本 这 一 概 念 考 察 女 性 主 义 通 俗 小 说 的 文 本 特 征 写 作 策 略 和 微 观 政 治 意 义 女 性 主 义 通 俗 小 说 通 过 对 传 统 通 俗 小 说 的 挪 用 和 戏 仿 传 播 女 性 主 义
金 不 少 于 800 万 元, 净 资 产 不 少 于 960 万 元 ; (3) 近 五 年 独 立 承 担 过 单 项 合 同 额 不 少 于 1000 万 元 的 智 能 化 工 程 ( 设 计 或 施 工 或 设 计 施 工 一 体 ) 不 少 于 2 项 ; (4) 近 三 年 每 年
工 程 设 计 与 施 工 资 质 标 准 一 总 则 建 筑 智 能 化 工 程 设 计 与 施 工 资 质 标 准 ( 一 ) 为 了 加 强 对 从 事 建 筑 智 能 化 工 程 设 计 与 施 工 企 业 的 管 理, 维 护 建 筑 市 场 秩 序, 保 证 工 程 质 量 和 安 全, 促 进 行 业 健 康 发 展, 结 合 建 筑 智 能 化 工 程 的 特 点, 制 定 本 标
世华财讯模拟操作手册
第 一 部 分 : 股 票 模 拟 操 作 部 分 1. 登 录 与 主 界 面 1.1 登 录 学 生 在 桌 面 上, 打 开 世 华 文 件 夹, 直 接 双 击 文 件 夹 中 的 快 捷 图 标, 系 统 弹 出 世 华 财 讯 模 拟 股 票 交 易 系 统 ( 客 户 端 ) 窗 口, 如 图 1.1 所 示 图 1.1 请 输 入 登 录 名 称 及 密 码, 单 击 确 认 登 录
3 复 试 如 何 准 备 4 复 试 成 绩 计 算 5 复 试 比 例 6 复 试 类 型 7 怎 么 样 面 对 各 种 复 试 04 05
1 复 试 流 程 2 复 试 考 查 形 式 02 03 3 复 试 如 何 准 备 4 复 试 成 绩 计 算 5 复 试 比 例 6 复 试 类 型 7 怎 么 样 面 对 各 种 复 试 04 05 2 怎 样 给 导 师 留 下 良 好 的 第 一 印 象 把 握 进 门 时 机 1 面 试 中 穿 着 的 瞒 天 过 海 3 无 声 胜 有 声 的 肢 体 语 言 育 4 眼 睛 是 心
抗 日 战 争 研 究! 年 第 期 # # # # #!!!!!!!! #!!
洪 小 夏 中 美 合 作 所 是 抗 战 时 期 中 美 两 国 在 反 法 西 斯 统 一 战 线 背 景 下 建 立 的 一 个 抗 日 军 事 合 作 机 构 但 过 去 由 文 学 影 视 作 品 给 人 造 成 的 印 象 似 乎 是 一 个 美 蒋 反 动 派 勾 结 的 集 中 营 中 共 十 一 届 三 中 全 会 以 后 逐 渐 有 人 为 其 正 名 但 长 期 宣 传 形 成
正 规 培 训 达 规 定 标 准 学 时 数, 并 取 得 结 业 证 书 二 级 可 编 程 师 ( 具 备 以 下 条 件 之 一 者 ) (1) 连 续 从 事 本 职 业 工 作 13 年 以 上 (2) 取 得 本 职 业 三 级 职 业 资 格 证 书 后, 连 续 从 事 本 职 业
1. 职 业 概 况 1.1 职 业 名 称 可 编 程 师 1.2 职 业 定 义 可 编 程 师 国 家 职 业 标 准 从 事 可 编 程 序 控 制 器 (PLC) 选 型 编 程, 并 对 应 用 进 行 集 成 和 运 行 管 理 的 人 员 1.3 职 业 等 级 本 职 业 共 设 四 个 等 级, 分 别 为 : 四 级 可 编 程 师 ( 国 家 职 业 资 格 四 级 ) 三
Microsoft Word - 资料分析练习题09.doc
行 测 高 分 冲 刺 练 习 题 资 料 分 析 ( 共 15 题, 参 考 时 限 10 分 钟 ) 材 料 题 - 1 2012 年 1 月 某 小 区 成 交 的 二 手 房 中, 面 积 为 60 平 方 米 左 右 的 住 宅 占 总 销 售 套 数 的 ( ) A.25% B.35% C.37.5% 长 沙 市 雨 花 区 侯 家 塘 佳 天 国 际 大 厦 北 栋 20 楼 第 1
收 入 支 出 项 目 2016 年 预 算 项 目 2016 年 预 算 预 算 01 表 单 位 : 万 元 ( 保 留 两 位 小 数 ) 一 公 共 财 政 预 算 拨 款 50.06 一 人 员 经 费 23.59 1 一 般 财 力 50.06 1 人 员 支 出 21.95 2 成 品
100.12 2016 年 龙 岩 市 部 门 预 算 表 报 送 日 期 : 年 月 日 单 位 负 责 人 签 章 : 财 务 负 责 人 签 章 : 制 表 人 签 章 : 收 入 支 出 项 目 2016 年 预 算 项 目 2016 年 预 算 预 算 01 表 单 位 : 万 元 ( 保 留 两 位 小 数 ) 一 公 共 财 政 预 算 拨 款 50.06 一 人 员 经 费 23.59
云信Linux SSH认证代理用户手册
Windows 主 机 登 录 保 护 (RDP) 管 理 员 配 置 手 册 V1.0 云 信 事 业 部 飞 天 诚 信 科 技 股 份 有 限 公 司 www.cloudentify.com 章 节 目 录 第 1 章 管 理 平 台 配 置 说 明... 1 1.1 注 册... 1 1.2 登 录... 3 1.3 添 加 应 用... 4 1.4 添 加 用 户... 7 1.5 激 活
上证指数
上 证 与 修 正 方 法 一 ( 一 ) 计 算 公 式 1. 上 证 指 数 系 列 均 采 用 派 许 加 权 综 合 价 格 指 数 公 式 计 算 2. 上 证 180 指 数 上 证 50 指 数 等 以 成 份 股 的 调 整 股 本 数 为 权 数 进 行 加 权 计 算, 计 算 公 式 为 : 报 告 期 指 数 =( 报 告 期 样 本 股 的 调 整 市 值 / 基 期 )
Microsoft Word - 第3章.doc
52 5 天 通 过 职 称 计 算 机 考 试 ( 考 点 视 频 串 讲 + 全 真 模 拟 ) Word 2003 中 文 字 处 理 ( 第 2 版 ) 第 3 章 3 字 符 格 式 需 要 掌 握 的 考 点 字 体 字 形 和 字 号 的 设 置 ; 上 标 下 标 空 心 字 等 字 体 效 果 的 使 用 ; 字 符 间 距 的 调 整 ; 改 变 字 符 颜 色 底 纹 添 加
¹ º ¹ º 农 业 流 动 人 口 是 指 户 口 性 质 为 农 业 户 口 在 流 入 地 城 市 工 作 生 活 居 住 一 个 月 及 以 上 的 流 动 人 口 非 农 流 动 人 口 是 指 户 口 性 质 为 非 农 户 口 在 流 入 地 城 市 工 作 生 活 居 住 一 个
¹ 改 革 开 放 年 来 人 口 流 动 规 模 持 续 增 加 对 我 国 社 会 经 济 的 持 续 发 展 起 到 了 重 要 作 用 为 全 面 了 解 我 国 流 动 人 口 生 存 状 况 准 确 把 握 流 动 人 口 发 展 规 律 和 趋 势 不 断 加 强 流 动 人 口 服 务 管 理 引 导 人 口 有 序 流 动 合 理 分 布 国 家 人 口 计 生 委 于 年 月 启
黄 金 原 油 总 持 仓 增 长, 同 比 增 幅 分 别 为 4.2% 和 4.1% 而 铜 白 银 以 及 玉 米 则 出 现 减 持, 减 持 同 比 减 少 分 别 为 9.4%,9.4% 以 及 6.5% 大 豆, 豆 粕 结 束 连 续 4 周 总 持 仓 量 增 长, 出 现 小 幅
小 麦 净 多 持 仓 增 加, 豆 油 豆 粕 净 多 持 仓 减 少 美 国 CFTC 持 仓 报 告 部 门 : 市 场 研 究 与 开 发 部 类 型 : 量 化 策 略 周 报 日 期 :212 年 5 月 7 日 电 话 :592-5678753 网 址 :www.jinyouqh.com 主 要 内 容 : 根 据 美 国 CFTC 公 布 的 数 据, 本 报 告 中 的 11 个
一 开 放 性 的 政 策 与 法 规 二 两 岸 共 同 的 文 化 传 承 三 两 岸 高 校 各 自 具 有 专 业 优 势 远 见 杂 志 年 月 日
河 北 师 范 大 学 学 报 新 时 期 海 峡 两 岸 高 校 开 放 招 生 问 题 探 讨 郑 若 玲 王 晓 勇 海 峡 两 岸 高 校 开 放 招 生 是 新 时 期 推 进 海 峡 两 岸 高 等 教 育 交 流 与 合 作 的 重 要 尝 试 系 统 梳 理 改 革 开 放 以 来 两 岸 招 生 政 策 与 就 学 人 数 发 展 变 化 的 历 史 进 程 可 发 现 促 进 两
附件1:
附 件 5 增 列 硕 士 专 业 学 位 授 权 点 申 请 表 硕 士 专 业 学 位 类 别 ( 工 程 领 域 ): 工 程 硕 士 ( 控 制 工 程 领 域 ) 申 报 单 位 名 称 : 上 海 工 程 技 术 大 学 一 申 请 增 列 硕 士 专 业 学 位 授 权 点 论 证 报 告 申 请 增 列 硕 士 专 业 学 位 授 权 点 论 证 报 告 一 专 业 人 才 需 求
21 业 余 制 -- 高 起 专 (12 级 ) 75 元 / 学 分 网 络 学 院 学 生 沪 教 委 财 (2005)49 号 江 西 化 校 工 科 22 业 余 制 -- 高 起 专 (12 级 ) 70 元 / 学 分 网 络 学 院 学 生 沪 教 委 财 (2005)49 号 吉
1 普 通 高 校 学 费 5000 元 / 学 年 一 般 专 业 2 普 通 高 校 学 费 5500 元 / 学 年 特 殊 专 业 3 普 通 高 校 学 费 10000 元 / 学 年 艺 术 专 业 4 中 德 合 作 办 学 15000 元 / 学 年 本 科 生 本 科 学 费 5 ( 含 港 澳 修 读 第 二 专 业 辅 修 专 业 及 学 位 学 费 不 超 过 选 读 专 业
现 场 会 议 时 间 为 :2016 年 5 月 19 日 网 络 投 票 时 间 为 :2016 年 5 月 18 日 -2016 年 5 月 19 日 其 中 通 过 深 圳 证 券 交 易 所 交 易 系 统 进 行 网 络 投 票 的 时 间 为 2016 年 5 月 19 日 9:30-
证 券 代 码 :300439 证 券 简 称 : 美 康 生 物 公 告 编 号 :2016-046 宁 波 美 康 生 物 科 技 股 份 有 限 公 司 2015 年 度 股 东 大 会 决 议 公 告 公 司 及 董 事 会 全 体 成 员 保 证 信 息 披 露 的 内 容 真 实 准 确 完 整, 没 有 虚 假 记 载 误 导 性 陈 述 或 重 大 遗 漏 特 别 提 示 : 1 2016
<4D6963726F736F667420576F7264202D20BFC9B1E0B3CCD0F2BFD8D6C6CFB5CDB3C9E8BCC6CAA6B9FABCD2D6B0D2B5B1EAD7BC2E646F63>
国 家 职 业 标 准 1 可 编 程 序 控 制 系 统 设 计 师 国 家 职 业 标 准 1. 职 业 概 况 1.1 职 业 名 称 可 编 程 序 控 制 系 统 设 计 师 1.2 职 业 定 义 从 事 可 编 程 序 控 制 器 (PLC) 选 型 编 程, 并 对 应 用 系 统 进 行 设 计 集 成 和 运 行 管 理 的 人 员 1.3 职 业 等 级 本 职 业 共 设 四
<4D F736F F D20D6D8D3CA3535BAC5B9D8D3DAD3A1B7A2A1B6D6D8C7ECD3CAB5E7B4F3D1A7D1A7CABFD1A7CEBBCADAD3E8B9A4D7F7CFB8D4F2A1B7B5C4CDA8D6AA2E646F63>
重 邮 2015 55 号 关 于 印 发 重 庆 邮 电 大 学 学 士 学 位 授 予 工 作 细 则 的 通 知 各 相 关 单 位 : 现 将 重 庆 邮 电 大 学 学 士 学 位 授 予 工 作 细 则 印 发 你 们, 请 遵 照 执 行 重 庆 邮 电 大 学 2015 年 3 月 18 日 1 重 庆 邮 电 大 学 学 士 学 位 授 予 工 作 细 则 第 一 章 总 则 第
朱 丽 明 柯 美 云 周 丽 雅 袁 耀 宗 罗 金 燕 候 晓 华 陈 旻 湖 滥 用 安 非 他 命 会 增 加 得 心 脏 病 的 风 险 据 美 国 科 技 新 闻 网 报 道 根 据 纽 约 路 透 社 报 道 一 份 新 的 研 究 显 示 青 年 及 成 年 人 若 滥 用 安 非 他 命 会 增 加 得 心 脏 病 的 风 险 美 国 德 州 大 学 西 南 医 学 中 心
三门峡市质量技术监督局清单公示
附 件 4 卢 氏 县 财 政 局 行 政 职 权 运 行 流 程 图 一 行 政 处 罚 类 1. 第 1 项 一 般 程 序 流 程 图 案 件 来 源 初 步 确 认 违 法 事 实, 责 令 停 止 违 法 行 为 县 财 政 局 立 案 审 批 综 合 股 登 记 立 案 调 查 取 证 不 予 立 案 综 合 股 撰 写 调 查 终 结 报 告 移 送 有 关 部 门 综 合 股 提 出
第 一 部 分 MagiCAD for Revit 安 装 流 程
MagiCAD 软 件 安 装 流 程 MagiCAD v2015.4 for Revit 广 联 达 软 件 股 份 有 限 公 司 BIM 中 心 编 写 2015 年 06 月 第 一 部 分 MagiCAD for Revit 安 装 流 程 一 安 装 前 需 要 确 认 的 内 容 安 装 MagiCAD 程 序 之 前, 请 您 先 确 定 以 下 事 宜 1. 当 前 用 户 账 户
类 似 地, 又 可 定 义 变 下 限 的 定 积 分 : ( ). 与 ψ 统 称 为 变 限 积 分. f ( ) d f ( t) dt,, 注 在 变 限 积 分 (1) 与 () 中, 不 可 再 把 积 分 变 量 写 成 的 形 式 ( 例 如 ) 以 免 与 积 分 上 下 限 的
5 ( 一 ) 微 积 分 学 基 本 定 理 当 函 数 的 可 积 性 问 题 告 一 段 落, 并 对 定 积 分 的 性 质 有 了 足 够 的 认 识 之 后, 接 着 要 来 解 决 一 个 以 前 多 次 提 到 过 的 问 题 在 定 积 分 形 式 下 证 明 连 续 函 数 必 定 存 在 原 函 数. 一 变 限 积 分 与 原 函 数 的 存 在 性 设 f 在 [,] 上
工 程 勘 察 资 质 标 准 根 据 建 设 工 程 勘 察 设 计 管 理 条 例 和 建 设 工 程 勘 察 设 计 资 质 管 理 规 定, 制 定 本 标 准 一 总 则 ( 一 ) 本 标 准 包 括 工 程 勘 察 相 应 专 业 类 型 主 要 专 业 技 术 人 员 配 备 技 术
住 房 和 城 乡 建 设 部 关 于 印 发 工 程 勘 察 资 质 标 准 的 通 知 建 市 [2013]9 号 各 省 自 治 区 住 房 和 城 乡 建 设 厅, 北 京 市 规 划 委, 天 津 上 海 市 建 设 交 通 委, 重 庆 市 城 乡 建 设 委, 新 疆 生 产 建 设 兵 团 建 设 局, 总 后 基 建 营 房 部 工 程 局, 国 务 院 有 关 部 门 建 设 司,
Template BR_Rec_2005.dot
ITU-R BT.1789 建 议 书 1 ITU-R BT.1789 建 议 书 在 分 组 视 频 传 输 中 利 用 传 输 误 码 信 息 重 建 接 收 视 频 的 方 法 (ITU-R 44/6 和 ITU-R 109/6 课 题 ) (2007 年 ) 范 围 本 建 议 书 对 业 务 提 供 商 重 建 接 收 视 频 的 方 法 做 了 详 细 介 绍, 以 便 利 用 传 输
4 进 入 交 互 区 设 置 的 组 件 管 理, 在 组 件 管 理 中, 教 师 可 以 选 择 课 程 空 间 中 的 所 有 组 件, 并 通 过 点 击 启 用 或 不 启 用 选 定 组 件 在 课 程 空 间 中 的 显 示 5 进 入 工 作 室 管 理 的 工 作 室 首 页,
网 络 教 育 新 平 台 教 师 使 用 简 易 手 册 一 登 录 教 师 工 作 室 1 打 开 西 南 科 技 大 学 网 络 教 育 教 学 教 务 新 平 台 主 页 面 :http://www.swust.net.cn/ 2 在 主 页 面 左 边 的 登 陆 区 中, 用 户 名 和 密 码 处 分 别 输 入 自 己 的 用 户 名 ( 教 师 ID 号 ) 和 密 码 ( 初 始
浙 江 海 洋 学 院 417 普 通 生 态 学 与 鱼 类 学 全 套 考 研 资 料 <2016 年 最 新 考 研 资 料 > 2-2 基 础 生 态 学 笔 记, 此 笔 记 为 高 分 研 究 生 复 习 所 用, 借 助 此 笔 记 可 以 大 大 提 高 复 习 效 率, 把 握 报
浙 江 海 洋 学 院 417 普 通 生 态 学 与 鱼 类 学 全 套 考 研 资 料 ......2 浙 江 海 洋 学 院 340 农 业 知 识 综 合 二 全 套 考 研 资 料 ......2 浙 江 海 洋 学 院 341 农 业 知 识 综 合 三 全 套 考 研 资 料
合 并 计 算 配 售 对 象 持 有 多 个 证 券 账 户 的, 多 个 证 券 账 户 市 值 合 并 计 算 确 认 多 个 证 券 账 户 为 同 一 配 售 对 象 持 有 的 原 则 为 证 券 账 户 注 册 资 料 中 的 账 户 持 有 人 名 称 有 效 身 份 证 明 文 件
深 圳 市 场 首 次 公 开 发 行 股 票 网 下 发 行 实 施 细 则 ( 征 求 意 见 稿 ) 第 一 章 总 则 第 一 条 为 规 范 深 圳 市 场 首 次 公 开 发 行 股 票 网 下 发 行 行 为, 根 据 证 券 发 行 与 承 销 管 理 办 法 及 相 关 规 定, 制 定 本 细 则 第 二 条 本 细 则 所 称 网 下 发 行 是 指 首 次 公 开 发 行 股
目 录 一 激 活 账 号... 2 二 忘 记 密 码 后 如 何 找 回 密 码?... 3 三 如 何 管 理 学 校 信 息 及 球 队 学 生 教 师 等 信 息... 6 四 如 何 发 布 本 校 校 园 文 化?... 11 五 如 何 向 教 师 发 送 通 知?... 13 六
一 刻 校 园 足 球 管 理 平 台 使 用 说 明 ( 学 校 管 理 员 版 ) 一 刻 软 件 科 技 有 限 公 司 目 录 一 激 活 账 号... 2 二 忘 记 密 码 后 如 何 找 回 密 码?... 3 三 如 何 管 理 学 校 信 息 及 球 队 学 生 教 师 等 信 息... 6 四 如 何 发 布 本 校 校 园 文 化?... 11 五 如 何 向 教 师 发 送
第 期 李 伟 等 用 方 法 对 中 国 历 史 气 温 数 据 插 值 可 行 性 讨 论
李 伟 李 庆 祥 江 志 红 使 用 插 值 方 法 对 已 经 过 质 量 控 制 和 均 一 化 的 年 月 年 月 中 国 全 部 基 本 基 准 站 气 温 资 料 逐 月 进 行 空 间 插 值 通 过 站 点 的 实 际 序 列 与 插 值 后 格 点 序 列 进 行 比 较 针 对 相 关 系 数 和 线 性 趋 势 等 多 个 量 来 检 验 方 法 对 气 候 资 料 插 值 的
!!!!!!!!!!
有 限 理 性 动 物 精 神 及 市 场 崩 溃 对 情 绪 波 动 与 交 易 行 为 的 实 验 研 究 林 树 俞 乔 资 本 市 场 的 经 验 表 明 市 场 参 与 主 体 投 资 者 的 情 绪 波 动 对 资 产 交 易 与 价 格 决 定 产 生 了 不 可 忽 视 的 影 响 但 是 现 有 文 献 尚 缺 乏 对 这 一 重 要 因 素 的 研 究 因 此 本 文 的 目 的
