TCP/IP协议栈初始化(十) ICMP带来的一段插曲 2

  • Post author:
  • Post category:其他


继续inet_create函数的下半部分。

321     rcu_read_unlock();
323     BUG_TRAP(answer_prot->slab != NULL);
325     err = -ENOBUFS;
326     sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
327     if (sk == NULL)
328         goto out;
330     err = 0;
331     sk->sk_no_check = answer_no_check;
332     if (INET_PROTOSW_REUSE & answer_flags)
333         sk->sk_reuse = 1;
335     inet = inet_sk(sk);
336     inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;
338     if (SOCK_RAW == sock->type) {
339         inet->num = protocol;
340         if (IPPROTO_RAW == protocol)
341             inet->hdrincl = 1;
342     }
344     if (ipv4_config.no_pmtu_disc)
345         inet->pmtudisc = IP_PMTUDISC_DONT;
346     else
347         inet->pmtudisc = IP_PMTUDISC_WANT;
349     inet->id = 0;
351     sock_init_data(sock, sk);
353     sk->sk_destruct    = inet_sock_destruct;
354     sk->sk_family      = PF_INET;
355     sk->sk_protocol    = protocol;
356     sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
358     inet->uc_ttl    = -1;
359     inet->mc_loop   = 1;
360     inet->mc_ttl    = 1;
361     inet->mc_index  = 0;
362     inet->mc_list   = NULL;
364     sk_refcnt_debug_inc(sk);
366     if (inet->num) {
372         inet->sport = htons(inet->num);
374         sk->sk_prot->hash(sk);
375     }
377     if (sk->sk_prot->init) {
378         err = sk->sk_prot->init(sk);
379         if (err)
380             sk_common_release(sk);
381     }
382 out:
383     return err;
384 out_rcu_unlock:
385     rcu_read_unlock();
386     goto out;
387 }

321 跟上半部分的rcu lock对应,这里表明已经读完了。可以解锁了。

323 一个指针判断。作为得到的协议,如果已经注册过了,如那么它的slab多半不会是空的,如果是空的,说明没有协议自身没有内存资源,就输出一个警告出来。

325 初始化一个错误码。这个我们不会用到,因为我们都是假设不会出错的,我们的流程都是正常的。:)

326 又来了一个sk_alloc函数。跟上一篇中遇到的sock_alloc,很相像。发现没有,内核中也是满满的套路。对应的是 struct socket与struct sock,善于分析总结的人已经发现了。socket比较长,是面向高层一些的,sock比较短,就是面向低层一些的。同样sock_alloc用于分配struct socket的,而sk_alloc是用来分配struct sock的。

有时候取舍很难,这个函数要不看下呢?看的话,与主线偏离的更远了,写的人一不注意,这成了主线。还是看下吧,毕竟关键的数据结构还没有串联起来。

326     sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);

先看下,传入的参数。

– net,就是一开始传入的init_net的命名空间。

– PF_INET,表示inet协议子族,这个已经遇到过了。

– GFP_KERNEL,是分配内存时标识,表示我们需要是一般的内核内存。关于它还有许多类似的内存类型标识,有兴趣的可以去看下。

– anser_prot, 就是上回说到的raw_prot。

函数定义在net/core/sock.c中。

929 struct sock *sk_alloc(struct net *net, int family, gfp_t priority,
930               struct proto *prot)
931 {
932     struct sock *sk;
934     sk = sk_prot_alloc(prot, priority | __GFP_ZERO, family);
935     if (sk) {
936         sk->sk_family = family;
941         sk->sk_prot = sk->sk_prot_creator = prot;
942         sock_lock_init(sk);
943         sk->sk_net = get_net(net);
944     }
946     return sk;
947 }

函数很简单,调用sk_prot_alloc分配内存空间。分配到了就初始化一下。sk_prot_alloc更简单,判断协议的slab是不是为空,如果空的话,就从系统中另行分配内存空间。分配的空间的大小,是slab对应的单元的大小,去net/ipv4/raw.c中看下raw_prot定义,发现的它对象大小就是sizeof(struct raw_sock)。

注意到941行,把sk的两个成员变量赋以了传入的prot,这个后面还会遇到。

942,把sk的锁初始化了。

943, 把对net的引用加1。这是Linux中记录对象是否有人使用的方法,当没有人使用时,这个对象占用的资源就可以释放了。

函数执行完了。同样假定得到了一个正常有资源的sk。

回到inet_create函数中。327-334对sk的进行初始化。这当中需要注意以下2点:

answer_no_check其余是赋以了raw_prot对应的在inetsw中元素的no_check标识。这个值为0,表示每次数据包到达时,协议都要自己进行一次校验和计算。

同样answer_flags,是对应是INET_PROTOSW_REUSE。也就是sk要支持复用。这又是为什么呢?别忘记了,我们现在正在创建的是ICMP协议的内核socket,ICMP与IP一起工作,当然要支持复用了。

335, 这个函数仅仅是把struct sock类型转换成了struct inet_sock。为什么能这么转换,因为当在调用sk_prot_alloc生成的sk实例,其实大小是raw_sock,而raw_sock是内含了struct inet_sock,而struct inet_sock又内含了struct sock。可以参考我另一篇《 linux源码-TCP/IP协议栈学习预备(1) 数据结构之各socket之间的关系 》中描述的关系。

336, 我们的ICMP socket是非连接socket,is_icsk被置0。

338-349 完成对inet成员的进一步初始化。

inet->num 给inet socket的本地端口。这里被初始化为协议类型号。也就是IPPROTO_ICMP。我们只是知道系统会保留一些端口号,80留给http协议,并没有听说过会1号端口留给ICMP协议。

341,hdrincl选项用来指示数据中是否包含头部。ICMP是使用的IP协议,数据中包含了IP的包首部。也就收到的raw数据包减去IP的首部长度,剩下的部分才是ICMP协议的报文部分。

344-347,这当中的选项不太清楚,根据源码中注释猜测,是接受用户配置选项,来决定ICMP socket是否支持MTU(Maximum Transmit Unit)的发现机制。

349, 现在还没有开始工作,把记录分片包的数据的变量id置0。

351,进一步初始化sk。别看传入的是sock和sk,几乎全是针对sk的初始化。既然是init_data,多半是接受发送有关的数据结构。主要做了以下事情,具体的内容在遇到时再说下。

初始化三个队列:接受队列,写队列,出错队列。

初始化各个状态变量值

初始化各个锁

继续回到inet_create中。下面一直到364行,进一步初始化sk与协议相关的成员。

366-375 如果有端口号,上面已经看到过被赋1,给inet的源端口赋上,被把sk加入到协议的散列表中。这里的散列表是定义在net/ipv4/raw.c中raw_v4_htable。

377 如果协议自身定义了初始化函数就去执行它。这里我们执行的是来自net/ipv4/raw.c上的raw_init。这个函数只是置0了一个成员变量filter。

至此inet_create执行完成了。我们的ICMP socket从inet层、控制层都准备好了。也是该返回主线的时候了。



版权声明:本文为u012520854原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。