跳至主要内容

【转】vivi开发笔记(十七):vivi与Linux kernel的参数传递情景分析(上)

文章说明:calmarrow(lqm)原创

文章引自:http://piaoxiang.cublog.cn

 
    在上一部分提到过了,vivi作为bootloader,向内核传递启动参数是其本职工作之一。要把这个情景分析清楚,不仅仅需要分析vivi的参数机制,而且要分析Linux kernel的接收机制。因为这是一个简单的通信过程,比起本科所学习的TCP/IP来简单的多,但是因为简单,所以在协议上并不规范,理解上反而不如TCP/IP协议。下面就分为两个方面对此情景分析。
 
一、综述内核参数传递机制
 
    现在内核参数传递机制有两种:一种是基于struct param_struct,这种已经比较老了。缺点是该结构每个成员的位置是固定的,受限比较大。另外一种就是新的struct tag way。说新是相对的,Linux kernel 2.4.x都希望采用这种tag的方式。关于这方面的资料,可以有如下参考(所给出的目录是基于linux-2.4.18的内核,以顶层Makefile所在目录为当前目录。这里基于ARM架构的S3C2410,其他的SoC可以类比很容易得到):
 
1、关于bootloader的理解--【Documentation/arm/booting】
 
    此文档详细的讲述了bootloader的作用,具体内容如下:
 

[armlinux@lqm arm]$ cat Booting
                        Booting ARM Linux
                        =================

Author: Russell King
Date : 18 May 2002

The following documentation is relevant to 2.4.18-rmk6 and beyond.

In order to boot ARM Linux, you require a boot loader, which is a small
program that runs before the main kernel. The boot loader is expected
to initialise various devices, and eventually call the Linux kernel,
passing information to the kernel.

Essentially, the boot loader should provide (as a minimum) the
following:

1. Setup and initialise the RAM.
2. Initialise one serial port.
3. Detect the machine type.
4. Setup the kernel tagged list.
5. Call the kernel image.


1. Setup and initialise RAM
---------------------------

Existing boot loaders: MANDATORY
New boot loaders: MANDATORY

The boot loader is expected to find and initialise all RAM that the
kernel will use for volatile data storage in the system. It performs
this in a machine dependent manner. (It may use internal algorithms
to automatically locate and size all RAM, or it may use knowledge of
the RAM in the machine, or any other method the boot loader designer
sees fit.)


2. Initialise one serial port
-----------------------------

Existing boot loaders: OPTIONAL, RECOMMENDED
New boot loaders: OPTIONAL, RECOMMENDED

The boot loader should initialise and enable one serial port on the
target. This allows the kernel serial driver to automatically detect
which serial port it should use for the kernel console (generally
used for debugging purposes, or communication with the target.)

As an alternative, the boot loader can pass the relevant 'console='
option to the kernel via the tagged lists specifing the port, and
serial format options as described in

       linux/Documentation/kernel-parameters.txt.


3. Detect the machine type
--------------------------

Existing boot loaders: OPTIONAL
New boot loaders: MANDATORY

The boot loader should detect the machine type its running on by some
method. Whether this is a hard coded value or some algorithm that
looks at the connected hardware is beyond the scope of this document.
The boot loader must ultimately be able to provide a MACH_TYPE_xxx
value to the kernel. (see linux/arch/arm/tools/mach-types).


4. Setup the kernel tagged list
-------------------------------

Existing boot loaders: OPTIONAL, HIGHLY RECOMMENDED
New boot loaders: MANDATORY

The boot loader must create and initialise the kernel tagged list.
A valid tagged list starts with ATAG_CORE and ends with ATAG_NONE.
The ATAG_CORE tag may or may not be empty. An empty ATAG_CORE tag
has the size field set to '2' (0x00000002). The ATAG_NONE must set
the size field to zero.

Any number of tags can be placed in the list. It is undefined
whether a repeated tag appends to the information carried by the
previous tag, or whether it replaces the information in its
entirety; some tags behave as the former, others the latter.

The boot loader must pass at a minimum the size and location of
the system memory, and root filesystem location. Therefore, the
minimum tagged list should look:

        +-----------+
base -> | ATAG_CORE | |
        +-----------+ |
        | ATAG_MEM | | increasing address
        +-----------+ |
        | ATAG_NONE | |
        +-----------+ v

The tagged list should be stored in system RAM.

The tagged list must be placed in a region of memory where neither
the kernel decompressor nor initrd 'bootp' program will overwrite
it. The recommended placement is in the first 16KiB of RAM.

5. Calling the kernel image
---------------------------

Existing boot loaders: MANDATORY
New boot loaders: MANDATORY

There are two options for calling the kernel zImage. If the zImage
is stored in flash, and is linked correctly to be run from flash,
then it is legal for the boot loader to call the zImage in flash
directly.

The zImage may also be placed in system RAM (at any location) and
called there. Note that the kernel uses 16K of RAM below the image
to store page tables. The recommended placement is 32KiB into RAM.

In either case, the following conditions must be met:

- CPU register settings
  r0 = 0,
  r1 = machine type number discovered in (3) above.
  r2 = physical address of tagged list in system RAM.

- CPU mode
  All forms of interrupts must be disabled (IRQs and FIQs)
  The CPU must be in SVC mode. (A special exception exists for Angel)

- Caches, MMUs
  The MMU must be off.
  Instruction cache may be on or off.
  Data cache must be off.

- The boot loader is expected to call the kernel image by jumping
  directly to the first instruction of the kernel image.

 
    可以看出bootloader最少具备5项功能,上面比较清晰。可以看出,现在2.4的内核都是希望采用tagged list的方式来进行传递的,这里没有提到比较老的方式。这里要特别注意的是,r2 = physical address of tagged list in system RAM.,这里的"必须"是针对于tagged list而言的,如果采用param_struct,则并没有这个限制。这在后面将会详细分析,而这正是可能导致疑惑的地方。
 
2、参数传递数据结构的定义位置【include/asm/setup.h】,在这里就可以看到两种参数传递方式了。可以说,现在bootloader和Linux kernel约定的参数传递机制就是这两种,必须严格按照这两种机制进行传输,否则的话,kernel可能因为无法识别bootloader传递过来的参数而导致无法启动。关于这两种方式,在这里还有说明:
 

/*
 * linux/include/asm/setup.h
 *
 * Copyright (C) 1997-1999 Russell King
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Structure passed to kernel to tell it about the
 * hardware it's running on. See linux/Documentation/arm/Setup
 * for more info.
 *
 * NOTE:
 * This file contains two ways to pass information from the boot
 * loader to the kernel. The old struct param_struct is deprecated,
 * but it will be kept in the kernel for 5 years from now
 * (2001).
This will allow boot loaders to convert to the new struct
 * tag way.
 */

 
    这说明,现在参数传递必须要采用tag方式,因为现在新的kernel已经不支持param_struct方式了。不幸的是,vivi还是采用的param_struct方式。这里暂时以param_struct为主分析,考虑以后更新为tag方式。在这里你也可以参考【Documentation/arm/setup】,里面有关于选项具体含义的详细说明。(在这里多说几句。Linux的Documentation是一个很好的学习库,几乎所有的问题在这里都能有初步的解答。如果要想继续深入,那么就要读源代码了。学习上,先看README,然后翻阅Documentation,无疑是一条捷径。而且,是否有完备的文档,也是判断这个软件是否优秀的重要标准。)
 
二、vivi设置Linux参数分析
 
    上面对bootloader与Linux kernel之间参数传递的两种方式已经有了一个总体的理解。下面就来先看vivi部分如何设置Linux参数。
 
【init/main.c】boot_or_vivi()-->run_autoboot()-->exec_string("boot")
 
    到此,也就是要执行boot命令。与命令相关部分都在【lib/command.c】中,找到boot_cmd,然后跟踪至【lib/boot_kernel.c】,boot的执行行为函数为command_boot(),继续分析:
 
【lib/boot_kernel.c】command_boot()-->
 
主要就是三步工作。
 
    ・ 获取media_type。
 

media_type = get_param_value("media_type", &ret);

 
    media_type是重要的,因为对于不同的存储介质,底层的驱动函数是不同的。通过media_type这个顶层抽象,实现了与底层驱动的联系。
 

[armlinux@lqm include]$ cat boot_kernel.h
#ifndef _VIVI_BOOT_KERNEL_H_
#define _VIVI_BOOT_KERNEL_H_

/*
 * Media Type: A type of storage device that contains the linux kernel
 *
 * +----------------+-----------------------------------------+
 * | Value(Integer) | Type |
 * +----------------+-----------------------------------------+
 * | 0 | UNKNOWN |
 * | 1 | RAM |
 * | 2 | NOR Flash Memory |
 * | 3 | SMC (NAND Flash Memory) on the S3C2410 |
 * +----------------+-----------------------------------------+
 */

enum {
        MT_UNKNOWN = 0,
        MT_RAM,
        MT_NOR_FLASH,
        MT_SMC_S3C2410
};

#endif /* _VIVI_BOOT_KERNEL_H_ */

 
    上面就是vivi支持的media_type,现在此开发板是MT_SMC_S3C2410,也就是nand flash memory的选择部分。
 
    ・获取nand flash的kernel分区信息,为下载做好准备
 

kernel_part = get_mtd_partition("kernel");
                        if (kernel_part == NULL) {
                                printk("Can't find default 'kernel' partition\n");
                                return;
                        }
                        from = kernel_part->offset;
                        size = kernel_part->size;

 
    这里获得了kernel所在nand flash的起始地址和大小。这里应该注意,虽然kernel_part->offset是偏移量,但是这个偏移是相对于0x00000000而言,所以这时的offset就是对应的起始地址。当然,对nand flash来说,这里的地址并非是内存映射,需要做一系列的变化,具体是在nand_read_ll函数中,前面的基本实验已经做过了。
 
    ・启动内核
 

boot_kernel(from, size, media_type);

 
    利用前面得到的media_type,from,size就可以来启动内核了,当然还有多步工作要去做。具体包括如下内容:
 
(1)获取内存基地址
 

boot_mem_base = get_param_value("boot_mem_base", &ret);

 
    在vivi中,sdram是从0x30000000开始的,所以这里的boot_mem_base就是0x30000000.
 
(2)把kernel映象从nand flash复制到sdram的固定位置
 

    to = boot_mem_base + LINUX_KERNEL_OFFSET;
    printk("Copy linux kernel from 0x%08lx to 0x%08lx, size = 0x%08lx ... ",
        from, to, size);
    ret = copy_kernel_img(to, (char *)from, size, media_type);

 
    这里LINUX_KERNEL_OFFSET是0x8000,关于为什么是0x8000,这是历史原因造成的,是Linux内核的一个约定,具体可以查看Linux内核的源代码中的arch/arm/kernel/head_armv.S,如下:
 

/*
 * We place the page tables 16K below TEXTADDR. Therefore, we must make sure
 * that TEXTADDR is correctly set. Currently, we expect the least significant
 * "short" to be 0x8000, but we could probably relax this restriction to
 * TEXTADDR > PAGE_OFFSET + 0x4000
 *
 * Note that swapper_pg_dir is the virtual address of the page tables, and
 * pgtbl gives us a position-independent reference to these tables. We can
 * do this because stext == TEXTADDR
 *
 * swapper_pg_dir, pgtbl and krnladr are all closely related.
 */

 
    可以看出,TEXTADDR就是stext的地址,本开发板上为0x30008000,在0x30008000往下,会放置16K的页表,预计是0x8000.不过此处可能会放松这个限制。另外,我们的一些参数也会放到内存起始区域。这在后面就可以看到。总之,这个地方的位置boot_mem_base也就是kernel的第一条指令所在地,最后的程序跳转要跳到这个位置。
 
(3)验证magic number
 

    if (*(ulong *)(to + 9*4) != LINUX_ZIMAGE_MAGIC) {
        printk("Warning: this binary is not compressed linux kernel image\n");
        printk("zImage magic = 0x%08lx\n", *(ulong *)(to + 9*4));
    } else {
        printk("zImage magic = 0x%08lx\n", *(ulong *)(to + 9*4));
    }

 
    这个地方是判断是否有zImage的存在,而zImage的判别的magic number为0x016f2818,这个也是和内核约定好的。你可以用ultra-edit32查看一下zImage,这是我的zImage的头的部分内容(注意,为小端存放格式):
 
00000000h: 00 00 A0 E1 00 00 A0 E1 00 00 A0 E1 00 00 A0 E1
00000010h: 00 00 A0 E1 00 00 A0 E1 00 00 A0 E1 00 00 A0 E1
00000020h: 02 00 00 EA 18 28 6f 01 00 00 00 00 DB 86 09 00
 
    至于为什么magic number在0x00000024这个位置,需要分析zImage是如何生成的,它的内容是什么,起始的几个字节是什么,这部分内容放到Linux kernel端进行深入分析。不过在这里应该提一句,此处的验证是考虑到Linux kernel相对比较大,而嵌入式系统的资源受限,为了节省资源,一般会将Linux kernel来压缩成zImage格式(识别方式就是在第9个字后有magic number0x016f2818);但是应该明确,这步工作并非是必需的。因为如果内核比较小,为了加快启动速度,我可以不使用压缩的映象,直接采用非压缩映象,那么vivi此处应该把无法找到maigc number的提示更改为printk("this binary is not compressed linux kernel image\n");。就Linux kernel来说,启动中支持压缩映象和非压缩映象两种启动方式,不管是那种启动方式,第一条指令的地址总是boot_mem_base,只不过放在这里的指令并非一定是真正的kernel启动指令。这个在后面会详细分析Linux kernel启动方式。
 
(4)设置Linux参数
 

setup_linux_param(boot_mem_base + LINUX_PARAM_OFFSET);

 
    现在看一下setup_linux_param的具体动作。
 

static void setup_linux_param(ulong param_base)
{
    struct param_struct *params = (struct param_struct *)param_base;
    char *linux_cmd;

    //第一步:打印出param_base的基地址,这里就是0x30000100

    //这里的这个位置实际上是约定的,预留了256字节

    //然后初始化param_struct这个数据结构
    printk("Setup linux parameters at 0x%08lx\n", param_base);
    memset(params, 0, sizeof(struct param_struct));

    //填写params的两个成员

    //Linux kernel采用了页表方式,设置页表的大小,这里是4K
    params->u1.s.page_size = LINUX_PAGE_SIZE;
    params->u1.s.nr_pages = (DRAM_SIZE >> LINUX_PAGE_SHIFT);


    /* set linux command line */
    linux_cmd = get_linux_cmd_line();
    if (linux_cmd == NULL) {
        printk("Wrong magic: could not found linux command line\n");
    } else {

        //把命令行参数复制到params的commandline成员
        memcpy(params->commandline, linux_cmd, strlen(linux_cmd) + 1);
        printk("linux command line is: \"%s\"\n", linux_cmd);
    }
}

   
    如上,把不相关部分去掉了,加了注释。可以看出,这里就设置了param_struct必需的三个成员,核心是commandline。关于param_struct在linux内核的【include/arm/setup.h】中,各个成员的含义是:
 

/*
 * Usage:
 * - do not go blindly adding fields, add them at the end
 * - when adding fields, don't rely on the address until
 * a patch from me has been released
 * - unused fields should be zero (for future expansion)
 * - this structure is relatively short-lived - only
 * guaranteed to contain useful data in setup_arch()
 */

#define COMMAND_LINE_SIZE 1024

/* This is the old deprecated way to pass parameters to the kernel */
struct param_struct {
    union {
        struct {
            unsigned long page_size; /* 0 */
            unsigned long nr_pages; /* 4 */
            unsigned long ramdisk_size; /* 8 */
            unsigned long flags; /* 12 */
#define FLAG_READONLY 1
#define FLAG_RDLOAD 4
#define FLAG_RDPROMPT 8
            unsigned long rootdev; /* 16 */
            unsigned long video_num_cols; /* 20 */
            unsigned long video_num_rows; /* 24 */
            unsigned long video_x; /* 28 */
            unsigned long video_y; /* 32 */
            unsigned long memc_control_reg; /* 36 */
            unsigned char sounddefault; /* 40 */
            unsigned char adfsdrives; /* 41 */
            unsigned char bytes_per_char_h; /* 42 */
            unsigned char bytes_per_char_v; /* 43 */
            unsigned long pages_in_bank[4]; /* 44 */
            unsigned long pages_in_vram; /* 60 */
            unsigned long initrd_start; /* 64 */
            unsigned long initrd_size; /* 68 */
            unsigned long rd_start; /* 72 */
            unsigned long system_rev; /* 76 */
            unsigned long system_serial_low; /* 80 */
            unsigned long system_serial_high; /* 84 */
            unsigned long mem_fclk_21285; /* 88 */
        } s;
        char unused[256];
    } u1;
    union {
        char paths[8][128];
        struct {
            unsigned long magic;
            char n[1024 - sizeof(unsigned long)];
        } s;
    } u2;
    char commandline[COMMAND_LINE_SIZE];
};

 
    如上,具体选项的含义,可以参考【Documentation/arm/setup】,这里仅就用到的三个来进行解释。
 

page_size

   This parameter must be set to the page size of the machine, and
   will be checked by the kernel.

 nr_pages

   This is the total number of pages of memory in the system. If
   the memory is banked, then this should contain the total number
   of pages in the system.

   If the system contains separate VRAM, this value should not
   include this information.
 commandline

   Kernel command line parameters. Details can be found elsewhere.

 
    可以看出,这步的设置工作还是非常简单的。现在使用的页表大小为4K,也就是page_size的值。因为现在使用的sdram是64M,总页表项自然就是64M/page_size,也就是进行简单的右移就可以了(4K等效右移12位)。后面就是获取命令行参数的地址,然后填充comandline成员,最长的限度为1024.
 
    至此,vivi设置参数就完成了,约定参数的起始地址为boot_mem_base+0x0100处。这个地方是否需要作为参数传递给kernel,就需要与内核配合了。如果像Linux kernel约定的boot_mem_base+0x8000处存放内核映象一样,Linux kernel对s3c2410的支持同样可以约定参数固定存放于boot_mem_base+0x0100。如果没有此约定,那么就需要传递参数首地址了。
 
(5)获取机器号
 

mach_type = get_param_value("mach_type", &ret);

 
    这个号是固定的。可以参考arch/arm/tools/mach-types。这里列出了所有支持的机器号。应该是按照先后的支持顺序排列。可以看到smdk2410为:
 

smdk2410        S3C2410_SMDK        SMDK2410        193

 
(6)启动内核
 

call_linux(0, mach_type, to);

 
    到这里才算是真正启动内核了,使用内嵌汇编写的。这里的三个参数,根据APCS原则,应该分别给R0,R1, R2.这样也就是说,现在:
 
    ・ R0 设置为0
    ・ R1 machine type number(193)
    ・ R2 内核的第一条指令的起始地址(注意,这里并非参数表的首地址)
 

void call_linux(long a0, long a1, long a2)
{
    cache_clean_invalidate();
    tlb_invalidate();

__asm__(
    "mov    r0, %0\n"
    "mov    r1, %1\n"
    "mov    r2, %2\n"
    "mov    ip, #0\n"
    "mcr    p15, 0, ip, c13, c0, 0\n"    /* zero PID */
    "mcr    p15, 0, ip, c7, c7, 0\n"    /* invalidate I,D caches */
    "mcr    p15, 0, ip, c7, c10, 4\n"    /* drain write buffer */
    "mcr    p15, 0, ip, c8, c7, 0\n"    /* invalidate I,D TLBs */
    "mrc    p15, 0, ip, c1, c0, 0\n"    /* get control register */
    "bic    ip, ip, #0x0001\n"        /* disable MMU */
    "mcr    p15, 0, ip, c1, c0, 0\n"    /* write control register */
    "mov    pc, r2\n"
    "nop\n"
    "nop\n"
    : /* no outpus */
    : "r" (a0), "r" (a1), "r" (a2)
    );

}

 
    汇编很简洁。参考前面booting文档,就是做上述工作。现在对R0、R1、R2参数传递完成,不过R2在这里并非tag的首地址,因为采用的是param_struct模式,所以可以猜测kernel的arch(实际上就是HAL层)肯定有对应的默认地址起始地址(这里是0x30000100)。其余部分,中断都关闭了,PID为0,I cache和D cache都禁止,write buffer清理,I D TLBS也禁止,禁止MMU。最后mov pc, r2则跳转到内核映象的第一条指令位置。
 
    到这里,vivi的使命完全完成了!后续的工作就交给kernel了。
 
    为了对参数传递这个情景分析清楚,所以还必须分析Linux kernel如何启动。这部分不打算过多的深入细节,首先应该从整体上分析。然而,还是应该借助代码才能理解的更为清晰。这里,有taoyuetao的Linux启动分析系列文章可以参考,我想,他分析Linux启动也是如同我分析vivi一样,一步一步走过来的。借鉴一下,省去了我不少劳动,在此感谢。后续的描述中Linux kernel启动部分借鉴taoyuetao的经验,但是对其进行了扩展,增加了zImage如何生成的更为详细的解释。

评论

发表评论

此博客中的热门博文

【转】cs8900网卡的移植至基于linux2.6内核的s3c2410平台

cs8900网卡的移植至基于linux2.6内核的s3c2410平台(转) 2008-03-11 20:58 硬件环境:SBC-2410X开发板(CPU:S3C2410X) 内核版本:2.6.11.1 运行环境:Debian2.6.8 交叉编译环境:gcc-3.3.4-glibc-2.3.3 第一部分 网卡CS8900A驱动程序的移植 一、从网上将Linux内核源代码下载到本机上,并将其解压: #tar jxf linux-2.6.11.1.tar.bz2 二、打开内核顶层目录中的Makefile文件,这个文件中需要修改的内容包括以下两个方面。 (1)指定目标平台。 移植前:         ARCH?= $(SUBARCH) 移植后: ARCH            :=arm (2)指定交叉编译器。 移植前: CROSS_COMPILE ?= 移植后: CROSS_COMPILE   :=/opt/crosstool/arm-s3c2410-linux-gnu/gcc-3.3.4-glibc-2.3.3/bin/arm-s3c2410-linux-gnu- 注:这里假设编译器就放在本机的那个目录下。 三、添加驱动程序源代码,这涉及到以下几个方面。(1)、从网上下载了cs8900.c和cs8900.h两个针对2.6.7的内核的驱动程序源代码,将其放在drivers/net/arm/目录下面。 #cp cs8900.c ./drivers/net/arm/ #cp cs8900.h ./drivers/net/arm/ 并在cs8900_probe()函数中,memset (&priv,0,sizeof (cs8900_t));函数之后添加如下两条语句: __raw_writel(0x2211d110,S3C2410_BWSCON); __raw_writel(0x1f7c,S3C2410_BANKCON3); 注:其原因在"第二部分"解释。 (2)、修改drivers/net/arm/目录下的Kconfig文件,在最后添加如...

【转】嵌入式开发中使用DDD进行调试

 在嵌入式程序开发过程中,程序员要进行大量的调试,以此验证程序的正确性,修改潜在的错误。调试器对于程序员来说是不可或缺的必备工具。在Linux环境中,有很多调试工具和调试辅助工具,例如GDB、XXGDB、RHIDE、XWPE、GVD和DDD等。其中,DDD是命令行调试器的图形前端,除了一般的程序调试功能以外,还具有交互式图形数据显示的功能。它在嵌入式应用开发中也十分出色。本文主要讲述DDD(Data Display Debugger)的使用方法。      1.认识DDD     GNU DDD是命令行调试程序,如GDB、DBX、WDB、Ladebug、JDB、XDB、Perl Debugger或Python Debugger的可视化图形前端。它特有的图形数据显示功能(Graphical Data Display)可以把数据结构按照图形的方式显示出来。DDD最初源于1990年Andreas Zeller编写的VSL结构化语言,后来经过一些程序员的努力,演化成今天的模样。DDD的功能非常强大,可以调试用C\C++、Ada、 Fortran、Pascal、Modula-2和Modula-3编写的程序;可以超文本方式浏览源代码;能够进行断点设置、回溯调试和历史纪录编辑;具有程序在终端运行的仿真窗口,并在远程主机上进行调试的能力;图形数据显示功能(Graphical Data Display)是创建该调试器的初衷之一,能够显示各种数据结构之间的关系,并将数据结构以图形化形式显示;具有GDB/DBX/XDB的命令行界面,包括完全的文本编辑、历史纪录、搜寻引擎。     DDD是开源软件,用户可以去 http://www.cs.tubs.de/softech/ddd/ 下载.rpm格式的DDD源码文件。      图1显示的是DDD的主窗口。它主要由选单栏、工具条、数据窗口、源文件窗口、机器码窗口、控制台和命令工具窗口等几部分组成。其中,数据窗口用于观察复杂的数据结构,在删除数据之后,显示仍然有效;源文件窗口显示源代码、断点和当前执行到达的位置,选择该窗口中的"Display"项,可以显示任意表达式的值;机...

【转】函数指针和指针函数

【函数指针】        在程序运行中,函数代码是程序的算法指令部分,它们和数组一样也占用存储空间,都有相应的地址。可以使用指针变量指向数组的首地址,也可以使用指针变量指向函数代码的首地址,指向函数代码首地址的指针变量称为函数指针。 1.函数指针定义 函数类型 (*指针变量名)(形参列表); "函数类型"说明函数的返回类型,由于"()"的优先级高于"*",所以指针变量名外的括号必不可少,后面的"形参列表"表示指针变量指向的函数所带的参数列表。 例如: int (*f)(int x); double (*ptr)(double x); 在定义函数指针时请注意:     函数指针和它指向的函数的参数个数和类型都应该是―致的; 函数指针的类型和函数的返回值类型也必须是一致的。 2.函数指针的赋值 函数名和数组名一样代表了函数代码的首地址,因此在赋值时,直接将函数指针指向函数名就行了。 例如, int func(int x);   /* 声明一个函数 */ int (*f) (int x);    /* 声明一个函数指针 */ f=func;            /* 将func函数的首地址赋给指针f */ 赋值时函数func不带括号,也不带参数,由于func代表函数的首地址,因此经过赋值以后,指针f就指向函数func(x)的代码的首地址。 3.通过函数指针调用函数 函数指针是通过函数名及有关参数进行调用的。 与其他指针变量相类似,如果指针变量pi是指向某整型变量i的指针,则*p等于它所指的变量i;如果pf是指向某浮点型变量f的指针,则*pf就等价于它所指的变量f。同样地,*f是指向函数func(x)的指针,则*f就代表它所指向的函数func。所以在执行了f=func;之后,(*f)和func代表同一函数。 由于函数指针指向存储区中的某个函数,因此可以通过函数指针调用相应的函数。现在我们就讨论如何用函数指针调用函数,它应执行下面三步: 首先,要说明函数指针变量。 例如:int (*f)...