最近想读一下linux1.0版中关于TCP/IP实现的代码,发现需要看一下其中的Makefile,于是一鼓作气看完了找到的Makefile教程,又发现其中有个配置步骤make config,于是深入看了配置过程中调用的Configure脚本,由于对shell不甚了解,看了两天才把整个脚步的设计思路大致搞清楚了。
Configure脚本供make调用(也可以直接调用该脚本),用来配置内核,它的标准输入是config.in文件,而不是终端,从config.in中读取信息进行配置。而脚本的输出有两个文件,一是include/linux/autoconf.h,二是在当前目录的.config隐藏文件。其实由于配置过程中用户有不同的偏好导致有不同的配置文件,Configure脚本可以根据用户本次的设置生成config.new文件,保存用户最新的设置,原来的config.in被重命名为config.old,config.new被重命名为config.in,下次执行Configure脚本时用户想保持和上次一样的配置,那就一路回车就可以了。不用重新设置。
Configure是从config.in中按行读取输入的,config.in文件中的行有不同的类型,Configure需要分别处理。有以下几种类型(//符号后面的部分,包括这两个符号,是我的注释):
1、 # internal comment
//以#开头的是内部注释,被Configure忽略掉,不处理
2、 : message
//以:开头的是要显示的消息,Configure脚本在处理过程中需要将
//message显示到标准输出
3、 * external comment
//以*开头的是外部注释,这个注释同时会保存到上面提到的两个输出
//文件中
4、 if condition
... commands ...
else
... commands ...
fi
//这个结构说成是条件配置吧,else语句是可选的,这个结构可以嵌套使
//用,condition可以是任何合法的bash表达式,注意,没有then这
//个关键字
5、 bool 'prompt' CONFIG_VARIABLE default
//这种类型的行会提示用户输入一个布尔值,默认的值是defaul(y或
//n),用户的输入被记录在下面
//四个地方:
# In .config, if `y'
# CONFIG_VARIABLE = CONFIG_VARIABLE
# In .config, if `n'
# # CONFIG_VARIABLE is not set
#
# In autoconf.h, if `y'
# #define CONFIG_VARIABLE 1
# In autoconf.h, if `n'
# #undef CONFIG_VARIABLE
#
# In config.in, if `y'
# bool 'prompt' CONFIG_VARIABLE y
# In config.in, if `n'
# bool 'prompt' CONFIG_VARIABLE n
#
# In the environment of the Configure script, if `y'
# CONFIG_VARIABLE = y
# In the environment of the Configure script, if `n'
# CONFIG_VARIABLE = n
//最后一个地方是当然Configure脚本的执行环境中,设置了这个变量,这会在
//条件配置中需要使用,用来测试CONFIG_VARIABLE变量的值是y还是n。
6、 int 'prompt' CONFIG_VARIABLE default
//这种类型的行会提示用户输入一个数字,默认的值是defaul,用户的输
//入也被记录在下面
//四个地方:
# In .config
# CONFIG_VARIABLE = response
# In autoconf.h
# #define CONFIG_VARIABLE (response)
# In config.in
# int 'prompt' CONFIG_VARIABLE response
# In the environment of the Configure script
# CONFIG_VARIABLE = response
7、 exec cmd
//Configure会让shell执行cmd命令的
//一共是七种类型的行。
//下面就是Configure的核心代码了:
//这个函数将读取的输入放到ans变量中,接收两个参数,第一个参数被显示到屏
//幕上.如果用户没有输入,则将第二个参数作为默认输入赋给ans变量。
function readln () {
echo -n "$1"read ans [ -z "$ans" ] && ans=$2
}
//这个函数处理上面第5种类型的行,接受三个参数:'prompt'和
//CONFIG_VARIABLE和 default
function bool () {
# Slimier hack to get bash to rescan a line.
eval "set -- $1"
ans=""
while [ "$ans" != "y" -a "$ans" != "n" ]; do
readln "$1 ($2) [$3] " "$3"
//$1 ($2) [$3] 是合并的字符串,作为readln的第1个参数
//$3就是default,作为readln的第2个参数 done
//调用readln后,返回值就放在ans中,开始对ans处理
if [ "$ans" = "y" ]; then
echo "$2 = $2" >>$CONFIG
echo "#define $2 1" >>$CONFIG_H
//CONFIG和CONFIG_H两个变量见下文的定义
//上面提到用户输入被放到四个地方,这里只有两个,其余两个
//呢?在下面的代码中会见到
else
echo "# $2 is not set" >>$CONFIG
echo "#undef $2" >>$CONFIG_H
fi
raw_input_line="bool '$1' $2 $ans"
//raw_input_line就保存着用户的偏好配置值(ans),这个
//字符串将会被保持到config.new文件中,
//然后config.new被重命名为config.in,也就是上面在
//第5中类型的行中提到的第3个地方,这里还未真正输出到
//config.new文件。
eval "$2=$ans"
//第5中类型的行中提到的第4个地方,这个语句很重要的,下面
//的代码中有个case语句,其中要对遇到的if后面的表达式进
//行测试,因为我之前把这个语句注释掉了,所以那个表达式一
//直验证是假。
}
//这个函数和bool函数类似,不说了
function int () {
# Slimier hack to get bash to rescan a line.
eval "set -- $1"
ans="x"
while [ $[$ans+0] != "$ans" ]; do
readln "$1 ($2) [$3] " "$3"
done
echo "$2 = $ans" >>$CONFIG
echo "#define $2 ($ans)" >>$CONFIG_H
raw_input_line="int '$1' $2 $ans"
eval "$2=$ans"
}
CONFIG=.tmpconfig
CONFIG_H=include/linux/autoconf.h
//上文见到的两个变量的定义
trap "rm -f $CONFIG $CONFIG_H config.new ; exit 1" 1 2
#
# Make sure we start out with a clean slate.
#
> config.new
echo "#" > $CONFIG
echo "# Automatically generated make config: don't edit" >> $CONFIG
echo "#" >> $CONFIG
echo "/*" > $CONFIG_H
echo " * Automatically generated C config: don't edit" >> $CONFIG_H
echo " */" >> $CONFIG_H
stack=''
branch='t'
//这两个变量用来处理if。。。else。。。fi结构
//下面开始关键的处理代码了
whileread raw_input_line
//从config.in中读取一行到raw_input_line变量中do
# Slimy hack to get bash to rescan a line.
read cmd rest
END_OF_COMMAND
//从raw_input_line变量中取出第一个字符串,赋给cmd,剩
//下的赋给rest.根据前面说的6种不同的行,cmd可能
//是*或:或int或bool或exec或if或else或fi或#,#就不
//处理了
if [ "$cmd" = "*" ]; then
if [ "$branch" = "t" ]; then
//把处在if或else结构中的行称为在if或else子分支中。if
//或else外层的称为父分支
//如果一个分支内的行需要进行处理,前提标志是否进行处理的
// 变量branch=t (true)
//如果该分支的branch=f,则处于该分支的行都不需要处理
// 了,在没有遇到任何if或else结构之前的行都是需要处理
// 的,因此最外层的分支的branch=t echo "$raw_input_line"
//如果该分支的branch=t,则分支内的行是需要处理的,下面
//几行代码就是按第3中类型的行进行处理的 echo "# $rest" >>$CONFIG
if [ "$prevcmd" != "*" ]; then
echo >>$CONFIG_H
echo "/* $rest" >>$CONFIG_H
//在autoconf.h中的注释是/* */,因此要进行小小的变换,
//对于遇到的第一行注释(prevcmd!=*)要加上/* else
echo " * $rest" >>$CONFIG_H
//不是第一行注释的话(prevcmd=*)
fi
prevcmd="*" //作为下一个cmd的prevcmd
fi
else //cmd!=*
[ "$prevcmd" = "*" ] && echo " */" >>$CONFIG_H
//本次处理的cmd不再是*,但刚处理过的那一行有*,说明注释
//结束,因此,要在此处理autoconf.h文件,在最后加上*/ prevcmd=""
case "$cmd" in
//在这里根据不同的cmd用case结构进行处理。
:) [ "$branch" = "t" ] && echo "$raw_input_line" ;;
//这行所在的分支内,如果该分支的branch=t说明该分支内
//的行是需要处理的,因此进行处理
int) [ "$branch" = "t" ] && int "$rest" ;;
bool) [ "$branch" = "t" ] && bool "$rest" ;;
exec) [ "$branch" = "t" ] && ( sh -c "$rest" ) ;;
if) stack="$branch $stack" //遇到当前分支(父分支)的
//一个子分支了,
//将当前的分支branch值压栈 if [ "$branch" = "t" ] && eval "$rest"; then
//当前分支(父分支)是t,且子分支if的表达
//式为真则if子分支的branch=t, branch=t
else //当前分支(父分支)是f,或者表达式是f,
//if分支就是f了,不再执行if分支内的那些行
branch=f
fi ;;
else) if [ "$branch" = "t" ]; then
//此时开始处理含有else的行,那么上面的行就是if分支所
//在的行了,条件中的branch还是if属于分支的,上个if分
//支的branch=t,表示if分支执行了,因此这个else分支就
//是f了,不执行
branch=f
else //之前的if分支内的行没有被处理,两种情况:
//1、if分支的父分支branch=t,但表达式是f,不执行
// 该if分支。暗示会执行else分支
//2、即使表达式是t,有可能是那个if的父分支是f,if
// 分支也不执行,else也不会被处理
//此时这个esle分支是否执行要看父分支是否是t(父
//分支是否要执行),
//如果父分支是t,因为if分支没执行,所以执行
// else分支(else分支的branch=t)
//如果父分支是f,则else分支肯定不执行(因此else
// 分支的branch=f)
//则在两种情况下,else分支的branch的值和父分支的
//branch的值是相同的,因此对else分支的branch的
//赋值就是下面这条代码:
read branch rest
//如果父分支是f,那么else分支branch=f,
//else语句不执行,如果父分支是t,那么else
//分支的branch=t,就是说if分支因为条件不
//成立,那么else就开始执行了。 END_OF_STACK
fi ;;
fi) [ -z "$stack" ] && echo "Error! Extra fi." 1>&2
read branch stack
//弹出栈
END_OF_STACK
;;
esac //此时才处理完不同的cmd fi
echo "$raw_input_line" >>config.new
//此时的raw_input_line已经在调用bool和int函数时被
//重新赋值了,此时才真正的将这个变量保存到用户的偏好配
//置文件config.new文件中,
done //到这里,config.in文件中的行已经被处理完,下面开始收
//尾工作了[ "$prevcmd" = "*" ] && echo " */" >>$CONFIG_H
[ -z "$stack" ] || echo "Error! Untermiated if." 1>&2
mv config.in config.old
mv config.new config.in //把用户此次配置的选项重新保存到
//config.in中,原来的config.in
//保存到config.old中。下次make
//config时,如果不需要重新配置,一
//路回车就可以了
总结:
关键是对if。。。else。。。fi结构的处理,使用了堆栈,因为不像C里面处理if。。。else结构那样,在处理config.in文件时,即使if分支不成立,还是需要决定处理if分支下面的每一行,因此就需要当前分支的branch变量做标志了,根据这个变量的值决定是否需要处理这些行的具体内容。关键就是如何决定是否处理这个分支,然后对这个分支的branch变量赋值。
另外,在int函数和bool函数的最后eval的使用也是很重要的,处理if分支时,那个if表达式就是需要测试eval的执行结果的。
原始的Configure文件和config.in文件见附件。
文件:
linux1.0.rar
大小:
4KB
下载: