1 /*
2 * linux/tools/build.c
3 *
4 * (C) 1991 Linus Torvalds
5 */
6
7 /*
8 * This file builds a disk-image from three different files:
9 *
10 * - bootsect: max 510 bytes of 8086 machine code, loads the rest
11 * - setup: max 4 sectors of 8086 machine code, sets up system parm
12 * - system: 80386 code for actual system
13 *
14 * It does some checking that all files are of the correct type, and
15 * just writes the result to stdout, removing headers and padding to
16 * the right amount. It also writes some system data to stderr.
17 */
/*
* 该程序从三个不同的程序中创建磁盘映像文件:
*
* - bootsect:该文件的8086机器码最长为510字节,用于加载其他程序。
* - setup:该文件的8086机器码最长为4个磁盘扇区,用于设置系统参数。
* - system:实际系统的80386代码。
*
* 该程序首先检查所有程序模块的类型是否正确,并将检查结果在终端上显示出来,
* 然后删除模块头部并扩充大正确的长度。该程序也会将一些系统数据写到stderr。
*/
18
19 /*
20 * Changes by tytso to allow root device specification
21 *
22 * Added swap-device specification: Linux 20.12.91
23 */
/*
* tytso对该程序作了修改,以允许指定根文件设备。
*
* 添加了指定交换设备功能:Linus 20.12.91
*/
24
25 #include <stdio.h> /* fprintf */ // 使用其中的fprintf()函数。
26 #include <string.h> // 字符串操作函数。
27 #include <stdlib.h> /* contains exit */ // 含exit函数原型说明。
28 #include <sys/types.h> /* unistd.h needs this */ // 该头文件供unistd.h文件使用。
29 #include <sys/stat.h> // 含文件状态信息结构定义。
30 #include <linux/fs.h> // 文件系统头文件。
31 #include <unistd.h> /* contains read/write */ // 含read/write函数原型说明。
32 #include <fcntl.h> // 包含文件操作模式符号常数。
33
34 #define MINIX_HEADER 32 // minix二进制目标文件模块头部长度为32字节。
35 #define GCC_HEADER 1024 // GCC头部信息长度为1024字节。
36
37 #define SYS_SIZE 0x3000 // system文件最长节数(字节数为SYS_SIZE*16=128KB)。
38
// 默认地把Linux根文件系统所在设备设置为在第2个硬盘的第1个分区上(即设备号为0x0306),
// 是因为Linus当时开发Linux时,把第1个硬盘用作MINIX系统盘,而第2个硬盘用作为Linux
// 的根文件系统盘。
39 #define DEFAULT_MAJOR_ROOT 3 // 默认根设备主设备号 - 3(硬盘)。
40 #define DEFAULT_MINOR_ROOT 6 // 默认根设备次设备号 - 6(第2个硬盘的第1分区)。
41
42 #define DEFAULT_MAJOR_SWAP 0 // 默认交换设备主设备号。
43 #define DEFAULT_MINOR_SWAP 0 // 默认交换设备次设备号。
44
45 /* max nr of sectors of setup: don't change unless you also change
46 * bootsect etc */
/* 下面指定setup模块占的最大扇区数:不要改变该值,除非也改变bootsect等相应文件。
47 #define SETUP_SECTS 4 // setup最大长度为4个扇区(2KB)。
48
49 #define STRINGIFY(x) #x // 把x转换成字符串类型,用于出错显示语句中。
50
//// 显示出错信息,并终止程序。
51 void die(char * str)
52 {
53 fprintf(stderr,"%s\n",str);
54 exit(1);
55 }
56
// 显示程序使用方法,并退出。
57 void usage(void)
58 {
59 die("Usage: build bootsect setup system [rootdev] [> image]");
60 }
61
// 主程序开始。
62 int main(int argc, char ** argv)
63 {
64 int i,c,id;
65 char buf[1024];
66 char major_root, minor_root;
67 char major_swap, minor_swap;
68 struct stat sb;
69
// 首先检查build程序执行时实际命令行参数个数,并根据参数个数作相应设置。如果build程序
// 命令行参数个数不是4到6个(程序名算作1个),则显示程序用法并退出。
70 if ((argc < 4) || (argc > 6))
71 usage();
// 若程序命令行上有多于4个参数,那么如果根设备名不是软盘("FLOPPY"),则取该设备文件的
// 状态信息。若取状态出错则显示信息并退出,否则取该设备名状态结构中的主设备号和次设备号
// 作为根设备号。如果根设备就是FLOPPY设备,则让主设备号和次设备号取0。表示根设备是当前
// 启动引导设备。
72 if (argc > 4) {
73 if (strcmp(argv[4], "FLOPPY")) {
74 if (stat(argv[4], &sb)) {
75 perror(argv[4]);
76 die("Couldn't stat root device.");
77 }
78 major_root = MAJOR(sb.st_rdev); // 取设备名状态结构中设备号。
79 minor_root = MINOR(sb.st_rdev);
80 } else {
81 major_root = 0;
82 minor_root = 0;
83 }
// 若参数只有4个,则让主设备号和次设备号等于系统默认的根设备号。
84 } else {
85 major_root = DEFAULT_MAJOR_ROOT;
86 minor_root = DEFAULT_MINOR_ROOT;
87 }
// 若程序命令行上有6个参数,那么如果最后一个表示交换设备的参数不是无("NONE"),则取该
// 设备文件的状态信息。若取状态出错则显示信息并退出,否则取该设备名状态结构中的主设备号
// 和次设备号作为交换设备号。如果最后一个参数就是"NONE",则让交换设备的主设备号和次设备
// 号取为0。表示交换设备就是当前启动引导设备。
88 if (argc == 6) {
89 if (strcmp(argv[5], "NONE")) {
90 if (stat(argv[5], &sb)) {
91 perror(argv[5]);
92 die("Couldn't stat root device.");
93 }
94 major_swap = MAJOR(sb.st_rdev); // 取设备名状态结构中设备号。
95 minor_swap = MINOR(sb.st_rdev);
96 } else {
97 major_swap = 0;
98 minor_swap = 0;
99 }
// 若参数没有6个而是5个,表示命令行上没有带交换设备名。于是就让交换设备主设备号和次设备
// 号等于系统默认的交换设备号。
100 } else {
101 major_swap = DEFAULT_MAJOR_SWAP;
102 minor_swap = DEFAULT_MINOR_SWAP;
103 }
// 接下来在标准错误终端上显示上面所选择的根设备主、次设备号和交换设备主、次设备号。如果
// 主设备号不等于2(软盘)或3(硬盘),也不为0(取系统默认设备),则显示出错信息并退出。
// 终端的标准输出被定向到文件Image,因此被用于输出保存内核代码数据,生成内核映像文件。
104 fprintf(stderr, "Root device is (%d, %d)\n", major_root, minor_root);
105 fprintf(stderr, "Swap device is (%d, %d)\n", major_swap, minor_swap);
106 if ((major_root != 2) && (major_root != 3) &&
107 (major_root != 0)) {
108 fprintf(stderr, "Illegal root device (major = %d)\n",
109 major_root);
110 die("Bad root device --- major #");
111 }
112 if (major_swap && major_swap != 3) {
113 fprintf(stderr, "Illegal swap device (major = %d)\n",
114 major_swap);
115 die("Bad root device --- major #");
116 }
// 下面开始执行读取各个文件内容并进行相应的复制处理。首先初始化1KB的复制缓冲区,置全0。
// 然后以只读方式打开参数1指定的文件(bootsect)。从中读取32字节的MINIX执行文件头结构
// 内容(参见列表后说明)到缓冲区buf中。
117 for (i=0;i<sizeof buf; i++) buf[i]=0;
118 if ((id=open(argv[1],O_RDONLY,0))<0)
119 die("Unable to open 'boot'");
120 if (read(id,buf,MINIX_HEADER) != MINIX_HEADER)
121 die("Unable to read header of 'boot'");
// 接下来根据MINIX头部结构判断bootsect是否为一个有效的MINIX执行文件。若是,则从文件中
// 读取512字节的引导扇区代码和数据。
// 0x0301 - MINIX头部a_magic魔数;0x10 - a_flag可执行;0x04 - a_cpu, Intel 8086机器码。
122 if (((long *) buf)[0]!=0x04100301)
123 die("Non-Minix header of 'boot'");
// 判断头部长度字段a_hdrlen(字节)是否正确(32字节)。(后三字节正好没有用,是0)
124 if (((long *) buf)[1]!=MINIX_HEADER)
125 die("Non-Minix header of 'boot'");
// 判断数据段长a_data字段(long)内容是否为0。
126 if (((long *) buf)[3]!=0)
127 die("Illegal data segment in 'boot'");
// 判断堆a_bss字段(long)内容是否为0。
128 if (((long *) buf)[4]!=0)
129 die("Illegal bss in 'boot'");
// 判断执行点a_entry字段(long)内容是否为0。
130 if (((long *) buf)[5] != 0)
131 die("Non-Minix header of 'boot'");
// 判断符号表长字段a_sym的内容是否为0。
132 if (((long *) buf)[7] != 0)
133 die("Illegal symbol table in 'boot'");
// 在上述判断都正确的条件下读取文件中随后的实际代码数据,应该返回读取字节数为512字节。
// 因为bootsect文件中包含的是1个扇区的引导扇区代码和数据,并且最后2字节应该是可引导
// 标志0xAA55。
134 i=read(id,buf,sizeof buf);
135 fprintf(stderr,"Boot sector %d bytes.\n",i);
136 if (i != 512)
137 die("Boot block must be exactly 512 bytes");
138 if ((*(unsigned short *)(buf+510)) != 0xAA55)
139 die("Boot block hasn't got boot flag (0xAA55)");
// 引导扇区的506、507偏移处需存放交换设备号,508、509偏移处需存放根设备号。
140 buf[506] = (char) minor_swap;
141 buf[507] = (char) major_swap;
142 buf[508] = (char) minor_root;
143 buf[509] = (char) major_root;
// 然后将该512字节的数据写到标准输出stdout,若写出字节数不对,则显示出错信息并退出。
// 在linux/Makefile中,build程序标准输出被重定向到内核映像文件名Image上,因此引导
// 扇区代码和数据会被写到Image开始的512字节处。最后关闭bootsect模块文件。
144 i=write(1,buf,512);
145 if (i!=512)
146 die("Write call failed");
147 close (id);
148
// 下面以只读方式打开参数2指定的文件(setup)。从中读取32字节的MINIX执行文件头结构
// 内容到缓冲区buf中。处理方式与上面相同。首先以只读方式打开指定的文件setup。从中读
// 取32字节的MINIX执行文件头结构内容到缓冲区buf中。
149 if ((id=open(argv[2],O_RDONLY,0))<0)
150 die("Unable to open 'setup'");
151 if (read(id,buf,MINIX_HEADER) != MINIX_HEADER)
152 die("Unable to read header of 'setup'");
// 接下来根据MINIX头部结构判断setup是否为一个有效的MINIX执行文件。若是,则从文件中
// 读取512字节的引导扇区代码和数据。
// 0x0301- MINIX头部a_magic魔数;0x10- a_flag可执行;0x04- a_cpu, Intel 8086机器码。
153 if (((long *) buf)[0]!=0x04100301)
154 die("Non-Minix header of 'setup'");
// 判断头部长度字段a_hdrlen(字节)是否正确(32字节)。(后三字节正好没有用,是0)
155 if (((long *) buf)[1]!=MINIX_HEADER)
156 die("Non-Minix header of 'setup'");
// 判断数据段长字段a_data、堆字段a_bss、起始执行点字段a_entry和符号表字段a_sym的内容
// 是否为0。必须都为0。
157 if (((long *) buf)[3]!=0) // 数据段长a_data字段。
158 die("Illegal data segment in 'setup'");
159 if (((long *) buf)[4]!=0) // 堆a_bss字段。
160 die("Illegal bss in 'setup'");
161 if (((long *) buf)[5] != 0) // 执行起始点a_entry字段。
162 die("Non-Minix header of 'setup'");
163 if (((long *) buf)[7] != 0)
164 die("Illegal symbol table in 'setup'");
// 在上述判断都正确的条件下读取文件中随后的实际代码数据,并且写到终端标准输出。同时统计
// 写的长度(i),并在操作结束后关闭setup文件。之后判断一下利用setup执行写操作的代码
// 和数据长度值,该值不能大于(SETUP_SECTS * 512)字节,否则就得重新修改build、bootsect
// 和setup程序中设定的setup所占扇区数并重新编译内核。若一切正常就显示setup实际长度值。
165 for (i=0 ; (c=read(id,buf,sizeof buf))>0 ; i+=c )
166 if (write(1,buf,c)!=c)
167 die("Write call failed");
168 close (id); //关闭setup模块文件。
169 if (i > SETUP_SECTS*512)
170 die("Setup exceeds " STRINGIFY(SETUP_SECTS)
171 " sectors - rewrite build/boot/setup");
172 fprintf(stderr,"Setup is %d bytes.\n",i);
// 在将缓冲区buf清零之后,判断实际写的setup长度与(SETUP_SECTS4*512)的数值差,若setup
// 长度小于该长度(4*512字节),则用NULL字符将setup填足为4*512字节。
173 for (c=0 ; c<sizeof(buf) ; c++)
174 buf[c] = '\0';
175 while (i<SETUP_SECTS*512) {
176 c = SETUP_SECTS*512-i;
177 if (c > sizeof(buf))
178 c = sizeof(buf);
179 if (write(1,buf,c) != c)
180 die("Write call failed");
181 i += c;
182 }
183
// 下面开始处理system模块文件。该文件使用gas编译,因此具有GNU a.out目标文件格式。
// 首先以只读方式打开文件,并读取其中a.out格式头部结构信息(1KB长度)。在判断system
// 是一个有效的a.out格式文件之后,就把该文件随后的所有数据都写到标准输出(Image文件)
// 中,并关闭该文件。然后显示system模块的长度。若system代码和数据长度超过SYS_SIZE节
// (即128KB字节),则显示出错信息并退出。若无错,则返回0,表示正常退出。
184 if ((id=open(argv[3],O_RDONLY,0))<0)
185 die("Unable to open 'system'");
186 if (read(id,buf,GCC_HEADER) != GCC_HEADER)
187 die("Unable to read header of 'system'");
188 if (((long *) buf)[5] != 0) // 执行入口点字段a_entry值应为0。
189 die("Non-GCC header of 'system'");
190 for (i=0 ; (c=read(id,buf,sizeof buf))>0 ; i+=c )
191 if (write(1,buf,c)!=c)
192 die("Write call failed");
193 close(id);
194 fprintf(stderr,"System is %d bytes.\n",i);
195 if (i > SYS_SIZE*16)
196 die("System is too big");
197 return(0);
198 }
199