内核代码段的起始地址 _text :
cat /proc/kallsyms | grep '\<_text\>'
内核代码段的结束地址 _etext:
cat /proc/kallsyms | grep '\<_etext\>'

内核代码段的大小:
size = _etext - _text
/proc/kallsyms中的符号从 _text到 _etext 都是 T 和 t 类型:
"T/t" :The symbol is in the text (code) section.


也会有少量的 W/w符号:
"W/w" The symbol is a weak symbol that has not been specifically tagged as a weak object symbol.
_etext - _text之间的W/w符号都是weak text symbol。
可以看到下面的符号位于_etext - _text之间,也是代码段符号,只不过是weak text symbol。

从/proc/kcore中也可以看到内核段,其起始地址与_text一样,但大小却大于上述的 size = _etext - _text,该内核段除了有内核代码段还有内核其他数据。

从_etext开始都是内核的其他的一些符号:
比如只读数据符号:
"R/r" The symbol is in a read only data section

将上述 _text + memsize = 0xffffffff81000000 + 0x0000000001411000 = 0xffffffff82411000

内核段的最后是BSS data section。
"B/b" The symbol is in the BSS data section. This section typically contains zero-initialized or uninitialized data, although the exact behavior is system dependent.
0xffffffff82411000就是内核段的结尾,对于/proc/kallsyms中的符号,从0xffffffff82411000之后就是模块的符号,因此模块的符号地址一定大于内核段的符号。
从内核段开始即内核代码段起始地址_text(T) 到 _end(B) 到内核段结尾,符号都是依次递增,从小到大排列。
内核段中的大致存放顺序:
"T/t" -> "R/r" -> "D/d" -> "B/b"
即:内核代码段 ,内核只读数据段,内核数据段,内核BSS段。这些相同的符号类型都是在同一块区域,地址大小依次递增。
同时对应的内核代码段 ,内核只读数据段,内核数据段,内核BSS段在物理内存上的布局也是这样:
centos 7(3.10.0) 的内核只读数据段,内核数据段在物理内存上布局合在一起:
/proc/iomem是整个系统物理地址空间的映射文件。

ubuntu 20.04 (5.15.0):

以下都是指内核段的虚拟地址空间布局:
(1)内核代码段:
static inline int init_kernel_text(unsigned long addr)
{
if (addr >= (unsigned long)_sinittext &&
addr <= (unsigned long)_einittext)
return 1;
return 0;
}
int core_kernel_text(unsigned long addr)
{
if (addr >= (unsigned long)_stext &&
addr <= (unsigned long)_etext)
return 1;
if (system_state == SYSTEM_BOOTING &&
init_kernel_text(addr))
return 1;
return 0;
}
"T/t" The symbol is in the text (code) section.

备注:有些地址有多个符号,比如上述的 0xffffffff81000000,有_text 和 startup_64。
当我们求内核代码段中函数的大小时,就可以用上下两条记录的地址值相减,就可以求出内核函数的大小,当然要考虑到有些地址有多个符号的情况。
此方法只适用求内核代码段函数的大小,不适求模块函数的大小。
(2)内核notes
内核代码段和内核只读数据段直接还有一小块 notes 代码段,这一小部分也属于代码段,内核正常运行时并不会调用这部分代码段。
ffffffff9d77c25f T _etext
ffffffff9d77c260 T __start_notes
ffffffff9d77c3dc T __stop_notes
ffffffff9d77c3e0 R __start___ex_table
ffffffff9d77e430 R __start___mc_table
ffffffff9d77e430 R __stop___ex_table
(3)内核只读数据段:
"R/r" The symbol is in a read only data section

当我们求内核数据段中数据的大小时,可以用上下两条记录的地址值相减,就可以求出内核数据的大小,比如系统调用表:
ffffffffad4028f0 r __func__.50507
ffffffffad402910 r __func__.50517
ffffffffad402940 R sys_call_table
ffffffffad4033a0 r __func__.37275
ffffffffad4033b2 r str__vsyscall__trace_system_name
ffffffffad4033c0 r setup_data_attr_group
(ffffffffad4033a0 - ffffffffad402940) = 0xa60 = 2656
即系统调用表的大小是2656字节
2656 / 8 = 332
系统调用表数组中的成员数量是332,即该版本系统调用的数目是332。
(4)内核数据段:
/**
* core_kernel_data - tell if addr points to kernel data
* @addr: address to test
*
* Returns true if @addr passed in is from the core kernel data
* section.
*
* Note: On some archs it may return true for core RODATA, and false
* for others. But will always be true for core RW data.
*/
int core_kernel_data(unsigned long addr)
{
if (addr >= (unsigned long)_sdata &&
addr < (unsigned long)_edata)
return 1;
return 0;
}
"D/d" The symbol is in the initialized data section.

(5)内核BSS段:
"B/b" The symbol is in the BSS data section. This section typically contains zero-initialized or uninitialized data, although the exact behavior is system dependent.

(6)
其中"D/d" -> “B/b” 之间有一部分内核启动时,内核退出时所用到的代码段,内核正常运行后,不会在调用这一部分内核代码段。
这一部分也属于内核段,并且属于内核代码段,但是这部分是内核启动,内核退出时用到的代码段,内核正常运行时并不会调用,这部分内核代码段没有在 _text 到 _etext 之间。
_sinittext到_einittext是内核初始化时运行的代码,内核正常运行时并不会调用。
system_state == SYSTEM_BOOTING
/* Values used for system_state */
extern enum system_states {
SYSTEM_BOOTING,
SYSTEM_RUNNING,
SYSTEM_HALT,
SYSTEM_POWER_OFF,
SYSTEM_RESTART,
} system_state;
static inline int init_kernel_text(unsigned long addr)
{
if (addr >= (unsigned long)_sinittext &&
addr <= (unsigned long)_einittext)
return 1;
return 0;
}
int core_kernel_text(unsigned long addr)
{
if (addr >= (unsigned long)_stext &&
addr <= (unsigned long)_etext)
return 1;
if (
&&
init_kernel_text(addr))
return 1;
return 0;
}
ffffffff9dd68000 D __init_begin
ffffffff9dd68000 D __per_cpu_load
ffffffff9dd85000 T early_idt_handler_array
ffffffff9dd85000 T _sinittext
ffffffff9dd85120 t early_idt_handler_common
ffffffff9dd851d7 t is_nmi
ffffffff9dd851f0 T startup_xen
ffffffff9dd85205 t copy_bootdata
ffffffff9dd853c1 t reset_early_page_tables
ffffffff9dd85496 T __early_make_pgtable
ffffffff9dd856e8 T early_make_pgtable
ffffffff9dd8570b T x86_64_start_reservations
ffffffff9dd85731 T x86_64_start_kernel
ffffffff9dd858a8 T reserve_ebda_region
ffffffff9dd85904 t set_reset_devices
ffffffff9dd85919 t debug_kernel
ffffffff9dd8592b t quiet_kernel
ffffffff9dd8593d t init_setup
ffffffff9dd85965 t rdinit_setup
ffffffff9dd8598d t loglevel
ffffffff9dd859e1 t do_early_param
ffffffff9dd85a6f t initcall_blacklist
ffffffff9dd85b1f t repair_env_string
ffffffff9dd85b7b t unknown_bootoption
ffffffff9dd85d04 T parse_early_options
ffffffff9dd85d2c T parse_early_param
ffffffff9dd85d69 W smp_setup_processor_id
ffffffff9dd85d6f W thread_info_cache_init
ffffffff9dd85d7b T start_kernel
ffffffff9dd861e7 t kernel_init_freeable
ffffffff9dd86406 T load_default_modules
ffffffff9dd86411 t rootwait_setup
.......
ffffffff8a7eea46 t packet_init
ffffffff8a7eea88 t wireless_nlevent_init
ffffffff8a7eeac1 T netlbl_netlink_init
ffffffff8a7eeade t netlbl_init
ffffffff8a7eeb61 T netlbl_domhsh_init
ffffffff8a7eec18 T netlbl_mgmt_genl_init
ffffffff8a7eec2a T netlbl_unlabel_genl_init
ffffffff8a7eec3c T netlbl_unlabel_init
ffffffff8a7eed03 T netlbl_unlabel_defconf
ffffffff8a7eed9d T netlbl_cipsov4_genl_init
ffffffff8a7eedaf t dcbnl_init
ffffffff8a7eedfc T net_sysctl_init
ffffffff8a7eee4b t mpls_gso_init
ffffffff8a7eee79 T _einittext
......
ffffffff9df3be81 t nvmem_exit
ffffffff9df3be93 t vmd_drv_exit
ffffffff9df3bea5 t sock_diag_exit
ffffffff9df3beb7 t exit_cgroup_netprio
ffffffff9df3bf1d t exit_cgroup_cls
ffffffff9df3bf3b t xt_fini
ffffffff9df3bf59 t tcpudp_mt_exit
ffffffff9df3bf70 t ipv4_netfilter_fini
ffffffff9df3bf82 t cubictcp_unregister
ffffffff9df3bf94 t xfrm_user_exit
ffffffff9df3bfb2 t af_unix_exit
ffffffff9df3bfda t inet6_exit
ffffffff9df3c0b8 t packet_exit
ffffffff9df3c0ec t dcbnl_exit
ffffffff9df3c162 t mpls_gso_exit
ffffffff9df3d000 R __smp_locks
ffffffff9df3d000 R __init_end
ffffffff9df44000 D in_suspend
ffffffff9df44000 D __nosave_begin
ffffffff9df44000 R __smp_locks_end
ffffffff9df45000 B empty_zero_page
ffffffff9df45000 B __bss_start
ffffffff9df45000 D __nosave_end
ffffffff9df46000 b dummy_mapping
ffffffff9df47000 b level3_user_vsyscall
ffffffff9df48000 B idt_table
ffffffff9df49000 B debug_idt_table
ffffffff9df4a000 B trace_idt_table
ffffffff9df4b000 b bm_pte
ffffffff9df4c000 B initcall_debug
ffffffff9df4c004 B reset_devices
ffffffff9df4c008 B saved_command_line
ffffffff9df4c020 b msgbuf
(7)
通过读取vmlinux文件也可以发现,vmlinux 内核镜像 除了 _text到 _etext 这一块代码段之外,还有几块代码段:
[root@localhost ~]# readelf -S vmlinux
There are 36 section headers, starting at offset 0x13451a0:
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .text PROGBITS ffffffff81000000 00200000
000000000077c25f 0000000000000000 AX 0 0 4096
[ 2] .notes NOTE ffffffff8177c260 0097c260
000000000000017c 0000000000000000 AX 0 0 4
[ 3] __ex_table PROGBITS ffffffff8177c3e0 0097c3e0
0000000000002050 0000000000000000 A 0 0 8
[ 4] __mc_table PROGBITS ffffffff8177e430 0097e430
0000000000000024 0000000000000000 A 0 0 4
[ 5] .rodata PROGBITS ffffffff81800000 00a00000
000000000030777e 0000000000000000 A 0 0 128
[ 6] __bug_table PROGBITS ffffffff81b07780 00d07780
0000000000006318 0000000000000000 A 0 0 1
[ 7] __unwind_end_of_s PROGBITS ffffffff81b0da98 00d0da98
0000000000000068 0000000000000000 A 0 0 1
[ 8] __unwind_unsafe_s PROGBITS ffffffff81b0db00 00d0db00
00000000000001d0 0000000000000000 A 0 0 1
[ 9] .pci_fixup PROGBITS ffffffff81b0dcd0 00d0dcd0
00000000000038b8 0000000000000000 A 0 0 8
[10] .tracedata PROGBITS ffffffff81b11588 00d11588
000000000000003c 0000000000000000 A 0 0 1
[11] __ksymtab PROGBITS ffffffff81b115d0 00d115d0
0000000000011f20 0000000000000000 A 0 0 16
[12] __ksymtab_gpl PROGBITS ffffffff81b234f0 00d234f0
000000000000df80 0000000000000000 A 0 0 16
[13] __kcrctab PROGBITS ffffffff81b31470 00d31470
0000000000008f90 0000000000000000 A 0 0 8
[14] __kcrctab_gpl PROGBITS ffffffff81b3a400 00d3a400
0000000000006fc0 0000000000000000 A 0 0 8
[15] __ksymtab_strings PROGBITS ffffffff81b413c0 00d413c0
00000000000261c0 0000000000000000 A 0 0 1
[16] __init_rodata PROGBITS ffffffff81b67580 00d67580
00000000000000e8 0000000000000000 A 0 0 32
[17] __param PROGBITS ffffffff81b67668 00d67668
0000000000001d80 0000000000000000 A 0 0 8
[18] __modver PROGBITS ffffffff81b693e8 00d693e8
0000000000000c18 0000000000000000 A 0 0 8
[19] .data PROGBITS ffffffff81c00000 00e00000
0000000000166200 0000000000000000 WA 0 0 8192
[20] .vvar PROGBITS ffffffff81d67000 00f67000
00000000000000f0 0000000000000000 WA 0 0 16
[21] .data..percpu PROGBITS 0000000000000000 01000000
000000000001d000 0000000000000000 WA 0 0 4096
[22] .init.text PROGBITS ffffffff81d85000 01185000
0000000000069e79 0000000000000000 AX 0 0 16
[23] .init.data PROGBITS ffffffff81df0000 011f0000
0000000000124020 0000000000000000 WA 0 0 8192
[24] .x86_cpu_dev.init PROGBITS ffffffff81f14020 01314020
0000000000000018 0000000000000000 A 0 0 8
[25] .parainstructions PROGBITS ffffffff81f14038 01314038
000000000001d92c 0000000000000000 A 0 0 8
[26] .altinstructions PROGBITS ffffffff81f31968 01331968
00000000000069d8 0000000000000000 A 0 0 1
[27] .altinstr_replace PROGBITS ffffffff81f38340 01338340
000000000000217e 0000000000000000 AX 0 0 1
[28] .iommu_table PROGBITS ffffffff81f3a4c0 0133a4c0
00000000000000f0 0000000000000000 A 0 0 8
[29] .apicdrivers PROGBITS ffffffff81f3a5b0 0133a5b0
0000000000000028 0000000000000000 WA 0 0 8
[30] .exit.text PROGBITS ffffffff81f3a5d8 0133a5d8
0000000000001ba8 0000000000000000 AX 0 0 1
[31] .smp_locks PROGBITS ffffffff81f3d000 0133d000
0000000000007000 0000000000000000 A 0 0 4
[32] .data_nosave PROGBITS ffffffff81f44000 01344000
0000000000001000 0000000000000000 WA 0 0 4
[33] .bss NOBITS ffffffff81f45000 01345000
000000000030c000 0000000000000000 WA 0 0 4096
[34] .brk NOBITS ffffffff82251000 01345000
0000000000429000 0000000000000000 WA 0 0 1
[35] .shstrtab STRTAB 0000000000000000 01345000
000000000000019b 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
其中的下面几个 section 都不属于内核 text section,但都是vmlinux 镜像中的代码段:
[ 2] .notes NOTE ffffffff8177c260 0097c260
000000000000017c 0000000000000000 AX 0 0 4
[22] .init.text PROGBITS ffffffff81d85000 01185000
0000000000069e79 0000000000000000 AX 0 0 16
[27] .altinstr_replace PROGBITS ffffffff81f38340 01338340
000000000000217e 0000000000000000 AX 0 0 1
[30] .exit.text PROGBITS ffffffff81f3a5d8 0133a5d8
0000000000001ba8 0000000000000000 AX 0 0 1
X (execute),表示可执行,也就是代码段。
内核段的符号从代码段的符号_text 到 BSS段的符号_end,之后就是模块的相关符号了。模块的符号肯定大于内核段中的符号。
备注:模块的符号大于内核段中的符号这是相对于x86_64,对于ARM64,在Linux 4.6 中 将内核镜像移到vmalloc的区域了。因此在ARM64,Linux 4.6 以上的版本内核段中的符号都大于模块的符号。
比如:
5.4.18-74-generic aarch64 GNU/Linux
ffffffc010080000 T _text
......
ffffffc0117f4000 B _end
ffffffc0092dc380 d $d [virt_wifi]
ffffffc0092da024 r $d [virt_wifi]
ffffffc0092da024 r _note_6 [virt_wifi]
......
内核段的地址都大于模块的地址。
https://blog.csdn.net/weixin_45030965/article/details/125164642
https://blog.csdn.net/weixin_45030965/article/details/125055828
https://tinylab.org/lwn-531148/