The success's road 嵌 入 式 LINUX 网 络 驱 动 开 发 Copyright 2007-2008 Farsight. All rights reserved.
要 点 Linux 网 络 设 备 驱 动 程 序 概 述 计 算 机 网 络 概 述 skbuf 数 据 结 构 介 绍 Linux 网 络 设 备 驱 动 程 序 API 介 绍 Linux 网 络 设 备 驱 动 程 序 实 现 算 法 剖 析 Linux 网 络 设 备 驱 动 程 序 源 代 码
Linux 网 络 设 备 驱 动 程 序 概 述 Linux 网 络 驱 动 程 序 遵 循 通 用 的 接 口 设 计 时 采 用 的 是 面 向 对 象 的 方 法 一 个 设 备 就 是 一 个 对 象 (net_device 结 构 ), 它 内 部 有 自 己 的 数 据 和 方 法 一 个 网 络 设 备 最 基 本 的 方 法 有 初 始 化, 发 送 和 接 收 Linux 网 络 驱 动 程 序 的 体 系 结 构 可 以 划 分 为 四 层 : 网 络 协 议 接 口, 网 络 设 备 接 口, 设 备 驱 动 功 能, 网 络 设 备 和 网 络 媒 介 层 网 络 驱 动 程 序, 最 主 要 的 工 作 就 是 完 成 设 备 驱 动 功 能 层 在 Linux 中 所 有 网 络 设 备 都 抽 象 为 一 个 接 口, 这 个 接 口 提 供 了 对 所 有 网 络 设 备 的 操 作 集 合 由 数 据 结 构 struct net_device 来 表 示 网 络 设 备 在 内 核 中 的 运 行 情 况, 即 网 络 设 备 接 口 它 既 包 括 纯 软 件 网 络 设 备 接 口, 如 环 路 (Loopback), 也 包 括 硬 件 网 络 设 备 接 口, 如 以 太 网 卡 而 由 以 dev_base 为 头 指 针 的 设 备 链 表 来 集 体 管 理 所 有 网 络 设 备, 该 设 备 链 表 中 的 每 个 元 素 代 表 一 个 网 络 设 备 接 口 数 据 结 构 net_device 中 有 很 多 供 系 统 访 问 和 协 议 层 调 用 的 设 备 方 法, 包 括 初 始 化, 打 开 和 关 闭 网 络 设 备 的 open 和 stop 函 数, 处 理 数 据 包 发 送 的 hard_start_xmit 函 数, 以 及 中 断 处 理 函 数 等 网 络 设 备 在 Linux 里 做 专 门 的 处 理 Linux 的 网 络 系 统 主 要 是 基 于 BSD unix 的 socket 机 制 在 系 统 和 驱 动 程 序 之 间 定 义 有 专 门 的 数 据 结 构 (sk_buff) 进 行 数 据 的 传 递 系 统 里 支 持 对 发 送 数 据 和 接 收 数 据 的 缓 存, 提 供 流 量 控 制 机 制, 提 供 对 多 协 议 的 支 持
Linux 与 网 络 架 构
OSI 网 络 参 考 模 型
OSI VS TCP/IP
Linux 网 络 设 备 网 络 设 备, 又 叫 网 络 接 口 是 Linux 第 三 类 标 准 设 备 网 络 设 备 和 块 设 备 类 似, 在 内 核 的 特 定 数 据 结 构 中 注 册 自 己 当 发 生 网 络 数 据 交 换 时, 网 络 设 备 驱 动 程 序 注 册 的 方 法 将 被 内 核 调 用 网 络 设 备 不 会 在 /dev 下 存 在 一 个 设 备 入 口, 它 使 用 保 留 的 内 部 设 备 名
网 络 设 备 的 特 点 网 络 设 备 异 步 的 接 收 外 来 的 数 据 包, 有 别 于 其 他 设 备 网 络 设 备 主 动 的 请 求 将 硬 件 获 得 的 数 据 包 压 入 内 核, 而 其 他 设 备 例 如 块 设 备 被 请 求 向 内 核 发 送 缓 冲 区 网 络 设 备 同 时 要 执 行 大 量 的 管 理 任 务 设 置 地 址 修 改 传 输 参 数 维 护 流 量 和 流 量 控 制 错 误 统 计 和 报 告 网 络 子 系 统 是 完 全 与 协 议 无 关 的, 网 络 驱 动 程 序 与 内 核 其 余 部 分 之 间 的 每 次 交 互 处 理 的 都 是 一 个 网 络 数 据 包
网 络 设 备 驱 动 体 系 结 构
套 接 字 socket 简 介
套 接 字 socket 简 介 客 户 端 与 服 务 器 都 围 绕 着 通 信 端 点 (commnication endpoint) 的 概 念, 即 套 接 字 一 个 套 接 字 通 过 使 用 socket() 函 数 惟 一 确 定 了 一 个 端 点 该 端 点 的 细 节 可 以 进 一 步 由 connect() 或 bin() 函 数 定 义 最 终, 客 户 端 定 义 的 端 点 将 与 服 务 器 定 义 的 端 点 进 行 连 接 和 通 信 使 用 UDP 和 TCP 时, 端 点 就 是 本 地 或 远 程 IP 地 址 与 端 口 的 组 合 Socket 广 泛 用 在 网 络 编 程 中, 使 用 socket 有 固 定 的 流 程
套 接 字 socket 简 介 (2)( 服 务 器 方 调 用 socket 调 用 bind 连 接 网 络 地 址 启 动 监 听 listen 等 待 连 接 accept recv,send 交 换 数 据 close socket 客 户 端 方 调 用 socket 调 用 connet 连 接 远 程 主 机 recv,send 交 换 数 据 close socket
sk_buff 结 构 分 析
sk_buff 结 构 套 接 字 缓 冲 区 (sk_buff) 结 构 是 Linux 内 核 网 络 子 系 统 的 核 心 内 容, 在 <linux/skbuff.h> 中 被 定 义 sk_buff 结 构 中 的 重 要 字 段 : struct net_device*input_dev; struct net_device*dev; 分 别 为 接 收 和 发 送 缓 冲 区 的 设 备 union { /*... */ } h; union { /*... */ } nh; union { /*... */} mac; 指 向 数 据 包 中 各 个 层 的 数 据 包 头 h 指 向 传 输 层 包 头,nh 指 向 网 络 层 包 头,mac 指 向 链 路 层 包 头
sk_buff 结 构 (2)( unsigned char *head; unsigned char *data; unsigned char *tail; unsigned char *end; 用 来 寻 址 数 据 包 中 的 数 据 指 针 head 指 向 已 分 配 空 间 开 头, data 指 向 有 效 的 octet 开 头,tail 指 向 有 效 的 octet 结 尾, 而 end 指 向 tail 可 以 到 达 的 最 大 地 址 unsigned long len; 描 述 数 据 长 度 unsigned char ip_summed; 描 述 该 数 据 包 的 校 验 和 策 略 unsigned char pkt_type; 描 述 该 数 据 包 的 类 型, 可 以 是 PACKET_HOST, PACKET_BROADCAST, PACKET_MULTICAST, PACKET_OTHERHOST
用 来 操 作 sk_buff 的 函 数 struct sk_buff*alloc_skb(unsigned int len, int priority); struct sk_buff*dev_alloc_skb(unsigned int len); 用 来 分 配 套 接 字 缓 冲 区 void kfree_skb(struct sk_buff*skb); void dev_kfree_skb(struct sk_buff*skb); 用 来 释 放 套 接 字 缓 冲 区 unsigned char *skb_put(struct sk_buff*skb, int len); unsigned char *skb_push(struct sk_buff*skb, int len); 这 两 个 函 数 用 来 在 缓 冲 区 末 尾 添 加 数 据, 前 一 个 函 数 会 进 行 检 查
用 来 操 作 sk_buff 的 函 数 (2)( unsigned char *skb_push(struct sk_buff*skb, int len); unsigned char * skb_push(struct sk_buff*skb, int len); 这 两 个 函 数 用 来 在 缓 冲 区 头 部 添 加 数 据 int skb_tailroom(struct sk_buff*skb); 该 函 数 返 回 缓 冲 区 后 部 可 用 空 间 总 量 int skb_headroom(struct sk_buff*skb); 该 函 数 返 回 缓 冲 区 前 面 部 分 可 用 空 间 总 量 void skb_reserve(struct sk_buff*skb, int len); 该 函 数 在 可 填 充 缓 冲 区 之 前 保 留 包 头 空 间 unsigned char *skb_pull(struct sk_buff*skb, int len); 该 函 数 从 数 据 包 头 拿 出 数 据, 通 常 用 来 剥 离 数 据 包 头
net_device 结 构 分 析
net_device 结 构 分 析 该 结 构 是 网 络 设 备 驱 动 的 核 心, 它 包 含 了 许 多 成 员 可 以 参 考 <include/linux/netdevice.h> 文 件, 阅 读 它 的 完 整 定 义 net_device 结 构 可 分 为 全 局 成 员 硬 件 相 关 成 员 接 口 相 关 成 员 设 备 方 法 成 员 和 公 用 成 员 等 五 个 部 分 char name[ifnamsiz]; unsigned long mem_end;
net_device 结 构 分 析 (2)( unsigned long mem_start; 这 些 字 段 描 述 了 设 备 共 享 内 存 的 起 止 地 址 unsigned long base_addr; 描 述 设 备 的 I/O 基 地 址 unsigned char irq; 描 述 设 备 中 断 号 unsigned char if_port; 描 述 多 端 口 设 备 的 活 动 端 口 unsigned char dma; 描 述 设 备 的 DMA 通 道
net_device 结 构 分 析 (3)( 网 络 设 备 要 实 现 一 系 列 函 数 作 为 调 用 方 法 的 实 现 基 本 方 法 包 括 int (*open)(structnet_device *dev); 打 开 接 口 当 ifconfig 激 活 网 络 设 备 时, 接 口 被 打 开 通 常 我 们 在 open 方 法 里 面 完 成 资 源 的 分 配, 包 括 I/O 映 射 中 断 注 册 DMA 注 册 等 同 时 激 活 硬 件, 并 增 加 使 用 计 数 int (*stop)(struct net_device *dev); 停 止 接 口 在 这 个 方 法 里 面, 我 们 完 成 与 open 方 法 相 反 的, 注 销 操 作 int (*hard_start_xmit) (structsk_buff *skb, structnet_device *dev); 该 方 法 初 始 化 数 据 包 的 传 输 是 网 络 设 备 驱 动 中 非 常 重 要 的 一 个 方 法 我 们 将 完 整 的 数 据 包 放 入 一 个 套 接 字 缓 冲 区 sk_buff 结 构 里
net_device 结 构 分 析 (4)( int (*hard_header) (structsk_buff *skb, struct net_device *dev, unsigned short type, void *daddr, void *saddr, unsigned len); 该 方 法 根 据 先 前 检 索 到 的 源 和 目 的 硬 件 地 址 建 立 硬 件 头 int (*rebuild_header)(structsk_buff *skb); 该 方 法 用 来 在 传 输 数 据 包 之 前 重 建 硬 件 头 void (*tx_timeout)(struct net_device *dev); 如 果 数 据 包 发 送 在 超 时 时 间 内 失 败, 这 时 该 方 法 被 调 用 这 个 方 法 应 该 解 决 失 败 的 问 题 并 重 新 开 始 发 送 数 据 包
net_device 结 构 分 析 (4-1)( structnet_device_stats *(*get_stats)(structnet_device*dev); 当 应 用 程 序 需 要 获 得 接 口 的 统 计 信 息 时, 这 个 方 法 被 调 用 int (*set_config)(struct net_device *dev, struct ifmap*map); 改 变 接 口 的 配 置 比 如 改 变 I/O 端 口 和 中 断 号 等, 现 在 的 驱 动 程 序 通 常 无 需 该 方 法
net_device 结 构 分 析 (5)( 可 选 方 法 在 以 太 网 设 备 中 通 常 都 可 以 使 用 系 统 提 供 的 方 法, 也 可 以 自 己 实 现 int (*do_ioctl)(struct net_device*dev, struct ifreq *ifr, int cmd); 用 来 实 现 设 备 自 定 义 的 ioctl 命 令 如 果 不 需 要 自 定 义 命 令, 可 以 为 NULL void (*set_multicast_list)(struct net_device*dev); 当 设 备 的 组 播 列 表 改 变 或 设 备 标 志 改 变 时, 该 方 法 被 调 用 int (*set_mac_address)(struct net_device*dev, void *addr); 如 果 接 口 支 持 MAC 地 址 改 变, 则 可 以 实 现 该 函 数
net_device 结 构 分 析 (6)( int (*change_mtu)(struct net_device*dev, int new_mtu); 当 接 口 的 MTU 改 变 时, 该 方 法 将 被 调 用, 负 责 做 出 相 应 的 特 定 处 理 int (*header_cache) (struct neighbour*neigh, struct hh_cache*hh); 根 据 ARP 查 询 的 结 果 填 充 hh_cache 结 构 int (*header_cache_update) (struct hh_cache*hh, struct net_device*dev, unsigned char *haddr); 在 发 生 变 化 时, 该 方 法 更 新 hh_cache 结 构 中 的 目 的 地 址 int (*hard_header_parse) (struct sk_buff*skb, unsigned char *haddr); 从 skb 中 包 含 的 数 据 包 中 获 得 源 地 址, 并 将 其 复 制 到 位 于 haddr 的 缓 冲 区 中
打 开 和 关 闭 网 络 设 备 系 统 响 应 ifconfig 命 令 时, 打 开 和 关 闭 一 个 接 口 ifconfig 开 始 会 调 用 ioctl(siocsifaddr) 来 将 地 址 赋 予 接 口 响 应 SIOCSIFADDR 由 内 核 来 完 成, 与 设 备 无 关 接 着,ifconfig 会 调 用 ioctl(siocsifflags), 设 置 dev->flag 的 IFF_UP 位 来 打 开 设 备, 这 个 调 用 会 使 得 设 备 的 open 方 法 得 到 调 用 当 ifconfig 调 用 ioctl(siocsifflags), 清 除 dev- >flag 的 IFF_UP 位 时, 设 备 的 stop 方 法 将 被 调 用
数 据 包 接 收 / 发 送
数 据 包 传 送 与 接 收 网 络 接 口 最 重 要 任 务 就 是 发 送 和 接 收 数 据 当 内 核 要 发 送 一 个 数 据 包 时, 它 会 调 用 hard_start_transmit 方 法 来 讲 数 据 放 入 发 送 队 列 内 核 处 理 过 的 每 个 数 据 包 位 于 一 个 套 接 字 缓 冲 区 (struct sk_buff) 里 面
控 制 并 发 传 输 hard_start_xmit 函 数 通 过 net_device 结 构 中 的 一 个 自 旋 锁 (xmit_lock) 获 得 并 发 调 用 时 的 保 护 实 际 硬 件 设 备 中 的 缓 冲 区 很 有 限, 驱 动 程 序 需 要 告 诉 网 络 子 系 统 在 硬 件 能 够 接 受 新 数 据 之 前, 不 能 启 动 其 他 的 数 据 发 送 调 用 netif_stop_queue 可 以 完 成 这 种 通 知 前 一 个 数 据 传 送 完 成, 当 设 备 缓 冲 区 再 次 可 用 时, 应 该 调 用 netif_wake_queue 来 通 知 网 络 子 系 统 重 新 启 动 传 送 队 列
传 输 超 时 网 络 传 输 需 要 面 对 可 能 不 可 靠 的 设 备 传 输 介 质 硬 件 驱 动 程 序 必 须 能 够 处 理 硬 件 不 能 正 确 响 应 的 问 题 一 般 来 说 驱 动 程 序 采 取 超 时 方 式 处 理 这 类 异 常 情 况, 即 : 如 果 某 个 操 作 在 定 时 器 到 期 时 还 未 完 成, 则 认 为 出 现 了 异 常 问 题 网 络 驱 动 程 序 可 以 在 net_device 结 构 的 watchdog_timeo 字 段 中 设 置 超 时 时 间, 以 jiffies 为 单 位 如 果 当 前 的 系 统 时 间 超 过 设 备 的 trans_start 时 间 至 少 一 个 超 时 周 期, 那 么 网 络 层 将 最 终 调 用 驱 动 程 序 的 tx_timeout 方 法 这 个 方 法 完 成 解 决 超 时 问 题 的 工 作, 并 保 证 正 在 进 行 的 任 何 传 输 能 够 正 常 结 束
数 据 包 的 接 收 接 收 数 据 包 首 先 通 过 设 备 中 断, 由 硬 件 通 知 驱 动 程 序 有 数 据 包 到 达 网 络 例 子 驱 动 程 序 实 现 cs8900_receive 函 数, 该 函 数 在 收 到 数 据 包 后 被 调 用 它 获 得 一 个 指 向 数 据 的 指 针 以 及 数 据 包 的 长 度, 然 后 将 数 据 以 及 附 加 信 息 发 送 到 上 层 的 网 络 代 码
网 络 驱 动 的 中 断 处 理
网 络 中 断 处 理 中 断 处 理 程 序 可 以 检 查 物 理 设 备 上 的 状 态 寄 存 器, 用 来 区 分 是 数 据 包 到 达 中 断 还 是 传 输 完 成 中 断 传 输 结 束 时, 中 断 处 理 程 序 更 新 统 计 信 息, 并 且 调 用 dev_kfree_skb 将 不 再 使 用 的 套 接 字 缓 冲 区 释 放 给 系 统 同 时 在 传 输 结 束 时, 如 果 之 前 驱 动 程 序 停 止 了 传 输 队 列, 则 调 用 netif_wake_queue 重 新 启 动 传 输 队 列 而 在 数 据 包 到 达 中 断 发 生 时, 中 断 处 理 程 序 只 是 调 用 数 据 接 收 函 数 就 够 了
网 络 设 备 驱 动 的 基 本 实 现
网 络 设 备 驱 动 的 基 本 实 现 一 个 网 络 设 备 最 基 本 的 方 法 有 初 始 化 打 开 关 闭 发 送 和 接 收 初 始 化 程 序 完 成 硬 件 的 初 始 化 网 络 设 备 中 变 量 的 初 始 化 和 系 统 资 源 的 申 请 发 送 程 序 是 在 驱 动 程 序 的 上 层 协 议 层 有 数 据 要 发 送 时 自 动 调 用 的 一 般 驱 动 程 序 中 不 对 发 送 数 据 进 行 缓 存, 而 是 直 接 使 用 硬 件 的 发 送 功 能 把 数 据 发 送 出 去 接 收 数 据 一 般 是 通 过 硬 件 中 断 来 通 知 的 在 中 断 处 理 程 序 里, 把 硬 件 帧 信 息 填 入 一 个 sk_buff 结 构 中, 然 后 调 用 netif_rx() 传 递 给 上 层 处 理
初 始 化 设 备 探 测 工 作 在 init 方 法 中 进 行, 一 般 调 用 一 个 称 之 为 probe 方 法 的 函 数 初 始 化 的 主 要 工 作 是 检 测 设 备, 配 置 和 初 始 化 硬 件, 最 后 向 系 统 申 请 这 些 资 源 此 外, 填 充 该 设 备 的 dev 结 构, 我 们 可 以 调 用 内 核 提 供 的 ether_setup 方 法 来 设 置 一 些 以 太 网 默 认 的 设 置 当 我 们 需 要 卸 载 网 络 驱 动 程 序 时, 我 们 应 该 释 放 初 始 化 时 分 配 的 资 源, 最 后 调 用 unregister_netdev 来 讲 自 己 从 全 局 网 络 设 备 链 表 中 注 销
打 开 (open( open) open 这 个 方 法 在 网 络 设 备 驱 动 程 序 里 是 网 络 设 备 被 激 活 的 时 候 被 调 用 ( 即 设 备 状 态 由 down-- >up) 实 际 上 很 多 在 初 始 化 中 的 工 作 可 以 放 到 这 里 来 做 比 如 资 源 的 申 请, 硬 件 的 激 活 如 果 dev- >open 返 回 非 0(error), 则 硬 件 的 状 态 还 是 down 网 络 驱 动 程 序 例 子 中 由 cs8900_start 函 数 实 现 open 方 法
关 闭 (stop( stop) stop 方 法 做 和 open 相 反 的 工 作 可 以 释 放 某 些 资 源 以 减 少 系 统 负 担 stop 是 在 设 备 状 态 由 up 转 为 down 时 被 调 用 的 网 络 驱 动 程 序 例 子 中 由 cs8900_stop 函 数 实 现 stop 方 法
发 送 (hard_start_xmit) 在 系 统 调 用 驱 动 程 序 的 hard_start_xmit 时, 发 送 的 数 据 放 在 一 个 sk_buff 结 构 中 一 般 的 驱 动 程 序 把 数 据 传 给 硬 件 发 出 去 也 有 一 些 特 殊 的 设 备 比 如 loopback 把 数 据 组 成 一 个 接 收 数 据 再 回 送 给 系 统, 或 者 dummy 设 备 直 接 丢 弃 数 据 如 果 发 送 成 功,hard_start_xmit 方 法 里 释 放 sk_buff, 返 回 0( 发 送 成 功 ) 如 果 设 备 暂 时 无 法 处 理, 比 如 硬 件 忙, 则 返 回 1 网 络 驱 动 程 序 例 子 中 由 cs8900_send_start 函 数 实 现 发 送
接 收 (reception) 驱 动 程 序 并 不 存 在 一 个 接 收 方 法 有 数 据 收 到 应 该 是 驱 动 程 序 来 通 知 系 统 的 一 般 设 备 收 到 数 据 后 都 会 产 生 一 个 中 断, 在 中 断 处 理 程 序 中 驱 动 程 序 申 请 一 块 sk_buff(skb), 从 硬 件 读 出 数 据 放 置 到 申 请 好 的 缓 冲 区 里 接 下 来 填 充 sk_buff 中 的 一 些 信 息 网 络 驱 动 程 序 例 子 中 由 cs8900_receive 函 数 实 现 接 收 功 能