普通网络驱动程序中必须要调用的函数是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. android测试分析1

    Android测试框架,开发环境中集成的一部分,提供一个架构和强有力的工具 可以帮助测试你的应用从单元到框架的每个方面. 测试框架有这些主要特征: 1.Android测试组件基于Junit.你可以使用 ...

  2. IP-MAC绑定导致网络故障

    前段时间将一台服务器A的服务迁移至了另外一台服务器B,外网IP地址也顺带迁移过来了,结果网络出现了问题. 其中内网是畅通的,但是外网IP怎么都连不上另外一台路由C(B和C是在一个交换机下的,网段也相同 ...

  3. 仿主题广告轮播js

    function SlideShow(c) { var a = document.getElementById("slide"); var f = document.getElem ...

  4. 浅谈break 、continue、return,goto四种语句的区别。

    浅谈break .continue.return三种语句的区别: break,continue,return这三个具有跳转功能的语句在c语言中经常被用到,近期身边有些小伙伴总是把它们的用法搞乱,在这里 ...

  5. postgres 利用unique index代替 primay key

    create UNIQUE INDEX uniq_index_piwik_log_action_idaction on piwik_log_action(idaction);   这样做的好处: 1. ...

  6. 原生js的数组除重复

    js对数组的操作在平常的项目中也会遇到,除去一些增加,或者减少的操作外,还有一个比较重要的操作就是数组的除重,通过数组的除重,我们可以将一个数组中存在的多个重复的数组进行清理,只留下不重复的.另外下面 ...

  7. Codevs 1064 虫食算 2004年NOIP全国联赛提高组

    1064 虫食算 2004年NOIP全国联赛提高组 时间限制: 2 s 空间限制: 128000 KB 题目等级 : 钻石 Diamond 题目描述 Description 所谓虫食算,就是原先的算式 ...

  8. C语言程序设计概述

    1 概论 1972年Dennis Ritchie发明了C语言,而后Dennis Ritchie又使用C语言重写了Unix系统,自那以后C语言逐渐受到了全世界大多数编程爱好者的喜爱,后期的主流操作系统L ...

  9. Java面向对象程序设计--泛型编程

    1. 为何要进行泛型编程? 泛型变成为不同类型集合提供相同的代码!省去了为不同类型而设计不同代码的麻烦! 2. 一个简单泛型类的定义: public class PairTest1 { public ...

  10. Unix环境高级编程学习笔记——fcntl

    写这篇文正主要是为了介绍下fcntl,并将我自己在学习过程中的一些理解写下来,不一定那么官方,也有错误,希望指正,共同进步- fcntl: 一个修改一打开文件的性质的函数.基本的格式是 int fcn ...