环境配置
sudo apt-get install build-essential linux-headers-`uname -r`
Hello Kernel
小记:
printk(KERN_INFO "Hello, World!\n");
注意使用printk而不是printf,另外,printk与printf共享的参数也不相同。
例如,KERN_INFO 是一个标志,用于声明应为该行设置的日志记录优先级,并且不带逗号。内核在printk函数中对此进行分类以节省堆栈内存。
module_init和module_exit函数告诉内核哪些函数是内核模块的加载和卸载函数。这使我们可以任意命名这两个函数。
网络模型
- 应用层 :HTTP
- 传输层:TCP UDP
- 网络层:IP ICMP
- 链路层:ARP
套接字缓冲区 sk_buff
struct sk_buff {
union {
struct {
/* 这两个成员必须放在最前面,用于将sk_buff组织成双向链表 */
struct sk_buff *next; // 指向下一个sk_buff
struct sk_buff *prev; // 指向前一个sk_buff
union {
ktime_t tstamp; // 时间戳(高精度时间)
struct skb_mstamp skb_mstamp; // 另一种时间戳表示
};
};
struct rb_node rbnode; /* 用于netem和tcp栈中的红黑树节点 */
};
struct sock *sk; // 与此sk_buff关联的套接字
union {
struct net_device *dev; // 网络设备指针(接收或发送数据的设备)
unsigned long dev_scratch; // 设备暂存数据(当设备指针为NULL时,某些协议使用此空间存储信息)
};
/*
* 控制缓冲区。每层都可以自由使用这个空间。
* 如果你想在层之间保持变量,需要先进行skb_clone()。
* 当前拥有此sk_buff排队权的人拥有这个缓冲区。
*/
char cb[48] __aligned(8); // 48字节的控制缓冲区,8字节对齐
unsigned long _skb_refdst; // 目标缓存引用
void (*destructor)(struct sk_buff *skb); // 析构函数,用于清理sk_buff时调用
#ifdef CONFIG_XFRM
struct sec_path *sp; // 安全路径(IPsec相关)
#endif
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
unsigned long _nfct; // 连接跟踪相关信息
#endif
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
struct nf_bridge_info *nf_bridge; // 网桥Netfilter信息
#endif
unsigned int len, // 数据包总长度(线性数据 + 分片数据)
data_len; // 分片数据的长度(存放在分页缓冲区中的数据长度)
__u16 mac_len, // MAC头长度
hdr_len; // 标头长度(可能用于克隆)
/* 以下字段在__copy_skb_header()中不会被复制 */
kmemcheck_bitfield_begin(flags1);
__u16 queue_mapping; // 队列映射(用于多队列设备)
/* 如果移动cloned字段,必须调整这些常量 */
#ifdef __BIG_ENDIAN_BITFIELD
#define CLONED_MASK (1 << 7)
#else
#define CLONED_MASK 1
#endif
#define CLONED_OFFSET() offsetof(struct sk_buff, __cloned_offset)
__u8 __cloned_offset[0]; // 用于位字段偏移计算
__u8 cloned:1, // 表示此sk_buff是克隆的
nohdr:1, // 表示没有头部空间(用于高速路径优化)
fclone:2, // 克隆状态(SKB_FCLONE_UNAVAILABLE等)
peeked:1, // 数据已被查看但未从队列中移除
head_frag:1, // 表示head指向分页片段
xmit_more:1, // 指示还有更多数据要发送
__unused:1; // 未使用的位(一个位的空洞)
kmemcheck_bitfield_end(flags1);
/* headers_start/headers_end之间的字段在__copy_skb_header()中使用单个memcpy()复制 */
/* private: */
__u32 headers_start[0]; // 头部开始标记(用于内存复制优化)
/* public: */
/* 如果移动pkt_type字段,必须调整这些常量 */
#ifdef __BIG_ENDIAN_BITFIELD
#define PKT_TYPE_MAX (7 << 5)
#else
#define PKT_TYPE_MAX 7
#endif
#define PKT_TYPE_OFFSET() offsetof(struct sk_buff, __pkt_type_offset)
__u8 __pkt_type_offset[0]; // 用于位字段偏移计算
__u8 pkt_type:3; // 数据包类型(PACKET_HOST等)
__u8 pfmemalloc:1; // 内存是从PF_MEMALLOC池分配的
__u8 ignore_df:1; // 忽略DF位(不分片)
__u8 nf_trace:1; // Netfilter跟踪启用
__u8 ip_summed:2; // IP校验和状态(CHECKSUM_NONE等)
__u8 ooo_okay:1; // 允许乱序处理
__u8 l4_hash:1; // 包含L4哈希值
__u8 sw_hash:1; // 软件计算的哈希
__u8 wifi_acked_valid:1; // wifi_acked字段是否有效
__u8 wifi_acked:1; // 在WiFi链路上已确认
__u8 no_fcs:1; // 不添加FCS(帧校验序列)
__u8 encapsulation:1; // 表示内部头部有效(用于隧道)
__u8 encap_hdr_csum:1; // 封装头部需要校验和计算
__u8 csum_valid:1; // 校验和是否已验证
__u8 csum_complete_sw:1; // 软件完成的校验和
__u8 csum_level:2; // 校验和级别(0-3)
__u8 csum_bad:1; // 校验和错误
__u8 dst_pending_confirm:1; // 需要确认目标邻居
#ifdef CONFIG_IPV6_NDISC_NODETYPE
__u8 ndisc_nodetype:2; // IPv6邻居发现节点类型
#endif
__u8 ipvs_property:1; // SKB属于ipvs连接
__u8 inner_protocol_type:1; // 内部协议类型
__u8 remcsum_offload:1; // 远程校验和卸载
#ifdef CONFIG_NET_SWITCHDEV
__u8 offload_fwd_mark:1; // 转发卸载标记
#endif
#ifdef CONFIG_NET_CLS_ACT
__u8 tc_skip_classify:1; // 跳过流量控制分类
__u8 tc_at_ingress:1; // 在入口处进行流量控制
__u8 tc_redirected:1; // 数据包被流量控制重定向
__u8 tc_from_ingress:1; // 重定向来自入口处
#endif
#ifdef CONFIG_NET_SCHED
__u16 tc_index; // 流量控制索引
#endif
union {
__wsum csum; // 校验和值
struct {
__u16 csum_start; // 校验和计算起始位置
__u16 csum_offset; // 校验和字段偏移量
};
};
__u32 priority; // 数据包优先级(QoS)
int skb_iif; // 接收数据包的接口索引
__u32 hash; // 数据包哈希
__be16 vlan_proto; // VLAN协议(如802.1Q)
__u16 vlan_tci; // VLAN标签控制信息
#if defined(CONFIG_NET_RX_BUSY_POLL) || defined(CONFIG_XPS)
union {
unsigned int napi_id; // NAPI ID(用于繁忙轮询)
unsigned int sender_cpu; // 发送CPU(用于XPS)
};
#endif
#ifdef CONFIG_NETWORK_SECMARK
__u32 secmark; // 安全标记(SELinux等)
#endif
union {
__u32 mark; // 数据包标记(用于策略路由等)
__u32 reserved_tailroom; // 保留的尾部空间
};
union {
__be16 inner_protocol; // 内部协议(用于隧道封装)
__u8 inner_ipproto; // 内部IP协议
};
__u16 inner_transport_header; // 内部传输层头部偏移
__u16 inner_network_header; // 内部网络层头部偏移
__u16 inner_mac_header; // 内部MAC层头部偏移
__be16 protocol; // 数据包协议(从链路层角度看)
__u16 transport_header; // 传输层头部偏移
__u16 network_header; // 网络层头部偏移
__u16 mac_header; // MAC层头部偏移
/* private: */
__u32 headers_end[0]; // 头部结束标记(用于内存复制优化)
/* public: */
/* 这些元素必须在末尾,详见alloc_skb() */
sk_buff_data_t tail; // 指向数据的末尾
sk_buff_data_t end; // 指向分配的缓冲区的末尾
unsigned char *head, // 指向缓冲区的头部
*data; // 指向数据的头部
unsigned int truesize; // 缓冲区的总大小(包括sk_buff结构和数据缓冲区)
atomic_t users; // 引用计数(用于克隆的sk_buff)
};
struct sk_buff {
union {
struct {
/* 这两个成员必须放在最前面,用于将sk_buff组织成双向链表 */
struct sk_buff *next; // 指向下一个sk_buff
struct sk_buff *prev; // 指向前一个sk_buff
union {
ktime_t tstamp; // 时间戳(高精度时间)
struct skb_mstamp skb_mstamp; // 另一种时间戳表示
};
};
struct rb_node rbnode; /* 用于netem和tcp栈中的红黑树节点 */
};
struct sock *sk; // 与此sk_buff关联的套接字
union {
struct net_device *dev; // 网络设备指针(接收或发送数据的设备)
unsigned long dev_scratch; // 设备暂存数据(当设备指针为NULL时,某些协议使用此空间存储信息)
};
/*
* 控制缓冲区。每层都可以自由使用这个空间。
* 如果你想在层之间保持变量,需要先进行skb_clone()。
* 当前拥有此sk_buff排队权的人拥有这个缓冲区。
*/
char cb[48] __aligned(8); // 48字节的控制缓冲区,8字节对齐
unsigned long _skb_refdst; // 目标缓存引用
void (*destructor)(struct sk_buff *skb); // 析构函数,用于清理sk_buff时调用
#ifdef CONFIG_XFRM
struct sec_path *sp; // 安全路径(IPsec相关)
#endif
#if defined(CONFIG_NF_CONNTRACK) || defined(CONFIG_NF_CONNTRACK_MODULE)
unsigned long _nfct; // 连接跟踪相关信息
#endif
#if IS_ENABLED(CONFIG_BRIDGE_NETFILTER)
struct nf_bridge_info *nf_bridge; // 网桥Netfilter信息
#endif
unsigned int len, // 数据包总长度(线性数据 + 分片数据)
data_len; // 分片数据的长度(存放在分页缓冲区中的数据长度)
__u16 mac_len, // MAC头长度
hdr_len; // 标头长度(用于clone时,表示clone的skb头长度)
...
__u16 transport_header; // 传输层头部偏移
__u16 network_header; // 网络层头部偏移
__u16 mac_header; // MAC层头部偏移
/* private: */
__u32 headers_end[0]; // 头部结束标记(用于内存复制优化)
/* public: */
/* 这些元素必须在末尾,详见alloc_skb() */
sk_buff_data_t tail; // 指向数据的末尾
sk_buff_data_t end; // 指向分配的缓冲区的末尾
unsigned char *head, // 指向缓冲区的头部
*data; // 指向数据的头部
unsigned int truesize; // 缓冲区的总大小(包括sk_buff结构和数据缓冲区)
atomic_t users; // 引用计数(用于克隆的sk_buff)
};
在套接字缓冲区传递到互联网层时,必须增加一个新层。只需要向已经分配但尚未占用的那部分内存空间定稿的数据即可,除了data之外所有的指针都不变,data现在指向IP首部的起始处。
接收分组进行分析过程类似:分组数据复制到内核分配的一个内存区中,并在整个分析期间一直处于该内存区中。与该分组关联的套接字缓冲区在各层之间顺序传递。
关键成员:
- tstamp 保存了分组到达的时间。
- dev 指定了处理分组的网络设备。
- sk 是一个指针,指向用于处理该分组的套接字对应的socket实例。
- dst 表示接下来该分组通过内核网络实现的路由。
- next和prev 用于将套接字缓冲区保存到一个双链表中。
sk_buff api
//分配一个新的sk_buff实例
static inline struct sk_buff *alloc_skb(unsigned int size,gfp_t priority)
//创建套接字缓冲区和相关数据的一个副本
int skb_copy_ubufs(struct sk_buff *skb,gfp_t gfp_mask)
//创建套接字缓冲区的一个副本,但原本和副本将使用同一个分组数据
struct sk_buff *skb_clone(struct sk_buff *skb,gfp_tgfp_mask)
//返回数据末端空闲空间的长度
static inline int skb_tailroom(const struct skbuff *skb)
//返回数据起始处空闲空间的长度
static inline unsigned int skb_headroom(const struct skbuff*skb)
//在数据起始处创建更多的空闲空间,现在数据不变
struct sk_buff *skb_realloc_headroom(struct sk_buff *skb,unsigned int headroom)
static inline struct tcphdr *tcp_hdr(const struct sk_buff *skb)
{
return (struct tcphdr *)skb_transport_header(skb);
}
static inline struct udphdr *udp_hdr(const struct sk_buff *skb)
{
return (struct udphdr *)skb_transport_header(skb);
}
sk_buff_head 等待队列头
struct sk_buff_head {
/* These two members must be first. */
struct sk_buff *next;
struct sk_buff *prev;
__u32 qlen;
spinlock_t lock;
};
net_device 网络设备
struct net_device {
// 网络设备名称,如 eth0、wlan0
char name[IFNAMSIZ];
// 用于将设备链接到按名称哈希的链表中
struct hlist_node name_hlist;
// 设备别名(可选)
char *ifalias;
/*
* I/O 特定字段
* FIXME: 这些字段和 struct ifmap 应该合并为一个
*/
// 共享内存结束地址
unsigned long mem_end;
// 共享内存起始地址
unsigned long mem_start;
// 设备I/O基地址
unsigned long base_addr;
// 设备中断请求号
int irq;
// 载波状态变化计数(原子操作)
atomic_t carrier_changes;
/*
* 一些硬件也需要这些字段(state, dev_list,
* napi_list, unreg_list, close_list),但它们不是
* Space.c 中指定的常规集合的一部分
*/
// 设备状态标志
unsigned long state;
// 所有网络设备的链表
struct list_head dev_list;
// NAPI(New API)结构链表,用于高性能网络处理
struct list_head napi_list;
// 待注销设备链表
struct list_head unreg_list;
// 待关闭设备链表
struct list_head close_list;
// 所有协议类型的数据包处理函数链表
struct list_head ptype_all;
// 特定协议类型的数据包处理函数链表
struct list_head ptype_specific;
// 上层和下层设备邻接链表
struct {
struct list_head upper;
struct list_head lower;
} adj_list;
// 设备支持的功能特性
netdev_features_t features;
// 硬件实际支持的功能特性
netdev_features_t hw_features;
// 用户期望启用的功能特性
netdev_features_t wanted_features;
// VLAN 相关功能特性
netdev_features_t vlan_features;
// 硬件加密功能特性
netdev_features_t hw_enc_features;
// MPLS 功能特性
netdev_features_t mpls_features;
// 部分GSO(Generic Segmentation Offload)功能特性
netdev_features_t gso_partial_features;
// 设备唯一标识索引号
int ifindex;
// 设备组标识
int group;
// 网络设备统计信息(收发包数量、错误等)
struct net_device_stats stats;
// 接收时丢弃的数据包计数(原子长整型)
atomic_long_t rx_dropped;
// 发送时丢弃的数据包计数(原子长整型)
atomic_long_t tx_dropped;
// 无处理程序的接收数据包计数
atomic_long_t rx_nohandler;
// 无线扩展支持
#ifdef CONFIG_WIRELESS_EXT
const struct iw_handler_def *wireless_handlers;
struct iw_public_data *wireless_data;
#endif
// 网络设备操作函数指针
const struct net_device_ops *netdev_ops;
// ethtool 操作函数指针
const struct ethtool_ops *ethtool_ops;
// 网络交换机设备支持
#ifdef CONFIG_NET_SWITCHDEV
const struct switchdev_ops *switchdev_ops;
#endif
// 三层主设备支持
#ifdef CONFIG_NET_L3_MASTER_DEV
const struct l3mdev_ops *l3mdev_ops;
#endif
// IPv6 支持
#if IS_ENABLED(CONFIG_IPV6)
const struct ndisc_ops *ndisc_ops;
#endif
// XFRM 支持(IPSec)
#ifdef CONFIG_XFRM
const struct xfrmdev_ops *xfrmdev_ops;
#endif
// 数据包头操作函数
const struct header_ops *header_ops;
// 标准设备标志
unsigned int flags;
// 内部使用的设备标志
unsigned int priv_flags;
// 全局标志
unsigned short gflags;
// 填充字段,用于对齐
unsigned short padded;
// 设备操作状态
unsigned char operstate;
// 链接模式
unsigned char link_mode;
// 接口端口类型
unsigned char if_port;
// DMA 通道
unsigned char dma;
// 最大传输单元(MTU)
unsigned int mtu;
// 最小MTU
unsigned int min_mtu;
// 最大MTU
unsigned int max_mtu;
// 接口硬件类型
unsigned short type;
// 硬件头部长度
unsigned short hard_header_len;
// 最小头部长度
unsigned char min_header_len;
// 需要预留的头部空间
unsigned short needed_headroom;
// 需要预留的尾部空间
unsigned short needed_tailroom;
/* 接口地址信息 */
// 永久硬件地址(MAC)
unsigned char perm_addr[MAX_ADDR_LEN];
// 地址分配类型
unsigned char addr_assign_type;
// 地址长度
unsigned char addr_len;
// 邻居结构私有数据长度
unsigned short neigh_priv_len;
// 设备标识
unsigned short dev_id;
// 设备端口号
unsigned short dev_port;
// 地址列表锁
spinlock_t addr_list_lock;
// 名称分配类型
unsigned char name_assign_type;
// 是否启用单播混杂模式
bool uc_promisc;
// 单播地址列表
struct netdev_hw_addr_list uc;
// 多播地址列表
struct netdev_hw_addr_list mc;
// 设备地址列表
struct netdev_hw_addr_list dev_addrs;
// Sysfs 支持
#ifdef CONFIG_SYSFS
struct kset *queues_kset;
#endif
// 混杂模式计数
unsigned int promiscuity;
// 全多播模式计数
unsigned int allmulti;
/* 协议特定指针 */
// VLAN 802.1Q 支持
#if IS_ENABLED(CONFIG_VLAN_8021Q)
struct vlan_info __rcu *vlan_info;
#endif
// 分布式交换机架构支持
#if IS_ENABLED(CONFIG_NET_DSA)
struct dsa_switch_tree *dsa_ptr;
#endif
// TIPC 协议支持
#if IS_ENABLED(CONFIG_TIPC)
struct tipc_bearer __rcu *tipc_ptr;
#endif
// AppleTalk 协议指针
void *atalk_ptr;
// IPv4 协议指针
struct in_device __rcu *ip_ptr;
// DECnet 协议指针
struct dn_dev __rcu *dn_ptr;
// IPv6 协议指针
struct inet6_dev __rcu *ip6_ptr;
// AX.25 协议指针
void *ax25_ptr;
// 无线设备指针
struct wireless_dev *ieee80211_ptr;
// WPAN 设备指针
struct wpan_dev *ieee802154_ptr;
// MPLS 路由支持
#if IS_ENABLED(CONFIG_MPLS_ROUTING)
struct mpls_dev __rcu *mpls_ptr;
#endif
/*
* 主要用于接收路径的缓存行(包括 eth_type_trans())
*/
// eth_type_trans() 中使用的接口地址信息
unsigned char *dev_addr;
// Sysfs 支持
#ifdef CONFIG_SYSFS
struct netdev_rx_queue *_rx;
// 接收队列数量
unsigned int num_rx_queues;
// 实际使用的接收队列数量
unsigned int real_num_rx_queues;
#endif
// XDP(eXpress Data Path)程序
struct bpf_prog __rcu *xdp_prog;
// GRO(Generic Receive Offload)刷新超时时间
unsigned long gro_flush_timeout;
// 接收处理函数
rx_handler_func_t __rcu *rx_handler;
// 接收处理函数数据
void __rcu *rx_handler_data;
// 网络分类器操作支持
#ifdef CONFIG_NET_CLS_ACT
struct tcf_proto __rcu *ingress_cl_list;
#endif
// 入口队列
struct netdev_queue __rcu *ingress_queue;
// Netfilter 入口钩子支持
#ifdef CONFIG_NETFILTER_INGRESS
struct nf_hook_entry __rcu *nf_hooks_ingress;
#endif
// 广播地址
unsigned char broadcast[MAX_ADDR_LEN];
// RFS(Receive Flow Steering)加速支持
#ifdef CONFIG_RFS_ACCEL
struct cpu_rmap *rx_cpu_rmap;
#endif
// 用于将设备链接到按索引哈希的链表中
struct hlist_node index_hlist;
/*
* 主要用于发送路径的缓存行
*/
// 发送队列数组
struct netdev_queue *_tx ____cacheline_aligned_in_smp;
// 发送队列数量
unsigned int num_tx_queues;
// 实际使用的发送队列数量
unsigned int real_num_tx_queues;
// 队列规则结构(Qdisc)
struct Qdisc *qdisc;
// 网络调度支持
#ifdef CONFIG_NET_SCHED
DECLARE_HASHTABLE (qdisc_hash, 4);
#endif
// 发送队列长度
unsigned long tx_queue_len;
// 全局发送锁
spinlock_t tx_global_lock;
// 看门狗超时时间
int watchdog_timeo;
// XPS(Transmit Packet Steering)支持
#ifdef CONFIG_XPS
struct xps_dev_maps __rcu *xps_maps;
#endif
// 网络分类器操作支持(出口方向)
#ifdef CONFIG_NET_CLS_ACT
struct tcf_proto __rcu *egress_cl_list;
#endif
/* 这些字段可能用于未来的网络功耗管理代码 */
// 看门狗定时器
struct timer_list watchdog_timer;
// 每CPU引用计数
int __percpu *pcpu_refcnt;
// 待处理操作列表
struct list_head todo_list;
// 链接监视列表
struct list_head link_watch_list;
// 设备注册状态
enum { NETREG_UNINITIALIZED=0,
NETREG_REGISTERED, /* 已完成 register_netdevice */
NETREG_UNREGISTERING, /* 已调用 unregister_netdevice */
NETREG_UNREGISTERED, /* 已完成未注册的待办事项 */
NETREG_RELEASED, /* 已调用 free_netdev */
NETREG_DUMMY, /* 用于NAPI轮询的虚拟设备 */
} reg_state:8;
// 是否正在拆除设备
bool dismantle;
// RTNL 链接状态
enum {
RTNL_LINK_INITIALIZED,
RTNL_LINK_INITIALIZING,
} rtnl_link_state:16;
// 是否需要释放网络设备
bool needs_free_netdev;
// 私有数据析构函数
void (*priv_destructor)(struct net_device *dev);
// 网络轮询支持
#ifdef CONFIG_NETPOLL
struct netpoll_info __rcu *npinfo;
#endif
// 所属网络命名空间
possible_net_t nd_net;
/* 中间层私有数据 */
union {
void *ml_priv;
struct pcpu_lstats __percpu *lstats;
struct pcpu_sw_netstats __percpu *tstats;
struct pcpu_dstats __percpu *dstats;
struct pcpu_vstats __percpu *vstats;
};
// GARP 支持
#if IS_ENABLED(CONFIG_GARP)
struct garp_port __rcu *garp_port;
#endif
// MRP 支持
#if IS_ENABLED(CONFIG_MRP)
struct mrp_port __rcu *mrp_port;
#endif
// 关联的设备结构
struct device dev;
// Sysfs 属性组
const struct attribute_group *sysfs_groups[4];
// 接收队列的Sysfs属性组
const struct attribute_group *sysfs_rx_queue_group;
// RTNL 链接操作
const struct rtnl_link_ops *rtnl_link_ops;
/* 用于在TCP连接设置时设置内核套接字属性 */
// 最大GSO大小
#define GSO_MAX_SIZE 65536
unsigned int gso_max_size;
// 最大GSO段数
#define GSO_MAX_SEGS 65535
u16 gso_max_segs;
// 数据中心桥接支持
#ifdef CONFIG_DCB
const struct dcbnl_rtnl_ops *dcbnl_ops;
#endif
// 流量类别数量
u8 num_tc;
// 流量类别到发送队列的映射
struct netdev_tc_txq tc_to_txq[TC_MAX_QUEUE];
// 优先级到流量类别的映射
u8 prio_tc_map[TC_BITMASK + 1];
// FCoE 支持
#if IS_ENABLED(CONFIG_FCOE)
unsigned int fcoe_ddp_xid;
#endif
// 网络优先级cgroup支持
#if IS_ENABLED(CONFIG_CGROUP_NET_PRIO)
struct netprio_map __rcu *priomap;
#endif
// PHY 设备指针
struct phy_device *phydev;
// Qdisc 发送繁忙锁的类键
struct lock_class_key *qdisc_tx_busylock;
// Qdisc 运行键
struct lock_class_key *qdisc_running_key;
// 协议关闭状态
bool proto_down;
};
struct net_device {
// 网络设备名称,如 eth0、wlan0
char name[IFNAMSIZ];
// 用于将设备链接到按名称哈希的链表中
struct hlist_node name_hlist;
// 设备别名(可选)
char *ifalias;
/*
* I/O 特定字段
* FIXME: 这些字段和 struct ifmap 应该合并为一个
*/
// 共享内存结束地址
unsigned long mem_end;
// 共享内存起始地址
unsigned long mem_start;
// 设备I/O基地址
unsigned long base_addr;
// 设备中断请求号
int irq;
// 载波状态变化计数(原子操作)
atomic_t carrier_changes;
/*
* 一些硬件也需要这些字段(state, dev_list,
* napi_list, unreg_list, close_list),但它们不是
* Space.c 中指定的常规集合的一部分
*/
// 设备状态标志
unsigned long state;
// 所有网络设备的链表
struct list_head dev_list;
// NAPI(New API)结构链表,用于高性能网络处理
struct list_head napi_list;
// 待注销设备链表
struct list_head unreg_list;
// 待关闭设备链表
struct list_head close_list;
// 所有协议类型的数据包处理函数链表
struct list_head ptype_all;
// 特定协议类型的数据包处理函数链表
struct list_head ptype_specific;
// 上层和下层设备邻接链表
struct {
struct list_head upper;
struct list_head lower;
} adj_list;
// 设备支持的功能特性
netdev_features_t features;
// 硬件实际支持的功能特性
netdev_features_t hw_features;
// 用户期望启用的功能特性
netdev_features_t wanted_features;
// VLAN 相关功能特性
netdev_features_t vlan_features;
// 硬件加密功能特性
netdev_features_t hw_enc_features;
// MPLS 功能特性
netdev_features_t mpls_features;
// 部分GSO(Generic Segmentation Offload)功能特性
netdev_features_t gso_partial_features;
...
// 标准设备标志
unsigned int flags;
// 内部使用的设备标志
unsigned int priv_flags;
// 全局标志
unsigned short gflags;
// 填充字段,用于对齐
unsigned short padded;
// 设备操作状态
unsigned char operstate;
// 链接模式
unsigned char link_mode;
// 接口端口类型
unsigned char if_port;
// DMA 通道
unsigned char dma;
// 最大传输单元(MTU)
unsigned int mtu;
// 最小MTU
unsigned int min_mtu;
// 最大MTU
unsigned int max_mtu;
// 接口硬件类型
unsigned short type;
// 硬件头部长度
unsigned short hard_header_len;
// 最小头部长度
unsigned char min_header_len;
...
// 是否启用单播混杂模式
bool uc_promisc;
// 单播地址列表
struct netdev_hw_addr_list uc;
// 多播地址列表
struct netdev_hw_addr_list mc;
// 设备地址列表
struct netdev_hw_addr_list dev_addrs;
// Sysfs 支持
#ifdef CONFIG_SYSFS
struct kset *queues_kset;
#endif
// 混杂模式计数
unsigned int promiscuity;
// 全多播模式计数
unsigned int allmulti;
...
/*
* 主要用于接收路径的缓存行(包括 eth_type_trans())
*/
// eth_type_trans() 中使用的接口地址信息
unsigned char *dev_addr;
...
// 广播地址
unsigned char broadcast[MAX_ADDR_LEN];
...
};
-
设备 IRQ
-
设备 MTU
-
设备 MAC
-
设备名称(eht1,eth0)
-
设备标志(up,down)
-
设备相关联的组播地址清单
-
设备支持的功能
-
网络设备回调函数的对象(net_device_ops)
-
设备最后一次发送数据包的时间戳
-
设备最后一次接收数据包的时间戳
网络设备(注册函数 注销函数)
int register_netdevice(struct net_device *dev)
static inline void unregister_netdevice(struct net_device *dev)
全局变量 dev_base 存储 所有设备的 net_device
- next 指针 连接系统所有网络设备,组成一个链表。
NAPI
网络设备中NAPI,老式网络设备驱动程序是在中断驱动模式下工作,意味着每接收一个数据包,就需要中断一次,事实证明此工作方式在负载很高情况下效率降低,为此解决这个问题,开发一种新的软件技术->NAPI(NewAPI)。采用NAPler技术时,如果负载很高,网络设备驱动程序将在轮询模式,而不是中断驱动模式下运行。
数据包的收发:网络设备驱动程序主要任务:接收目的地为当前主机的数据包,并将其传递给网络层,之后再将其传递给传输层。传输当前主机生成的外出数据包或转换当前主机收到数据包。对于每个数据包,无论它是接收到还是发送出去,都需要在路由子系统中执行一次查找操作。
当数据包位于网络设备驱动程序接收路径的L2时,skb->data指向的是L2(以以太网)报头;调用方法 eth_type_trans() 后,数据包即将进入第3层,因此skb->data应指向网络层(L3)报头,而这个报头紧跟在以太网报头后面。
每个 SKB 都有一个 dev 成员(net_device),对于到来的数据包,表示接收它的网络设备;对于外出的数据包,表示发送它的网络设备。
Linux邻接子系统
Linux邻接子系统负责发现当前链路上的结点,并且将L3(网络层)地址转换为L2(数
据链路层)地址。在IPv4当中,实现这种转换的协议为地址解析协(AddressResolution
Protocol,ARP),而在IPv6则为邻居发现协议(Neighbour Discovery Protocol,NDISC 或 ND),邻接子系统为执行L3到L2映射提供了独立于协议的基础设施。
在IPV4中,使用邻接协议为ARP,相应的请求和应答分别被称为ARP请求和ARP应答;在IPV6中,使用的邻接协议为NDISC,相应的请求和应答分别称为邻居请求和邻居通告。
NDISC 协议避免重复 IPv4 地址。
不需要邻接子系统的情况?
发送广播,L2 的目的地址固定。在以太网为 FF:FF:FF:FF:FF:FF;
发送组播,L3 组播地址和 L2 组播地址 映射关系固定。
struct neighbour
neigh_table
arp_rcv 处理 ARP 数据包
neigh_ops 定义回调
邻居 创建 / 删除
邻居创建 __neigh_create
邻居删除 __neigh_release
在方法_neigh_create() 的最后,将dead标志初始化为0,并将邻居对象添加到邻居散列表中,方法neigh_release() 将邻居的引用计数器减1。如果它变成0,调用方法neigh_destr() 将邻居对象释放。
添加 / 删除邻居条目
ARP 协议
ARP 协议头 arphdr
紧跟在 ar_op 后面的是发送方的硬件(MAC)地址和IPv4地址,以及目标硬件(MAC)地址和IPv4地址。这些地址并非ARP报头(结构arphdr)的组成部分。我们可以在方法arp_process() 中,通过读取ARP报头相应的偏移量来提取它们。
ARP 发送/接收
在方法 arp_process() 中,只处理ARP请求和ARP响应操作。对ARP请求,将使用方法ip_route_input_noref() 执行路由选择子系统查找。如果ARP数据包是发送给当前主机的(路由选择条目的 rt_type 为 RTN_LOCAL ),就接着检查一些条件。如果所有检查全部通过,就使用方法 arp_send() 发回ARP应答。
网络层 IP
IP 首部
版本(version):指定所用IP协议的版本。该字段有效值为4或6。
IP首部长度(IHL):定义首部的长度,由于选项数量可变。
代码点/服务类型:用于更复杂协议选项。
长度:指定分组的总长度,首部加数据的长度。
分片标识:标识一个分片的IP分组的各个部分。
TTL:指定从发送者到接收者的传输路径上中间站点的最大数目。
Proctocol标识:IP分组承载的高层协议(传输层)。
检验和:根据首部和数据的内容计算。
IP首部所有的数值都以网络字节序存储(大端序)。
struct iphdr
网络层 Netfiliter
接收分组
- ip_rcv()函数入口
- netfilter NE_IP_PRE_ROUTING 挂钩
- ip_route_input() 选择路由
- ip_local_deliver() / ip_forward()
ip_rcv
- 更新 SNMP
- 确保 skb->data 满足 IP 头部大小
- 检验 IP 首部长度 及 协议版本
- 检验 IP 头部校验和
- 设置 skb->transport_header TCP 报头指针
- skb_orphan(skb)
- HF_HOOK 执行 PRE_ROUTING 筛选,在 PER_ROUTING 上传注册所有 HOOK。执行 ip_rcv_finish 函数,继续执行路由等处理。如果是本地,上层处理;否则 FORWARD
ip_local_deliver
- IP 报文组装
- NF_HOOK,执行 NF_INET_LOCAL_IN,实现 iptables 功能。最后调用ip_local_deliver_finish
ip_local_deliver_finish
- 报文头移动到 传输层头
- 得到 Ip 头中协议类型 (4 层)
- raw socket 处理
- 上次处理报文 TCP/UDP/ICMP
- 如果一个导致错误的数据包是发给 RAW 套接字的,那么后续处理(包括是否响应)通常应由应用程序自身决定,内核无需过多干预。反之,ICMP 消息用于报告网络错误和查询状态。
RAW 套接字
这是一种特殊的网络套接字,允许应用程序直接访问底层的数据包,绕过内核的大部分协议栈处理(例如,用于实现 ping或自定义协议)。
ip_queue_xmit
完成面向连接套接字 (TCP)的包输出,当套接字处理连接状态,所有从套接字发出的包都具有指定的路由,无需为每一个输出包查询它的目的入口,可将套接字直接绑定到路由入口上,这由套接字的目的缓存指针 dst_cache 完成
ip_queue_xmit 首先为输入包 建立 IP 报头,经过本地包过滤器后,再将 IP 包分片输出(ip_fragment)。由 IP 层提供给 TCP 层发送回调。
ip_build_and_send_pkt 函数是服务器端在给客户都安恢复 syn+ack 时调用的,该函数在构造 ip 头之后,调用 ip_local_out 发送数据包。
ip_output
ip_forward
ipforward流程主要功能:根据报文信息得到路由、ipset安全检测、转发的基本逻辑,IP层提交本地处理的流程等,根据IP地址决定是提交给本处理,还是报文转的,报文转发的入口函数ip_forward。
ip_forward_finish
dst_entry
套接字缓冲区 dst 成员指向 dst_entry 实例
路由查找期间填充
- 路由查询与 dst_entry创建:
当数据包进入网络层(ip_rcv) 或本地套接字准备发送数据时,内核会进行路由查询(例如,通过 ip_route_input_rcu用于输入,ip_route_output_flow用于输出)。查询结果(如目的地址、输出设备、下一跳网关等信息)会被封装到一个 dst_entry(或其更具体的子类,如 IPv4 的 rtable)中,并关联到该数据包对应的 sk_buff结构体(skb_dst_set(skb, dst))。
- 指导数据包处理:
dst_entry的核心是其 input 和 output 函数指针,它们根据路由查询结果被设置为不同的处理函数,从而决定数据包的命运:
- 对于接收到的数据包(ip_rcv):
-
- 若路由查询确定数据包是发给本机的,则 dst.input 被设置为 ip_local_deliver。后续协议栈会调用 dst_input(skb),其内部最终执行 skb_dst(skb)->input(skb),将数据包上交传输层(TCP/UDP)。
- 若确定数据包需要被转发,则 dst.input被设置为 ip_forward。dst_input(skb)会触发转发流程。
- 对于本地产生的数据包(准备发送):
-
- dst.output通常初始设置为 ip_output函数。在发送路径上,该函数会与邻居子系统协作,最终将数据包发送出去。
- 与邻居子系统的协作:
dst_entry通常包含一个指向 struct neighbour的指针(或能通过它找到邻居信息)。neighbour结构负责管理下一跳的链路层地址(如通过 ARP 获取 IPv4 对应的 MAC 地址)。当 dst.output函数(如 ip_output)被调用时,它会借助邻居子系统(例如调用 neigh_output)来解析或使用该链路层地址,最终通过 dev_queue_xmit将数据包放入指定网络设备的发送队列。
- 生命周期与销毁:
dst_entry使用引用计数(__refcnt)来管理其生命周期。当一个 dst_entry被关联到 sk_buff时,其引用计数会增加。当数据包处理完毕、不再需要该路由信息时,会通过类似 skb_dst_drop的函数减少引用计数。当引用计数降为零时,内核才会安全地销毁并释放该 dst_entry结构所占用的内存,防止内存泄漏。
内核 Netlink 套接字
Netlink
事实上最常用的是afnetlink模块,它提供netlink内核套接字APl,而genetlink模块提供了新的通用netlinkAPI。监视接口模块diag(diag.c)提供的API用于读写有关的netlink套接字信息。从理论原则来讲,netlink套接字可用于在用户空间进程间通信(包括发送组播消息),但通常不这样做,而且这也不是开发netlink套接字初衷。UNIX域套接字提供用于IPC的API,被广泛用于两个用户空间进行间的通信。
优势:
-
不需要轮询,recvmsg 的时候,如果没有内核消息,进入阻塞状态
-
内核主动向用户空间发送异步消息,不需要用户空间触发;
-
netlink 支持组播
-
通过系统调用 socket 创建 netlink 套接字。SOCK_RAW / SOCK_DGRAM
创建 Netlink 套接字
用户空间:
- socket
内核:
- netlink_kernel_create() -> netlink_create()
struct sockaddr_nl
控制 TCP/IP 联网的用户空间包
net-tools iproute2
ip:用于管理网络表和网络接口
tc:用于流量控制管理
ss:用于转储套接字统计信息
Instat:用于转储Linux网络统计信息
brigde:用于管理网桥地址和设备
iproute2 包主要基于通过netlink套接字从用户空间向内核发送请示并获取应答。
net-tools 包是基于IOCTL的,也有些主要命令:
ifconfig
arp
route
netstat
hostname
netlink 套接字 创建 / 注册 / 数据包发送
netlink_kernel_create
rtnl_register
发送数据包 nlmsg_notify
netlink 数据包 报头 16 字节
netlink 消息类型 nlattr
nlattr是 Netlink 消息体的基本单元,采用类型 - 长度 - 值(TLV)格式,允许在不修改协议本身的情况下动态扩展消息内容。
路由选择 添加/删除
添加:ip route add 参数
删除:ip route del ip 地址
使用 iproute2 命令 ip 来监视网络事件:ip monitor route
创建/发送 通用 Netlink 消息
genlmsg_nulticast():此方法将消息发送到默认网络命名空间net_init()
genlmsg_multicast_allns():此方法将消息发送到所有网络命名空间。
套接字监控接口
netlink套接字sockdiag提供一个基于netlink子系统,可用于获取有关套接字的信息,在内核中添加它是指在Linux用户空间中支持检查点/恢复功能(CRIU)。要支持这项功能,还需要有关套接字的其他数据。
Netlink 实战
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>
#define NETLINK_TEST 30
#define MSG_LEN 125
#define USER_PORT 100
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yang");
MODULE_DESCRIPTION("netlink protocol example");
struct sock *nlsk = NULL;
extern struct net init_net;
int send_usrmsg(char *pbuf, uint16_t len)
{
struct sk_buff *nl_skb;
struct nlmsghdr *nlh;
int ret;
/* 创建sk_buff 空间 */
nl_skb = nlmsg_new(len, GFP_ATOMIC);
if(!nl_skb)
{
printk("\nError:netlink alloc failure.\n\n");
return -1;
}
/* 设置netlink消息头部 */
nlh = nlmsg_put(nl_skb, 0, 0, NETLINK_TEST, len, 0);
if(nlh == NULL)
{
printk("\nError:nlmsg_put failaure. \n\n");
nlmsg_free(nl_skb);
return -1;
}
/* 拷贝数据发送 */
memcpy(nlmsg_data(nlh), pbuf, len);
ret = netlink_unicast(nlsk, nl_skb, USER_PORT, MSG_DONTWAIT);
return ret;
}
static void netlink_rcv_msg(struct sk_buff *skb)
{
struct nlmsghdr *nlh = NULL;
char *umsg = NULL;
char *kmsg = "Hello vico users program.";
if(skb->len >= nlmsg_total_size(0))
{
nlh = nlmsg_hdr(skb);
umsg = NLMSG_DATA(nlh);
if(umsg)
{
printk("kernel recv from user: %s\n", umsg);
send_usrmsg(kmsg, strlen(kmsg));
}
}
}
struct netlink_kernel_cfg cfg = {
.input = netlink_rcv_msg, /* set recv callback */
};
int test_netlink_init(void)
{
/* create netlink socket */
nlsk = (struct sock *)netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
if(nlsk == NULL)
{
printk("\nError:netlink_kernel_create error !\n");
return -1;
}
printk("\ntest_netlink_init\n");
return 0;
}
void test_netlink_exit(void)
{
if (nlsk){
netlink_kernel_release(nlsk); /* release ..*/
nlsk = NULL;
}
printk("test_netlink_exit!\n");
}
module_init(test_netlink_init);
module_exit(test_netlink_exit);
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <string.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <unistd.h>
#include <errno.h>
#define NETLINK_TEST 30
#define MSG_LEN 125
#define MAX_PLOAD 125
typedef struct _user_msg_info
{
struct nlmsghdr hdr;
char msg[MSG_LEN];
} user_msg_info;
int main(int argc, char **argv)
{
int skfd;
int ret;
user_msg_info u_info;
socklen_t len;
struct nlmsghdr *nlh = NULL;
struct sockaddr_nl saddr, daddr;
char *umsg = "Hello Netlink protocol.";
/* 创建NETLINK socket */
skfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
if(skfd == -1)
{
perror("\nError:Create socket error.\n");
return -1;
}
memset(&saddr, 0, sizeof(saddr));
saddr.nl_family = AF_NETLINK; //AF_NETLINK
saddr.nl_pid = 100; //端口号(port ID)
saddr.nl_groups = 0;
if(bind(skfd, (struct sockaddr *)&saddr, sizeof(saddr)) != 0)
{
perror("\nError:bind() error.\n");
close(skfd);
return -1;
}
memset(&daddr, 0, sizeof(daddr));
daddr.nl_family = AF_NETLINK;
daddr.nl_pid = 0; // to kernel
daddr.nl_groups = 0;
nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
memset(nlh, 0, sizeof(struct nlmsghdr));
nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD);
nlh->nlmsg_flags = 0;
nlh->nlmsg_type = 0;
nlh->nlmsg_seq = 0;
nlh->nlmsg_pid = saddr.nl_pid; //self port
memcpy(NLMSG_DATA(nlh), umsg, strlen(umsg));
ret = sendto(skfd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&daddr, sizeof(struct sockaddr_nl));
if(!ret)
{
perror("\nError:sendto error.\n");
close(skfd);
exit(-1);
}
printf("\nApplication-->Send kernel:%s\n\n", umsg);
memset(&u_info, 0, sizeof(u_info));
len = sizeof(struct sockaddr_nl);
ret = recvfrom(skfd, &u_info, sizeof(user_msg_info), 0, (struct sockaddr *)&daddr, &len);
if(!ret)
{
perror("\nError:recv form kernel error.\n");
close(skfd);
exit(-1);
}
printf("\nApplication-->From kernel:%s\n\n", u_info.msg);
close(skfd);
free((void *)nlh);
return 0;
}
prj_n := netlink_kmod
obj-m += ${prj_n}.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
test:
dmesg -C
insmod lkm_example.ko
rmmod lkm_example.ko
dmesg
网络层 TCP/UDP
套接字
- SOCK_STREAM(流套接字):提供可靠的字节流通信信道。TCP套接字就属于流套接字。
- SOCK_DGRAM(数据报套接字):支持消息交换。数据报套接字提供的通信信道不可靠,因为数据包可能被丢弃、不按顺序到达或重复。UDP套接字属于数据报套接字。
- SOCK_RAW(原始套接字):直接访问IP层,支持使用协议无关的传输层格式收发数据流。
- SOCK_RDM(可靠传输的消息):用于透明进程间通信(TIPC)
- SOCK_SEQPACKET(顺序数据包流):这种套接字类似于SOCK_STREAM,也是面向连接的。
- SOCK_DCCP套接字:数据报拥塞控制协议是一种传输层协议,提供不可靠数据报拥塞控制流。
套接字 API
socket() :用于创建一个套接字sys_socket() ;
bind():将套接字与本地端口和IP地址关联sys_bind();
send():发送消息
recv():接收消息
listen():能够让套接字接收来自其他套接字的连接请求;
accept():接受套接字连接请求,仅适用于基于连接的套接字类型SOCK_STREAM/SOCK_SEQPACKET;
connect():建立到对等套接字的连接。仅适用于基于连接的套接字类型(SOCK_STREAM/SOCK_SEQPACKET)以及无连接的套接字类型(SOCK_DGRAM)。
创建套接字 struct socket
- sys_socket() 创建
- struct sock 位于网络层,协议无关结构
struct sock
- 入站数据包队列
- 接收缓冲区大小
- 发送缓冲区大小
- 出站数据包队列
- 协议标识符
- 套接字类型
struct msghdr
从用户空间套接字发送数据或在用户空间套接字中接收来自传输层的数据,这些工作分配是通信内核中调用方法 sendmsg() /recvmsg() 来处理。它们会将一个msghdr对象作为参数,这个msghdr对象包含要发送或填充的数据块及其它参数。
struct udphdr UDP 报头
UDP 初始化
UDP初始化操作,定义对象udp_protocol(net_protocol对象)并使用方法inet_add_protocolo来添加它,具体源码如下:
udp_sendmsg 发送 UDP 数据包
- udp 数据包通常会立即发送