Linux内核初始化过程中加载TCP/IP协议栈

news2024/9/25 19:25:09

Linux内核初始化过程中加载TCP/IP协议栈

Linux内核初始化过程中加载TCP/IP协议栈,从start_kernel、kernel_init、do_initcalls、inet_init,找出Linux内核初始化TCP/IP的入口位置,即为inet_init函数。

Linux内核启动过程

之前的实验中我们设置了断点start_kernel,start_kernel即是Linux内核的起点,相当于我们普通C程序的main函数,我们知道C语言代码从main函数开启动,C程序的阅读也从main函数开始。这个start_kernel也是整个Linux内核启动的起点,我们可以在内核代码路面init/main.c中找到start_kernel函数,这个地方就是初始化Linux内核启动的起点。

我们知道如何跟踪内核代码运行过程的话,我们应该有目的的来跟踪它。我们来跟踪内核启动过程,并重点找出初始化TCP/IP协议栈的位置。

首先我们找到内核启动的起点start_kernel函数所在的main.c,我们简单浏览一下start_kernel函数,这里有很多其他的模块初始化工作,因为这里边每一个启动的点都涉及到比较复杂的模块,因为内核非常庞大,包括很多的模块,当然如果你研究内核的某个模块的话,往往都需要了解main.c中start_kernel这一块,因为内核的主要模块的初始化工作,都是直接或间接从start_kernel函数里开始调用的。涉及到的模块太多太复杂,那我们只看我们需要了解的东西,这里边有很多setup设置的东西,这里边有一个trap_init函数调用,涉及到一些初始化中断向量,可以看到它在set_intr_gate设置到很多的中断门,很多的硬件中断,其中有一个系统陷阱门,进行系统调用的。其他还有mm_init内存管理模块的初始化等等。start_kernel中的最后一句为rest_init,这个比较有意思。内核启动完了之后,有一个call_cpu_idle,当系统没有进程需要执行时就调用idle进程。rest_init是0号进程,它创建了1号进程init和其他的一些服务进程。这就是内核的启动过程,我们先简单这样看,然后可以在重点找出网络初始化以及初始化TCP/IP协议栈的位置。下面我们再分析一下关键的函数。

start_kernel()

main.c 中没有 main 函数,start_kernel() 相当于main函数。start_kernel是一切的起点,在此函数被调用之前内核代码主要是用汇编语言写的,完成硬件系统的初始化工作,为C代码的运行设置环境。由调试可得start_kernel在/linux-src/init/main.c#500:

500asmlinkage __visible void __init start_kernel(void)
501{
...
679	/* Do the rest non-__init'ed, we're now alive */
680	rest_init();
681}

rest_init()函数

rest_init在linux-src/init/main.c#393的位置:

393static noinline void __init_refok rest_init(void)
394{
395	int pid;
396
397	rcu_scheduler_starting();
398	/*
399	 * We need to spawn init first so that it obtains pid 1, however
400	 * the init task will end up wanting to create kthreads, which, if
401	 * we schedule it before we create kthreadd, will OOPS.
402	 */
403	kernel_thread(kernel_init, NULL, CLONE_FS);
404	numa_default_policy();
405	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
406	rcu_read_lock();
407	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
408	rcu_read_unlock();
409	complete(&kthreadd_done);
410
411	/*
412	 * The boot idle thread must execute schedule()
413	 * at least once to get things moving:
414	 */
415	init_idle_bootup_task(current);
416	schedule_preempt_disabled();
417	/* Call into cpu_idle with preempt disabled */
418	cpu_startup_entry(CPUHP_ONLINE);
419}

通过rest_init()新建kernel_initkthreadd内核线程。403行代码 kernel_thread(kernel_init, NULL, CLONE_FS);,由注释得调用 kernel_thread()创建1号内核线程(在kernel_init函数正式启动),kernel_init函数启动了init用户程序。

另外405行代码 pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);调用kernel_thread执行kthreadd,创建PID为2的内核线程。

rest_init()最后调用cpu_idle() 演变成了idle进程。

Linux内核是如何加载TCP/IP协议栈的?

kernel_init函数和do_basic_setup函数

kernel_init函数的主要工作是夹在init用户程序,但是在加载init用户程序前通过kernel_init_freeable函数进一步做了一些初始化的工作。kernel_init函数和kernel_init_freeable函数:

930static int __ref kernel_init(void *unused)
931{
932	int ret;
933
934	kernel_init_freeable();
935	/* need to finish all async __init code before freeing the memory */
936	async_synchronize_full();
937	free_initmem();
938	mark_rodata_ro();
939	system_state = SYSTEM_RUNNING;
940	numa_default_policy();
941
942	flush_delayed_fput();
943
944	if (ramdisk_execute_command) {
945		ret = run_init_process(ramdisk_execute_command);
946		if (!ret)
947			return 0;
948		pr_err("Failed to execute %s (error %d)\n",
949		       ramdisk_execute_command, ret);
950	}
951
952	/*
953	 * We try each of these until one succeeds.
954	 *
955	 * The Bourne shell can be used instead of init if we are
956	 * trying to recover a really broken machine.
957	 */
958	if (execute_command) {
959		ret = run_init_process(execute_command);
960		if (!ret)
961			return 0;
962		pr_err("Failed to execute %s (error %d).  Attempting defaults...\n",
963			execute_command, ret);
964	}
965	if (!try_to_run_init_process("/sbin/init") ||
966	    !try_to_run_init_process("/etc/init") ||
967	    !try_to_run_init_process("/bin/init") ||
968	    !try_to_run_init_process("/bin/sh"))
969		return 0;
970
971	panic("No working init found.  Try passing init= option to kernel. "
972	      "See Linux Documentation/init.txt for guidance.");
973}
974
975static noinline void __init kernel_init_freeable(void)
976{
977	/*
978	 * Wait until kthreadd is all set-up.
979	 */
980	wait_for_completion(&kthreadd_done);
981
...
1004	do_basic_setup();
1005
...
1033}
1034

kernel_init_freeable函数做的一些初始化的工作与我们网络初始化有关的主要在do_basic_setup函数中,其中do_initcalls用一种巧妙的方式对一些子系统进行了初始化,其中包括TCP/IP网络协议栈的初始化。

867/*
868 * Ok, the machine is now initialized. None of the devices
869 * have been touched yet, but the CPU subsystem is up and
870 * running, and memory and process management works.
871 *
872 * Now we can finally start doing some real work..
873 */
874static void __init do_basic_setup(void)
875{
876	cpuset_init_smp();
877	usermodehelper_init();
878	shmem_init();
879	driver_init();
880	init_irq_proc();
881	do_ctors();
882	usermodehelper_enable();
883	do_initcalls();
884	random_int_secret_init();
885}

do_initcalls函数巧妙地对网络协议进行初始化

do_initcalls函数是table驱动的,维护了一个initcalls的table,从而可以对每一个注册进来的初始化项目进行初始化,这个巧妙的机制可以理解成观察者模式,每一个协议子系统是一个观察者,将它的初始化入口注册进来,do_initcalls函数是被观察者负责统一调用每一个子系统的初始化函数指针。

859static void __init do_initcalls(void)
860{
861	int level;
862
863	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
864		do_initcall_level(level);
865}

以TCP/IP协议栈为例,inet_init函数是TCP/IP协议栈初始化的入口函数,通过fs_initcall(inet_init)将inet_init函数注册进initcalls的table。

1674static int __init inet_init(void)
1675{
...
1795}
1796
1797fs_initcall(inet_init);

这里do_initcalls的注册和调用机制是通过复杂的宏来实现的,代码读起来非常晦涩,这里我们换一种方法通过跟踪代码运行过程来验证它。

我们首先将端点设在kernel_init、do_initcalls、inet_init以及do_initcalls后面的random_int_secret_init,预期这四个断点会依次触发,从而可以间接验证fs_initcall(inet_init)确实将inet_init注册进了do_initcalls并被do_initcalls调用执行了。

在lab3目录下执行qemu -kernel …/…/linux-src/arch/x86/boot/bzImage -initrd …/rootfs.img -s -S

shiyanlou:~/ $ cd LinuxKernel                                        [14:08:18]
shiyanlou:LinuxKernel/ $ git clone https://github.com/mengning/linuxnet.git
\u6b63\u514b\u9686\u5230 'linuxnet'...
remote: Enumerating objects: 175, done.
remote: Counting objects: 100% (175/175), done.
remote: Compressing objects: 100% (151/151), done.
remote: Total 175 (delta 100), reused 47 (delta 21), pack-reused 0
\u63a5\u6536\u5bf9\u8c61\u4e2d: 100% (175/175), 4.57 MiB | 2.58 MiB/s, done.
\u5904\u7406 delta \u4e2d: 100% (100/100), done.
\u68c0\u67e5\u8fde\u63a5... \u5b8c\u6210\u3002
shiyanlou:LinuxKernel/ $ cd linuxnet/lab3                            [14:08:38]
shiyanlou:lab3/ (master) $ make rootfs                               [14:08:38]
gcc -o init linktable.c menu.c main.c -m32 -static -lpthread
find init | cpio -o -Hnewc |gzip -9 > ../rootfs.img
1889 \u5757
qemu -kernel ../../linux-src/arch/x86/boot/bzImage -initrd ../rootfs.img
shiyanlou:lab3/ (master*) $ qemu -kernel ../../linux-src/arch/x86/boot/bzImage -initrd ../rootfs.img -s -S

在另一个窗口执行gdb并依次执行如下gdb命令:

(gdb) file ../../linux-src/vmlinux
Reading symbols from ../../linux-src/vmlinux...done.
(gdb) target remote:1234
Remote debugging using :1234
0x0000fff0 in ?? ()
(gdb) b kernel_init
Breakpoint 1 at 0xc1740240: file init/main.c, line 931.
(gdb) b do_initcalls
Breakpoint 2 at 0xc1a2fc2f: file init/main.c, line 851.
(gdb) b inet_init
Breakpoint 3 at 0xc1a76de3: file net/ipv4/af_inet.c, line 1675.
(gdb) b random_int_secret_init
Breakpoint 4 at 0xc132dbf0: file drivers/char/random.c, line 1712.

这样我们就设置好了验证的系统环境,如图:
alt

依次按c让Linux内核从断点处继续执行,可以看到Linux内核依次断点在kernel_init、do_initcalls、inet_init以及do_initcalls后面的random_int_secret_init,如下输出信息与我们的预期是一致的,fs_initcall(inet_init)确实将inet_init注册进了do_initcalls并被do_initcalls调用执行了。

(gdb) c
Continuing.

Breakpoint 1, kernel_init (unused=0x0) at init/main.c:931
931	{
(gdb) c
Continuing.

Breakpoint 2, kernel_init_freeable () at init/main.c:1004
1004		do_basic_setup();
(gdb) c
Continuing.

Breakpoint 3, inet_init () at net/ipv4/af_inet.c:1675
1675	{
(gdb) c
Continuing.

Breakpoint 4, random_int_secret_init () at drivers/char/random.c:1712
1712	{
(gdb) 

到这里我们就找到了Linux内核初始化TCP/IP的入口位置,即为inet_init函数。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2126062.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

差值图中像素总和的正负性(极性)含义

1. 正总和的含义 当 I1 减去 I2 的差值图总和为正时&#xff0c;这意味着整体上 I1 的像素值比 I2 高。即&#xff1a; 表示&#xff1a; 2. 局部亮度变化 即便差值图的总和为正&#xff0c;也不能确保 I1 在所有区域都比 I2 亮。差值图的正总和只是表明在整个图像中&#xf…

webpack5 创建多页面应用配置

简单版webpack创建多页面应用&#xff0c;只要把配置文件复制下来&#xff0c;然后npm安装相应插件&#xff0c;正常是能跑起来了 创建 初始化 npm init生成package.json文件安装webpack npm i -D webpack webpack-cli webpack-dev-server创建main.js入口文件和webpack.config…

云计算实训48——k8s环境搭建(详细版)

1.创建主机、设置ip、设置hostname 2.设置免密登录 # 生成私钥 [rootk8s-master ~]# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): /root/.ssh/id_rsa already exists. Overwrite (y/n)? y Enter passphr…

智能化转型的基石:精心策划楼宇自控系统的选择与部署

智能化转型的基石&#xff1a;精心策划楼宇自控系统的选择与部署 在智慧城市的宏伟蓝图中&#xff0c;建筑智能化已成为推动城市进步的强劲动力。楼宇自控系统&#xff0c;作为这一进程中的核心组件&#xff0c;其选择与部署策略不仅关乎建筑内部的运营效率与能源管理&#xff…

十大排序算法的特点及应用场景

一.十大经典排序算法介绍 1. 冒泡排序&#xff08;Bubble Sort&#xff09; 原理&#xff1a;通过重复遍历要排序的数列&#xff0c;一次比较两个元素&#xff0c;如果它们的顺序错误就把它们交换过来。遍历数列的工作是重复进行的&#xff0c;直到没有再需要交换的元素为止。…

视频编辑SDK解决方案,完整的前端代码+SDK功能交付

繁琐的视频编辑过程往往成为创作者们提升作品质量的瓶颈&#xff0c;美摄科技凭借其深厚的AI技术积累与创新的移动端视频编辑SDK解决方案&#xff0c;正引领着视频编辑领域的新潮流&#xff0c;让每一位创作者都能轻松驾驭创意&#xff0c;实现从灵感闪现到作品呈现的无缝对接。…

点餐小程序实战教程05登录界面搭建

目录 1 设置tab栏2 添加页面3 搭建登录界面3.1 显示头像3.2 显示昵称3.3 注册按钮 总结 上一篇我们讲解了如何在首页加载的时候获取用户信息&#xff0c;一般小程序会在底部放置tab栏&#xff0c;将不同的菜单放置在tab栏中。我们的用户注册逻辑是&#xff0c;如果用户点击了底…

Java码农人生开启手册——多态与重写

一、多态 概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff1b;具体点&#xff0c;就是去完成某个行为&#xff0c;当不同的对象去完成时会产生不同的状态。 1、多态的实现条件 三个条件&#xff0c;缺一不可&#xff1a; 必须在继承体系下子类必须对父类中方…

训练 Vision Transformer 模型并运行推理

目录 CV Architecture ViT and U-Net Training ViT Florence-2 Load Model Load images CV Scenarios test Genarate CAPTION from the images DENSE REGION CAPTION and REGION_PROPOSA Caption to Phrase Grounding Bounding boxes OCR test Fine Tuning Floren…

2024年转行做网络安全工程师还来得及吗?薪资怎么样呢

&#x1f91f; 基于入门网络安全/黑客打造的&#xff1a;&#x1f449;黑客&网络安全入门&进阶学习资源包 2022年以来&#xff0c;我国网络安全行业的市场规模持续增长&#xff0c;根据市场调研在线网发布的2023-2029年中国网络安全集成行业市场运行态势及发展趋向分析…

Pycharm配置ssh远程服务器解析器

算法学习、4对1辅导、论文辅导或核心期刊可以通过公众号滴滴我 文章目录 需求配置流程 需求 之前在开发中&#xff0c;Pycharm都是通过本机Python环境来解析。但有时候&#xff0c;可能受限于本机电脑配置原因&#xff0c;导致运行速度并不快。因此推荐大家尝试下&#xff0c…

一文带你彻底掌握二分查找

1. 认识二分查找 二分查找也被称为折半查找&#xff0c;他是一种查询效率较高的查找方式&#xff0c;普通查找的方式通常是从头到尾遍历一遍数组&#xff0c;二分查找的方式是找到数组中间的那个元素mid与目标值target进行比较&#xff0c;比target小就去前半段找&#xff0c;…

Java中List集合去重

反问问题&#xff1a;为什么不直接使用 Set 或者 LinkedHashSet 呢 实际场景&#xff1a;实际的业务开发中遇到的情况会更复杂。比如&#xff0c;List 集合可能是历史遗留问题&#xff0c;也有可能是调用接口返回的类型限制&#xff0c;只能使用 List 接收&#xff0c;又或者是…

Qualcomm Linux 交叉编译应用程序

1. 前提条件 Ubuntu 20.04 系统 Qualcomm RB3 Gen2开发板 2.下载并安装 eSDK 平台 1.从 Qualcomm 发布存档平台下载 eSDK。 wget https://artifacts.codelinaro.org/artifactory/qli-ci/flashable-binaries/qimpsdk/qcm6490/x86/qcom-6.6.28-QLI.1.1-Ver.1.1_qim-product-s…

消除数字球-第15届蓝桥省赛Scratch初级组真题第5题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第184讲。 如果想持续关注Scratch蓝桥真题解读&#xff0c;可以点击《Scratch蓝桥杯历年真题》并订阅合集&#xff0c;…

python程序使用nohup后台执行不能实时输出到定向文件的解决方法

问题描述&#xff1a;使用nohup命令后台执行python&#xff0c;但python中print方法打印结果不能实时输出到nohup后台定向文件&#xff0c;只能在程序结束时一次性输出。典型问题样例&#xff1a;在python中使用了os.system(command)方法&#xff0c;command命令打印的结果可以…

免费爬虫软件“HyperlinkCollector超链采集器v0.1”

HyperlinkCollector超链采集器单机版v0.1 软件采用python的pyside2和selenium开发,暂时只支持window环境&#xff0c;抓取方式支持普通程序抓取和selenium模拟浏览器抓取。软件遵守robots协议。 首先下载后解压缩&#xff0c;然后运行app目录下的HyperlinkCollector.exe 运行…

网页与App无缝衔接,揭秘拉起应用的黑科技!

随着移动互联网的飞速发展&#xff0c;App已经成为了我们日常生活中不可或缺的一部分。然而&#xff0c;在推广和运营App的过程中&#xff0c;如何让用户更便捷地从网页跳转到App&#xff0c;一直是困扰推广者的难题。今天&#xff0c;我们就来聊聊网页拉起应用这一黑科技&…

开源 AI 智能名片 S2B2C 商城小程序中的全渠道供应策略

摘要&#xff1a;本文深入探讨在开源 AI 智能名片 S2B2C 商城小程序的情境下&#xff0c;全渠道供应的运行机制。阐述各环节企业相互配合的重要性&#xff0c;重点分析零售企业在其中的关键作用&#xff0c;包括协调工作、信息传递、需求把握等方面&#xff0c;旨在实现高效的全…

Python中的上下文管理器:提升代码的优雅与安全

在编写Python程序时&#xff0c;处理资源&#xff08;如文件、网络连接、数据库会话等&#xff09;的正确打开和关闭至关重要。不当的资源管理可能导致内存泄漏、数据损坏等问题。幸运的是&#xff0c;Python提供了一种优雅的方式来解决这个问题——上下文管理器。本文将探讨上…