普通网络驱动程序中必须要调用的函数是eth_type_trans(略),然后向上递交sk_buff时调用netif_rx()(net/core/dev.c).其函数中主要几行
__skb_queue_tail(&queue->input_pkt_queue, skb);添加skb到接受队列中
netif_rx_schedule(&queue->backlog_dev); 开启接受软中断处理. struct softnet_data * queue 在net_dev_init()(dev.c)中初始化.其中有:
open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL); //发送sk_buff软中断
open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL); //接受sk_buff软中断 [进入软中断]
首先来看net_rx_action(), 其中最重要一行
if (dev->quota <= || dev->poll(dev, &budget))
dev->poll()处理skb的递交, 此回掉函数也是在net_dev_init()中初始化,其中queue->backlog_dev.poll = process_backlog;初始化了这个回掉函数.
process_backlog 函数循环递交队列中所有的skb.
for(;;) {
......
skb = __skb_dequeue(&queue->input_pkt_queue);
......
netif_receive_skb(skb);
......
}
接下来 netif_receive_skb 进一步处理skb的递交,我们来看.
......
skb->h.raw = skb->nh.raw = skb->data; //初始化 sk_buff 中的 ip或其他协议的头
skb->mac_len = skb->nh.raw - skb->mac.raw;
......
if (handle_bridge(&skb, &pt_prev, &ret, orig_dev)) //处理桥接数据,具体参考 bridge 实现.
goto out;
......
list_for_each_entry_rcu(ptype, &ptype_base[ntohs(type)&], list) {
if (ptype->type == type &&
(!ptype->dev || ptype->dev == skb->dev)) {
if (pt_prev)
ret = deliver_skb(skb, pt_prev, orig_dev); //向上层递交
pt_prev = ptype;
}
}
这是最主要的递交过程,根据协议类型向上层协议层递交 skb. 关于NAPI 你需要实现自己的poll而不是默认的process_backlog, 在你自己的poll函数中你需要调用netif_rx_schedule 而不是一般的 netif_rx,现在明白其实你自己实现的poll就是在软中断中被调用,代替netif_rx中给你的默认poll.
下面进入具体协议过程:
static struct packet_type ip_packet_type = { // ip 类型 af_inet.c
.type = __constant_htons(ETH_P_IP),
.func = ip_rcv,
.gso_send_check = inet_gso_send_check,
.gso_segment = inet_gso_segment,
};
static struct packet_type arp_packet_type = { // arp 类型 arp.c
.type = __constant_htons(ETH_P_ARP),
.func = arp_rcv,
};
其他略,自己看去.
所有类型通过函数 dev_add_pack 注册到 ptype_base. ip_rcv 就是开始 ip 层协议解析的开始 .
ip_rcv 中有 return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL, ip_rcv_finish);
如果核心编译了netfilter,就会调用相关的hook函数,具体参考netfilter实现说明。现在我们关心的是 ip_rcv_finish.
其中最重要的是 ip_route_input 和 dst_input, dst_input 中主要是 err = skb->dst->input(skb);
那现在我们知道要在 ip_route_input 中查找或创造路由表,也就是找到skb->dst 然后初始化着指针,那么最后会调用dst的input函数.
那接下来主要看 ip_route_input 函数:
函数中主要调用 ip_route_input_slow:
如果数据报是本地的:
......
rth->u.dst.input= ip_local_deliver;
......
如果数据报需要被转发:
......
ip_mkroute_input -> __mkroute_input 中有 rth->u.dst.input = ip_forward;
......
其他可能还有
ip_mr_input 处理多播等函数. 路由表和转发等其他过程不是讨论的范围,下面我们看 ip_local_deliver.
首先检测 ip 是否被分片如果是调用 ip_defrag IP碎片重组函数,然后
return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL, ip_local_deliver_finish); 调用了NF_IP_LOCAL_IN处的hook函数进行处理.
然后调用 ip_local_deliver_finish. 其中
......
__skb_pull(skb, ihl); //剥去IP头
skb->h.raw = skb->data; //指向传输层协议
......
int protocol = skb->nh.iph->protocol;
......
hash = protocol & (MAX_INET_PROTOS - );
......
if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {
......
ret = ipprot->handler(skb);
......
else {
......
icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PROT_UNREACH, ); //发送icmp不可达
......
}
......
现在我们的重点转移到 inet_protos 和 ipprot->handler中了.
struct net_protocol { // include/net/protocal.h
int (*handler)(struct sk_buff *skb);
void (*err_handler)(struct sk_buff *skb, u32 info);
int (*gso_send_check)(struct sk_buff *skb);
struct sk_buff *(*gso_segment)(struct sk_buff *skb,
int features);
int no_policy;
};
现在我们看看谁都进行注册了.
我又在 af_inet.c 文件的 inet_init() 中看到
if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < )
printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n");
if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < )
printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n");
if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < )
printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");
#ifdef CONFIG_IP_MULTICAST
if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < )
printk(KERN_CRIT "inet_init: Cannot add IGMP protocol\n");
#endif 这些协议也在此文件中被初始化 #ifdef CONFIG_IP_MULTICAST
static struct net_protocol igmp_protocol = {
.handler = igmp_rcv,
};
#endif static struct net_protocol tcp_protocol = {
.handler = tcp_v4_rcv,
.err_handler = tcp_v4_err,
.gso_send_check = tcp_v4_gso_send_check,
.gso_segment = tcp_tso_segment,
.no_policy = ,
}; static struct net_protocol udp_protocol = {
.handler = udp_rcv,
.err_handler = udp_err,
.no_policy = ,
}; static struct net_protocol icmp_protocol = {
.handler = icmp_rcv,
}; 我们来个最难的tcp然后在往下看,那么就是 tcp_v4_rcv了,我们现在进入了传输层.
ret = tcp_v4_do_rcv(sk, skb); -> tcp_rcv_state_process(sk, skb, skb->h.th, skb->len)) -> tcp_data_queue(sk, skb);
最后数据会被 __skb_queue_tail(&sk->sk_receive_queue, skb); 看放到了sk的接受队列中.
上面看的流程主要展示了一个带数据的数据报被接收的过程,意味着tcp已经established,其他的tcp状态处理很复杂不是本文描述范围,呵呵. 注意:下面是用户读取数据时,在系统调用中的过程了,已经不是软中断了. 当用户读取数据时,系统调用函数 sys_socketcall.
根据 call 不管是 sys_recv, sys_recvfrom 还是 sys_recvmsg 都会调用到 sock_recvmsg -> __sock_recvmsg -> sock->ops->recvmsg(iocb, sock, msg, size, flags);
struct socket * sock->ops 谁初始化的呐 ?
af_inet.c 中又找到
static struct inet_protosw inetsw_array[] =
{
{
.type = SOCK_STREAM,
.protocol = IPPROTO_TCP,
.prot = &tcp_prot, //sock inet socket (struct sock)
.ops = &inet_stream_ops, //socket 伯克利 socket (struct socket)
.capability = -,
.no_check = ,
.flags = INET_PROTOSW_PERMANENT | INET_PROTOSW_ICSK,
},
......
}
显然上面是传输层协议数组中的tcp操作据柄初始化,还有其他协议并没有列出.先往下看在inet_init()中还有一行是(void)sock_register(&inet_family_ops);
这又是什么呐 ?
static struct net_proto_family inet_family_ops = {
.family = PF_INET,
.create = inet_create,
.owner = THIS_MODULE,
};
其中实现了inet_create函数,也就是当你调用 socket() 系统调用时内核会调用到实现的inet_create函数,family是PF_INET是针对所有PF_INET协议族的create函数,该函数会创建一个struct sock 数据结构保存在 struct socket结构中. inet_create函数中
......
sock->ops = answer->ops; // socket操作据柄
answer_prot = answer->prot; // sock 操作据柄
......
sk = sk_alloc(PF_INET, GFP_KERNEL, answer_prot, );
......
所以上面 sock->ops->recvmsg 调用到了伯克利 socket.
const struct proto_ops inet_stream_ops = { //af_inet.c
......
.recvmsg = sock_common_recvmsg,
......
};
sock_common_recvmsg实现是
struct sock *sk = sock->sk;
......
err = sk->sk_prot->recvmsg(iocb, sk, msg, size, flags & MSG_DONTWAIT, flags & ~MSG_DONTWAIT, &addr_len);
......
有调用了inet socket的 recvmsg.
struct proto tcp_prot = { //tcp_ipv4.c
......
.recvmsg = tcp_recvmsg,
......
};
在tcp_recvmsg中
skb = skb_peek(&sk->sk_receive_queue);从接收队列中读取了相应的skb然后拷贝到用户地址空间中
err = skb_copy_datagram_iovec(skb, offset, msg->msg_iov, used);
整个数据接受流程就全部完成了.

Linux协议栈函数调用流程的更多相关文章

  1. vlan linux内核数据流程

    转:http://blog.sina.com.cn/s/blog_62bbc49c0100fs0n.html 一.前言 前几天做协议划分vlan的时候看了一些linux内核,了解不深,整理了下vlan ...

  2. Android中Linux suspend/resume流程

    Android中Linux suspend/resume流程首先我们从linux kernel 的suspend说起,不管你是使用echo mem > /sys/power/state 或者使用 ...

  3. [置顶] Linux协议栈代码阅读笔记(一)

    Linux协议栈代码阅读笔记(一) (基于linux-2.6.21.7) (一)用户态通过诸如下面的C库函数访问协议栈服务 int socket(int domain, int type, int p ...

  4. Linux下函数调用堆栈帧的详细解释【转】

    转自:http://blog.chinaunix.net/uid-30339363-id-5116170.html 原文地址:Linux下函数调用堆栈帧的详细解释 作者:cssjtuer http:/ ...

  5. 【内核】linux内核启动流程详细分析

    Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件,包括内核入口ENTRY(stext)到start_kernel间的初始化代码, 主要作用 ...

  6. 【内核】linux内核启动流程详细分析【转】

    转自:http://www.cnblogs.com/lcw/p/3337937.html Linux内核启动流程 arch/arm/kernel/head-armv.S 该文件是内核最先执行的一个文件 ...

  7. linux驱动开发流程

    嵌入式linux驱动开发流程嵌入式系统中,操作系统是通过各种驱动程序来驾驭硬件设备的.设备驱动程序是操作系统内核和硬件设备之间的接口,它为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个 ...

  8. Linux协议栈代码阅读笔记(二)网络接口的配置

    Linux协议栈代码阅读笔记(二)网络接口的配置 (基于linux-2.6.11) (一)用户态通过C库函数ioctl进行网络接口的配置 例如,知名的ifconfig程序,就是通过C库函数sys_io ...

  9. 9.Linux系统引导流程

    一.Linux系统引导流程 当我们按下主机电源键的那时候开始,主板上的CMOS/BIOS模块将进行固件自检,以此检查各个硬件是否正确连接. 在Linux引导流程中,一般可以分为以下几个主要过程: 1. ...

随机推荐

  1. VS2010数据库连接问题

    我用SQL server 2008写了一个小表格,运行VS的前选择了 工具-连接数据库,然后选择了那个表格的名称.运行后出现如下错误提示,请问是哪里出了问题呢? 数据库连接版本有问题.在VS中把信用数 ...

  2. VS 2013 编译和使用 Boost

    以 1.58.0 版本 boost 为例, 当前系统版本为 Windows 8.1 x64   1 编译boost  当前解压路径 "D:\Libraries\boost_1_58_0&qu ...

  3. SQL SERVER HTTP请求

    --开启Sql Server 通讯配置-- sp_configure ; GO RECONFIGURE WITH OVERRIDE; GO sp_configure ; GO RECONFIGURE ...

  4. 自动构建工具Ant的使用-笔记

    第一:什么是Ant? Apache Ant是一个基于Java的生成工具.据最初的创始人James Duncan Davidson的介绍,这个工具的名称是another neat tool(另一个整洁的 ...

  5. javaee学习-JSP指令简介

    JSP指令(directive)是为JSP引擎而设计的,它们并不直接产生任何可见输出,而只是告诉引擎如何处理JSP页面中的其余部分. 在JSP 2.0规范中共定义了三个指令: page指令 Inclu ...

  6. UVA 11991 Easy Problem from Rujia Liu?(vector map)

    Easy Problem from Rujia Liu? Though Rujia Liu usually sets hard problems for contests (for example, ...

  7. 自己动手丰衣足食,h5手机端jquery弹窗插件(事件冒泡、单例模式、遮盖部分禁止默认滚动)

    感谢浏览,欢迎交流=.= 公司开发微信网页多处需要使用弹窗,使用jquery-ui的定制化下载仍需要150多kb,想来有些奢侈(最终下来只有11kb,压缩后2kb,啊,我的神), 手机端弹窗方式与pc ...

  8. sublime2 Ctags 快捷键

    Commands Listing Command Key Binding Alt Binding Mouse Binding rebuild_ctags ctrl+t, ctrl+r     navi ...

  9. React组件一

    <div id='test'></div> <script type='text/babel'> var Zu=React.createClass({ return ...

  10. hbuider 中点击就显示出一个单选的列表 ,然后后台跨域向里面动态添加数据,注意里面的格式是json object

    jsp页面: <li class="mui-table-view-cell" onclick="showActionSheet()"> <di ...