Linux内核配置系统浅析

  随着 Linux 操作系统的广泛应用,特别是 Linux 在嵌入式领域的发展,越来越多的人开始投身到 Linux 内核级的开发中。面对日益庞大的 Linux 内核源代码,开发者在完成自己的内核代码后,都将面临着同样的问题,即如何将源代码融入到 Linux 内核中,增加相应的 Linux 配置选项,并最终被编译进 Linux 内核。这就需要了解 Linux 的内核配置系统。

众所周知,Linux 内核是由分布在全球的 Linux 爱好者共同开发的,Linux 内核每天都面临着许多新的变化。但是,Linux 内核的组织并没有出现混乱的现象,反而显得非常的简洁,而且具有很好的扩展性,开发人员可以很方便的向 Linux 内核中增加新的内容。原因之一就是 Linux 采用了模块化的内核配置系统,从而保证了内核的扩展性。

本文首先分析了 Linux 内核中的配置系统结构,然后,解释了 Makefile 和配置文件的格式以及配置语句的含义,最后,通过一个简单的例子--TEST Driver,具体说明如何将自行开发的代码加入到 Linux 内核中。在下面的文章中,不可能解释所有的功能和命令,只对那些常用的进行解释,至于那些没有讨论到的,请读者参考后面的参考文献。

1. 配置系统的基本结构
Linux内核的配置系统由三个部分组成,分别是:

Makefile:分布在 Linux 内核源代码中的 Makefile,定义 Linux 内核的编译规则;
配置文件(config.in):给用户提供配置选择的功能;
配置工具:包括配置命令解释器(对配置脚本中使用的配置命令进行解释)和配置用户界面(提供基于字符界面、基于 Ncurses 图形界面以及基于 Xwindows 图形界面的用户配置界面,各自对应于 Make config、Make menuconfig 和 make xconfig)。
这些配置工具都是使用脚本语言,如 Tcl/TK、Perl 编写的(也包含一些用 C 编写的代码)。本文并不是对配置系统本身进行分析,而是介绍如何使用配置系统。所以,除非是配置系统的维护者,一般的内核开发者无须了解它们的原理,只需要知道如何编写 Makefile 和配置文件就可以。所以,在本文中,我们只对 Makefile 和配置文件进行讨论。另外,凡是涉及到与具体 CPU 体系结构相关的内容,我们都以 ARM 为例,这样不仅可以将讨论的问题明确化,而且对内容本身不产生影响。

2. Makefile


2.1 Makefile 概述
Makefile 的作用是根据配置的情况,构造出需要编译的源文件列表,然后分别编译,并把目标代码链接到一起,最终形成 Linux 内核二进制文件。

由于 Linux 内核源代码是按照树形结构组织的,所以 Makefile 也被分布在目录树中。Linux 内核中的 Makefile 以及与 Makefile 直接相关的文件有:

Makefile:顶层 Makefile,是整个内核配置、编译的总体控制文件。
.config:内核配置文件,包含由用户选择的配置选项,用来存放内核配置后的结果(如 make config)。
arch/*/Makefile:位于各种 CPU 体系目录下的 Makefile,如 arch/arm/Makefile,是针对特定平台的 Makefile。
各个子目录下的 Makefile:比如 drivers/Makefile,负责所在子目录下源代码的管理。
Rules.make:规则文件,被所有的 Makefile 使用。
用户通过 make config 配置后,产生了 .config。顶层 Makefile 读入 .config 中的配置选择。顶层 Makefile 有两个主要的任务:产生 vmlinux 文件和内核模块(module)。为了达到此目的,顶层 Makefile 递归的进入到内核的各个子目录中,分别调用位于这些子目录中的 Makefile。至于到底进入哪些子目录,取决于内核的配置。在顶层 Makefile 中,有一句:include arch/$(ARCH)/Makefile,包含了特定 CPU 体系结构下的 Makefile,这个 Makefile 中包含了平台相关的信息。

位于各个子目录下的 Makefile 同样也根据 .config 给出的配置信息,构造出当前配置下需要的源文件列表,并在文件的最后有 include $(TOPDIR)/Rules.make。

Rules.make 文件起着非常重要的作用,它定义了所有 Makefile 共用的编译规则。比如,如果需要将本目录下所有的 c 程序编译成汇编代码,需要在 Makefile 中有以下的编译规则:

%.s: %.c
$(CC) $(CFLAGS) -S $< -o $@

有很多子目录下都有同样的要求,就需要在各自的 Makefile 中包含此编译规则,这会比较麻烦。而 Linux 内核中则把此类的编译规则统一放置到 Rules.make 中,并在各自的 Makefile 中包含进了 Rules.make(include Rules.make),这样就避免了在多个 Makefile 中重复同样的规则。对于上面的例子,在 Rules.make 中对应的规则为:

%.s: %.c
$(CC) $(CFLAGS) $(EXTRA_CFLAGS) $(CFLAGS_$(*F)) $(CFLAGS_$@) -S $< -o $@

2.2 Makefile 中的变量
顶层 Makefile 定义并向环境中输出了许多变量,为各个子目录下的 Makefile 传递一些信息。有些变量,比如 SUBDIRS,不仅在顶层 Makefile 中定义并且赋初值,而且在 arch/*/Makefile 还作了扩充。

常用的变量有以下几类:

1) 版本信息
版本信息有:VERSION,PATCHLEVEL, SUBLEVEL, EXTR***ERSION,KERNELRELEASE。版本信息定义了当前内核的版本,比如 VERSION=2,PATCHLEVEL=4,SUBLEVEL=18,EXAT***ERSION=-rmk7,它们共同构成内核的发行版本KERNELRELEASE:2.4.18-rmk7

2) CPU 体系结构:ARCH
在顶层 Makefile 的开头,用 ARCH 定义目标 CPU 的体系结构,比如 ARCH:=arm 等。许多子目录的 Makefile 中,要根据 ARCH 的定义选择编译源文件的列表。

3) 路径信息:TOPDIR, SUBDIRS
TOPDIR 定义了 Linux 内核源代码所在的根目录。例如,各个子目录下的 Makefile 通过 $(TOPDIR)/Rules.make 就可以找到 Rules.make 的位置。
SUBDIRS 定义了一个目录列表,在编译内核或模块时,顶层 Makefile 就是根据 SUBDIRS 来决定进入哪些子目录。SUBDIRS 的值取决于内核的配置,在顶层 Makefile 中 SUBDIRS 赋值为 kernel drivers mm fs net ipc lib;根据内核的配置情况,在 arch/*/Makefile 中扩充了 SUBDIRS 的值,参见4)中的例子。

4) 内核组成信息:HEAD, CORE_FILES, NETWORKS, DRIVERS, LIBS
Linux 内核文件 vmlinux 是由以下规则产生的:


vmlinux: $(CONFIGURATION) init/main.o init/version.o linuxsubdirs
$(LD) $(LINKFLAGS) $(HEAD) init/main.o init/version.o /
--start-group /
$(CORE_FILES) /
$(DRIVERS) /
$(NETWORKS) /
$(LIBS) /
--end-group /
-o vmlinux

 

可以看出,vmlinux 是由 HEAD、main.o、version.o、CORE_FILES、DRIVERS、NETWORKS 和 LIBS 组成的。这些变量(如 HEAD)都是用来定义连接生成 vmlinux 的目标文件和库文件列表。其中,HEAD在arch/*/Makefile 中定义,用来确定被最先链接进 vmlinux 的文件列表。比如,对于 ARM 系列的 CPU,HEAD 定义为:


HEAD := arch/arm/kernel/head-$(PROCESSOR).o /
arch/arm/kernel/init_task.o

 

表明 head-$(PROCESSOR).o 和 init_task.o 需要最先被链接到 vmlinux 中。PROCESSOR 为 armv 或 armo,取决于目标 CPU。 CORE_FILES,NETWORK,DRIVERS 和 LIBS 在顶层 Makefile 中定义,并且由 arch/*/Makefile 根据需要进行扩充。 CORE_FILES 对应着内核的核心文件,有 kernel/kernel.o,mm/mm.o,fs/fs.o,ipc/ipc.o,可以看出,这些是组成内核最为重要的文件。同时,arch/arm/Makefile 对 CORE_FILES 进行了扩充:


# arch/arm/Makefile

# If we have a machine-specific directory, then include it in the build.
MACHDIR := arch/arm/mach-$(MACHINE)
ifeq ($(MACHDIR),$(wildcard $(MACHDIR)))
SUBDIRS += $(MACHDIR)
CORE_FILES := $(MACHDIR)/$(MACHINE).o $(CORE_FILES)
endif

HEAD := arch/arm/kernel/head-$(PROCESSOR).o /
arch/arm/kernel/init_task.o
SUBDIRS += arch/arm/kernel arch/arm/mm arch/arm/lib arch/arm/nwfpe
CORE_FILES := arch/arm/kernel/kernel.o arch/arm/mm/mm.o $(CORE_FILES)
LIBS := arch/arm/lib/lib.a $(LIBS)


5) 编译信息:CPP, CC, AS, LD, AR,CFLAGS,LINKFLAGS
在 Rules.make 中定义的是编译的通用规则,具体到特定的场合,需要明确给出编译环境,编译环境就是在以上的变量中定义的。针对交叉编译的要求,定义了 CROSS_COMPILE。比如:


CROSS_COMPILE = arm-linux-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
......


CROSS_COMPILE 定义了交叉编译器前缀 arm-linux-,表明所有的交叉编译工具都是以 arm-linux- 开头的,所以在各个交叉编译器工具之前,都加入了 $(CROSS_COMPILE),以组成一个完整的交叉编译工具文件名,比如 arm-linux-gcc。
CFLAGS 定义了传递给 C 编译器的参数。
LINKFLAGS 是链接生成 vmlinux 时,由链接器使用的参数。LINKFLAGS 在 arm/*/Makefile 中定义,比如:


# arch/arm/Makefile

LINKFLAGS :=-p -X -T arch/arm/vmlinux.lds


6) 配置变量CONFIG_*
.config 文件中有许多的配置变量等式,用来说明用户配置的结果。例如 CONFIG_MODULES=y 表明用户选择了 Linux 内核的模块功能。
.config 被顶层 Makefile 包含后,就形成许多的配置变量,每个配置变量具有确定的值:y 表示本编译选项对应的内核代码被静态编译进 Linux 内核;m 表示本编译选项对应的内核代码被编译成模块;n 表示不选择此编译选项;如果根本就没有选择,那么配置变量的值为空。

2.3 Rules.make 变量
前面讲过,Rules.make 是编译规则文件,所有的 Makefile 中都会包括 Rules.make。Rules.make 文件定义了许多变量,最为重要是那些编译、链接列表变量。


O_OBJS,L_OBJS,OX_OBJS,LX_OBJS:本目录下需要编译进 Linux 内核 vmlinux 的目标文件列表,其中 OX_OBJS 和 LX_OBJS 中的 "X" 表明目标文件使用了 EXPORT_SYMBOL 输出符号。

M_OBJS,MX_OBJS:本目录下需要被编译成可装载模块的目标文件列表。同样,MX_OBJS 中的 "X" 表明目标文件使用了 EXPORT_SYMBOL 输出符号。

O_TARGET,L_TARGET:每个子目录下都有一个 O_TARGET 或 L_TARGET,Rules.make 首先从源代码编译生成 O_OBJS 和 OX_OBJS 中所有的目标文件,然后使用 $(LD) -r 把它们链接成一个 O_TARGET 或 L_TARGET。O_TARGET 以 .o 结尾,而 L_TARGET 以 .a 结尾。

2.4 子目录 Makefile
子目录 Makefile 用来控制本级目录以下源代码的编译规则。我们通过一个例子来讲解子目录 Makefile 的组成:


#
# Makefile for the linux kernel.
#
# All of the (potential) objects that export symbols.
# This list comes from 'grep -l EXPORT_SYMBOL *.[hc]'.

export-objs := tc.o

# Object file lists.

obj-y :=
obj-m :=
obj-n :=
obj- :=

obj-$(CONFIG_TC) += tc.o
obj-$(CONFIG_ZS) += zs.o
obj-$(CONFIG_VT) += lk201.o lk201-map.o lk201-remap.o

# Files that are both resident and modular: remove from modular.

obj-m := $(filter-out $(obj-y), $(obj-m))

# Translate to Rules.make lists.

L_TARGET := tc.a

L_OBJS := $(sort $(filter-out $(export-objs), $(obj-y)))
LX_OBJS := $(sort $(filter $(export-objs), $(obj-y)))
M_OBJS := $(sort $(filter-out $(export-objs), $(obj-m)))
MX_OBJS := $(sort $(filter $(export-objs), $(obj-m)))

include $(TOPDIR)/Rules.make


a) 注释
对 Makefile 的说明和解释,由#开始。

b) 编译目标定义
类似于 obj-$(CONFIG_TC) += tc.o 的语句是用来定义编译的目标,是子目录 Makefile 中最重要的部分。编译目标定义那些在本子目录下,需要编译到 Linux 内核中的目标文件列表。为了只在用户选择了此功能后才编译,所有的目标定义都融合了对配置变量的判断。
前面说过,每个配置变量取值范围是:y,n,m 和空,obj-$(CONFIG_TC) 分别对应着 obj-y,obj-n,obj-m,obj-。如果 CONFIG_TC 配置为 y,那么 tc.o 就进入了 obj-y 列表。obj-y 为包含到 Linux 内核 vmlinux 中的目标文件列表;obj-m 为编译成模块的目标文件列表;obj-n 和 obj- 中的文件列表被忽略。配置系统就根据这些列表的属性进行编译和链接。
export-objs 中的目标文件都使用了 EXPORT_SYMBOL() 定义了公共的符号,以便可装载模块使用。在 tc.c 文件的最后部分,有 "EXPORT_SYMBOL(search_tc_card);",表明 tc.o 有符号输出。
这里需要指出的是,对于编译目标的定义,存在着两种格式,分别是老式定义和新式定义。老式定义就是前面 Rules.make 使用的那些变量,新式定义就是 obj-y,obj-m,obj-n 和 obj-。Linux 内核推荐使用新式定义,不过由于 Rules.make 不理解新式定义,需要在 Makefile 中的适配段将其转换成老式定义。

c) 适配段
适配段的作用是将新式定义转换成老式定义。在上面的例子中,适配段就是将 obj-y 和 obj-m 转换成 Rules.make 能够理解的 L_TARGET,L_OBJS,LX_OBJS,M_OBJS,MX_OBJS。
L_OBJS := $(sort $(filter-out $(export-objs), $(obj-y))) 定义了 L_OBJS 的生成方式:在 obj-y 的列表中过滤掉 export-objs(tc.o),然后排序并去除重复的文件名。这里使用到了 GNU Make 的一些特殊功能,具体的含义可参考 Make 的文档(info make)。

d) include $(TOPDIR)/Rules.make

3. 配置文件


3.1 配置功能概述
除了 Makefile 的编写,另外一个重要的工作就是把新功能加入到 Linux 的配置选项中,提供此项功能的说明,让用户有机会选择此项功能。所有的这些都需要在 config.in 文件中用配置语言来编写配置脚本,
在 Linux 内核中,配置命令有多种方式:

配置命令 解释脚本
Make config, make oldconfig scripts/Configure
Make menuconfig scripts/Menuconfig
Make xconfig scripts/tkparse

以字符界面配置(make config)为例,顶层 Makefile 调用 scripts/Configure, 按照 arch/arm/config.in 来进行配置。命令执行完后产生文件 .config,其中保存着配置信息。下一次再做 make config 将产生新的 .config 文件,原 .config 被改名为 .config.old

3.2 配置语言
1) 顶层菜单
mainmenu_name /prompt/ /prompt/ 是用'或"包围的字符串,'与"的区别是'…'中可使用$引用变量的值。mainmenu_name 设置最高层菜单的名字,它只在 make xconfig 时才会显示。

2) 询问语句


bool /prompt/ /symbol/
hex /prompt/ /symbol/ /word/
int /prompt/ /symbol/ /word/
string /prompt/ /symbol/ /word/
tristate /prompt/ /symbol/


询问语句首先显示一串提示符 /prompt/,等待用户输入,并把输入的结果赋给 /symbol/ 所代表的配置变量。不同的询问语句的区别在于它们接受的输入数据类型不同,比如 bool 接受布尔类型( y 或 n ),hex 接受 16 进制数据。有些询问语句还有第三个参数 /word/,用来给出缺省值。


3) 定义语句


define_bool /symbol/ /word/
define_hex /symbol/ /word/
define_int /symbol/ /word/
define_string /symbol/ /word/
define_tristate /symbol/ /word/

 

不同于询问语句等待用户输入,定义语句显式的给配置变量 /symbol/ 赋值 /word/。

4) 依赖语句

 

dep_bool /prompt/ /symbol/ /dep/ ...
dep_mbool /prompt/ /symbol/ /dep/ ...
dep_hex /prompt/ /symbol/ /word/ /dep/ ...
dep_int /prompt/ /symbol/ /word/ /dep/ ...
dep_string /prompt/ /symbol/ /word/ /dep/ ...
dep_tristate /prompt/ /symbol/ /dep/ ...

 

与询问语句类似,依赖语句也是定义新的配置变量。不同的是,配置变量/symbol/的取值范围将依赖于配置变量列表/dep/ …。这就意味着:被定义的配置变量所对应功能的取舍取决于依赖列表所对应功能的选择。以dep_bool为例,如果/dep/ …列表的所有配置变量都取值y,则显示/prompt/,用户可输入任意的值给配置变量/symbol/,但是只要有一个配置变量的取值为n,则/symbol/被强制成n。
不同依赖语句的区别在于它们由依赖条件所产生的取值范围不同。

5) 选择语句


choice /prompt/ /word/ /word/


choice 语句首先给出一串选择列表,供用户选择其中一种。比如 Linux for ARM 支持多种基于 ARM core 的 CPU,Linux 使用 choice 语句提供一个 CPU 列表,供用户选择:


choice 'ARM system type' /
"Anakin CONFIG_ARCH_ANAKIN /
Archimedes/A5000 CONFIG_ARCH_ARCA5K /
Cirrus-CL-PS7500FE CONFIG_ARCH_CLPS7500 /
……
SA1100-based CONFIG_ARCH_SA1100 /
Shark CONFIG_ARCH_SHARK" RiscPC

 

Choice 首先显示 /prompt/,然后将 /word/ 分解成前后两个部分,前部分为对应选择的提示符,后部分是对应选择的配置变量。用户选择的配置变量为 y,其余的都为 n。

6) if语句


if [ /expr/ ] ; then
/statement/
...
fi

if [ /expr/ ] ; then
/statement/
...
else
/statement/
...
fi

 

if 语句对配置变量(或配置变量的组合)进行判断,并作出不同的处理。判断条件 /expr/ 可以是单个配置变量或字符串,也可以是带操作符的表达式。操作符有:=,!=,-o,-a 等。

7) 菜单块(menu block)语句

 

mainmenu_option next_comment
comment '…..'

endmenu


引入新的菜单。在向内核增加新的功能后,需要相应的增加新的菜单,并在新菜单下给出此项功能的配置选项。Comment 后带的注释就是新菜单的名称。所有归属于此菜单的配置选项语句都写在 comment 和 endmenu 之间。

8) Source 语句
source /word/
/word/ 是文件名,source 的作用是调入新的文件。

3.3 缺省配置
Linux 内核支持非常多的硬件平台,对于具体的硬件平台而言,有些配置就是必需的,有些配置就不是必需的。另外,新增加功能的正常运行往往也需要一定的先决条件,针对新功能,必须作相应的配置。因此,特定硬件平台能够正常运行对应着一个最小的基本配置,这就是缺省配置。

Linux 内核中针对每个 ARCH 都会有一个缺省配置。在向内核代码增加了新的功能后,如果新功能对于这个 ARCH 是必需的,就要修改此 ARCH 的缺省配置。修改方法如下(在 Linux 内核根目录下):

备份 .config 文件
cp arch/arm/deconfig .config
修改 .config
cp .config arch/arm/deconfig
恢复 .config
如果新增的功能适用于许多的 ARCH,只要针对具体的 ARCH,重复上面的步骤就可以了。

3.4 help file
大家都有这样的经验,在配置 Linux 内核时,遇到不懂含义的配置选项,可以查看它的帮助,从中可得到选择的建议。下面我们就看看如何给给一个配置选项增加帮助信息。

所有配置选项的帮助信息都在 Documentation/Configure.help 中,它的格式为:

 

<description>
<variable name>
<help file>


<description> 给出本配置选项的名称,<variable name> 对应配置变量,<help file> 对应配置帮助信息。在帮助信息中,首先简单描述此功能,其次说明选择了此功能后会有什么效果,不选择又有什么效果,最后,不要忘了写上"如果不清楚,选择 N(或者)Y",给不知所措的用户以提示。

4. 实例
对于一个开发者来说,将自己开发的内核代码加入到 Linux 内核中,需要有三个步骤。首先确定把自己开发代码放入到内核的位置;其次,把自己开发的功能增加到 Linux 内核的配置选项中,使用户能够选择此功能;最后,构建子目录 Makefile,根据用户的选择,将相应的代码编译到最终生成的 Linux 内核中去。下面,我们就通过一个简单的例子--test driver,结合前面学到的知识,来说明如何向 Linux 内核中增加新的功能。

4.1 目录结构
test driver 放置在 drivers/test/ 目录下:


$cd drivers/test
$tree
.
|-- Config.in
|-- Makefile
|-- cpu
| |-- Makefile
| `-- cpu.c
|-- test.c
|-- test_client.c
|-- test_ioctl.c
|-- test_proc.c
|-- test_queue.c
`-- test
|-- Makefile
`-- test.c

 

4.2 配置文件
1) drivers/test/Config.in

 

#
# TEST driver configuration
#
mainmenu_option next_comment
comment 'TEST Driver'

bool 'TEST support' CONFIG_TEST
if [ "$CONFIG_TEST" = "y" ]; then
tristate 'TEST user-space interface' CONFIG_TEST_USER
bool 'TEST CPU ' CONFIG_TEST_CPU
fi

endmenu


由于 test driver 对于内核来说是新的功能,所以首先创建一个菜单 TEST Driver。然后,显示 "TEST support",等待用户选择;接下来判断用户是否选择了 TEST Driver,如果是(CONFIG_TEST=y),则进一步显示子功能:用户接口与 CPU 功能支持;由于用户接口功能可以被编译成内核模块,所以这里的询问语句使用了 tristate(因为 tristate 的取值范围包括 y、n 和 m,m 就是对应着模块)。

2) arch/arm/config.in
在文件的最后加入:source drivers/test/Config.in,将 TEST Driver 子功能的配置纳入到 Linux 内核的配置中。

4.3 Makefile
1)drivers/test/Makefile

 

# drivers/test/Makefile
#
# Makefile for the TEST.
#

SUB_DIRS :=
MOD_SUB_DIRS := $(SUB_DIRS)
ALL_SUB_DIRS := $(SUB_DIRS) cpu

L_TARGET := test.a
export-objs := test.o test_client.o

obj-$(CONFIG_TEST) += test.o test_queue.o test_client.o
obj-$(CONFIG_TEST_USER) += test_ioctl.o
obj-$(CONFIG_PROC_FS) += test_proc.o

subdir-$(CONFIG_TEST_CPU) += cpu

include $(TOPDIR)/Rules.make

clean:
for dir in $(ALL_SUB_DIRS); do make -C $$dir clean; done
rm -f *.[oa] .*.flags

 

drivers/test 目录下最终生成的目标文件是 test.a。在 test.c 和 test-client.c 中使用了 EXPORT_SYMBOL 输出符号,所以 test.o 和 test-client.o 位于 export-objs 列表中。然后,根据用户的选择(具体来说,就是配置变量的取值),构建各自对应的 obj-* 列表。由于 TEST Driver 中包一个子目录 cpu,当 CONFIG_TEST_CPU=y(即用户选择了此功能)时,需要将 cpu 目录加入到 subdir-y 列表中。

2)drivers/test/cpu/Makefile

 

# drivers/test/test/Makefile
#
# Makefile for the TEST CPU
#

SUB_DIRS :=
MOD_SUB_DIRS := $(SUB_DIRS)
ALL_SUB_DIRS := $(SUB_DIRS)

L_TARGET := test_cpu.a

obj-$(CONFIG_test_CPU) += cpu.o


include $(TOPDIR)/Rules.make

clean:
rm -f *.[oa] .*.flags

 

3)drivers/Makefile

 

……
subdir-$(CONFIG_TEST) += test
……
include $(TOPDIR)/Rules.make


在 drivers/Makefile 中加入 subdir-$(CONFIG_TEST)+= test,使得在用户选择 TEST Driver 功能后,内核编译时能够进入 test 目录。

4)Makefile

 

……
DRIVERS-$(CONFIG_PLD) += drivers/pld/pld.o
DRIVERS-$(CONFIG_TEST) += drivers/test/test.a
DRIVERS-$(CONFIG_TEST_CPU) += drivers/test/cpu/test_cpu.a

DRIVERS := $(DRIVERS-y)
……


在顶层 Makefile 中加入 DRIVERS-$(CONFIG_TEST) += drivers/test/test.a 和 DRIVERS-$(CONFIG_TEST_CPU) += drivers/test/cpu/test_cpu.a。如何用户选择了 TEST Driver,那么 CONFIG_TEST 和 CONFIG_TEST_CPU 都是 y,test.a 和 test_cpu.a 就都位于 DRIVERS-y 列表中,然后又被放置在 DRIVERS 列表中。在前面曾经提到过,Linux 内核文件 vmlinux 的组成中包括 DRIVERS,所以 test.a 和 test_cpu.a 最终可被链接到 vmlinux 中。

参考资料

Document/kbuild/makefiles.txt,Linux Kernel Source code


Document/kbuild/config-language.txt,Linux Kernel Source code

?

http://www.ublog.cn/user1/3595/
 
 
 
添加评论
16:29  |  固定链接 | 引用通告 (0) | 记录它
 
 
固定链接  关闭
 
 
 

 
 
 
 
 

 
linux 2.6内模模块加载问题
 
1,一个简单的驱动程序
?
//hello.c
#ifndef __KERNEL__
#define __KERNEL__
#endif
#ifndef MODULE
#define MODULE
#endif
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
int hello(void)
{
?printk("This is a module,hello PeiJun!/n");
?return 0;
}
void bye(void)
{
?printk("Bye-bye!/n");
}
module_init(hello);
module_exit(bye);
?
2, 编写Makefile
obj-m := hello.o
KERNELDIR = /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
?$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
?rm -f hello.ko hello.mod hello.mod.o hello.o
?
3,执行make后,生成hello.ko hello.mod hello.mod.o hello.o
?
4, 执行insmod hello.o
.....
insmod: erro inserting 'hello.o' : -1 Invalid module format
?
2.4不会生成这些文件,只有2.6才会生成hello.ko文件,用*.ko后缀来区分内核模块与一般的目标文件,在2.4下也是在x window的终端gnome终端口加载这样的模块,printk可以正常打印,但到了2.6就不行了.文本模式下不行,主要是printk优先级的问题.内核的优先级定义可以在linux/kernel.h和kernel/printk.c中看.
?
5,执行insmod hello.ko
显示 This is a module,hello PeiJun!
?
6,lsmod查看
Module???????????????????? Size??????????? Used by
hello???????????????????????? 1536?????????? 0
?
7,执行rmmod hello
显示: Bye-bye!
?
 
 
添加评论
16:17  |  固定链接 | 引用通告 (0) | 记录它
 
 
固定链接  关闭
 
 
 

 
 
 
 
 

 
Linux内核Makefile文件
 
Linux内核Makefile文件

--译自Linux2.6.x Kernel Makefiles
http://bbs.zndev.com/htm_data/12/0510/100065.html
本文档描述了linux内核的makefile文件。
=== 目录
? ? ?=== 1 概述
? ? ?=== 2 角色分工
? ? ?=== 3 内核编译文件
? ? ? ? --- 3.1 目标定义
? ? ? ? --- 3.2 内嵌对象 - obj-y
? ? ? ? --- 3.3 可加载模块 - obj-m
? ? ? ? --- 3.4 导出符号
? ? ? ? --- 3.5 库文件 - lib-y
? ? ? ? --- 3.6 目录递归
? ? ? ? --- 3.7 编译标记
? ? ? ? --- 3.8 命令依赖
? ? ? ? --- 3.9 依赖关系
? ? ? ? --- 3.10 特殊规则
? ? ?=== 4 辅助程序
? ? ? ? --- 4.1 简单辅助程序
? ? ? ? --- 4.2 组合辅助程序
? ? ? ? --- 4.3 定义共享库
? ? ? ? --- 4.4 C++语言使用方法
? ? ? ? --- 4.5 辅助程序编译控制选项
? ? ? ? --- 4.6 何时建立辅助程序
? ? ? ? --- 4.7 使用hostprogs-$(CONFIG_FOO)
? ? ?=== 5 编译清除机制
? ? ?=== 6 体系Makefile文件
? ? ? ? --- 6.1 变量设置
? ? ? ? --- 6.2 增加预设置项
? ? ? ? --- 6.3 目录表
? ? ? ? --- 6.4 引导映像
? ? ? ? --- 6.5 编译非内核目标
? ? ? ? --- 6.6 编译引导映像命令
? ? ? ? --- 6.7 定制编译命令
? ? ? ? --- 6.8 预处理连接脚本
? ? ? ? --- 6.9 $(CC)支持功能
? ? ?=== 7 Kbuild变量
? ? ?=== 8 Makefile语言
? ? ?=== 9 Credits
? ? ?=== 10 TODO

=== 1 概述
Makefile包括五部分:
? ? ?Makefile ? ? ? ? ? ?顶层Makefile文件
? ? ?.config ? ? ? ? ? ? ? ? ?内核配置文件
? ? ?arch/$(ARCH)/Makefile ? ? ?机器体系Makefile文件
? ? ?scripts/Makefile.* ? ? ?所有内核Makefiles共用规则
? ? ?kbuild Makefiles ? ? ?其它makefile文件
通过内核配置操作产生.config文件,顶层Makefile文件读取该文件的配置。顶层Makefile文件负责产生两个主要的程序:vmlinux (内核image)和模块。顶层Makefile文件根据内核配置,通过递归编译内核代码树子目录建立这两个文件。顶层Makefile文件文本一个名为arch/$(ARCH)/Makefile的机器体系makefile文件。机器体系Makefile文件为顶层makefile文件提供与机器相关的信息。每一个子目录有一个makefile文件,子目录makefile文件根据上级目录makefile文件命令启动编译。这些makefile使用.config文件配置数据构建各种文件列表,并使用这些文件列表编译内嵌或模块目标文件。scripts/Makefile.*包含了所有的定义和规则,与makefile文件一起编译出内核程序。

=== 2 角色分工
人们与内核makefile存在四种不同的关系:
*用户* 用户使用"make menuconfig"或"make"命令编译内核。他们通常不读或编辑内核makefile文件或其他源文件。
*普通开发者* 普通开发者维护设备驱动程序、文件系统和网络协议代码,他们维护相关子系统的makefile文件,因此他们需要内核makefile文件整体性的一般知识和关于kbuild公共接口的详细知识。
*体系开发者* 体系开发者关注一个整体的体系架构,比如sparc或者ia64。体系开发者既需要掌握关于体系的makefile文件,也要熟悉内核makefile文件。
*内核开发者* 内核开发者关注内核编译系统本身。他们需要清楚内核makefile文件的所有方面。
本文档的读者对象是普通开发者和系统开发者。

=== 3 内核编译文件
内核中大多数makefile文件是使用kbuild基础架构的makefile文件。本章介绍kbuild的makefile中的语法。
3.1节“目标定义”是一个快速导引,后面各章有详细介绍和实例。
--- 3.1 目标定义
? ? ?目标定义是makefile文件的主要部分(核心)。这些目标定义行定义了如何编译文件,特殊的兼容选项和递归子目录。
? ? ? 最简单的makefile文件只包含一行:
? ? ?Example: obj-y += foo.o
? ? 这行告诉kbuild在该目录下名为foo.o的目标文件(object),foo.o通过编译foo.c或者foo.S而得到。
? ? 如果foo.o编译成一个模块,则使用obj-m变量,因此常见写法如下:
? ? ?Example: obj-$(CONFIG_FOO) += foo.o
? ? ?$(CONFIG_FOO)可以代表y(built-in对象)或m(module对象)。
? ? ? 如果CONFIG_FOO不是y或m,那么这个文件不会被编译和链接。
--- 3.2 内嵌对象 - obj-y
? ? makefile文件将为编译vmlinux的目标文件放在$(obj-y)列表中,这些列表依赖于内核配置。
? ? ? Kbuild编译所有的$(obj-y)文件,然后调用"$(LD) -r"合并这些文件到一个built-in.o文件中。built-in.o经过父makefile文件链接到vmlinux。$(obj-y)中的文件顺序很重要。列表中文件允许重复,文件第一次出现将被链接到built-in.o,后续出现该文件将被忽略。
? ? ? 链接顺序之所以重要是因为一些函数在内核引导时将按照他们出现的顺序被调用,如函数(module_init() / __initcall)。所以要牢记改变链接顺序意味着也要改变SCSI控制器的检测顺序和重数磁盘。
? ? ? 例如: #drivers/isdn/i4l/Makefile
? ? ?# 内核ISDN子系统和设备驱动程序Makefile
? ? ?# 每个配置项是一个文件列表
? ? ?obj-$(CONFIG_ISDN) ? ? ? ? += isdn.o
? ? ?obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o
--- 3.3 可加载模块 - obj-m
? $(obj-m)表示对象文件(object files)编译成可加载的内核模块。
? 一个模块可以通过一个源文件或几个源文件编译而成。makefile只需简单地它们加到$(obj-m)。
? ? ? 例如:#drivers/isdn/i4l/Makefile
? ? ? ? obj-$(CONFIG_ISDN_PPP_BSDCOMP) += isdn_bsdcomp.o
? ? 注意:在这个例子中$(CONFIG_ISDN_PPP_BSDCOMP)含义是'm'。
? ? ? 如果内核模块通过几个源文件编译而成,使用以上同样的方法。
? ? ? Kbuild需要知道通过哪些文件编译模块,因此需要设置一个$(<module_name>-objs)变量。
? ? 例如:#drivers/isdn/i4l/Makefile
? ? ?obj-$(CONFIG_ISDN) += isdn.o
? ? ?isdn-objs := isdn_net_lib.o isdn_v110.o isdn_common.o
? ? ?在这个例子中,模块名isdn.o. Kbuild首先编译$(isdn-objs)中的object文件,然后运行"$(LD) -r"将列表中文件生成isdn.o.
? Kbuild使用后缀-objs、-y识别对象文件。这种方法允许makefile使用CONFIG_符号值确定一个object文件是否是另外一个object的组成部分。
? ? ?例如: #fs/ext2/Makefile
? ? ? ? obj-$(CONFIG_EXT2_FS) ? ? += ext2.o
? ? ? ? ext2-y := balloc.o bitmap.o
? ? ? ? ext2-$(CONFIG_EXT2_FS_XATTR) += xattr.o
? ? ?在这个例子中,如果$(CONFIG_EXT2_FS_XATTR)表示'y',则ext2.o只有xattr.o组成部分。
? ? ?注意: 当然,当你将对象文件编译到内核时,以上语法同样有效。因此,如果CONFIG_EXT2_FS=y,Kbuild将先编译ext2.o文件,然后链接到built-in.o。
--- 3.4 导出符号目标
? ? ? 在makefile文件中没有特别导出符号的标记。
--- 3.5 库文件 - lib-y
? ? ? obj-*中的object文件用于模块或built-in.o编译。object文件也可能编译到库文件中--lib.a。
? ? ? 所有罗列在lib-y中的object文件都将编译到该目录下的一个单一的库文件中。
? ? ? 包含在0bj-y中的object文件如果也列举在lib-y中将不会包含到库文件中,因为他们不能被访问。但lib-m中的object文件将被编译进lib.a库文件。
? ? ? 注意在相同的makefile中可以列举文件到buit-in内核中也可以作为库文件的一个组成部分。因此在同一个目录下既可以有built-in.o也可以有lib.a文件。
? ? ? 例如:#arch/i386/lib/Makefile
? ? ? ? lib-y ? := checksum.o delay.o
? ? ?这样将基于checksum.o、delay.o创建一个lib.a文件。
? ? ? 对于内核编译来说,lib.a文件被包含在libs-y中。将“6.3 目录表”。
? ? ? lib-y通常被限制使用在lib/和arch/*/lib目录中。
--- 3.6 目录递归
? ? ?makefile文件负责编译当前目录下的目标文件,子目录中的文件由子目录中的makefile文件负责编译。编译系统将使用obj-y和obj-m自动递归编译各个子目录中文件。
? ? ?如果ext2是一个子目录,fs目录下的makefile将使用以下赋值语句是编译系统编译ext2子目录。
? ? ?例如: #fs/Makefile
? ? ? ? obj-$(CONFIG_EXT2_FS) += ext2/
? ? ?如果CONFIG_EXT2_FS设置成'y(built-in)或'm'(modular),则对应的obj-变量也要设置,内核编译系统将进入ext2目录编译文件。
? ? ? 内核编译系统只使用这些信息来决定是否需要编译这个目录,子目录中makefile文件规定那些文件编译为模块那些是内核内嵌对象。
? ? ? 当指定目录名时使用CONFIG_变量是一种良好的做法。如果CONFIG_选项不为'y'或'm',内核编译系统就会跳过这个目录。
--- 3.7 编译标记
? EXTRA_CFLAGS, EXTRA_AFLAGS, EXTRA_LDFLAGS, EXTRA_ARFLAGS
? 所有的EXTRA_变量只能使用在定义该变量后的makefile文件中。EXTRA_变量被makefile文件所有的执行命令语句所使用。
? ? ?$(EXTRA_CFLAGS)是使用$(CC)编译C文件的选项。
? ? ?例如: # drivers/sound/emu10k1/Makefile
? ? ? ? ? ?EXTRA_CFLAGS += -I$(obj)
? ? ? ? ? ?ifdef
? ? ? ? ? ? DEBUG EXTRA_CFLAGS += -DEMU10K1_DEBUG
? ? ? ? ? ? endif
? ? ?定义这个变量是必须的,因为顶层makefile定义了$(CFLAGS)变量并使用该变量编译整个代码树。
? ? ?$(EXTRA_AFLAGS)是每个目录编译汇编语言源文件的选项。
? ? ?例如: #arch/x86_64/kernel/Makefile
? ? ? ? ? ?EXTRA_AFLAGS := -traditional
? ? ?$(EXTRA_LDFLAGS)和$(EXTRA_ARFLAGS)用于每个目录的$(LD)和$(AR)选项。
? ? ?例如: #arch/m68k/fpsp040/Makefile
? ? ? ? ? ?EXTRA_LDFLAGS := -x
? CFLAGS_$@, AFLAGS_$@
? ? ?CFLAGS_$@和AFLAGS_$@只使用到当前makefile文件的命令中。
? ? ?$(CFLAGS_$@)定义了使用$(CC)的每个文件的选项。$@部分代表该文件。
? ? ?例如: # drivers/scsi/Makefile
? ? ? ? ? ?CFLAGS_aha152x.o = ? -DAHA152X_STAT -DAUTOCONF
? ? ? ? ? ?CFLAGS_gdth.o ? = # -DDEBUG_GDTH=2 -D__SERIAL__ -D__COM2__ /
? -DGDTH_STATISTICS CFLAGS_seagate.o = ? -DARBITRATE -DPARITY -DSEAGATE_USE_ASM
? ? ?这三行定义了aha152x.o、gdth.o和seagate.o文件的编译选项。
? ? ?$(AFLAGS_$@)使用在汇编语言代码文件中,具有同上相同的含义。
? ? ?例如: # arch/arm/kernel/Makefile
? ? ? ? ? ?AFLAGS_head-armv.o := -DTEXTADDR=$(TEXTADDR) -traditional
? ? ? ? ? ?AFLAGS_head-armo.o := -DTEXTADDR=$(TEXTADDR) -traditional
--- 3.9 依赖关系
? ? ?内核编译记录如下依赖关系:
? ? ? 1) 所有的前提文件(both *.c and *.h)
? ? ? 2) CONFIG_ 选项影响到的所有文件
? ? ? 3) 编译目标文件使用的命令行
? ? ?因此,假如改变$(CC)的一个选项,所有相关的文件都要重新编译。
--- 3.10 特殊规则
? ? ? 特殊规则使用在内核编译需要规则定义而没有相应定义的时候。典型的例子如编译时头文件的产生规则。其他例子有体系makefile编译引导映像的特殊规则。特殊规则写法同普通的Make规则。
? ? ?Kbuild(应该是编译程序)在makefile所在的目录不能被执行,因此所有的特殊规则需要提供前提文件和目标文件的相对路径。
? ? ?定义特殊规则时将使用到两个变量:
? $(src): $(src)是对于makefile文件目录的相对路径,当使用代码树中的文件时使用该变量$(src)。
? $(obj): $(obj)是目标文件目录的相对路径。生成文件使用$(obj)变量。
? ? ?例如: #drivers/scsi/Makefile
? ? ?$(obj)/53c8xx_d.h: $(src)/53c7,8xx.scr $(src)/script_asm.pl
? ? ? ? $(CPP) -DCHIP=810 - < $< | ... $(src)/script_asm.pl
? ? ?这就是使用普通语法的特殊编译规则。
? ? ?目标文件依赖于两个前提文件。目标文件的前缀是$(obj), 前提文件的前缀是$(src)(因为它们不是生成文件)。

=== 4 辅助程序
? ? 内核编译系统支持在编译(compliation)阶段编译主机可执行程序。为了使用主机程序需要两个步骤:第一个步骤使用hostprogs-y变量告诉内核编译系统有主机程序可用。第二步给主机程序添加潜在的依赖关系。有两种方法,在规则中增加依赖关系或使用$(always)变量。具体描述如下。
--- 4.1 简单辅助程序
? ? ?在一些情况下需要在主机上编译和运行主机程序。下面这行告诉kbuild在主机上建立bin2hex程序。
? ? ?例如: hostprogs-y := bin2hex
? ? ?Kbuild假定使用makefile相同目录下的单一C代码文件bin2hex.c编译bin2hex。
--- 4.2 组合辅助程序
? ? ?主机程序也可以由多个object文件组成。定义组合辅助程序的语法同内核对象的定义方法。
? ? ?$(<executeable>-objs)包含了所有的用于链接最终可执行程序的对象。
? ? ?例如: #scripts/lxdialog/Makefile
? ? ? ? hostprogs-y ? := lxdialog
? ? ? ? lxdialog-objs := checklist.o lxdialog.o
? ? ?扩展名.o文件都编译自对应的.c文件。在上面的例子中checklist.c编译成checklist.o,lxdialog.c编译为lxdialog.o。最后两个.o文件链接成可执行文件lxdialog。
? ? ?注意:语法<executable>-y不能用于定义主机程序。
--- 4.3 定义共享库
? ? ?扩展名为.so的对象是共享库文件,并且是位置无关的object文件。内核编译系统提供共享库使用支持,但使用方法有限制。在下面例子中libkconfig.so库文件被链接到可执行文件conf中。
? ? ?例如: #scripts/kconfig/Makefile
? ? ? ? ? ?hostprogs-y ? := conf
? ? ? ? ? ?conf-objs ? ? := conf.o libkconfig.so
? ? ? ? ? ?libkconfig-objs := expr.o type.o
? ? ?共享库文件需要对应的-objs定义, 在上面例子中库libkconfig由两个对象组成:expr.o和type.o。expr.o和type.o将被编译为位置无关代码并被链接如libkconfig.so。共享库不支持C++语言。
--- 4.4 C++语言使用方法
? ? ?内核编译系统提供了对C++主机程序的支持以用于内核配置,但不主张其它方面使用这种方法。
? ? ?例如: #scripts/kconfig/Makefile
? ? ? ? ? ?hostprogs-y ? := qconf
? ? ? ? ? ?qconf-cxxobjs := qconf.o
? ? ?在上面例子中可执行文件由C++文件qconf.cc组成 - 通过$(qconf-cxxobjs)标识。
? ? ?如果qconf由.c和.cc文件混合组成,附加行表示这种情况。
? ? ?例如: #scripts/kconfig/Makefile
? ? ? ? ? ?hostprogs-y ? := qconf
? ? ? ? ? ?qconf-cxxobjs := qconf.o
? ? ? ? ? ?qconf-objs ? := check.o
--- 4.5 辅助程序编译控制选项
? ? ?当编译主机程序时仍然可以使用$(HOSTCFLAGS)设置编译选项传递给$(HOSTCC)。这些选项将影响所有使用变量HOST_EXTRACFLAG的makefile创建的主机程序。
? ? ?例如: #scripts/lxdialog/Makefile
? ? ? ? ? ?HOST_EXTRACFLAGS += -I/usr/include/ncurses
? ? ?为单个文件设置选项使用下面方式:
? ? ?例如: #arch/ppc64/boot/Makefile
? ? ?HOSTCFLAGS_piggyback.o := -DKERNELBASE=$(KERNELBASE)
? ? ?也可以使用附加链接选项:
? ? ?例如: #scripts/kconfig/Makefile
? ? ? ? ? ?HOSTLOADLIBES_qconf := -L$(QTDIR)/lib
? ? ?当链接qconf时将使用外部选项"-L$(QTDIR)/lib"。
--- 4.6 何时建立辅助程序
? ? ?只有当需要时内核编译系统才会编译主机程序。有两种方式:
? ? ?(1) 在特殊规则中作为隐式的前提需求
? ? ?例如: #drivers/pci/Makefile
? ? ?hostprogs-y := gen-devlist
? ? ?$(obj)/devlist.h: $(src)/pci.ids $(obj)/gen-devlist
? ? ? ? ? ? ? ( cd $(obj); ./gen-devlist ) < $<
? ? ?编译目标文件$(obj)/devlist.h需要先建立$(obj)/gen-devlist。注意在特殊规则中使用主机程序必须加前缀$(obj)。
? ? ?(2) 使用$(always)
? ? ?当没有合适的特殊规则可以使用,并且在进入makefile文件时就要建立主机程序,可以使用变量$(always)。
? ? ?例如: #scripts/lxdialog/Makefile
? ? ? ? ? ?hostprogs-y ? := lxdialog
? ? ? ? ? ?always ? ? := $(hostprogs-y)
? ? ?这样就告诉内核编译系统即使没有任何规则使用lxdialog也要编译它。
--- 4.7 使用hostprogs-$(CONFIG_FOO)
? ? ?在Kbuild文件中典型模式如下:
? ? ?例如: #scripts/Makefile
? ? ? ? ? ?hostprogs-$(CONFIG_KALLSYMS) += kallsyms
? ? ?对Kbuild来说'y'用于内嵌对象'm'用于模块。
? ? ?因此如果config符号是'm',编译系统也将创建该程序。换句话说内核编译系统等同看待hostprogs-m和hostprogs-y。但如果不涉及到CONFIG符号仅建议使用hostprogs-y。

=== 5 编译清除机制
? "make clean"命令删除在编译内核生成的大部分文件,例如主机程序,列举在 $(hostprogs-y)、$(hostprogs-m)、$(always)、$(extra-y)和$(targets)中目标文件都将被删除。代码目录数中的"*.[oas]"、"*.ko"文件和一些由编译系统产生的附加文件也将被删除。
? 附加文件可以使用$(clean-files)进行定义。
? ? ?例如: #drivers/pci/Makefile
? ? ? ? ? ?clean-files := devlist.h classlist.h
? 当执行"make clean"命令时, "devlist.h classlist.h"两个文件将被删除。内核编译系统默认这些文件与makefile具有相同的相对路径,否则需要设置以'/'开头的绝对路径。
? 删除整个目录使用以下方式:
? 例如: #scripts/package/Makefile
? ? ?clean-dirs := $(objtree)/debian/
? 这样就将删除包括子目录在内的整个debian目录。如果不使用以'/'开头的绝对路径内核编译系统见默认使用相对路径。
? 通常内核编译系统根据"obj-* := dir/"进入子目录,但是在体系makefile中需要显式使用如下方式:
? ? ?例如: #arch/i386/boot/Makefile
? ? ? ? ? ?subdir- := compressed/
? 上面赋值语句指示编译系统执行"make clean"命令时进入compressed/目录。
? 在编译最终的引导映像文件的makefile中有一个可选的目标对象名称是archclean。
? ? ?例如: #arch/i386/Makefile
? ? ? ? ? ?archclean:
? ? ? ? ? ? ? $(Q)$(MAKE) $(clean)=arch/i386/boot
? 当执行"make clean"时编译器进入arch/i386/boot并象通常一样工作。arch/i386/boot中的makefile文件可以使用subdir-标识进入更下层的目录。
? 注意1: arch/$(ARCH)/Makefile不能使用"subdir-",因为它被包含在顶层makefile文件中,在这个位置编译机制是不起作用的。
? 注意2: 所有列举在core-y、libs-y、drivers-y和net-y中的目录将被"make clean"命令清除。

=== 6 体系Makefile文件
? 在开始进入各个目录编译之前,顶层makefile文件设置编译环境和做些准备工作。顶层makefile文件包含通用部分,arch/$(ARCH)/Makefile包含该体系架构所需的设置。因此arch/$(ARCH)/Makefile会设置一些变量和少量的目标。
? 当编译时将按照以下大概步骤执行:
1) 配置内核 => 产生 .config文件
2) 保存内核版本到include/linux/version.h文件中
3) 符号链接include/asm to include/asm-$(ARCH)
4) 更新所有目标对象的其它前提文件
? - 附加前提文件定义在arch/$(ARCH)/Makefile文件中
5) 递归进入init-* core* drivers-* net-* libs-*中的所有子目录和编译所有的目标对象
? - 上面变量值都引用到arch/$(ARCH)/Makefile文件。
6) 链接所有的object文件生成vmlinux文件,vmlinux文件放在代码树根目录下。
? 最开始链接的几个object文件列举在arch/$(ARCH)/Makefile文件的head-y变量中。
7) 最后体系makefile文件定义编译后期处理规则和建立最终的引导映像bootimage。
? - 包括创建引导记录
? - 准备initrd映像和相关处理
--- 6.1 变量设置
? LDFLAGS ? ? ?$(LD)一般选项
? ? ?选项使用于链接器的所有调用中。通常定义emulation就可以了。
? ? ?例如: #arch/s390/Makefile
? ? ? ? ? ?LDFLAGS ? := -m elf_s390
? ? ? 注意: EXTRA_LDFLAGS和LDFLAGS_$@可以进一步订制使用选项,将第7章。
? LDFLAGS_MODULE ? ? ? $(LD)链接模块的选项
? ? ?LDFLAGS_MODULE通常设置$(LD)链接模块的.ko选项。
? ? ?默认为"-r"即可重定位输出文件。
? LDFLAGS_vmlinux ? $(LD)链接vmlinux选项
? ? ?LDFLAGS_vmlinux定义链接最终vmlinux时链接器的选项。
? ? ?LDFLAGS_vmlinux支持使用LDFLAGS_$@。
? ? ?例如: #arch/i386/Makefile
? ? ? ? ? ?LDFLAGS_vmlinux := -e stext
? OBJCOPYFLAGS ? ? ?objcopy选项
? ? ?当使用$(call if_changed,objcopy)转化a .o文件时,OBJCOPYFLAGS中的选项将被使用。
? ? ?$(call if_changed,objcopy)经常被用作为vmlinux产生原始的二进制文件。
? ? ?例如: #arch/s390/Makefile
? ? ? ? ? ?OBJCOPYFLAGS := -O binary
? ? ? ? ? #arch/s390/boot/Makefile
? ? ? ? ? ?$(obj)/image: vmlinux FORCE $(call if_changed,objcopy)
? ? ?在上面例子中$(obj)/image是vmlinux的二进制版本文件。$(call if_changed,xxx)
的使用方法见后。
? AFLAGS ? $(AS)汇编选项
? ? ?默认值见顶层Makefile文件
? ? ?针对每个体系需要另外添加和修改它。
? ? ?例如: #arch/sparc64/Makefile
? ? ? ? ? ?AFLAGS += -m64 -mcpu=ultrasparc
? CFLAGS ? ? ?$(CC)编译器选项
? ? ?默认值见顶层Makefile文件
? ? ?针对每个体系需要另外添加和修改它。
? ? ?通常CFLAGS变量值取决于内核配置。
? ? ?例如: #arch/i386/Makefile
? ? ? ? ? ?cflags-$(CONFIG_M386) += -march=i386
? ? ? ? ? ?CFLAGS += $(cflags-y)
? ? ?许多体系Makefiles文件动态启动市场目标机器上的C编译器检测支持的选项:
? ? ? ? ? ?#arch/i386/Makefile
? ? ? ? ? ?...
? ? ? ? ? ?cflags-$(CONFIG_MPENTIUMII) ? += $(call cc-option,/
? ? ? ? ? ? ? ? ?-march=pentium2,-march=i686) ...
? ? ? ? ? ?# Disable unit-at-a-time mode ...
? ? ? ? ? ?CFLAGS += $(call cc-option,-fno-unit-at-a-time)
? ? ? ? ? ?...
? ? ?第一个例子当config选项是'y'时将被选中。
? CFLAGS_KERNEL ? ? ?$(CC)编译built-in对象的选项
? ? ?$(CFLAGS_KERNEL)包含外部C编译器选项编译本地内核代码。
? CFLAGS_MODULE ? ? ?$(CC)编译模块选项
? ? ?$(CFLAGS_MODULE)包含外部C编译器选项编译可加载内核代码。
--- 6.2 增加预设置项
? ? ?prepare: 这个规则用于列举开始进入子目录编译前需要的前提文件。通常是些包含汇编常量的头文件。
? ? ?例如:
? ? ?#arch/s390/Makefile
? ? ?prepare: include/asm-$(ARCH)/offsets.h
? ? ?在这个例子中include/asm-$(ARCH)/offsets.h将在进入子目录前编译。
? ? ? 详见XXX-TODO文件描述了kbuild如何产生offset头文件。
--- 6.3 目录表
? ? ?体系makefile文件和顶层makefile文件共同定义了如何建立vmlinux文件的变量。注意没有体系相关的模块对象定义部分:所有的模块对象都是体系无关的。
? head-y, init-y, core-y, libs-y, drivers-y, net-y
? ? ?$(head-y) 列举首先链接到vmlinux的对象文件。
? ? ?$(libs-y) 列举了能够找到lib.a文件的目录。
? ? ?其余的变量列举了能够找到内嵌对象文件的目录。
? ? ?$(init-y) 列举的对象位于$(head-y)对象之后。
? ? ?然后是如下位置秩序:
? ? ?$(core-y), $(libs-y), $(drivers-y) 和 $(net-y)。
? ? ?顶层makefile定义了所有同用目录,arch/$(ARCH)/Makefile文件只需增加体系相关的目录。
? ? ?例如: #arch/sparc64/Makefile
? ? ? ? ? ?core-y += arch/sparc64/kernel/
? ? ? ? ? ?libs-y += arch/sparc64/prom/ arch/sparc64/lib/
? ? ? ? ? ?drivers-$(CONFIG_OPROFILE) += arch/sparc64/oprofile/
--- 6.4 引导映像
? ? ?体系makefile文件定义了编译vmlinux文件的目标对象,将它们压缩和封装成引导代码,并复制到合适的位置。这包括各种安装命令。如何定义实际的目标对象无法为所有的体系结构提供标准化的方法。
? ? ?附加处理过程常位于arch/$(ARCH)/下的boot/目录。
? ? ?内核编译系统无法在boot/目录下提供一种便捷的方法创建目标系统文件。因此arch/$(ARCH)/Makefile要调用make命令在boot/目录下建立目标系统文件。建议使用的方法是在arch/$(ARCH)/Makefile中设置调用,并且使用完整路径引用arch/$(ARCH)/boot/Makefile。
? ? ?例如: #arch/i386/Makefile
? ? ? ? ? ?boot := arch/i386/boot
? ? ? ? ? ?bzImage: vmlinux
? ? ? ? ? ? ? $(Q)$(MAKE) $(build)=$(boot) $(boot)/$@
? ? ?建议使用"$(Q)$(MAKE) $(build)=<dir>"方式在子目录中调用make命令。
? ? ?没有定义体系目标系统文件的规则,但执行"make help"命令要列出所有目标系统文件,因此必须定义$(archhelp)变量。
? ? ?例如: #arch/i386/Makefile
? ? ?define
? ? ? ? archhelp echo '* bzImage ? ? - Image (arch/$(ARCH)/boot/bzImage)'
? ? ? endef
? ? ?当执行不带参数的make命令时,将首先编译第一个目标对象。在顶层makefile中第一个目标对象是all:。
? ? ?一个体系结构需要定义一个默认的可引导映像。
? ? ?"make help"命令的默认目标是以*开头的对象。
? ? ?增加新的前提文件给all目标可以设置不同于vmlinux的默认目标对象。
? ? ?例如: #arch/i386/Makefile
? ? ? ? ? ?all: bzImage
? ? ?当执行不带参数的"make"命令时,bzImage文件将被编译。
--- 6.5 编译非内核目标
? extra-y
? ? ?extra-y定义了在当前目录下创建没有在obj-*定义的附加的目标文件。
? ? ?在extra-y中列举目标是处于两个目的:
? ? ? 1) 是内核编译系统在命令行中检查变动情况
? ? ? ? - 当使用$(call if_changed,xxx)时
? ? ? 2) 内核编译系统知道执行"make clean"命令时删除哪些文件
? ? ?例如: #arch/i386/kernel/Makefile
? ? ? ? ? ?extra-y := head.o init_task.o
? ? ?上面例子extra-y中的对象文件将被编译但不会练接到built-in.o中。
--- 6.6 编译引导映像命令
? ? ?Kbuild提供了一些编译引导映像有用的宏。
? if_changed
? ? ?if_changed是后面命令使用的基础。
? ? ?用法:
? ? ? ? target: source(s)
? ? ? ? ? ? FORCE $(call if_changed,ld/objcopy/gzip)
? ? ?当这条规则被使用时它将检查哪些文件需要更新,或命令行被改变。后面这种情况将迫使重新编译编译选项被改变的执行文件。使用if_changed的目标对象必须列举在$(targets)中,否则命令行检查将失败,目标一直会编译。
? ? ?赋值给$(targets)的对象没有$(obj)/前缀。
? ? ?if_changed也可以和定制命令配合使用,见6.7"kbuild定制命令"。
? ? ?注意: 一个常见错误是忘记了FORCE前导词。
? ld
? ? ? 链接目标。常使用LDFLAGS_$@作为ld的选项。
? objcopy
? ? ? 复制二进制文件。常用于arch/$(ARCH)/Makefile中和使用OBJCOPYFLAGS作为选项。
? ? ?也可以用OBJCOPYFLAGS_$@设置附加选项。
? gzip
? ? ? 压缩目标文件。使用最大压缩算法压缩目标文件。
? ? ?例如: #arch/i386/boot/Makefile
? ? ?LDFLAGS_bootsect := -Ttext 0x0 -s --oformat binary
? ? ?LDFLAGS_setup ? := -Ttext 0x0 -s --oformat binary -e begtext

? ? ?targets += setup setup.o bootsect bootsect.o
? ? ?$(obj)/setup $(obj)/bootsect: %: %.o FORCE
? ? ? ? ? ? $(call if_changed,ld)
? ? ? 在上面例子中有两个可能的目标对象,分别需要不同的链接选项。使用LDFLAGS_$@语法为每个目标对象设置不同的链接选项。
? ? ?$(targets)包含所有的目标对象,因此内核编译系统知道所有的目标对象并且将:
? ? ? 1) 检查命令行的改变情况
? ? ? 2) 执行make clean命令时删除目标对象
? ? ?": %: %.o"是简写方法,减写setup.o和bootsect.o文件。
? ? ?注意: 常犯错误是忘记"target :="语句,导致没有明显的原因目标文件被重新编译。
--- 6.7 定制编译命令
? ? ?当执行带KBUILD_VERBOSE=0参数的编译命令时命令的简短信息会被显示。要让定制命令具有这种功能需要设置两个变量:
? ? ?quiet_cmd_<command> - 将被显示的内容
? ? ? cmd_<command> ? ? ?- 被执行的命令
? ? ?例如: #
? ? ? ? ? ?quiet_cmd_image = BUILD ? $@
? ? ? ? ? ? cmd_image = $(obj)/tools/build $(BUILDFLAGS) /
? ? ? ? ? ? ? ? ? ? $(obj)/vmlinux.bin > $@
? ? ? ? ? ?targets += bzImage
? ? ? ? ? ?$(obj)/bzImage: $(obj)/vmlinux.bin $(obj)/tools/build FORCE
? ? ? ? ? ? ? ? $(call if_changed,image)
? ? ? ? ? ? ? ? @echo 'Kernel: $@ is ready'
? ? ?执行"make KBUILD_VERBOSE=0"命令编译$(obj)/bzImage目标时将显示:
? ? ?BUILD ? arch/i386/boot/bzImage
--- 6.8 预处理连接脚本
? ? ?当编译vmlinux映像时将使用arch/$(ARCH)/kernel/vmlinux.lds链接脚本。
? ? ?相同目录下的vmlinux.lds.S文件是这个脚本的预处理的变体。内核编译系统知晓.lds文件并使用规则*lds.S -> *lds。
? ? ?例如: #arch/i386/kernel/Makefile
? ? ? ? ? ?always := vmlinux.lds
? ? ? ? ? ?#Makefile
? ? ? ? ? ?export CPPFLAGS_vmlinux.lds += -P -C -U$(ARCH)
? ? ?$(always)赋值语句告诉编译系统编译目标是vmlinux.lds。$(CPPFLAGS_vmlinux.lds)赋值语句告诉编译系统编译vmlinux.lds目标的编译选项。
? ? ?编译*.lds时将使用到下面这些变量:
? ? ?CPPFLAGS ? ? ?: 定义在顶层Makefile
? ? ?EXTRA_CPPFLAGS ? ? ?: 可以设置在编译的makefile文件中
? ? ?CPPFLAGS_$(@F) : 目标编译选项。注意要使用文件全名。
--- 6.9 $(CC)支持功能
? ? ?内核可能会用不同版本的$(CC)进行编译,每个版本有不同的性能和选项,内核编译系统提供基本的支持用于验证$(CC)选项。$(CC)通常是gcc编译器,但其它编译器也是可以。
? cc-option cc-option 用于检测$(CC)是否支持给定的选项,如果不支持就使用第二个可选项。
? ? ?例如: #arch/i386/Makefile
? ? ? ? ? ?cflags-y += $(call cc-option,-march=pentium-mmx,-march=i586)
? ? ?在上面例子中如果$(CC)支持-march=pentium-mmx则cflags-y等于该值,否则等于-march-i586。如果没有第二个可选项且第一项不支持则cflags-y没有被赋值。
? cc-option-yn cc-option-yn用于检测gcc是否支持给定的选项,支持返回'y'否则'n'。
? ? ?例如: #arch/ppc/Makefile
? ? ? ? ? ?biarch := $(call cc-option-yn, -m32)
? ? ? ? ? ?aflags-$(biarch) += -a32
? ? ? ? ? ?cflags-$(biarch) += -m32
? ? ?在上面例子中如果$(CC)支持-m32选项则$(biarch)设置为y。当$(biarch)等于y时,变量$(aflags-y)和$(cflags-y)将分别等于-a32和-m32。
? cc-option-align gcc版本>= 3.0用于定义functions、loops等边界对齐选项。
? ? ?gcc < 3.00
? ? ? ? cc-option-align = -malign
? ? ? gcc >= 3.00
? ? ? ? cc-option-align = -falign
? ? ?例如:
? ? ? ? CFLAGS += $(cc-option-align)-functions=4
? ? ?在上面例子中对于gcc >= 3.00来说-falign-functions=4,gcc < 3.00版本使用-malign-functions=4。
? cc-version cc-version返回$(CC)编译器数字版本号。
? ? ?版本格式是<major><minor>,均为两位数字。例如gcc 3.41将返回0341。
? ? ?当一个特定$(CC)版本在某个方面有缺陷时cc-version是很有用的。例如-mregparm=3在一些gcc版本会失败尽管gcc接受这个选项。
? ? ?例如: #arch/i386/Makefile
? ? ? ? ? ?GCC_VERSION := $(call cc-version)
? ? ? ? ? ?cflags-y += $(shell /
? ? ? ? ? ?if [ $(GCC_VERSION) -ge 0300 ] ; then echo "-mregparm=3"; fi ;)
? ? ?在上面例子中-mregparm=3只使用在版本大于等于3.0的gcc中。
=== 7 Kbuild变量
? 顶层Makefile文件导出下面这些变量:
? VERSION, PATCHLEVEL, SUBLEVEL, EXTRAVERSION
? ? ?这几个变量定义了当前内核版本号。很少体系体系Makefiles文件直接使用他们,常用$(KERNELRELEASE)代替。
? ? ?$(VERSION)、$(PATCHLEVEL)和$(SUBLEVEL)定义了三个基本部分版本号,例如"2", "4",和"0"。这三个变量一直使用数值表示。
? ? ?$(EXTRAVERSION)定义了更细的补钉号,通常是短横跟一些非数值字符串,例如"-pre4"。
? KERNELRELEASE
? ? ?$(KERNELRELEASE)是一个单一字符如"2.4.0-pre4",适合用于构造安装目录和显示版本字符串。一些体系文件使用它用于以上目的。
? ARCH
? ? ?这个变量定义了目标系统体系结构,例如"i386"、“arm"、"sparc". 一些内核编译文件测试$(ARCH)用于确定编译哪个文件。默认情况下顶层Makefile文件设置$(ARCH)为主机相同的系统体系。当交叉编译编译时,用户可以使用命令行改变$(ARCH)值:
? ? ? ? make ARCH=m68k ...
? INSTALL_PATH
? ? ?这个变量定义了体系Makefiles文件安装内核映项和System.map文件的路径。
? INSTALL_MOD_PATH, MODLIB
? ? ?$(INSTALL_MOD_PATH)定义了模块安装变量$(MODLIB)的前缀。这个变量通常不在Makefile文件中定义,如果需要可以由用户添加。
? ? ?$(MODLIB)定义了模块安装目录。
? ? ?顶层Makefile定义$(MODLIB)为$(INSTALL_MOD_PATH)/lib/modules/$(KERNELRELEASE)。用户可以使用命令行修改这个值。
=== 8 Makefile语言
? 内核Makefiles设计目标用于运行GNU Make程序。Makefiles仅使用GNU Make提到的特性,但使用了较多的GNU扩展部分。
? GNU Make程序支持基本的列表处理功能。内核Makefiles文件结合"if"语句使用了简单的列表建立和维护功能。
? GNU Make程序有两种赋值操作符:":="和"="。 ":="执行时立即计算右值并赋值给左值。"="类似公式定义,当每次使用左值要被使用时计算右值并赋给它。
? 一些情况中使用"="合适,而一些情况中使用":="才是正确选择。
=== 9 Credits
? Original version made by Michael Elizabeth Chastain, <mailto:mec@shout.net> Updates
by Kai Germaschewski <kai@tp1.ruhr-uni-bochum.de> Updates by Sam Ravnborg <sam@ravnborg.org>
=== 10 TODO
- Describe how kbuild support shipped files with _shipped.
- Generating offset header files.
- Add more variables to section 7?
 
 
 
添加评论 | 阅读评论 (1)
16:05  |  固定链接 | 引用通告 (0) | 记录它
 
 
固定链接  关闭
 
 
 

 
 
 
 
 

 
Linux下Makefile的automake生成全攻略
 
Linux下Makefile的automake生成全攻略
 
http://fanqiang.chinaunix.net/system/linux/2005-12-14/3926.shtml
 
 
作为Linux下的程序开发人员,大家一定都遇到过Makefile,用make命令来编译自己写的程序确实是很方便。一般情况下,大家都是手工写一个简单Makefile,如果要想写出一个符合自由软件惯例的Makefile就不那么容易了。
  在本文中,将给大家介绍如何使用autoconf和automake两个工具来帮助我们自动地生成符合自由软件惯例的Makefile,这样就可以象常见的GNU程序一样,只要使用“./configure”,“make”,“make instal”就可以把程序安装到Linux系统中去了。这将特别适合想做开放源代码软件的程序开发人员,又或如果你只是自己写些小的Toy程序,那么这个文章对你也会有很大的帮助。

  一、Makefile介绍

  Makefile是用于自动编译和链接的,一个工程有很多文件组成,每一个文件的改变都会导致工程的重新链接,但是不是所有的文件都需要重新编译,Makefile中纪录有文件的信息,在make时会决定在链接的时候需要重新编译哪些文件。

  Makefile的宗旨就是:让编译器知道要编译一个文件需要依赖其他的哪些文件。当那些依赖文件有了改变,编译器会自动的发现最终的生成文件已经过时,而重新编译相应的模块。

  Makefile的基本结构不是很复杂,但当一个程序开发人员开始写Makefile时,经常会怀疑自己写的是否符合惯例,而且自己写的Makefile经常和自己的开发环境相关联,当系统环境变量或路径发生了变化后,Makefile可能还要跟着修改。这样就造成了手工书写Makefile的诸多问题,automake恰好能很好地帮助我们解决这些问题。

  使用automake,程序开发人员只需要写一些简单的含有预定义宏的文件,由autoconf根据一个宏文件生成configure,由automake根据另一个宏文件生成Makefile.in,再使用configure依据Makefile.in来生成一个符合惯例的Makefile。下面我们将详细介绍Makefile的automake生成方法。

  二、使用的环境

  本文所提到的程序是基于Linux发行版本:Fedora Core release 1,它包含了我们要用到的autoconf,automake。

  三、从helloworld入手

  我们从大家最常使用的例子程序helloworld开始。

  下面的过程如果简单地说来就是:

  新建三个文件:

   helloworld.c
   configure.in
   Makefile.am

  然后执行:

aclocal; autoconf; automake --add-missing; ./configure; make; ./helloworld

  就可以看到Makefile被产生出来,而且可以将helloworld.c编译通过。

  很简单吧,几条命令就可以做出一个符合惯例的Makefile,感觉如何呀。

  现在开始介绍详细的过程:

  1、建目录

  在你的工作目录下建一个helloworld目录,我们用它来存放helloworld程序及相关文件,如在/home/my/build下:

$ mkdir helloword
$ cd helloworld

  2、 helloworld.c

  然后用你自己最喜欢的编辑器写一个hellowrold.c文件,如命令:vi helloworld.c。使用下面的代码作为helloworld.c的内容。

int main(int argc, char** argv)
{
printf("Hello, Linux World!/n");
return 0;
}

  完成后保存退出。

  现在在helloworld目录下就应该有一个你自己写的helloworld.c了。

  3、生成configure

  我们使用autoscan命令来帮助我们根据目录下的源代码生成一个configure.in的模板文件。

  命令:

$ autoscan
$ ls
configure.scan helloworld.c

  执行后在hellowrold目录下会生成一个文件:configure.scan,我们可以拿它作为configure.in的蓝本。

  现在将configure.scan改名为configure.in,并且编辑它,按下面的内容修改,去掉无关的语句:

============================configure.in内容开始=========================================
# -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

AC_INIT(helloworld.c)
AM_INIT_AUTOMAKE(helloworld, 1.0)

# Checks for programs.
AC_PROG_CC

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.
AC_OUTPUT(Makefile)
============================configure.in内容结束=========================================

  然后执行命令aclocal和autoconf,分别会产生aclocal.m4及configure两个文件:

$ aclocal
$ls
aclocal.m4 configure.in helloworld.c
$ autoconf
$ ls
aclocal.m4 autom4te.cache configure configure.in helloworld.c


  大家可以看到configure.in内容是一些宏定义,这些宏经autoconf处理后会变成检查系统特性、环境变量、软件必须的参数的shell脚本。

  autoconf 是用来生成自动配置软件源代码脚本(configure)的工具。configure脚本能独立于autoconf运行,且在运行的过程中,不需要用户的干预。

  要生成configure文件,你必须告诉autoconf如何找到你所用的宏。方式是使用aclocal程序来生成你的aclocal.m4。

  aclocal根据configure.in文件的内容,自动生成aclocal.m4文件。aclocal是一个perl 脚本程序,它的定义是:“aclocal - create aclocal.m4 by scanning configure.ac”。

  autoconf从configure.in这个列举编译软件时所需要各种参数的模板文件中创建configure。

  autoconf需要GNU m4宏处理器来处理aclocal.m4,生成configure脚本。

  m4是一个宏处理器。将输入拷贝到输出,同时将宏展开。宏可以是内嵌的,也可以是用户定义的。除了可以展开宏,m4还有一些内建的函数,用来引用文件,执行命令,整数运算,文本操作,循环等。m4既可以作为编译器的前端,也可以单独作为一个宏处理器。

4、新建Makefile.am

  新建Makefile.am文件,命令:


$ vi Makefile.am


  内容如下:


AUTOMAKE_OPTIONS=foreign
bin_PROGRAMS=helloworld
helloworld_SOURCES=helloworld.c


  automake会根据你写的Makefile.am来自动生成Makefile.in。

  Makefile.am中定义的宏和目标,会指导automake生成指定的代码。例如,宏bin_PROGRAMS将导致编译和连接的目标被生成。

  5、运行automake

  命令:


$ automake --add-missing
configure.in: installing `./install-sh'
configure.in: installing `./mkinstalldirs'
configure.in: installing `./missing'
Makefile.am: installing `./depcomp'


  automake会根据Makefile.am文件产生一些文件,包含最重要的Makefile.in。

  6、执行configure生成Makefile


$ ./configure
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for gcc... gcc
checking for C compiler default output... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables...
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ANSI C... none needed
checking for style of include used by make... GNU
checking dependency style of gcc... gcc3
configure: creating ./config.status
config.status: creating Makefile
config.status: executing depfiles commands
$ ls -l Makefile
-rw-rw-r-- 1 yutao yutao 15035 Oct 15 10:40 Makefile


你可以看到,此时Makefile已经产生出来了。

7、使用Makefile编译代码

?

$ make
if gcc -DPACKAGE_NAME="" -DPACKAGE_TARNAME="" -DPACKAGE_VERSION="" -

DPACKAGE_STRING="" -DPACKAGE_BUGREPORT="" -DPACKAGE="helloworld" -DVERSION="1.0"

-I. -I. -g -O2 -MT helloworld.o -MD -MP -MF ".deps/helloworld.Tpo" /
-c -o helloworld.o `test -f 'helloworld.c' || echo './'`helloworld.c; /
then mv -f ".deps/helloworld.Tpo" ".deps/helloworld.Po"; /
else rm -f ".deps/helloworld.Tpo"; exit 1; /
fi
gcc -g -O2 -o helloworld helloworld.o?


  运行helloworld

?

$ ./helloworld
Hello, Linux World!


  这样helloworld就编译出来了,你如果按上面的步骤来做的话,应该也会很容易地编译出正确的helloworld文件。你还可以试着使用一些其他的make命令,如make clean,make install,make dist,看看它们会给你什么样的效果。感觉如何?自己也能写出这么专业的Makefile,老板一定会对你刮目相看。

  四、深入浅出

  针对上面提到的各个命令,我们再做些详细的介绍。

  1、 autoscan

  autoscan是用来扫描源代码目录生成configure.scan文件的。autoscan可以用目录名做为参数,但如果你不使用参数的话,那么autoscan将认为使用的是当前目录。autoscan将扫描你所指定目录中的源文件,并创建configure.scan文件。

  2、 configure.scan

  configure.scan包含了系统配置的基本选项,里面都是一些宏定义。我们需要将它改名为configure.in

  3、 aclocal

  aclocal是一个perl 脚本程序。aclocal根据configure.in文件的内容,自动生成aclocal.m4文件。aclocal的定义是:“aclocal - create aclocal.m4 by scanning configure.ac”。

  4、 autoconf

  autoconf是用来产生configure文件的。configure是一个脚本,它能设置源程序来适应各种不同的操作系统平台,并且根据不同的系统来产生合适的Makefile,从而可以使你的源代码能在不同的操作系统平台上被编译出来。

  configure.in文件的内容是一些宏,这些宏经过autoconf 处理后会变成检查系统特性、环境变量、软件必须的参数的shell脚本。configure.in文件中的宏的顺序并没有规定,但是你必须在所有宏的最前面和最后面分别加上AC_INIT宏和AC_OUTPUT宏。

  在configure.ini中:

  #号表示注释,这个宏后面的内容将被忽略。

  AC_INIT(FILE)

  这个宏用来检查源代码所在的路径。

AM_INIT_AUTOMAKE(PACKAGE, VERSION)?

   这个宏是必须的,它描述了我们将要生成的软件包的名字及其版本号:PACKAGE是软件包的名字,VERSION是版本号。当你使用make dist命令时,它会给你生成一个类似helloworld-1.0.tar.gz的软件发行包,其中就有对应的软件包的名字和版本号。

AC_PROG_CC

  这个宏将检查系统所用的C编译器。

AC_OUTPUT(FILE)

  这个宏是我们要输出的Makefile的名字。

  我们在使用automake时,实际上还需要用到其他的一些宏,但我们可以用aclocal 来帮我们自动产生。执行aclocal后我们会得到aclocal.m4文件。

  产生了configure.in和aclocal.m4 两个宏文件后,我们就可以使用autoconf来产生configure文件了。

  5、 Makefile.am

  Makefile.am是用来生成Makefile.in的,需要你手工书写。Makefile.am中定义了一些内容:

AUTOMAKE_OPTIONS

  这个是automake的选项。在执行automake时,它会检查目录下是否存在标准GNU软件包中应具备的各种文件,例如AUTHORS、ChangeLog、NEWS等文件。我们将其设置成foreign时,automake会改用一般软件包的标准来检查。

bin_PROGRAMS

  这个是指定我们所要产生的可执行文件的文件名。如果你要产生多个可执行文件,那么在各个名字间用空格隔开。

helloworld_SOURCES

  这个是指定产生“helloworld”时所需要的源代码。如果它用到了多个源文件,那么请使用空格符号将它们隔开。比如需要helloworld.h,helloworld.c那么请写成helloworld_SOURCES= helloworld.h helloworld.c。

  如果你在bin_PROGRAMS定义了多个可执行文件,则对应每个可执行文件都要定义相对的filename_SOURCES。

  6、 automake

  我们使用automake --add-missing来产生Makefile.in。

  选项--add-missing的定义是“add missing standard files to package”,它会让automake加入一个标准的软件包所必须的一些文件。

  我们用automake产生出来的Makefile.in文件是符合GNU Makefile惯例的,接下来我们只要执行configure这个shell 脚本就可以产生合适的 Makefile 文件了。

  7、 Makefile

  在符合GNU Makefiel惯例的Makefile中,包含了一些基本的预先定义的操作:

make

  根据Makefile编译源代码,连接,生成目标文件,可执行文件。

make clean

  清除上次的make命令所产生的object文件(后缀为“.o”的文件)及可执行文件。

make install

  将编译成功的可执行文件安装到系统目录中,一般为/usr/local/bin目录。

make dist

  产生发布软件包文件(即distribution package)。这个命令将会将可执行文件及相关文件打包成一个tar.gz压缩的文件用来作为发布软件的软件包。

  它会在当前目录下生成一个名字类似“PACKAGE-VERSION.tar.gz”的文件。PACKAGE和VERSION,是我们在configure.in中定义的AM_INIT_AUTOMAKE(PACKAGE, VERSION)。

make distcheck

  生成发布软件包并对其进行测试检查,以确定发布包的正确性。这个操作将自动把压缩包文件解开,然后执行configure命令,并且执行make,来确认编译不出现错误,最后提示你软件包已经准备好,可以发布了。

===============================================
helloworld-1.0.tar.gz is ready for distribution
===============================================
make distclean

  类似make clean,但同时也将configure生成的文件全部删除掉,包括Makefile。

  五、结束语

  通过上面的介绍,你应该可以很容易地生成一个你自己的符合GNU惯例的Makefile文件及对应的项目文件。

  如果你想写出更复杂的且符合惯例的Makefile,你可以参考一些开放代码的项目中的configure.in和Makefile.am文件,比如:嵌入式数据库sqlite,单元测试cppunit。
 
 
 
 
添加评论
15:56  |  固定链接 | 引用通告 (0) | 记录它
 
 
固定链接  关闭
 
 
 

 
 
 
 
 

 
4月29日
 
 
 
Linux环境下的Socket编程
 
Linux环境下的Socket编程
?
什么是Socket
  Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程,必须理解Socket接口。
  Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。
Socket建立
  为了建立Socket,程序可以调用Socket函数,该函数返回一个类似于文件描述符的句柄。socket函数原型为:
  int socket(int domain, int type, int protocol);
  domain指明所使用的协议族,通常为PF_INET,表示互联网协议族(TCP/IP协议族);type参数指定socket的类型:SOCK_STREAM 或SOCK_DGRAM,Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议;protocol通常赋值"0"。Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。
  Socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用Socket函数时,socket执行体将建立一个Socket,实际上"建立一个Socket"意味着为一个Socket数据结构分配存储空间。Socket执行体为你管理描述符表。
  两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。Socket数据结构中包含这五种信息。
Socket配置
  通过socket调用返回一个socket描述符后,在使用socket进行网络传输以前,必须配置该socket。面向连接的socket客户端通过调用Connect函数在socket数据结构中保存本地和远端信息。无连接socket的客户端和服务端以及面向连接socket的服务端通过调用bind函数来配置本地信息。
Bind函数将socket与本机上的一个端口相关联,随后你就可以在该端口监听服务请求。Bind函数原型为:
  int bind(int sockfd,struct sockaddr *my_addr, int addrlen);
  Sockfd是调用socket函数返回的socket描述符,my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;addrlen常被设置为sizeof(struct sockaddr)。
  struct sockaddr结构类型是用来保存socket信息的:
  struct sockaddr {
   unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14 字节的协议地址 */
};
  sa_family一般为AF_INET,代表Internet(TCP/IP)地址族;sa_data则包含该socket的IP地址和端口号。
  另外还有一种结构类型:
  struct sockaddr_in {
   short int sin_family; /* 地址族 */
   unsigned short int sin_port; /* 端口号 */
   struct in_addr sin_addr; /* IP地址 */
   unsigned char sin_zero[8]; /* 填充0 以保持与struct sockaddr同样大小 */
  };
  这个结构更方便使用。sin_zero用来将sockaddr_in结构填充到与struct sockaddr同样的长度,可以用bzero()或memset()函数将其置为零。指向sockaddr_in 的指针和指向sockaddr的指针可以相互转换,这意味着如果一个函数所需参数类型是sockaddr时,你可以在函数调用的时候将一个指向sockaddr_in的指针转换为指向sockaddr的指针;或者相反。
  使用bind函数时,可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号:
  my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口号 */
  my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本机IP地址 */
通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。同样,通过将my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自动填入本机IP地址。
注意在使用bind函数是需要将sin_port和sin_addr转换成为网络字节优先顺序;而sin_addr则不需要转换。
  计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。Internet上数据以高位字节优先顺序在网络上传输,所以对于在内部是以低位字节优先方式存储数据的机器,在Internet上传输数据时就需要进行转换,否则就会出现数据不一致。
  下面是几个字节顺序转换函数:
·htonl():把32位值从主机字节序转换成网络字节序
·htons():把16位值从主机字节序转换成网络字节序
·ntohl():把32位值从网络字节序转换成主机字节序
·ntohs():把16位值从网络字节序转换成主机字节序
  Bind()函数在成功被调用时返回0;出现错误时返回"-1"并将errno置为相应的错误号。需要注意的是,在调用bind函数时一般不要将端口号置为小于1024的值,因为1到1024是保留端口号,你可以选择大于1024中的任何一个没有被占用的端口号。
连接建立
  面向连接的客户程序使用Connect函数来配置socket并与远端服务器建立一个TCP连接,其函数原型为:
  int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);
Sockfd是socket函数返回的socket描述符;serv_addr是包含远端主机IP地址和端口号的指针;addrlen是远端地质结构的长度。Connect函数在出现错误时返回-1,并且设置errno为相应的错误码。进行客户端程序设计无须调用bind(),因为这种情况下只需知道目的机器的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,socket执行体为你的程序自动选择一个未被占用的端口,并通知你的程序数据什么时候到打断口。
  Connect函数启动和远端主机的直接连接。只有面向连接的客户程序使用socket时才需要将此socket与远端主机相连。无连接协议从不建立直接连接。面向连接的服务器也从不启动一个连接,它只是被动的在协议端口监听客户的请求。
  Listen函数使socket处于被动的监听模式,并为该socket建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。
  int listen(int sockfd, int backlog);
Sockfd是Socket系统调用返回的socket 描述符;backlog指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待accept()它们(参考下文)。Backlog对队列中等待服务的请求的数目进行了限制,大多数系统缺省值为20。如果一个服务请求到来时,输入队列已满,该socket将拒绝连接请求,客户将收到一个出错信息。
当出现错误时listen函数返回-1,并置相应的errno错误码。
  accept()函数让服务器接收客户的连接请求。在建立好输入队列后,服务器就调用accept函数,然后睡眠并等待客户的连接请求。
  int accept(int sockfd, void *addr, int *addrlen);
  sockfd是被监听的socket描述符,addr通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求服务的主机的信息(某台主机从某个端口发出该请求);addrten通常为一个指向值为sizeof(struct sockaddr_in)的整型指针变量。出现错误时accept函数返回-1并置相应的errno值。
  首先,当accept函数监视的socket收到连接请求时,socket执行体将建立一个新的socket,执行体将这个新socket和请求连接进程的地址联系起来,收到服务请求的初始socket仍可以继续在以前的 socket上监听,同时可以在新的socket描述符上进行数据传输操作。
数据传输
  Send()和recv()这两个函数用于面向连接的socket上进行数据传输。
  Send()函数原型为:
  int send(int sockfd, const void *msg, int len, int flags);
Sockfd是你想用来传输数据的socket描述符;msg是一个指向要发送数据的指针;Len是以字节为单位的数据的长度;flags一般情况下置为0(关于该参数的用法可参照man手册)。
  Send()函数返回实际上发送出的字节数,可能会少于你希望发送的数据。在程序中应该将send()的返回值与欲发送的字节数进行比较。当send()返回值与len不匹配时,应该对这种情况进行处理。
char *msg = "Hello!";
int len, bytes_sent;
……
len = strlen(msg);
bytes_sent = send(sockfd, msg,len,0);
……
  recv()函数原型为:
  int recv(int sockfd,void *buf,int len,unsigned int flags);
  Sockfd是接受数据的socket描述符;buf 是存放接收数据的缓冲区;len是缓冲的长度。Flags也被置为0。Recv()返回实际上接收的字节数,当出现错误时,返回-1并置相应的errno值。
Sendto()和recvfrom()用于在无连接的数据报socket方式下进行数据传输。由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址。
sendto()函数原型为:
  int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
  该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。
  Recvfrom()函数原型为:
  int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
  from是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或当出现错误时返回-1,并置相应的errno。
如果你对数据报socket调用了connect()函数时,你也可以利用send()和recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息。
结束传输
  当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:
close(sockfd);
  你也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输,而一个方向上的数据传输继续进行。如你可以关闭某socket的写操作而允许继续在该socket上接受数据,直至读入所有数据。
  int shutdown(int sockfd,int how);
  Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式:
  ·0-------不允许继续接收数据
  ·1-------不允许继续发送数据
·2-------不允许继续发送和接收数据,
·均为允许则调用close ()
  shutdown在操作成功时返回0,在出现错误时返回-1并置相应errno。
面向连接的Socket实例
  代码实例中的服务器通过socket连接向客户端发送字符串"Hello, you are connected!"。只要在服务器上运行该服务器软件,在客户端运行客户软件,客户端就会收到该字符串。
  该服务器软件代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define SERVPORT 3333 /*服务器监听端口号 */
#define BACKLOG 10 /* 最大同时连接请求数 */
main()
{
int sockfd,client_fd; /*sock_fd:监听socket;client_fd:数据传输socket */
 struct sockaddr_in my_addr; /* 本机地址信息 */
 struct sockaddr_in remote_addr; /* 客户端地址信息 */
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
  perror("socket创建出错!"); exit(1);
}
my_addr.sin_family=AF_INET;
 my_addr.sin_port=htons(SERVPORT);
 my_addr.sin_addr.s_addr = INADDR_ANY;
bzero(&(my_addr.sin_zero),8);
 if (bind(sockfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) /
   == -1) {
perror("bind出错!");
exit(1);
}
 if (listen(sockfd, BACKLOG) == -1) {
perror("listen出错!");
exit(1);
}
while(1) {
  sin_size = sizeof(struct sockaddr_in);
  if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, /
  &sin_size)) == -1) {
perror("accept出错");
continue;
}
  printf("received a connection from %s/n", inet_ntoa(remote_addr.sin_addr));
  if (!fork()) { /* 子进程代码段 */
   if (send(client_fd, "Hello, you are connected!/n", 26, 0) == -1)
   perror("send出错!");
close(client_fd);
exit(0);
}
  close(client_fd);
  }
 }
}
  服务器的工作流程是这样的:首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,然后调用listen在相应的socket上监听,当accpet接收到一个连接服务请求时,将生成一个新的socket。服务器显示该客户机的IP地址,并通过新的socket向客户端发送字符串"Hello,you are connected!"。最后关闭该socket。
  代码实例中的fork()函数生成一个子进程来处理数据传输部分,fork()语句对于子进程返回的值为0。所以包含fork函数的if语句是子进程代码部分,它与if语句后面的父进程代码部分是并发执行的。
客户端程序代码如下:
#include<stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define SERVPORT 3333
#define MAXDATASIZE 100 /*每次最大数据传输量 */
main(int argc, char *argv[]){
 int sockfd, recvbytes;
 char buf[MAXDATASIZE];
 struct hostent *host;
 struct sockaddr_in serv_addr;
 if (argc < 2) {
fprintf(stderr,"Please enter the server's hostname!/n");
exit(1);
}
 if((host=gethostbyname(argv[1]))==NULL) {
herror("gethostbyname出错!");
exit(1);
}
 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket创建出错!");
exit(1);
}
 serv_addr.sin_family=AF_INET;
 serv_addr.sin_port=htons(SERVPORT);
 serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
 bzero(&(serv_addr.sin_zero),8);
 if (connect(sockfd, (struct sockaddr *)&serv_addr, /
   sizeof(struct sockaddr)) == -1) {
perror("connect出错!");
exit(1);
}
 if ((recvbytes=recv(sockfd, buf, MAXDATASIZE, 0)) ==-1) {
perror("recv出错!");
exit(1);
}
 buf[recvbytes] = '/0';
 printf("Received: %s",buf);
 close(sockfd);
}
  客户端程序首先通过服务器域名获得服务器的IP地址,然后创建一个socket,调用connect函数与服务器建立连接,连接成功之后接收从服务器发送过来的数据,最后关闭socket。
  函数gethostbyname()是完成域名转换的。由于IP地址难以记忆和读写,所以为了方便,人们常常用域名来表示主机,这就需要进行域名和IP地址的转换。函数原型为:
  struct hostent *gethostbyname(const char *name);
  函数返回为hosten的结构类型,它的定义如下:
  struct hostent {
  char *h_name; /* 主机的官方域名 */
   char **h_aliases; /* 一个以NULL结尾的主机别名数组 */
   int h_addrtype; /* 返回的地址类型,在Internet环境下为AF-INET */
  int h_length; /* 地址的字节长度 */
   char **h_addr_list; /* 一个以0结尾的数组,包含该主机的所有地址*/
  };
  #define h_addr h_addr_list[0] /*在h-addr-list中的第一个地址*/
  当 gethostname()调用成功时,返回指向struct hosten的指针,当调用失败时返回-1。当调用gethostbyname时,你不能使用perror()函数来输出错误信息,而应该使用herror()函数来输出。
  无连接的客户/服务器程序的在原理上和连接的客户/服务器是一样的,两者的区别在于无连接的客户/服务器中的客户一般不需要建立连接,而且在发送接收数据时,需要指定远端机的地址。
阻塞和非阻塞
  阻塞函数在完成其指定的任务以前不允许程序调用另一个函数。例如,程序执行一个读数据的函数调用时,在此函数完成读操作以前将不会执行下一程序语句。当服务器运行到accept语句时,而没有客户连接服务请求到来,服务器就会停止在accept语句上等待连接服务请求的到来。这种情况称为阻塞(blocking)。而非阻塞操作则可以立即完成。比如,如果你希望服务器仅仅注意检查是否有客户在等待连接,有就接受连接,否则就继续做其他事情,则可以通过将Socket设置为非阻塞方式来实现。非阻塞socket在没有客户在等待时就使accept调用立即返回。
  #include <unistd.h>
  #include <fcntl.h>
  ……
sockfd = socket(AF_INET,SOCK_STREAM,0);
fcntl(sockfd,F_SETFL,O_NONBLOCK);
……
  通过设置socket为非阻塞方式,可以实现"轮询"若干Socket。当企图从一个没有数据等待处理的非阻塞Socket读入数据时,函数将立即返回,返回值为-1,并置errno值为EWOULDBLOCK。但是这种"轮询"会使CPU处于忙等待方式,从而降低性能,浪费系统资源。而调用select()会有效地解决这个问题,它允许你把进程本身挂起来,而同时使系统内核监听所要求的一组文件描述符的任何活动,只要确认在任何被监控的文件描述符上出现活动,select()调用将返回指示该文件描述符已准备好的信息,从而实现了为进程选出随机的变化,而不必由进程本身对输入进行测试而浪费CPU开销。Select函数原型为:
int select(int numfds,fd_set *readfds,fd_set *writefds,
fd_set *exceptfds,struct timeval *timeout);
  其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合。如果你希望确定是否可以从标准输入和某个socket描述符读取数据,你只需要将标准输入的文件描述符0和相应的sockdtfd加入到readfds集合中;numfds的值是需要检查的号码最高的文件描述符加1,这个例子中numfds的值应为sockfd+1;当select返回时,readfds将被修改,指示某个文件描述符已经准备被读取,你可以通过FD_ISSSET()来测试。为了实现fd_set中对应的文件描述符的设置、复位和测试,它提供了一组宏:
  FD_ZERO(fd_set *set)----清除一个文件描述符集;
  FD_SET(int fd,fd_set *set)----将一个文件描述符加入文件描述符集中;
  FD_CLR(int fd,fd_set *set)----将一个文件描述符从文件描述符集中清除;
  FD_ISSET(int fd,fd_set *set)----试判断是否文件描述符被置位。
  Timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout长时间后没有文件描述符准备好即返回。struct timeval数据结构为:
  struct timeval {
   int tv_sec; /* seconds */
   int tv_usec; /* microseconds */
};
POP3客户端实例
  下面的代码实例基于POP3的客户协议,与邮件服务器连接并取回指定用户帐号的邮件。与邮件服务器交互的命令存储在字符串数组POPMessage中,程序通过一个do-while循环依次发送这些命令。
#include<stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define POP3SERVPORT 110
#define MAXDATASIZE 4096
main(int argc, char *argv[]){
int sockfd;
struct hostent *host;
struct sockaddr_in serv_addr;
char *POPMessage[]={
"USER userid/r/n",
"PASS password/r/n",
"STAT/r/n",
"LIST/r/n",
"RETR 1/r/n",
"DELE 1/r/n",
"QUIT/r/n",
NULL
};
int iLength;
int iMsg=0;
int iEnd=0;
char buf[MAXDATASIZE];
if((host=gethostbyname("your.server"))==NULL) {
perror("gethostbyname error");
exit(1);
}
if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket error");
exit(1);
}
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(POP3SERVPORT);
serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(serv_addr.sin_zero),8);
if (connect(sockfd, (struct sockaddr *)&serv_addr,sizeof(struct sockaddr))==-1){
perror("connect error");
exit(1);
}
do {
send(sockfd,POPMessage[iMsg],strlen(POPMessage[iMsg]),0);
printf("have sent: %s",POPMessage[iMsg]);
iLength=recv(sockfd,buf+iEnd,sizeof(buf)-iEnd,0);
iEnd+=iLength;
buf[iEnd]='/0';
printf("received: %s,%d/n",buf,iMsg);
iMsg++;
} while (POPMessage[iMsg]);
close(sockfd);
}   

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值