赞
踩
start_kernel
函数位于 init/main.c
文件中,是内核启动的入口函数。- asmlinkage __visible void __init start_kernel(void)
- {
- // ...
-
- // 调用 rest_init 函数
- rest_init();
-
- // ...
- }
rest_init
函数位于 kernel/init/main.c
文件中,完成一些初始化工作,并通过 kernel_thread
函数创建一个新的内核线程。- static void __init rest_init(void)
- {
- // ...
-
- // 创建一个新的内核线程,并指定其执行函数为 kernel_init
- kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
-
- // ...
- }
kernel_init
函数位于 init/main.c
文件中,是内核初始化的第一个阶段。- static int __init kernel_init(void *unused)
- {
- // ...
-
- // 调用 do_basic_setup 函数
- do_basic_setup();
-
- // ...
-
- // 调用 do_initcalls 函数执行内核初始化的第二个阶段
- do_initcalls();
-
- // ...
- }
do_basic_setup
函数位于 init/main.c
文件中,完成一些基本的系统初始化工作,并调用 do_initcalls
函数执行内核初始化的第二个阶段。- static void __init do_basic_setup(void)
- {
- // ...
-
- // 调用 do_initcalls 函数执行内核初始化的第二个阶段
- do_initcalls();
-
- // ...
- }
do_initcalls
函数位于 init/main.c
文件中,会调用 do_one_initcall
函数多次,以执行一系列的初始化函数。- static void __init do_initcalls(void)
- {
- // ...
-
- // 调用 do_one_initcall 函数,执行一系列的初始化函数
- do_one_initcall(inet_init);
-
- // ...
- }
inet_init
函数位于 net/ipv4/af_inet.c
文件中,是网络子系统中 IPv4 协议族的初始化函数。它执行了一些与 IPv4 相关的初始化工作。- static int __init inet_init(void)
- {
- // ...
-
- // 执行 IPv4 相关的初始化工作
-
- // ...
-
- return 0;
- }
因此,start_kernel
-> rest_init
-> kernel_init
-> do_basic_setup
-> do_initcalls
-> do_one_initcall
-> inet_init
是 inet_init
函数从 start_kernel
到 inet_init
的调用路径。
在 Linux 内核中,TCP/IP 协议栈通过网络套接口(socket)与上层应用程序和下层数据链路层进行关联。
- // 创建套接字
- int sockfd = socket(AF_INET, SOCK_STREAM, 0);
-
- // 绑定套接字到本地地址和端口
- struct sockaddr_in addr;
- addr.sin_family = AF_INET;
- addr.sin_port = htons(1234);
- addr.sin_addr.s_addr = htonl(INADDR_ANY);
- bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
-
- // 监听连接请求
- listen(sockfd, SOMAXCONN);
socket
系统调用创建了一个 TCP 套接字,并返回相应的文件描述符。bind
系统调用将套接字绑定到本地地址和端口。之后,通过 listen
系统调用开始监听连接请求。
- // 接受连接请求
- struct sockaddr_in client_addr;
- socklen_t client_addrlen = sizeof(client_addr);
- int connfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_addrlen);
-
- // 发送和接收数据
- char buffer[1024];
- ssize_t num_bytes = recv(connfd, buffer, sizeof(buffer), 0);
- send(connfd, buffer, num_bytes, 0);
accept
系统调用接受连接请求,并返回一个新的文件描述符 connfd
,用于与客户端进行通信。通过 recv
和 send
系统调用,可以在套接字之间传输数据。
- // 发送数据帧
- struct ethhdr eth_header;
- struct iphdr ip_header;
- struct tcphdr tcp_header;
- // 构建以太网头部、IP 头部和 TCP 头部
-
- struct sk_buff *skb = alloc_skb(total_len, GFP_KERNEL);
- // 分配 sk_buff 结构体
-
- // 将数据帧封装到 skb
- memcpy(skb_put(skb, sizeof(eth_header)), ð_header, sizeof(eth_header));
- memcpy(skb_put(skb, sizeof(ip_header)), &ip_header, sizeof(ip_header));
- memcpy(skb_put(skb, sizeof(tcp_header)), &tcp_header, sizeof(tcp_header));
-
- // 发送 skb
- dev_queue_xmit(skb);
在上述示例中,通过构建以太网头部、IP 头部和 TCP 头部,将数据帧封装到一个 sk_buff 结构体中。然后,使用 dev_queue_xmit
函数将 skb 发送到网络设备队列中,以便进一步处理和发送到物理链路。
在 TCP 的三次握手过程中,服务器端(被动打开方)负责设置和发送 SYN/ACK 报文。
- // 文件:net/ipv4/tcp_ipv4.c
- // 函数:tcp_v4_rcv
- // 位置:约在 3500 行附近
-
- // 处理接收到的 SYN 报文段
- static void tcp_v4_rcv(struct sk_buff *skb)
- {
- // ...
-
- // 检查报文段是否是 SYN 报文
- if (th->syn) {
- // ...
-
- // 创建一个新的连接请求
- struct tcp_sock *newtp = tcp_create_openreq_child(sk, skb);
- if (newtp) {
- // 设置新连接请求的状态和序列号等参数
- tcp_init_sock(newtp, req->rsk_listener);
-
- // 设置 SYN/ACK 报文的标志
- tcp_initialize_rcv_mss(newtp);
- tcp_synack_rtt_meas(newtp, req);
-
- // 构建 SYN/ACK 报文
- tcp_send_synack(newtp, req);
-
- // 发送 SYN/ACK 报文
- tcp_event_data_recv(newtp, skb);
-
- // ...
- }
- }
-
- // ...
- }
在上述代码中,tcp_v4_rcv
函数用于处理接收到的 TCP 报文。当接收到的报文段的 SYN 标志被设置时,表示这是一个连接请求的 SYN 报文。在这种情况下,内核会根据接收到的报文创建一个新的连接请求,并通过调用 tcp_send_synack
函数设置并发送 SYN/ACK 报文。
在 TCP 的三次握手过程中,涉及到多个状态的转换,其中包括 SYN_SENT、SYN_RECV、ESTABLISHED 等。
- // 文件:net/ipv4/tcp_ipv4.c
- // 函数:tcp_v4_syn_recv_sock
- // 位置:约在 3600 行附近
-
- // 创建 SYN_RECV 状态的套接字
- static struct sock *tcp_v4_syn_recv_sock(struct sock *sk, struct sk_buff *skb,
- struct request_sock *req,
- struct dst_entry *dst)
- {
- // ...
-
- // 创建 SYN_RECV 状态的套接字
- newsk = tcp_create_openreq_child(sk, skb);
- if (newsk) {
- // 设置套接字的状态为 SYN_RECV
- newtp = tcp_sk(newsk);
- newtp->rx_opt.tstamp_ok = sysctl_tcp_timestamps;
- newtp->advmss = tcp_mtu_to_mss(newtp, dst_mtu(dst));
-
- // ...
-
- // 触发状态转换
- inet_csk(sk)->icsk_accept_queue.rskq_defer_accept = 1;
- inet_csk(newsk)->icsk_ulp_data = req->ulp_info;
-
- // ...
- }
-
- // ...
-
- return newsk;
- }
在上述代码中,tcp_v4_syn_recv_sock
函数用于创建 SYN_RECV 状态的套接字。在创建套接字后,通过设置套接字的状态和其他相关参数来触发状态转换。这里使用了 inet_csk(sk)->icsk_accept_queue.rskq_defer_accept
标志来延迟接受连接,从而将套接字状态从 SYN_SENT 转换为 SYN_RECV。
send
函数用于将数据发送到 TCP/IP 协议栈中的 TCP 套接字
send
函数:- // 文件:net/socket.c
- // 函数:sock_sendmsg
- // 位置:约在 3500 行附近
-
- ssize_t sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t len)
- {
- // ...
-
- // 调用协议族的 sendmsg 函数
- ssize_t ret = sock->ops->sendmsg(sock, msg, len);
-
- // ...
-
- return ret;
- }
sock_sendmsg
函数是用户层调用 send
函数的入口。它将调用协议族的 sendmsg
函数来处理数据的发送。
sendmsg
函数:- // 文件:net/ipv4/tcp.c
- // 函数:tcp_sendmsg
- // 位置:约在 8000 行附近
-
- ssize_t tcp_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
- {
- // ...
-
- // 将用户层的数据复制到发送缓冲区
- ret = memcpy_from_msg(data, msg, size);
-
- // ...
-
- // 调用核心的发送函数
- ret = tcp_sendmsg_locked(sk, msg, size);
-
- // ...
-
- return ret;
- }
tcp_sendmsg
函数被调用来处理 TCP 协议族的数据发送。它首先将用户层的数据复制到发送缓冲区,然后调用核心的发送函数 tcp_sendmsg_locked
进行实际的发送操作。
tcp_sendmsg_locked
:- // 文件:net/ipv4/tcp_output.c
- // 函数:tcp_sendmsg_locked
- // 位置:约在 1200 行附近
-
- ssize_t tcp_sendmsg_locked(struct sock *sk, struct msghdr *msg, size_t size)
- {
- // ...
-
- // 创建发送的 sk_buff
- skb = sock_alloc_send_skb(sk, size, msg->msg_flags & MSG_DONTWAIT, &err);
- if (!skb) {
- // 处理内存不足的情况
- goto failure;
- }
-
- // ...
-
- // 将用户层的数据复制到 sk_buff
- ret = memcpy_from_msg(skb_put(skb, size), msg, size);
-
- // ...
-
- // 调用 TCP/IP 协议栈的数据发送函数
- ret = tcp_transmit_skb(sk, skb, msg->msg_flags);
-
- // ...
-
- return ret;
- }
tcp_sendmsg_locked
函数首先通过调用 sock_alloc_send_skb
函数创建一个发送的 sk_buff 结构体,然后将用户层的数据复制到 sk_buff 中。最后,它调用 TCP/IP 协议栈的数据发送函数 tcp_transmit_skb
来实际发送数据。
recv
函数用于从 TCP/IP 协议栈中的 TCP 套接字接收数据。
recv
函数- // 文件:net/socket.c
- // 函数:sock_recvmsg
- // 位置:约在 4000 行附近
-
- ssize_t sock_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
- int flags)
- {
- // ...
-
- // 调用协议族的 recvmsg 函数
- ssize_t ret = sock->ops->recvmsg(sock, msg, size, flags);
-
- // ...
-
- return ret;
- }
sock_recvmsg
函数是用户层调用 recv
函数的入口。它将调用协议族的 recvmsg
函数来处理数据的接收。
调用 TCP 协议族的 recvmsg
函数:
- // 文件:net/ipv4/tcp.c
- // 函数:tcp_recvmsg
- // 位置:约在 10000 行附近
-
- ssize_t tcp_recvmsg(struct socket *sock, struct msghdr *msg, size_t size,
- int flags)
- {
- // ...
-
- // 调用核心的接收函数
- ret = tcp_recvmsg_locked(sk, msg, size, flags);
-
- // ...
-
- return ret;
- }
tcp_recvmsg
函数被调用来处理 TCP 协议族的数据接收。它调用核心的接收函数 tcp_recvmsg_locked
进行实际的接收操作。
tcp_recvmsg_locked
:- / 文件:net/ipv4/tcp.c
- // 函数:tcp_recvmsg_locked
- // 位置:约在 9800 行附近
-
- ssize_t tcp_recvmsg_locked(struct sock *sk, struct msghdr *msg, size_t size,
- int flags)
- {
- // ...
-
- // 从套接字接收数据
- copied = tcp_read_sock(sk, msg, size, flags);
-
- // ...
-
- return copied;
- }
tcp_recvmsg_locked
函数通过调用 tcp_read_sock
函数从套接字接收数据。
tcp_read_sock
函数:- // 文件:net/ipv4/tcp.c
- // 函数:tcp_read_sock
- // 位置:约在 9100 行附近
-
- static int tcp_read_sock(struct sock *sk, struct msghdr *msg, int len,
- int nonblock)
- {
- // ...
-
- // 从接收缓冲区读取数据
- copied = skb_copy_datagram_msg(skb, 0, msg, len);
-
- // ...
-
- return copied;
- }
tcp_read_sock
函数通过调用 skb_copy_datagram_msg
函数从接收缓冲区读取数据,并将数据复制到 msg
结构体中。
在 Linux 内核中,路由表用于确定将网络数据包从源地址传输到目标地址的路径。
- // 文件:include/net/route.h
- // 位置:约在 200 行附近
-
- struct rtable {
- // ...
-
- struct net_device *dst.dev; // 目标设备
- __be32 rt_dst; // 目标 IP 地址
- __be32 rt_gateway; // 网关 IP 地址
- unsigned int rt_pmtu; // 最大传输单元
- // ...
-
- struct fib_nh_common nhc;
- // ...
- };
rtable
结构体表示路由表的一条路由项。其中包含了目标设备、目标 IP 地址、网关 IP 地址、最大传输单元等信息。
路由表的初始化过程:
- // 文件:net/ipv4/route.c
- // 函数:ip_route_input_noref
- // 位置:约在 1000 行附近
-
- struct rtable *ip_route_input_noref(struct net *net, __be32 daddr,
- __be32 saddr, u8 tos, struct net_device *dev)
- {
- struct rtable *rt;
-
- // ...
-
- // 查找匹配的路由项
- rt = __ip_route_output_key(net, daddr, saddr, tos, dev);
- if (IS_ERR(rt)) {
- // 处理无法找到匹配路由项的情况
- return NULL;
- }
-
- // ...
-
- return rt;
- }
在上述代码中,ip_route_input_noref
函数用于在输入路径上查找匹配的路由项。它调用 __ip_route_output_key
函数来查找路由表中与目标 IP 地址最匹配的路由项。
- // 文件:net/ipv4/route.c
- // 函数:__ip_route_output_key
- // 位置:约在 2000 行附近
-
- struct rtable *__ip_route_output_key(struct net *net, __be32 daddr,
- __be32 saddr, u8 tos, struct net_device *dev)
- {
- struct rtable *rt;
-
- // ...
-
- // 遍历路由缓存进行查找
- rt = ip_route_cache_lookup(net, daddr, saddr, tos, dev);
- if (rt) {
- // 找到匹配的路由项
- return rt;
- }
-
- // ...
-
- // 如果缓存中没有找到匹配项,则调用路由查找函数
- rt = ip_route_output_key_hash(net, daddr, saddr, tos, dev, 0, 0);
-
- // ...
-
- return rt;
- }
在上述代码中,__ip_route_output_key
函数首先尝试从路由缓存中查找匹配的路由项。如果路由缓存中没有找到匹配项,则调用 ip_route_output_key_hash
函数进行实际的路由查找。
通过目的 IP 查询路由表获取下一跳 IP 地址的过程,可以通过以下步骤完成:
获取目的 IP 地址。
遍历路由表的条目,逐个比较目的 IP 地址与路由表中的目标地址。
对于每个路由表条目,检查是否匹配目的 IP 地址。匹配通常涉及子网掩码和最长前缀匹配。
如果匹配成功,获取路由表条目中的下一跳 IP 地址。
- // 文件:net/ipv4/route.c
- // 函数:ip_route_output_key_hash
- // 位置:约在 2400 行附近
-
- struct rtable *ip_route_output_key_hash(struct net *net, __be32 daddr,
- __be32 saddr, u8 tos,
- struct net_device *dev,
- int oif, int hash)
- {
- // ...
-
- struct fib_result res;
-
- // 查询路由表
- fib_lookup(net, &fl, &res);
-
- // 检查是否匹配目的 IP 地址
- if (!res.fi) {
- // 没有匹配的路由项
- return NULL;
- }
-
- // 获取下一跳 IP 地址
- if (res.type == RTN_UNICAST) {
- // 单播路由
- struct fib_nh *nh = fib_info_nh(res.fi, res.table);
- next_hop = nh->nh_gw;
- } else {
- // 非单播路由,如多播、广播等
- // 处理方法根据需要进行调整
- // ...
- }
-
- // ...
-
- return rt;
- }
在上述代码中,ip_route_output_key_hash
函数调用了 fib_lookup
函数查询路由表,并根据查询结果判断是否匹配到目的 IP 地址。如果匹配成功,根据路由类型获取下一跳 IP 地址。
ARP(Address Resolution Protocol)缓存用于存储IP地址与MAC地址之间的映射关系,以便在网络通信中进行地址解析。下面是一个简化的示例源代码,展示了ARP缓存的数据结构及初始化过程。
- // 文件:include/net/arp.h
- // 位置:约在 100 行附近
-
- struct neighbour {
- // ...
-
- struct hh_cache hh;
- // ...
-
- u8 nud_state;
- // ...
-
- u8 ha[HARDWARE_ADDRESS_LEN]; // MAC地址
- // ...
-
- __be32 primary_key; // IP地址
- // ...
-
- struct timer_list timer;
- // ...
- };
在上述代码中,neighbour
结构体表示ARP缓存的一项。其中包含了MAC地址、IP地址等信息。
- // 文件:net/ipv4/arp.c
- // 函数:arp_init
- // 位置:约在 200 行附近
-
- void arp_init(void)
- {
- // ...
-
- // 创建ARP缓存
- arp_tbl = neigh_table_init(&arp_tbl_parm);
- if (!arp_tbl) {
- printk(KERN_INFO "ARP: Failed to allocate memory for ARP table\n");
- return;
- }
-
- // ...
-
- return;
- }
在上述代码中,arp_init
函数用于初始化ARP缓存。它调用 neigh_table_init
函数创建ARP缓存,并将参数传递给该函数进行初始化。
在Linux内核中,将IP地址解析为对应的MAC地址主要通过ARP(Address Resolution Protocol)实现。大致的执行过程如下:
用户空间调用套接字API发送一个IP数据包。
相关函数:sendto()
、connect()
等。
相关文件:net/socket.c
、net/ipv4/raw.c
等。
数据包到达网络层,内核检查目标IP地址是否在本地ARP缓存中,ARP缓存中存储了IP地址和对应的MAC地址。
相关数据结构:struct neighbour
、struct neighbour_table
等。
相关函数:neigh_lookup()
等。
相关文件:net/core/neighbour.c
、include/net/neighbour.h
等。
相关函数:arp_create()
、arp_send()
等。
相关文件:net/ipv4/arp.c
等。
相关函数:neigh_xmit()
等。
相关文件:net/core/neighbour.c
、net/ipv4/devinet.c
等。
目标主机收到ARP请求帧后,根据请求中的IP地址判断是否为自己的IP地址,并构建一个ARP响应消息发送回请求方。
相关函数:arp_rcv()
等。
相关文件:net/ipv4/arp.c
等。
请求方收到ARP响应帧,提取MAC地址并将其存储到本地ARP缓存中。
相关函数:neigh_update()
等。
相关文件:net/core/neighbour.c
、include/net/neighbour.h
等。
请求方将数据包发送到目标MAC地址。
相关函数:dev_queue_xmit()
等。
相关文件:net/core/dev.c
等。
这件事情主要由 ip_route_output_flow 函数完成,调用链 为:ip_route_output_flow->__ip_route_output_key->ip_route_output_key_hash->ip_route_output_key_hash_rcu
ip_route_output_key_hash_rcu
是 Linux 内核中用于根据给定的散列键值进行路由查找和输出的函数。它是 IPv4 路由子系统的一部分,用于确定给定目标 IP 地址的下一跳。
- struct rtable *ip_route_output_key_hash_rcu(struct net *net, struct flowi4 *fl4, struct fib_result *res, const struct sk_buff *skb)
- {
- // ...
- err = fib_lookup(net, fl4, res, 0);
- // ...
- make_route:
- rth = __mkroute_output(res, fl4, orig_oif, dev_out, flags);
- // ...
- }
fib_lookup
是 Linux 内核中用于进行 FIB(Forwarding Information Base,转发信息库)查找的函数之一。它用于根据给定的目标地址查找匹配的路由表项。
- static inline int fib_lookup(struct net *net, const struct flowi4 *flp, struct fib_result *res, unsigned int flags)
- { struct fib_table *tb;
- // ...
- tb = fib_get_table(net, RT_TABLE_MAIN);
- if (tb)
- err = fib_table_lookup(tb, flp, res, flags | FIB_LOOKUP_NOREF);
- // ...
- }
__ipv4_neigh_lookup_noref
函数用于在本地 ARP 表中查找下一跳的 MAC 地址。该函数接收两个参数:dev
表示网络设备,dst
表示目标 IP 地址。它通过调用__neigh_lookup_noref
函数在 ARP 表中查找与目标 IP 地址匹配的邻居项(neighbour entry)。它接收邻居表 tbl
、目标 IP 地址 pkey
和网络设备 dev
作为参数,返回一个指向邻居项结构体的指针。
若查找失败,则会调用 __neigh_create
函数。
- void create_neigh_entry(struct net_device *dev, __be32 dst_ip, unsigned char *dst_mac)
- {
- struct neighbour *neigh;
-
- // 创建邻居项
- neigh = __neigh_create(&arp_tbl, &dst_ip, dev, false);
- if (!neigh) {
- // 邻居项创建失败,处理错误情况
- return;
- }
-
- // 设置邻居项的 MAC 地址
- memcpy(neigh->ha, dst_mac, ETH_ALEN);
-
- // 将邻居项添加到邻居表
- neigh_add(neigh, false);
- }
它接收网络设备 dev
、目标 IP 地址 dst_ip
和目标 MAC 地址 dst_mac
作为参数。函数首先调用 __neigh_create
函数创建一个新的邻居项,并指定邻居表 arp_tbl
、目标 IP 地址和网络设备。然后,它将目标 MAC 地址拷贝到邻居项的 ha
字段中,并通过调用 neigh_add
函数将邻居项添加到邻居表中。
在 __neigh_create 之后,会调用 neigh_output 发送网络包。neigh_output调用neigh_resolve_output。neigh_resolve_output
函数是用于发送网络包的关键函数之一,它用于解析目标 IP 地址的邻居项,并将网络包发送到相应的邻居。
neigh_resolve_output
函数的主要作用是将网络包发送给指定的邻居。它通常会执行以下步骤:
验证邻居项的状态:检查邻居项的状态,例如是否是有效的、是否已解析等。如果邻居项状态不满足发送条件,可能需要进行邻居探测或其他操作来解析邻居项。
设置网络包的目标 MAC 地址:从邻居项中获取目标 MAC 地址,并将其设置为网络包的目标 MAC 地址,以便正确路由和传递给下一跳。
发送网络包:将网络包发送到网络设备的发送队列,以便进行实际的数据传输。发送过程可能涉及网络设备驱动程序的调用,通过底层的网络协议栈将数据封装成数据帧并发送出去。
- int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb) {
- if (neigh->nud_state != NUD_VALID) {
- // 邻居项状态不满足发送条件,可能需要进行邻居探测或其他操作
- // ...
- }
-
- // 设置网络包的目标 MAC 地址
- skb->mac_dest = neigh->ha;
-
- // 发送网络包
- int ret = dev_queue_xmit(skb);
-
- return ret;
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。