Linux内核初始化过程中加载TCP/IP协议栈
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_init
、kthreadd
内核线程。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.
这样我们就设置好了验证的系统环境,如图:
依次按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函数。
相关文章:

Linux内核初始化过程中加载TCP/IP协议栈
Linux内核初始化过程中加载TCP/IP协议栈 Linux内核初始化过程中加载TCP/IP协议栈,从start_kernel、kernel_init、do_initcalls、inet_init,找出Linux内核初始化TCP/IP的入口位置,即为inet_init函数。 Linux内核启动过程 之前的实验中我们设…...

Mysql树形结构表-查询所有子集数据
表结构,这里只是个例子,所有的树形结构表均可用: CREATE TABLE zhkt_course_chapter (id bigint NOT NULL COMMENT 唯一id,course_id bigint NOT NULL COMMENT 所属课程id,name varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general…...

Vue 3 Composition API进阶指南
在上一篇文章中,我们介绍了Vue 3的Composition API基础,包括如何使用setup函数、ref和reactive来创建响应式数据,以及使用watchEffect来监控数据变化。本文将继续深入探讨Composition API的高级用法,帮助你更好地理解和利用Vue 3的…...

C++学习,多继承
多继承,一个子类可以有多个父类,它继承了多个父类的特性。这种机制提供了强大的灵活性,但也带来了复杂性,特别是当涉及到基类中的同名成员(包括成员函数和变量)时。 C 类从多个类继承成员,语法如…...

苹果研究人员提出了一种新颖的AI算法来优化字节级表示以自动语音识别(ASR),并将其与UTF-8表示进行比较
端到端(E2E)神经网络已成为多语言自动语音识别(ASR)的灵活且准确的模型。然而,随着支持的语言数量增加,尤其是像中文、日语、韩语(CJK)这样大字符集的语言,输出层的大小显…...

2024年重磅报告!国内AI大模型产业飞速发展!
伴随人工智能技术的加速演进,AI 大模型已成为全球科技竞争的新高地、未来产业的新赛道、经济发展的新引擎,发展潜力大、应用前景广。近年来,我国高度重视人工智能的发展,将其上升为国家战略,出台一系列扶持政策和规划&…...

Sentinel 安装
一、下载jar包 下载地址:Releases alibaba/Sentinel GitHub 二、运行 将jar包放在任意非中文、不包含特殊字符的目录下,启动 启动命令:运行cmd 使用一下命令 java -Dserver.port8090 -Dcsp.sentinel.dashboard.serverlocalhost:8090 -D…...

大佬,简单解释下“嵌入式软件开发”和“嵌入式硬件开发”的区别
在开始前刚好我有一些资料,是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」, 点个关注在评论区回复“888”之后私信回复“888”,全部无偿共享给大家!!!首先,嵌入式硬…...

04 奇偶分家
题目: 代码: #include<iostream> using namespace std; #include<stdlib.h> #include<stdio.h>int main() {int N;cin>>N;int jicount0,oucount0;for(int i0;i<N;i){int temp;cin>>temp;if(temp%20){oucount;}else if…...

普通人秒变AI专家:李沐创业同款RAG微调实战,打造专属外卖评论大模型
8月14日晚上,李沐发布了一篇关于他创业一年的复盘文章《创业一年,人间三年》,引起了广泛关注。这篇文章中,李沐分享了从创业初期到现在的心路历程,许多读者读后都倍感激动。 创业之初,李沐的团队原本打算利用大语言模型(LLM)开发生产力工具。然而,在张一鸣的建议下,…...

微模块冷通道动环监控:智能化数据中心管理利器@卓振思众
在现代数据中心和机房管理中,微模块冷通道动环监控系统的引入,标志着对冷却和环境管理的新纪元。这一系统不仅提升了数据中心的运维效率,还对设备的安全性和稳定性提供了强有力的保障。本文将详细探讨微模块冷通道动环监控的功能和其在数据中…...

【Linux】进程调度与切换
【Linux】进程调度与切换 1. 基本概念2. 进程切换3. 进程调度3.1运行队列实现优先级设计3.2 处理效率问题3.3 活动队列与过期队列3.4 如何解决饥饿问题3.5 active指针和expired指针 1. 基本概念 竞争性: 系统进程数目众多,而CPU资源只有少量,甚至1个&am…...

SAM 2:分割图像和视频中的任何内容
文章目录 摘要1 引言2 相关工作3 任务:可提示视觉分割4 模型5 数据5.1 数据引擎5.2 SA-V数据集6 零样本实验6.1 视频任务6.1.1 提示视频分割6.1.2 半监督视频对象分割6.1.3 公平性评估6.2 图像任务7 与半监督VOS的最新技术的比较8 数据和模型消融8.1 数据消融8.2 模型架构消融…...

【免越狱】iOS任意版本号APP下载
下载地址 https://pan.quark.cn/s/570e928ee2c4 软件介绍 下载iOS旧版应用,简化繁琐的抓包流程。一键生成去更新IPA(手机安装后,去除App Store的更新检测)。 软件界面 使用方法 一、直接搜索方式 搜索APP,双击选…...

告别植物神经紊乱,这5种运动让你身心平衡,活力满满!♀️✨
Hey小伙伴们~👋 最近是不是感觉压力山大,晚上辗转反侧,白天又无精打采?😴😔 这可能是植物神经紊乱在悄悄作祟哦!别怕,今天就来给大家种草几个超有效的运动方式,帮你找回那…...

又一个iPhone时代开始
今年的苹果秋季发布会在昨晚召开了,今天早上我们也看到了很多相关的新闻。我猜你看完后的感觉可能是,这不过又是一次普普通通的参数升级。又是提升了百分之多少,又是增加了多少倍——非常简单的一些更新。比如说芯片升级了、相机的摄像头一会…...

在 CentOS 中永久关闭防火墙的步骤
在 CentOS 中永久关闭防火墙的步骤 在 CentOS 系统中,防火墙通常由 firewalld 服务管理。如果你希望在系统中永久关闭防火墙,可以按照以下步骤操作: 1. 停止防火墙服务 首先,你需要停止当前正在运行的防火墙服务。可以使用以下…...

【数据库】详解基本SQL语句用法
一、SELECTING DATA FROM TABLES【查询数据】 SELECT命令是表上所有查询的基础,因此给出它的完整描述以显示它的功能。在描述之后提供各种格式的示例。 1.1 整体描述 SELECT column1, column2, ... FROMtable1 [INNER | LEFT | RIGHT] JOIN table2 on conditions…...

R语言地理加权回归、主成份分析、判别分析等空间异质性数据分析
在自然和社会科学领域有大量与地理或空间有关的数据,这一类数据一般具有严重的空间异质性,而通常的统计学方法并不能处理空间异质性,因而对此类型的数据无能为力。以地理加权回归为基础的一系列方法:经典地理加权回归,…...

数学建模笔记—— 非线性规划
数学建模笔记—— 非线性规划 非线性规划1. 模型原理1.1 非线性规划的标准型1.2 非线性规划求解的Matlab函数 2. 典型例题3. matlab代码求解3.1 例1 一个简单示例3.2 例2 选址问题1. 第一问 线性规划2. 第二问 非线性规划 非线性规划 非线性规划是一种求解目标函数或约束条件中…...

JavaScript中的控制流语句:break、continue、return、throw
在JavaScript编程中,控制流语句是控制代码执行流程的重要工具。这些语句包括break、continue、return和throw,它们可以在循环、函数以及其他代码块中使用,以改变正常的执行顺序。下面我们将逐一探讨这些语句的用途和示例。 break break语句…...

移动通信为啥要用双极化天线?
❝本文简单介绍下移动通信为啥要用双极化天线及其简单概述。 移动通信为啥要用双极化天线? - RFASK射频问问❝本文简单介绍下移动通信为啥要用双极化天线及其简单概述。什么是极化?电磁波的极化通常是用其电场矢量的空间指向来描述:在空间某…...

C语言从头学59——学习头文件math.h(二)
继续学习头文件<math.h>,编号接续前文。 六、三角函数 math.h中的三角函数包括: acos():反余弦,参数范围-1至1,返回值double类型:0~PI asin():反正弦,参…...

Leetcode 3283. Maximum Number of Moves to Kill All Pawns
Leetcode 3283. Maximum Number of Moves to Kill All Pawns 1. 解题思路2. 代码实现 题目链接:3283. Maximum Number of Moves to Kill All Pawns 1. 解题思路 这一题坦率地说没有想到什么好的思路,因此只能非常暴力地按照题意进行了一下构造。 显然…...

智能物流新“黑神话”:各位“天命人”,这份行业应用锦集请收下!
全球工业革新浪潮中,智能物流正成为制造业转型升级的核心驱动力之一。高柔性的智能物流解决方案可以帮助企业应对复杂的物流挑战,实现生产到仓储全过程的智能化、柔性化和高度集成,带来显著的经济效益。 作为行业领先的全场景柔性物流综合解…...

SpringSecurity原理解析(五):HttpSecurity 类处理流程
1、SpringSecurity 在spring boot中与SSM项目中基于配置文件的区别 通过前边的笔记我们可以知道,在传统的SSM项目中 SpringSecurity的使用是基于配置文件 的,然后spring 容器初始化的时候将 SpringSecurity 中的各种标签解析成对应的Bean对象,…...

C++系列-匿名对象
匿名对象 💢什么是匿名对象💢匿名对象的创建方式及作用域💢匿名对象的对象类型💢💢匿名的基本数据类型对象💢💢匿名的自定义的类类型对象💢💢匿名的标准库的类对象 &…...

tofixed和math.round什么区别
1、floor 返回不大于的最大整数(向下取整) 2、round 则是4舍5入的计算,入的时候是到大于它的整数(当-1.5时可见,四舍五入后得到的结果不是我们期待的,解决办法是先对他取绝对值,然后在用round方…...

OPENAIGC开发者大赛高校组金奖 | 基于混合大语言模型与多模态的全过程通用AI Agent
在第二届拯救者杯OPENAIGC开发者大赛中,涌现出一批技术突出、创意卓越的作品。为了让这些优秀项目被更多人看到,我们特意开设了优秀作品报道专栏,旨在展示其独特之处和开发者的精彩故事。 无论您是技术专家还是爱好者,希望能带给您…...

MySql批量迁移数据库
导出数据库 将指定数据库实例(MYSQL_HOST、MYSQL_PORT、MYSQL_USER、MYSQL_PASSWORD)中的所有数据库(表结构、数据)导出到指定目录(BACKUP_DIR)下的多个单独的SQL脚本,每个SQL脚本名称即为数据…...