linux/init/main.c(一)

本文详细介绍了Linux内核启动过程,从BIOS自检到MBR加载,再到系统引导程序GRUB的工作原理,以及内核如何加载并初始化硬件驱动程序。此外还探讨了initrd与initramfs的区别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

linux内核启动过程:

启动Linux内核的最终目的是使用Linux上的应用程序,这些应用程序可以是纯软件的,也可以是硬件相关的。

1.BIOS自检

BIOS(Basic Input/Output System)又称基本输入输出系统,现在的主板都使用一种叫Flash EPROM的芯片来存储系统BIOS,里面的内容可通过使用主板厂商提供的擦写程序擦除后重新写入,这样就给用户升级BIOS提供了极大的方便。

(1)上电自检POST(Power-on self test), 主要负责检测系统外围关键设备是否正常,例如,最常见的是内存松动的情况,BIOS自检阶段报错,系统就无法启动起来。

(2)步骤1成功后,便会执行一段小程序用来枚举本地设备并对其初始化。这一步主要是根据我们在BIOS中设置的系统启动顺序来搜索启动系统的驱动器,我们以硬盘为例,BIOS此时去读取硬盘驱动器的第一个扇区(MBR,512字节),然后执行里面的代码。至此,BIOS的任务就完成了,此后系统启动的控制权移交到MBR部分的代码。

在个人电脑中,Linux的启动是从0xFFFF0地址开始的。

2.系统引导

MBR是Master Boot Record的缩写,硬盘的0柱面、0磁头、1扇区称为主引导扇区。它由三部分组成:主引导程序(BootLoader)、硬盘分区表DPT(Disk Partition table)和硬盘有效标志(55AA)。

通常情况下,诸如lilo、grub这些常见的引导程序都直接安装在MBR中,我们以grub为例分析

(1)stage1:当控制权交给GRUB的代码,也就是MBR中446个字节空间中存放的代码,此时代码已被BIOS载入0x7c00处。这段代码的任务只是将硬盘0头0道2扇区读入内存。

(2)0头0道2扇区存放的代码的主要作用就是负责将stage2或stage1.5从硬盘中读到内存中。

stage1_5作为stage1和stage2中间的桥梁,stage1_5有识别文件系统的能力,此后grub才有能力云访问/boot分区/boot/grub目录下的stage2文件,将stage2载入内存并执行。

如果没有stage1_5,这里读取的是是/boot分区Boot Sector的stage2。这种情况下就有一个限制,因为这时是通过BIOS中断方式直接对硬盘寻址(而不是通过具体的文件系统),其寻址范围有限。

3.启动内核

当stage2被加载入内存执行后,它首先会云解析grub配置文件/boot/grub/grub.conf,然后加载内核镜像到内存中,并将控制权转交给内核。

关于Linux的设备驱动程序的加载,有一部分驱动程序直接被编译进内核镜像中,另一部分驱动程序则是以模块的形式放在initrd(ramdisk)中。

实际上Linux的内核镜像仅是包含了基本的硬件驱动,在系统安装过程中会检测系统硬件信息,根据安装信息和系统硬件信息将一部分设备驱动写入initrd。这样在以后启动系统时,一部分设备驱动就放在initrd中加载。

initrd的英文含义是bootloader initialized RAM disk,就是由boot loader初始化的内存盘。内核启动前,boot loader针将存储介质中的initrd文件加载到内存,内核启动时会在访问真正的根文件系统前先访问内存的initrd文件系统,在boot loader配置了initrd的情况下,内核启动被分成两个阶段,第一阶段先执行initrd文件系统中的init,完成加载驱动模块等任务,第二阶段才会执行真正的根文件系统中的/sbin/init进程。

另一个概念:initramfs

initramfs是在kernel 2.5中引入的技术,实际上它的含义就是:在内核镜像中附加一个cpio包,这个cpio包中包含了一个小型的文件系统,当内核启动时,内核将这个cpio包解开,并且将其中包含的文件系统释放到rootfs中,内核中的一部分初始化代码会放到这个文件系统中,作为用启层进程执行。这样带来的好处是精简了内核的初始化代码,而且使用得内核初始化过程更容易定制。

grub的stage2将initrd加载到内存里,然后将其中的内容释放到内容中,内核便去执行initrd中的init脚本,这时内核将控制权交给了init文件处理。我们简单浏览一下init脚本的内容,发现它也主要是加载各种存储介质相关的设备驱动程序。当所需的驱动程序加载完后,会创建一个根设备,然后将根文件系统rootfs以只读的方式挂载。这一步结束后,释放未使用的内存,转换到真正的根文件系统上面去,同时运行/sbin/init程序,执行系统的1号进程。此后系统的控制权就全权交给/sbin/init进程了。

1)执行系统初始化脚本(/etc/rc.d/rc.sysinit),对系统进行基本的配置,以读写方式挂载根文件系统及其它文件系统,到此系统算是基本运行起来了,后面需要进行运行级别的确定及相应服务的启动。

(1)获取网络环境与主机类型。首先会读取网络环境设置文件“/etc/sysconfig/network”,获取主机名称与默认网关等网格环境。

(2)测试与载入内存设备/proc及usb设备/sys。除了/proc外,系统会主动检测是否有usb设备,并主动加载usb驱动,尝试载入usb文件系统。

(3)决定是否启动SELinux。

(4)接口设备的检测与即插即用(pnp)参数的测试。

(5)用户自定义模块的加载。用户可以在/etc/sysconfig/modules/*.modules加入自定义模块,此时会加载到系统中。

(6)加载核心的相关设置。按/etc/sysctl.conf这个文件的设置值配置功能

(7)设置系统时间(clock)

(8)设置终端的控制台的字形

(9)设置raid及LVM等硬盘功能

(10)查看检验磁盘文件系统

(11)进行磁盘配额的quota的转换

(12)重新以读取模式载入系统磁盘

(13)启动quota功能

(14)启动系统随机设备(产生随机数功能)

(15)清理启动过程中的临时文件

(16)将启动信息加载到/var/log/dmesg文件中

当/etc/rc.d/rc.sysinit执行完后,系统就可以顺利工作了,只是还需要启动系统所需要的各种服务,这样主机才可以提供相关的网格和主机功能。

2)执行/etc/rc.d/rc脚本。该文件定义了服务启动的顺序是先k后s,而具体的每个运行级别的服务状态是放在/etc/rc.d/rc*.d(*=0~6)目录下,所有的文件均是指向/etc/init.d下相应文件的符号链接。rc.sysinit通过分析/etc/inittab文件来确定系统的启动级别,然后才去执行/etc/rc.d/rc*.d下的文件。

/etc/init.d-> /etc/rc.d/init.d

/etc/rc ->/etc/rc.d/rc

/etc/rc*.d ->/etc/rc.d/rc*.d

/etc/rc.local-> /etc/rc.d/rc.local

/etc/rc.sysinit-> /etc/rc.d/rc.sysinit

我们以启动级别3为例,/etc/rc.d/rc3.d目录,该目录下的内容全部是以S或K开头的链接文件,都链接到/etc/rc.d/init.d目录下的各种shell脚本。S表示的是启动时需要start的服务内容,K表示关机 时需要关闭的服务内容。/etc/rc.d/rc*.d中的系统服务会在系统后台启动,如果需要对某个运行级别的服务进行更具体的定制,通过chkconfig命令来操作,或者通过setup、ntsys、system-config-services来进行定制。如查我们需要自已增加启动的内容,可以在init.d目录中增加相磁的shell脚本,然后在rc*.d目录中建立链接文件指向该shell脚本。这样sehll脚本的启动或结束顺序是由S或K字母后面数字决定,数字越小越先执行。

3)执行用户自定义引导程序/etc/rc.d/rc.local。其实当执行/etc/rc.d/rc3.d/S99local时,它就是在执行/etc/rc.d/rc.local。S99local是指向rc.local的符号链接,就是一般来说,自定义的程序不需要执行上面所说的的繁琐的建立shell增加链接文件的步骤,保需要将命令放在rc.local里面就可以了,这个shell脚本就是保留给用户自定义启动内容。

4)完成了系统所有的启动任务后,linux会启动终端或X-Window来等待用户登录,tty1, tty2, tty3...这表示运行等级1,2,3,4的时候,都会执行/sbin/mingetty,而且执行了6个,所以linux会有6个纯文本终端。

linux的内核启动的入口文件:

#define DEBUG		/* Enable initcall_debug */

#include <linux/types.h>
#include <linux/extable.h>	//exception table?
#include <linux/module.h>	//动态添加和卸载模块
#include <linux/proc_fs.h>	//proc 文件系统的常量的结构
#include <linux/binfmts.h>	//二进制格式
#include <linux/kernel.h>	//内核头文件,含有一些内核常用的原形定义
#include <linux/syscalls.h>	//系统调用接口
#include <linux/stackprotector.h>	//栈保护
#include <linux/string.h>	//字符串头文件
#include <linux/ctype.h>	//字符类型头文件,定义了一些有关字符类型判断和转换的宏
#include <linux/delay.h>	//延时函数
#include <linux/ioport.h>	//检测,保留,分配系统资源
#include <linux/init.h>		//包含模块初始化宏
#include <linux/initrd.h>	//initial ram disk,初始化内存盘
#include <linux/bootmem.h>	//启动时对内存的一种简单页面管理方式
#include <linux/acpi.h>		//ACPI接口
#include <linux/console.h>	//跟控制台有关的定义
#include <linux/nmi.h>		//Non Maskable Interrupt,不可屏蔽中断
#include <linux/percpu.h>	//per_cpu机制
#include <linux/kmod.h>		//kmod是一个用于处理内核模块中一般任务的工具集
#include <linux/vmalloc.h>	//申请连续的虚拟地址空间
#include <linux/kernel_stat.h>		//内核统计
#include <linux/start_kernel.h>		//start_kernle函数原型的定义
#include <linux/security.h>			//linux security
#include <linux/smp.h>				//Symmetrical Multi-Processing,对称多处理
#include <linux/profile.h>
#include <linux/rcupdate.h>			//Read-Copy Update mechanism
#include <linux/moduleparam.h>		//内核模块参数传递
#include <linux/kallsyms.h>			//kallsyms机制
#include <linux/writeback.h>		//writeback机制
#include <linux/cpu.h>				//通用的cpu机制	
#include <linux/cpuset.h>			//cpu分组机制
#include <linux/cgroup.h>			//cgroup本身提供将进程进行分组化管理的功能和接口的基础结构
#include <linux/efi.h>				//Extensible Firmware Interface
#include <linux/tick.h>				//Tick related global functions
#include <linux/sched/isolation.h>	//
#include <linux/interrupt.h>		//中断
#include <linux/taskstats_kern.h>	//kernel header for per-task statistics interface
#include <linux/delayacct.h>		//per-task delay accounting
#include <linux/unistd.h>			//Linux标准头文件
#include <linux/utsname.h>			//UTS:Unix Timesharing System
#include <linux/rmap.h>				//Reverse Mapping functions
#include <linux/mempolicy.h>		//numa memory policy
#include <linux/key.h>				//Authentication token and access key management
#include <linux/buffer_head.h>		//Everything to do with buffer_heads.
#include <linux/page_ext.h>			//page  extension
#include <linux/debug_locks.h>		//死锁调试
#include <linux/debugobjects.h>		//
#include <linux/lockdep.h>			//Runtime locking correctness validator
#include <linux/kmemleak.h>			//内存泄露检测
#include <linux/pid_namespace.h>	//pid命名空间
#include <linux/device.h>			//generic, centralized driver model
#include <linux/kthread.h>			//Simple interface for creating and stopping kernel threads without mess
#include <linux/sched.h>			//内核等待队列中要使用的TASK_NORMAL,TASK_INTERRUPTIBLE包含在这个头文件
#include <linux/sched/init.h>		//Scheduler init related prototypes:
#include <linux/signal.h>			//信号头文件,定义信号符号常量,信号结构以及信号操作函数原型
#include <linux/idr.h>				//idr机制
#include <linux/kgdb.h>				//This provides the callbacks and functions that KGDB needs to share between the core, I/O and arch-specific portions.
#include <linux/ftrace.h>			//ftrace,内核的追踪工具
#include <linux/async.h>			//Asynchronous function calls for boot performance
#include <linux/sfi.h>				//Simple Firmware Interface
#include <linux/shmem_fs.h>			//共享内存文件系统
#include <linux/slab.h>				//slab机制
#include <linux/perf_event.h>		//Performance events
#include <linux/ptrace.h>			//ptrace,主要用于实现断点调试和跟踪系统调用
#include <linux/pti.h>				//Page Table Isolation
#include <linux/blkdev.h>			//block device,块设备
#include <linux/elevator.h>			//电梯算法
#include <linux/sched_clock.h>		//support for extending counters to full 64-bit ns counter
#include <linux/sched/task.h>		//Interface between the scheduler and various task lifetime (fork()/exit())
#include <linux/sched/task_stack.h>	//task->stack (kernel stack) handling interfaces
#include <linux/context_tracking.h>	//
#include <linux/random.h>			// random number generator
#include <linux/list.h>				//Simple doubly linked list implementation
#include <linux/integrity.h>		//完整性度量
#include <linux/proc_ns.h>			// procfs namespace
#include <linux/io.h>				//内核访问IO内存等函数的定义
#include <linux/cache.h>			//cache机制
#include <linux/rodata_test.h>		//functional test for mark_rodata_ro function
#include <linux/jump_label.h>		//Jump label support

//这些头文件主要定义了一些与CPU体系结构密切相关的
#include <asm/io.h>					//以宏的嵌入汇编形式定义对IO端口操作的函数
#include <asm/bugs.h>				//to check for architecture-dependent bugs.
#include <asm/setup.h>				//structure passed to kernel to tell it about the hardware it's running on
#include <asm/sections.h>           //section boundaries
#include <asm/cacheflush.h>			//cache flush

#define CREATE_TRACE_POINTS
#include <trace/events/initcall.h>
static int kernel_init(void *);	//函数声明

extern void init_IRQ(void);
extern void fork_init(void);
extern void radix_tree_init(void);

extern说明函数定义在其他文件中,这三个extern函数分别是中断初始化、fork功能初始化、基数树初始化。

/*
 * Debug helper: via this flag we know that we are in 'early bootup code'
 * where only the boot processor is running with IRQ disabled.  This means
 * two things - IRQ must not be enabled before the flag is cleared and some
 * operations which are not allowed with IRQ disabled are allowed while the
 * flag is set.
 */
bool early_boot_irqs_disabled __read_mostly;

__read_mostly是一个宏,这个宏定义在include/asm/cache.h中,它标记了前面这个变量是经常读取的,这样如果在缓存平台上,它就能把这个变量存放到cache中,以保证后续读取的速度。

#define __read_mostly __attribute__((__section__(".data..read_mostly")))
enum system_states system_state __read_mostly;
EXPORT_SYMBOL(system_state);

EXPORT_SYMBOL标签内定义的函数或者符号对全部内核代码公开,你可以直接在你的内核模块直接调用。宏定义在include\linux\export.h中。

(1)#运算符,##运算符

通常在宏定义中使用#来创建字符串,#abc就表示字符串“abc”等。

##运算符称为预处理器的粘合剂,用来替换粘合两个不同的符号。

如:#define xName (n)  x##n

则xName(4)  则变为x4

(2)gcc的__attribute__属性

__attribute__((section("section_name)))的作用是将指定的函数或变量放入到名为“section_name”的段中。

__attribute__属性添加可以在函数或变量定义的时候直接加入定义语句中。

如:int myvar__attribute__((section("mydata"))) = 0;

表示定义了整形变量myvar=0;并且将该变量存放到名为”mydata”的section中

 

/* For every exported symbol, place a struct in the __ksymtab section */
#define ___EXPORT_SYMBOL(sym, sec)					\
	extern typeof(sym) sym;						\
	__CRC_SYMBOL(sym, sec)						\
	static const char __kstrtab_##sym[] //用于存放导出的符号名
	__attribute__((section("__ksymtab_strings"), aligned(1)))//放置到__ksymtabl_strings的section中
	= VMLINUX_SYMBOL_STR(sym);					\
	static const struct kernel_symbol __ksymtab_##sym //存放导出符号在内存的地址和名称
	__used								\
	__attribute__((section("___ksymtab" sec "+" #sym), used))//放置到__ksymatab的section中
	= { (unsigned long)&sym, __kstrtab_##sym }
/*
 * Boot command-line arguments
 */
#define MAX_INIT_ARGS CONFIG_INIT_ENV_ARG_LIMIT
#define MAX_INIT_ENVS CONFIG_INIT_ENV_ARG_LIMIT

extern void time_init(void);
/* Default late time init is NULL. archs can override this later. */
void (*__initdata late_time_init)(void);

 

/*
 * Used to generate warnings if static_key manipulation functions are used
 * before jump_label_init is called.
 */
bool static_key_initialized __read_mostly;
EXPORT_SYMBOL_GPL(static_key_initialized);//只对GPL协议的模块开放

 

/*
 * If set, this is an indication to the drivers that reset the underlying
 * device before going ahead with the initialization otherwise driver might
 * rely on the BIOS and skip the reset operation.
 *
 * This is useful if kernel is booting in an unreliable environment.
 * For ex. kdump situation where previous kernel has crashed, BIOS has been
 * skipped and devices will be in unknown state.
 */
unsigned int reset_devices;
EXPORT_SYMBOL(reset_devices);
/* __init标识的代码存在特殊的内存段中,初始化结束后就释放这段内存*/
static int __init set_reset_devices(char *str)
{
	reset_devices = 1;
	return 1;
}

__setup("reset_devices", set_reset_devices);

在kernel中有很多__init,其定义在/include/linux/init.h

 #define __init __attribute__ ((__section__ (".init.text"))) __cold
 #define __initdata __attribute__ ((__section__ (".init.data")))
 #define __exitdata __attribute__ ((__section__(".exit.data")))
 #define __exit_call __attribute_used__ __attribute__ ((__section__ (".exitcall.exit")))

section("SECTION-NAME"),正常情况下编译器会它的生成的objects放在像“data”和“bss”段中,但是有时候,你需要额外的section或你需要把特定的数据放在特定的section中。比如,为了映射特定的硬件,就需要把一个变量或函数放在一个特殊的段中。下面是一个例子:

struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };
struct duart b __attribute__ ((section ("DUART_B"))) = { 0 };
char stack[10000] __attribute__ ((section ("STACK"))) = { 0 };
int init_data __attribute__ ((section ("INITDATA"))) = 0;
main()
{
/* Initialize stack pointer */
init_sp (stack + sizeof (stack));
/* Initialize initialized data */
memcpy (&init_data, &data, &edata - &data);
/* Turn on the serial ports */
init_duart (&a);
init_duart (&b);
}

linux中把一些启动及初始化时候用的数据用__init标识,然后在适当的时候把它们释放,回收内存。

说到这个__init,就不能不说module_init,subsys_initcall。

在init.h中我们能够找到

#define subsys_initcall(fn) __define_initcall("4",fn,4)
 #define __define_initcall(level,fn,id) \
 static initcall_t __initcall_##fn##id __attribute_used__ \
 __attribute__((__section__(".initcall" level ".init"))) = fn

subsys_initcall(usb_init)转换后就变成了

static initcall_t __initcall_usbinit4 __attribute_used__ \
__attribute__((__section__(".initcall4.init"))) = usb_init

就是把usb_init的函数入口指针存放在.initcall4.init中。

在/include/asm-generic/vmlinux.lds.h

#define INITCALLS \
 *(.initcall0.init) \
 *(.initcall0s.init) \
 *(.initcall1.init) \
 *(.initcall1s.init) \
 *(.initcall2.init) \
 *(.initcall2s.init) \
 *(.initcall3.init) \
 *(.initcall3s.init) \
 *(.initcall4.init) \
 *(.initcall4s.init) \
 *(.initcall5.init) \
 *(.initcall5s.init) \
 *(.initcallrootfs.init) \
 *(.initcall6.init) \
 *(.initcall6s.init) \
 *(.initcall7.init) \
 *(.initcall7s.init)

文件/arch/kernel/vmlinux_32.lds.S

 .initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
 __initcall_start = .;
 INITCALLS
 __initcall_end = .;
 }

那么系统是如何执行这些函数呢?

main.c中

start_kernel->reset_init()->kernel_init()->do_basic_setup()->do_initcalls()

而__setup()这个宏定义,在include/linux/init.h文件中。

#define __setup(str, fn) \ 
static char __setup_str_##fn[] __initdata = str; \ 
static struct kernel_param __setup_##fn __attribute__((unused)) __initsetup = { __setup_str_##fn, fn } 

vmlinux.lds这个关于ld链接器的脚本文件有这样的一段

.init.data : {

*(.init.data)  .=ALIGN(8);

//输入段为.init.data   8字节对齐

*(.init.rodata);

//输入段为.init.rodata  

__setup_start = .; 

//.表示当前的offset,相当于该变量在vmlix镜像中的文件偏移

 *(.setup.init) 
__setup_end = .; 

……

}

这里的意思就是__setup_start一个节的开始,而__setup_end是一个节的结束,这个节的名称是.init.setup,这个你可以用readelf -a这个来看一下你的vmlinux这个文件,可以看到.init.setup就在.init.data的节中。

 

 

最后欢迎大家访问我的个人网站:1024s​​​​​​​

 

 

 

 

arm-seev300-linux-uclibcgnueabihf-g++ -fPIC -Wall -O2 -Drgn_private -g -I./ -I../../include -I../../include/common -I../../inc_drv -I../../inc_lib -fPIC -Wall -O2 -Drgn_private -g -I./ -I../../include -I../../include/common -I../../inc_drv -I../../inc_lib ./common.o ./FilterFunction.o ./HVM_MPI_IMG_Set+GetConnectMode.o ./HVM_MPI_IMG_Set+GetDdrMode.o ./HVM_MPI_VGP_CreatePipeTest.o ./main.o -L../../lib -L../../lib/gtest_lib -lvb -lpthread -lvmm -lbase -lm -losal -lgdma -limg -lvic -lcxm -lbind -lprint -lisp -lvdp -lhvm2a -lpqt -lhvm188 -lhvm187 -lge -lrgn -lvencrgn -lvenc -lvgp -lime -losal -lgtest -o run_img_test -Xlinker "-(" -L../../lib -L../../lib/gtest_lib -lvb -lpthread -lvmm -lbase -lm -losal -lgdma -limg -lvic -lcxm -lbind -lprint -lisp -lvdp -lhvm2a -lpqt -lhvm188 -lhvm187 -lge -lrgn -lvencrgn -lvenc -lvgp -lime -losal -Xlinker "-)" /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libimg.a(hvm_mpi_img.o): in function `_GLOBAL__sub_I_65535_0_hvm_mpi_img.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/img/lib/mpi/hvm_mpi_img.c:141: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libimg.a(hvm_mpi_img.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libimg.a(pil_func_img.o): in function `_GLOBAL__sub_I_65535_0_pil_func_img.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/img/lib/pil/pil_func_img.c:797: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libimg.a(pil_func_img.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvic.a(hvm_mpi_vic.o): in function `_GLOBAL__sub_I_65535_0_hvm_mpi_vic.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/vic/lib/mpi/hvm_mpi_vic.c:642: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvic.a(hvm_mpi_vic.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvic.a(pil_vic_sys.o): in function `_GLOBAL__sub_I_65535_0_pil_vic_sys.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/vic/lib/pil/pil_vic_sys.c:1994: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvic.a(pil_vic_sys.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libisp.a(pil_base_isp.o): in function `_GLOBAL__sub_I_65535_0_pil_base_isp.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/isp/lib/pil/pil_base_isp.c:1283: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libisp.a(pil_base_isp.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libisp.a(pil_imp_isp.o): in function `_GLOBAL__sub_I_65535_0_pil_imp_isp.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/isp/lib/pil/pil_imp_isp.c:2742: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libisp.a(pil_imp_isp.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libisp.a(pil_func_isp.o): in function `_GLOBAL__sub_I_65535_0_pil_func_isp.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/isp/lib/pil/pil_func_isp.c:1655: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libisp.a(pil_func_isp.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvgp.a(hvm_mpi_vgp.o): in function `_GLOBAL__sub_I_65535_0_hvm_mpi_vgp.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/vgp/lib/mpi/hvm_mpi_vgp.c:2416: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvgp.a(hvm_mpi_vgp.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvgp.a(pil_vgp_dfx.o): in function `_GLOBAL__sub_I_65535_0_pil_vgp_dfx.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/vgp/lib/pil/pil_vgp_dfx.c:79: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvgp.a(pil_vgp_dfx.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvgp.a(pil_vgp_algo.o): in function `_GLOBAL__sub_I_65535_0_pil_vgp_algo.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/vgp/lib/pil/pil_vgp_algo.c:1931: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvgp.a(pil_vgp_algo.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvgp.a(pil_vgp_func.o): in function `_GLOBAL__sub_I_65535_0_pil_vgp_func.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/vgp/lib/pil/pil_vgp_func.c:2595: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvgp.a(pil_vgp_func.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvgp.a(pil_vgp_sys.o): in function `_GLOBAL__sub_I_65535_0_pil_vgp_sys.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/vgp/lib/pil/pil_vgp_sys.c:1653: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libvgp.a(pil_vgp_sys.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libcxm.a(hvm_mpi_cxm.o): in function `_GLOBAL__sub_I_65535_0_hvm_mpi_cxm.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/cxm/lib/hvm_mpi_cxm.c:199: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libcxm.a(hvm_mpi_cxm.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libcxm.a(pil_flow_cxm.o): in function `_GLOBAL__sub_I_65535_0_pil_flow_cxm.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/cxm/lib/pil_flow_cxm.c:332: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libcxm.a(pil_flow_cxm.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libcxm.a(pil_func_cxm.o): in function `_GLOBAL__sub_I_65535_0_pil_func_cxm.c': /share/users/fanjiahe/svn/mars/btk_img/g2s/branches_v0.5/api/cxm/lib/pil_func_cxm.c:303: undefined reference to `__gcov_init' /share/eda/toolchains/arm-seev300-linux-uclibcgnueabihf/bin/../lib/gcc/arm-seev300-linux-uclibcgnueabihf/6.5.0/../../../../arm-seev300-linux-uclibcgnueabihf/bin/ld.bfd: ../../lib/libcxm.a(pil_func_cxm.o):(.data.rel+0x10): undefined reference to `__gcov_merge_add' collect2: error: ld returned 1 exit status
最新发布
08-12
<think>我们正在解决个特定的链接错误:使用arm-seev300-linux-uclibcgnueabihf-g++编译C++程序时,出现未定义的引用`__gcov_init`和`__gcov_merge_add`。这些符号与GCC的代码覆盖率(gcov)相关。 问题分析: 当我们在编译时使用了覆盖率检测选项(如`-fprofile-arcs`、`-ftest-coverage`等),编译器会在目标文件中插入些额外的代码,并需要链接到gcov库(通常是libgcov.a或libgcov.so)。如果链接器找不到这些符号,通常是因为没有正确链接gcov库。 解决方案: 1. 确认是否确实需要覆盖率检测:如果项目不需要覆盖率测试,那么可以移除覆盖率相关的编译选项(如`-fprofile-arcs`、`-ftest-coverage`、`--coverage`等)。 2. 如果需要覆盖率检测,则需要确保在链接时添加了gcov库。 具体步骤: 步骤1:检查编译选项 查看你的编译命令,是否包含了覆盖率选项。例如,常见的选项有: -fprofile-arcs -ftest-coverage 或者 --coverage(它相当于同时指定-fprofile-arcs -ftest-coverage,并且在链接时自动包含libgcov) 步骤2:在链接时添加gcov库 如果你明确需要覆盖率检测,那么在链接阶段需要确保链接了gcov库。通常,GCC会自动链接libgcov,但在交叉编译环境下,可能需要显式指定。 尝试在链接命令中添加`-lgcov`,例如: arm-seev300-linux-uclibcgnueabihf-g++ ... -lgcov ... 步骤3:如果步骤2无效,可能需要指定gcov库的路径 交叉编译工具链可能将库放在特定目录下。你可以使用`-L`选项指定库路径,例如: arm-seev300-linux-uclibcgnueabihf-g++ ... -L<path_to_gcov_lib> -lgcov ... 步骤4:检查工具链中是否存在gcov库 在交叉编译工具链的目录中查找`libgcov.a`或`libgcov.so`文件。例如,可以在工具链的库目录下搜索: find /path/to/toolchain -name "libgcov.*" 步骤5:如果工具链中确实没有gcov库,可能需要重新配置工具链以包含gcov支持,或者安装额外的包。 步骤6:使用`--coverage`选项 在编译和链接时都使用`--coverage`选项,这个选项会自动处理所需的标志和链接库。例如: arm-seev300-linux-uclibcgnueabihf-g++ --coverage -o your_program your_source.cpp 注意:在交叉编译环境中,确保工具链支持gcov,并且已经为交叉编译构建了相应的gcov库。 步骤7:如果以上步骤都无效,可以考虑使用静态链接gcov库 如果动态链接库有问题,可以尝试静态链接。找到`libgcov.a`,然后在链接时直接指定该库的完整路径,例如: arm-seev300-linux-uclibcgnueabihf-g++ ... /path/to/libgcov.a ... 示例命令: 假设我们有个源文件`main.cpp`,编译链接命令如下: arm-seev300-linux-uclibcgnueabihf-g++ --coverage -o main main.cpp 如果自动链接不起作用,可以尝试: arm-seev300-linux-uclibcgnueabihf-g++ -fprofile-arcs -ftest-coverage -o main main.cpp -lgcov 总结: 这个问题的根本原因是链接阶段缺少gcov库。解决方法主要是确保在链接时正确链接了gcov库(通过`-lgcov`),并且确保工具链中确实提供了这个库。 如果问题仍然存在,可能需要检查工具链的配置,或者联系工具链供应商以确认gcov支持情况。 相关问题: 1. 如何在交叉编译环境中使用gcov进行代码覆盖率测试? 2. 如何为交叉编译工具链添加gcov支持? 3. 如何解决其他未定义的符号引用问题?</think>针对 `arm-seev300-linux-uclibcgnueabihf-g++` 链接时出现的 `undefined reference to __gcov_init` 和 `__gcov_merge_add` 错误,以下是完整的解决方案: --- ### 问题原因 这些符号属于 GCC 的代码覆盖率检测工具(gcov),当编译时启用了覆盖率选项(如 `-fprofile-arcs` 或 `--coverage`),但链接时未正确链接 gcov 库会导致此错误[^1]。 --- ### 解决方法 #### 1. **添加 gcov 链接库** 在链接命令中显式添加 `-lgcov` 或 `--coverage` 选项: ```bash arm-seev300-linux-uclibcgnueabihf-g++ your_file.cpp -o output -lgcov # 或 arm-seev300-linux-uclibcgnueabihf-g++ your_file.cpp -o output --coverage ``` #### 2. **检查工具链的 gcov 支持** 确认工具链是否包含 gcov 库: ```bash # 查找 libgcov.a 或 libgcov.so find /path/to/toolchain -name "libgcov*" ``` 若不存在,需重新安装带 gcov 支持的交叉编译工具链。 #### 3. **静态链接 gcov 库** 若动态链接失败,尝试直接指定库路径: ```bash arm-seev300-linux-uclibcgnueabihf-g++ your_file.cpp -o output \ /path/to/toolchain/lib/libgcov.a ``` #### 4. **检查编译选项致性** 确保 **所有目标文件** 使用相同的覆盖率选项: - 编译和链接阶段均需添加 `-fprofile-arcs` 或 `--coverage` - 错误示例:编译时带 `--coverage` 但链接时未添加 #### 5. **清理构建环境** 若曾切换编译选项,执行彻底清理: ```bash make clean # 或删除所有 .o 和 .gcno 文件 rm -f *.gcda *.gcno ``` #### 6. **验证工具链兼容性** 检查工具链是否支持 gcov: ```bash arm-seev300-linux-uclibcgnueabihf-g++ -v --help 2>&1 | grep -i "coverage" ``` 若无输出,可能需要升级或更换工具链。 --- ### 完整操作示例 ```bash # 清理旧构建 make clean # 编译+链接(启用覆盖率) arm-seev300-linux-uclibcgnueabihf-g++ -c --coverage main.cpp -o main.o arm-seev300-linux-uclibcgnueabihf-g++ -c --coverage utils.cpp -o utils.o arm-seev300-linux-uclibcgnueabihf-g++ main.o utils.o -o app --coverage # 运行测试生成覆盖率数据 ./app # 查看覆盖率报告 gcov main.cpp ``` --- ### 关键点总结 | 步骤 | 操作 | 检查点 | |------|------|--------| | 1 | 添加 `-lgcov` | 链接命令包含覆盖率库 | | 2 | 统编译选项 | 所有文件使用 `--coverage` | | 3 | 验证库存在 | `libgcov.a` 在工具链路径 | | 4 | 清理构建 | 删除旧的 `.gcno` 文件 | | 5 | 工具链验证 | 确认 gcov 支持 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值