まず、抑えておくべきNet名前空間はどんなものかというとこです。ここを抑えておかないと話が進まないですね。 Net名前空間はstruct netです。これは比較的大きめの構造体です。メンバ変数としてipv4、ipv6などの名前が見えます。unxはunitドメインソケットのデータっぽいですね。そんな感じでこのstruct netがネットワークの主要な構造体と鳴っています。
45 struct net { 46 atomic_t passive; /* To decided when the network 47 * namespace should be freed. 48 */ 4 〜略〜 85 86 struct net_device *loopback_dev; /* The loopback */ 87 struct netns_core core; 88 struct netns_mib mib; 89 struct netns_packet packet; 90 struct netns_unix unx; 91 struct netns_ipv4 ipv4; 92 #if IS_ENABLED(CONFIG_IPV6) 93 struct netns_ipv6 ipv6; 94 #endif 〜略〜
では、どこから見ていくのが良いかというところですが、たまたま見たsocket(2)がちょうど良さ気だったのでここからスタートです。
socket(2)では実際にsocketを作成するのは別のところになります。ここではsock_create()を呼びます。
1241 1242 retval = sock_create(family, type, protocol, &sock); 1243 if (retval < 0) 1244 goto out;
そしてsock_create()を見るとcurrentタスクのnsproxyからnet_nsにアクセスしています。ということで、いつ名前空間にアクセスするのかというところは分かりました。
1210 int sock_create(int family, int type, int protocol, struct socket **res) 1211 { 1212 return __sock_create(current->nsproxy->net_ns, family, type, protocol, res, 0); 1213 }
あとはこれがどう使われるかですね。__sock_create()では以下の場所で使います。pfは struct net_proto_familyです。ということでこのcreate()はプロトコル依存になります。ここではipv4の場合を見てみたいと思います。
1172 err = pf->create(net, sock, protocol, kern); 1173 if (err < 0) 1174 goto out_module_put;
ipv4でのcreate()はnet/ipv4/af_inet.cにて設定されています。この場合はinet_create()ですね。
979 static const struct net_proto_family inet_family_ops = { 980 .family = PF_INET, 981 .create = inet_create, 982 .owner = THIS_MODULE, 983 };
inet_create()はinet_family_opsの定義と同じくaf_inet.cにあります。inet_create()の処理の中ではsk_alloc()の引数に使用しています(user名前空間も使っているのですが、今はnet名前空間だけ見てます)。
320 sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot); 321 if (!sk) 322 goto out;
sk_alloc()はnet/core/sock.cにあります。ここではsock_net_set()の2番めの引数に使っているget_net()にstruct netを渡しています。
1410 sock_lock_init(sk); 1411 sock_net_set(sk, get_net(net)); 1412 atomic_set(&sk->sk_wmem_alloc, 1);
get_net()は何をしているかというと、自身の参照カウンタを1増やしてから自分自身を返します。socketを作っているので参照数を増やす必要があるってことですね。
176 static inline struct net get_net(struct net net) 177 { 178 atomic_inc(&net->count); 179 return net; 180 }
そして、参照数を増やしたところでsock_net_set()が呼ばれます。これはwrite_pnet()を呼び出します。
2190 void sock_net_set(struct sock *sk, struct net *net) 2191 { 2192 write_pnet(&sk->sk_net, net); 2193 } 2194
write_pnet()は何をするかというと、possible_net_t構造体のメンバ変数netに現在使用している(さっき参照数を増やした)net名前空間を設定します。
240 static inline void write_pnet(possible_net_t *pnet, struct net *net) 241 { 242 #ifdef CONFIG_NET_NS 243 pnet->net = net; 244 #endif 245 } 246
socket(2)でNet名前空間を使う主なところはこんなところです。次にこれを使う時のコードを読んでみます。先ほどwrite_pnet()で名前空間を設定したので、読み出しにはread_pnet()ということが想像できます。 read_pnet()はwrite_pnet()のすぐ下にありますし。read_pnet()はこのようになっています。
247 static inline struct net *read_pnet(const possible_net_t *pnet) 248 { 249 #ifdef CONFIG_NET_NS 250 return pnet->net; 251 #else 252 return &init_net; 253 #endif 254 }
read_pnet()がどこで使われているかはlxrで調べるのが簡単なので検索してみるとまあまあります。 適当に見てみると、sock_net()という関数が見つかったのでここから調べてみます。
2183 static inline 2184 struct net *sock_net(const struct sock *sk) 2185 { 2186 return read_pnet(&sk->sk_net); 2187 } 2188
ラッパー関数っぽいですし、これから調べるのが良さそうですね。これを呼んででいるのを探して、ここから呼び出し元を遡っていくわけです。そこで見つけたbind()だとsock->ops->bindを呼び、
1385 if (!err) 1386 err = sock->ops->bind(sock, 1387 (struct sockaddr *) 1388 &address, addrlen);
sock->opsはsocket構造体のstruct proto_opsです。
118 const struct proto_ops *ops;
tcpでipv4の場合は[af_inet.c](http://lxr.free-electrons.com/source/net/ipv4/af_inet.c?v=4.1#L898で関数を設定しています。
894 const struct proto_ops inet_stream_ops = { 895 .family = PF_INET, 896 .owner = THIS_MODULE, 897 .release = inet_release, 898 .bind = inet_bind, 899 .connect = inet_stream_connect, 900 .socketpair = sock_no_socketpair,
では、inet_bindを見てみます。ここでは最初にNet名前空間を取得します。
419 int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len) 420 { 421 struct sockaddr_in *addr = (struct sockaddr_in *)uaddr; 422 struct sock *sk = sock->sk; 423 struct inet_sock *inet = inet_sk(sk); 424 struct net *net = sock_net(sk); 425 unsigned short snum; 426 int chk_addr_ret;
inet_bind()の中でnetを使っているのはここらへんです。inet_addr_type()の引数と、
447 448 chk_addr_ret = inet_addr_type(net, addr->sin_addr.s_addr);
sysctlのチェック。
58 if (!net->ipv4.sysctl_ip_nonlocal_bind && 459 !(inet->freebind || inet->transparent) && 460 addr->sin_addr.s_addr != htonl(INADDR_ANY) && 461 chk_addr_ret != RTN_LOCAL && 462 chk_addr_ret != RTN_MULTICAST && 463 chk_addr_ret != RTN_BROADCAST) 464 goto out; 465
inet_addr_type()は何をしているのか? 処理はこれで__inet_dev_addr_type()を呼び出しています。
241 unsigned int inet_addr_type(struct net *net, __be32 addr) 242 { 243 return __inet_dev_addr_type(net, NULL, addr); 244 }
__inet_dev_addr_type()はfib_get_table()の呼び出しでfib_table構造体の取得に使っています。
228 local_table = fib_get_table(net, RT_TABLE_LOCAL); 229 if (local_table) {
fib_get_tableはこうなっています。Net名前空間にあるipv4のデータよりfib_table_hashテーブルの要素にアクセスするという使い方でした。
209 static inline struct fib_table *fib_get_table(struct net *net, u32 id) 210 { 211 struct hlist_node *tb_hlist; 212 struct hlist_head *ptr; 213 214 ptr = id == RT_TABLE_LOCAL ? 215 &net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX] : 216 &net->ipv4.fib_table_hash[TABLE_MAIN_INDEX]; 217 218 tb_hlist = rcu_dereference_rtnl(hlist_first_rcu(ptr)); 219 220 return hlist_entry(tb_hlist, struct fib_table, tb_hlist); 221 }
ここまで読んでみてわかったのはsocketのcreate時に名前空間をsocket構造体からアクセスできる場所に保存し、そのsocketを使った操作を実行するときにcreateで設定しておいた名前空間にアクセスして、何かしらの固有なデータを読んだりするということでした。
こうやって見てみるとNet名前空間に関してはCONFIG_NET_NSの有無にかかわらず、カーネルの処理にオーバーヘッドはなさそうですね。ただ、pnet_write()はCONFIG_NET_NSがNの場合は空の関数になるので呼び出し自体が最適化で消されるとか、read_pnet()なら関数呼び出してはなくてinit_netにアクセスというような最適化が入るかもしれませんが。その場合は関数呼び出しのオーバーヘッドはあるでしょうね。 CONFIG_NET_NSはYだけど、たんにNet名前空間を作っていないだけなら作っている場合と比較してオーバーヘッドはなさそうですが。
データサイエンティスト養成読本 機械学習入門編 (Software Design plus)
- 作者: 比戸将平,馬場雪乃,里洋平,戸嶋龍哉,得居誠也,福島真太朗,加藤公一,関喜史,阿部厳,熊崎宏樹
- 出版社/メーカー: 技術評論社
- 発売日: 2015/09/10
- メディア: 大型本
- この商品を含むブログを見る