版權聲明:本文為部落客原創文章,未經部落客允許不得轉載。 https://blog.csdn.net/feilengcui008/article/details/49509993
本文主要針對Linux-3.19.3版本的核心簡單分析核心協定棧初始化涉及到的主要步驟和關鍵函數,不針對協定的解析以及資料包的處理流程做具體分析,後續有機會再詳細分析
1.準備
- Linux核心協定棧本身建構在虛拟檔案系統之上,是以對Linux VFS不太了解的可以參考核心源碼根目錄下Documentation/filesystems/vfs.txt,另外,socket接口層,協定層,裝置層的許多資料結構涉及到記憶體管理,是以對基本虛拟記憶體管理,slab緩存,頁高速緩存不太了解的也可以查閱相關文檔。
- 源碼涉及的主要檔案位于net/socket.c,net/core,include/linux/net*. 基本上整個初始化過程主要與net,net_namespace,/proc,/proc/sys相關結構的初始化和檔案的建立,主要使用register_pernet_subsystem鈎子注冊和調用各種操作.init和.exit
2.開始
開始分析前,這裡有些小技巧可以快速定位到主要的初始化函數,在分析其他子系統源碼時也可以采用這個技巧
grep _initcall socket.c
find ./core/ -name "*.c" |xargs cat | grep _initcall
grep net_inuse_init tags
這裡*__initcall宏是設定初始化函數位于核心代碼段.initcall#id.init的位置其中id代表優先級level,小的一般初始化靠前,定義在include/linux/init.h,使用gcc的attribute擴充。而各個level的初始化函數的調用流程基本如下:
start_kernel -> rest_init -> kernel_init核心線程 -> kernel_init_freeable -> do_basic_setup -> do_initcalls -> do_initcall_level -> do_one_initcall -> *(initcall_t)
3.詳細分析
- 可以看到pure_initcall(net_ns_init)位于0的初始化level,基本不依賴其他的初始化子系統,是以從這個開始
//core/net_namespace.c
//基本上這個函數主要的作用是初始化net結構init_net的一些資料,比如namespace相關,并且調用注冊的pernet operations的init鈎子針對net進行各自需求的初始化
pure_initcall(net_ns_init);
static int __init net_ns_init(void)
{
struct net_generic *ng;
//net namespace相關
#ifdef CONFIG_NET_NS
//配置設定slab緩存
net_cachep = kmem_cache_create("net_namespace", sizeof(struct net),SMP_CACHE_BYTES,SLAB_PANIC, NULL);
/* Create workqueue for cleanup */
netns_wq = create_singlethread_workqueue("netns");
if (!netns_wq)
panic("Could not create netns workq");
#endif
ng = net_alloc_generic();
if (!ng)
panic("Could not allocate generic netns");
rcu_assign_pointer(init_net.gen, ng);
mutex_lock(&net_mutex);
//初始化net namespace相關的對象, 傳入初始的namespace init_user_ns
//設定net結構的初始namespace
//對每個pernet_list中注冊的pernet operation,調用其初始化net中的對應資料對象
if (setup_net(&init_net, &init_user_ns))
panic("Could not setup the initial network namespace");
rtnl_lock();
//加入初始net結構的list中
list_add_tail_rcu(&init_net.list, &net_namespace_list);
rtnl_unlock();
mutex_unlock(&net_mutex);
//加入pernet_list連結清單,并且調用pernet operation的init函數初始化net
register_pernet_subsys(&net_ns_ops);
return 0;
}
- 下面分析core_init(sock_init):
//socket.c
//在.initcall1.init代碼段注冊,以便核心啟動時do_initcalls中調用
//進而注冊socket filesystem
core_initcall(sock_init); /* early initcall */
進入core_init(sock_init):
static int __init sock_init(void)
{
int err;
//sysctl 支援
err = net_sysctl_init();
if (err)
goto out;
//初始化skbuff_head_cache 和 skbuff_clone_cache的slab緩存區
skb_init();
//與vfs挂接,為sock inode配置設定slab緩存
init_inodecache();
//注冊socket 檔案系統
err = register_filesystem(&sock_fs_type);
if (err)
goto out_fs;
//通過kern_mount核心層接口調用mount系統調用,最終調用
//fs_type->mount 而socket filesystem 使用mount_pesudo僞挂載
sock_mnt = kern_mount(&sock_fs_type);
if (IS_ERR(sock_mnt)) {
err = PTR_ERR(sock_mnt);
goto out_mount;
}
//協定與裝置相關的資料結構等初始化在後續的各子子產品subsys_init操作中
/* The real protocol initialization is performed in later initcalls.
*/
//netfilter初始化
#ifdef CONFIG_NETFILTER
err = netfilter_init();
if (err)
goto out;
#endif
/*省略部分*/
}
- core_init(net_inuse_init)
//core/sock.c
//主要功能是為net配置設定inuse的percpu辨別
core_initcall(net_inuse_init);
static int __net_init sock_inuse_init_net(struct net *net)
{
net->core.inuse = alloc_percpu(struct prot_inuse);
return net->core.inuse ? 0 : -ENOMEM;
}
static void __net_exit sock_inuse_exit_net(struct net *net)
{
free_percpu(net->core.inuse);
}
static struct pernet_operations net_inuse_ops = {
.init = sock_inuse_init_net,
.exit = sock_inuse_exit_net,
};
static __init int net_inuse_init(void)
{
if (register_pernet_subsys(&net_inuse_ops))
panic("Cannot initialize net inuse counters");
return 0;
}
- core_init(netpoll_init)
//core/netpoll.c
//主要功能就是把預留的sk_buffer poll初始化成隊列
core_initcall(netpoll_init);
static int __init netpoll_init(void)
{
skb_queue_head_init(&skb_pool);
return 0;
}
- subsys_initcall(proto_init)
//core/sock.c
//涉及的操作主要是在/proc/net域下建立protocols檔案,注冊相關檔案操作函數
subsys_initcall(proto_init);
// /proc/net/protocols支援的檔案操作
static const struct file_operations proto_seq_fops = {
.owner = THIS_MODULE,
.open = proto_seq_open, //打開
.read = seq_read, //讀
.llseek = seq_lseek,//seek
.release = seq_release_net,
};
static __net_init int proto_init_net(struct net *net)
{
//建立/proc/net/protocols
if (!proc_create("protocols", S_IRUGO, net->proc_net, &proto_seq_fops))
return -ENOMEM;
return 0;
}
static __net_exit void proto_exit_net(struct net *net)
{
remove_proc_entry("protocols", net->proc_net);
}
static __net_initdata struct pernet_operations proto_net_ops = {
.init = proto_init_net,
.exit = proto_exit_net,
};
//注冊 pernet_operations, 并用.init鈎子初始化net,此處即建立proc相關檔案
static int __init proto_init(void)
{
return register_pernet_subsys(&proto_net_ops);
}
- subsys_initcall(net_dev_init)
//core/dev.c
//基本上是建立net device在/proc,/sys相關的資料結構,并且開啟網卡收發中斷
//初始化net device
static int __init net_dev_init(void)
{
int i, rc = -ENOMEM;
BUG_ON(!dev_boot_phase);
//主要也是在/proc/net/下建立相應的屬性檔案,如dev網卡資訊檔案
if (dev_proc_init())
goto out;
//注冊/sys檔案系統,添加相關屬性項
//注冊網絡核心對象namespace相關的一些操作
//注冊net interface(dev)到 /sys/class/net
if (netdev_kobject_init())
goto out;
INIT_LIST_HEAD(&ptype_all);
for (i = 0; i < PTYPE_HASH_SIZE; i++)
INIT_LIST_HEAD(&ptype_base[i]);
INIT_LIST_HEAD(&offload_base);
//注冊并調用針對每個net的裝置初始化操作
if (register_pernet_subsys(&netdev_net_ops))
goto out;
//對每個cpu,初始化資料包處理相關隊列
for_each_possible_cpu(i) {
struct softnet_data *sd = &per_cpu(softnet_data, i);
//入
skb_queue_head_init(&sd->input_pkt_queue);
skb_queue_head_init(&sd->process_queue);
INIT_LIST_HEAD(&sd->poll_list);
//出
sd->output_queue_tailp = &sd->output_queue;
#ifdef CONFIG_RPS
sd->csd.func = rps_trigger_softirq;
sd->csd.info = sd;
sd->cpu = i;
#endif
sd->backlog.poll = process_backlog;
sd->backlog.weight = weight_p;
}
//隻在boot phase調用一次, 防止重複調用
dev_boot_phase = 0;
/* The loopback device is special if any other network devices
* is present in a network namespace the loopback device must
* be present. Since we now dynamically allocate and free the
* loopback device ensure this invariant is maintained by
* keeping the loopback device as the first device on the
* list of network devices. Ensuring the loopback devices
* is the first device that appears and the last network device
* that disappears.
*/
//回環裝置的建立與初始化
if (register_pernet_device(&loopback_net_ops))
goto out;
//退出的通用操作
if (register_pernet_device(&default_device_ops))
goto out;
//開啟收發隊列的中斷
open_softirq(NET_TX_SOFTIRQ, net_tx_action);
open_softirq(NET_RX_SOFTIRQ, net_rx_action);
hotcpu_notifier(dev_cpu_callback, 0);
//destination cache related?
dst_init();
rc = 0;
out:
return rc;
}
- fs_initcall(sysctl_core_init)
//core/sysctl_net_core.c
//主要是建立sysctl中與net相關的一些配置參數(見下圖)
static __init int sysctl_core_init(void)
{
register_net_sysctl(&init_net, "net/core", net_core_table);
return register_pernet_subsys(&sysctl_core_ops);
}
static __net_init int sysctl_core_net_init(struct net *net)
{
struct ctl_table *tbl;
net->core.sysctl_somaxconn = SOMAXCONN;
tbl = netns_core_table;
if (!net_eq(net, &init_net)) {
tbl = kmemdup(tbl, sizeof(netns_core_table), GFP_KERNEL);
if (tbl == NULL)
goto err_dup;
tbl[0].data = &net->core.sysctl_somaxconn;
if (net->user_ns != &init_user_ns) {
tbl[0].procname = NULL;
}
}
net->core.sysctl_hdr = register_net_sysctl(net, "net/core", tbl);
if (net->core.sysctl_hdr == NULL)
goto err_reg;
return 0;
err_reg:
if (tbl != netns_core_table)
kfree(tbl);
err_dup:
return -ENOMEM;
}
static __net_exit void sysctl_core_net_exit(struct net *net)
{
struct ctl_table *tbl;
tbl = net->core.sysctl_hdr->ctl_table_arg;
unregister_net_sysctl_table(net->core.sysctl_hdr);
BUG_ON(tbl == netns_core_table);
kfree(tbl);
}
static __net_initdata struct pernet_operations sysctl_core_ops = {
.init = sysctl_core_net_init,
.exit = sysctl_core_net_exit,
};
4.總結
本文主要按照關于核心協定棧的各個子系統的*_initcall的調用順序分析了幾個核心的初始化步驟,包括socket層,協定層,裝置層等,整個初始化過程還是比較簡單的,主要涉及一些資料結構和緩存等的初始化,但是整個核心協定棧的對資料包的處理流程并不能很好地呈現,後續有機會再分析從系統調用開始整個資料包的收發流程。
ref: Linux 3.19.3 source tree