uboot启动流程分析

u-boot u-boot,全称 universal boot loader,是遵循gpl条款的开放源码项目。u-boot的作用是系统引导。u-boot从fadsrom、8xxrom、ppcboot逐步发展演化而来。其源码目录、编译形式与linux内核很相似,事实上,不少u-boot源码就是根据相应的linux内核源程序进行简化而形成的,尤其是一些设备的驱动程序,这从u-boot源码的注释中能体现这一点。
u-boot不仅仅支持嵌入式linux系统的引导,它还支持netbsd, vxworks, qnx, rtems, artos, lynxos, android嵌入式操作系统。其目前要支持的目标操作系统是openbsd, netbsd, freebsd,4.4bsd, linux, svr4, esix, solaris, irix, sco, dell, ncr, vxworks, lynxos, psos, qnx, rtems, artos, android。这是u-boot中universal的一层含义,另外一层含义则是u-boot除了支持powerpc系列的处理器外,还能支持mips、 x86、arm、nios、xscale等诸多常用系列的处理器。
这两个特点正是u-boot项目的开发目标,即支持尽可能多的嵌入式处理器和嵌入式操作系统。就目前来看,u-boot对powerpc系列处理器支持最为丰富,对linux的支持最完善。其它系列的处理器和操作系统基本是在2002年11 月ppcboot改名为u-boot后逐步扩充的。从ppcboot向u-boot的顺利过渡,很大程度上归功于u-boot的维护人德国denx软件工程中心wolfgang denk[以下简称w.d]本人精湛专业水平和执着不懈的努力。当前,u-boot项目正在他的领军之下,众多有志于开放源码boot loader移植工作的嵌入式开发人员正如火如荼地将各个不同系列嵌入式处理器的移植工作不断展开和深入,以支持更多的嵌入式操作系统的装载与引导。
uboot启动流程分析 可知程序的入口在_start,在sourceinsight中查找可发现程序的入口_start在u-boot-2016.05\arch\arm\lib\vectors.s中。
。。。
entry(_start)
sections
{
。。。
。 = 0x00000000;
。 = align(4);
.text :
{
*(.__image_copy_start)
*(.vectors)
cpudir/start.o (.text*)
*(.text*)
}
。。。
。 = align(4);
.rodata : { *(sort_by_alignment(sort_by_name(.rodata*))) }
。 = align(4);
.data : {
*(.data*)
}
。 = align(4);
。 = 。;
。。。
.bss_start __rel_dyn_start (overlay) : {
keep(*(.__bss_start));
__bss_base = 。;
}
.bss __bss_base (overlay) : {
*(.bss*)
。 = align(4);
__bss_limit = 。;
}
.bss_end __bss_limit (overlay) : {
keep(*(.__bss_end));
}
。。。
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
进入boot-2016.05\arch\arm\lib\vectors.s中,可以看到从_start开始后就跳转到reset去执行:
。。。
.globl _start
。。。
_start:
#ifdef config_sys_dv_nor_boot_cfg
.word config_sys_dv_nor_boot_cfg
#endif
b reset
ldr pc, _undefined_instruction
ldr pc, _software_interrupt
ldr pc, _prefetch_abort
ldr pc, _data_abort
ldr pc, _not_used
ldr pc, _irq
ldr pc, _fiq
。。.12345678910111213141516171819202122
1、从u-boot-2016.05\arch\arm\cpu\arm920t\start.s中reset执行
主要执行流程:reset -》 cpu_init_crit -》 lowlevel_init -》 _main
reset:
。。。
#ifndef config_skip_lowlevel_init
bl cpu_init_crit
#endif
bl _main
。。。
#ifndef config_skip_lowlevel_init
cpu_init_crit:
。。。
bl lowlevel_init
。。。
#endif /* config_skip_lowlevel_init */
1234567891011121314151617181920212223
2、由bl _main跳转到u-boot-2016.05\arch\arm\lib\crt0.s中从入口_main开始执行
主要执行流程:board_init_f -》 relocate_code -》 board_init_r
entry(_main)
。。。
bl board_init_f_alloc_reserve
。。。
bl board_init_f_init_reserve
。。。
bl board_init_f
#if ! defined(config_spl_build)
。。。
b relocate_code
。。。
#endif
#if !defined(config_spl_build) || defined(config_spl_framework)
。。。
#if defined(config_sys_thumb_build)
。。。
#else
ldr pc, =board_init_r
#endif
#endif
endproc(_main)123456789101112131415161718192021222324252627282930313233
这部分有三点说明:
⑴、u-boot-2016.05\common\board_f.c:board_init_f通过initcall_run_list(init_sequence_f)函数执行一系列初始化函数以实现前半部分板级初始化。全局结构体gd在u-boot-2016.05\arch\arm\include\asm\global_data.h中声明:
#define declare_global_data_ptr register volatile gd_t *gd asm (“r9”)1
⑵、u-boot-2016.05\arch\arm\lib\relocate.s:relocate_code实现uboot代码的重定位,此部分如果觉得源代码不是简单明了可自己改写。
⑶、去重定位uboot有两种路径:
一种是将gd-》flags设为0,在初始化函数序列init_sequence_f中的jump_to_copy函数中去跳转到relocate_code:
static int jump_to_copy(void)
{
if (gd-》flags & gd_flg_skip_reloc)
return 0;
。。。
#if defined(config_x86) || defined(config_arc)
。。。
#else
relocate_code(gd-》start_addr_sp, gd-》new_gd, gd-》relocaddr);
#endif
return 0;
}1234567891011121314
另一种就是不宏定义config_spl_build,然后在u-boot-2016.05\arch\arm\lib\crt0.s中通过
#if ! defined(config_spl_build)
。。。
b relocate_code
。。。
#endif123456789
来跳转到relocate_code。以上两种方法选其一,另一种就得去掉。
3、在上一步通过ldr pc, =board_init_r指令进入u-boot-2016.05\common\board_r.c:board_init_r函数,进而调用initcall_run_list(init_sequence_r)函数执行一系列初始化函数以实现后半部分板级初始化,并在initcall_run_list函数里进入run_main_loop不再返回。
void board_init_r(gd_t *new_gd, ulong dest_addr)
{
。。。
if (initcall_run_list(init_sequence_r))
hang();
/* notreached - run_main_loop() does not return */
hang();
}
1234567891011
init_sequence_r是一个函数指针数组,里面存放了很多初始化函数指针,里面有两个重要的函数指针initr_announce和run_main_loop:
init_fnc_t init_sequence_r[] = {
。。。
initr_announce,
。。。
run_main_loop,
};12345678910
initr_announce函数声明从此处开始程序就将跳转到ram中运行:
static int initr_announce(void)
{
debug(“now running in ram - u-boot at: %08lx\n”, gd-》relocaddr);
return 0;
}12345
最后一项是run_main_loop ,进入run_main_loop 后便不再返回。
4、在run_main_loop 里会进入u-boot-2016.05\common\main.c:main_loop函数
static int run_main_loop(void)
{
。。。
for (;;)
main_loop();
return 0;
}12345678
进入main_loop之前就已经完成初始化,接下来准备去处理命令
/* we come here after u-boot is initialised and ready to process commands */
void main_loop(void)
{
const char *s;
bootstage_mark_name(bootstage_id_main_loop, “main_loop”);
。。。
/* get environment_variable: s = getenv(“bootcmd”); -》 bootcmd */
s = bootdelay_process();
。。。
autoboot_command(s);
。。。
}123456789101112131415161718
main_loop函数里有两个重要的过程:
⑴、首先在bootdelay_process函数里通过s = getenv(“bootcmd”)得到bootcmd参数并返回bootcmd参数,
const char *bootdelay_process(void)
{
char *s;
int bootdelay;
。。。
s = getenv(“bootdelay”);
。。。
debug(“### main_loop entered: bootdelay=%d\n\n”, bootdelay);
。。。
s = getenv(“bootcmd”);
。。。
stored_bootdelay = bootdelay;
return s;
}1234567891011121314151617181920212223
其中,bootcmd参数通过以下方式指定:
先在u-boot-2016.05\include\env_default.h中
#ifdef config_bootcommand
“bootcmd=” config_bootcommand “\0”
#endif123
再在u-boot-2016.05\include\configs\smdk2440.h中指定
#define config_bootcommand “nand read 30000000 kernel;bootm 30000000”1
⑵、然后进入autoboot_command函数,并将bootcmd参数传入,继而进入run_command_list函数,继续将bootcmd参数传入
void autoboot_command(const char *s)
{
。。。
if (stored_bootdelay != -1 && s && !abortboot(stored_bootdelay)) {
。。。
run_command_list(s, -1, 0);
。。。
}
。。。
}
123456789101112
5、从autoboot_command函数进入u-boot-2016.05\common\cli.c:run_command_list函数后,接着会调用board_run_command函数去执行命令
int run_command_list(const char *cmd, int len, int flag)
{
int need_buff = 1;
char *buff = (char *)cmd; /* cast away const */
int rcode = 0;
if (len == -1) {
len = strlen(cmd);
#ifdef config_sys_hush_parser
。。。
#else
/* the built-in parser will change our string if it sees \n */
need_buff = strchr(cmd, ‘\n’) != null;
#endif
}
if (need_buff) {
buff = malloc(len + 1);
if (!buff)
return 1;
memcpy(buff, cmd, len);
buff[len] = ‘\0’;
}
#ifdef config_sys_hush_parser
。。。
#ifdef config_cmdline
。。。
#else
rcode = board_run_command(buff);
#endif
#endif
。。。
}1234567891011121314151617181920212223242526272829303132
那么,board_run_command如何去执行命令?
首先,board_run_command函数通过bootcmd参数中的bootm命令找到u-boot-2016.05\cmd\bootm.c中的
u_boot_cmd(
bootm, config_sys_maxargs, 1, do_bootm,
“boot application image from memory”, bootm_help_text
);1234
然后,根据这个信息找到执行bootm命令的处理函数指针do_bootm,并进入do_bootm函数执行相关代码,而u_boot_cmd在u-boot-2016.05\include\command.h中定义:
#define u_boot_cmd(_name, _maxargs, _rep, _cmd, _usage, _help) \
u_boot_cmd_complete(_name, _maxargs, _rep, _cmd, _usage, _help, null)12
#define u_boot_cmd_complete(_name, _maxargs, _rep, _cmd, _usage, _help, \
_comp) \
_cmd_remove(sub_ ## _name, _cmd)123
#define _cmd_remove(_name, _cmd) \
int __remove_ ## _name(void) \
{ \
if (0) \
_cmd(null, 0, 0, null); \
return 0; \
}1234567
在此,board_run_command函数还会将bootm命令中的参数(内核映像所在地址)30000000赋给bootm_headers_t结构体变量images,则images首地址就是30000000,images在u-boot-2016.05\cmd\bootm.c中定义:
bootm_headers_t images; 1
6、根据u_boot_cmd信息进入u-boot-2016.05\cmd\bootm.c:do_bootm函数
int do_bootm(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
。。。
return do_bootm_states(cmdtp, flag, argc, argv, bootm_state_start |
bootm_state_findos | bootm_state_findother |
bootm_state_loados |
#if defined(config_ppc) || defined(config_mips)
bootm_state_os_cmdline |
#endif
bootm_state_os_prep | bootm_state_os_fake_go |
bootm_state_os_go, &images, 1);
}
1234567891011121314
其中bootm_state_start 、bootm_state_findos 、bootm_state_findother 、bootm_state_loados 、bootm_state_os_prep 、bootm_state_os_fake_go 这些在u-boot-2016.05\include\image.h中bootm_headers结构体中指定:
#define bootm_state_start (0x00000001)
#define bootm_state_findos (0x00000002)
#define bootm_state_findother (0x00000004)
#define bootm_state_loados (0x00000008)
#define bootm_state_ramdisk (0x00000010)
#define bootm_state_fdt (0x00000020)
#define bootm_state_os_cmdline (0x00000040)
#define bootm_state_os_bd_t (0x00000080)
#define bootm_state_os_prep (0x00000100)
#define bootm_state_os_fake_go (0x00000200) /* ‘almost’ run the os */
#define bootm_state_os_go (0x00000400)1234567891011
7、从do_bootm进入u-boot-2016.05\common\bootm.c:do_bootm_states函数,now run the os!
int do_bootm_states(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[],
int states, bootm_headers_t *images, int boot_progress)
{
boot_os_fn *boot_fn;
ulong iflag = 0;
int ret = 0, need_boot_fn;
images-》state |= states;
/*
* work through the states and see how far we get. we stop on
* any error.
*/
if (states & bootm_state_start)
ret = bootm_start(cmdtp, flag, argc, argv);
if (!ret && (states & bootm_state_findos))
ret = bootm_find_os(cmdtp, flag, argc, argv);
if (!ret && (states & bootm_state_findother)) {
ret = bootm_find_other(cmdtp, flag, argc, argv);
argc = 0; /* consume the args */
}
/* load the os */
if (!ret && (states & bootm_state_loados)) {
ulong load_end;
iflag = bootm_disable_interrupts();
ret = bootm_load_os(images, &load_end, 0);
if (ret == 0)
lmb_reserve(&images-》lmb, images-》os.load,
(load_end - images-》os.load));
else if (ret && ret != bootm_err_overlap)
goto err;
else if (ret == bootm_err_overlap)
ret = 0;
#if defined(config_silent_console) && !defined(config_silent_u_boot_only)
if (images-》os.os == ih_os_linux)
fixup_silent_linux();
#endif
}
/* relocate the ramdisk */
#ifdef config_sys_boot_ramdisk_high
if (!ret && (states & bootm_state_ramdisk)) {
ulong rd_len = images-》rd_end - images-》rd_start;
ret = boot_ramdisk_high(&images-》lmb, images-》rd_start,
rd_len, &images-》initrd_start, &images-》initrd_end);
if (!ret) {
setenv_hex(“initrd_start”, images-》initrd_start);
setenv_hex(“initrd_end”, images-》initrd_end);
}
}
#endif
#if image_enable_of_libfdt && defined(config_lmb)
if (!ret && (states & bootm_state_fdt)) {
boot_fdt_add_mem_rsv_regions(&images-》lmb, images-》ft_addr);
ret = boot_relocate_fdt(&images-》lmb, &images-》ft_addr,
&images-》ft_len);
}
#endif
/* from now on, we need the os boot function */
if (ret)
return ret;
boot_fn = bootm_os_get_boot_func(images-》os.os);
need_boot_fn = states & (bootm_state_os_cmdline |
bootm_state_os_bd_t | bootm_state_os_prep |
bootm_state_os_fake_go | bootm_state_os_go);
if (boot_fn == null && need_boot_fn) {
if (iflag)
enable_interrupts();
printf(“error: booting os ‘%s’ (%d) is not supported\n”,
genimg_get_os_name(images-》os.os), images-》os.os);
bootstage_error(bootstage_id_check_boot_os);
return 1;
}
/* call various other states that are not generally used */
if (!ret && (states & bootm_state_os_cmdline))
ret = boot_fn(bootm_state_os_cmdline, argc, argv, images);
if (!ret && (states & bootm_state_os_bd_t))
ret = boot_fn(bootm_state_os_bd_t, argc, argv, images);
if (!ret && (states & bootm_state_os_prep))
ret = boot_fn(bootm_state_os_prep, argc, argv, images);
#ifdef config_trace
/* pretend to run the os, then run a user command */
if (!ret && (states & bootm_state_os_fake_go)) {
char *cmd_list = getenv(“fakegocmd”);
ret = boot_selected_os(argc, argv, bootm_state_os_fake_go,
images, boot_fn);
if (!ret && cmd_list)
ret = run_command_list(cmd_list, -1, flag);
}
#endif
/* check for unsupported subcommand. */
if (ret) {
puts(“subcommand not supported\n”);
return ret;
}
/* now run the os! we hope this doesn‘t return */
if (!ret && (states & bootm_state_os_go))
ret = boot_selected_os(argc, argv, bootm_state_os_go,
images, boot_fn);
/* deal with any fallout */
err:
if (iflag)
enable_interrupts();
if (ret == bootm_err_unimplemented)
bootstage_error(bootstage_id_decomp_unimpl);
else if (ret == bootm_err_reset)
do_reset(cmdtp, flag, argc, argv);
return ret;
}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
do_bootm_states函数总共分8个部分:
⑴、 work through the states and see how far we get. we stop on any error.
其中主要函数bootm_find_os实现三个功能:get kernel image header, start address and length,get image parameters。大概过程是:bootm_find_os -》 boot_get_kernel -》 image_get_kernel 。
static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
const void *os_hdr;
bool ep_found = false;
int ret;
/* get kernel image header, start address and length */
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
&images, &images.os.image_start, &images.os.image_len);
。。。
/* get image parameters */
switch (genimg_get_format(os_hdr)) {
#if defined(config_image_format_legacy)
case image_format_legacy: /*旧系统格式的内核映像*/
images.os.type = image_get_type(os_hdr);
images.os.comp = image_get_comp(os_hdr);
images.os.os = image_get_os(os_hdr);
images.os.end = image_get_image_end(os_hdr);
images.os.load = image_get_load(os_hdr);
images.os.arch = image_get_arch(os_hdr);
break;
#endif
#if image_enable_fit
case image_format_fit:
。。。
#endif
#ifdef config_android_boot_image
case image_format_android:
。。。
#endif
default:
puts(“error: unknown image format type!\n”);
return 1;
}
。。。
if (images.os.type == ih_type_kernel_noload) {
images.os.load = images.os.image_start;
images.ep += images.os.load;
}
。。.123456789101112131415161718192021222324252627282930313233343536373839404142434445
关于boot_get_kernel 、image_get_kernel 的说明:
boot_get_kernel - find kernel image(returns:
pointer to image header if valid image was found, plus kernel start address and length, otherwise null)
image_get_kernel - verify legacy format kernel image(returns:
pointer to a legacy image header if valid image was found otherwise return null)
⑵、load the os
⑶、relocate the ramdisk
⑷、from now on, we need the os boot function
由boot_fn = bootm_os_get_boot_func(images-》os.os);得到boot处理函数指针并赋给boot_fn。
①、关于参数images-》os.os,可以由下列定义得知它是系统内核的类型,并在(2)中被赋值,若系统类型为linux,则images-》os.os=5。
typedef struct bootm_headers {
。。。
#ifndef use_hostcc /*use_hostcc 没有宏定义*/
image_info_t os; /* os image info */
ulong ep; /* entry point of os */
ulong rd_start, rd_end;/* ramdisk start/end */
char *ft_addr; /* flat dev tree address */
ulong ft_len; /* length of flat device tree */
ulong initrd_start;
ulong initrd_end;
ulong cmdline_start;
ulong cmdline_end;
bd_t *kbd;
#endif
。。。
} bootm_headers_t;1234567891011121314151617181920212223
bootm_headers_t images;1
typedef struct image_info {
ulong start, end; /* start/end of blob */
ulong image_start, image_len; /* start of image within blob, len of image */
ulong load; /* load addr for the image */
uint8_t comp, type, os; /* compression, type of image, os type */
uint8_t arch; /* cpu architecture */
} image_info_t;1234567
得到images.os.os的值:
static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
const void *os_hdr;
。。。
/* get kernel image header, start address and length */
os_hdr = boot_get_kernel(cmdtp, flag, argc, argv,
&images, &images.os.image_start, &images.os.image_len);
。。。
/* get image parameters */
switch (genimg_get_format(os_hdr)) {
#if defined(config_image_format_legacy)
case image_format_legacy: /*旧系统格式的内核映像*/
。。。
images.os.os = image_get_os(os_hdr);
。。。
break;
#endif12345678910111213141516171819202122
②、bootm_os_get_boot_func中会用到函数指针数组boot_os,该数组利用传入的images.os.os=5的值得到boot处理函数指针do_bootm_linux返回给boot_fn 。
boot_fn = bootm_os_get_boot_func(images-》os.os);1
boot_os_fn *bootm_os_get_boot_func(int os)
{
。。。
return boot_os[os];
}
123456
static boot_os_fn *boot_os[] = {
[ih_os_u_boot] = do_bootm_standalone,
#ifdef config_bootm_linux
[ih_os_linux] = do_bootm_linux,
#endif
#ifdef config_bootm_netbsd
[ih_os_netbsd] = do_bootm_netbsd,
#endif
。。。
};12345678910
操作系统代号可在u-boot-2016.05\include\image.h中查看
/*
* operating system codes
*/
#define ih_os_invalid 0 /* invalid os */
#define ih_os_openbsd 1 /* openbsd */
#define ih_os_netbsd 2 /* netbsd */
#define ih_os_freebsd 3 /* freebsd */
#define ih_os_4_4bsd 4 /* 4.4bsd */
#define ih_os_linux 5 /* linux */
。。。
123456789101112
⑸、call various other states that are not generally used
⑹、check for unsupported subcommand
⑺、now run the os! we hope this doesn’t return
if (!ret && (states & bootm_state_os_go))
ret = boot_selected_os(argc, argv, bootm_state_os_go,
images, boot_fn);
1234
从do_bootm_states进入u-boot-2016.05\common\bootm_os.c:boot_selected_os函数,执行boot_fn(state, argc, argv, images);
int boot_selected_os(int argc, char * const argv[], int state,
bootm_headers_t *images, boot_os_fn *boot_fn)
{
。。。
boot_fn(state, argc, argv, images);
。。。
}1234567
⑻、deal with any fallout
8、执行boot_fn(state, argc, argv, images),因为boot_fn=do_bootm_linux,所以相当于执行do_bootm_linux(state, argc, argv, images),程序跳到u-boot-2016.05\arch\arm\lib\bootm.c:
/* main entry point for arm bootm implementation*/
int do_bootm_linux(int flag, int argc, char * const argv[],
bootm_headers_t *images)
{
。。。
boot_jump_linux(images, flag);
。。。
}12345678
do_bootm_linux -》 boot_jump_linux -》 kernel_entry(0, machid, r2);
static void boot_jump_linux(bootm_headers_t *images, int flag)
{
。。。
unsigned long machid = gd-》bd-》bi_arch_number;
char *s;
void (*kernel_entry)(int zero, int arch, uint params);
unsigned long r2;
int fake = (flag & bootm_state_os_fake_go);
kernel_entry = (void (*)(int, int, uint))images-》ep; /* ep:entry point of os*/
s = getenv(“machid”);
if (s) {
if (strict_strtoul(s, 16, &machid) 《 0) {
debug(“strict_strtoul failed!\n”);
return;
}
printf(“using machid 0x%lx from environment\n”, machid);
}
。。。
if (image_enable_of_libfdt && images-》ft_len)
r2 = (unsigned long)images-》ft_addr;
else
r2 = gd-》bd-》bi_boot_params;
if (!fake) {
。。。
kernel_entry(0, machid, r2);
}
#endif
}12345678910111213141516171819202122232425262728293031323334
run the os!
说明:
关于kernel_entry = (void (*)(int, int, uint))images-》ep;中的images-》ep在u-boot-2016.05\common\bootm.c:bootm_find_os函数中被赋值。
static int bootm_find_os(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
。。。
if (images.os.type == ih_type_kernel_noload) {
images.os.load = images.os.image_start;
images.ep += images.os.load;
}
。。。
}

ST经Qi认证的无线电源解决方案:充分激发无线的力量
零电流关断(ZCS)PWM DC/DC变换器电路图
硕博电子16周年|16年耕耘移动机械控制器,特种装备控制器
PWM技术在雷达天线控制中的应用
OBG调试接口在单片机中的应用解析
uboot启动流程分析
半导体产业,开疆拓土的先锋-Shockley
声学底座的制作
分析智能汽车环境感测的三种主流传感器
如何将易灵思FPGA干到750MHz(1080P显示)
高通骁龙865移动平台性能跑分曝光相比骁龙855提升了25%
全面解析MOS管封装分析报告
中芯国际荣获高通公司供应商奖
realme 6手机的真机图疑似曝光,搭载高通骁龙710处理器
多媒体投影机遥控器的单片机仿真
北京丽泽金融商务区首座110千伏变电站万泉变电站正式投入运行
功率放大器在管道螺旋导波信号测量实验中的应用
压力变压器的分类
智联物联技术分享之QoS配置
压力变送器的常见问题及解决方法